You now know enough to write your first Newbus driver. Example 7-1 is a simple Newbus driver (based on code written by Murray Stokely) for a fictitious PCI device.
Take a quick look at this code and try to discern some of its structure. If you don’t understand all of it, don’t worry; an explanation follows.
Example 7-1. foo_pci.c
#include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/conf.h> #include <sys/uio.h> #include <sys/bus.h> #include <dev/pci/pcireg.h> #include <dev/pci/pcivar.h> struct foo_pci_softc { device_t device; struct cdev *cdev; }; static d_open_t foo_pci_open; static d_close_t foo_pci_close; static d_read_t foo_pci_read; static d_write_t foo_pci_write; static struct cdevsw foo_pci_cdevsw = { .d_version = D_VERSION, .d_open = foo_pci_open, .d_close = foo_pci_close, .d_read = foo_pci_read, .d_write = foo_pci_write, .d_name = "foo_pci" }; static devclass_t foo_pci_devclass; static int foo_pci_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { struct foo_pci_softc *sc; sc = dev->si_drv1; device_printf(sc->device, "opened successfully "); return (0); } static int foo_pci_close(struct cdev *dev, int fflag, int devtype, struct thread *td) { struct foo_pci_softc *sc; sc = dev->si_drv1; device_printf(sc->device, "closed "); return (0); } static int foo_pci_read(struct cdev *dev, struct uio *uio, int ioflag) { struct foo_pci_softc *sc; sc = dev->si_drv1; device_printf(sc->device, "read request = %dB ", uio->uio_resid); return (0); } static int foo_pci_write(struct cdev *dev, struct uio *uio, int ioflag) { struct foo_pci_softc *sc; sc = dev->si_drv1; device_printf(sc->device, "write request = %dB ", uio->uio_resid); return (0); } static struct _pcsid { uint32_t type; const char *desc; } pci_ids[] = { { 0x1234abcd, "RED PCI Widget" }, { 0x4321fedc, "BLU PCI Widget" }, { 0x00000000, NULL } }; static int foo_pci_probe(device_t dev) { uint32_t type = pci_get_devid(dev); struct _pcsid *ep = pci_ids; while (ep->type && ep->type != type) ep++; if (ep->desc) { device_set_desc(dev, ep->desc); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int foo_pci_attach(device_t dev) { struct foo_pci_softc *sc = device_get_softc(dev); int unit = device_get_unit(dev); sc->device = dev; sc->cdev = make_dev(&foo_pci_cdevsw, unit, UID_ROOT, GID_WHEEL, 0600, "foo_pci%d", unit); sc->cdev->si_drv1 = sc; return (0); } static int foo_pci_detach(device_t dev) { struct foo_pci_softc *sc = device_get_softc(dev); destroy_dev(sc->cdev); return (0); } static device_method_t foo_pci_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, foo_pci_probe), DEVMETHOD(device_attach, foo_pci_attach), DEVMETHOD(device_detach, foo_pci_detach), { 0, 0 } }; static driver_t foo_pci_driver = { "foo_pci", foo_pci_methods, sizeof(struct foo_pci_softc) }; DRIVER_MODULE(foo_pci, pci, foo_pci_driver, foo_pci_devclass, 0, 0);
This driver begins by defining its software context, which will maintain a pointer to its device and the cdev
returned by the make_dev
call.
Next, its character device switch table is defined. This table contains four d_foo
functions named foo_pci_open
, foo_pci_close
, foo_pci_read
, and foo_pci_write
. I’ll describe these functions in d_foo Functions in d_foo Functions.
Then a devclass_t
variable is declared. This variable is passed to the DRIVER_MODULE
macro as its devclass
argument.
Finally, the d_foo
and device_foo
functions are defined. These functions are described in the order they would execute.
The foo_pci_probe
function is the device_probe
implementation for this driver. Before I walk through this function, a description of the pci_ids
array (found around the middle of Example 7-1) is needed.
static struct _pcsid { uint32_t type; const char *desc; } pci_ids[] = { { 0x1234abcd, "RED PCI Widget" }, { 0x4321fedc, "BLU PCI Widget" }, { 0x00000000, NULL } };
This array is composed of three _pcsid
structures. Each _pcsid
structure contains a PCI ID and a description of the PCI device. As you might have guessed, pci_ids
lists the devices that Example 7-1 supports.
Now that I’ve described pci_ids
, let’s walk through foo_pci_probe
.
static int foo_pci_probe(device_t dev) { uint32_t type = pci_get_devid(dev); struct _pcsid *ep = pci_ids; while (ep->type && ep->type != type) ep++; if (ep->desc) { device_set_desc(dev, ep->desc); return (BUS_PROBE_DEFAULT); } return (ENXIO); }
Here, dev
describes an identified device found on the PCI bus. So this function begins by obtaining the PCI ID of dev
. Then it determines if dev
’s PCI ID is listed in pci_ids
. If it is, dev
’s verbose description is set and the success code BUS_PROBE_DEFAULT
is returned.
The verbose description is printed to the system console when foo_pci_attach
executes.
The foo_pci_attach
function is the device_attach
implementation for this driver. Here is its function definition (again):
static int foo_pci_attach(device_t dev) { struct foo_pci_softc *sc = device_get_softc(dev); int unit = device_get_unit(dev); sc->device = dev; sc->cdev = make_dev(&foo_pci_cdevsw, unit, UID_ROOT, GID_WHEEL, 0600, "foo_pci%d", unit); sc->cdev->si_drv1 = sc; return (0); }
Here, dev
describes a device under this driver’s control. Thus, this function starts by getting dev
’s software context and unit number. Then a character device node is created and the variables sc->device
and sc->cdev->si_drv1
are set to dev
and sc
, respectively.
The d_foo
functions (described next) use sc->device
and cdev->si_drv1
to gain access to dev
and sc
.
Because every d_foo
function in Example 7-1 just prints a debug message (that is to say, they’re all basically the same), I’m only going to walk through one of them: foo_pci_open
.
static int foo_pci_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { struct foo_pci_softc *sc; sc = dev->si_drv1; device_printf(sc->device, "opened successfully "); return (0); }
Here, dev
is the cdev
returned by the make_dev
call in foo_pci_attach
. So, this function first obtains its software context. Then it prints a debug message.
The foo_pci_detach
function is the device_detach
implementation for this driver. Here is its function definition (again):
static int foo_pci_detach(device_t dev) { struct foo_pci_softc *sc = device_get_softc(dev); destroy_dev(sc->cdev); return (0); }
Here, dev
describes a device under this driver’s control. Thus, this function simply obtains dev
’s software context to destroy its device node.
Now that we’ve discussed Example 7-1, let’s give it a try:
$sudo kldload ./foo_pci.ko
$kldstat
Id Refs Address Size Name 1 3 0xc0400000 c9f490 kernel 2 1 0xc3af0000 2000 foo_pci.ko $ls -l /dev/foo*
ls: /dev/foo*: No such file or directory
Of course, it fails miserably, because foo_pci_probe
is probing for fictitious PCI devices. Before concluding this chapter, one additional topic bears mentioning.
18.223.119.17