aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/core/interrupts.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/core/interrupts.c')
-rw-r--r--roms/skiboot/core/interrupts.c513
1 files changed, 513 insertions, 0 deletions
diff --git a/roms/skiboot/core/interrupts.c b/roms/skiboot/core/interrupts.c
new file mode 100644
index 000000000..0a617d385
--- /dev/null
+++ b/roms/skiboot/core/interrupts.c
@@ -0,0 +1,513 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * Excuse me, you do work for me now?
+ *
+ * Copyright 2013-2019 IBM Corp.
+ */
+
+#include <skiboot.h>
+#include <chip.h>
+#include <cpu.h>
+#include <fsp.h>
+#include <interrupts.h>
+#include <opal.h>
+#include <io.h>
+#include <cec.h>
+#include <device.h>
+#include <ccan/str/str.h>
+#include <timer.h>
+#include <sbe-p8.h>
+#include <sbe-p9.h>
+
+/* ICP registers */
+#define ICP_XIRR 0x4 /* 32-bit access */
+#define ICP_CPPR 0x4 /* 8-bit access */
+#define ICP_MFRR 0xc /* 8-bit access */
+
+static LIST_HEAD(irq_sources);
+static LIST_HEAD(irq_sources2);
+static struct lock irq_lock = LOCK_UNLOCKED;
+
+void __register_irq_source(struct irq_source *is, bool secondary)
+{
+ struct irq_source *is1;
+ struct list_head *list = secondary ? &irq_sources2 : &irq_sources;
+
+ prlog(PR_DEBUG, "IRQ: Registering %04x..%04x ops @%p (data %p)%s\n",
+ is->start, is->end - 1, is->ops, is->data,
+ secondary ? " [secondary]" : "");
+
+ lock(&irq_lock);
+ list_for_each(list, is1, link) {
+ if (is->end > is1->start && is->start < is1->end) {
+ prerror("register IRQ source overlap !\n");
+ prerror(" new: %x..%x old: %x..%x\n",
+ is->start, is->end - 1,
+ is1->start, is1->end - 1);
+ assert(0);
+ }
+ }
+ list_add_tail(list, &is->link);
+ unlock(&irq_lock);
+}
+
+void register_irq_source(const struct irq_source_ops *ops, void *data,
+ uint32_t start, uint32_t count)
+{
+ struct irq_source *is;
+
+ is = zalloc(sizeof(struct irq_source));
+ assert(is);
+ is->start = start;
+ is->end = start + count;
+ is->ops = ops;
+ is->data = data;
+
+ __register_irq_source(is, false);
+}
+
+void unregister_irq_source(uint32_t start, uint32_t count)
+{
+ struct irq_source *is;
+
+ /* Note: We currently only unregister from the primary sources */
+ lock(&irq_lock);
+ list_for_each(&irq_sources, is, link) {
+ if (start >= is->start && start < is->end) {
+ if (start != is->start ||
+ count != (is->end - is->start)) {
+ prerror("unregister IRQ source mismatch !\n");
+ prerror("start:%x, count: %x match: %x..%x\n",
+ start, count, is->start, is->end);
+ assert(0);
+ }
+ list_del(&is->link);
+ unlock(&irq_lock);
+ /* XXX Add synchronize / RCU */
+ free(is);
+ return;
+ }
+ }
+ unlock(&irq_lock);
+ prerror("unregister IRQ source not found !\n");
+ prerror("start:%x, count: %x\n", start, count);
+ assert(0);
+}
+
+struct irq_source *irq_find_source(uint32_t isn)
+{
+ struct irq_source *is;
+
+ lock(&irq_lock);
+ /*
+ * XXX This really needs some kind of caching !
+ */
+ list_for_each(&irq_sources, is, link) {
+ if (isn >= is->start && isn < is->end) {
+ unlock(&irq_lock);
+ return is;
+ }
+ }
+ list_for_each(&irq_sources2, is, link) {
+ if (isn >= is->start && isn < is->end) {
+ unlock(&irq_lock);
+ return is;
+ }
+ }
+ unlock(&irq_lock);
+
+ return NULL;
+}
+
+void irq_for_each_source(void (*cb)(struct irq_source *, void *), void *data)
+{
+ struct irq_source *is;
+
+ lock(&irq_lock);
+ list_for_each(&irq_sources, is, link)
+ cb(is, data);
+ list_for_each(&irq_sources2, is, link)
+ cb(is, data);
+ unlock(&irq_lock);
+}
+
+/*
+ * This takes a 6-bit chip id and returns a 20 bit value representing
+ * the PSI interrupt. This includes all the fields above, ie, is a
+ * global interrupt number.
+ *
+ * For P8, this returns the base of the 8-interrupts block for PSI
+ */
+uint32_t get_psi_interrupt(uint32_t chip_id)
+{
+ uint32_t irq;
+
+ switch(proc_gen) {
+ case proc_gen_p8:
+ irq = p8_chip_irq_block_base(chip_id, P8_IRQ_BLOCK_MISC);
+ irq += P8_IRQ_MISC_PSI_BASE;
+ break;
+ default:
+ assert(false);
+ };
+
+ return irq;
+}
+
+
+struct dt_node *add_ics_node(void)
+{
+ struct dt_node *ics = dt_new_addr(dt_root, "interrupt-controller", 0);
+ bool has_xive;
+
+ if (!ics)
+ return NULL;
+
+ has_xive = proc_gen >= proc_gen_p9;
+
+ dt_add_property_cells(ics, "reg", 0, 0, 0, 0);
+ dt_add_property_strings(ics, "compatible",
+ has_xive ? "ibm,opal-xive-vc" : "IBM,ppc-xics",
+ "IBM,opal-xics");
+ dt_add_property_cells(ics, "#address-cells", 0);
+ dt_add_property_cells(ics, "#interrupt-cells", 2);
+ dt_add_property_string(ics, "device_type",
+ "PowerPC-Interrupt-Source-Controller");
+ dt_add_property(ics, "interrupt-controller", NULL, 0);
+
+ return ics;
+}
+
+uint32_t get_ics_phandle(void)
+{
+ struct dt_node *i;
+
+ for (i = dt_first(dt_root); i; i = dt_next(dt_root, i)) {
+ if (streq(i->name, "interrupt-controller@0")) {
+ return i->phandle;
+ }
+ }
+ abort();
+}
+
+void add_opal_interrupts(void)
+{
+ struct irq_source *is;
+ unsigned int i, ns, tns = 0, count = 0;
+ uint32_t isn;
+ __be32 *irqs = NULL;
+ char *names = NULL;
+
+ lock(&irq_lock);
+ list_for_each(&irq_sources, is, link) {
+ /*
+ * Don't even consider sources that don't have an interrupts
+ * callback or don't have an attributes one.
+ */
+ if (!is->ops->interrupt || !is->ops->attributes)
+ continue;
+ for (isn = is->start; isn < is->end; isn++) {
+ uint64_t attr = is->ops->attributes(is, isn);
+ uint32_t iflags;
+ char *name;
+
+ if (attr & IRQ_ATTR_TARGET_LINUX)
+ continue;
+ if (attr & IRQ_ATTR_TYPE_MSI)
+ iflags = 0;
+ else
+ iflags = 1;
+ name = is->ops->name ? is->ops->name(is, isn) : NULL;
+ ns = name ? strlen(name) : 0;
+ prlog(PR_DEBUG, "irq %x name: %s %s\n",
+ isn,
+ name ? name : "<null>",
+ iflags ? "[level]" : "[edge]");
+ names = realloc(names, tns + ns + 1);
+ if (name) {
+ strcpy(names + tns, name);
+ tns += (ns + 1);
+ free(name);
+ } else
+ names[tns++] = 0;
+ i = count++;
+ irqs = realloc(irqs, 8 * count);
+ irqs[i*2] = cpu_to_be32(isn);
+ irqs[i*2+1] = cpu_to_be32(iflags);
+ }
+ }
+ unlock(&irq_lock);
+
+ /* First create the standard "interrupts" property and the
+ * corresponding names property
+ */
+ dt_add_property_cells(opal_node, "interrupt-parent", get_ics_phandle());
+ dt_add_property(opal_node, "interrupts", irqs, count * 8);
+ dt_add_property(opal_node, "opal-interrupts-names", names, tns);
+ dt_add_property(opal_node, "interrupt-names", names, tns);
+
+ /* Now "reduce" it to the old style "opal-interrupts" property
+ * format by stripping out the flags. The "opal-interrupts"
+ * property has one cell per interrupt, it is not a standard
+ * "interrupt" property.
+ *
+ * Note: Even if empty, create it, otherwise some bogus error
+ * handling in Linux can cause problems.
+ */
+ for (i = 1; i < count; i++)
+ irqs[i] = irqs[i * 2];
+ dt_add_property(opal_node, "opal-interrupts", irqs, count * 4);
+
+ free(irqs);
+ free(names);
+}
+
+/*
+ * This is called at init time (and one fast reboot) to sanitize the
+ * ICP. We set our priority to 0 to mask all interrupts and make sure
+ * no IPI is on the way. This is also called on wakeup from nap
+ */
+void reset_cpu_icp(void)
+{
+ void *icp = this_cpu()->icp_regs;
+
+ if (!icp)
+ return;
+
+ /* Dummy fetch */
+ in_be32(icp + ICP_XIRR);
+
+ /* Clear pending IPIs */
+ out_8(icp + ICP_MFRR, 0xff);
+
+ /* Set priority to max, ignore all incoming interrupts, EOI IPIs */
+ out_be32(icp + ICP_XIRR, 2);
+}
+
+/* Used by the PSI code to send an EOI during reset. This will also
+ * set the CPPR to 0 which should already be the case anyway
+ */
+void icp_send_eoi(uint32_t interrupt)
+{
+ void *icp = this_cpu()->icp_regs;
+
+ if (!icp)
+ return;
+
+ /* Set priority to max, ignore all incoming interrupts */
+ out_be32(icp + ICP_XIRR, interrupt & 0xffffff);
+}
+
+/* This is called before winkle or nap, we clear pending IPIs and
+ * set our priority to 1 to mask all but the IPI.
+ */
+void icp_prep_for_pm(void)
+{
+ void *icp = this_cpu()->icp_regs;
+
+ if (!icp)
+ return;
+
+ /* Clear pending IPIs */
+ out_8(icp + ICP_MFRR, 0xff);
+
+ /* Set priority to 1, ignore all incoming interrupts, EOI IPIs */
+ out_be32(icp + ICP_XIRR, 0x01000002);
+}
+
+/* This is called to wakeup somebody from winkle */
+void icp_kick_cpu(struct cpu_thread *cpu)
+{
+ void *icp = cpu->icp_regs;
+
+ if (!icp)
+ return;
+
+ /* Send high priority IPI */
+ out_8(icp + ICP_MFRR, 0);
+}
+
+/* Returns the number of chip ID bits used for interrupt numbers */
+static uint32_t p8_chip_id_bits(uint32_t chip)
+{
+ struct proc_chip *proc_chip = get_chip(chip);
+
+ assert(proc_chip);
+ switch (proc_chip->type) {
+ case PROC_CHIP_P8_MURANO:
+ case PROC_CHIP_P8_VENICE:
+ return 6;
+ break;
+
+ case PROC_CHIP_P8_NAPLES:
+ return 5;
+ break;
+
+ default:
+ /* This shouldn't be called on non-P8 based systems */
+ assert(0);
+ return 0;
+ break;
+ }
+}
+
+/* The chip id mask is the upper p8_chip_id_bits of the irq number */
+static uint32_t chip_id_mask(uint32_t chip)
+{
+ uint32_t chip_id_bits = p8_chip_id_bits(chip);
+ uint32_t chip_id_mask;
+
+ chip_id_mask = ((1 << chip_id_bits) - 1);
+ chip_id_mask <<= P8_IRQ_BITS - chip_id_bits;
+ return chip_id_mask;
+}
+
+/* The block mask is what remains of the 19 bit irq number after
+ * removing the upper 5 or 6 bits for the chip# and the lower 11 bits
+ * for the number of bits per block. */
+static uint32_t block_mask(uint32_t chip)
+{
+ uint32_t chip_id_bits = p8_chip_id_bits(chip);
+ uint32_t irq_block_mask;
+
+ irq_block_mask = P8_IRQ_BITS - chip_id_bits - P8_IVE_BITS;
+ irq_block_mask = ((1 << irq_block_mask) - 1) << P8_IVE_BITS;
+ return irq_block_mask;
+}
+
+uint32_t p8_chip_irq_block_base(uint32_t chip, uint32_t block)
+{
+ uint32_t irq;
+
+ assert(chip < (1 << p8_chip_id_bits(chip)));
+ irq = SETFIELD(chip_id_mask(chip), 0, chip);
+ irq = SETFIELD(block_mask(chip), irq, block);
+
+ return irq;
+}
+
+uint32_t p8_chip_irq_phb_base(uint32_t chip, uint32_t phb)
+{
+ assert(chip < (1 << p8_chip_id_bits(chip)));
+
+ return p8_chip_irq_block_base(chip, phb + P8_IRQ_BLOCK_PHB_BASE);
+}
+
+uint32_t p8_irq_to_chip(uint32_t irq)
+{
+ /* This assumes we only have one type of cpu in a system,
+ * which should be ok. */
+ return GETFIELD(chip_id_mask(this_cpu()->chip_id), irq);
+}
+
+uint32_t p8_irq_to_block(uint32_t irq)
+{
+ return GETFIELD(block_mask(this_cpu()->chip_id), irq);
+}
+
+uint32_t p8_irq_to_phb(uint32_t irq)
+{
+ return p8_irq_to_block(irq) - P8_IRQ_BLOCK_PHB_BASE;
+}
+
+bool __irq_source_eoi(struct irq_source *is, uint32_t isn)
+{
+ if (!is->ops->eoi)
+ return false;
+
+ is->ops->eoi(is, isn);
+ return true;
+}
+
+bool irq_source_eoi(uint32_t isn)
+{
+ struct irq_source *is = irq_find_source(isn);
+
+ if (!is)
+ return false;
+
+ return __irq_source_eoi(is, isn);
+}
+
+static int64_t opal_set_xive(uint32_t isn, uint16_t server, uint8_t priority)
+{
+ struct irq_source *is = irq_find_source(isn);
+
+ if (!is || !is->ops->set_xive)
+ return OPAL_PARAMETER;
+
+ return is->ops->set_xive(is, isn, server, priority);
+}
+opal_call(OPAL_SET_XIVE, opal_set_xive, 3);
+
+static int64_t opal_get_xive(uint32_t isn, __be16 *server, uint8_t *priority)
+{
+ struct irq_source *is = irq_find_source(isn);
+ uint16_t s;
+ int64_t ret;
+
+ if (!opal_addr_valid(server))
+ return OPAL_PARAMETER;
+
+ if (!is || !is->ops->get_xive)
+ return OPAL_PARAMETER;
+
+ ret = is->ops->get_xive(is, isn, &s, priority);
+ *server = cpu_to_be16(s);
+ return ret;
+}
+opal_call(OPAL_GET_XIVE, opal_get_xive, 3);
+
+static int64_t opal_handle_interrupt(uint32_t isn, __be64 *outstanding_event_mask)
+{
+ struct irq_source *is = irq_find_source(isn);
+ int64_t rc = OPAL_SUCCESS;
+
+ if (!opal_addr_valid(outstanding_event_mask))
+ return OPAL_PARAMETER;
+
+ /* No source ? return */
+ if (!is || !is->ops->interrupt) {
+ rc = OPAL_PARAMETER;
+ goto bail;
+ }
+
+ /* Run it */
+ is->ops->interrupt(is, isn);
+
+ /* Check timers if SBE timer isn't working */
+ if (!p8_sbe_timer_ok() && !p9_sbe_timer_ok())
+ check_timers(true);
+
+ /* Update output events */
+ bail:
+ if (outstanding_event_mask)
+ *outstanding_event_mask = cpu_to_be64(opal_pending_events);
+
+ return rc;
+}
+opal_call(OPAL_HANDLE_INTERRUPT, opal_handle_interrupt, 2);
+
+void init_interrupts(void)
+{
+ struct dt_node *icp;
+ const struct dt_property *sranges;
+ struct cpu_thread *cpu;
+ u32 base, count, i;
+ u64 addr, size;
+
+ dt_for_each_compatible(dt_root, icp, "ibm,ppc-xicp") {
+ sranges = dt_require_property(icp,
+ "ibm,interrupt-server-ranges",
+ -1);
+ base = dt_get_number(sranges->prop, 1);
+ count = dt_get_number(sranges->prop + 4, 1);
+ for (i = 0; i < count; i++) {
+ addr = dt_get_address(icp, i, &size);
+ cpu = find_cpu_by_server(base + i);
+ if (cpu)
+ cpu->icp_regs = (void *)addr;
+ }
+ }
+}
+