INTRNG (Was: svn commit: r301453....)

Nathan Whitehorn nwhitehorn at freebsd.org
Tue Aug 2 04:00:58 UTC 2016



On 07/31/16 11:43, Nathan Whitehorn wrote:
>
>
> On 07/31/16 08:40, Michal Meloun wrote:
>> Dne 31.07.2016 v 8:11 Nathan Whitehorn napsal(a):
>> [snip]
>>>> The current API is certainly a bit of a hack and I would be pleased to
>>>>> see it go; but the replacement needs to be more functional and more
>>>>> robust. As far as I can tell, I literally *cannot* move PowerPC over
>>>>> to this architecture.
>>>> Yes, at this time, I agree. If you can accept enhancement of
>>>> (owf_)bus_map_intr() then we can move to next, significantly less
>>>> hackish, state.
>>> OK, sure. I wrote some code this afternoon (attached) to sketch this.
>>> It does not compile or run or anything useful, but it should
>>> illustrate the important parts of what I think is an API that should
>>> work for everyone. I'm happy to finish it if it looks reasonable -- I
>>> may in any case just to replace PowerPC's increasingly teetering
>>> intr_machdep.c.
>>>
>>> The OF part is essentially unchanged from how it currently works:
>>> there's a method that you pass the interrupt specifier and interrupt
>>> parent to, and it spits back an IRQ that means that combination of
>>> things in perpetuity. OFW_BUS_MAP_INTR() in nexus.c, with its current
>>> API, can be implemented in terms of it in one line. Whenever the
>>> relevant PIC attaches, it is told to use the given IRQ to refer to
>>> that opaque bytestring.
>>>
>>> I've added a set of equivalent functions for ACPI that take the
>>> contents of an ACPI interrupt specifier and do the same things. It
>>> tries to have the IRQ be human-meaningful, if possible, by matching
>>> the ACPI interrupt number. Having separate functions seemed a little
>>> cleaner than exposing the enums and unions as part of the KPI, but I'm
>>> not wedded to it -- this is just an example.
>>>
>>> PICs register (and deregister) themselves with either an OF phandle or
>>> an ACPI resource string or (god help us) both as needed. The PICs have
>>> corresponding methods for interpreting various kinds of interrupt
>>> specifiers.
>>>
>>> Whether it allows bus_alloc_resource(), bus_setup_intr(), etc. to
>>> succeed before the PICs are attached is gated on an #ifdef. That can
>>> probably be off by default on ARM. A PowerPC version of this code
>>> would have to keep it on until various bus drivers are fixed.
>>>
>>> Here's the general flow:
>>> - Parent bus enumerates children, maps IRQs as needed, adds to
>>> resource list. The struct resources involved aren't special (just a
>>> single number), so PCIB_ROUTE_INTERRUPT(), MSIs, etc. are trivial to
>>> implement (and already are implemented, in general, for OF/FDT 
>>> drivers).
>>> - bus_alloc_resource() does nothing special
>>> - bus_setup_intr() calls into the PIC and does what is needed, as in
>>> r301453 (to within that #ifdef)
>>>
>>> This should keep all the best pieces of everything:
>>> - ACPI and OF are supported together, and easy to extend for other
>>> types of mapping technologies without breaking either KBI or KPI (just
>>> add new functions)
>>> - No APIs change for existing Open Firmware code
>>> - The support code can live entirely in machine-dependent directories,
>>> with no code changes at all required in kern/ or dev/ofw/
>>> - bus_setup_intr() and friends behave as in r301453 and succeed or
>>> fail for real
>>> - PCI code is not an issue
>>> - No disconnected interrupt state maintained in mapping table (unless
>>> the transitional #ifdef is on) and the mapping table content is only
>>> larger than r301453 by having a copy of the original interrupt 
>>> specifier.
>>>
>>> If and when we manage to fix the kernel APIs to allow non-scalar
>>> interrupt specifiers, this should also be easy to gradually
>>> transmogrify to support that case since there exists a 1:1 mapping of
>>> scalar IRQs to rich data and the control flow would be identical:
>>> interrupt specifiers are read at enumeration time and a resulting
>>> handle -- struct resource or scalar IRQ -- used afterward to refer 
>>> to it.
>>>
>> Nice, nice, i think that we are very close at this point.
>> The problem with the above workflow is that maps IRQ function is called
>> at parent bus enumeration time, generally at BUS_PASS_BUS pass. And at
>> this time, PICs are not exist.
>> Also, 'static struct intr_irqsrc *irq_sources[NIRQ];' is not a mapping
>> table,  it doesn't provide this functionality.
>> But yes, i understand that mapping table is important and necessary 
>> for you.
>>
>> So, if i can extend our  concept a little bit, then:
>> We can add new table (map_data_tbl for example)  that holds  a copy of
>> the original interrupt specifier, index to irq_sources table and
>> probably some flags.
>> And we can modify the above workflow to:
>> - Parent bus enumerates children, allocates entries from map_data_tbl,
>> adds to resource list. The struct resources involved aren't special
>> (just a single number), so PCIB_ROUTE_INTERRUPT(), MSIs, etc. are
>> trivial to implement (and already are implemented, in general, for
>> OF/FDT drivers). Index to map_data_tbl is used as resource ID.
>> - bus_alloc_resource()  sets 'ALLOCATED' in map_data_tbl flags field,
>> *maps IRQs* and stores result in 'index' field.
>> - bus_setup_intr() sets 'SETUP_DONE' in map_data_tbl flags field, calls
>> into the PIC and does what is needed, as in r301453. (to within that
>> #ifdef)
>>
>> And, in PPC case, newly attached PIC scans whole map_data_tbl table,
>> finds his entries and makes what needs.
>>
>> Does that make sense?
>> I hope that postponing of map IRQ call is not a problem for PPC,
>> everything else looks easy.
>> Moreover,  this additional level of indirection also solves all my
>> 'hypothetical corner cases' with N:1 mappings (between original
>> interrupt specifier and real interrupt number).
>
> Yes, I think we are converging.
>
> My qualm about bus_alloc_resource() is twofold:
> 1. Traditionally, with interrupts, bus_alloc_resource() has no side 
> effects and I'm not sure it propagates cleanly down the tree in all 
> cases.
> 2. T. ,                                                       x n nn b 
> n   /here are a few bus APIs (bus_config_intr() comes to mind) that 
> use raw IRQ IDs and, so far as I know, can be, and sometimes are 
> called before bus_alloc_resource(). If the PIC doesn't know about the 
> IRQ yet when bus_config_intr() (etc.) is called, things will just break.
>
> So you would need to make sure that any bus method handling a resource 
> ID causes it to be mapped on the PIC at that time. It's OK if that 
> happens in bus_alloc_resource() so long as bus_config_intr(), 
> bus_setup_intr(), etc. cause that to happen if it hasn't happened yet 
> -- I don't care when these calls are made to the PIC driver so long as 
> *what* calls will be made is set at enumeration time.
>
> I am also totally fine with having, on ARM, bus_config_intr(), 
> bus_setup_intr() etc. fail if the PIC hasn't attached yet (On PowerPC, 
> we can't do that yet, but after this conversation, I regard that as a 
> bug and would fix that later), as well as delaying setup on the PIC to 
> the first time any bus driver actually tries to *use* the interrupt 
> (alloc_resource(), setup_intr(), config_intr(), whatever) rather than 
> when the interrupt is originally allocated.
>
> ------------ The following is a large parenthesis -------------------
>
> One other possible route here that would also work well would be to 
> make ofwbus.c MD (it's a trivial piece of code anyway, so we don't 
> gain a lot by sharing it) and implement ofw_bus_map_intr() locally as 
> an ofwbus bus method. Then you could have the mapping table stored in 
> ofwbus's softc -- the API was designed for this initially. You would 
> need MD extensions for doing PIC registration there (which is fine), 
> but that segregates all the OFW-specific information into OFW-specific 
> code and would let the bus methods and the OFW interrupt mapping table 
> interact cleanly in the same place. This still preserves the 
> pre-r301453 API, makes PowerPC work, and maybe address's skra@'s 
> concern about extensibility and letting core interrupt code know about 
> FDT (or ACPI). I'd be happy to mock this up as well if you think it's 
> a good approach.
>
[snip]
>
> This has the following features:
> - Existing OFW API and semantics unchanged
> - As such, PowerPC, PCI, etc. work fine with no changes
> - Details encapsulated in MD code, so individual platforms can 
> implement this however they like
> - arm/arm/intr.c (or whatever) only needs a method to allocate a fresh 
> interrupt, with no state, and anoter to set the device_t for an 
> interrupt sometime later.
> - The internal table in the platform interrupt code has no knowledge 
> of any mappings whatsoever except having the appropriate device_t for 
> the PIC stored with the interrupt vector.
> - Device tree bits handled purely in device tree code
> - No action need be taken on any mapping until the interrupt is 
> actually allocated/set up, like r301453
> - Easy to add more mapping mechanisms (e.g. ACPI) by having similar 
> enumeration-mechanism-specific code in the root bus for that mapping 
> mechanism.
>
> -------------- End parenthesis -------------------------------

Here's an implementation of the parenthesis I wrote on an airplane this 
afternoon. It should be complete, though has not been tested. The code 
is short and simple (+70 lines in ofwbus.c). This preserves the 
pre-r301453 API and semantics relative to drivers, which means PowerPC 
and PCI work out of the box, while keeping the semantics relative to the 
interrupt layer of r301453 (PIC methods only called on resource 
allocation, no allocatable IRQs on unattached PICs, encapsulation of 
OFW-specific code in OFW-specific bits of the tree). It turns out those 
two things are compatible, somewhat to my surprise, and that makes the 
result very clean. I like this approach and would be happy to move 
forward with it. There are five functions of interest:

1. OFW_BUS_MAP_INTR(). This has the semantics and API it has now: you 
pass an interrupt specifier and parent, you get back an IRQ. No changes. 
This is the core of the normal OFW interrupt API.

2. OFW_BUS_REGISTER_PIC(device_t pic, phandle_t phandle). This is a new 
function that PIC drivers are supposed to use to register control of an 
interrupt domain. This replaces machine-specific code like 
powerpc_register_pic() to allow the PIC table to be in a bus parent 
rather than in the interrupt core.

3. PIC_MAP_OFW_INTR(device_t pic, int irq, interrupt specifier). This is 
a new function that PIC drivers that know how to handle device-tree 
interrupt descriptors implement (analogous to various existing ones that 
vary by platform). It tells the PIC that the given abstract IRQ means 
the given opaque interrupt specifier.

4. arm_allocate_irq(int suggested). This allocates a new IRQ number not 
(yet) attached to a PIC, as in r301453. I've added a parameter that lets 
you pass a suggested number to try in case it is possible to make it 
match an interrupt pin or something for human-readability.

5. arm_set_pic_for_irq(int irq, device_t). This tells the MD interrupt 
layer to associate a given PIC device_t with an IRQ. That is all the 
information the MD layer ever has about the IRQ mapping.

Functions #1 and #2 are now implemented completely in ofwbus.c: there 
are no callouts anywhere else and the interrupt mapping table is 
maintained entirely internally to ofwbus (in its softc). In order to 
implement ACPI, or some other strategy, you would implement analogs to 
functions #1 and #2 that live somewhere in the bus hierarchy that is 
guaranteed to be above all devices that might want that type of 
interrupt (e.g. in acpi0), and some analog to #3 that PIC drivers 
implementing the mapping scheme would provide.

Since the system interrupt code has no knowledge at all of interrupt 
mapping, of any type, in this scheme, adding new mapping types is 
trivial and can be done on a driver-by-driver basis if necessary without 
changing KPI and without any other part of the system even being aware. 
For example, GPIOs can use a completely different mechanism if they want 
and can do setup purely in the GPIO controller driver. You could have a 
method GPIO_GET_INTERRUPT_FOR_PIN() on a GPIO controller in which the 
GPIO controller allocates a generic IRQ, assigns through some internal 
table just in the GPIO driver, and returns to it to a consumer in some 
other device driver -- without a GPIO mapping type, new bus functions, 
or modifications to the platform interrupt code.

The control flow goes like this:
- Bus driver enumerates children, parses interrupts properties, calls 
OFW_BUS_MAP_INTR() to get IRQs for them (as pre-r301453), adds to 
interrupt list.
- ofwbus receives the OFW_BUS_MAP_INTR() call, allocates a blank 
disconnected IRQ from the MD interrupt layer, and stores the mapping 
from the new IRQ to the given interrupt specifier and phandle in an 
internal table in ofwbus's softc.
   NB: Nothing else happens here, like post-r301453. Changing this does 
not change any semantics of the API pre-r301453, which means it remains 
fully compatible with PCI and PowerPC. Also, like post-r301453, there is 
no involvement of nexus.
- PICs attach and call OFW_BUS_REGISTER_PIC(). ofwbus receives these 
messages and adds a (device_t, phandle_t) mapping to a second internal 
table. Note that the interrupt layer does not need to handle PIC 
registration anymore at all (except for the root PIC).
- Bus child eventually calls a function that tries to set the interrupt 
up (e.g. bus_setup_intr()). That propagates up the bus hierarchy, 
eventually getting to ofwbus. ofwbus notes the IRQ number, looks it up 
in the table, looks up the appropriate PIC from the PIC table, then:
   A) calls arm_set_pic_for_irq(irq, pic_device_t) -- this is the 
interrupt layer's only interaction with the mapping code. All it deals 
with is device_ts and abstract IRQ numbers.
   B) calls PIC_MAP_OFW_INTR(pic, irq_number, interrupt-cells, 
interrupt-specifier) to tell the PIC that the interrupt layer's IRQ 
irq_number means the given specifier
   C) finally, passes the call onto nexus, which will do whatever would 
