ChorusOS 5.0 Board Support Package Developer's Guide

Bus Event Handler Function

The event handler is invoked by the parent bus/nexus driver when a bus/nexus event occurs. The event handler address is given to the bus/nexus driver when a connection is established between the child driver and its parent bus/nexus driver. The event handler may be called as an interrupt handler and therefore the event handler implementation must be restricted to the API allowed at interrupt level.

Among all events which are mostly bus/nexus class specific, there are three shutdown-related events (specified by the common bus API) which are discussed in this section:

SYS_SHUTDOWN

system emergency shutdown

The SYS_SHUTDOWN event notifies the driver instance that the system is going to be shut down. The parent bus/nexus driver requires the child driver instance to perform an emergency shutdown of the device hardware.

DEV_SHUTDOWN

normal device shutdown

The DEV_SHUTDOWN event notifies the driver instance that a normal device shutdown is requested by the bus/nexus driver.

DEV_REMOVAL

surprise device removal

The DEV_REMOVAL event notifies the driver instance that the associated device has been removed from the bus/nexus and therefore the driver instance has to be shut down.


Note -

The omitted prefix ( DKI_, BUS_, ...) means that the event semantics are the same for all events.


In the standard case, the shutdown event processing goes through three phases:

  1. shutdown prolog

  2. shutdown mode

  3. shutdown epilog

The shutdown prolog phase is processed synchronously within the event handler. The main purpose of the shutdown prolog is to notify driver clients that the shutdown event has occurred, in other words, to propagate the shutdown prolog downstream (from driver to clients).

Once the shutdown event is processed by driver clients, the driver enters the shutdown epilog phase. Basically, the shutdown epilog is invoked when the last reference on the driver instance goes away. Between the shutdown prolog and epilog, the driver operates in a special mode (called shutdown mode). In this mode, the driver accepts only a subset of operations from clients allowing proper closure of connections to the driver.

The table below shows typical actions taken by the shutdown prolog depending on the event type:

 ActionSYS_SHUTDOWNDEV_SHUTDOWNDEV_REMOVAL
 notify driver clients (with svDeviceEvent) - + +
 abort operations in progress  - - +
 reset hardware  + - -

The SYS_SHUTDOWN prolog of a leaf (device) driver does not notify driver clients about the system shutdown event. The driver simply puts the hardware into a clean state.

Note that the SYS_SHUTDOWN event is processed synchronously, within the event handler. In other words, the system shutdown epilog is empty.

The only purpose of the SYS_SHUTDOWN event is to put the board hardware into a clean state to perform the system reboot (or restart) correctly.

The DEV_SHUTDOWN prolog simply notifies the driver clients that the DEV_SHUTDOWN event has occurred. Actual shutdown of the device is deferred until the DEV_SHUTDOWN epilog.

The DEV_REMOVAL prolog is similar to the DEV_SHUTDOWN one. In addition, the DEV_REMOVAL prolog aborts all I/O operations in progress (otherwise, these operations would never be completed).

Aborted operations return to callers with an error code.

As soon as the shutdown prolog is processed, the driver changes its internal state to enter into a shutdown mode. In this mode, the driver accepts only a subset of operations from client drivers:

All other operations (like opening a new connection, starting an I/O operation) are refused by the driver. In other words, in shutdown mode, the driver is waiting until a shutdown epilog condition is met. This enables clients to close existing connections to the driver correctly. The shutdown epilog condition is met within a leaf device driver when the device entry is released by the last driver client, and the callback release handler is invoked by the device registry.


Note -

The callback release handler is called in the DKI thread context. Therefore, the shutdown epilog is processed in the DKI thread context. This makes it possible to directly invoke the parent bus/nexus and DKI services allowed in the DKI thread context.


The table below shows typical actions taken by the shut-down epilog depending on the event type:

 ActionDEV_SHUTDOWNDEV_REMOVAL
 reset hardware + -
 release system resources + +
 close connection to the parent driver  + +

The DEV_SHUTDOWN epilog puts hardware into a clean state, releases system resources used by the driver instance ( io_unmap, mem_unmap, ...) and finally, closes connection to the parent driver (close).

