/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-08-24     GuEe-GUI     first version
 */

#include <rthw.h>
#include <rtthread.h>

#define DBG_TAG "rtdm.pic"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>

#include <drivers/pic.h>

struct irq_traps
{
    rt_list_t list;

    void *data;
    rt_bool_t (*handler)(void *);
};

static int _ipi_hash[] =
{
#ifdef RT_USING_SMP
    [RT_SCHEDULE_IPI] = RT_SCHEDULE_IPI,
    [RT_STOP_IPI] = RT_STOP_IPI,
#endif
};

/* reserved ipi */
static int _pirq_hash_idx = RT_ARRAY_SIZE(_ipi_hash);
static struct rt_pic_irq _pirq_hash[MAX_HANDLERS] =
{
    [0 ... MAX_HANDLERS - 1] =
    {
        .irq = -1,
        .hwirq = -1,
        .mode = RT_IRQ_MODE_NONE,
        .priority = RT_UINT32_MAX,
        .rw_lock = { },
    }
};

static struct rt_spinlock _pic_lock = { };
static rt_size_t _pic_name_max = sizeof("PIC");
static rt_list_t _pic_nodes = RT_LIST_OBJECT_INIT(_pic_nodes);
static rt_list_t _traps_nodes = RT_LIST_OBJECT_INIT(_traps_nodes);

static struct rt_pic_irq *irq2pirq(int irq)
{
    struct rt_pic_irq *pirq = RT_NULL;

    if ((irq >= 0) && (irq < MAX_HANDLERS))
    {
        pirq = &_pirq_hash[irq];

        if (pirq->irq < 0)
        {
            pirq = RT_NULL;
        }
    }

    if (!pirq)
    {
        LOG_E("irq = %d is invalid", irq);
    }

    return pirq;
}

static void append_pic(struct rt_pic *pic)
{
    int pic_name_len = rt_strlen(pic->ops->name);

    rt_list_insert_before(&_pic_nodes, &pic->list);

    if (pic_name_len > _pic_name_max)
    {
        _pic_name_max = pic_name_len;
    }
}

void rt_pic_default_name(struct rt_pic *pic)
{
    if (pic)
    {
    #if RT_NAME_MAX > 0
        rt_strncpy(pic->parent.name, "PIC", RT_NAME_MAX);
    #else
        pic->parent.name = "PIC";
    #endif
    }
}

struct rt_pic *rt_pic_dynamic_cast(void *ptr)
{
    struct rt_pic *pic = RT_NULL, *tmp = RT_NULL;

    if (ptr)
    {
        struct rt_object *obj = ptr;

        if (obj->type == RT_Object_Class_Unknown)
        {
            tmp = (void *)obj;
        }
        else if (obj->type == RT_Object_Class_Device)
        {
            tmp = (void *)obj + sizeof(struct rt_device);
        }
        else
        {
            tmp = (void *)obj + sizeof(struct rt_object);
        }

        if (tmp && !rt_strcmp(tmp->parent.name, "PIC"))
        {
            pic = tmp;
        }
    }

    return pic;
}

rt_err_t rt_pic_linear_irq(struct rt_pic *pic, rt_size_t irq_nr)
{
    rt_err_t err = RT_EOK;

    if (pic && pic->ops && pic->ops->name)
    {
        rt_ubase_t level = rt_spin_lock_irqsave(&_pic_lock);

        if (_pirq_hash_idx + irq_nr <= RT_ARRAY_SIZE(_pirq_hash))
        {
            rt_list_init(&pic->list);

            rt_pic_default_name(pic);
            pic->parent.type = RT_Object_Class_Unknown;

            pic->irq_start = _pirq_hash_idx;
            pic->irq_nr = irq_nr;
            pic->pirqs = &_pirq_hash[_pirq_hash_idx];

            _pirq_hash_idx += irq_nr;

            append_pic(pic);

            LOG_D("%s alloc irqs ranges [%d, %d]", pic->ops->name,
                    pic->irq_start, pic->irq_start + pic->irq_nr);
        }
        else
        {
            LOG_E("%s alloc %d irqs is overflow", pic->ops->name, irq_nr);

            err = -RT_EEMPTY;
        }

        rt_spin_unlock_irqrestore(&_pic_lock, level);
    }
    else
    {
        err = -RT_EINVAL;
    }

    return err;
}

