root/arch/powerpc/sysdev/xics/icp-hv.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright 2011 IBM Corporation.
 */
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/smp.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/cpu.h>
#include <linux/of.h>

#include <asm/smp.h>
#include <asm/irq.h>
#include <asm/errno.h>
#include <asm/xics.h>
#include <asm/io.h>
#include <asm/hvcall.h>

static inline unsigned int icp_hv_get_xirr(unsigned char cppr)
{
        unsigned long retbuf[PLPAR_HCALL_BUFSIZE];
        long rc;
        unsigned int ret = XICS_IRQ_SPURIOUS;

        rc = plpar_hcall(H_XIRR, retbuf, cppr);
        if (rc == H_SUCCESS) {
                ret = (unsigned int)retbuf[0];
        } else {
                pr_err("%s: bad return code xirr cppr=0x%x returned %ld\n",
                        __func__, cppr, rc);
                WARN_ON_ONCE(1);
        }

        return ret;
}

static inline void icp_hv_set_cppr(u8 value)
{
        long rc = plpar_hcall_norets(H_CPPR, value);
        if (rc != H_SUCCESS) {
                pr_err("%s: bad return code cppr cppr=0x%x returned %ld\n",
                        __func__, value, rc);
                WARN_ON_ONCE(1);
        }
}

static inline void icp_hv_set_xirr(unsigned int value)
{
        long rc = plpar_hcall_norets(H_EOI, value);
        if (rc != H_SUCCESS) {
                pr_err("%s: bad return code eoi xirr=0x%x returned %ld\n",
                        __func__, value, rc);
                WARN_ON_ONCE(1);
                icp_hv_set_cppr(value >> 24);
        }
}

static inline void icp_hv_set_qirr(int n_cpu , u8 value)
{
        int hw_cpu = get_hard_smp_processor_id(n_cpu);
        long rc;

        /* Make sure all previous accesses are ordered before IPI sending */
        mb();
        rc = plpar_hcall_norets(H_IPI, hw_cpu, value);
        if (rc != H_SUCCESS) {
                pr_err("%s: bad return code qirr cpu=%d hw_cpu=%d mfrr=0x%x "
                        "returned %ld\n", __func__, n_cpu, hw_cpu, value, rc);
                WARN_ON_ONCE(1);
        }
}

static void icp_hv_eoi(struct irq_data *d)
{
        unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d);

        iosync();
        icp_hv_set_xirr((xics_pop_cppr() << 24) | hw_irq);
}

static void icp_hv_teardown_cpu(void)
{
        int cpu = smp_processor_id();

        /* Clear any pending IPI */
        icp_hv_set_qirr(cpu, 0xff);
}

static void icp_hv_flush_ipi(void)
{
        /* We take the ipi irq but and never return so we
         * need to EOI the IPI, but want to leave our priority 0
         *
         * should we check all the other interrupts too?
         * should we be flagging idle loop instead?
         * or creating some task to be scheduled?
         */

        icp_hv_set_xirr((0x00 << 24) | XICS_IPI);
}

static unsigned int icp_hv_get_irq(void)
{
        unsigned int xirr = icp_hv_get_xirr(xics_cppr_top());
        unsigned int vec = xirr & 0x00ffffff;
        unsigned int irq;

        if (vec == XICS_IRQ_SPURIOUS)
                return 0;

        irq = irq_find_mapping(xics_host, vec);
        if (likely(irq)) {
                xics_push_cppr(vec);
                return irq;
        }

        /* We don't have a linux mapping, so have rtas mask it. */
        xics_mask_unknown_vec(vec);

        /* We might learn about it later, so EOI it */
        icp_hv_set_xirr(xirr);

        return 0;
}

static void icp_hv_set_cpu_priority(unsigned char cppr)
{
        xics_set_base_cppr(cppr);
        icp_hv_set_cppr(cppr);
        iosync();
}

#ifdef CONFIG_SMP

static void icp_hv_cause_ipi(int cpu)
{
        icp_hv_set_qirr(cpu, IPI_PRIORITY);
}

static irqreturn_t icp_hv_ipi_action(int irq, void *dev_id)
{
        int cpu = smp_processor_id();

        icp_hv_set_qirr(cpu, 0xff);

        return smp_ipi_demux();
}

#endif /* CONFIG_SMP */

static const struct icp_ops icp_hv_ops = {
        .get_irq        = icp_hv_get_irq,
        .eoi            = icp_hv_eoi,
        .set_priority   = icp_hv_set_cpu_priority,
        .teardown_cpu   = icp_hv_teardown_cpu,
        .flush_ipi      = icp_hv_flush_ipi,
#ifdef CONFIG_SMP
        .ipi_action     = icp_hv_ipi_action,
        .cause_ipi      = icp_hv_cause_ipi,
#endif
};

int __init icp_hv_init(void)
{
        struct device_node *np;

        np = of_find_compatible_node(NULL, NULL, "ibm,ppc-xicp");
        if (!np)
                np = of_find_node_by_type(NULL,
                                    "PowerPC-External-Interrupt-Presentation");
        if (!np)
                return -ENODEV;

        icp_ops = &icp_hv_ops;

        of_node_put(np);
        return 0;
}