normally happen (unmasking the interrupt, setting handlers, etc.) in 
terms only of the abstract IRQ and the device_t assigned by ofwbus.

You would implement ACPI just by doing a s/OFW/ACPI/g search-and-replace 
above -- since the interrupt layer doesn't know about OFW or ACPI or 
anything else, there is no need to touch it. This seems clean, simple, 
compartmentalized, preserves the existing API, and should work on all of 
our various hardware. PowerPC can't quite work with it yet without some 
multipass foo, but, since the API is preserved, that transition can 
happen gradually without KPI changes. For the same reason that it is 
API-preserving, I think this code is also MFC-able.
-Nathan
-------------- next part --------------
/*-
 * Copyright 2016 Nathan Whitehorn
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/queue.h>

#include <dev/ofw/openfirm.h>
#include <dev/fdt/simplebus.h>

#include <machine/bus.h>
#include <machine/resource.h>

/*
 * The ofwbus forms the root of the OF bus hierarchy and binds the device tree
 * "/" node to newbus. Since the root node has most of the same bindings and
 * semantics as the FDT simplebus, this inherits from it. It adds termination
 * of some resource requests, notably interrupt mapping, that cannot be pushed
 * further down the bus hierarchy.
 */

struct ofwbus_intrtabent {
	int irq;

