ChorusOS 4.0 Device Driver Framework Guide

Write the Init Function

The initialization routine of a device driver component is optional. In case it is not provided (NULL entry point), the driver is typically a "probe only" driver.

The initialization process ( drv_init) of a leaf device driver goes through the following steps:

First of all, the driver must establish connection to the parent driver by calling open . In open, the driver specifies call-back handlers which will be used by the parent driver to manage the device instance driver (such as to shutdown the driver instance). In addition, global bus events (such as a catastrophic bus error) are delivered to the driver through a call-back handler.

Once the child-to-parent connection is established, the driver may use services provided by the parent driver. Typically, at this point, the driver asks its parent driver to make the bus hardware resources needed for the device available.


Note -

The bus resources needed for the device are specified as properties in the device node. These resources are already allocated by the parent bus/nexus driver prior to the drv_init invocation. To make a given bus resource available (such as device I/O registers), the driver obtains an appropriate property value ("io-regs") and calls an appropriate bus/nexus service routine (such as io_map).


Once access to the device hardware is allowed, the driver initializes it to an operational state. Once initialized, the driver performs self-registration in the device registry to declare itself as a new device (such as a new device driver instance) within the system.

Once the device (the device driver instance) is registered, a driver client may find it in the registry (using the device registry API) and may perform operations on device-calling driver service routines exported through the device registry entry.


Note -

The driver is not necessarily required to register any device driver instances or offer any device service routines through the device registry. Device driver instance registration is required only for clients that find their devices through the device registry. If other client-to-driver binding mechanisms are in use, the associated devices need not take part in the device registry.


The drv_init routine is called in the context of the DKI thread. This makes it possible to directly invoke the bus/nexus and DKI services allowed in the DKI thread context.

Below is an example of the initialization function of the NS16x50 compatible UART device driver. The NS16_DEV_REMOVAL and NS16_DRV_UNLOAD compilation flags are used to allow downsizing of the driver component at compile time in case these mechanisms are not needed.

    /*
     * Init the NS16x50 uart. Called by BUS driver.
     */
    static void
