Prex Home > Document Index > Driver Development Guide

Prex Driver Development Guide

For Prex version 0.9.0, 2009/11/07

Table of Contents


Introduction

This document describes how to write a device driver for Prex.

The following documents will be helpful to understand more features of Prex.

Architecture Overview

Kernel I/O Components

When Prex task accesses to the raw device, it calls kernel API for device I/O operations. A microkernel dispatches the I/O request from application to the device driver associated to the target device object. The microkernel provides driver helper service named driver-kernel interface (DKI). DKI includes the interface to the hardware abstraction layer (HAL) which provides the platform dependent functions to handle timer, interrupt or power circuit.

The following figure illustrates the Prex I/O model.

Driver Components
Figure 1. Kernel I/O Components

Driver Support Services

Prex provides two different driver helper interface:

The interface between device drivers and the Prex kernel has been standardized and documented in the reference manual named "Prex Driver-Kernel Interface".

Layered Driver Architecture

Prex supports layered driver architecture. Almost all devices are serviced by a chain of drivers, typically called a driver stack. Each driver in the stack isolates some hardware-dependent features from the drivers above it.

Driver Stack
Figure 2. Driver Stack

The interface in the driver stack is defined as unique interface depends on each device class.

Driver Data Structures

Device Private Data

The softc (software context) structure stores all private data for the target device. The driver framework allocates a block of memory for the softc structure for each device instance. The softc structure is only accessed within the driver, so its definition is local to the driver.

The following is an example about the private data for the 'foo' device.

struct foo_softc {
        device_t        dev;            /* device object */
        ...
};

Usually, the device object for the target device is stored in the softc structure because it is needed to destroy the device object at a later time.

The Prex DKI provides the interface to create, refer and destroy these device private data.

Device Operations

If the driver need to publish the I/O interface for user applications, it must define the devops (device operations) structure. The devops structure enables the kernel to find the I/O entry points of the device driver. The interface to the driver is constant and is defined statically inside the driver.

static struct devops foo_devops = {
        /* open */      foo_open,
        /* close */     foo_close,
        /* read */      foo_read,
        /* write */     foo_write,
        /* ioctl */     foo_ioctl,
        /* devctl */    foo_devctl,
};
open
Creates an initial access handle to the device.
close
Deletes access to the device.
read
Reads from the device.
write
Writes to the device.
ioctl
Controls the I/O operations.
devctl
Controls the device.

Driver Object

Each device driver must define the driver object properly. The driver framework maintains the list of the driver object, and uses it to find the driver for initialization or shutdown.

struct driver foo_driver = {
        /* name */      "foo",
        /* devops */    &foo_devops,
        /* devsz */     sizeof(struct foo_softc),
        /* flags */     0,
        /* probe */     foo_probe,
        /* init */      foo_init,
        /* unload */    foo_unload,
};
name
Null terminated string for the driver name. The driver name may only contain alphanumeric and has a maximum length of 16 characters.
devops
Pointer to the devops structure.
devsz
Size of the device private (softc) structure.
flags
Must be initialized to zero.
probe
Probes the device existence.
init
Initializes device.
unload
Prepares device for unloading.

Note: Set NULL if probe/init/unload routine is not needed.

Device Object

Flow of an I/O Request

The device object represents a physical device or logical/virtual device. It is created by the device driver to communicate with applications or another device driver. A microkernel maintains the name space for the device objects.

Device Object
Figure 3. Device Object

Creating a Device Object

The device object can be created by the following kernel service.

device_t dev;

dev = device_create(self, "foo", D_CHR);

If the driver manages multiple devices, it can creates any number of device objects. In this case, the name of each device object must have an instance number at the tail of the name. The instance number should be zero-based (ex. "foo0", "foo1", ...).

Acessing Device Private Data

A pointer to the device private data can be obtained from the device object by calling device_read().

struct foo_softc *sc;

sc = device_private(dev);

Device Special File

Most Unix-like operating systems treat every hardware device as a file. This design allows applications to access without getting technical details about hardware. Prex also supports the device-special file as POSIX emulation.

In case of Prex, the device file is handled by the file system server in the user mode. So, an application will send an I/O request IPC message to the file system server instead of calling kernel API for device access. The device file system (devfs) maps each device object to the device file under /dev directory.

[prex:/dev]# ls -l
b---------  1 prex   Nov  9 2009 12:00       0 fd0
b---------  1 prex   Nov  9 2009 12:00       0 ram0
crw-------  1 prex   Nov  9 2009 12:00       0 zero
crw-------  1 prex   Nov  9 2009 12:00       0 null
crw-------  1 prex   Nov  9 2009 12:00       0 rtc
c---------  1 prex   Nov  9 2009 12:00       0 cpufreq
crw-------  1 prex   Nov  9 2009 12:00       0 vga
crw-------  1 prex   Nov  9 2009 12:00       0 kbd
crw-------  1 prex   Nov  9 2009 12:00       0 tty
crw-------  1 prex   Nov  9 2009 12:00       0 console
c---------  1 prex   Nov  9 2009 12:00       0 pm
total 11
[prex:/dev]#

Driver Initialization

xxx_probe()

During system bootstrap, the driver framework searches the system for devices. For each device driver, its probe routine is called to check the existence of target device. The argument for the probe routine is a pointer to the driver structure.

static int
foo_probe(struct driver *self)
{
        ...
}

If the device is not available for some reasons, it should return ENXIO. Otherwise, it returns 0 for success. If the probe routine returns an error, the driver framework mark it as inactive and does not call any routnies in that driver.

If the driver does not provide the probe routine, we assume the target device is avilable at anytime.

The probe routine is used only for checking the device availability. So, we can not assume the probe routine programs the device hardware before the initliaze routine described below.

xxx_init()

