ChorusOS 4.0 Device Driver Framework Guide

Write the Init Function

The initialization routine of a bus driver component is optional. In case it is not provided (NULL entry point), the driver is typically either a probe only or bind only driver. That is, either a driver that probes a bus to discover devices and create associated device nodes, or a driver that examines device node properties to perform driver-to-device binding.

The initialization process (drv_init) of a bus/nexus driver mainly goes through the following steps:

  1. bus/nexus device initialization

  2. child device nodes creation (enumeration/probing)

  3. bus resource allocation across child device nodes

  4. driver-to-device binding

  5. driver instances creation for child device nodes

A bus/nexus driver, like any device driver, needs to access its hardware (internal bus bridge registers).. It therefore needs to establish connection to its parent bus/nexus driver and needs to use services implemented by its parent driver.


Note -

The bus/nexus driver does not need to register a new bus/nexus instance in the device registry because the standard child-to-parent driver binding mechanism does not use the device registry. The bus/nexus driver gives a pointer to its service routines vector and its identifier to the child driver, when the drv_probe or drv_init routine of the child driver is invoked.


Once the bus/nexus device is initialized, the bus/nexus driver searches drivers in the driver registry which match the given bus/nexus class and implement the drv_probe entry point.

The probe routine gives the child driver an opportunity to discover a device (serviceable by that driver) residing on the bus/nexus and to create the device node (associated to this device) in the device tree (see the section Write the Bind Function for an example).

A device node specifies bus resource properties required for the associated device. Note that some bus resources may be hardwired (such as fixed interrupt requests (IRQs)), while for other bus resources, some constraints may be specified (such as device address decoder constraints). When presented with configurable constraints, the bus/nexus driver iterates through existing child nodes to allocate configurable resources (with respect to constraints) and to check possible resource conflicts.


Note -

If a resource conflict is detected, the bus/nexus driver behavior is implementation specific. In any case, the bus/nexus driver must not activate any driver on a node for which bus resources are not allocated successfully.


Once the bus resources allocation is done, the bus/nexus driver searches the driver registry for drivers that implement the drv_bind entry point and that match the given bus/nexus class. Once a driver is found, its drv_probe routine is invoked (once for each existing child device node). The drv_bind routine gives the child driver an opportunity to bind itsekf to the device by attaching to its name.

When the driver-to-device binding is complete, the bus/nexus driver iterates through the child nodes and, for each device node, tries to determine a driver component to apply to the given device. Once a driver component is found, its drv_init routine is invoked by the bus/nexus driver.

If the child device is not a leaf one, the initalization process is recursively continued by the drv_init routine of the child driver.

The example below presents a drv_init routine for a PCI-to-ISA bus driver. The W83C553_DRV_UNLOAD compilation flag is used to allow downsizing of the driver component at compile time, in case the unload mechanism is not needed.

childrenPropbeInit()()

static void
childrenProbeInit (W83c553Data* bus)
{
        /*
         * Call probe methods for both supported bus classes: pci and bus.
         */
    genBusDrvProbe(ISA_CLASS, ISA_VERSION_INITIAL,
                   bus->node, bus->isaOps, bus);
    genBusDrvProbe(BUS_CLASS, BUS_VERSION_INITIAL,
                   bus->node, bus->busOps, bus);

        /*
         * Should check for resource overlapping between devices
         * and allocate each resources through resource_alloc()
         */

        /*
         * Call bind methods for both supported bus classes: pci and bus.
         */
    genBusDrvBind(ISA_CLASS, ISA_VERSION_INITIAL, bus->node);
    genBusDrvBind(BUS_CLASS, BUS_VERSION_INITIAL, bus->node);
        /*
         * Call init methods for both supported bus classes: pci and bus.
         */
    genBusDrvInit(ISA_CLASS, ISA_VERSION_INITIAL,
                  bus->node, bus->isaOps, bus);
    genBusDrvInit(BUS_CLASS, BUS_VERSION_INITIAL,
                  bus->node, bus->busOps, bus);
}