static void config_pirq(struct rt_pic *pic, struct rt_pic_irq *pirq, int irq, int hwirq)
{
    rt_ubase_t level = rt_spin_lock_irqsave(&pirq->rw_lock);

    pirq->irq = irq;
    pirq->hwirq = hwirq;

    pirq->pic = pic;

    rt_list_init(&pirq->list);
    rt_list_init(&pirq->children_nodes);
    rt_list_init(&pirq->isr.list);

    rt_spin_unlock_irqrestore(&pirq->rw_lock, level);
}

int rt_pic_config_ipi(struct rt_pic *pic, int ipi_index, int hwirq)
{
    int ipi = ipi_index;

    if (pic && ipi < RT_ARRAY_SIZE(_ipi_hash) && hwirq >= 0 && pic->ops->irq_send_ipi)
    {
        config_pirq(pic, &_pirq_hash[ipi], ipi, hwirq);

        LOG_D("%s config %s %d to hwirq %d", pic->ops->name, "ipi", ipi, hwirq);
    }
    else
    {
        ipi = -RT_EINVAL;
    }

    return ipi;
}

int rt_pic_config_irq(struct rt_pic *pic, int irq_index, int hwirq)
{
    int irq;

    if (pic && hwirq >= 0)
    {
        irq = pic->irq_start + irq_index;

        if (irq >= 0 && irq < MAX_HANDLERS)
        {
            config_pirq(pic, &_pirq_hash[irq], irq, hwirq);

            LOG_D("%s config %s %d to hwirq %d", pic->ops->name, "irq", irq, hwirq);
        }
        else
        {
            irq = -RT_ERROR;
        }
    }
    else
    {
        irq = -RT_EINVAL;
    }

    return irq;
}

struct rt_pic_irq *rt_pic_find_ipi(struct rt_pic *pic, int ipi_index)
{
    struct rt_pic_irq *pirq = &_pirq_hash[ipi_index];

    RT_ASSERT(ipi_index < RT_ARRAY_SIZE(_ipi_hash));
    RT_ASSERT(pirq->pic == pic);

    return pirq;
}

struct rt_pic_irq *rt_pic_find_pirq(struct rt_pic *pic, int irq)
{
    if (pic && irq >= pic->irq_start && irq <= pic->irq_start + pic->irq_nr)
    {
        return &pic->pirqs[irq - pic->irq_start];
    }

    return RT_NULL;
}

rt_err_t rt_pic_cascade(struct rt_pic_irq *pirq, int parent_irq)
{
    rt_err_t err = RT_EOK;

    if (pirq && !pirq->parent && parent_irq >= 0)
    {
        struct rt_pic_irq *parent;

        rt_spin_lock(&pirq->rw_lock);

        parent = irq2pirq(parent_irq);

        if (parent)
        {
            pirq->parent = parent;
            pirq->priority = parent->priority;
            rt_memcpy(&pirq->affinity, &parent->affinity, sizeof(pirq->affinity));
        }

        rt_spin_unlock(&pirq->rw_lock);

        if (parent && pirq->pic->ops->flags & RT_PIC_F_IRQ_ROUTING)
        {
            rt_spin_lock(&parent->rw_lock);

            rt_list_insert_before(&parent->children_nodes, &pirq->list);

            rt_spin_unlock(&parent->rw_lock);
        }
    }
    else
    {
        err = -RT_EINVAL;
    }

    return err;
}

rt_err_t rt_pic_uncascade(struct rt_pic_irq *pirq)
{
    rt_err_t err = RT_EOK;

    if (pirq && pirq->parent)
    {
        struct rt_pic_irq *parent;

        rt_spin_lock(&pirq->rw_lock);

        parent = pirq->parent;
        pirq->parent = RT_NULL;

        rt_spin_unlock(&pirq->rw_lock);

        if (parent && pirq->pic->ops->flags & RT_PIC_F_IRQ_ROUTING)
        {
            rt_spin_lock(&parent->rw_lock);

            rt_list_remove(&pirq->list);

            rt_spin_unlock(&parent->rw_lock);
        }
    }
    else
    {
        err = -RT_EINVAL;
    }

    return err;
}

