|  | // 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.h> | 
|  | #include <xive.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; | 
|  | bool has_xive; | 
|  | bool has_xive_only = proc_gen >= proc_gen_p10; | 
|  |  | 
|  | if (has_xive_only) | 
|  | return NULL; | 
|  |  | 
|  | ics = dt_new_addr(dt_root, "interrupt-controller", 0); | 
|  | 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; | 
|  | bool has_xive_only = proc_gen >= proc_gen_p10; | 
|  |  | 
|  | if (has_xive_only) | 
|  | return xive2_get_phandle(); | 
|  |  | 
|  | for (i = dt_first(dt_root); i; i = dt_next(dt_root, i)) { | 
|  | if (streq(i->name, "interrupt-controller@0")) { | 
|  | return i->phandle; | 
|  | } | 
|  | } | 
|  | abort(); | 
|  | } | 
|  |  | 
|  | static bool source_has_opal_interrupts(struct irq_source *is) | 
|  | { | 
|  | /* check with the source first */ | 
|  | if (is->ops->has_opal_interrupts) | 
|  | return is->ops->has_opal_interrupts(is); | 
|  | /* | 
|  | * Default case: to handle an interrupt in opal, a source | 
|  | * needs at least an attribute callback to declare it and a | 
|  | * handler | 
|  | */ | 
|  | if (!is->ops->interrupt || !is->ops->attributes) | 
|  | return false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void add_opal_interrupts(void) | 
|  | { | 
|  | struct irq_source *is; | 
|  | unsigned int i, ns, tns = 0, count = 0; | 
|  | uint32_t parent; | 
|  | uint32_t isn; | 
|  | __be32 *irqs = NULL; | 
|  | char *names = NULL; | 
|  |  | 
|  | parent = get_ics_phandle(); | 
|  | if (!parent) | 
|  | return; | 
|  |  | 
|  | lock(&irq_lock); | 
|  | list_for_each(&irq_sources, is, link) { | 
|  |  | 
|  | if (!source_has_opal_interrupts(is)) | 
|  | 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", parent); | 
|  | 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 well */ | 
|  | if (!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; | 
|  | } | 
|  | } | 
|  | } | 
|  |  |