The genBusDrvProbe(), genBusDrvBind() and genBusDrvInit() functions, detailed below, are implemented in the libebd.s.a library, not in the driver code itself.

   /*
    * A generic (standard) probing loop performed by a bus driver.
    * Go through the driver registry. Check for each entry whether
    * it matches the bus class and provides a drv_probe() method.
    * If so, apply the drv_probe() method to the bus node.
    */
    void
genBusDrvProbe (char*   bus_class,
                int     bus_version,
                DevNode bus_node,
                void*   bus_ops,
                void*   bus_id)
{
    DrvRegId     drv_curr;
    DrvRegId     drv_prev;
    DrvRegEntry* entry;

    drv_curr = svDriverLookupFirst();
    while (drv_curr) {
        entry = svDriverEntry(drv_curr);
        if (entry->drv_probe && !strcmp(bus_class, entry->bus_class) &&
            (bus_version >= entry->bus_version)) {
            entry->drv_probe(bus_node, bus_ops, bus_id);
        }
        drv_prev = drv_curr;
        drv_curr = svDriverLookupNext(drv_curr);
        svDriverRelease(drv_prev);
    }
}

   /*
    * A generic (standard) binding loop performed by a bus driver.
    * Go through the driver registry. Check for each entry whether
    * it matches the bus class and provides a drv_bind() method.
    * If so, apply the drv_bind() method to each child node
    * attached to the bus node.
    */
    void
genBusDrvBind (char*   bus_class,
               int     bus_version,
               DevNode bus_node)
{
    DevNode      dev_node;
    DrvRegId     drv_curr;
    DrvRegId     drv_prev;
    DrvRegEntry* entry;

    drv_curr = svDriverLookupFirst();
    while (drv_curr) {
        entry = svDriverEntry(drv_curr);
        if (entry->drv_bind && !strcmp(bus_class, entry->bus_class) &&
            (bus_version >= entry->bus_version)) {
            dev_node = dtreeNodeChild(bus_node);
            while (dev_node) {
                entry->drv_bind(dev_node);
                dev_node = dtreeNodePeer(dev_node);
            }
        }
        drv_prev = drv_curr;
        drv_curr = svDriverLookupNext(drv_curr);
        svDriverRelease(drv_prev);
    }
}

   /*
    * A generic (standard) initialization loop performed by a bus driver.
    * Go through the child device nodes. Check for each node whether
    * it is inactive and has a driver bound to. If so, go through the
    * driver registry. Check for each entry whether it matches the bus
    * class, provides a drv_init() method and matches the driver name.
    * If so, apply the drv_init() method to the child node. The iteration
    * through the driver registry is aborted once the device node becomes
    * active.
    */
    void
genBusDrvInit (char*   bus_class,
               int     bus_version,
               DevNode bus_node,
               void*   bus_ops,
               void*   bus_id)
{
    DevNode      dev_node;
    DrvRegId     drv_curr;
    DrvRegId     drv_prev;
    char*        drv_name;
    DrvRegEntry* entry;
    DevProperty  prop;    

    dev_node = dtreeNodeChild(bus_node);
    while (dev_node) {
        if (!dtreePropFind(dev_node, PROP_ACTIVE)) {
            prop = dtreePropFind(dev_node, PROP_DRIVER);            
            if (prop) {
                drv_name = (char*)dtreePropValue(prop);
                drv_curr = svDriverLookupFirst();
                while (drv_curr) {
                    entry = svDriverEntry(drv_curr);
                    if (entry->drv_init &&
                        !strcmp(bus_class, entry->bus_class) &&
                        (bus_version >= entry->bus_version) &&
                        !strcmp(drv_name, entry->drv_name)) {
                        entry->drv_init(dev_node, bus_ops, bus_id);
                        if (dtreePropFind(dev_node, PROP_ACTIVE)) {
                            svDriverRelease(drv_curr);
                            break;
                        }
                    }
                    drv_prev = drv_curr;
                    drv_curr = svDriverLookupNext(drv_curr);
                    svDriverRelease(drv_prev);
                }
            }
        }
        dev_node = dtreeNodePeer(dev_node);
    }
}