rt_err_t rt_pic_attach_irq(int irq, rt_isr_handler_t handler, void *uid, const char *name, int flags)
{
    rt_err_t err = -RT_EINVAL;
    struct rt_pic_irq *pirq;

    if (handler && name && (pirq = irq2pirq(irq)))
    {
        struct rt_pic_isr *isr = RT_NULL;
        rt_ubase_t level = rt_spin_lock_irqsave(&pirq->rw_lock);

        err = RT_EOK;

        if (!pirq->isr.action.handler)
        {
            /* first attach */
            isr = &pirq->isr;
            rt_list_init(&isr->list);
        }
        else
        {
            rt_spin_unlock_irqrestore(&pirq->rw_lock, level);

            if ((isr = rt_malloc(sizeof(*isr))))
            {
                rt_list_init(&isr->list);

                level = rt_spin_lock_irqsave(&pirq->rw_lock);

                rt_list_insert_after(&pirq->isr.list, &isr->list);
            }
            else
            {
                LOG_E("No memory to save '%s' isr", name);
                err = -RT_ERROR;
            }
        }

        if (!err)
        {
            isr->flags = flags;
            isr->action.handler = handler;
            isr->action.param = uid;
        #ifdef RT_USING_INTERRUPT_INFO
            isr->action.counter = 0;
            rt_strncpy(isr->action.name, name, RT_NAME_MAX);
        #endif

            rt_spin_unlock_irqrestore(&pirq->rw_lock, level);
        }
    }

    return err;
}

rt_err_t rt_pic_detach_irq(int irq, void *uid)
{
    rt_err_t err = -RT_EINVAL;
    struct rt_pic_irq *pirq = irq2pirq(irq);

    if (pirq)
    {
        rt_bool_t will_free = RT_FALSE;
        struct rt_pic_isr *isr = RT_NULL;
        rt_ubase_t level = rt_spin_lock_irqsave(&pirq->rw_lock);

        isr = &pirq->isr;

        if (isr->action.param == uid)
        {
            if (rt_list_isempty(&isr->list))
            {
                isr->action.handler = RT_NULL;
                isr->action.param = RT_NULL;
            }
            else
            {
                struct rt_pic_isr *next_isr = rt_list_entry(isr->list.next, struct rt_pic_isr, list);

                rt_list_remove(&next_isr->list);

                isr->action.handler = next_isr->action.handler;
                isr->action.param = next_isr->action.param;
            #ifdef RT_USING_INTERRUPT_INFO
                isr->action.counter = next_isr->action.counter;
                rt_strncpy(isr->action.name, next_isr->action.name, RT_NAME_MAX);
            #endif

                isr = next_isr;
                will_free = RT_TRUE;
            }

            err = RT_EOK;
        }
        else
        {
            rt_list_for_each_entry(isr, &pirq->isr.list, list)
            {
                if (isr->action.param == uid)
                {
                    err = RT_EOK;

                    will_free = RT_TRUE;
                    rt_list_remove(&isr->list);
                    break;
                }
            }
        }

        rt_spin_unlock_irqrestore(&pirq->rw_lock, level);

        if (will_free)
        {
            rt_free(isr);
        }
    }

    return err;
}

rt_err_t rt_pic_add_traps(rt_bool_t (*handler)(void *), void *data)
{
    rt_err_t err = -RT_EINVAL;

    if (handler)
    {
        struct irq_traps *traps = rt_malloc(sizeof(*traps));

        if (traps)
        {
            rt_ubase_t level = rt_hw_interrupt_disable();

            rt_list_init(&traps->list);

            traps->data = data;
            traps->handler = handler;

            rt_list_insert_before(&_traps_nodes, &traps->list);
            err = RT_EOK;

            rt_hw_interrupt_enable(level);
        }
        else
        {
            LOG_E("No memory to save '%p' handler", handler);
            err = -RT_ENOMEM;
        }
    }

    return err;
}

rt_err_t rt_pic_do_traps(void)
{
    rt_err_t err = -RT_ERROR;
    struct irq_traps *traps;

    rt_list_for_each_entry(traps, &_traps_nodes, list)
    {
        if (traps->handler(traps->data))
        {
            err = RT_EOK;

            break;
        }
    }

    return err;
}

