diff --git a/core/init.c b/core/init.c index 7bcb68096adc..58f96f471e01 100644 --- a/core/init.c +++ b/core/init.c @@ -954,6 +954,9 @@ void __noreturn __nomcount main_cpu_entry(const void *fdt) * regions after that */ + /* Create the LPC bus interrupt-map on P9 */ + lpc_finalize_interrupts(); + /* Add the list of interrupts going to OPAL */ add_opal_interrupts(); diff --git a/hw/bt.c b/hw/bt.c index 2ecc7d3e720e..10990e93c86f 100644 --- a/hw/bt.c +++ b/hw/bt.c @@ -27,6 +27,7 @@ #include #include #include +#include /* BT registers */ #define BT_CTRL 0 @@ -669,7 +670,8 @@ void bt_init(void) irq = dt_prop_get_u32(n, "interrupts"); bt_lpc_client.interrupts = LPC_IRQ(irq); - lpc_register_client(dt_get_chip_id(n), &bt_lpc_client); + lpc_register_client(dt_get_chip_id(n), &bt_lpc_client, + IRQ_ATTR_TARGET_OPAL); /* Enqueue an IPMI message to ask the BMC about its BT capabilities */ get_bt_caps(); diff --git a/hw/lpc-mbox.c b/hw/lpc-mbox.c index fb8852a5e197..ec101aed56f8 100644 --- a/hw/lpc-mbox.c +++ b/hw/lpc-mbox.c @@ -273,7 +273,7 @@ void mbox_init(void) chip_id = dt_get_chip_id(np); mbox_lpc_client.interrupts = LPC_IRQ(irq); - lpc_register_client(chip_id, &mbox_lpc_client); + lpc_register_client(chip_id, &mbox_lpc_client, IRQ_ATTR_TARGET_OPAL); prlog(PR_DEBUG, "Using chipid: %d and IRQ: %d at 0x%08x\n", chip_id, irq, mbox.base); } diff --git a/hw/lpc-uart.c b/hw/lpc-uart.c index d0637102f4d7..e536bbd9d20c 100644 --- a/hw/lpc-uart.c +++ b/hw/lpc-uart.c @@ -70,6 +70,7 @@ static uint8_t tx_room; static uint8_t cached_ier; static void *mmio_uart_base; static int uart_console_policy = UART_CONSOLE_OPAL; +static int lpc_irq = -1; void uart_set_console_policy(int policy) { @@ -423,15 +424,30 @@ static void uart_setup_os_passthrough(void) { char *path; + static struct lpc_client uart_lpc_os_client = { + }; + dt_add_property_strings(uart_node, "status", "ok"); path = dt_get_path(uart_node); dt_add_property_string(dt_chosen, "linux,stdout-path", path); free(path); + + /* Setup LPC client for OS interrupts */ + if (lpc_irq >= 0) { + uint32_t chip_id = dt_get_chip_id(uart_node); + uart_lpc_os_client.interrupts = LPC_IRQ(lpc_irq); + lpc_register_client(chip_id, &uart_lpc_os_client, + IRQ_ATTR_TARGET_LINUX); + } prlog(PR_DEBUG, "UART: Enabled as OS pass-through\n"); } static void uart_setup_opal_console(void) { + static struct lpc_client uart_lpc_opal_client = { + .interrupt = uart_irq, + }; + /* Add the opal console node */ add_opal_console_node(0, "raw", OUT_BUF_SIZE); @@ -444,6 +460,19 @@ static void uart_setup_opal_console(void) */ dt_add_property_strings(uart_node, "status", "reserved"); + /* Allocate an input buffer */ + in_buf = zalloc(IN_BUF_SIZE); + out_buf = zalloc(OUT_BUF_SIZE); + + /* Setup LPC client for OPAL interrupts */ + if (lpc_irq >= 0) { + uint32_t chip_id = dt_get_chip_id(uart_node); + uart_lpc_opal_client.interrupts = LPC_IRQ(lpc_irq); + lpc_register_client(chip_id, &uart_lpc_opal_client, + IRQ_ATTR_TARGET_OPAL); + has_irq = true; + } + /* * If the interrupt is enabled, turn on RX interrupts (and * only these for now @@ -451,10 +480,7 @@ static void uart_setup_opal_console(void) tx_full = rx_full = false; uart_update_ier(); - /* Allocate an input buffer */ - in_buf = zalloc(IN_BUF_SIZE); - out_buf = zalloc(OUT_BUF_SIZE); - + /* Start console poller */ opal_add_poller(uart_console_poll, NULL); } @@ -519,16 +545,11 @@ static bool uart_init_hw(unsigned int speed, unsigned int clock) return false; } -static struct lpc_client uart_lpc_client = { - .interrupt = uart_irq, -}; - void uart_init(void) { const struct dt_property *prop; struct dt_node *n; char *path __unused; - uint32_t chip_id; const uint32_t *irqp; /* UART lock is in the console path and thus must block @@ -580,13 +601,8 @@ void uart_init(void) uart_base = dt_property_get_cell(prop, 1); if (irqp) { - uint32_t irq = be32_to_cpu(*irqp); - - chip_id = dt_get_chip_id(uart_node); - uart_lpc_client.interrupts = LPC_IRQ(irq); - lpc_register_client(chip_id, &uart_lpc_client); - prlog(PR_DEBUG, "UART: Using LPC IRQ %d\n", irq); - has_irq = true; + lpc_irq = be32_to_cpu(*irqp); + prlog(PR_DEBUG, "UART: Using LPC IRQ %d\n", lpc_irq); } } diff --git a/hw/lpc.c b/hw/lpc.c index e86c9a359702..6bba61efed47 100644 --- a/hw/lpc.c +++ b/hw/lpc.c @@ -27,6 +27,7 @@ #include #include #include +#include //#define DBG_IRQ(fmt...) prerror(fmt) #define DBG_IRQ(fmt...) do { } while(0) @@ -119,6 +120,12 @@ DEFINE_LOG_ENTRY(OPAL_RC_LPC_SYNC_PERF, OPAL_PLATFORM_ERR_EVT, OPAL_LPC, #define LPC_NUM_SERIRQ 17 +enum { + LPC_ROUTE_FREE = 0, + LPC_ROUTE_OPAL, + LPC_ROUTE_LINUX +}; + struct lpcm { uint32_t chip_id; uint32_t xbase; @@ -129,7 +136,10 @@ struct lpcm { struct list_head clients; bool has_serirq; uint8_t sirq_routes[LPC_NUM_SERIRQ]; + bool sirq_routed[LPC_NUM_SERIRQ]; uint32_t sirq_rmasks[4]; + uint8_t sirq_ralloc[4]; + struct dt_node *node; }; @@ -138,6 +148,7 @@ struct lpcm { struct lpc_client_entry { struct list_node node; const struct lpc_client *clt; + uint32_t policy; }; /* Default LPC bus */ @@ -636,13 +647,17 @@ static void lpc_setup_serirq(struct lpcm *lpc) } } -static void __lpc_route_serirq(struct lpcm *lpc, uint32_t sirq, - uint32_t psi_idx) +static void lpc_route_serirq(struct lpcm *lpc, uint32_t sirq, + uint32_t psi_idx) { - uint32_t reg, shift, val; + uint32_t reg, shift, val, psi_old; int64_t rc; + psi_old = lpc->sirq_routes[sirq]; + lpc->sirq_rmasks[psi_old] &= ~(LPC_HC_IRQ_SERIRQ0 >> sirq); + lpc->sirq_rmasks[psi_idx] |= (LPC_HC_IRQ_SERIRQ0 >> sirq); lpc->sirq_routes[sirq] = psi_idx; + lpc->sirq_routed[sirq] = true; /* We may not be ready yet ... */ if (!lpc->has_serirq) @@ -664,28 +679,116 @@ static void __lpc_route_serirq(struct lpcm *lpc, uint32_t sirq, opb_write(lpc, opb_master_reg_base + reg, val, 4); } -void lpc_route_serirq(uint32_t chip_id, uint32_t sirq, uint32_t psi_idx) +static void lpc_alloc_route(struct lpcm *lpc, unsigned int irq, + unsigned int policy) { - struct proc_chip *chip; - struct lpcm *lpc; - uint32_t psi_old; + unsigned int i, r, c; + int route = -1; + + if (policy == IRQ_ATTR_TARGET_OPAL) + r = LPC_ROUTE_OPAL; + else + r = LPC_ROUTE_LINUX; - if (sirq >= LPC_NUM_SERIRQ) { - prerror("LPC[%03x]: Routing request for invalid SerIRQ %d\n", - chip_id, sirq); + prlog(PR_DEBUG, "LPC: Routing irq %d, policy: %d (r=%d)\n", + irq, policy, r); + + /* Are we already routed ? */ + if (lpc->sirq_routed[irq] && + r != lpc->sirq_ralloc[lpc->sirq_routes[irq]]) { + prerror("LPC: irq %d has conflicting policies\n", irq); return; } - chip = get_chip(chip_id); - if (!chip || !chip->lpc) + /* First try to find a free route. Leave one for another + * policy though + */ + for (i = 0, c = 0; i < 4; i++) { + /* Count routes with identical policy */ + if (lpc->sirq_ralloc[i] == r) + c++; + + /* Use the route if it's free and there is no more + * than 3 existing routes with that policy + */ + if (lpc->sirq_ralloc[i] == LPC_ROUTE_FREE && c < 4) { + lpc->sirq_ralloc[i] = r; + route = i; + break; + } + } + + /* If we couldn't get a free one, try to find an existing one + * with a matching policy + */ + for (i = 0; route < 0 && i < 4; i++) { + if (lpc->sirq_ralloc[i] == r) + route = i; + } + + /* Still no route ? bail. That should never happen */ + if (route < 0) { + prerror("LPC: Can't find a route for irq %d\n", irq); return; - lpc = chip->lpc; - lock(&lpc->lock); - psi_old = lpc->sirq_routes[sirq]; - lpc->sirq_rmasks[psi_old] &= ~(LPC_HC_IRQ_SERIRQ0 >> sirq); - lpc->sirq_rmasks[psi_idx] |= (LPC_HC_IRQ_SERIRQ0 >> sirq); - __lpc_route_serirq(lpc, sirq, psi_idx); - unlock(&lpc->lock); + } + + /* Program route */ + lpc_route_serirq(lpc, irq, route); + + prlog(PR_DEBUG, "LPC: SerIRQ %d using route %d targetted at %s\n", + irq, route, r == LPC_ROUTE_LINUX ? "OS" : "OPAL"); +} + +unsigned int lpc_get_irq_policy(uint32_t chip_id, uint32_t psi_idx) +{ + struct proc_chip *c = get_chip(chip_id); + + if (!c || !c->lpc) + return IRQ_ATTR_TARGET_LINUX; + + if (c->lpc->sirq_ralloc[psi_idx] == LPC_ROUTE_LINUX) + return IRQ_ATTR_TARGET_LINUX; + else + return IRQ_ATTR_TARGET_OPAL; +} + +static void lpc_create_int_map(struct lpcm *lpc, struct dt_node *psi_node) +{ + uint32_t map[LPC_NUM_SERIRQ * 5], *pmap; + uint32_t i; + + if (!psi_node) + return; + pmap = map; + for (i = 0; i < LPC_NUM_SERIRQ; i++) { + if (!lpc->sirq_routed[i]) + continue; + *(pmap++) = 0; + *(pmap++) = 0; + *(pmap++) = i; + *(pmap++) = psi_node->phandle; + *(pmap++) = lpc->sirq_routes[i] + P9_PSI_IRQ_LPC_SIRQ0; + } + if (pmap == map) + return; + dt_add_property(lpc->node, "interrupt-map", map, + (pmap - map) * sizeof(uint32_t)); + dt_add_property_cells(lpc->node, "interrupt-map-mask", 0, 0, 0xff); + dt_add_property_cells(lpc->node, "#interrupt-cells", 1); +} + +void lpc_finalize_interrupts(void) +{ + struct proc_chip *chip; + + lpc_irqs_ready = true; + + for_each_chip(chip) { + if (chip->lpc && chip->psi && + (chip->type == PROC_CHIP_P9_NIMBUS || + chip->type == PROC_CHIP_P9_CUMULUS)) + lpc_create_int_map(chip->lpc, chip->psi->node); + } } static void lpc_init_interrupts_one(struct proc_chip *chip) @@ -726,12 +829,11 @@ static void lpc_init_interrupts_one(struct proc_chip *chip) break; case PROC_CHIP_P9_NIMBUS: case PROC_CHIP_P9_CUMULUS: - /* On P9, we additionall setup the routing */ + /* On P9, we additionally setup the routing. */ lpc->has_serirq = true; for (i = 0; i < LPC_NUM_SERIRQ; i++) { - uint32_t pin = lpc->sirq_routes[i]; - __lpc_route_serirq(lpc, i, pin); - lpc->sirq_rmasks[pin] |= LPC_HC_IRQ_SERIRQ0 >> i; + if (lpc->sirq_routed[i]) + lpc_route_serirq(lpc, i, lpc->sirq_routes[i]); } lpc_setup_serirq(lpc); break; @@ -1004,6 +1106,7 @@ static void lpc_init_chip_p8(struct dt_node *xn) lpc->xbase = dt_get_address(xn, 0, NULL); lpc->fw_idsel = 0xff; lpc->fw_rdsz = 0xff; + lpc->node = xn; list_head_init(&lpc->clients); init_lock(&lpc->lock); @@ -1025,43 +1128,6 @@ static void lpc_init_chip_p8(struct dt_node *xn) chip->lpc = lpc; } -static void lpc_parse_interrupt_map(struct lpcm *lpc, struct dt_node *lpc_node) -{ - const u32 *imap; - size_t imap_size; - - imap = dt_prop_get_def_size(lpc_node, "interrupt-map", NULL, &imap_size); - if (!imap) - return; - imap_size >>= 2; - if (imap_size % 5) { - prerror("LPC[%03x]: Odd format for LPC interrupt-map !\n", - lpc->chip_id); - return; - } - - while(imap_size >= 5) { - uint32_t sirq = be32_to_cpu(imap[2]); - uint32_t pirq = be32_to_cpu(imap[4]); - - if (sirq >= LPC_NUM_SERIRQ) { - prerror("LPC[%03x]: LPC irq %d out of range in" - " interrupt-map\n", lpc->chip_id, sirq); - } else if (pirq < P9_PSI_IRQ_LPC_SIRQ0 || - pirq > P9_PSI_IRQ_LPC_SIRQ3) { - prerror("LPC[%03x]: PSI irq %d out of range in" - " interrupt-map\n", lpc->chip_id, pirq); - } else { - uint32_t pin = pirq - P9_PSI_IRQ_LPC_SIRQ0; - lpc->sirq_routes[sirq] = pin; - prlog(PR_INFO, "LPC[%03x]: SerIRQ %d routed to PSI input %d\n", - lpc->chip_id, sirq, pin); - } - imap += 5; - imap_size -= 5; - } -} - static void lpc_init_chip_p9(struct dt_node *opb_node) { uint32_t gcid = dt_get_chip_id(opb_node); @@ -1090,6 +1156,7 @@ static void lpc_init_chip_p9(struct dt_node *opb_node) lpc->mbase = (void *)addr; lpc->fw_idsel = 0xff; lpc->fw_rdsz = 0xff; + lpc->node = lpc_node; list_head_init(&lpc->clients); init_lock(&lpc->lock); @@ -1098,9 +1165,6 @@ static void lpc_init_chip_p9(struct dt_node *opb_node) lpc_default_chip_id = gcid; } - /* Parse interrupt map if any to setup initial routing */ - lpc_parse_interrupt_map(lpc, lpc_node); - /* Mask all interrupts for now */ opb_write(lpc, lpc_reg_opb_base + LPC_HC_IRQMASK, 0, 4); @@ -1177,11 +1241,13 @@ bool lpc_ok(void) } void lpc_register_client(uint32_t chip_id, - const struct lpc_client *clt) + const struct lpc_client *clt, + uint32_t policy) { struct lpc_client_entry *ent; struct proc_chip *chip; struct lpcm *lpc; + bool has_routes; chip = get_chip(chip_id); assert(chip); @@ -1191,11 +1257,30 @@ void lpc_register_client(uint32_t chip_id, chip_id); return; } + + has_routes = + chip->type == PROC_CHIP_P9_NIMBUS || + chip->type == PROC_CHIP_P9_CUMULUS; + + if (policy != IRQ_ATTR_TARGET_OPAL && !has_routes) { + prerror("LPC: Chip doesn't support OS interrupt policy\n"); + return; + } + ent = malloc(sizeof(*ent)); assert(ent); ent->clt = clt; + ent->policy = policy; lock(&lpc->lock); list_add(&lpc->clients, &ent->node); + + if (has_routes) { + unsigned int i; + for (i = 0; i < LPC_NUM_SERIRQ; i++) + if (clt->interrupts & LPC_IRQ(i)) + lpc_alloc_route(lpc, i, policy); + } + if (lpc->has_serirq) lpc_setup_serirq(lpc); unlock(&lpc->lock); diff --git a/hw/psi.c b/hw/psi.c index 93b5a75c0486..089f42962eff 100644 --- a/hw/psi.c +++ b/hw/psi.c @@ -613,21 +613,26 @@ static uint64_t psi_p9_irq_attributes(struct irq_source *is __unused, { 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 (psi->no_lpc_irqs && - (idx == P9_PSI_IRQ_LPC_SIRQ0 || - idx == P9_PSI_IRQ_LPC_SIRQ1 || - idx == P9_PSI_IRQ_LPC_SIRQ2 || - idx == P9_PSI_IRQ_LPC_SIRQ3 || - idx == P9_PSI_IRQ_LPCHC)) + if (is_lpc_serirq && psi->no_lpc_irqs) return IRQ_ATTR_TARGET_LINUX; - /* XXX For now, all go to OPAL, this will change */ - return IRQ_ATTR_TARGET_OPAL | IRQ_ATTR_TARGET_FREQUENT; + /* 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); + + return IRQ_ATTR_TARGET_OPAL; } static char *psi_p9_irq_name(struct irq_source *is, uint32_t isn) @@ -940,6 +945,8 @@ static void psi_create_p9_int_map(struct psi *psi, struct dt_node *np) map[i][3] = 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) @@ -973,6 +980,7 @@ static void psi_create_mm_dtnode(struct psi *psi) 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) diff --git a/include/lpc.h b/include/lpc.h index 3e92d537cf13..2347011d3657 100644 --- a/include/lpc.h +++ b/include/lpc.h @@ -60,6 +60,7 @@ extern void lpc_init(void); extern void lpc_init_interrupts(void); +extern void lpc_finalize_interrupts(void); /* Check for a default bus */ extern bool lpc_present(void); @@ -93,10 +94,11 @@ struct lpc_client { #define LPC_IRQ(n) (0x80000000 >> (n)) }; -extern void lpc_register_client(uint32_t chip_id, const struct lpc_client *clt); +extern void lpc_register_client(uint32_t chip_id, const struct lpc_client *clt, + uint32_t policy); -/* Manual control of routing on P9 for use by platforms if necessary */ -extern void lpc_route_serirq(uint32_t chip_id, uint32_t sirq, uint32_t psi_idx); +/* Return the policy for a given serirq */ +extern unsigned int lpc_get_irq_policy(uint32_t chip_id, uint32_t psi_idx); /* Clear SerIRQ latch on P9 DD1 */ extern void lpc_p9_sirq_eoi(uint32_t chip_id, uint32_t index); diff --git a/include/psi.h b/include/psi.h index 1a3e649fa4c2..d51ab9465e2b 100644 --- a/include/psi.h +++ b/include/psi.h @@ -246,6 +246,7 @@ struct psi { unsigned int interrupt; bool active; bool no_lpc_irqs; + struct dt_node *node; }; extern void psi_set_link_polling(bool active);