ChorusOS 5.0 Board Support Package Developer's Guide

Chapter 9 Driver Kernel Interface Overview

This chapter describes both the common and family-specific DKI services available within the Driver Framework. Understanding the functions in this overview will make the tasks shown in the next chapters (writing device drivers and writing bus drivers) much clearer.

Refer to section 9DKI of the man pages for a complete description of each of the calls included in this chapter.

Common Driver Services

This section describes the services that are common to all processor families.

Synchronization

Synchronization services (handling calls for the same services from different threads) are performed through the DKI thread. This thread is typically used for the shutdown and initialization of drivers, so it makes sense that synchronization services be handled within the DKI thread as well. The DKI thread is launched by the ChorusOS operating system microkernel at initialization time.

By ensuring this type of synchronization the DKI thread avoids using any other synchronization mechanism (locks) in the driver implementations.

The DKI thread acts as a synchronization mechanism in the following two cases:

Normal Case

In the normal case, all calls related to initialization/shutdown of the drivers are performed implicitly in the context of the DKI thread. This means that drivers need not be concerned with synchronization issues, because their routines are called directly from the DKI thread.

Special Cases

There are two special cases in which a driver must use DKI thread services to ensure synchronization:

  • Hot-pluggable device drivers

    With a hot-pluggable device driver, the initialization/shutdown process must be executed at runtime (not as part of the kernel/driver initialization process). In this case, drivers use DKI thread services (described below) to provide synchronization with any running drivers.

  • Deferred driver initialization

    In some cases, a device driver may defer its initialization until it is opened. In this scheme, initialization/shutdown processes are executed at runtime (at time of open/close) and not as part of the kernel/driver initialization process. Thus, this kind of driver uses thread services to synchronize with drivers that are already running.

    This is a way to resolve conflicts that arise when the same resources are used by multiple drivers. By using deferred driver initialization, drivers which share resources can be loaded at the same time (as long as they are not opened at the same time).

DKI thread related services are described below. See the man pages for complete descriptions of the calls listed:

svDkiThreadCall()

synchronously invokes a routine in the context of the DKI thread.

svDkiThreadTrigger()

asynchronously invokes a routine in the context of the DKI thread.

svDkiThreadCancel()

cancels the execution of a handler previously requested by svDkiThreadTrigger().

Device and Driver Registration

The driver and device registry mechanisms, described briefly in Chapter 8, Introduction to the ChorusOS Driver Framework, are explained in more detail below.

Device Tree

The device tree is a data structure providing a description of the hardware topology and device properties of a given device. The hardware topology is specified in terms of parent/child relationships. Device properties associated with each device node in the tree are device specific.

A device property is a name/value pair. The property name is a null terminated ASCII string. The property value is a stream of bytes specified by the length/address pair. Note that the property value format is property-specific and has to be standardized between the property producer and its consumers.

For instance, among all device node properties, there are some related to the bus resources allocated to the device (for example, interrupt lines, I/O registers, DMA channels). These properties must be standardized to be understood by the bus driver, as well as any device drivers connected to the given bus.

The device tree data structure may be built either statically or dynamically.

Note that it is possible to combine both methods. In other words, an initial (incomplete) device tree may be provided by the ChorusOS operating system boot, which will later be completed dynamically using an enumeration/probing mechanism. In any case, the device tree structure can be modified (extended/truncated) dynamically at run time using hot-plug insertion/removal service (for example, when using PCMCIA cards).

Device tree related services are described below. See the man pages for complete descriptions of the calls listed:

Device Tree Browsing

dtreeNodeRoot()

returns the root device node

dtreeNodeChild()

returns the first child node

dtreeNodePeer()

returns the next "sibling" device node

dtreeNodeParent()

returns the parent device node

dtreePathLeng()

returns the pathname length of the given device node

dtreePathGet()

returns, in buf, the absolute pathname of the given device node. The trailing part of the pathname is the name of the node and is read in a node property. If this property does not exist, the trailing part of the returned pathname is set to '???'.

Device Tree Modification

dtreeNodeAlloc()

allocates a new device node object

dtreeNodeFree()

releases all memory and properties attached to the node

dtreeNodeAttach()

adds a child node to the specified parent

dtreeNodeDetach()

detaches a node from its parent

Device Node Properties

dtreePropFind()

returns the first property of a node

dtreePropFindNext()

returns the next property of a node

dtreePropLength()

returns the property value length (in bytes)

dtreePropValue()

returns a pointer to the first byte of the property value