	device_t pic;

	phandle_t iparent;
	pcell_t *ispec;
	int icells;

	SLIST_ENTRY(ofwbus_intrtabent) entry;
};

struct ofwbus_pic {
	phandle_t phandle;
	device_t dev;

	SLIST_ENTRY(ofwbus_pic) entry;
};

struct ofwbus_softc {
	struct simplebus_softc simplebus_sc;
	struct rman	intr_rman;
	struct rman	mem_rman;

	struct mtx	intr_tab_lock;
	SLIST_HEAD(ofwbus_intrtabs, ofwbus_intrtabent) intr_tab;
	SLIST_HEAD(ofwbus_pics, ofwbus_pic) pic_tab;
};

#ifndef __aarch64__
static device_identify_t ofwbus_identify;
#endif
static device_probe_t ofwbus_probe;
static device_attach_t ofwbus_attach;
static bus_alloc_resource_t ofwbus_alloc_resource;
static bus_adjust_resource_t ofwbus_adjust_resource;
static bus_release_resource_t ofwbus_release_resource;

static bus_bind_intr_t ofwbus_bind_intr;
static bus_config_intr_t ofwbus_config_intr;
static bus_setup_intr_t ofwbus_setup_intr;
static ofw_bus_map_intr_t ofwbus_ofw_map_intr;
static ofw_bus_register_pic_t ofwbus_register_pic;