The DEV_REMOVAL epilog is similar to the DEV_SHUTDOWN one, except the device hardware is not touched by the driver because the device hardware is no longer present on the parent bus.

When a shutdown epilog closes the last connection to the parent bus driver, the shutdown epilog condition may be met in the parent driver too. In such a way, the shutdown epilog is propagated upstream (from child to parent).


Note -

If one of the driver clients does not implement the shutdown procedure properly (for example, if it simply does not support the shutdown), the driver may be caught in shutdown mode forever. This type of driver would then never meet the shutdown epilog condition.


In the following example, the bus events management code from the NS16x50 compatible UART device driver is shown.

    /*
     * ns16_down_xxx stubs are used in the device removal mode in order
     * to avoid to access the device hardware.
     */

    static void
ns16_down (UartId id)
{
}

#define ns16_down_mask          ns16_down
#define ns16_down_unmask        ns16_down
#define ns16_down_txbreak       ns16_down

    /*
     * Open device.
     */
    static int
ns16_down_open (UartId        id,
                UartConfig*   cfg,
                void*         cookie,
                UartCallBack* client_ops,
                uint32_f*     signals)
{
    return K_EFAIL;
}

    /*
     * Send a buffer.
     */
    static void
ns16_down_transmit (UartId      id,
                    uint8_f*    buffer,
                    uint32_f    count)
{
}

    static void
ns16_down_control (UartId   id,
                   uint32_f signals)
{
}

static UartDevOps ns16_down_ops =
{
    UART_VERSION_INITIAL,
    ns16_down_open,
    ns16_down_mask,
    ns16_down_unmask,
    ns16_down_transmit,
    ns16_abort,
    ns16_down_txbreak,
    ns16_down_control,
    ns16_rxbuffer,
    ns16_close
};
    /*
     * This routine aborts a pending operation (if any) on the device.
     * The driver client is notified by a call-back handler about
     * the operation failing.
     *
     * This routine is only used by the hot-plug management code
     * in order to abort an operation in progress when a hot-plug removal 
     * occurs.
     */
    static void
ns16_removal (Ns16_Device* dev)
{
    if (dev->tx_csize || dev->tx_signals) {
        uint32_f count   = dev->tx_isize - dev->tx_csize;
        uint32_f signals = dev->tx_signals | UART_SIG_TX_ABORTED;
        dev->tx_cbuff    = NULL;
        dev->tx_csize    = 0;
        dev->tx_isize    = 0;
        dev->tx_signals  = 0;
        dev->clientOps->txdone(dev->cookie, count, signals);
    }
}


    /*
     * NS16550 event handler
     *
     * The event handler is invoked by the parent bus driver when a bus
     * event occurs in the bus.
     *
     * The NS16550 UART driver always supports the BUS_SYS_SHUTDOWN and
     * BUS_DEV_SHUTDOWN events. The BUS_DEV_REMOVAL support is optional and
     * is provided only when NS16_DEV_REMOVAL is defined.
     */
    static KnError
