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