/* Register IRQs with PIC if not done yet */
static int ofwbus_setup_irq_with_pic(device_t dev, int irq);

static device_method_t ofwbus_methods[] = {
	/* Device interface */
#ifndef __aarch64__
	DEVMETHOD(device_identify,	ofwbus_identify),
#endif
	DEVMETHOD(device_probe,		ofwbus_probe),
	DEVMETHOD(device_attach,	ofwbus_attach),

	/* Bus interface */
	DEVMETHOD(bus_alloc_resource,	ofwbus_alloc_resource),
	DEVMETHOD(bus_adjust_resource,	ofwbus_adjust_resource),
	DEVMETHOD(bus_release_resource,	ofwbus_release_resource),

	/* ofw_bus interface */
	DEVMETHOD(ofw_bus_map_intr,	ofwbus_map_intr)
	DEVMETHOD(ofw_bus_register_pic,	ofwbus_register_pic)

	DEVMETHOD_END
};

DEFINE_CLASS_1(ofwbus, ofwbus_driver, ofwbus_methods,
    sizeof(struct ofwbus_softc), simplebus_driver);
static devclass_t ofwbus_devclass;
EARLY_DRIVER_MODULE(ofwbus, nexus, ofwbus_driver, ofwbus_devclass, 0, 0,
    BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
MODULE_VERSION(ofwbus, 1);

#ifndef __aarch64__
static void
ofwbus_identify(driver_t *driver, device_t parent)
{

	/* Check if Open Firmware has been instantiated */
	if (OF_peer(0) == 0)
		return;

	if (device_find_child(parent, "ofwbus", -1) == NULL)
		BUS_ADD_CHILD(parent, 0, "ofwbus", -1);
}
#endif

static int
ofwbus_probe(device_t dev)
{

#ifdef __aarch64__
	if (OF_peer(0) == 0)
		return (ENXIO);
#endif

	device_set_desc(dev, "Open Firmware Device Tree");
	return (BUS_PROBE_NOWILDCARD);
}

static int
ofwbus_attach(device_t dev)
{
	struct ofwbus_softc *sc;
	phandle_t node;
	struct ofw_bus_devinfo obd;

	sc = device_get_softc(dev);

	node = OF_peer(0);

	/*
	 * If no Open Firmware, bail early
	 */
	if (node == -1)
		return (ENXIO);

	/*
	 * Allocate root rmans for ofwbus.
	 */
	sc->sc_intr_rman.rm_type = RMAN_ARRAY;
	sc->sc_intr_rman.rm_descr = "Interrupts";
	sc->sc_mem_rman.rm_type = RMAN_ARRAY;
	sc->sc_mem_rman.rm_descr = "Device Memory";
	if (rman_init(&sc->sc_intr_rman) != 0 ||
	    rman_init(&sc->sc_mem_rman) != 0 ||
	    rman_manage_region(&sc->sc_intr_rman, 0, ~0) != 0 ||
	    rman_manage_region(&sc->sc_mem_rman, 0, BUS_SPACE_MAXADDR) != 0)
		panic("%s: failed to set up rmans.", __func__);

	/*
	 * Init interrupt mapping table
	 */
	SLIST_INIT(&sc->pic_tab);
	SLIST_INIT(&sc->intr_tab);
	mtx_init(&sc->intr_tab_lock, "ofwbus intr", NULL, MTX_DEF);

	/*
	 * This is the root of the tree, so simplebus_attach() will fail
	 * when it asks our parent (nexus) for its OFW node. Pass node to
	 * simplebus_init directly.
	 */
	simplebus_init(dev, node);

	/*
	 * Now walk the OFW tree and attach top-level devices.
	 */
	for (node = OF_child(node); node > 0; node = OF_peer(node))
		simplebus_add_device(dev, node, 0, NULL, -1, NULL);
	return (bus_generic_attach(dev));
}

static struct resource *
ofwbus_alloc_resource(device_t bus, device_t child, int type, int *rid,
    rman_res_t start, rman_res_t end, rman_res_t count, u_int flags)
{
	struct ofwbus_softc *sc;
	struct rman *rm;
	struct resource *rv;
	struct resource_list_entry *rle;
	int isdefault, passthrough, err;

	isdefault = RMAN_IS_DEFAULT_RANGE(start, end);
	passthrough = (device_get_parent(child) != bus);
	sc = device_get_softc(bus);
	rle = NULL;
	if (!passthrough && isdefault) {
		rle = resource_list_find(BUS_GET_RESOURCE_LIST(bus, child),
		    type, *rid);
		if (rle == NULL) {
			if (bootverbose)
				device_printf(bus, "no default resources for "
				    "rid = %d, type = %d\n", *rid, type);
			return (NULL);
		}
		start = rle->start;
		count = ummax(count, rle->count);
		end = ummax(rle->end, start + count - 1);
	}

	switch (type) {
	case SYS_RES_IRQ:
		rm = &sc->sc_intr_rman;
		break;
	case SYS_RES_MEMORY:
		rm = &sc->sc_mem_rman;
		break;
	default:
		return (NULL);
	}

	rv = rman_reserve_resource(rm, start, end, count, flags & ~RF_ACTIVE,
	    child);
	if (rv == NULL)
		return (NULL);
	rman_set_rid(rv, *rid);

	if ((flags & RF_ACTIVE) != 0 && bus_activate_resource(child, type,
	    *rid, rv) != 0) {
		rman_release_resource(rv);
		return (NULL);
	}

	if (!passthrough && rle != NULL) {
		rle->res = rv;
		rle->start = rman_get_start(rv);
		rle->end = rman_get_end(rv);
		rle->count = rle->end - rle->start + 1;
	}

	return (rv);
}

static int
ofwbus_adjust_resource(device_t bus, device_t child __unused, int type,
    struct resource *r, rman_res_t start, rman_res_t end)
{
	struct ofwbus_softc *sc;
	struct rman *rm;
	device_t ofwbus;

	ofwbus = bus;
	while (strcmp(device_get_name(device_get_parent(ofwbus)), "root") != 0)
		ofwbus = device_get_parent(ofwbus);
	sc = device_get_softc(ofwbus);
	switch (type) {
	case SYS_RES_IRQ:
		rm = &sc->sc_intr_rman;
		break;
	case SYS_RES_MEMORY:
		rm = &sc->sc_mem_rman;
		break;
	default:
		return (EINVAL);
	}
	if (rm == NULL)
		return (ENXIO);
	if (rman_is_region_manager(r, rm) == 0)
		return (EINVAL);
	return (rman_adjust_resource(r, start, end));
}

static int
ofwbus_release_resource(device_t bus, device_t child, int type,
    int rid, struct resource *r)
{
	struct resource_list_entry *rle;
	int passthrough;
	int error;

	passthrough = (device_get_parent(child) != bus);
	if (!passthrough) {
		/* Clean resource list entry */
		rle = resource_list_find(BUS_GET_RESOURCE_LIST(bus, child),
		    type, rid);
		if (rle != NULL)
			rle->res = NULL;
	}

	if ((rman_get_flags(r) & RF_ACTIVE) != 0) {
		error = bus_deactivate_resource(child, type, rid, r);
		if (error)
			return (error);
	}
	return (rman_release_resource(r));
}

static int
ofwbus_map_intr(device_t dev, device_t child, phandle_t iparent, int icells,
    pcell_t *ispec)
{
	struct ofwbus_intrtabent *intr;
	struct ofwbus_softc *sc;

	sc = device_get_softc(dev);

	mtx_lock(&sc->intr_tab_lock);
	SLIST_FOREACH(intr, &sc->intr_tab, entry) {
		if (intr->iparent != iparent)
			continue;
		if (intr->icells != icells)
			continue;
		if (memcmp(ispec, intr->ispec, sizeof(*ispec)*icells) == 0)
			break;
	}
	if (intr == NULL) {
		intr = malloc(sizeof(*intr), M_DEVBUF, M_ZERO | M_WAITOK);
		intr->iparent = iparent;
		intr->icells = icells;
		intr->ispec = malloc(sizeof(*ispec)*icells, M_DEVBUF, M_WAITOK);
		memcpy(intr->ispec, ispec, sizeof(*ispec)*icells);

		/*
		 * Try to set ispec[0] as the IRQ ID. This is often the
		 * interrupt pin on the PIC, so getting that as the IRQ ID
		 * makes dmesg a little more human-friendly and consistent. No
		 * big deal if that fails and we get something random, though.
		 */

		intr->irq = arm_allocate_irq(ispec[0]);

		SLIST_INSERT_HEAD(&sc->intr_tab, intr, entry);
	}
	mtx_unlock(&sc->intr_tab_lock); /* Entries never deleted, so safe */

	return (intr->irq);
}

static int
ofwbus_register_pic(device_t dev, device_t pic, phandle_t phandle)
{
	struct ofwbus_pic *picent;
	struct ofwbus_softc *sc;

	sc = device_get_softc(dev);

	mtx_lock(&sc->intr_tab_lock);
	SLIST_FOREACH(picent, &sc->pic_tab, entry) {
		if (picent->phandle == phandle)
			panic("PIC %#x already registered as %s", phandle,
			    device_get_nameunit(picent->dev));
	}

	picent = malloc(sizeof(*picent), M_DEVBUF, M_ZERO | M_WAITOK);
	picent->phandle = phandle;
	picent->dev = dev;
	SLIST_INSERT_HEAD(&sc->pic_tab, picent, entry);
	mtx_unlock(&sc->intr_tab_lock);

	return (0);
}

static int
ofwbus_setup_irq_with_pic(device_t dev, int irq)
{
	struct ofwbus_intrtabent *intr;
	struct ofwbus_pic *picent;
	struct ofwbus_softc *sc;
	int err = 0;

	sc = device_get_softc(dev);

	mtx_lock(&sc->intr_tab_lock);
	SLIST_FOREACH(intr, &sc->intr_tab, entry) {
		if (intr->irq == irq)
			break;
	}
	KASSERT(intr != NULL, ("Setting up unmapped IRQ %d", irq));

	if (intr->pic == NULL) {
		SLIST_FOREACH(picent, &sc->pic_tab, entry) {
			if (picent->phandle == intr->phandle)
				break;
		}
		if (picent == NULL)
			panic("Mapping IRQ %d to non-existant PIC %#x", irq,
			    intr->phandle);
		intr->pic = picent->dev;

		/*
		 * NB: this ordering is safe so long as the MD interrupt
		 * code never iterates through all interrupts and calls PIC
		 * methods on them:
		 * 1. No bus code (bus_config_intr()), etc. can happen until
		 *    this function releases intr_tab_lock.
		 * 2. The PIC can't fire the interrupt until PIC_MAP_OFW_IRQ()
		 *    at the very least (hopefully the PIC brings it up masked
		 *    anyway and the MD code unmasks later).
		 */
		arm_set_pic_for_irq(intr->irq, intr->pic);
		err = PIC_MAP_OFW_IRQ(intr->pic, intr->irq, intr->icells,
		    intr->ispec);
		if (err != 0)
			intr->pic = NULL;
	}

	mtx_unlock(&sc->intr_tab_lock);
	return (err);
}

static int
ofwbus_setup_intr(device_t dev, device_t child, struct resource *r,
    int flags, driver_filter_t *filt, driver_intr_t *intr, void *arg,
    void **cookiep)
{
	struct ofwbus_softc *sc = device_get_softc(dev);
	int err;

	if (r == NULL)
		panic("%s: NULL interrupt resource!", __func__);

	/* Make sure we're good to go */
	err = ofwbus_setup_irq_with_pic(rman_get_start(r));
	if (err != 0)
		return (err);

	return (BUS_SETUP_INTR(device_get_parent(dev), child, r, flags, filt,
	    intr, arg, cookiep));
}

static int
ofwbus_bind_intr(device_t dev, device_t child, struct resource *r, int cpu)
{
	struct ofwbus_softc *sc = device_get_softc(dev);
	int err;

	if (r == NULL)
		panic("%s: NULL interrupt resource!", __func__);

	/* Make sure we're good to go */
	err = ofwbus_setup_irq_with_pic(rman_get_start(r));
	if (err != 0)
		return (err);

	return (BUS_BIND_INTR(device_get_parent(dev), child, r, cpu));
}

static int
ofwbus_config_intr(device_t dev, int irq, enum intr_trigger trig,
    enum intr_polarity pol)
{
	struct ofwbus_softc *sc = device_get_softc(dev);
	int err;

	/* Make sure we're good to go */
	err = ofwbus_setup_irq_with_pic(irq)
	if (err != 0)
		return (err);

	return (BUS_CONFIG_INTR(device_get_parent(dev), irq, trig, pol));
}

-------------- next part --------------
Index: dev/ofw/ofw_bus.h
===================================================================
--- dev/ofw/ofw_bus.h	(revision 303312)
+++ dev/ofw/ofw_bus.h	(working copy)
@@ -76,4 +76,10 @@
 	return (OFW_BUS_MAP_INTR(dev, dev, iparent, icells, intr));
 }
 
