]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/ofw/ofw_bus_subr.c
Merge llvm trunk r238337 from ^/vendor/llvm/dist, resolve conflicts, and
[FreeBSD/FreeBSD.git] / sys / dev / ofw / ofw_bus_subr.c
1 /*-
2  * Copyright (c) 2001 - 2003 by Thomas Moestl <tmm@FreeBSD.org>.
3  * Copyright (c) 2005 Marius Strobl <marius@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions, and the following disclaimer,
11  *    without modification, immediately at the beginning of the file.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
21  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32
33 #include "opt_platform.h"
34 #include <sys/param.h>
35 #include <sys/systm.h>
36 #include <sys/bus.h>
37 #include <sys/errno.h>
38 #include <sys/libkern.h>
39
40 #include <machine/resource.h>
41
42 #include <dev/ofw/ofw_bus.h>
43 #include <dev/ofw/ofw_bus_subr.h>
44 #include <dev/ofw/openfirm.h>
45
46 #include "ofw_bus_if.h"
47
48 int
49 ofw_bus_gen_setup_devinfo(struct ofw_bus_devinfo *obd, phandle_t node)
50 {
51
52         if (obd == NULL)
53                 return (ENOMEM);
54         /* The 'name' property is considered mandatory. */
55         if ((OF_getprop_alloc(node, "name", 1, (void **)&obd->obd_name)) == -1)
56                 return (EINVAL);
57         OF_getprop_alloc(node, "compatible", 1, (void **)&obd->obd_compat);
58         OF_getprop_alloc(node, "device_type", 1, (void **)&obd->obd_type);
59         OF_getprop_alloc(node, "model", 1, (void **)&obd->obd_model);
60         OF_getprop_alloc(node, "status", 1, (void **)&obd->obd_status);
61         obd->obd_node = node;
62         return (0);
63 }
64
65 void
66 ofw_bus_gen_destroy_devinfo(struct ofw_bus_devinfo *obd)
67 {
68
69         if (obd == NULL)
70                 return;
71         if (obd->obd_compat != NULL)
72                 free(obd->obd_compat, M_OFWPROP);
73         if (obd->obd_model != NULL)
74                 free(obd->obd_model, M_OFWPROP);
75         if (obd->obd_name != NULL)
76                 free(obd->obd_name, M_OFWPROP);
77         if (obd->obd_type != NULL)
78                 free(obd->obd_type, M_OFWPROP);
79         if (obd->obd_status != NULL)
80                 free(obd->obd_status, M_OFWPROP);
81 }
82
83 int
84 ofw_bus_gen_child_pnpinfo_str(device_t cbdev, device_t child, char *buf,
85     size_t buflen)
86 {
87
88         if (ofw_bus_get_name(child) != NULL) {
89                 strlcat(buf, "name=", buflen);
90                 strlcat(buf, ofw_bus_get_name(child), buflen);
91         }
92
93         if (ofw_bus_get_compat(child) != NULL) {
94                 strlcat(buf, " compat=", buflen);
95                 strlcat(buf, ofw_bus_get_compat(child), buflen);
96         }
97         return (0);
98 };
99
100 const char *
101 ofw_bus_gen_get_compat(device_t bus, device_t dev)
102 {
103         const struct ofw_bus_devinfo *obd;
104
105         obd = OFW_BUS_GET_DEVINFO(bus, dev);
106         if (obd == NULL)
107                 return (NULL);
108         return (obd->obd_compat);
109 }
110
111 const char *
112 ofw_bus_gen_get_model(device_t bus, device_t dev)
113 {
114         const struct ofw_bus_devinfo *obd;
115
116         obd = OFW_BUS_GET_DEVINFO(bus, dev);
117         if (obd == NULL)
118                 return (NULL);
119         return (obd->obd_model);
120 }
121
122 const char *
123 ofw_bus_gen_get_name(device_t bus, device_t dev)
124 {
125         const struct ofw_bus_devinfo *obd;
126
127         obd = OFW_BUS_GET_DEVINFO(bus, dev);
128         if (obd == NULL)
129                 return (NULL);
130         return (obd->obd_name);
131 }
132
133 phandle_t
134 ofw_bus_gen_get_node(device_t bus, device_t dev)
135 {
136         const struct ofw_bus_devinfo *obd;
137
138         obd = OFW_BUS_GET_DEVINFO(bus, dev);
139         if (obd == NULL)
140                 return (0);
141         return (obd->obd_node);
142 }
143
144 const char *
145 ofw_bus_gen_get_type(device_t bus, device_t dev)
146 {
147         const struct ofw_bus_devinfo *obd;
148
149         obd = OFW_BUS_GET_DEVINFO(bus, dev);
150         if (obd == NULL)
151                 return (NULL);
152         return (obd->obd_type);
153 }
154
155 const char *
156 ofw_bus_get_status(device_t dev)
157 {
158         const struct ofw_bus_devinfo *obd;
159
160         obd = OFW_BUS_GET_DEVINFO(device_get_parent(dev), dev);
161         if (obd == NULL)
162                 return (NULL);
163
164         return (obd->obd_status);
165 }
166
167 int
168 ofw_bus_status_okay(device_t dev)
169 {
170         const char *status;
171
172         status = ofw_bus_get_status(dev);
173         if (status == NULL || strcmp(status, "okay") == 0)
174                 return (1);
175         
176         return (0);
177 }
178
179 static int
180 ofw_bus_node_is_compatible(const char *compat, int len, const char *onecompat)
181 {
182         int onelen, l, ret;
183
184         onelen = strlen(onecompat);
185
186         ret = 0;
187         while (len > 0) {
188                 if (strlen(compat) == onelen &&
189                     strncasecmp(compat, onecompat, onelen) == 0) {
190                         /* Found it. */
191                         ret = 1;
192                         break;
193                 }
194
195                 /* Slide to the next sub-string. */
196                 l = strlen(compat) + 1;
197                 compat += l;
198                 len -= l;
199         }
200
201         return (ret);
202 }
203
204 int
205 ofw_bus_is_compatible(device_t dev, const char *onecompat)
206 {
207         phandle_t node;
208         const char *compat;
209         int len;
210
211         if ((compat = ofw_bus_get_compat(dev)) == NULL)
212                 return (0);
213
214         if ((node = ofw_bus_get_node(dev)) == -1)
215                 return (0);
216
217         /* Get total 'compatible' prop len */
218         if ((len = OF_getproplen(node, "compatible")) <= 0)
219                 return (0);
220
221         return (ofw_bus_node_is_compatible(compat, len, onecompat));
222 }
223
224 int
225 ofw_bus_is_compatible_strict(device_t dev, const char *compatible)
226 {
227         const char *compat;
228         size_t len;
229
230         if ((compat = ofw_bus_get_compat(dev)) == NULL)
231                 return (0);
232
233         len = strlen(compatible);
234         if (strlen(compat) == len &&
235             strncasecmp(compat, compatible, len) == 0)
236                 return (1);
237
238         return (0);
239 }
240
241 const struct ofw_compat_data *
242 ofw_bus_search_compatible(device_t dev, const struct ofw_compat_data *compat)
243 {
244
245         if (compat == NULL)
246                 return NULL;
247
248         for (; compat->ocd_str != NULL; ++compat) {
249                 if (ofw_bus_is_compatible(dev, compat->ocd_str))
250                         break;
251         }
252
253         return (compat);
254 }
255
256 int
257 ofw_bus_has_prop(device_t dev, const char *propname)
258 {
259         phandle_t node;
260
261         if ((node = ofw_bus_get_node(dev)) == -1)
262                 return (0);
263
264         return (OF_hasprop(node, propname));
265 }
266
267 void
268 ofw_bus_setup_iinfo(phandle_t node, struct ofw_bus_iinfo *ii, int intrsz)
269 {
270         pcell_t addrc;
271         int msksz;
272
273         if (OF_getencprop(node, "#address-cells", &addrc, sizeof(addrc)) == -1)
274                 addrc = 2;
275         ii->opi_addrc = addrc * sizeof(pcell_t);
276
277         ii->opi_imapsz = OF_getencprop_alloc(node, "interrupt-map", 1,
278             (void **)&ii->opi_imap);
279         if (ii->opi_imapsz > 0) {
280                 msksz = OF_getencprop_alloc(node, "interrupt-map-mask", 1,
281                     (void **)&ii->opi_imapmsk);
282                 /*
283                  * Failure to get the mask is ignored; a full mask is used
284                  * then.  We barf on bad mask sizes, however.
285                  */
286                 if (msksz != -1 && msksz != ii->opi_addrc + intrsz)
287                         panic("ofw_bus_setup_iinfo: bad interrupt-map-mask "
288                             "property!");
289         }
290 }
291
292 int
293 ofw_bus_lookup_imap(phandle_t node, struct ofw_bus_iinfo *ii, void *reg,
294     int regsz, void *pintr, int pintrsz, void *mintr, int mintrsz,
295     phandle_t *iparent)
296 {
297         uint8_t maskbuf[regsz + pintrsz];
298         int rv;
299
300         if (ii->opi_imapsz <= 0)
301                 return (0);
302         KASSERT(regsz >= ii->opi_addrc,
303             ("ofw_bus_lookup_imap: register size too small: %d < %d",
304                 regsz, ii->opi_addrc));
305         if (node != -1) {
306                 rv = OF_getencprop(node, "reg", reg, regsz);
307                 if (rv < regsz)
308                         panic("ofw_bus_lookup_imap: cannot get reg property");
309         }
310         return (ofw_bus_search_intrmap(pintr, pintrsz, reg, ii->opi_addrc,
311             ii->opi_imap, ii->opi_imapsz, ii->opi_imapmsk, maskbuf, mintr,
312             mintrsz, iparent));
313 }
314
315 /*
316  * Map an interrupt using the firmware reg, interrupt-map and
317  * interrupt-map-mask properties.
318  * The interrupt property to be mapped must be of size intrsz, and pointed to
319  * by intr.  The regs property of the node for which the mapping is done must
320  * be passed as regs. This property is an array of register specifications;
321  * the size of the address part of such a specification must be passed as
322  * physsz.  Only the first element of the property is used.
323  * imap and imapsz hold the interrupt mask and it's size.
324  * imapmsk is a pointer to the interrupt-map-mask property, which must have
325  * a size of physsz + intrsz; it may be NULL, in which case a full mask is
326  * assumed.
327  * maskbuf must point to a buffer of length physsz + intrsz.
328  * The interrupt is returned in result, which must point to a buffer of length
329  * rintrsz (which gives the expected size of the mapped interrupt).
330  * Returns number of cells in the interrupt if a mapping was found, 0 otherwise.
331  */
332 int
333 ofw_bus_search_intrmap(void *intr, int intrsz, void *regs, int physsz,
334     void *imap, int imapsz, void *imapmsk, void *maskbuf, void *result,
335     int rintrsz, phandle_t *iparent)
336 {
337         phandle_t parent;
338         uint8_t *ref = maskbuf;
339         uint8_t *uiintr = intr;
340         uint8_t *uiregs = regs;
341         uint8_t *uiimapmsk = imapmsk;
342         uint8_t *mptr;
343         pcell_t pintrsz;
344         int i, rsz, tsz;
345
346         rsz = -1;
347         if (imapmsk != NULL) {
348                 for (i = 0; i < physsz; i++)
349                         ref[i] = uiregs[i] & uiimapmsk[i];
350                 for (i = 0; i < intrsz; i++)
351                         ref[physsz + i] = uiintr[i] & uiimapmsk[physsz + i];
352         } else {
353                 bcopy(regs, ref, physsz);
354                 bcopy(intr, ref + physsz, intrsz);
355         }
356
357         mptr = imap;
358         i = imapsz;
359         while (i > 0) {
360                 bcopy(mptr + physsz + intrsz, &parent, sizeof(parent));
361                 if (OF_searchencprop(OF_node_from_xref(parent),
362                     "#interrupt-cells", &pintrsz, sizeof(pintrsz)) == -1)
363                         pintrsz = 1;    /* default */
364                 pintrsz *= sizeof(pcell_t);
365
366                 /* Compute the map stride size. */
367                 tsz = physsz + intrsz + sizeof(phandle_t) + pintrsz;
368                 KASSERT(i >= tsz, ("ofw_bus_search_intrmap: truncated map"));
369
370                 if (bcmp(ref, mptr, physsz + intrsz) == 0) {
371                         bcopy(mptr + physsz + intrsz + sizeof(parent),
372                             result, MIN(rintrsz, pintrsz));
373
374                         if (iparent != NULL)
375                                 *iparent = parent;
376                         return (pintrsz/sizeof(pcell_t));
377                 }
378                 mptr += tsz;
379                 i -= tsz;
380         }
381         return (0);
382 }
383
384 int
385 ofw_bus_reg_to_rl(device_t dev, phandle_t node, pcell_t acells, pcell_t scells,
386     struct resource_list *rl)
387 {
388         uint64_t phys, size;
389         ssize_t i, j, rid, nreg, ret;
390         uint32_t *reg;
391         char *name;
392
393         /*
394          * This may be just redundant when having ofw_bus_devinfo
395          * but makes this routine independent of it.
396          */
397         ret = OF_getencprop_alloc(node, "name", sizeof(*name), (void **)&name);
398         if (ret == -1)
399                 name = NULL;
400
401         ret = OF_getencprop_alloc(node, "reg", sizeof(*reg), (void **)&reg);
402         nreg = (ret == -1) ? 0 : ret;
403
404         if (nreg % (acells + scells) != 0) {
405                 if (bootverbose)
406                         device_printf(dev, "Malformed reg property on <%s>\n",
407                             (name == NULL) ? "unknown" : name);
408                 nreg = 0;
409         }
410
411         for (i = 0, rid = 0; i < nreg; i += acells + scells, rid++) {
412                 phys = size = 0;
413                 for (j = 0; j < acells; j++) {
414                         phys <<= 32;
415                         phys |= reg[i + j];
416                 }
417                 for (j = 0; j < scells; j++) {
418                         size <<= 32;
419                         size |= reg[i + acells + j];
420                 }
421                 /* Skip the dummy reg property of glue devices like ssm(4). */
422                 if (size != 0)
423                         resource_list_add(rl, SYS_RES_MEMORY, rid,
424                             phys, phys + size - 1, size);
425         }
426         free(name, M_OFWPROP);
427         free(reg, M_OFWPROP);
428
429         return (0);
430 }
431
432 int
433 ofw_bus_intr_to_rl(device_t dev, phandle_t node,
434     struct resource_list *rl, int *rlen)
435 {
436         phandle_t iparent;
437         uint32_t icells, *intr;
438         int err, i, irqnum, nintr, rid;
439         boolean_t extended;
440
441         nintr = OF_getencprop_alloc(node, "interrupts",  sizeof(*intr),
442             (void **)&intr);
443         if (nintr > 0) {
444                 if (OF_searchencprop(node, "interrupt-parent", &iparent,
445                     sizeof(iparent)) == -1) {
446                         for (iparent = node; iparent != 0;
447                             iparent = OF_parent(node)) {
448                                 if (OF_hasprop(iparent, "interrupt-controller"))
449                                         break;
450                         }
451                         if (iparent == 0) {
452                                 device_printf(dev, "No interrupt-parent found, "
453                                     "assuming direct parent\n");
454                                 iparent = OF_parent(node);
455                         }
456                         iparent = OF_xref_from_node(iparent);
457                 }
458                 if (OF_searchencprop(OF_node_from_xref(iparent), 
459                     "#interrupt-cells", &icells, sizeof(icells)) == -1) {
460                         device_printf(dev, "Missing #interrupt-cells "
461                             "property, assuming <1>\n");
462                         icells = 1;
463                 }
464                 if (icells < 1 || icells > nintr) {
465                         device_printf(dev, "Invalid #interrupt-cells property "
466                             "value <%d>, assuming <1>\n", icells);
467                         icells = 1;
468                 }
469                 extended = false;
470         } else {
471                 nintr = OF_getencprop_alloc(node, "interrupts-extended",
472                     sizeof(*intr), (void **)&intr);
473                 if (nintr <= 0)
474                         return (0);
475                 extended = true;
476         }
477         err = 0;
478         rid = 0;
479         for (i = 0; i < nintr; i += icells) {
480                 if (extended) {
481                         iparent = intr[i++];
482                         if (OF_searchencprop(OF_node_from_xref(iparent), 
483                             "#interrupt-cells", &icells, sizeof(icells)) == -1) {
484                                 device_printf(dev, "Missing #interrupt-cells "
485                                     "property\n");
486                                 err = ENOENT;
487                                 break;
488                         }
489                         if (icells < 1 || (i + icells) > nintr) {
490                                 device_printf(dev, "Invalid #interrupt-cells "
491                                     "property value <%d>\n", icells);
492                                 err = ERANGE;
493                                 break;
494                         }
495                 }
496                 irqnum = ofw_bus_map_intr(dev, iparent, icells, &intr[i]);
497                 resource_list_add(rl, SYS_RES_IRQ, rid++, irqnum, irqnum, 1);
498         }
499         if (rlen != NULL)
500                 *rlen = rid;
501         free(intr, M_OFWPROP);
502         return (err);
503 }
504
505 phandle_t
506 ofw_bus_find_child(phandle_t start, const char *child_name)
507 {
508         char *name;
509         int ret;
510         phandle_t child;
511
512         for (child = OF_child(start); child != 0; child = OF_peer(child)) {
513                 ret = OF_getencprop_alloc(child, "name", sizeof(*name), (void **)&name);
514                 if (ret == -1)
515                         continue;
516                 if (strcmp(name, child_name) == 0) {
517                         free(name, M_OFWPROP);
518                         return (child);
519                 }
520
521                 free(name, M_OFWPROP);
522         }
523
524         return (0);
525 }
526
527 phandle_t
528 ofw_bus_find_compatible(phandle_t node, const char *onecompat)
529 {
530         phandle_t child, ret;
531         void *compat;
532         int len;
533
534         /*
535          * Traverse all children of 'start' node, and find first with
536          * matching 'compatible' property.
537          */
538         for (child = OF_child(node); child != 0; child = OF_peer(child)) {
539                 len = OF_getprop_alloc(child, "compatible", 1, &compat);
540                 if (len >= 0) {
541                         ret = ofw_bus_node_is_compatible(compat, len,
542                             onecompat);
543                         free(compat, M_OFWPROP);
544                         if (ret != 0)
545                                 return (child);
546                 }
547
548                 ret = ofw_bus_find_compatible(child, onecompat);
549                 if (ret != 0)
550                         return (ret);
551         }
552         return (0);
553 }