summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>2017-02-03 20:51:59 +1100
committerStewart Smith <stewart@linux.vnet.ibm.com>2017-03-03 09:40:40 +1100
commita2940770ca6da2c58081f8207aded197e45f6cd6 (patch)
treef51be7c9dbe321fa6a270d88b502bf70269c6f9d
parent95c59d192376a4360278393da5df53e21ae3fc72 (diff)
downloadblackbird-skiboot-a2940770ca6da2c58081f8207aded197e45f6cd6.tar.gz
blackbird-skiboot-a2940770ca6da2c58081f8207aded197e45f6cd6.zip
lpc/uart: Support routing of selected LPC interrupts to Linux
Each LPC interrupt can be routed to one of 4 lines to the PSI bridge which represent 4 different system interrupts. This allows LPC clients to request as specific target (Linux or OPAL) and makes the LPC core pick a route and configure it appropriately. The UART is updated to properly forward interrupts to Linux if necessary Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org> Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
-rw-r--r--core/init.c3
-rw-r--r--hw/bt.c4
-rw-r--r--hw/lpc-mbox.c2
-rw-r--r--hw/lpc-uart.c48
-rw-r--r--hw/lpc.c213
-rw-r--r--hw/psi.c24
-rw-r--r--include/lpc.h8
-rw-r--r--include/psi.h1
8 files changed, 210 insertions, 93 deletions
diff --git a/core/init.c b/core/init.c
index 7bcb6809..58f96f47 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 2ecc7d3e..10990e93 100644
--- a/hw/bt.c
+++ b/hw/bt.c
@@ -27,6 +27,7 @@
#include <ipmi.h>
#include <timebase.h>
#include <chip.h>
+#include <interrupts.h>
/* 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 fb8852a5..ec101aed 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 d0637102..e536bbd9 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 e86c9a35..6bba61ef 100644
--- a/hw/lpc.c
+++ b/hw/lpc.c
@@ -27,6 +27,7 @@
#include <opal-api.h>
#include <platform.h>
#include <psi.h>
+#include <interrupts.h>
//#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 93b5a75c..089f4296 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 3e92d537..2347011d 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 1a3e649f..d51ab946 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);
OpenPOWER on IntegriCloud