+static __inline int
+ofw_bus_register_pic(device_t dev, phandle_t phandle)
+{
+	return (OFW_BUS_REGISTER_PIC(dev, dev, phandle));
+}
+
 #endif /* !_DEV_OFW_OFW_BUS_H_ */
Index: dev/ofw/ofw_bus_if.m
===================================================================
--- dev/ofw/ofw_bus_if.m	(revision 303312)
+++ dev/ofw/ofw_bus_if.m	(working copy)
@@ -113,8 +113,23 @@
 		/* If that fails, then assume a one-domain system */
 		return (interrupt[0]);
 	}
+
+	int
+	ofw_bus_default_register_pic(device_t bus, device_t dev,
+	    phandle_t phandle)
+	{
+		/* Propagate up the bus hierarchy until someone handles it. */	
+		if (device_get_parent(bus) != NULL)
+			return OFW_BUS_REGISTER_PIC(device_get_parent(bus), dev,
+			    phandle);
+
+		/* If that fails, then it doesn't matter, I guess */
+		return (0);
+	}
 };
 
+};
+
 # Get the ofw_bus_devinfo struct for the device dev on the bus. Used for bus
 # drivers which use the generic methods in ofw_bus_subr.c to implement the
 # reset of this interface. The default method will return NULL, which means
