[Skiboot] [PATCH 4/5] lpc/uart: Support routing of selected LPC interrupts to Linux

Benjamin Herrenschmidt benh at kernel.crashing.org
Fri Feb 3 20:51:59 AEDT 2017


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 at kernel.crashing.org>
---
 core/init.c   |   3 +
 hw/bt.c       |   4 +-
 hw/lpc-mbox.c |   2 +-
 hw/lpc-uart.c |  48 ++++++++-----
 hw/lpc.c      | 213 ++++++++++++++++++++++++++++++++++++++++------------------
 hw/psi.c      |  24 ++++---
 include/lpc.h |   8 ++-
 include/psi.h |   1 +
 8 files changed, 210 insertions(+), 93 deletions(-)

diff --git a/core/init.c b/core/init.c
index ea6dafd..805f54d 100644
--- a/core/init.c
+++ b/core/init.c
@@ -948,6 +948,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 2ecc7d3..10990e9 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 7818943..1c59b1c 100644
--- a/hw/lpc-mbox.c
+++ b/hw/lpc-mbox.c
@@ -298,7 +298,7 @@ void mbox_init(void)
 
 	chip_id = dt_get_chip_id(n);
 	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 %d chipid and %d IRQ at 0x%08x\n", chip_id, irq, mbox.base);
 }
 
diff --git a/hw/lpc-uart.c b/hw/lpc-uart.c
index d063710..e536bbd 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 6dccf35..5df964b 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 */
@@ -651,13 +662,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)
@@ -679,28 +694,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)
@@ -741,12 +844,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;
@@ -1019,6 +1121,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);
 
@@ -1040,43 +1143,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);
@@ -1105,6 +1171,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);
 
@@ -1113,9 +1180,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);
 
@@ -1192,11 +1256,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);
@@ -1206,11 +1272,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 93b5a75..089f429 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 3e92d53..2347011 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 1a3e649..d51ab94 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);
-- 
2.9.3



More information about the Skiboot mailing list