rt_err_t rt_pic_handle_isr(struct rt_pic_irq *pirq)
{
    rt_err_t err = -RT_EEMPTY;
    rt_list_t *handler_nodes;
    struct rt_irq_desc *action;

    RT_ASSERT(pirq != RT_NULL);
    RT_ASSERT(pirq->pic != RT_NULL);

    /* Corrected irq affinity */
    rt_bitmap_set_bit(pirq->affinity, rt_hw_cpu_id());

    handler_nodes = &pirq->isr.list;
    action = &pirq->isr.action;

    if (!rt_list_isempty(&pirq->children_nodes))
    {
        struct rt_pic_irq *child;

        rt_list_for_each_entry(child, &pirq->children_nodes, list)
        {
            rt_pic_irq_ack(child->irq);

            err = rt_pic_handle_isr(child);

            rt_pic_irq_eoi(child->irq);
        }
    }

    if (action->handler)
    {
        action->handler(pirq->irq, action->param);
    #ifdef RT_USING_INTERRUPT_INFO
        action->counter++;
    #endif

        if (!rt_list_isempty(handler_nodes))
        {
            struct rt_pic_isr *isr;

            rt_list_for_each_entry(isr, handler_nodes, list)
            {
                action = &isr->action;

                RT_ASSERT(action->handler != RT_NULL);

                action->handler(pirq->irq, action->param);
            #ifdef RT_USING_INTERRUPT_INFO
                action->counter++;
            #endif
            }
        }

        err = RT_EOK;
    }

    return err;
}

rt_weak rt_err_t rt_pic_user_extends(struct rt_pic *pic)
{
    return -RT_ENOSYS;
}

rt_err_t rt_pic_irq_init(void)
{
    rt_err_t err = RT_EOK;
    struct rt_pic *pic;

    rt_list_for_each_entry(pic, &_pic_nodes, list)
    {
        if (pic->ops->irq_init)
        {
            err = pic->ops->irq_init(pic);

            if (err)
            {
                LOG_E("PIC = %s init fail", pic->ops->name);
                break;
            }
        }
    }

    return err;
}

rt_err_t rt_pic_irq_finit(void)
{
    rt_err_t err = RT_EOK;
    struct rt_pic *pic;

    rt_list_for_each_entry(pic, &_pic_nodes, list)
    {
        if (pic->ops->irq_finit)
        {
            err = pic->ops->irq_finit(pic);

            if (err)
            {
                LOG_E("PIC = %s finit fail", pic->ops->name);
                break;
            }
        }
    }

    return err;
}

void rt_pic_irq_enable(int irq)
{
    struct rt_pic_irq *pirq = irq2pirq(irq);

    RT_ASSERT(pirq != RT_NULL);

    rt_hw_spin_lock(&pirq->rw_lock.lock);

    if (pirq->pic->ops->irq_enable)
    {
        pirq->pic->ops->irq_enable(pirq);
    }

    rt_hw_spin_unlock(&pirq->rw_lock.lock);
}

void rt_pic_irq_disable(int irq)
{
    struct rt_pic_irq *pirq = irq2pirq(irq);

    RT_ASSERT(pirq != RT_NULL);

    rt_hw_spin_lock(&pirq->rw_lock.lock);

    if (pirq->pic->ops->irq_disable)
    {
        pirq->pic->ops->irq_disable(pirq);
    }

    rt_hw_spin_unlock(&pirq->rw_lock.lock);
}

void rt_pic_irq_ack(int irq)
{
    struct rt_pic_irq *pirq = irq2pirq(irq);

    RT_ASSERT(pirq != RT_NULL);

    rt_hw_spin_lock(&pirq->rw_lock.lock);

    if (pirq->pic->ops->irq_ack)
    {
        pirq->pic->ops->irq_ack(pirq);
    }

    rt_hw_spin_unlock(&pirq->rw_lock.lock);
}

void rt_pic_irq_mask(int irq)
{
    struct rt_pic_irq *pirq = irq2pirq(irq);

    RT_ASSERT(pirq != RT_NULL);

    rt_hw_spin_lock(&pirq->rw_lock.lock);

    if (pirq->pic->ops->irq_mask)
    {
        pirq->pic->ops->irq_mask(pirq);
    }

    rt_hw_spin_unlock(&pirq->rw_lock.lock);
}

void rt_pic_irq_unmask(int irq)
{
    struct rt_pic_irq *pirq = irq2pirq(irq);

    RT_ASSERT(pirq != RT_NULL);

    rt_hw_spin_lock(&pirq->rw_lock.lock);

    if (pirq->pic->ops->irq_unmask)
    {
        pirq->pic->ops->irq_unmask(pirq);
    }

    rt_hw_spin_unlock(&pirq->rw_lock.lock);
}