ns16_init (DevNode node, void* pOps, void* pId)
{
    BusOps*       busOps = (BusOps*)pOps;
    DevProperty   prop;
    Ns16_Device*  dev;
    void*         ioRegs;
    void*         intr;
    KnError       res;
    char*         dpath;
    int           dpathLeng;

    dpathLeng = dtreePathLeng(node);
    dpath     = (char*) svMemAlloc(dpathLeng);
    if (!dpath) {
        DKI_ERR(("%s: error -- no enough memory\n", NS16_DRV_NAME));
        return;
    }
    dtreePathGet(node, dpath);

        /*
         * Allocate the device descriptor
         * (i.e. the driver instance local data)
         */
    dev = (Ns16_Device*)svMemAlloc(sizeof(Ns16_Device));
    if (dev == NULL) {
        DKI_ERR(("%s: error -- no enough memory\n", dpath));
        return;
    }

    bzero(dev, sizeof(Ns16_Device));

    dev->dpath           = dpath;
    dev->dpathLeng       = dpathLeng;
    dev->busOps          = busOps;
    dev->devEvent        = DEV_EVENT_NULL;
    dev->entry.dev_class = UART_CLASS;
    dev->entry.dev_id    = dev;
    dev->entry.dev_node  = node;

        /*
         * If the hot-plug removal is supported, the device driver ops
         * are located within the device descriptor. It makes possible
         * to substitute the service routines (to an empty implementation) 
         * when a hot-plug removal occurs.
         */
#if defined(NS16_DEV_REMOVAL)
    bcopy(&ns16_ops, &(dev->devOps), sizeof(ns16_ops));
    dev->entry.dev_ops = &(dev->devOps);
#else
    dev->entry.dev_ops = &ns16_ops
#endif

        /*
         * Allocate the device driver instance descriptor in the
         * device registry.
         * Note that the descriptor is allocated in an invalid state
         * and it is not visible for clients until svDeviceRegister()
         * is invoked.
         * On the other hand, the allocated device entry allows the
         * event handler (ns16_event) to invoke svDeviceEvent() on it.
         * If svDeviceEvent() is called on an invalid device entry,
         * the shutdown processing is deferred until svDeviceRegister().
         * In other words, if a shutdown event occurs during the
         * initialization phase, the event processing will be deferred
         * until the initialization is done.
         */
    dev->regId = svDeviceAlloc(&(dev->entry), 
                               UART_VERSION_INITIAL,
                               FALSE, 
                               ns16_release);
    if (!dev->regId) {
        DKI_ERR(("%s: error -- no enough memory\n", dpath));
        svMemFree(dev, sizeof(Ns16_Device));
        svMemFree(dpath, dpathLeng);
        return;
    }

        /*
         * Retrieve the device I/O base addr from device tree.
         */
    prop = dtreePropFind(node, BUS_PROP_IO_REGS);
    if (prop == NULL) {
        DKI_ERR(("%s: error -- no '%s' property\n", dpath, BUS_PROP_IO_REGS));
        svDeviceFree(dev->regId);
        svMemFree(dev, sizeof(Ns16_Device));
        svMemFree(dpath, dpathLeng);
        return;
    }
    ioRegs = dtreePropValue(prop);

        /*
         * Retrieve the device interrupt source from device tree.
         */
    prop = dtreePropFind(node, BUS_PROP_INTR);
    if (prop == NULL) {
        DKI_ERR(("%s: error -- no '%s' property\n", dpath, BUS_PROP_INTR));
        svDeviceFree(dev->regId);
        svMemFree(dev, sizeof(Ns16_Device));
        svMemFree(dpath, dpathLeng);
        return;
    }
    intr = dtreePropValue(prop);

        /*
         * Retrieve the device clock frequency from device tree.
         * (if not specified, the default clock frequency is used)
         */
    prop = dtreePropFind(node, PROP_CLOCK_FREQ);
    if (prop == NULL) {
        dev->clock = NS16_CLOCK_FREQ;
    } else {
        dev->clock = *(PropClockFreq*)dtreePropValue(prop);
    }

        /*
         * Open a connection to the parent bus.
         */
    res = busOps->open(pId, node, ns16_event, NULL, dev, &dev->devId);
    if (res != K_OK) {
        DKI_ERR(("%s: error -- open() failed (%d)\n", dpath, res));
        svDeviceFree(dev->regId);
        svMemFree(dev, sizeof(Ns16_Device));
        svMemFree(dpath, dpathLeng);
        return;
    }

        /*
         * Map the device I/O registers.
         */
    res = busOps->io_map(dev->devId, ioRegs, ns16_bus_error, dev,
                         &dev->ioOps, &dev->ioId);
    if (res != K_OK) {
        DKI_ERR(("%s: error -- io_map() failed (%d)\n", dpath, res));
        busOps->close(dev->devId);
        svDeviceFree(dev->regId);
        svMemFree(dev, sizeof(Ns16_Device));
        svMemFree(dpath, dpathLeng);
        return;
    }

        /*
         * Connect interrupt handler to the bus interrupt source
         * (mask interrupts at chip level first).
         *
         * Note that the mask() routine is invoked indirecty because 
         * it may be substituted by the event handler (if a device
         * removal event has been already occured).
         */
    UART_OPS(dev->entry.dev_ops)->mask((UartId)dev);
    res = busOps->intr_attach(dev->devId, intr, ns16_intr, dev,
                              &dev->intrOps, &dev->intrId);
    if (res != K_OK) {
        DKI_ERR(("%s: error -- intr_attach() failed (%d)\n", dpath, res));
        busOps->io_unmap(dev->ioId);
        busOps->close(dev->devId);
        svDeviceFree(dev->regId);
        svMemFree(dev, sizeof(Ns16_Device));
        svMemFree(dpath, dpathLeng);
        return;
    }

        /*
         * If the driver unloading is supported, the list of active
         * device driver instances is handled.
         * Thus, we should add the new driver instance to the list.
         */
#if defined(NS16_DRV_UNLOAD)
    dev->next = ns16_devs;
    ns16_devs = dev;    
#endif

        /*
         * Finally, we register the new device driver instance
         * in the device registry. In case when a shutdown event
         * has been signaled during the initialization, the device entry
         * remains invalid and the ns16_release() handler is invoked
         * to shutdown the device driver instance. Otherwise, the device
         * entry becames valid and therefore visible for driver clients.
         */
    svDeviceRegister(dev->regId);

    DKI_MSG(("%s: %s driver started\n", dpath, NS16_DRV_NAME));
}