dtreePropName()

returns a pointer to the property name

dtreePropAlloc()

allocates a new device property object

dtreePropFree()

releases the memory allocated by the property object

dtreePropAttach()

attaches a property object to a device node

dtreePropDetach()

detaches a property object from a device node

Device tree high-level services

dtreeNodeAdd()

adds a named device node to the tree

dtreeNodeFind()

looks for a named node in the list of children of a given device node

dtreePropAdd()

allocates a new property, sets its value and attaches it to a given device node

Driver Registry

The driver registry module implements a database of drivers registered in the ChorusOS operating system. The driver registry database is populated by drivers that perform self-registration (using svDriverRegister()) at driver initialization time.

The bus/nexus drivers perform a search in the driver registry database to find a driver they are interested in. Typically, there are two kinds of searches used by the bus/nexus drivers. The first one is done at device enumeration/probing time when the bus/nexus driver is interested in all drivers matching the bus/nexus class (specified as the parent device class). The second is at device instance creation time, when the bus/nexus driver looks for a driver which must be started for a particular device node.

Driver Registry related services are described below. See the man pages for complete descriptions of the calls listed:

svDriverRegister()

adds a driver entry to the driver registry

svDriverLookupFirst()

returns the id of the first driver entity

svDriverLookupNext()

returns the id of the next driver entity

svDriverRelease()

releases the lock of a driver

svDriverEntry()

returns a pointer to the driver entry structure (using an id)

svDriverCap()

returns a pointer to the driver actor capability (using an id)

svDriverUnregister()

removes a driver entry from the registry

Device Registry

The device registry microkernel module implements a database of driver instances servicing devices currently supported by the system. The device registry database is populated by drivers that perform self-registration (using svDeviceRegister()) at device initialization time.

The device registry database is accessed by driver clients in order to obtain a pointer to the driver instance servicing a given (logical) device.

The device registry API is described in detail in the man pages. Note that only the svDeviceLookup(), svDeviceRelease() and svDeviceEntry() microkernel calls should be used by driver clients. The rest of API is dedicated to device drivers.

Device Registry related services are described below. See the man pages for complete descriptions of listed calls:

svDeviceAlloc()

allocates a device registry entry for a given device driver instance

svDeviceRegister()

adds a given entry to the device registry

svDeviceUnregister()

removes an entry from the device registry

svDeviceEvent()

notifies the device registry module that a given event has occurred

svDeviceFree()

releases a previously allocated device registry entry

svDeviceLookup()

searches a device entry in the registry, matching given device class and logical unit.

svDeviceEntry()

returns the device entry associated to a client identifier returned by svDeviceLookup

svDeviceRelease()

releases the lock on a looked-up device entry

General Purpose Memory Allocation

The microkernel provides general purpose memory management services for device drivers that need to dynamically allocate and free parts of memory in supervisor memory space. As initialization schemes are normally dynamic, device drivers need to dynamically allocate and free small pieces of supervisor data.

Typically, a device driver needs to dynamically allocate data associated to each instance that it will register in the Device Registry at initialization time. Moreover, most of the DDI services called from base level by the driver clients lead to the dynamic allocation and freeing of certain linked list elements for internal management purposes.


Note -

The memory allocated using these services is anonymous. That means it is not associated to any actor context. For this reason, all the allocated memory must be freed by drivers before they terminate, as the kernel won't be able to do it at actor deletion time.


General purpose memory allocation related services are described below. See the man pages for complete descriptions of listed calls:

svMemAlloc()

allocates a specified amount of memory from the supervisor address space

svMemFree()

frees memory previously allocated with svMemAlloc()

Special Purpose Physical Memory Allocation

Typically, different I/O buses impose different constraints on the memory used by their devices for Direct Memory Access (DMA), such as alignment, specific boundary crossing, maximum size, or specific location within the physical memory space.

To satisfy all constraints on physical memory imposed by the different I/O buses, (mainly for DMA purposes), the DKI provides an interface to allocate and free special purpose physical memory that satisfies the given constraints.

Special purpose memory allocation related services are described below. See the man pages for complete descriptions of listed calls:

svPhysAlloc()

allocates contiguous physical memory

svPhysFree()

frees memory allocated with svPhysAlloc

Timeouts

Device drivers may need timeout services to check whether there is activity on a device, or to verify that a started action will terminate before a given time limit is reached.


Note -

As these services should be implemented using drivers, they are not available and must not be used by drivers at initialization time.


Timeout-related services are described below. See the man pages for complete descriptions of listed calls:

