fdtbus.c revision 208747
1/*-
2 * Copyright (c) 2009-2010 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Semihalf under sponsorship from
6 * the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the 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
21 * FOR 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: head/sys/dev/fdt/fdtbus.c 208747 2010-06-02 17:17:45Z raj $");
32
33#include <sys/param.h>
34#include <sys/systm.h>
35#include <sys/ktr.h>
36#include <sys/kernel.h>
37#include <sys/module.h>
38#include <sys/bus.h>
39#include <sys/rman.h>
40#include <sys/malloc.h>
41
42#include <dev/ofw/openfirm.h>
43
44#include <machine/fdt.h>
45
46#include "fdt_common.h"
47#include "ofw_bus_if.h"
48
49#define DEBUG
50#undef DEBUG
51
52#ifdef DEBUG
53#define debugf(fmt, args...) do { printf("%s(): ", __func__);	\
54    printf(fmt,##args); } while (0)
55#else
56#define debugf(fmt, args...)
57#endif
58
59static MALLOC_DEFINE(M_FDTBUS, "fdtbus", "FDTbus devices information");
60
61struct fdtbus_devinfo {
62	phandle_t		di_node;
63	char			*di_name;
64	char			*di_type;
65	char			*di_compat;
66	struct resource_list	di_res;
67
68	/* Interrupts sense-level info for this device */
69	struct fdt_sense_level	di_intr_sl[DI_MAX_INTR_NUM];
70};
71
72struct fdtbus_softc {
73	struct rman	sc_irq;
74	struct rman	sc_mem;
75};
76
77/*
78 * Prototypes.
79 */
80static int fdtbus_probe(device_t);
81static int fdtbus_attach(device_t);
82
83static int fdtbus_print_child(device_t, device_t);
84static struct resource *fdtbus_alloc_resource(device_t, device_t, int,
85    int *, u_long, u_long, u_long, u_int);
86static int fdtbus_release_resource(device_t, device_t, int, int,
87    struct resource *);
88static int fdtbus_activate_resource(device_t, device_t, int, int,
89    struct resource *);
90static int fdtbus_deactivate_resource(device_t, device_t, int, int,
91    struct resource *);
92static int fdtbus_setup_intr(device_t, device_t, struct resource *, int,
93    driver_filter_t *, driver_intr_t *, void *, void **);
94static int fdtbus_teardown_intr(device_t, device_t, struct resource *,
95    void *);
96
97static const char *fdtbus_ofw_get_name(device_t, device_t);
98static phandle_t fdtbus_ofw_get_node(device_t, device_t);
99static const char *fdtbus_ofw_get_type(device_t, device_t);
100static const char *fdtbus_ofw_get_compat(device_t, device_t);
101
102/*
103 * Local routines.
104 */
105static void newbus_device_from_fdt_node(device_t, phandle_t);
106
107/*
108 * Bus interface definition.
109 */
110static device_method_t fdtbus_methods[] = {
111	/* Device interface */
112	DEVMETHOD(device_probe,		fdtbus_probe),
113	DEVMETHOD(device_attach,	fdtbus_attach),
114	DEVMETHOD(device_detach,	bus_generic_detach),
115	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
116	DEVMETHOD(device_suspend,	bus_generic_suspend),
117	DEVMETHOD(device_resume,	bus_generic_resume),
118
119	/* Bus interface */
120	DEVMETHOD(bus_print_child,	fdtbus_print_child),
121	DEVMETHOD(bus_alloc_resource,	fdtbus_alloc_resource),
122	DEVMETHOD(bus_release_resource,	fdtbus_release_resource),
123	DEVMETHOD(bus_activate_resource, fdtbus_activate_resource),
124	DEVMETHOD(bus_deactivate_resource, fdtbus_deactivate_resource),
125	DEVMETHOD(bus_setup_intr,	fdtbus_setup_intr),
126	DEVMETHOD(bus_teardown_intr,	fdtbus_teardown_intr),
127
128	/* OFW bus interface */
129	DEVMETHOD(ofw_bus_get_node,	fdtbus_ofw_get_node),
130	DEVMETHOD(ofw_bus_get_name,	fdtbus_ofw_get_name),
131	DEVMETHOD(ofw_bus_get_type,	fdtbus_ofw_get_type),
132	DEVMETHOD(ofw_bus_get_compat,	fdtbus_ofw_get_compat),
133
134	{ 0, 0 }
135};
136
137static driver_t fdtbus_driver = {
138	"fdtbus",
139	fdtbus_methods,
140	sizeof(struct fdtbus_softc)
141};
142
143devclass_t fdtbus_devclass;
144
145DRIVER_MODULE(fdtbus, nexus, fdtbus_driver, fdtbus_devclass, 0, 0);
146
147static int
148fdtbus_probe(device_t dev)
149{
150
151	device_set_desc(dev, "FDT main bus");
152	if (!bootverbose)
153		device_quiet(dev);
154	return (BUS_PROBE_DEFAULT);
155}
156
157static int
158fdtbus_attach(device_t dev)
159{
160	phandle_t root;
161	phandle_t child;
162	struct fdtbus_softc *sc;
163	u_long start, end;
164	int error;
165
166	if ((root = OF_peer(0)) == 0)
167		panic("fdtbus_attach: no root node.");
168
169	sc = device_get_softc(dev);
170
171	/*
172	 * IRQ rman.
173	 */
174	start = 0;
175	end = FDT_INTR_MAX - 1;
176	sc->sc_irq.rm_start = start;
177	sc->sc_irq.rm_end = end;
178	sc->sc_irq.rm_type = RMAN_ARRAY;
179	sc->sc_irq.rm_descr = "Interrupt request lines";
180	if ((error = rman_init(&sc->sc_irq)) != 0) {
181		device_printf(dev, "could not init IRQ rman, error = %d\n",
182		    error);
183		return (error);
184	}
185	if ((error = rman_manage_region(&sc->sc_irq, start, end)) != 0) {
186		device_printf(dev, "could not manage IRQ region, error = %d\n",
187		    error);
188		return (error);
189	}
190
191	/*
192	 * Mem-mapped I/O space rman.
193	 */
194	start = 0;
195	end = ~0u;
196	sc->sc_mem.rm_start = start;
197	sc->sc_mem.rm_end = end;
198	sc->sc_mem.rm_type = RMAN_ARRAY;
199	sc->sc_mem.rm_descr = "I/O memory";
200	if ((error = rman_init(&sc->sc_mem)) != 0) {
201		device_printf(dev, "could not init I/O mem rman, error = %d\n",
202		    error);
203		return (error);
204	}
205	if ((error = rman_manage_region(&sc->sc_mem, start, end)) != 0) {
206		device_printf(dev, "could not manage I/O mem region, "
207		    "error = %d\n", error);
208		return (error);
209	}
210
211	/*
212	 * Walk the FDT root node and add top-level devices as our children.
213	 */
214	for (child = OF_child(root); child != 0; child = OF_peer(child)) {
215		/* Check and process 'status' property. */
216		if (!(fdt_is_enabled(child)))
217			continue;
218
219		newbus_device_from_fdt_node(dev, child);
220	}
221
222	return (bus_generic_attach(dev));
223}
224
225static int
226fdtbus_print_child(device_t dev, device_t child)
227{
228	struct fdtbus_devinfo *di;
229	struct resource_list *rl;
230	int rv;
231
232	di = device_get_ivars(child);
233	rl = &di->di_res;
234
235	rv = 0;
236	rv += bus_print_child_header(dev, child);
237	rv += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#lx");
238	rv += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%ld");
239	rv += bus_print_child_footer(dev, child);
240
241	return (rv);
242}
243
244static void
245newbus_device_destroy(device_t dev)
246{
247	struct fdtbus_devinfo *di;
248
249	di = device_get_ivars(dev);
250
251	free(di->di_name, M_OFWPROP);
252	free(di->di_type, M_OFWPROP);
253	free(di->di_compat, M_OFWPROP);
254
255	resource_list_free(&di->di_res);
256	free(di, M_FDTBUS);
257}
258
259static device_t
260newbus_device_create(device_t dev_par, phandle_t node, char *name, char *type,
261    char *compat)
262{
263	device_t child;
264	struct fdtbus_devinfo *di;
265
266	child = device_add_child(dev_par, NULL, -1);
267	if (child == NULL) {
268		free(name, M_OFWPROP);
269		free(type, M_OFWPROP);
270		free(compat, M_OFWPROP);
271		return (NULL);
272	}
273
274	di = malloc(sizeof(*di), M_FDTBUS, M_WAITOK);
275	di->di_node = node;
276	di->di_name = name;
277	di->di_type = type;
278	di->di_compat = compat;
279
280	resource_list_init(&di->di_res);
281
282	if (fdt_reg_to_rl(node, &di->di_res, fdt_immr_va)) {
283		device_printf(child, "could not process 'reg' property\n");
284		newbus_device_destroy(child);
285		child = NULL;
286		goto out;
287	}
288
289	if (fdt_intr_to_rl(node, &di->di_res, di->di_intr_sl)) {
290		device_printf(child, "could not process 'interrupts' "
291		    "property\n");
292		newbus_device_destroy(child);
293		child = NULL;
294		goto out;
295	}
296
297	device_set_ivars(child, di);
298	debugf("added child name='%s', node=%p\n", name, (void *)node);
299
300out:
301	return (child);
302}
303
304static device_t
305newbus_pci_create(device_t dev_par, phandle_t dt_node, u_long par_base,
306    u_long par_size)
307{
308	pcell_t reg[3 + 2];
309	device_t dev_child;
310	u_long start, end, count;
311	struct fdtbus_devinfo *di;
312	char *name, *type, *compat;
313	int len;
314
315	OF_getprop_alloc(dt_node, "device_type", 1, (void **)&type);
316	if (!(type != NULL && strcmp(type, "pci") == 0)) {
317		/* Only process 'pci' subnodes. */
318		free(type, M_OFWPROP);
319		return (NULL);
320	}
321
322	OF_getprop_alloc(dt_node, "name", 1, (void **)&name);
323	OF_getprop_alloc(OF_parent(dt_node), "compatible", 1,
324	    (void **)&compat);
325
326	dev_child = device_add_child(dev_par, NULL, -1);
327	if (dev_child == NULL) {
328		free(name, M_OFWPROP);
329		free(type, M_OFWPROP);
330		free(compat, M_OFWPROP);
331		return (NULL);
332	}
333
334	di = malloc(sizeof(*di), M_FDTBUS, M_WAITOK);
335	di->di_node = dt_node;
336	di->di_name = name;
337	di->di_type = type;
338	di->di_compat = compat;
339
340	resource_list_init(&di->di_res);
341
342	/*
343	 * Produce and set SYS_RES_MEMORY resources.
344	 */
345	start = 0;
346	count = 0;
347
348	len = OF_getprop(dt_node, "reg", &reg, sizeof(reg));
349	if (len > 0) {
350		if (fdt_data_verify((void *)&reg[1], 2) != 0) {
351			device_printf(dev_child, "'reg' address value out of "
352			    "range\n");
353			newbus_device_destroy(dev_child);
354			dev_child = NULL;
355			goto out;
356		}
357		start = fdt_data_get((void *)&reg[1], 2);
358
359		if (fdt_data_verify((void *)&reg[3], 2) != 0) {
360			device_printf(dev_child, "'reg' size value out of "
361			    "range\n");
362			newbus_device_destroy(dev_child);
363			dev_child = NULL;
364			goto out;
365		}
366		count = fdt_data_get((void *)&reg[3], 2);
367	}
368
369	/* Calculate address range relative to base. */
370	par_base &= 0x000ffffful;
371	start &= 0x000ffffful;
372	start += par_base + fdt_immr_va;
373	if (count == 0)
374		count = par_size;
375	end = start + count - 1;
376
377	debugf("start = 0x%08lx, end = 0x%08lx, count = 0x%08lx\n",
378	    start, end, count);
379
380	if (count > par_size) {
381		device_printf(dev_child, "'reg' size value out of range\n");
382		newbus_device_destroy(dev_child);
383		dev_child = NULL;
384		goto out;
385	}
386
387	resource_list_add(&di->di_res, SYS_RES_MEMORY, 0, start, end, count);
388
389	/*
390	 * Set SYS_RES_IRQ resources.
391	 */
392	if (fdt_intr_to_rl(OF_parent(dt_node), &di->di_res, di->di_intr_sl)) {
393		device_printf(dev_child, "could not process 'interrupts' "
394		    "property\n");
395		newbus_device_destroy(dev_child);
396		dev_child = NULL;
397		goto out;
398	}
399
400	device_set_ivars(dev_child, di);
401	debugf("added child name='%s', node=%p\n", name,
402	    (void *)dt_node);
403
404out:
405	return (dev_child);
406}
407
408static void
409pci_from_fdt_node(device_t dev_par, phandle_t dt_node, char *name,
410    char *type, char *compat)
411{
412	u_long reg_base, reg_size;
413	phandle_t dt_child;
414
415	/*
416	 * Retrieve 'reg' property.
417	 */
418	if (fdt_regsize(dt_node, &reg_base, &reg_size) != 0) {
419		device_printf(dev_par, "could not retrieve 'reg' prop\n");
420		return;
421	}
422
423	/*
424	 * Walk the PCI node and instantiate newbus devices representing
425	 * logical resources (bridges / ports).
426	 */
427	for (dt_child = OF_child(dt_node); dt_child != 0;
428	    dt_child = OF_peer(dt_child)) {
429
430		if (!(fdt_is_enabled(dt_child)))
431			continue;
432
433		newbus_pci_create(dev_par, dt_child, reg_base, reg_size);
434	}
435}
436
437/*
438 * These FDT nodes do not need a corresponding newbus device object.
439 */
440static char *fdt_devices_skip[] = {
441	"aliases",
442	"chosen",
443	"memory",
444	NULL
445};
446
447static void
448newbus_device_from_fdt_node(device_t dev_par, phandle_t node)
449{
450	char *name, *type, *compat;
451	device_t child;
452	int i;
453
454	OF_getprop_alloc(node, "name", 1, (void **)&name);
455	OF_getprop_alloc(node, "device_type", 1, (void **)&type);
456	OF_getprop_alloc(node, "compatible", 1, (void **)&compat);
457
458	for (i = 0; fdt_devices_skip[i] != NULL; i++)
459		if (name != NULL && strcmp(name, fdt_devices_skip[i]) == 0) {
460			debugf("skipping instantiating FDT device='%s'\n",
461			    name);
462			return;
463		}
464
465	if (type != NULL && strcmp(type, "pci") == 0) {
466		pci_from_fdt_node(dev_par, node, name, type, compat);
467		return;
468	}
469
470	child = newbus_device_create(dev_par, node, name, type, compat);
471}
472
473static struct resource *
474fdtbus_alloc_resource(device_t bus, device_t child, int type, int *rid,
475    u_long start, u_long end, u_long count, u_int flags)
476{
477	struct fdtbus_softc *sc;
478	struct resource *res;
479	struct rman *rm;
480	struct fdtbus_devinfo *di;
481	struct resource_list_entry *rle;
482	int needactivate;
483
484	/*
485	 * Request for the default allocation with a given rid: use resource
486	 * list stored in the local device info.
487	 */
488	if ((start == 0UL) && (end == ~0UL)) {
489		if ((di = device_get_ivars(child)) == NULL)
490			return (NULL);
491
492		if (type == SYS_RES_IOPORT)
493			type = SYS_RES_MEMORY;
494
495		rle = resource_list_find(&di->di_res, type, *rid);
496		if (rle == NULL) {
497			device_printf(bus, "no default resources for "
498			    "rid = %d, type = %d\n", *rid, type);
499			return (NULL);
500		}
501		start = rle->start;
502		end = rle->end;
503		count = rle->count;
504	}
505
506	sc = device_get_softc(bus);
507
508	needactivate = flags & RF_ACTIVE;
509	flags &= ~RF_ACTIVE;
510
511	switch (type) {
512	case SYS_RES_IRQ:
513		rm = &sc->sc_irq;
514		break;
515
516	case SYS_RES_IOPORT:
517	case SYS_RES_MEMORY:
518		rm = &sc->sc_mem;
519		break;
520
521	default:
522		return (NULL);
523	}
524
525	res = rman_reserve_resource(rm, start, end, count, flags, child);
526	if (res == NULL) {
527		device_printf(bus, "failed to reserve resource %#lx - %#lx "
528		    "(%#lx)\n", start, end, count);
529		return (NULL);
530	}
531
532	rman_set_rid(res, *rid);
533
534	if (type == SYS_RES_IOPORT || type == SYS_RES_MEMORY) {
535		/* XXX endianess should be set based on SOC node */
536		rman_set_bustag(res, fdtbus_bs_tag);
537		rman_set_bushandle(res, rman_get_start(res));
538	}
539
540	if (needactivate)
541		if (bus_activate_resource(child, type, *rid, res)) {
542			device_printf(child, "resource activation failed\n");
543			rman_release_resource(res);
544			return (NULL);
545		}
546
547	return (res);
548}
549
550static int
551fdtbus_release_resource(device_t bus, device_t child, int type, int rid,
552    struct resource *res)
553{
554	int err;
555
556	if (rman_get_flags(res) & RF_ACTIVE) {
557		err = bus_deactivate_resource(child, type, rid, res);
558		if (err)
559			return (err);
560	}
561
562	return (rman_release_resource(res));
563}
564
565static int
566fdtbus_setup_intr(device_t bus, device_t child, struct resource *res,
567    int flags, driver_filter_t *filter, driver_intr_t *ihand, void *arg,
568    void **cookiep)
569{
570	int err;
571
572	*cookiep = 0;
573	if ((rman_get_flags(res) & RF_SHAREABLE) == 0)
574		flags |= INTR_EXCL;
575
576	err = rman_activate_resource(res);
577	if (err)
578		return (err);
579
580#if defined(__powerpc__)
581	err = powerpc_setup_intr(device_get_nameunit(child),
582	    rman_get_start(res), filter, ihand, arg, flags, cookiep);
583#elif defined(__arm__)
584	arm_setup_irqhandler(device_get_nameunit(child),
585	    filter, ihand, arg, rman_get_start(res), flags, cookiep);
586	arm_unmask_irq(rman_get_start(res));
587	err = 0;
588#endif
589
590	return (err);
591}
592
593static int
594fdtbus_activate_resource(device_t bus, device_t child, int type, int rid,
595    struct resource *res)
596{
597
598	return (rman_activate_resource(res));
599}
600
601static int
602fdtbus_deactivate_resource(device_t bus, device_t child, int type, int rid,
603    struct resource *res)
604{
605
606	return (rman_deactivate_resource(res));
607}
608
609
610static int
611fdtbus_teardown_intr(device_t bus, device_t child, struct resource *res,
612    void *cookie)
613{
614
615#if defined(__powerpc__)
616	return (powerpc_teardown_intr(cookie));
617#elif defined(__arm__)
618	return (arm_remove_irqhandler(rman_get_start(res), cookie));
619#endif
620}
621
622static const char *
623fdtbus_ofw_get_name(device_t bus, device_t dev)
624{
625	struct fdtbus_devinfo *di;
626
627	return ((di = device_get_ivars(dev)) == NULL ? NULL : di->di_name);
628}
629
630static phandle_t
631fdtbus_ofw_get_node(device_t bus, device_t dev)
632{
633	struct fdtbus_devinfo *di;
634
635	return ((di = device_get_ivars(dev)) == NULL ? 0 : di->di_node);
636}
637
638static const char *
639fdtbus_ofw_get_type(device_t bus, device_t dev)
640{
641	struct fdtbus_devinfo *di;
642
643	return ((di = device_get_ivars(dev)) == NULL ? NULL : di->di_type);
644}
645
646static const char *
647fdtbus_ofw_get_compat(device_t bus, device_t dev)
648{
649	struct fdtbus_devinfo *di;
650
651	return ((di = device_get_ivars(dev)) == NULL ? NULL : di->di_compat);
652}
653
654
655