Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 20 Dec 2000 14:55:01 +0900
From:      Kazutaka YOKOTA <yokota@zodiac.mech.utsunomiya-u.ac.jp>
To:        freebsd-hackers@freebsd.org
Cc:        yokota@zodiac.mech.utsunomiya-u.ac.jp
Subject:   Request for comments: ISA_PNP_SCAN() (long)
Message-ID:  <200012200555.OAA01093@zodiac.mech.utsunomiya-u.ac.jp>

next in thread | raw e-mail | index | archive | help
This is to propose a new ISA bus method to sys/isa/isa_common.c.
The new method is to enumerate PnP device instances matching the
specified PnP IDs. (Well, may be this is a kludge after all.)

device_t ISA_PNP_SCAN(device_t bus, struct isa_pnp_id *ids, int *n);

It will return the (n + 1)th instance of the given PnP IDs on the
specified ISA bus. You set -1 to n to obtain the first PnP instance
matching the given PnP IDs and can enumerate all matching instances
by calling ISA_PNP_SCAN() until it returns NULL.

I think this is useful for the following situation.

The ISA device drivers supporting PnP look like the following
in -CURRENT.

struct isa_pnp_id foo_ids[] = {
	{ 0xNNNNNNN,	"Foo bar" },
};

int
foo_probe(device_t dev)
{
	if (ISA_PNP_PROBE(dev, foo_ids) == ENXIO)
		return ENXIO;
	...
}

ISA_PNP_PROBE() returns 0 if the ISA device instance dev has the
matching PNP ID, returns ENXIO if the ID doesn't match, and returns
ENOENT if the device instance doesn't have a PNP ID (because it was
created by the isahint driver, based on /boot/device.hints).  This
way, the driver can work correctly with both the "hint" (non-PnP) device
instance and the PnP device instance.

The trouble is that we always need to have hints for this driver in
/boot/device.hints for those systems without a PnP BIOS.  Then, the
isahint driver will create a device instance.  The pnpbios driver will
create a PnP device instance separately if the PnP BIOS exists and
reports the presence of a device.

Problem 1:
In -CURRENT the non-PnP device instance is probed first.  If this is
successful, it will become available to the system as the 'foo0'
device.  Probe on the PnP device instance will fail in this case
because the resources for this device has already been claimed by the
non-PnP device instance, and the user will see erroneous boot message
"unknown: <PNP####> cannot assign resources". 

Problem 2:
If the non-PnP device instance fails probe (because device hints are
wrong), the PnP device instance will succeed (because its resource
description is supposed to be correct). The PnP device instance will
become available in the system as 'foo1', rather than 'foo0'.  This is
because the non-PnP device instance wasn't deleted after its probe
failed. 

To avoid the second problem, we may prepare two separate drivers for
non-PnP and PnP device instances as follows.

/* the driver for non-PnP device instance */
driver_t foo_driver = {
	"foo",
	foo_methods,
	sizeof(struct foo_softc),
};

int
foo_probe(device_t dev)
{
	/* proceed only if this is not a PnP device instance */
	if (ISA_PNP_PROBE(dev, foo_ids) != ENOENT)
		return ENXIO;
	...
}

/* the driver for PnP device instance */
driver_t foopnp_driver = {
	"foopnp",
	foopnp_methods,
	sizeof(struct foo_softc),
};

int
foopnp_probe(device_t dev)
{
	/* proceed only if this is a PnP device instance */
	if (ISA_PNP_PROBE(dev, foo_ids) != 0)
		return ENXIO;
	...
}

This way, we will have 'foo0' when the PnP BIOS is not present or
device hints for the non-PnP device instance are correct. Otherwise,
we will have 'foopnp0'.

But, we will still have the first problem: "unknown: <PNPXXXX> can't
assign resources."

If we have ISA_PNP_SCAN() above, we can do something like below to
solve this problem.

/* the driver for non-PnP device instance */
driver_t foo_driver = {
	"foo",
	foo_methods,
	sizeof(struct foo_softc),
};

int
foo_probe(device_t dev)
{
	device_t bus;
	device_t pnpdev;
	int unit;
	int i;

	/* proceed only if this is a non-PnP device instance */
	if (ISA_PNP_PROBE(dev, foo_ids) != ENOENT)
		return ENXIO;

	bus = device_get_parent(dev);
	unit = device_get_unit(dev);

	/* fail if we have a PnP sibling */
	i = unit - 1;
	pnpdev = ISA_PNP_SCAN(bus, foo_ids, &i);
	if (pnpdev && device_get_state(pnpdev) == DS_NOTPRESENT)
		return ENXIO;
	...
}

