New-bus unit wiring via hints..

John Baldwin jhb at FreeBSD.org
Thu Oct 11 14:42:55 PDT 2007


So one of the things I've wanted to get working for a while is to make ACPI or 
PNP BIOS devices that are also enumerated by hints "claim" the hints device 
(assuming that ACPI/PNP BIOS has a more accurate resource list) and use the 
unit number of the hint it claims.  For the non-ACPI case this would get rid 
of many of the boot messages about PNP devices not probing b/c they couldn't 
get resources.  For the ACPI case allowing devices to be wired to units based 
on hints will fix the COM port switching problem where a BIOS lists COM2 
before COM1 in the ACPI namespace and so the FreeBSD kernel ends up calling 
COM1 sio1 and COM2 sio0.  The idea then, is that ideally a bus driver (like 
acpi0 for ACPI, or isa0 for PNP BIOS) would be able to hint to new-bus what a 
potential device's unit number should be by comparing properties of the 
device about to be probed with the list of hints.

It should also not trample on the existing method of enumerating devices from 
hints using bus_enumerate_hinted_children() / BUS_HINTED_CHILD().

There are some assumptions about hints that do have to change slightly:

1) For a device to count as a "hint" it has to have an "at" hint.
   (hint.foo.0.at="blah").
2) An "at" hint for a given (name, unit) _reserves_ that unit number.  New-bus
   will no longer use that unit for a device with a wildcard unit unless the
   bus specifies that it should use this unit.  Note that if you add a device
   with a specific unit it will still use that unit regardless of any hints
   that may exist.

The way I've done this is to add a new bus callout that is invoked by new-bus 
when it is going to assign a unit number to a device prior to probing the 
device.  New-bus will now invoke a BUS_HINT_DEVICE_UNIT() method for any 
devices with a wildcard unit to ask the parent bus driver if it wants to 
specify a unit for this device.  BUS_HINT_DEVICE_UNIT() gets a reference to 
the new device and the name of the devclass it is being added to.

The bus driver can then employ whatever matching scheme it wants.

So one simple example I have is that this can be used to wire PCI devices.  
For example, one impl I have of PCI unit wiring is to make the "at" hint be a 
selector similar to the argument you give to pciconf(8).  Thus, you could do:

hint.em.0.at="pci4:3:0"

and this will bind em0 to the PCI device at bus 4, slot 3, function 0.  (And 
yes, it is domain aware, but supports the same "shortcut" notations for 
domain 0 that pciconf(8) does.)  Note that if there isn't an em(4) device at 
pci4:3:0, then em0 is simply reserved, and any other em(4) devices in the 
system will use em1, em2, etc.  The code for the PCI routine to handle this 
wiring looks something like this:

void
pci_hint_device_unit(device_t bus, device_t child, const char *name, int 
*unitp)
{
	struct pci_devinfo *dinfo = device_get_ivars(child);
	char buf[32];
	int line, unit;

	line = 0;
	snprintf(buf, sizeof(buf), "pci%d:%d:%d:%d", dinfo->cfg.domain,
	    dinfo->cfg.bus, dinfo->cfg.slot, dinfo->cfg.func);
	line = 0;
	if (resource_find_dev(&line, name, &unit, "at", buf) == 0)
		*unitp = unit;
	else if (dinfo->cfg.domain == 0) {
		snprintf(buf, sizeof(buf), "pci%d:%d:%d", dinfo->cfg.bus,
		    dinfo->cfg.slot, dinfo->cfg.func);
		line = 0;
		if (resource_find_dev(&line, name, &unit, "at", buf) == 0)
			*unitp = unit;
		...
	}
}

Alternatively, if a bus wants to do matching based on resources specified in 
the hints (like acpi(4) or isa(4)) the BUS_HINT_DEVICE_UNIT() method can use 
a loop to iterate over all the "foo" devices and perform more detailed checks 
on each unit:

static void
acpi_hint_device_unit(device_t acdev, device_t child, const char *name,
    int *unitp)
{
	...

    /*
     * Iterate over all the hints for the devices with the specified
     * name to see if one's resources are a subset of this device.
     */
    line = 0;
    for (;;) {
	if (resource_find_dev(&line, name, &unit, "at", NULL) != 0)
	    break;

	/* Must have an "at" for acpi or isa. */
	resource_string_value(name, unit, "at", &s);
	if (!(strcmp(s, "acpi0") == 0 || strcmp(s, "acpi") == 0 ||
	    strcmp(s, "isa0") == 0 || strcmp(s, "isa") == 0))
	    continue;

	/*
	 * Check for matching resources.  We must have at least one,
	 * and all resources specified have to match.
	 *
	 */
	matches = 0;

	/* ... here it compares "port", "mem", "irq", etc. resources
	   with the resources ACPI specified for this device via _CRS */

	if (matches > 0) {
	    /* We have a winner! */
	    *unitp = unit;
	    break;
	}
    }
}

The full patch of what I have so far is at 
http://www.FreeBSD.org/~jhb/patches/hint_wire.patch and it includes the 
new-bus changes as well as adding unit-wiring to ACPI, ISA, and PCI.

However, what I would like to commit first is a simpler version that just 
includes the new-bus changes and the ACPI unit-wiring.  The ACPI changes have 
to be included because:

1) ACPI doesn't currently respect hints, so it uses sio0 for the first serial
   port it encounters.  Without a BUS_HINT_DEVICE_UNIT() method, new-bus would
   reserve sio0 and sio1 via the default hints and would assign the first
   serial port in ACPI to sio2.  The current ISA bus doesn't suffer from this
   as it statically adds devices based on hints before probing any children
   devices.

2) One of the things this fixes that is visible to users is that if your
   machine gets the COM ports backwards when using ACPI it should now get
   them correct (COM1 as sio0) assuming your COM ports use the default hints
   and you have the default sio hints in your /boot/device.hints file.

The simple patch is at 
http://www.FreeBSD.org/~jhb/patches/hint_wire_acpi.patch

Testing and feedback welcome.

-- 
John Baldwin


More information about the freebsd-current mailing list