ns16_event (void*    id,
            BusEvent event,
            void*    arg)
{
    KnError      res  = K_OK;
    Ns16_Device* dev  = NS16_DEV(id);
    DevNode      node = dev->entry.dev_node;

    switch (event) {
            /*
             * In case of system (emergancy) shutdown,
             * we only disable the device interruts, in order to
             * properly perform the system reboot (or restart).
             *
             * Note that the mask() routine is invoked indirecty because 
             * it may be substituted by the event handler.
             */
        case BUS_SYS_SHUTDOWN: {
            UART_OPS(dev->entry.dev_ops)->mask(id);
            break;
        }

            /*
             * The normal device shutdown is processed only from the
             * normal mode. In other words, this event is
             * ignored if the driver already operates in the device
             * shut-down or removal mode.
             *
             * Here, we just flag that the device is entered into shutdown
             * mode (dev->devEvent) and ask the device registry to 
             * notify clients about it. The real shutdown procedure 
             * will be done by the ns16_release() handler. This handler
             * is called by device registry when the the reference to the
             * driver instance goes away (i.e. when svDeviceRelease() is
             * called by client).
             */
        case BUS_DEV_SHUTDOWN: {
            if (dev->devEvent == DEV_EVENT_NULL) {

                dev->devEvent = DEV_EVENT_SHUTDOWN;
                svDeviceEvent(dev->regId, DEV_EVENT_SHUTDOWN, NULL);

                DKI_MSG(("%s: entered into shut-down mode\n", dev->dpath));
            }
            break;
        }

            /*
             * The device removal is processed from either the
             * normal mode or shutdown mode. In other words,
             * this event is ignored if the driver already operates in the
             * device removal mode.
             *
             * Here, we flag that the device is entered into removal
             * mode (dev->devEvent). In addition, the device ops are
             * substituted to empty routines in order to avoid to access
             * the hardware which has been disappeared from the bus.
             * Once ops are substituted, we ask the device registry to 
             * notify clients about the device removal event.
             * The real shutdown procedure will be done by the 
             * ns16_release() handler. This handler is called by device 
             * registry when the reference to the driver instance goes 
             * away (i.e. when svDeviceRelease() is called by client).
             * ns16_removal() is called in order to abort a transmission
             * in progess (if any). 
             *
             * Note that, receiving DEV_EVENT_REMOVAL, the driver client 
             * must update pointers to the device service routines (ops) 
             * if they have been previously copied by the client.
             */
        case BUS_DEV_REMOVAL: {
            if (dev->devEvent != DEV_EVENT_REMOVAL) {

                dev->devEvent = DEV_EVENT_REMOVAL;
                bcopy(&ns16_down_ops, &(dev->devOps), 
                       sizeof(ns16_down_ops));
                svDeviceEvent(dev->regId, DEV_EVENT_REMOVAL, NULL);

                ns16_removal(dev);

                DKI_MSG(("%s: entered into removal mode\n", dev->dpath));
            }
            break;
        }

        default: {
            res = K_ENOTIMP;
            break;
        }
    }

    return res;
}

    /*
     * The error handler is called by the parent bus driver
     * if a bus error occurs when accessing the device registers.
     * In the current implementation, we consider that the device
     * is not present on the bus if such an error occurs.
     * Thus, an I/O error is equivalent to the device removal event.
     */
    static void
ns16_bus_error (void*     id,
                BusError* err)
{
    DKI_ERR(("%s: error -- bus error (%d, 0x%x)\n",
             NS16_DEV(id)->dpath, err->code, err->offset));

    (void) ns16_event(id, BUS_DEV_REMOVAL, NULL);
}

    /*
     * ns16_shutdown() implements the real shutdown of a given
     * device driver instance.
     * This routine is called either by ns16_release() or ns16_unload().
     * In both cases, this routine is invoked in the DKI thread context.
     * Note that the device driver instance has been unregistered by the
     * device registry, i.e. the corresponding device registry entry is
     * invalid.
     */
    static void
ns16_shutdown (Ns16_Device* dev)
{
        /*
         * When the driver unloading is supported, we must remove
         * the driver instance from the list.
         */
    Ns16_Device*  cdev;
    Ns16_Device** link = &ns16_devs

    while ((cdev = *link) != dev) {
        link = &(cdev->next);
    } 

    *link = dev->next;
        /*
         * Release bus resources and close connection to the bus.
         */
    dev->busOps->intr_detach(dev->intrId);
    dev->busOps->io_unmap(dev->ioId);
    dev->busOps->close(dev->devId);
        /*
         * Release the device registry entry.
         */
    svDeviceFree(dev->regId);
    DKI_MSG(("%s: %s driver stopped\n", dev->dpath, NS16_DRV_NAME));
        /*
         * Finally, free memory allocated for the device descriptor.
         */
    svMemFree(dev->dpath, dev->dpathLeng);
    svMemFree(dev, sizeof(Ns16_Device));
}

    /*
     * The release handler is called by the device registry when
     * a DEV_EVENT_SHUTDOWN or DEV_EVENT_REMOVAL event has been signaled
     * (via svDeviceEvent()) and the (last) reference to the device driver
     * instance goes away.
     */
    static void
ns16_release (DevRegEntry* entry)
{
    ns16_shutdown(NS16_DEV(entry->dev_id));
}