void rt_pic_irq_eoi(int irq)
{
    struct rt_pic_irq *pirq = irq2pirq(irq);

    RT_ASSERT(pirq != RT_NULL);

    rt_hw_spin_lock(&pirq->rw_lock.lock);

    if (pirq->pic->ops->irq_eoi)
    {
        pirq->pic->ops->irq_eoi(pirq);
    }

    rt_hw_spin_unlock(&pirq->rw_lock.lock);
}

rt_err_t rt_pic_irq_set_priority(int irq, rt_uint32_t priority)
{
    rt_err_t err = -RT_EINVAL;
    struct rt_pic_irq *pirq = irq2pirq(irq);

    if (pirq)
    {
        rt_hw_spin_lock(&pirq->rw_lock.lock);

        if (pirq->pic->ops->irq_set_priority)
        {
            err = pirq->pic->ops->irq_set_priority(pirq, priority);

            if (!err)
            {
                pirq->priority = priority;
            }
        }
        else
        {
            err = -RT_ENOSYS;
        }

        rt_hw_spin_unlock(&pirq->rw_lock.lock);
    }

    return err;
}

rt_uint32_t rt_pic_irq_get_priority(int irq)
{
    rt_uint32_t priority = RT_UINT32_MAX;
    struct rt_pic_irq *pirq = irq2pirq(irq);

    if (pirq)
    {
        rt_hw_spin_lock(&pirq->rw_lock.lock);

        priority = pirq->priority;

        rt_hw_spin_unlock(&pirq->rw_lock.lock);
    }

    return priority;
}

rt_err_t rt_pic_irq_set_affinity(int irq, rt_bitmap_t *affinity)
{
    rt_err_t err = -RT_EINVAL;
    struct rt_pic_irq *pirq;

    if (affinity && (pirq = irq2pirq(irq)))
    {
        rt_hw_spin_lock(&pirq->rw_lock.lock);

        if (pirq->pic->ops->irq_set_affinity)
        {
            err = pirq->pic->ops->irq_set_affinity(pirq, affinity);

            if (!err)
            {
                rt_memcpy(pirq->affinity, affinity, sizeof(pirq->affinity));
            }
        }
        else
        {
            err = -RT_ENOSYS;
        }

        rt_hw_spin_unlock(&pirq->rw_lock.lock);
    }

    return err;
}

rt_err_t rt_pic_irq_get_affinity(int irq, rt_bitmap_t *out_affinity)
{
    rt_err_t err = -RT_EINVAL;
    struct rt_pic_irq *pirq;

    if (out_affinity && (pirq = irq2pirq(irq)))
    {
        rt_hw_spin_lock(&pirq->rw_lock.lock);

        rt_memcpy(out_affinity, pirq->affinity, sizeof(pirq->affinity));
        err = RT_EOK;

        rt_hw_spin_unlock(&pirq->rw_lock.lock);
    }

    return err;
}

rt_err_t rt_pic_irq_set_triger_mode(int irq, rt_uint32_t mode)
{
    rt_err_t err = -RT_EINVAL;
    struct rt_pic_irq *pirq;

    if ((~mode & RT_IRQ_MODE_MASK) && (pirq = irq2pirq(irq)))
    {
        rt_hw_spin_lock(&pirq->rw_lock.lock);

        if (pirq->pic->ops->irq_set_triger_mode)
        {
            err = pirq->pic->ops->irq_set_triger_mode(pirq, mode);

            if (!err)
            {
                pirq->mode = mode;
            }
        }
        else
        {
            err = -RT_ENOSYS;
        }

        rt_hw_spin_unlock(&pirq->rw_lock.lock);
    }

    return err;
}

rt_uint32_t rt_pic_irq_get_triger_mode(int irq)
{
    rt_uint32_t mode = RT_UINT32_MAX;
    struct rt_pic_irq *pirq = irq2pirq(irq);

    if (pirq)
    {
        rt_hw_spin_lock(&pirq->rw_lock.lock);

        mode = pirq->mode;

        rt_hw_spin_unlock(&pirq->rw_lock.lock);
    }

    return mode;
}

