Module Name:    src
Committed By:   jmcneill
Date:           Fri Jun  2 00:55:26 UTC 2017

Modified Files:
        src/sys/dev/fdt: fdt_intr.c

Log Message:
Rewrite interrupt-map support based on ePAPR spec.


To generate a diff of this commit:
cvs rdiff -u -r1.8 -r1.9 src/sys/dev/fdt/fdt_intr.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/fdt/fdt_intr.c
diff -u src/sys/dev/fdt/fdt_intr.c:1.8 src/sys/dev/fdt/fdt_intr.c:1.9
--- src/sys/dev/fdt/fdt_intr.c:1.8	Wed May 25 12:43:08 2016
+++ src/sys/dev/fdt/fdt_intr.c	Fri Jun  2 00:55:26 2017
@@ -1,4 +1,4 @@
-/* $NetBSD: fdt_intr.c,v 1.8 2016/05/25 12:43:08 jmcneill Exp $ */
+/* $NetBSD: fdt_intr.c,v 1.9 2017/06/02 00:55:26 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2015 Jared D. McNeill <[email protected]>
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: fdt_intr.c,v 1.8 2016/05/25 12:43:08 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: fdt_intr.c,v 1.9 2017/06/02 00:55:26 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -47,8 +47,8 @@ struct fdtbus_interrupt_controller {
 static struct fdtbus_interrupt_controller *fdtbus_ic = NULL;
 
 static bool has_interrupt_map(int phandle);
-static u_int *get_entry_from_map(int phandle, int pindex, u_int *spec);
 static u_int *get_specifier_by_index(int phandle, int pindex, u_int *spec);
+static u_int *get_specifier_from_map(int phandle, u_int *spec, u_int spec_len, int *piphandle);
 
 static int
 fdtbus_get_interrupt_parent(int phandle)
@@ -122,41 +122,22 @@ void *
 fdtbus_intr_establish(int phandle, u_int index, int ipl, int flags,
     int (*func)(void *), void *arg)
 {
-	void * result = NULL;
+	struct fdtbus_interrupt_controller *ic;
+	int ihandle = phandle;
 	u_int *specifier;
 	u_int spec_length;
-	int ihandle;
-	struct fdtbus_interrupt_controller *ic;
 
-	if (has_interrupt_map(phandle)) {
-		specifier = get_entry_from_map(phandle, index, &spec_length);
-		ihandle = be32toh(specifier[1]);
-		ihandle = fdtbus_get_phandle_from_native(ihandle);
-		specifier += 2;
-	} else {
-		specifier = get_specifier_by_index(phandle, index,
-						   &spec_length);
-		ihandle = phandle;
-	}
-	if (specifier == NULL) {
-		printf("%s: Unable to get specifier %d for phandle %d\n",
-		       __func__, index, phandle);
-		goto done;
-	}
-	ic = fdtbus_get_interrupt_controller(ihandle);
-	if (ic == NULL) {
-		printf("%s: Unable to get interrupt controller for %d\n",
-		       __func__, ihandle);
-		goto done;
-	}
-	result = ic->ic_funcs->establish(ic->ic_dev, specifier,
-					       ipl, flags, func, arg);
-done:
+	specifier = get_specifier_by_index(phandle, index, &spec_length);
 	if (has_interrupt_map(phandle))
-	    specifier -= 2;
-	if (specifier && spec_length > 0)
-		kmem_free(specifier, spec_length);
-	return result;
+		specifier = get_specifier_from_map(phandle, specifier,
+		    spec_length, &ihandle);
+
+	ic = fdtbus_get_interrupt_controller(ihandle);
+	if (ic == NULL)
+		return NULL;
+
+	return ic->ic_funcs->establish(ic->ic_dev, specifier,
+	    ipl, flags, func, arg);
 }
 
 void
@@ -173,166 +154,163 @@ fdtbus_intr_disestablish(int phandle, vo
 bool
 fdtbus_intr_str(int phandle, u_int index, char *buf, size_t buflen)
 {
-	bool result = false;
 	struct fdtbus_interrupt_controller *ic;
-	int ihandle;
+	int ihandle = phandle;
 	u_int *specifier;
 	u_int spec_length;
-	if (has_interrupt_map(phandle)) {
-		specifier = get_entry_from_map(phandle, index,
-			&spec_length);
-		ihandle = be32toh(specifier[1]);
-		ihandle = fdtbus_get_phandle_from_native(ihandle);
-		specifier += 2;
-	} else {
-		ihandle = phandle;
-		specifier = get_specifier_by_index(phandle, index,
-						   &spec_length);
-	}
-	if (specifier == NULL) {
-		printf("%s: Unable to get specifier %d for phandle %d\n",
-		       __func__, index, phandle);
-		goto done;
-	}
+
+	specifier = get_specifier_by_index(phandle, index, &spec_length);
+	if (has_interrupt_map(phandle))
+		specifier = get_specifier_from_map(phandle, specifier,
+		    spec_length, &ihandle);
+
 	ic = fdtbus_get_interrupt_controller(ihandle);
-	if (ic == NULL) {
-		printf("%s: Unable to get interrupt controller for %d\n",
-		       __func__, ihandle);
-		goto done;
+	if (ic == NULL)
+		return false;
+
+	return ic->ic_funcs->intrstr(ic->ic_dev, specifier, buf, buflen);
+}
+
+static int
+find_interrupt_map(int phandle)
+{
+	while (phandle > 0) {
+		if (of_hasprop(phandle, "interrupt-map"))
+			return phandle;
+		phandle = OF_parent(phandle);
 	}
-	result = ic->ic_funcs->intrstr(ic->ic_dev, specifier, buf, buflen);
-done:
-	if (has_interrupt_map(phandle))
-	    specifier -= 2;
-	if (specifier && spec_length > 0)
-		kmem_free(specifier, spec_length);
-	return result;
+	return -1;
+}
+
+static int
+find_address_cells(int phandle)
+{
+	uint32_t cells;
+
+	if (of_getprop_uint32(phandle, "#address-cells", &cells) != 0)
+		cells = 2;
+
+	return cells;
+}
+
+static int
+find_interrupt_cells(int phandle)
+{
+	uint32_t cells;
+
+	while (phandle > 0) {
+		if (of_getprop_uint32(phandle, "#interrupt-cells", &cells) == 0)
+			return cells;
+		phandle = OF_parent(phandle);
+	}
+	return 0;
 }
 
-/*
- * Devices that have multiple interrupts, connected to two or more
- * interrupt sources use an interrupt map rather than a simple
- * interrupt parent to indicate which interrupt controller goes with
- * which map.  The interrupt map is contained in the node describing
- * the first level parent and contains one entry per interrupt:
- *   index -- the index of the entry in the map
- *   &parent -- pointer to the node containing the actual interrupt parent
- *              for the specific interrupt
- *  [specifier 0 - specifier N-1] The N (usually 2 or 3) 32 bit words
- *              that make up the specifier.
- *
- * returns true if the device phandle has an interrupt-parent that
- * contains an interrupt-map.
- */
 static bool
 has_interrupt_map(int phandle)
 {
-	int ic_phandle;
-	of_getprop_uint32(phandle, "interrupt-parent", &ic_phandle);
-	ic_phandle = fdtbus_get_phandle_from_native(ic_phandle);
-	if (ic_phandle <= 0)
-		return false;
-	int len = OF_getproplen(ic_phandle, "interrupt-map");
-	if (len > 0)
-		return true;
-	return false;
+	return find_interrupt_map(OF_parent(phandle)) != -1;
 }
 