@@ -159,9 +174,9 @@
 	device_t dev;
 } DEFAULT ofw_bus_default_get_type;
 
-# Map an (interrupt parent, IRQ) pair to a unique system-wide interrupt number.
-# If the interrupt encoding includes a sense field, the interrupt sense will
-# also be configured.
+# Map an (interrupt parent, interrupt specifier) pair to a unique system-wide
+# interrupt number. If the interrupt encoding includes metadata (sense field,
+# for example) that will be passed to the PIC at bring-up.
 METHOD int map_intr {
 	device_t bus;
 	device_t dev;
@@ -169,3 +184,11 @@
 	int icells;
 	pcell_t *interrupt;
 } DEFAULT ofw_bus_default_map_intr;
+
+# Register a device as handling interrupts for a given interrupt-parent value.
+METHOD int register_pic {
+	device_t bus;
+	device_t dev;
+	phandle_t phandle;
+} DEFAULT ofw_bus_default_register_pic;
+
Index: powerpc/powerpc/nexus.c
===================================================================
--- powerpc/powerpc/nexus.c	(revision 303312)
+++ powerpc/powerpc/nexus.c	(working copy)
@@ -72,6 +72,7 @@
 #endif
 static bus_config_intr_t nexus_config_intr;
 static ofw_bus_map_intr_t nexus_ofw_map_intr;
+static ofw_bus_register_pic_t nexus_ofw_register_pic;
 
 static device_method_t nexus_methods[] = {
 	/* Device interface */
@@ -92,6 +93,7 @@
 
 	/* ofw_bus interface */
 	DEVMETHOD(ofw_bus_map_intr,	nexus_ofw_map_intr),
+	DEVMETHOD(ofw_bus_register_pic,	nexus_ofw_register_pic),
 
 	DEVMETHOD_END
 };
@@ -193,6 +195,15 @@
 }
 
 static int
+nexus_ofw_register_pic(device_t dev, device_t pic, phandle_t phandle)
+{
+
+	/* XXX: make up random numbers until intr_machdep.c is updated */
+	powerpc_register_pic(pic, phandle, 124, 4, FALSE);
+	return (0);
+}
+
+static int
 nexus_activate_resource(device_t bus __unused, device_t child __unused,
     int type, int rid __unused, struct resource *r)
 {


More information about the freebsd-arm mailing list