void rt_pic_irq_send_ipi(int irq, rt_bitmap_t *cpumask)
{
    struct rt_pic_irq *pirq;

    if (cpumask && (pirq = irq2pirq(irq)))
    {
        rt_hw_spin_lock(&pirq->rw_lock.lock);

        if (pirq->pic->ops->irq_send_ipi)
        {
            pirq->pic->ops->irq_send_ipi(pirq, cpumask);
        }

        rt_hw_spin_unlock(&pirq->rw_lock.lock);
    }
}

void rt_pic_irq_parent_enable(struct rt_pic_irq *pirq)
{
    RT_ASSERT(pirq != RT_NULL);
    pirq = pirq->parent;

    if (pirq->pic->ops->irq_enable)
    {
        pirq->pic->ops->irq_enable(pirq);
    }
}

void rt_pic_irq_parent_disable(struct rt_pic_irq *pirq)
{
    RT_ASSERT(pirq != RT_NULL);
    pirq = pirq->parent;

    if (pirq->pic->ops->irq_disable)
    {
        pirq->pic->ops->irq_disable(pirq);
    }
}

void rt_pic_irq_parent_ack(struct rt_pic_irq *pirq)
{
    RT_ASSERT(pirq != RT_NULL);
    pirq = pirq->parent;

    if (pirq->pic->ops->irq_ack)
    {
        pirq->pic->ops->irq_ack(pirq);
    }
}

void rt_pic_irq_parent_mask(struct rt_pic_irq *pirq)
{
    RT_ASSERT(pirq != RT_NULL);
    pirq = pirq->parent;

    if (pirq->pic->ops->irq_mask)
    {
        pirq->pic->ops->irq_mask(pirq);
    }
}

void rt_pic_irq_parent_unmask(struct rt_pic_irq *pirq)
{
    RT_ASSERT(pirq != RT_NULL);
    pirq = pirq->parent;

    if (pirq->pic->ops->irq_unmask)
    {
        pirq->pic->ops->irq_unmask(pirq);
    }
}

void rt_pic_irq_parent_eoi(struct rt_pic_irq *pirq)
{
    RT_ASSERT(pirq != RT_NULL);
    pirq = pirq->parent;

    if (pirq->pic->ops->irq_eoi)
    {
        pirq->pic->ops->irq_eoi(pirq);
    }
}

rt_err_t rt_pic_irq_parent_set_priority(struct rt_pic_irq *pirq, rt_uint32_t priority)
{
    rt_err_t err = -RT_ENOSYS;

    RT_ASSERT(pirq != RT_NULL);
    pirq = pirq->parent;

    if (pirq->pic->ops->irq_set_priority)
    {
        if (!(err = pirq->pic->ops->irq_set_priority(pirq, priority)))
        {
            pirq->priority = priority;
        }
    }

    return err;
}

rt_err_t rt_pic_irq_parent_set_affinity(struct rt_pic_irq *pirq, rt_bitmap_t *affinity)
{
    rt_err_t err = -RT_ENOSYS;

    RT_ASSERT(pirq != RT_NULL);
    pirq = pirq->parent;

    if (pirq->pic->ops->irq_set_affinity)
    {
        if (!(err = pirq->pic->ops->irq_set_affinity(pirq, affinity)))
        {
            rt_memcpy(pirq->affinity, affinity, sizeof(pirq->affinity));
        }
    }

    return err;
}

rt_err_t rt_pic_irq_parent_set_triger_mode(struct rt_pic_irq *pirq, rt_uint32_t mode)
{
    rt_err_t err = -RT_ENOSYS;

    RT_ASSERT(pirq != RT_NULL);
    pirq = pirq->parent;

    if (pirq->pic->ops->irq_set_triger_mode)
    {
        if (!(err = pirq->pic->ops->irq_set_triger_mode(pirq, mode)))
        {
            pirq->mode = mode;
        }
    }

    return err;
}

#ifdef RT_USING_OFW
RT_OFW_STUB_RANGE_EXPORT(pic, _pic_ofw_start, _pic_ofw_end);

static rt_err_t ofw_pic_init(void)
{
    struct rt_ofw_node *ic_np;

    rt_ofw_foreach_node_by_prop(ic_np, "interrupt-controller")
    {
        rt_ofw_stub_probe_range(ic_np, &_pic_ofw_start, &_pic_ofw_end);
    }

    return RT_EOK;
}
#else
static rt_err_t ofw_pic_init(void)
{
    return RT_EOK;
}
#endif /* !RT_USING_OFW */