svTimeoutSet()

sets a timeout request

svTimeoutCancel()

cancels a timeout request

svTimeoutGetRes()

returns the smallest possible difference between two distinct "time" values

Precise Busy Wait

Device drivers may use precise busy wait services to wait for a very short time. Note that busy wait means that the caller waits without releasing the CPU, as if executing a busy loop.

Note that these services may be used during the driver initialization process.

Precise busy wait related services are described below. See the man pages for complete descriptions of listed calls:

usecBusyWait()

waits for (at least) the given number of microseconds

System Event Management

System event management services are provided by the microkernel to the lowest-layer drivers. They are intended to register event handlers for all the running drivers, and to start propagating events from the microkernel.

Typically, a system reboot starts propagating a specific event from the microkernel to the lowest-layer drivers. Those drivers then recursively propagate the event to the upper layer drivers by calling their event handler (BusEventHandler) registered at open time.

System event management related services are described below. See the man pages for complete descriptions of listed calls:

svDkiOpen()

establishes connection between a child device driver and the DKI

svDkiClose()

releases the DKI/driver connection

svDkiEvent()

starts the propagation of an event to the device driver hierarchy

Global Interrupts Masking

Some of the Interrupt Management Service (IMS) routines are included as part of the DKI to provide drivers with global interrupt masking services.

These services may be used by a driver to protect a critical section from interrupts, if needed.

Global interrupt masking related services are described below. See the man pages for complete descriptions of listed calls:

imsIntrMask_f()

masks all maskable interrupts at processor level, and increments imsIntrMaskCount_f kernel variable

imsIntrUnmask_f()

unmasks interrupts at processor level (if calls are not nested)

Thread Preemption Disabling

The DKI API provides a means for a driver to disable/enable the preemption of the current thread. These services may be useful for a driver to prevent the current thread being preempted while interrupts are masked at bus/device level. Note that these services are implemented as macros.

DISABLE_PREEMPT()

disables preemption of the currently executed thread. Basically, this macro increments a per-processor preemption mask count. When the preemption mask count is not zero, the ChorusOS scheduler is locked, such as when there is a preemption request, the scheduler just raises a pending preemption flag deferring the real thread preemption until the preemption mask count drops to zero

ENABLE_PREEMPT()

enables preemption of the currently executed thread which has been previously disabled by DISABLE_PREEMPT(). Basically, this macro decrements the preemption mask count and, if it drops to zero, checks whether the current thread should be preempted because the pending preemption flag is raised.

Note that, as DISABLE_PREEMPT()/ENABLE_PREEMPT() rely on the preemption mask count, a driver may issue nested calls to these services.

Specific Input/Output Services

The DKI provides specific I/O routines optimized to handle byte swapping. Typically, these services are intended to be used by a host bus driver to handle different byte ordering between the processor bus and the host bus.

Specific I/O services are defined below as sets of routines where the _xx suffix indicates the bit length of the data on which the services apply. This suffix may take one of the following values:

Specific input/output related services are described below. See the man pages for complete descriptions of listed calls:

loadSwap_xx()

loads data from a given address and returns the corresponding byte swapped value. The addr argument specifies the address to read from

storeSwap_xx()

stores the value byte swapped at a given address

swap_xx()

swap in place the bytes of the data stored at a given address

Processor Family Specific DKI Services

Processor family specific DKI services are available only on a given processor family, and should be used only by the drivers servicing devices directly connected to the local CPU bus. Drivers using these DKI services become "processor specific" and therefore can not be considered common.

Note that the availability of services is different between processor families, and not all services are listed here. An overview of commonly available services is provided below. For an accurate indication of what services are provided for each processor family see the "Processor-Specific DKI Services" section in Appendix A (or the appropriate 9DKI man pages).

Depending on the processor family architecture, the family-specific DKIs may offer the following services.

Interrupt Management

All processor families offer DKI services to manage interrupts. These services allow the driver to perform the following tasks:

  • attach a handler to a given interrupt

  • mask an interrupt attached to a handler

  • unmask an interrupt attached to a handler

  • detach an interrupt handler

Cache management

Allows the host bus to manage memory coherence for DMA purposes by flushing and/or invalidating caches

Specific I/O services

Provides interface to processor specific I/O instructions, managing:

  • I/O ports

  • Synchronization of memory mapped I/O operations

Physical to virtual memory mapping

allows device drivers to map physical space to virtual memory space. These services are used mainly by the host bus driver to map bus I/O space or DMA memory.