Example 10-1 is a simple driver for an i-Opener’s LEDs (based on code written by Warner Losh). An i-Opener includes two LEDs that are controlled by bits 0 and 1 of the register located at 0x404c. Hopefully, this example will clarify any misunderstandings you may have about PMIO (and MMIO).
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 10-1. led.c
#include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/bus.h> #include <sys/conf.h> #include <sys/uio.h> #include <sys/lock.h> #include <sys/mutex.h> #include <machine/bus.h> #include <sys/rman.h> #include <machine/resource.h> #define LED_IO_ADDR 0x404c #define LED_NUM 2 struct led_softc { int sc_io_rid; struct resource *sc_io_resource; struct cdev *sc_cdev0; struct cdev *sc_cdev1; u_int32_t sc_open_mask; u_int32_t sc_read_mask; struct mtx sc_mutex; }; static devclass_t led_devclass; static d_open_t led_open; static d_close_t led_close; static d_read_t led_read; static d_write_t led_write; static struct cdevsw led_cdevsw = { .d_version = D_VERSION, .d_open = led_open, .d_close = led_close, .d_read = led_read, .d_write = led_write, .d_name = "led" }; static int led_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { int led = dev2unit(dev) & 0xff; struct led_softc *sc = dev->si_drv1; if (led >= LED_NUM) return (ENXIO); mtx_lock(&sc->sc_mutex); if (sc->sc_open_mask & (1 << led)) { mtx_unlock(&sc->sc_mutex); return (EBUSY); } sc->sc_open_mask |= 1 << led; sc->sc_read_mask |= 1 << led; mtx_unlock(&sc->sc_mutex); return (0); } static int led_close(struct cdev *dev, int fflag, int devtype, struct thread *td) { int led = dev2unit(dev) & 0xff; struct led_softc *sc = dev->si_drv1; if (led >= LED_NUM) return (ENXIO); mtx_lock(&sc->sc_mutex); sc->sc_open_mask &= ˜(1 << led); mtx_unlock(&sc->sc_mutex); return (0); } static int led_read(struct cdev *dev, struct uio *uio, int ioflag) { int led = dev2unit(dev) & 0xff; struct led_softc *sc = dev->si_drv1; u_int8_t ch; int error; if (led >= LED_NUM) return (ENXIO); mtx_lock(&sc->sc_mutex); /* No error EOF condition. */ if (!(sc->sc_read_mask & (1 << led))) { mtx_unlock(&sc->sc_mutex); return (0); } sc->sc_read_mask &= ˜(1 << led); mtx_unlock(&sc->sc_mutex); ch = bus_read_1(sc->sc_io_resource, 0); if (ch & (1 << led)) ch = '1'; else ch = '0'; error = uiomove(&ch, 1, uio); return (error); } static int led_write(struct cdev *dev, struct uio *uio, int ioflag) { int led = dev2unit(dev) & 0xff; struct led_softc *sc = dev->si_drv1; u_int8_t ch; u_int8_t old; int error; if (led >= LED_NUM) return (ENXIO); error = uiomove(&ch, 1, uio); if (error) return (error); old = bus_read_1(sc->sc_io_resource, 0); if (ch & 1) old |= (1 << led); else old &= ˜(1 << led); bus_write_1(sc->sc_io_resource, 0, old); return (error); } static void led_identify(driver_t *driver, device_t parent) { device_t child; child = device_find_child(parent, "led", −1); if (!child) { child = BUS_ADD_CHILD(parent, 0, "led", −1); bus_set_resource(child, SYS_RES_IOPORT, 0, LED_IO_ADDR, 1); } } static int led_probe(device_t dev) { if (!bus_get_resource_start(dev, SYS_RES_IOPORT, 0)) return (ENXIO); device_set_desc(dev, "I/O Port Example"); return (BUS_PROBE_SPECIFIC); } static int led_attach(device_t dev) { struct led_softc *sc = device_get_softc(dev); sc->sc_io_rid = 0; sc->sc_io_resource = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->sc_io_rid, RF_ACTIVE); if (!sc->sc_io_resource) { device_printf(dev, "unable to allocate resource "); return (ENXIO); } sc->sc_open_mask = 0; sc->sc_read_mask = 0; mtx_init(&sc->sc_mutex, "led", NULL, MTX_DEF); sc->sc_cdev0 = make_dev(&led_cdevsw, 0, UID_ROOT, GID_WHEEL, 0644, "led0"); sc->sc_cdev1 = make_dev(&led_cdevsw, 1, UID_ROOT, GID_WHEEL, 0644, "led1"); sc->sc_cdev0->si_drv1 = sc; sc->sc_cdev1->si_drv1 = sc; return (0); } static int led_detach(device_t dev) { struct led_softc *sc = device_get_softc(dev); destroy_dev(sc->sc_cdev0); destroy_dev(sc->sc_cdev1); mtx_destroy(&sc->sc_mutex); bus_release_resource(dev, SYS_RES_IOPORT, sc->sc_io_rid, sc->sc_io_resource); return (0); } static device_method_t led_methods[] = { /* Device interface. */ DEVMETHOD(device_identify, led_identify), DEVMETHOD(device_probe, led_probe), DEVMETHOD(device_attach, led_attach), DEVMETHOD(device_detach, led_detach), { 0, 0 } }; static driver_t led_driver = { "led", led_methods, sizeof(struct led_softc) }; DRIVER_MODULE(led, isa, led_driver, led_devclass, 0, 0);
Before I describe the functions defined in Example 10-1, note that the constant LED_IO_ADDR
is defined as 0x404c
and that the constant LED_NUM
is defined as 2
.
The following sections describe the functions defined in Example 10-1 in the order they would roughly execute.
The led_identify
function is the device_identify
implementation for this driver. This function is required because the ISA bus cannot identify its children unaided. Here is the function definition for led_identify
(again):
static void led_identify(driver_t *driver, device_t parent) { device_t child; child = device_find_child(parent, "led", −1); if (!child) { child = BUS_ADD_CHILD(parent, 0, "led", −1); bus_set_resource(child, SYS_RES_IOPORT, 0, LED_IO_ADDR, 1); } }
This function first determines if the ISA bus has identified a child device named "led"
. If it has not, then "led"
is appended to the ISA bus’s catalog of identified children. Afterward, bus_set_resource
is called to specify that I/O port access for "led"
starts at LED_IO_ADDR
.
The led_probe
function is the device_probe
implementation for this driver. Here is its function definition (again):
static int led_probe(device_t dev) { if (!bus_get_resource_start(dev, SYS_RES_IOPORT, 0)) return (ENXIO); device_set_desc(dev, "I/O Port Example"); return (BUS_PROBE_SPECIFIC); }
This function first checks if "led"
can acquire I/O port access. Afterward, the verbose description of "led"
is set and the success code BUS_PROBE_SPECIFIC
is returned.
The led_attach
function is the device_attach
implementation for this driver. Here is its function definition (again):
static int led_attach(device_t dev) { struct led_softc *sc = device_get_softc(dev); sc->sc_io_rid = 0; sc->sc_io_resource = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->sc_io_rid, RF_ACTIVE); if (!sc->sc_io_resource) { device_printf(dev, "unable to allocate resource "); return (ENXIO); } sc->sc_open_mask = 0; sc->sc_read_mask = 0; mtx_init(&sc->sc_mutex, "led", NULL, MTX_DEF); sc->sc_cdev0 = make_dev(&led_cdevsw, 0, UID_ROOT, GID_WHEEL, 0644, "led0"); sc->sc_cdev1 = make_dev(&led_cdevsw, 1, UID_ROOT, GID_WHEEL, 0644, "led1"); sc->sc_cdev0->si_drv1 = sc; sc->sc_cdev1->si_drv1 = sc; return (0); }
This function begins by acquiring an I/O port. If unsuccessful, the error code ENXIO
is returned. Then the member variables sc_open_mask
and sc_read_mask
are zeroed; in the d_foo
functions, these variables will be protected by sc_mutex
. Finally, led_attach
creates a character device node for each LED.
The led_detach
function is the device_detach
implementation for this driver. Here is its function definition (again):
static int led_detach(device_t dev) { struct led_softc *sc = device_get_softc(dev); destroy_dev(sc->sc_cdev0); destroy_dev(sc->sc_cdev1); mtx_destroy(&sc->sc_mutex); bus_release_resource(dev, SYS_RES_IOPORT, sc->sc_io_rid, sc->sc_io_resource); return (0); }
This function begins by destroying its device nodes. Once this is done, it destroys its mutex and releases its I/O port.
The led_open
function is defined in led_cdevsw
(that is, the character device switch table) as the d_open
operation. Here is its function definition (again):
static int led_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { int led = dev2unit(dev) & 0xff; struct led_softc *sc = dev->si_drv1; if (led >= LED_NUM) return (ENXIO); mtx_lock(&sc->sc_mutex); if (sc->sc_open_mask & (1 << led)) { mtx_unlock(&sc->sc_mutex); return (EBUSY); } sc->sc_open_mask |= 1 << led; sc->sc_read_mask |= 1 << led; mtx_unlock(&sc->sc_mutex); return (0); }
This function first stores in led
the unit number of the device node being opened. If led
is greater than or equal to LED_NUM
, then ENXIO
is returned. Next, the value of sc_open_mask
is examined. If its led
bit does not equal 0
, which indicates that another process has opened the device, then EBUSY
is returned. Otherwise, sc_open_mask
and sc_read_mask
are set to include 1 << led
. That is, their led
bit will be changed to 1
.
The led_close
function is defined in led_cdevsw
as the d_close
operation. Here is its function definition (again):
static int led_close(struct cdev *dev, int fflag, int devtype, struct thread *td) { int led = dev2unit(dev) & 0xff; struct led_softc *sc = dev->si_drv1; if (led >= LED_NUM) return (ENXIO); mtx_lock(&sc->sc_mutex); sc->sc_open_mask &= ˜(1 << led); mtx_unlock(&sc->sc_mutex); return (0); }
As you can see, this function simply clears sc_open_mask
’s led
bit (which allows another process to open this device).
The led_read
function is defined in led_cdevsw
as the d_read
operation. This function returns one character indicating whether the LED is on (1
) or off (0
). Here is its function definition (again):
static int led_read(struct cdev *dev, struct uio *uio, int ioflag) { int led = dev2unit(dev) & 0xff; struct led_softc *sc = dev->si_drv1; u_int8_t ch; int error; if (led >= LED_NUM) return (ENXIO); mtx_lock(&sc->sc_mutex); /* No error EOF condition. */ if (!(sc->sc_read_mask & (1 << led))) { mtx_unlock(&sc->sc_mutex); return (0); } sc->sc_read_mask &= ˜(1 << led); mtx_unlock(&sc->sc_mutex); ch = bus_read_1(sc->sc_io_resource, 0); if (ch & (1 << led)) ch = '1'; else ch = '0'; error = uiomove(&ch, 1, uio); return (error); }
This function first checks that sc_read_mask
’s led
bit is set; otherwise, it exits. Next, 1 byte from the LED’s control register is read into ch
. Then ch
’s led
bit is isolated and its value is returned to user space.
The led_write
function is defined in led_cdevsw
as the d_write
operation. This function takes in one character to turn on (1
) or off (0
) the LED. Here is its function definition (again):
static int led_write(struct cdev *dev, struct uio *uio, int ioflag) { int led = dev2unit(dev) & 0xff; struct led_softc *sc = dev->si_drv1; u_int8_t ch; u_int8_t old; int error; if (led >= LED_NUM) return (ENXIO); error = uiomove(&ch, 1, uio); if (error) return (error); old = bus_read_1(sc->sc_io_resource, 0); if (ch & 1) old |= (1 << led); else old &= ˜(1 << led); bus_write_1(sc->sc_io_resource, 0, old); return (error); }
This function first copies one character from user space to ch
. Next, 1 byte from the LED’s control register is read into old
. Then, based on the value from user space, old
’s led
bit is turned on or off. Afterward, old
is written back to the LED’s control register.
3.145.61.170