/* the driver for PnP device instance */
driver_t foopnp_driver = {
	"foopnp",
	foopnp_methods,
	sizeof(struct foo_softc),
};

int
foopnp_probe(device_t dev)
{
	/* proceed only if this is a PnP device instance */
	if (ISA_PNP_PROBE(dev, foo_ids) != 0)
		return ENXIO;
	...
}

The non-PnP device instance will fail, regardless of device hints, if
a PnP device instance for this device exists on this ISA bus. Then
we will always have 'foopnp0' if the PnP BIOS reports the pretense of
this device.  The non-PnP device instance will succeed, as 'foo0',
only if the PnP BIOS doesn't exist and device hints are correct.

This way, we are now clear of the two problems described above.

We can collapse the device methods for the two drivers into one.

driver_t foo_driver = {
	"foo",
	foo_common_methods,
	sizeof(struct foo_softc),
};
driver_t foopnp_driver = {
	"foopnp",
	foo_common_methods,
	sizeof(struct foo_softc),
};

/* common methods */
int
foo_probe(device_t dev)
{
	device_t bus;
	device_t pnpdev;
	int i;

	switch (ISA_PNP_PROBE(dev, foo_ids)) {
	case ENXIO:
	default:
		return ENXIO;
	case ENOENT:
		bus = device_get_parent(dev);
		unit = device_get_unit(dev);
		i = unit - 1;
		pnpdev = ISA_PNP_SCAN(bus, foo_ids, &i);
		if (pnpdev && device_get_state(pnpdev) == DS_NOTPRESENT)
			return ENXIO;
		break;
	case 0:
		break;
	}
	....
}

If we don't like the idea the device name change depending on the
presence of the PnP BIOS, we may do the following.

/* the driver for non-PnP device instance */
driver_t foo_driver = {
	"foo",
	foo_methods,
	sizeof(struct foo_softc),
};

int
foo_probe(device_t dev)
{
	device_t bus;
	device_t pnpdev;
	int unit;
	int i;

	if (ISA_PNP_PROBE(dev, foo_ids) != ENOENT)
		return ENXIO;

	bus = device_get_parent(dev);
	unit = device_get_unit(dev);

	i = unit - 1;
	pnpdev = ISA_PNP_SCAN(bus, foo_ids, &i);
	if (pnpdev && device_get_state(pnpdev) == DS_NOTPRESENT)
		return ENXIO;
	...
}

/* the driver for PnP device instance */
driver_t foopnp_driver = {
	"foopnp",
	foopnp_methods,
	sizeof(struct foo_softc),
};

int
foopnp_probe(device_t dev)
{
	if (ISA_PNP_PROBE(dev, foo_ids) != 0)
		return ENXIO;

	/* do nothing else but succeed */
	device_quiet(dev);
	return 0;
}

int
foopnp_attach(device_t dev)
{
	device_t bus;
	device_t hintdev;
	device_t newdev;
	u_int32_t flags;

	bus = device_get_parent(dev);
	unit = device_get_unit(dev);
	flags = 0;

	/* delete the existing non-PnP instance */
	hintdev = device_find_child(bus, "foo", unit);
	if (hintdev) {
		flags = device_get_flags(hintdev);
		device_delete_child(bus, hintdev);
	}

	/* create a new non-PnP instance */
	newdev = BUS_ADD_DEVICE(bus, 0, "foo", unit);

	/* _MOVE_ all resources from dev to newdev */
	device_set_flags(newdev, flags);
	bus_get_resource(dev,...);
	bus_set_resource(newdev,...);

	/* probe the new non-PnP instance */
	device_probe_and_attach(newdev);

	return 0;
}

In this case, the PnP device instance is nothing but a resource
holder. The non-PnP driver will do all the actual probe and attach.

The non-PnP device instance will be deleted during
foopnp_attach() and a new, non-PnP device instance will be created,
given resources recorded in the PnP device instance, and probed. 

Note that non-PnP's foo_probe() works correctly both in the first
invocation (for the instance created by the isahint driver) and in the
second invocation (for the instance created in foopnp_attach()).

Comments?

Kazu


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-hackers" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200012200555.OAA01093>