Tying Everything Together

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).

Note

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.

led_identify Function

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.

led_probe Function

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.

led_attach Function

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.

led_detach Function

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.

led_open Function

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.

led_close Function

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).

led_read Function

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.

led_write Function

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.61.170