rt_err_t rt_pic_init(void)
{
    rt_err_t err;

    LOG_D("init start");

    err = ofw_pic_init();

    LOG_D("init end");

    return err;
}

#if defined(RT_USING_CONSOLE) && defined(RT_USING_MSH)
static int list_irq(int argc, char**argv)
{
    rt_ubase_t level;
    rt_size_t irq_nr = 0;
    rt_bool_t dump_all = RT_FALSE;
    const char *const irq_modes[] =
    {
        [RT_IRQ_MODE_NONE] = "None",
        [RT_IRQ_MODE_EDGE_RISING] = "Edge-Rising",
        [RT_IRQ_MODE_EDGE_FALLING] = "Edge-Falling",
        [RT_IRQ_MODE_EDGE_BOTH] = "Edge-Both",
        [RT_IRQ_MODE_LEVEL_HIGH] = "Level-High",
        [RT_IRQ_MODE_LEVEL_LOW] = "Level-Low",
    };
    static char info[RT_CONSOLEBUF_SIZE];
#ifdef RT_USING_SMP
    static char cpumask[RT_CPUS_NR + 1] = { [RT_CPUS_NR] = '\0' };
#endif

    if (argc > 1)
    {
        if (!rt_strcmp(argv[1], "all"))
        {
            dump_all = RT_TRUE;
        }
    }

    level = rt_hw_interrupt_disable();

    rt_kprintf("%-*.s %-*.s %s %-*.s %-*.s %-*.s %-*.sUsers%s\n",
            10, "IRQ",
            10, "HW-IRQ",
            "MSI",
            _pic_name_max, "PIC",
            12, "Mode",
        #ifdef RT_USING_SMP
            RT_CPUS_NR, "CPUs",
        #else
            0, 0,
        #endif
        #ifdef RT_USING_INTERRUPT_INFO
            11, "Count",
            ""
        #else
            0, 0,
            "-Number"
        #endif
            );

    for (int i = 0; i < RT_ARRAY_SIZE(_pirq_hash); ++i)
    {
        struct rt_pic_irq *pirq = &_pirq_hash[i];

        if (!pirq->pic || !(dump_all || pirq->isr.action.handler))
        {
            continue;
        }

        rt_snprintf(info, sizeof(info), "%-10d %-10d %c   %-*.s %-*.s ",
                pirq->irq,
                pirq->hwirq,
                pirq->msi_desc ? 'Y' : 'N',
                _pic_name_max, pirq->pic->ops->name,
                12, irq_modes[pirq->mode]);

    #ifdef RT_USING_SMP
        for (int group = 0, id = 0; group < RT_ARRAY_SIZE(pirq->affinity); ++group)
        {
            rt_bitmap_t mask = pirq->affinity[group];

            for (int idx = 0; id < RT_CPUS_NR && idx < RT_BITMAP_BIT_LEN(1); ++idx, ++id)
            {
                cpumask[RT_ARRAY_SIZE(cpumask) - id - 2] = '0' + ((mask >> idx) & 1);
            }
        }
    #endif /* RT_USING_SMP */

        rt_kputs(info);
    #ifdef RT_USING_SMP
        rt_kputs(cpumask);
    #endif

    #ifdef RT_USING_INTERRUPT_INFO
        rt_kprintf(" %-10d ", pirq->isr.action.counter);
        rt_kputs(pirq->isr.action.name);
        rt_kputs("\n");

        if (!rt_list_isempty(&pirq->isr.list))
        {
            struct rt_pic_isr *repeat_isr;

            rt_list_for_each_entry(repeat_isr, &pirq->isr.list, list)
            {
                rt_kputs(info);
            #ifdef RT_USING_SMP
                rt_kputs(cpumask);
            #endif
                rt_kprintf("%-10d ", repeat_isr->action.counter);
                rt_kputs(repeat_isr->action.name);
                rt_kputs("\n");
            }
        }
    #else
        rt_kprintf(" %d\n", rt_list_len(&pirq->isr.list));
    #endif

        ++irq_nr;
    }

    rt_hw_interrupt_enable(level);

    rt_kprintf("%d IRQs found\n", irq_nr);

    return 0;
}
MSH_CMD_EXPORT(list_irq, dump using or args = all of irq information);
#endif /* RT_USING_CONSOLE && RT_USING_MSH */