-/*
- * Walk the specifier map and return a pointer to the map entry
- * associated with pindex.  Return null if there is no entry.
- *
- * Because the length of the specifier depends on the interrupt
- * controller, we need to repeatedly obtain interrupt-celss for
- * the controller for the current index.
- *
- */
 static u_int *
-get_entry_from_map(int phandle, int pindex, u_int *spec_length)
+get_specifier_from_map(int phandle, u_int *specifier, u_int spec_length, int *piphandle)
 {
-	int intr_cells;
-	int intr_parent;
 	u_int *result = NULL;
 
-	of_getprop_uint32(phandle, "#interrupt-cells", &intr_cells);
-	of_getprop_uint32(phandle, "interrupt-parent", &intr_parent);
+	const int nexus_phandle = find_interrupt_map(phandle);
 
-	intr_parent = fdtbus_get_phandle_from_native(intr_parent);
-	int len = OF_getproplen(intr_parent, "interrupt-map");
+	int len = OF_getproplen(nexus_phandle, "interrupt-map");
 	if (len <= 0) {
 		printf("%s: no interrupt-map.\n", __func__);
 		return NULL;
 	}
-	uint resid = len;
+	int resid = len;
 	char *data = kmem_alloc(len, KM_SLEEP);
-	len = OF_getprop(intr_parent, "interrupt-map", data, len);
+	len = OF_getprop(nexus_phandle, "interrupt-map", data, len);
 	if (len <= 0) {
-		printf("%s:  can't get property interrupt-map.\n", __func__);
+		printf("%s: can't get property interrupt-map.\n", __func__);
 		goto done;
 	}
-	u_int *p = (u_int *)data;
 
+	/* child unit address: #address-cells prop of child bus node */
+	const int cua_cells = find_address_cells(nexus_phandle);
+	/* child interrupt specifier: #interrupt-cells of the nexus node */
+	const int cis_cells = find_interrupt_cells(nexus_phandle);
+
+	/* Offset (in cells) from map entry to child unit address specifier */
+	const u_int cua_off = 0;
+	/* Offset (in cells) from map entry to child interrupt specifier */
+	const u_int cis_off = cua_off + cua_cells;
+	/* Offset (in cells) from map entry to interrupt parent phandle */
+	const u_int ip_off = cis_off + cis_cells;
+	/* Offset (in cells) from map entry to parent unit specifier */
+	const u_int pus_off = ip_off + 1;
+
+#ifdef FDT_INTR_DEBUG
+	printf("cua_cells: %d, cis_cells: %d, ip_off = %d\n", cua_cells, cis_cells, ip_off);
+	printf("searching for interrupt in map:");
+	for (int i = 0; i < spec_length; i++)
+		printf(" %08x", specifier[i]);
+	printf("\n");
+#endif
+
+	u_int *p = (u_int *)data;
 	while (resid > 0) {
-		u_int index = be32toh(p[0]);
-		const u_int parent = fdtbus_get_phandle_from_native(be32toh(p[intr_cells]));
-		u_int pintr_cells;
-		of_getprop_uint32(parent, "#interrupt-cells", &pintr_cells);
-		if (index == pindex) {
-			result = kmem_alloc((pintr_cells + 2) * sizeof(u_int),
-					    KM_SLEEP);
-			*spec_length = (pintr_cells + 2) * sizeof (u_int);
-			for (int i = 0; i < pintr_cells + 2; i++)
-				result[i] =  p[i];
+		/* Interrupt parent phandle */
+		const u_int iparent = fdtbus_get_phandle_from_native(be32toh(p[ip_off]));
+
+		/* parent unit specifier: #address-cells of the interrupt parent */
+		const u_int pus_cells = find_address_cells(iparent);
+		/* parent interrupt specifier: #interrupt-cells of the interrupt parent */
+		const u_int pis_cells = find_interrupt_cells(iparent);
+
+		/* Offset (in cells) from map entry to parent interrupt specifier */
+		const u_int pis_off = pus_off + pus_cells;
+
+		if (cis_cells == spec_length && memcmp(&p[cis_off], specifier, spec_length * 4) == 0) {
+			const int slen = pus_cells + pis_cells;
+#ifdef FDT_INTR_DEBUG
+			printf(" intr map match iparent %08x slen %d:", iparent, slen);
+			for (int i = 0; i < slen; i++)
+				printf(" %08x", p[pus_off + i]);
+			printf("\n");
+#endif
+			result = kmem_alloc(slen, KM_SLEEP);
+			memcpy(result, &p[pus_off], slen * 4);
+			*piphandle = iparent;
 			goto done;
-									
 		}
 		/* Determine the length of the entry and skip that many
 		 * 32 bit words
 		 */
-		const u_int reclen = (intr_cells + pintr_cells + 1);
+		const u_int reclen = pis_off + pis_cells;
 		resid -= reclen * sizeof(u_int);
 		p += reclen;
 	}
+
 done:
 	kmem_free(data, len);
 	return result;
 }
 
-
-/*
- * Devices that don't connect to more than one interrupt source use
- * an array of specifiers.  Find the specifier that matches pindex
- * and return a pointer to it.
- *
- */
-static u_int *get_specifier_by_index(int phandle, int pindex,
-				     u_int *spec_length)
+static u_int *
+get_specifier_by_index(int phandle, int pindex, u_int *spec_length)
 {
 	u_int *specifiers;
 	u_int *specifier;
 	int interrupt_parent, interrupt_cells, len;
 
 	interrupt_parent = fdtbus_get_interrupt_parent(phandle);
-	if (interrupt_parent <= 0) {
-		printf("%s: interrupt_parent sanity check failed\n", __func__);
-		printf("%s: interrupt_parent = %d\n", __func__,
-		       interrupt_parent);
+	if (interrupt_parent <= 0)
 		return NULL;
-	}
 
-	len = OF_getprop(interrupt_parent, "#interrupt-cells",
-			 &interrupt_cells,  sizeof(interrupt_cells));
-	interrupt_cells = be32toh(interrupt_cells);
-	if (len != sizeof(interrupt_cells) || interrupt_cells <= 0) {
-		printf("%s: interrupt_cells sanity check failed\n", __func__);
+	interrupt_cells = find_interrupt_cells(interrupt_parent);
+	if (interrupt_cells <= 0)
 		return NULL;
-	}
 
 	len = OF_getproplen(phandle, "interrupts");
-	if (len <= 0) {
-		printf("%s: Couldn't get property interrupts\n", __func__);
+	if (len <= 0)
 		return NULL;
-	}
 
 	const u_int nintr = len / interrupt_cells;
 
@@ -346,7 +324,7 @@ static u_int *get_specifier_by_index(int
 		return NULL;
 	}
 	specifier = kmem_alloc(interrupt_cells * sizeof(u_int), KM_SLEEP);
-	*spec_length = interrupt_cells * sizeof(u_int);
+	*spec_length = interrupt_cells;
 	for (int i = 0; i < interrupt_cells; i++)
 		specifier[i] = specifiers[pindex * interrupt_cells + i];
 	kmem_free(specifiers, len);

Reply via email to