1 /* $NetBSD: fdt_intr.c,v 1.7 2016/01/10 23:01:29 marty Exp $ */ 2 3 /*- 4 * Copyright (c) 2015 Jared D. McNeill <jmcneill@invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __KERNEL_RCSID(0, "$NetBSD: fdt_intr.c,v 1.7 2016/01/10 23:01:29 marty Exp $"); 31 32 #include <sys/param.h> 33 #include <sys/bus.h> 34 #include <sys/kmem.h> 35 36 #include <libfdt.h> 37 #include <dev/fdt/fdtvar.h> 38 39 struct fdtbus_interrupt_controller { 40 device_t ic_dev; 41 int ic_phandle; 42 const struct fdtbus_interrupt_controller_func *ic_funcs; 43 44 struct fdtbus_interrupt_controller *ic_next; 45 }; 46 47 static struct fdtbus_interrupt_controller *fdtbus_ic = NULL; 48 49 static bool has_interrupt_map(int phandle); 50 static u_int *get_entry_from_map(int phandle, int pindex, u_int *spec); 51 static u_int *get_specifier_by_index(int phandle, int pindex, u_int *spec); 52 53 static int 54 fdtbus_get_interrupt_parent(int phandle) 55 { 56 u_int interrupt_parent; 57 58 while (phandle >= 0) { 59 if (of_getprop_uint32(phandle, "interrupt-parent", 60 &interrupt_parent) == 0) { 61 break; 62 } 63 if (phandle == 0) { 64 return -1; 65 } 66 phandle = OF_parent(phandle); 67 } 68 if (phandle < 0) { 69 return -1; 70 } 71 72 const void *data = fdtbus_get_data(); 73 const int off = fdt_node_offset_by_phandle(data, interrupt_parent); 74 if (off < 0) { 75 return -1; 76 } 77 78 return fdtbus_offset2phandle(off); 79 } 80 81 static struct fdtbus_interrupt_controller * 82 fdtbus_get_interrupt_controller_ic(int phandle) 83 { 84 struct fdtbus_interrupt_controller * ic; 85 for (ic = fdtbus_ic; ic; ic = ic->ic_next) { 86 if (ic->ic_phandle == phandle) { 87 return ic; 88 } 89 } 90 return NULL; 91 } 92 93 static struct fdtbus_interrupt_controller * 94 fdtbus_get_interrupt_controller(int phandle) 95 { 96 const int ic_phandle = fdtbus_get_interrupt_parent(phandle); 97 if (ic_phandle < 0) { 98 return NULL; 99 } 100 101 return fdtbus_get_interrupt_controller_ic(ic_phandle); 102 } 103 104 int 105 fdtbus_register_interrupt_controller(device_t dev, int phandle, 106 const struct fdtbus_interrupt_controller_func *funcs) 107 { 108 struct fdtbus_interrupt_controller *ic; 109 110 ic = kmem_alloc(sizeof(*ic), KM_SLEEP); 111 ic->ic_dev = dev; 112 ic->ic_phandle = phandle; 113 ic->ic_funcs = funcs; 114 115 ic->ic_next = fdtbus_ic; 116 fdtbus_ic = ic; 117 118 return 0; 119 } 120 121 void * 122 fdtbus_intr_establish(int phandle, u_int index, int ipl, int flags, 123 int (*func)(void *), void *arg) 124 { 125 void * result = NULL; 126 u_int *specifier; 127 u_int spec_length; 128 int ihandle; 129 struct fdtbus_interrupt_controller *ic; 130 131 if (has_interrupt_map(phandle)) { 132 specifier = get_entry_from_map(phandle, index, &spec_length); 133 ihandle = be32toh(specifier[1]); 134 ihandle = fdtbus_get_phandle_from_native(ihandle); 135 specifier += 2; 136 } else { 137 specifier = get_specifier_by_index(phandle, index, 138 &spec_length); 139 ihandle = phandle; 140 } 141 if (specifier == NULL) { 142 printf("%s: Unable to get specifier %d for phandle %d\n", 143 __func__, index, phandle); 144 goto done; 145 } 146 ic = fdtbus_get_interrupt_controller(ihandle); 147 if (ic == NULL) { 148 printf("%s: Unable to get interrupt controller for %d\n", 149 __func__, ihandle); 150 goto done; 151 } 152 result = ic->ic_funcs->establish(ic->ic_dev, specifier, 153 ipl, flags, func, arg); 154 done: 155 if (has_interrupt_map(phandle)) 156 specifier -= 2; 157 if (specifier && spec_length > 0) 158 kmem_free(specifier, spec_length); 159 return result; 160 } 161 162 void 163 fdtbus_intr_disestablish(int phandle, void *ih) 164 { 165 struct fdtbus_interrupt_controller *ic; 166 167 ic = fdtbus_get_interrupt_controller(phandle); 168 KASSERT(ic != NULL); 169 170 return ic->ic_funcs->disestablish(ic->ic_dev, ih); 171 } 172 173 bool 174 fdtbus_intr_str(int phandle, u_int index, char *buf, size_t buflen) 175 { 176 bool result = false; 177 struct fdtbus_interrupt_controller *ic; 178 int ihandle; 179 u_int *specifier; 180 u_int spec_length; 181 if (has_interrupt_map(phandle)) { 182 specifier = get_entry_from_map(phandle, index, 183 &spec_length); 184 ihandle = be32toh(specifier[1]); 185 ihandle = fdtbus_get_phandle_from_native(ihandle); 186 specifier += 2; 187 } else { 188 ihandle = phandle; 189 specifier = get_specifier_by_index(phandle, index, 190 &spec_length); 191 } 192 if (specifier == NULL) { 193 printf("%s: Unable to get specifier %d for phandle %d\n", 194 __func__, index, phandle); 195 goto done; 196 } 197 ic = fdtbus_get_interrupt_controller(ihandle); 198 if (ic == NULL) { 199 printf("%s: Unable to get interrupt controller for %d\n", 200 __func__, ihandle); 201 goto done; 202 } 203 result = ic->ic_funcs->intrstr(ic->ic_dev, specifier, buf, buflen); 204 done: 205 if (has_interrupt_map(phandle)) 206 specifier -= 2; 207 if (specifier && spec_length > 0) 208 kmem_free(specifier, spec_length); 209 return result; 210 } 211 212 /* 213 * Devices that have multiple interrupts, connected to two or more 214 * interrupt sources use an interrupt map rather than a simple 215 * interrupt parent to indicate which interrupt controller goes with 216 * which map. The interrupt map is contained in the node describing 217 * the first level parent and contains one entry per interrupt: 218 * index -- the index of the entry in the map 219 * &parent -- pointer to the node containing the actual interrupt parent 220 * for the specific interrupt 221 * [specifier 0 - specifier N-1] The N (usually 2 or 3) 32 bit words 222 * that make up the specifier. 223 * 224 * returns true if the device phandle has an interrupt-parent that 225 * contains an interrupt-map. 226 */ 227 static bool 228 has_interrupt_map(int phandle) 229 { 230 int ic_phandle; 231 of_getprop_uint32(phandle, "interrupt-parent", &ic_phandle); 232 ic_phandle = fdtbus_get_phandle_from_native(ic_phandle); 233 if (ic_phandle <= 0) 234 return false; 235 int len = OF_getproplen(ic_phandle, "interrupt-map"); 236 if (len > 0) 237 return true; 238 return false; 239 } 240 241 /* 242 * Walk the specifier map and return a pointer to the map entry 243 * associated with pindex. Return null if there is no entry. 244 * 245 * Because the length of the specifier depends on the interrupt 246 * controller, we need to repeatedly obtain interrupt-celss for 247 * the controller for the current index. 248 * 249 */ 250 static u_int * 251 get_entry_from_map(int phandle, int pindex, u_int *spec_length) 252 { 253 int intr_cells; 254 int intr_parent; 255 u_int *result = NULL; 256 257 of_getprop_uint32(phandle, "#interrupt-cells", &intr_cells); 258 of_getprop_uint32(phandle, "interrupt-parent", &intr_parent); 259 260 intr_parent = fdtbus_get_phandle_from_native(intr_parent); 261 int len = OF_getproplen(intr_parent, "interrupt-map"); 262 if (len <= 0) { 263 printf("%s: no interrupt-map.\n", __func__); 264 return NULL; 265 } 266 uint resid = len; 267 char *data = kmem_alloc(len, KM_SLEEP); 268 len = OF_getprop(intr_parent, "interrupt-map", data, len); 269 if (len <= 0) { 270 printf("%s: can't get property interrupt-map.\n", __func__); 271 goto done; 272 } 273 u_int *p = (u_int *)data; 274 275 while (resid > 0) { 276 u_int index = be32toh(p[0]); 277 const u_int parent = fdtbus_get_phandle_from_native(be32toh(p[intr_cells])); 278 u_int pintr_cells; 279 of_getprop_uint32(parent, "#interrupt-cells", &pintr_cells); 280 if (index == pindex) { 281 result = kmem_alloc((pintr_cells + 2) * sizeof(u_int), 282 KM_SLEEP); 283 *spec_length = (pintr_cells + 2) * sizeof (u_int); 284 for (int i = 0; i < pintr_cells + 2; i++) 285 result[i] = p[i]; 286 goto done; 287 288 } 289 /* Determine the length of the entry and skip that many 290 * 32 bit words 291 */ 292 const u_int reclen = (intr_cells + pintr_cells + 1); 293 resid -= reclen * sizeof(u_int); 294 p += reclen; 295 } 296 done: 297 kmem_free(data, len); 298 return result; 299 } 300 301 302 /* 303 * Devices that don't connect to more than one interrupt source use 304 * an array of specifiers. Find the specifier that matches pindex 305 * and return a pointer to it. 306 * 307 */ 308 static u_int *get_specifier_by_index(int phandle, int pindex, 309 u_int *spec_length) 310 { 311 u_int *specifiers; 312 u_int *specifier; 313 int interrupt_parent, interrupt_cells, len; 314 315 interrupt_parent = fdtbus_get_interrupt_parent(phandle); 316 if (interrupt_parent <= 0) { 317 printf("%s: interrupt_parent sanity check failed\n", __func__); 318 printf("%s: interrupt_parent = %d\n", __func__, 319 interrupt_parent); 320 return NULL; 321 } 322 323 len = OF_getprop(interrupt_parent, "#interrupt-cells", 324 &interrupt_cells, sizeof(interrupt_cells)); 325 interrupt_cells = be32toh(interrupt_cells); 326 if (len != sizeof(interrupt_cells) || interrupt_cells <= 0) { 327 printf("%s: interrupt_cells sanity check failed\n", __func__); 328 return NULL; 329 } 330 331 len = OF_getproplen(phandle, "interrupts"); 332 if (len <= 0) { 333 printf("%s: Couldn't get property interrupts\n", __func__); 334 return NULL; 335 } 336 337 const u_int clen = interrupt_cells * sizeof(u_int); 338 const u_int nintr = len / interrupt_cells; 339 340 if (pindex >= nintr) 341 return NULL; 342 343 specifiers = kmem_alloc(len, KM_SLEEP); 344 345 if (OF_getprop(phandle, "interrupts", specifiers, len) != len) { 346 kmem_free(specifiers, len); 347 return NULL; 348 } 349 specifier = kmem_alloc(interrupt_cells * sizeof(u_int), KM_SLEEP); 350 *spec_length = interrupt_cells * sizeof(u_int); 351 for (int i = 0; i < interrupt_cells; i++) 352 specifier[i] = specifiers[pindex * clen + i]; 353 kmem_free(specifiers, len); 354 return specifier; 355 } 356