| // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| /* |
| * Service Processor serial console handling code |
| * |
| * Copyright 2013-2019 IBM Corp. |
| */ |
| |
| #include <io.h> |
| #include <psi.h> |
| #include <fsp.h> |
| #include <opal.h> |
| #include <interrupts.h> |
| #include <cpu.h> |
| #include <dio-p9.h> |
| #include <trace.h> |
| #include <xscom.h> |
| #include <chip.h> |
| #include <lpc.h> |
| #include <i2c.h> |
| #include <timebase.h> |
| #include <platform.h> |
| #include <errorlog.h> |
| #include <xive.h> |
| #include <sbe-p9.h> |
| #include <phys-map.h> |
| #include <occ.h> |
| |
| static LIST_HEAD(psis); |
| static u64 psi_link_timer; |
| static u64 psi_link_timeout; |
| static bool psi_link_poll_active; |
| |
| static void psi_activate_phb(struct psi *psi); |
| |
| struct lock psi_lock = LOCK_UNLOCKED; |
| |
| DEFINE_LOG_ENTRY(OPAL_RC_PSI_TIMEOUT, OPAL_PLATFORM_ERR_EVT, OPAL_PSI, |
| OPAL_PLATFORM_FIRMWARE, |
| OPAL_UNRECOVERABLE_ERR_LOSS_OF_FUNCTION, OPAL_NA); |
| |
| void psi_set_link_polling(bool active) |
| { |
| printf("PSI: %sing link polling\n", |
| active ? "start" : "stopp"); |
| psi_link_poll_active = active; |
| } |
| |
| void psi_disable_link(struct psi *psi) |
| { |
| lock(&psi_lock); |
| |
| /* |
| * Note: This can be called with the link already down but |
| * not detected as such yet by this layer since psi_check_link_active() |
| * operates locklessly and thus won't update the PSI structure. This |
| * is a non-issue, the only consequence is the messages in the log |
| * mentioning first the link having gone down then being disabled. |
| */ |
| if (psi->active) { |
| u64 reg; |
| psi->active = false; |
| |
| /* Mask errors in SEMR */ |
| reg = in_be64(psi->regs + PSIHB_SEMR); |
| reg &= ((0xfffull << 36) | (0xfffull << 20)); |
| out_be64(psi->regs + PSIHB_SEMR, reg); |
| printf("PSI: SEMR set to %llx\n", reg); |
| |
| /* Reset all the error bits in PSIHB_CR and |
| * disable FSP interrupts |
| */ |
| reg = in_be64(psi->regs + PSIHB_CR); |
| reg &= ~(0x7ffull << 20); |
| reg &= ~PSIHB_CR_PSI_LINK_ENABLE; /* flip link enable */ |
| /* |
| * Ensure no commands/spurious interrupts reach |
| * the processor, by flipping the command enable. |
| */ |
| reg &= ~PSIHB_CR_FSP_CMD_ENABLE; |
| reg &= ~PSIHB_CR_FSP_IRQ_ENABLE; |
| reg &= ~PSIHB_CR_FSP_IRQ; /* Clear interrupt state too */ |
| printf("PSI[0x%03x]: Disabling link!\n", psi->chip_id); |
| out_be64(psi->regs + PSIHB_CR, reg); |
| printf("PSI: PSIHB_CR (error bits) set to %llx\n", |
| in_be64(psi->regs + PSIHB_CR)); |
| psi_set_link_polling(true); |
| } |
| |
| unlock(&psi_lock); |
| } |
| |
| /* |
| * Resetting the FSP is a multi step sequence: |
| * 1. Read the PSIHBCR |
| * 2. Set the PSIHBCR[6] -- write register back. |
| * 3. Read PSIHBCR again |
| * 4. Reset PSIHBCR[6] -- write register back. |
| */ |
| void psi_reset_fsp(struct psi *psi) |
| { |
| lock(&psi_lock); |
| |
| if (psi->active) { |
| u64 reg; |
| |
| printf("PSI: Driving FSP reset via PSI\n"); |
| reg = in_be64(psi->regs + PSIHB_CR); |
| reg &= ~(0xfffull << 20); /* Reset error bits */ |
| reg |= PSIHB_CR_FSP_RESET; /* FSP reset trigger start */ |
| out_be64(psi->regs + PSIHB_CR, reg); |
| printf("PSI[0x%03x]: FSP reset start PSIHBCR set to %llx\n", |
| psi->chip_id, in_be64(psi->regs + PSIHB_CR)); |
| |
| reg = in_be64(psi->regs + PSIHB_CR); |
| reg &= ~PSIHB_CR_FSP_RESET; /* Clear FSP reset bit */ |
| out_be64(psi->regs + PSIHB_CR, reg); /* Complete reset */ |
| printf("PSI[0x%03x]: FSP reset complete. PSIHBCR set to %llx\n", |
| psi->chip_id, in_be64(psi->regs + PSIHB_CR)); |
| } |
| unlock(&psi_lock); |
| |
| /* Now bring down the PSI link too... */ |
| psi_disable_link(psi); |
| } |
| |
| bool psi_check_link_active(struct psi *psi) |
| { |
| u64 val = in_be64(psi->regs + PSIHB_CR); |
| |
| /* |
| * Unlocked, used during fsp_poke_msg so we really want |
| * to avoid fancy link re-entrancy and deadlocks here |
| */ |
| if (!psi->active) |
| return false; |
| return (val & PSIHB_CR_PSI_LINK_ENABLE) && |
| (val & PSIHB_CR_FSP_LINK_ACTIVE); |
| } |
| |
| struct psi *psi_find_link(uint32_t chip_id) |
| { |
| struct psi *psi; |
| |
| list_for_each(&psis, psi, list) { |
| if (psi->chip_id == chip_id) |
| return psi; |
| } |
| return NULL; |
| } |
| |
| #define PSI_LINK_CHECK_INTERVAL 10 /* Interval in secs */ |
| #define PSI_LINK_RECOVERY_TIMEOUT 1800 /* 30 minutes */ |
| |
| static void psi_link_poll(void *data __unused) |
| { |
| struct psi *psi; |
| u64 now; |
| |
| if (!psi_link_poll_active) |
| return; |
| |
| now = mftb(); |
| if (psi_link_timer == 0 || |
| (tb_compare(now, psi_link_timer) == TB_AAFTERB) || |
| (tb_compare(now, psi_link_timer) == TB_AEQUALB)) { |
| |
| lock(&psi_lock); |
| |
| list_for_each(&psis, psi, list) { |
| u64 val; |
| |
| if (psi->active) |
| continue; |
| |
| val = in_be64(psi->regs + PSIHB_CR); |
| |
| printf("PSI[0x%03x]: Poll CR=0x%016llx\n", |
| psi->chip_id, val); |
| |
| if ((val & PSIHB_CR_PSI_LINK_ENABLE) && |
| (val & PSIHB_CR_FSP_LINK_ACTIVE)) { |
| printf("PSI[0x%03x]: Found active link!\n", |
| psi->chip_id); |
| psi_link_timeout = 0; |
| psi->active = true; |
| psi_activate_phb(psi); |
| psi_set_link_polling(false); |
| unlock(&psi_lock); |
| if (platform.psi && platform.psi->link_established) |
| platform.psi->link_established(); |
| return; |
| } |
| } |
| if (!psi_link_timeout) |
| psi_link_timeout = |
| now + secs_to_tb(PSI_LINK_RECOVERY_TIMEOUT); |
| |
| if (tb_compare(now, psi_link_timeout) == TB_AAFTERB) { |
| log_simple_error(&e_info(OPAL_RC_PSI_TIMEOUT), |
| "PSI: Link timeout -- loss of FSP\n"); |
| /* Reset the link timeout and continue looking */ |
| psi_link_timeout = 0; |
| } |
| |
| /* Poll every 10 seconds */ |
| psi_link_timer = now + secs_to_tb(PSI_LINK_CHECK_INTERVAL); |
| |
| unlock(&psi_lock); |
| } |
| } |
| |
| void psi_enable_fsp_interrupt(struct psi *psi) |
| { |
| /* Enable FSP interrupts in the GXHB */ |
| lock(&psi_lock); |
| out_be64(psi->regs + PSIHB_CR, |
| in_be64(psi->regs + PSIHB_CR) | PSIHB_CR_FSP_IRQ_ENABLE); |
| unlock(&psi_lock); |
| } |
| |
| /* Multiple bits can be set on errors */ |
| static void decode_psihb_error(u64 val) |
| { |
| if (val & PSIHB_CR_PSI_ERROR) |
| printf("PSI: PSI Reported Error\n"); |
| if (val & PSIHB_CR_PSI_LINK_INACTIVE) |
| printf("PSI: PSI Link Inactive Transition\n"); |
| if (val & PSIHB_CR_FSP_ACK_TIMEOUT) |
| printf("PSI: FSP Ack Timeout\n"); |
| if (val & PSIHB_CR_MMIO_LOAD_TIMEOUT) |
| printf("PSI: MMIO Load Timeout\n"); |
| if (val & PSIHB_CR_MMIO_LENGTH_ERROR) |
| printf("PSI: MMIO Length Error\n"); |
| if (val & PSIHB_CR_MMIO_ADDRESS_ERROR) |
| printf("PSI: MMIO Address Error\n"); |
| if (val & PSIHB_CR_MMIO_TYPE_ERROR) |
| printf("PSI: MMIO Type Error\n"); |
| if (val & PSIHB_CR_UE) |
| printf("PSI: UE Detected\n"); |
| if (val & PSIHB_CR_PARITY_ERROR) |
| printf("PSI: Internal Parity Error\n"); |
| if (val & PSIHB_CR_SYNC_ERR_ALERT1) |
| printf("PSI: Sync Error Alert1\n"); |
| if (val & PSIHB_CR_SYNC_ERR_ALERT2) |
| printf("PSI: Sync Error Alert2\n"); |
| if (val & PSIHB_CR_FSP_COMMAND_ERROR) |
| printf("PSI: FSP Command Error\n"); |
| } |
| |
| |
| static void handle_psi_interrupt(struct psi *psi, u64 val) |
| { |
| printf("PSI[0x%03x]: PSI mgmnt interrupt CR=0x%016llx\n", |
| psi->chip_id, val); |
| |
| if (val & (0xfffull << 20)) { |
| decode_psihb_error(val); |
| psi_disable_link(psi); |
| } else if (val & (0x1full << 11)) |
| printf("PSI: FSP error detected\n"); |
| } |
| |
| static void psi_spurious_fsp_irq(struct psi *psi) |
| { |
| u64 reg, bit; |
| |
| prlog(PR_NOTICE, "PSI: Spurious interrupt, attempting clear\n"); |
| |
| if (proc_gen == proc_gen_p10) { |
| reg = PSIHB_XSCOM_P10_HBCSR_CLR; |
| bit = PSIHB_XSCOM_P10_HBSCR_FSP_IRQ; |
| } else if (proc_gen == proc_gen_p9) { |
| reg = PSIHB_XSCOM_P9_HBCSR_CLR; |
| bit = PSIHB_XSCOM_P9_HBSCR_FSP_IRQ; |
| } else if (proc_gen == proc_gen_p8) { |
| reg = PSIHB_XSCOM_P8_HBCSR_CLR; |
| bit = PSIHB_XSCOM_P8_HBSCR_FSP_IRQ; |
| } else { |
| assert(false); |
| } |
| xscom_write(psi->chip_id, psi->xscom_base + reg, bit); |
| } |
| |
| bool psi_poll_fsp_interrupt(struct psi *psi) |
| { |
| return !!(in_be64(psi->regs + PSIHB_CR) & PSIHB_CR_FSP_IRQ); |
| } |
| |
| static void psihb_interrupt(struct irq_source *is, uint32_t isn __unused) |
| { |
| struct psi *psi = is->data; |
| u64 val; |
| |
| val = in_be64(psi->regs + PSIHB_CR); |
| |
| if (psi_link_poll_active) { |
| printf("PSI[0x%03x]: PSI interrupt CR=0x%016llx (A=%d)\n", |
| psi->chip_id, val, psi->active); |
| } |
| |
| /* Handle PSI interrupts first in case it's a link down */ |
| if (val & PSIHB_CR_PSI_IRQ) { |
| handle_psi_interrupt(psi, val); |
| |
| /* |
| * If the link went down, re-read PSIHB_CR as |
| * the FSP interrupt might have been cleared. |
| */ |
| if (!psi->active) |
| val = in_be64(psi->regs + PSIHB_CR); |
| } |
| |
| |
| /* |
| * We avoid forwarding FSP interrupts if the link isn't |
| * active. They should be masked anyway but it looks |
| * like the CR bit can remain set. |
| */ |
| if (val & PSIHB_CR_FSP_IRQ) { |
| /* |
| * We have a case a flood with FSP mailbox interrupts |
| * when the link is down, see if we manage to clear |
| * the condition |
| */ |
| if (!psi->active) |
| psi_spurious_fsp_irq(psi); |
| else { |
| if (platform.psi && platform.psi->fsp_interrupt) |
| platform.psi->fsp_interrupt(); |
| } |
| } |
| |
| if (platform.psi && platform.psi->psihb_interrupt) |
| platform.psi->psihb_interrupt(); |
| } |
| |
| |
| static const uint32_t psi_p8_irq_to_xivr[P8_IRQ_PSI_IRQ_COUNT] = { |
| [P8_IRQ_PSI_FSP] = PSIHB_XIVR_FSP, |
| [P8_IRQ_PSI_OCC] = PSIHB_XIVR_OCC, |
| [P8_IRQ_PSI_FSI] = PSIHB_XIVR_FSI, |
| [P8_IRQ_PSI_LPC] = PSIHB_XIVR_LPC, |
| [P8_IRQ_PSI_LOCAL_ERR] = PSIHB_XIVR_LOCAL_ERR, |
| [P8_IRQ_PSI_EXTERNAL]= PSIHB_XIVR_HOST_ERR, |
| }; |
| |
| static void psi_cleanup_irq(struct psi *psi) |
| { |
| uint32_t irq; |
| uint64_t xivr, xivr_p; |
| |
| for (irq = 0; irq < P8_IRQ_PSI_IRQ_COUNT; irq++) { |
| prlog(PR_DEBUG, "PSI[0x%03x]: Cleaning up IRQ %d\n", |
| psi->chip_id, irq); |
| |
| xivr_p = psi_p8_irq_to_xivr[irq]; |
| xivr = in_be64(psi->regs + xivr_p); |
| xivr |= (0xffull << 32); |
| out_be64(psi->regs + xivr_p, xivr); |
| time_wait_ms_nopoll(10); |
| xivr = in_be64(psi->regs + xivr_p); |
| if (xivr & PPC_BIT(39)) { |
| printf(" Need EOI !\n"); |
| icp_send_eoi(psi->interrupt + irq); |
| } |
| } |
| } |
| |
| /* Called on a fast reset, make sure we aren't stuck with |
| * an accepted and never EOId PSI interrupt |
| */ |
| void psi_irq_reset(void) |
| { |
| struct psi *psi; |
| |
| printf("PSI: Hot reset!\n"); |
| |
| assert(proc_gen == proc_gen_p8); |
| |
| list_for_each(&psis, psi, list) { |
| psi_cleanup_irq(psi); |
| } |
| } |
| |
| static int64_t psi_p8_set_xive(struct irq_source *is, uint32_t isn, |
| uint16_t server, uint8_t priority) |
| { |
| struct psi *psi = is->data; |
| uint64_t xivr_p, xivr; |
| uint32_t irq_idx = isn & 7; |
| |
| if (irq_idx >= P8_IRQ_PSI_IRQ_COUNT) |
| return OPAL_PARAMETER; |
| xivr_p = psi_p8_irq_to_xivr[irq_idx]; |
| |
| /* Populate the XIVR */ |
| xivr = (uint64_t)server << 40; |
| xivr |= (uint64_t)priority << 32; |
| xivr |= (uint64_t)(isn & 7) << 29; |
| |
| out_be64(psi->regs + xivr_p, xivr); |
| |
| return OPAL_SUCCESS; |
| } |
| |
| static int64_t psi_p8_get_xive(struct irq_source *is, uint32_t isn __unused, |
| uint16_t *server, uint8_t *priority) |
| { |
| struct psi *psi = is->data; |
| uint64_t xivr_p, xivr; |
| uint32_t irq_idx = isn & 7; |
| |
| if (irq_idx >= P8_IRQ_PSI_IRQ_COUNT) |
| return OPAL_PARAMETER; |
| |
| xivr_p = psi_p8_irq_to_xivr[irq_idx]; |
| |
| /* Read & decode the XIVR */ |
| xivr = in_be64(psi->regs + xivr_p); |
| |
| *server = (xivr >> 40) & 0xffff; |
| *priority = (xivr >> 32) & 0xff; |
| |
| return OPAL_SUCCESS; |
| } |
| |
| static void psihb_p8_interrupt(struct irq_source *is, uint32_t isn) |
| { |
| struct psi *psi = is->data; |
| uint32_t idx = isn - psi->interrupt; |
| |
| switch (idx) { |
| case P8_IRQ_PSI_FSP: |
| psihb_interrupt(is, isn); |
| break; |
| case P8_IRQ_PSI_OCC: |
| occ_p8_interrupt(psi->chip_id); |
| break; |
| case P8_IRQ_PSI_FSI: |
| printf("PSI: FSI irq received\n"); |
| break; |
| case P8_IRQ_PSI_LPC: |
| lpc_interrupt(psi->chip_id); |
| |
| /* |
| * i2c interrupts are ORed with the LPC ones on |
| * Murano DD2.1 and Venice DD2.0 |
| */ |
| p8_i2c_interrupt(psi->chip_id); |
| break; |
| case P8_IRQ_PSI_LOCAL_ERR: |
| prd_psi_interrupt(psi->chip_id); |
| break; |
| case P8_IRQ_PSI_EXTERNAL: |
| if (platform.external_irq) |
| platform.external_irq(psi->chip_id); |
| break; |
| } |
| |
| /* |
| * TODO: Per Vicente Chung, CRESPs don't generate interrupts, |
| * and are just informational. Need to define the policy |
| * to handle them. |
| */ |
| } |
| |
| static uint64_t psi_p8_irq_attributes(struct irq_source *is, uint32_t isn) |
| { |
| struct psi *psi = is->data; |
| uint32_t idx = isn - psi->interrupt; |
| uint64_t attr; |
| |
| if (psi->no_lpc_irqs && idx == P8_IRQ_PSI_LPC) |
| return IRQ_ATTR_TARGET_LINUX; |
| |
| /* Only direct external interrupts to OPAL if we have a handler */ |
| if (idx == P8_IRQ_PSI_EXTERNAL && !platform.external_irq) |
| return IRQ_ATTR_TARGET_LINUX; |
| |
| attr = IRQ_ATTR_TARGET_OPAL | IRQ_ATTR_TYPE_LSI; |
| if (idx == P8_IRQ_PSI_EXTERNAL || idx == P8_IRQ_PSI_LPC || |
| idx == P8_IRQ_PSI_FSP) |
| attr |= IRQ_ATTR_TARGET_FREQUENT; |
| return attr; |
| } |
| |
| static char *psi_p8_irq_name(struct irq_source *is, uint32_t isn) |
| { |
| struct psi *psi = is->data; |
| uint32_t idx = isn - psi->interrupt; |
| char tmp[30]; |
| |
| static const char *names[P8_IRQ_PSI_IRQ_COUNT] = { |
| "fsp", |
| "occ", |
| "fsi", |
| "lpchc", |
| "local_err", |
| "external", |
| }; |
| |
| if (idx >= P8_IRQ_PSI_IRQ_COUNT) |
| return NULL; |
| |
| snprintf(tmp, sizeof(tmp), "psi#%x:%s", |
| psi->chip_id, names[idx]); |
| |
| return strdup(tmp); |
| } |
| |
| static const struct irq_source_ops psi_p8_irq_ops = { |
| .get_xive = psi_p8_get_xive, |
| .set_xive = psi_p8_set_xive, |
| .interrupt = psihb_p8_interrupt, |
| .attributes = psi_p8_irq_attributes, |
| .name = psi_p8_irq_name, |
| }; |
| |
| static const char *psi_p9_irq_names[P9_PSI_NUM_IRQS] = { |
| "fsp", |
| "occ", |
| "fsi", |
| "lpchc", |
| "local_err", |
| "global_err", |
| "external", |
| "lpc_serirq_mux0", /* Have a callback to get name ? */ |
| "lpc_serirq_mux1", /* Have a callback to get name ? */ |
| "lpc_serirq_mux2", /* Have a callback to get name ? */ |
| "lpc_serirq_mux3", /* Have a callback to get name ? */ |
| "i2c", |
| "dio", |
| "psu" |
| }; |
| |
| static void psi_p9_mask_all(struct psi *psi) |
| { |
| struct irq_source *is; |
| int isn; |
| |
| /* Mask all sources */ |
| is = irq_find_source(psi->interrupt); |
| for (isn = is->start; isn < is->end; isn++) |
| xive_source_mask(is, isn); |
| } |
| |
| static void psi_p9_mask_unhandled_irq(struct irq_source *is, uint32_t isn) |
| { |
| struct psi *psi = is->data; |
| int idx = isn - psi->interrupt; |
| const char *name; |
| |
| if (idx < ARRAY_SIZE(psi_p9_irq_names)) |
| name = psi_p9_irq_names[idx]; |
| else |
| name = "unknown!"; |
| |
| prerror("PSI[0x%03x]: Masking unhandled LSI %d (%s)\n", |
| psi->chip_id, idx, name); |
| |
| /* |
| * All the PSI interrupts are LSIs and will be constantly re-fired |
| * unless the underlying interrupt condition is cleared. If we don't |
| * have a handler for the interrupt then it needs to be masked to |
| * prevent the IRQ from locking up the thread which handles it. |
| */ |
| switch (proc_gen) { |
| case proc_gen_p9: |
| xive_source_mask(is, isn); |
| break; |
| case proc_gen_p10: |
| xive2_source_mask(is, isn); |
| return; |
| default: |
| assert(false); |
| } |
| |
| } |
| |
| static void psihb_p9_interrupt(struct irq_source *is, uint32_t isn) |
| { |
| struct psi *psi = is->data; |
| uint32_t idx = isn - psi->interrupt; |
| |
| switch (idx) { |
| case P9_PSI_IRQ_PSI: |
| psihb_interrupt(is, isn); |
| break; |
| case P9_PSI_IRQ_OCC: |
| occ_p9_interrupt(psi->chip_id); |
| break; |
| case P9_PSI_IRQ_LPCHC: |
| lpc_interrupt(psi->chip_id); |
| break; |
| case P9_PSI_IRQ_LOCAL_ERR: |
| prd_psi_interrupt(psi->chip_id); |
| break; |
| case P9_PSI_IRQ_EXTERNAL: |
| if (platform.external_irq) |
| platform.external_irq(psi->chip_id); |
| else |
| psi_p9_mask_unhandled_irq(is, isn); |
| break; |
| case P9_PSI_IRQ_LPC_SIRQ0: |
| case P9_PSI_IRQ_LPC_SIRQ1: |
| case P9_PSI_IRQ_LPC_SIRQ2: |
| case P9_PSI_IRQ_LPC_SIRQ3: |
| lpc_serirq(psi->chip_id, idx - P9_PSI_IRQ_LPC_SIRQ0); |
| break; |
| case P9_PSI_IRQ_SBE_I2C: |
| p8_i2c_interrupt(psi->chip_id); |
| break; |
| case P9_PSI_IRQ_DIO: |
| printf("PSI: DIO irq received\n"); |
| dio_interrupt_handler(psi->chip_id); |
| break; |
| case P9_PSI_IRQ_PSU: |
| p9_sbe_interrupt(psi->chip_id); |
| break; |
| |
| default: |
| psi_p9_mask_unhandled_irq(is, isn); |
| } |
| } |
| |
| static uint64_t psi_p9_irq_attributes(struct irq_source *is __unused, |
| uint32_t isn) |
| { |
| struct psi *psi = is->data; |
| unsigned int idx = isn & 0xf; |
| bool is_lpc_serirq; |
| |
| is_lpc_serirq = |
| (idx == P9_PSI_IRQ_LPC_SIRQ0 || |
| idx == P9_PSI_IRQ_LPC_SIRQ1 || |
| idx == P9_PSI_IRQ_LPC_SIRQ2 || |
| idx == P9_PSI_IRQ_LPC_SIRQ3); |
| |
| /* If LPC interrupts are disabled, route them to Linux |
| * (who will not request them since they aren't referenced |
| * in the device tree) |
| */ |
| if (is_lpc_serirq && psi->no_lpc_irqs) |
| return IRQ_ATTR_TARGET_LINUX; |
| |
| /* For serirq, check the LPC layer for policy */ |
| if (is_lpc_serirq) |
| return lpc_get_irq_policy(psi->chip_id, idx - P9_PSI_IRQ_LPC_SIRQ0); |
| |
| /* Only direct external interrupts to OPAL if we have a handler */ |
| if (idx == P9_PSI_IRQ_EXTERNAL && !platform.external_irq) |
| return IRQ_ATTR_TARGET_LINUX | IRQ_ATTR_TYPE_LSI; |
| |
| return IRQ_ATTR_TARGET_OPAL | IRQ_ATTR_TYPE_LSI; |
| } |
| |
| static char *psi_p9_irq_name(struct irq_source *is, uint32_t isn) |
| { |
| struct psi *psi = is->data; |
| uint32_t idx = isn - psi->interrupt; |
| char tmp[30]; |
| |
| if (idx >= ARRAY_SIZE(psi_p9_irq_names)) |
| return NULL; |
| |
| snprintf(tmp, sizeof(tmp), "psi#%x:%s", |
| psi->chip_id, psi_p9_irq_names[idx]); |
| |
| return strdup(tmp); |
| } |
| |
| static const struct irq_source_ops psi_p9_irq_ops = { |
| .interrupt = psihb_p9_interrupt, |
| .attributes = psi_p9_irq_attributes, |
| .name = psi_p9_irq_name, |
| }; |
| |
| static void psi_init_p8_interrupts(struct psi *psi) |
| { |
| uint32_t irq; |
| uint64_t xivr_p; |
| |
| /* On P8 we get a block of 8, set up the base/mask |
| * and mask all the sources for now |
| */ |
| out_be64(psi->regs + PSIHB_IRSN, |
| SETFIELD(PSIHB_IRSN_COMP, 0ul, psi->interrupt) | |
| SETFIELD(PSIHB_IRSN_MASK, 0ul, 0x7fff8ul) | |
| PSIHB_IRSN_DOWNSTREAM_EN | |
| PSIHB_IRSN_UPSTREAM_EN); |
| |
| for (irq = 0; irq < P8_IRQ_PSI_IRQ_COUNT; irq++) { |
| xivr_p = psi_p8_irq_to_xivr[irq]; |
| out_be64(psi->regs + xivr_p, (0xffull << 32) | (irq << 29)); |
| } |
| |
| /* |
| * Register the IRQ sources FSP, OCC, FSI, LPC |
| * and Local Error. Host Error is actually the |
| * external interrupt and the policy for that comes |
| * from the platform |
| */ |
| register_irq_source(&psi_p8_irq_ops, psi, |
| psi->interrupt, P8_IRQ_PSI_IRQ_COUNT); |
| } |
| |
| static void psi_init_p9_interrupts(struct psi *psi) |
| { |
| struct proc_chip *chip; |
| u64 val; |
| |
| /* Grab chip */ |
| chip = get_chip(psi->chip_id); |
| if (!chip) |
| return; |
| |
| /* Configure the CI BAR */ |
| phys_map_get(chip->id, PSIHB_ESB, 0, &val, NULL); |
| val |= PSIHB_ESB_CI_VALID; |
| out_be64(psi->regs + PSIHB_ESB_CI_BASE, val); |
| |
| val = in_be64(psi->regs + PSIHB_ESB_CI_BASE); |
| psi->esb_mmio = (void *)(val & ~PSIHB_ESB_CI_VALID); |
| prlog(PR_DEBUG, "PSI[0x%03x]: ESB MMIO at @%p\n", |
| psi->chip_id, psi->esb_mmio); |
| |
| /* Register sources */ |
| prlog(PR_DEBUG, |
| "PSI[0x%03x]: Interrupts sources registered for P9 DD2.x\n", |
| psi->chip_id); |
| xive_register_hw_source(psi->interrupt, P9_PSI_NUM_IRQS, |
| 12, psi->esb_mmio, XIVE_SRC_LSI, |
| psi, &psi_p9_irq_ops); |
| |
| psi_p9_mask_all(psi); |
| |
| /* Setup interrupt offset */ |
| val = xive_get_notify_base(psi->interrupt); |
| val <<= 32; |
| out_be64(psi->regs + PSIHB_IVT_OFFSET, val); |
| |
| /* Grab and configure the notification port */ |
| val = xive_get_notify_port(psi->chip_id, XIVE_HW_SRC_PSI); |
| val |= PSIHB_ESB_NOTIF_VALID; |
| out_be64(psi->regs + PSIHB_ESB_NOTIF_ADDR, val); |
| |
| /* Reset irq handling and switch to ESB mode */ |
| out_be64(psi->regs + PSIHB_INTERRUPT_CONTROL, PSIHB_IRQ_RESET); |
| out_be64(psi->regs + PSIHB_INTERRUPT_CONTROL, 0); |
| } |
| |
| /* |
| * P9 and P10 have the same PSIHB interface |
| */ |
| static const struct irq_source_ops psi_p10_irq_ops = { |
| .interrupt = psihb_p9_interrupt, |
| .attributes = psi_p9_irq_attributes, |
| .name = psi_p9_irq_name, |
| }; |
| |
| #define PSIHB10_CAN_STORE_EOI(x) XIVE2_STORE_EOI_ENABLED |
| |
| static void psi_init_p10_interrupts(struct psi *psi) |
| { |
| struct proc_chip *chip; |
| u64 val; |
| uint32_t esb_shift = 16; |
| uint32_t flags = XIVE_SRC_LSI; |
| struct irq_source *is; |
| int isn; |
| |
| /* Grab chip */ |
| chip = get_chip(psi->chip_id); |
| if (!chip) |
| return; |
| |
| /* Configure the CI BAR */ |
| phys_map_get(chip->id, PSIHB_ESB, 0, &val, NULL); |
| val |= PSIHB_ESB_CI_VALID; |
| if (esb_shift == 16) |
| val |= PSIHB10_ESB_CI_64K; |
| out_be64(psi->regs + PSIHB_ESB_CI_BASE, val); |
| |
| val = in_be64(psi->regs + PSIHB_ESB_CI_BASE); |
| psi->esb_mmio = (void *)(val & ~(PSIHB_ESB_CI_VALID|PSIHB10_ESB_CI_64K)); |
| prlog(PR_DEBUG, "PSI[0x%03x]: ESB MMIO at @%p\n", |
| psi->chip_id, psi->esb_mmio); |
| |
| /* Store EOI */ |
| if (PSIHB10_CAN_STORE_EOI(psi)) { |
| val = in_be64(psi->regs + PSIHB_CR); |
| val |= PSIHB10_CR_STORE_EOI; |
| out_be64(psi->regs + PSIHB_CR, val); |
| prlog(PR_DEBUG, "PSI[0x%03x]: store EOI is enabled\n", |
| psi->chip_id); |
| flags |= XIVE_SRC_STORE_EOI; |
| } |
| |
| /* Register sources */ |
| prlog(PR_DEBUG, |
| "PSI[0x%03x]: Interrupts sources registered for P10 DD%i.%i\n", |
| psi->chip_id, 0xf & (chip->ec_level >> 4), chip->ec_level & 0xf); |
| |
| xive2_register_hw_source(psi->interrupt, P9_PSI_NUM_IRQS, |
| esb_shift, psi->esb_mmio, flags, |
| psi, &psi_p10_irq_ops); |
| |
| /* Mask all sources */ |
| is = irq_find_source(psi->interrupt); |
| for (isn = is->start; isn < is->end; isn++) |
| xive2_source_mask(is, isn); |
| |
| /* Setup interrupt offset */ |
| val = xive2_get_notify_base(psi->interrupt); |
| val <<= 32; |
| out_be64(psi->regs + PSIHB_IVT_OFFSET, val); |
| |
| /* Grab and configure the notification port */ |
| val = xive2_get_notify_port(psi->chip_id, XIVE_HW_SRC_PSI); |
| val |= PSIHB_ESB_NOTIF_VALID; |
| out_be64(psi->regs + PSIHB_ESB_NOTIF_ADDR, val); |
| |
| /* Reset irq handling and switch to ESB mode */ |
| out_be64(psi->regs + PSIHB_INTERRUPT_CONTROL, PSIHB_IRQ_RESET); |
| out_be64(psi->regs + PSIHB_INTERRUPT_CONTROL, 0); |
| } |
| |
| static void psi_init_interrupts(struct psi *psi) |
| { |
| /* Configure the interrupt BUID and mask it */ |
| switch (proc_gen) { |
| case proc_gen_p8: |
| psi_init_p8_interrupts(psi); |
| break; |
| case proc_gen_p9: |
| psi_init_p9_interrupts(psi); |
| break; |
| case proc_gen_p10: |
| psi_init_p10_interrupts(psi); |
| break; |
| default: |
| /* Unknown: just no interrupts */ |
| prerror("PSI: Unknown interrupt type\n"); |
| } |
| } |
| |
| static void psi_activate_phb(struct psi *psi) |
| { |
| u64 reg; |
| |
| /* |
| * Disable interrupt emission in the control register, |
| * it will be re-enabled later, after the mailbox one |
| * will have been enabled. |
| */ |
| reg = in_be64(psi->regs + PSIHB_CR); |
| reg &= ~PSIHB_CR_FSP_IRQ_ENABLE; |
| out_be64(psi->regs + PSIHB_CR, reg); |
| |
| /* Enable interrupts in the mask register. We enable everything |
| * except for bit "FSP command error detected" which the doc |
| * (P7 BookIV) says should be masked for normal ops. It also |
| * seems to be masked under OPAL. |
| */ |
| reg = 0x0000010000100000ull; |
| out_be64(psi->regs + PSIHB_SEMR, reg); |
| |
| #if 0 |
| /* Dump the GXHB registers */ |
| printf(" PSIHB_BBAR : %llx\n", |
| in_be64(psi->regs + PSIHB_BBAR)); |
| printf(" PSIHB_FSPBAR : %llx\n", |
| in_be64(psi->regs + PSIHB_FSPBAR)); |
| printf(" PSIHB_FSPMMR : %llx\n", |
| in_be64(psi->regs + PSIHB_FSPMMR)); |
| printf(" PSIHB_TAR : %llx\n", |
| in_be64(psi->regs + PSIHB_TAR)); |
| printf(" PSIHB_CR : %llx\n", |
| in_be64(psi->regs + PSIHB_CR)); |
| printf(" PSIHB_SEMR : %llx\n", |
| in_be64(psi->regs + PSIHB_SEMR)); |
| printf(" PSIHB_XIVR : %llx\n", |
| in_be64(psi->regs + PSIHB_XIVR)); |
| #endif |
| } |
| |
| static void psi_create_p9_int_map(struct psi *psi, struct dt_node *np) |
| { |
| __be32 map[P9_PSI_NUM_IRQS][4]; |
| int i; |
| |
| for (i = 0; i < P9_PSI_NUM_IRQS; i++) { |
| map[i][0] = cpu_to_be32(i); |
| map[i][1] = cpu_to_be32(get_ics_phandle()); |
| map[i][2] = cpu_to_be32(psi->interrupt + i); |
| map[i][3] = cpu_to_be32(1); |
| } |
| dt_add_property(np, "interrupt-map", map, sizeof(map)); |
| dt_add_property_cells(np, "#address-cells", 0); |
| dt_add_property_cells(np, "#interrupt-cells", 1); |
| } |
| |
| static void psi_create_mm_dtnode(struct psi *psi) |
| { |
| struct dt_node *np; |
| uint64_t addr = (uint64_t)psi->regs; |
| |
| np = dt_new_addr(dt_root, "psi", addr); |
| if (!np) |
| return; |
| |
| /* Hard wire size to 4G */ |
| dt_add_property_u64s(np, "reg", addr, 0x100000000ull); |
| switch (proc_gen) { |
| case proc_gen_p8: |
| dt_add_property_strings(np, "compatible", "ibm,psi", |
| "ibm,power8-psi"); |
| break; |
| case proc_gen_p9: |
| case proc_gen_p10: |
| dt_add_property_strings(np, "compatible", "ibm,psi", |
| "ibm,power9-psi"); |
| psi_create_p9_int_map(psi, np); |
| break; |
| default: |
| assert(0); |
| break; |
| } |
| dt_add_property_cells(np, "interrupt-parent", get_ics_phandle()); |
| dt_add_property_cells(np, "interrupts", psi->interrupt, 1); |
| dt_add_property_cells(np, "ibm,chip-id", psi->chip_id); |
| psi->node = np; |
| } |
| |
| static struct psi *alloc_psi(struct proc_chip *chip, uint64_t base) |
| { |
| struct psi *psi; |
| |
| psi = zalloc(sizeof(struct psi)); |
| if (!psi) { |
| prerror("PSI: Could not allocate memory\n"); |
| return NULL; |
| } |
| psi->xscom_base = base; |
| psi->chip_id = chip->id; |
| return psi; |
| } |
| |
| static struct psi *psi_probe_p8(struct proc_chip *chip, u64 base) |
| { |
| struct psi *psi = NULL; |
| uint64_t rc, val; |
| |
| rc = xscom_read(chip->id, base + PSIHB_XSCOM_P8_BASE, &val); |
| if (rc) { |
| prerror("PSI[0x%03x]: Error %llx reading PSIHB BAR\n", |
| chip->id, rc); |
| return NULL; |
| } |
| if (val & PSIHB_XSCOM_P8_HBBAR_EN) { |
| psi = alloc_psi(chip, base); |
| if (!psi) |
| return NULL; |
| psi->regs = (void *)(val & ~PSIHB_XSCOM_P8_HBBAR_EN); |
| psi->interrupt = get_psi_interrupt(chip->id); |
| } else |
| printf("PSI[0x%03x]: Working chip not found\n", chip->id); |
| |
| return psi; |
| } |
| |
| static struct psi *psi_probe_p9(struct proc_chip *chip, u64 base) |
| { |
| struct psi *psi = NULL; |
| uint64_t addr; |
| |
| phys_map_get(chip->id, PSIHB_REG, 0, &addr, NULL); |
| xscom_write(chip->id, base + PSIHB_XSCOM_P9_BASE, |
| addr | PSIHB_XSCOM_P9_HBBAR_EN); |
| |
| psi = alloc_psi(chip, base); |
| if (!psi) |
| return NULL; |
| psi->regs = (void *)addr; |
| psi->interrupt = xive_alloc_hw_irqs(chip->id, P9_PSI_NUM_IRQS, 16); |
| return psi; |
| } |
| |
| static struct psi *psi_probe_p10(struct proc_chip *chip, u64 base) |
| { |
| struct psi *psi = NULL; |
| uint64_t addr; |
| |
| phys_map_get(chip->id, PSIHB_REG, 0, &addr, NULL); |
| xscom_write(chip->id, base + PSIHB_XSCOM_P9_BASE, |
| addr | PSIHB_XSCOM_P9_HBBAR_EN); |
| |
| psi = alloc_psi(chip, base); |
| if (!psi) |
| return NULL; |
| psi->regs = (void *)addr; |
| psi->interrupt = xive2_alloc_hw_irqs(chip->id, P9_PSI_NUM_IRQS, 16); |
| return psi; |
| } |
| |
| static bool psi_init_psihb(struct dt_node *psihb) |
| { |
| uint32_t chip_id = dt_get_chip_id(psihb); |
| struct proc_chip *chip = get_chip(chip_id); |
| struct psi *psi = NULL; |
| u64 base, val; |
| |
| if (!chip) { |
| prerror("PSI: Can't find chip!\n"); |
| return false; |
| } |
| |
| base = dt_get_address(psihb, 0, NULL); |
| |
| if (dt_node_is_compatible(psihb, "ibm,power8-psihb-x")) |
| psi = psi_probe_p8(chip, base); |
| else if (dt_node_is_compatible(psihb, "ibm,power9-psihb-x")) |
| psi = psi_probe_p9(chip, base); |
| else if (dt_node_is_compatible(psihb, "ibm,power10-psihb-x")) |
| psi = psi_probe_p10(chip, base); |
| else { |
| prerror("PSI: Unknown processor type\n"); |
| return false; |
| } |
| if (!psi) |
| return false; |
| |
| list_add(&psis, &psi->list); |
| |
| val = in_be64(psi->regs + PSIHB_CR); |
| if (val & PSIHB_CR_FSP_LINK_ACTIVE) { |
| lock(&psi_lock); |
| psi->active = true; |
| unlock(&psi_lock); |
| } |
| chip->psi = psi; |
| |
| if (dt_has_node_property(psihb, "no-lpc-interrupts", NULL)) |
| psi->no_lpc_irqs = true; |
| |
| psi_activate_phb(psi); |
| psi_init_interrupts(psi); |
| psi_create_mm_dtnode(psi); |
| |
| prlog(PR_INFO, "PSI[0x%03x]: Found PSI bridge [active=%d]\n", |
| psi->chip_id, psi->active); |
| return true; |
| } |
| |
| void psi_fsp_link_in_use(struct psi *psi __unused) |
| { |
| static bool poller_created = false; |
| |
| /* Do this once only */ |
| if (!poller_created) { |
| poller_created = true; |
| opal_add_poller(psi_link_poll, NULL); |
| } |
| } |
| |
| struct psi *psi_find_functional_chip(void) |
| { |
| return list_top(&psis, struct psi, list); |
| } |
| |
| void psi_init(void) |
| { |
| struct dt_node *np; |
| |
| dt_for_each_compatible(dt_root, np, "ibm,psihb-x") |
| psi_init_psihb(np); |
| } |
| |
| |