The driver framework will call each probe() routine for all drivers at first. Next, the driver framework calls an init() routine of the driver which returns 0 for probe() routine.

static int
foo_init(struct driver *self)
{
        ...
}

In general, the init() routine will do the following jobs:

Driver State

The driver state will be change as following figure:

Driver State
Figure 4. Driver State

I/O Routines

xxx_open()/xxx_close()

T.B.D.

static int
foo_open(device_t dev, int mode)
{
	...
}
static int
foo_close(device_t dev)
{
	...
}

xxx_read()/xxx_write()

T.B.D.

static int
foo_read(device_t dev, char *buf, size_t *nbyte, int blkno)
{
	...
}
static int
foo_write(device_t dev, char *buf, size_t *nbyte, int blkno)
{
	...
}

xxx_ioctl()

T.B.D.

static int
foo_ioctl(device_t dev, u_long cmd, void *arg)
{
	...
}

xxx_devctl()

T.B.D.

static int
foo_devctl(device_t dev, u_long cmd, void *arg)
{
	...
}

Interrupt Handling

Interrupt Sequence

Interrupt Processing
Figure 4. Interrupt Processing

  1. A device signals the interrupt to ICU.
  2. ICU transfers the control to HAL.
  3. HAL calls the registered ISR associated with interrupt vector.
  4. ISR processes interrupt and returns with completion status.
  5. A scheduler starts IST to process remaining job.

Registering Interrupt

irq_t irq_attach(int irqno, int prio, int shared, int (*isr)(void *), void (*ist)(void *), void *data);

Disabling Interrupt

Deferred Procedure Call (DPC)

Memory Allocation

Accessing User Memory

Callout Timer

Timer Object

Synchronization

Raising Exceptions

Power Management

Driver Sample

Choosing a Driver Name

The driver name must be unique in the system, and it should be same name with the name of the driver source file in order to identify them easily. For example, the device driver named "ramdisk" will be put to the file named "ramdisk.c".

The driver name may only contain alphanumeric and has a maximum length of 16 characters.

Required Header File

Every device driver must include the common driver header file.

#include <driver.h>

Defining Driver Data

Driver Private Data

Since the ramdisk driver does not handle an actual device hardware, it just holds information about the ramdisk image.

struct ramdisk_softc {
        device_t        dev;            /* device object */
        char            *addr;          /* base address of image */
        size_t          size;           /* image size */
};

Device Operations

The ramdisk driver supports only read() and write() operations.

static struct devops ramdisk_devops = {
        /* open */      no_open,
        /* close */     no_close,
        /* read */      ramdisk_read,
        /* write */     ramdisk_write,
        /* ioctl */     no_ioctl,
        /* devctl */    no_devctl,
};

Driver Object

There is no unload routine in the ramdisk driver.
struct driver ramdisk_driver = {
        /* name */      "ramdisk",
        /* devops */    &ramdisk_devops,
        /* devsz */     sizeof(struct ramdisk_softc),
        /* flags */     0,
        /* probe */     ramdisk_probe,
        /* init */      ramdisk_init,
        /* unload */    NULL,
};

Probing Device

The ramdisk driver will check whether the disk image is loaded by boot loader. It returns ENXIO if it can not find the disk image.

static int
ramdisk_probe(struct driver *self)
{
        struct bootinfo *bi;
        struct physmem *phys;

        machine_bootinfo(&bi);
        phys = &bi->bootdisk;
        if (phys->size == 0) {
                return ENXIO;
        }
        return 0;
}

Initializing Device

static int
ramdisk_init(struct driver *self)
{
        struct ramdisk_softc *sc;
        struct bootinfo *bi;
        struct physmem *phys;
        device_t dev;

        machine_bootinfo(&bi);
        phys = &bi->bootdisk;

        dev = device_create(self, "ram0", D_BLK|D_PROT);

        sc = device_private(dev);
        sc->dev = dev;
        sc->addr = (char *)ptokv(phys->base);
        sc->size = (size_t)phys->size;

        return 0;
}

Defining I/O Operations

static int
ramdisk_read(device_t dev, char *buf, size_t *nbyte, int blkno)
{
        struct ramdisk_softc *sc = device_private(dev);
        int offset = blkno * BSIZE;
        void *kbuf;
        size_t nr_read;

        DPRINTF(("ramdisk_read: buf=%x nbyte=%d blkno=%x\n",
                 buf, *nbyte, blkno));

        /* Check overrun */
        if (offset > (int)sc->size) {
                DPRINTF(("ramdisk_read: overrun!\n"));
                return EIO;
        }
        nr_read = *nbyte;
        if (offset + nr_read > (int)sc->size)
                nr_read = sc->size - offset;

        /* Translate buffer address to kernel address */
        if ((kbuf = kmem_map(buf, nr_read)) == NULL) {
                return EFAULT;
        }

        /* Copy data */
        memcpy(kbuf, sc->addr + offset, nr_read);
        *nbyte = nr_read;
        return 0;
}

Installing a Driver

You can add new driver into system by using "device" option in the platform configureation file under /conf. The order of device options indicates the driver initialization order. So, it's important to put the device option with appropriate order considering for the driver dependency.

device          pm              # Power management
device          i8237           # I8237 DMA controller
device          cons            # System console
device          wscons          # Workstation console
device          pckbd           # PC/AT Keyboard (P:wscons)
device          vga             # VGA video (P:wscons)
device          cpufreq         # CPU frequency control
device          est             # Intel enhanced speedstep (P:cpufreq)
device          rtc             # Logical RTC
device          mc146818        # MC146818 Real time clock (P:rtc)
device          null            # NULL device
device          zero            # Zero device
device          fdd             # Floppy disk drive
device          ramdisk         # RAM disk          <== Add!!