NAME | SYNOPSIS | API RESTRICTIONS | FEATURES | DESCRIPTION | EXTENDED DESCRIPTION | Allowed Calling Contexts | ATTRIBUTES | SEE ALSO
#include <ddi/bus/bus.h>
The function or functions documented here may not be used safely in all application contexts with all APIs provided in the ChorusOS 5.0 product.
See API(5FEA) for details.
DDI
Provides common bus driver interface services.
The bus (that is, the bus bridge) driver is a keystone in the driver framework. The bus driver API enables development of platform independent device drivers. Note that the driver framework does not provide a fully generic bus driver API. Bus driver APIs are specific for each bus class (like ISA, PCI, USB, ...). However, in order to facilitate writing multi-bus device drivers, a part of a bus driver API is abstracted for some bus classes such as the Common Bus Driver Interface.
The common bus driver interface covers the following bus bridge services (provided to the device drivers):
Bus/device driver connection establishment
Interrupt management
Interrupt handler connection/disconnection
Interrupt source enabling/disabling
Interrupt handler invocation
I/O registers mapping/unmapping
I/O registers load/store
Memory-mapped regions
Usually, these services are provided by any bus bridge driver independent of its class. On the other hand, on some busses, these features may include some bus class specific features. Note that the driver framework does not guarantee that the Common Bus Driver Interface will be provided for all bus classes.
Instead, the driver framework explicitly specifies whether the Common Bus Driver Interface is provided for a particular bus class. Initially, the driver framework will provide the API specifications for the following bus classes:
PCI
ISA
VME
All bus classes mentioned above support the Common Bus Driver Interface. It allows you to develop a multi-bus device driver. Obviously, APIs used by this type of driver must be limited to the Common Bus Driver Interface described in this chapter. In other words, this type of driver must not use bus class specific routines. A multi-bus device driver specifies the "bus" parent class in the driver entry. In such a case, a bus driver supporting the Common Bus Driver Interface would invoke the driver initialization routine if a device serviced by the driver resides on the bus. The bus driver gives a BusOps / BusId pair to the device driver initialization routine. The BusOps / BusId pair specifies a bus driver instance which provides the Common Bus Driver Interface.
The BusOps structure contains pointers to the bus driver service routines.
The BusOps structure is the following:
typedef BusIntrStatus (*BusIntrHandler) (void*); typedef KnError (*BusEventHandler) (void*, BusEvent, void*); typedef void (*BusLoadHandler) (void*); typedef void (*BusErrHandler) (void*, BusError*); typedef struct { BusVersion version; KnError (*open) (BusId bus_id, DevNode dev_node, BusEventHandler dev_event_handler, BusLoadHandler dev_load_handler, void* dev_cookie, BusDevId* dev_id); void (*close) (BusDevId dev_id); KnError (*intr_attach) (BusDevId dev_id, void* dev_intr, BusIntrHandler dev_intr_handler, void* dev_intr_cookie, BusIntrOps** intr_ops, BusIntrId* intr_id)); void (*intr_detach) (BusIntrId intr_id); KnError (*io_map) (BusDevId dev_id, void* io_regs, BusErrHandler err_handler, void* err_cookie, BusIoOps** io_ops, BusIoId* io_id); void (*io_unmap) (BusIoId io_id); KnError (*mem_map) (BusDevId dev_id, void* mem_rgn, BusMemAttr mem_attr, BusErrHandler err_handler, void* err_cookie, void** mem_addr, BusMemId* mem_id); void (*mem_unmap) (BusMemId mem_id); void* (*intr_find) (void* propv, int index); void* (*io_regs_find) (void* propv, int index); void* (*mem_rgn_find) (void* propv, int index); } BusOps;
The BusId is an opaque parameter for the device driver. It must be passed back to the bus driver as an argument of the open() routine.
The version field specifies the bus driver API version number. The version number is incremented each time one of the bus driver structures is extended in order to include new service routines. In other words, a new symbol is added to the BusVersion (for example, BUS_VERSION_1) each time this type of an API extension is performed. Obviously, all extensions made in the bus driver API must be explicitly documented.
A device driver specifies a minimal API version number required by the driver within its registry entry. The device driver initialization routine is not invoked by the bus driver if the API version number provided by the bus driver is less than the API version number specified within the device driver registry entry.
open() must be the first call issued by the device driver to the bus driver. It establishes a connection between the device and bus driver.
The bus_id argument specifies the bus driver instance.
The dev_node argument specifies the device node (in the device tree) which is serviced by the device driver instance. In the case of initialization, the device node is given as an argument of dev_init() by the parent bus driver. In the case of probing, the device node is either found (among existing child nodes attached to the parent node) or created (and attached to the parent node) by the device driver.
The event_handler argument specifies the device driver handler which is invoked by the bus driver when a bus event occurs.
event_handler has three arguments. The first argument is event_cookie. The second one specifies the bus event type. The third argument points to a structure which is event type specific.
Among all bus events which are mostly bus class specific, there are three shut-down related events specified by the common bus API:
Notifies a device driver that the device should be shut down. The device driver should notify driver clients (via svDeviceShutDown() ) that the device is going to be shut down and then should return from the event handler. Once the device entry is released by the last driver client, the device registry module invokes a driver call-back handler. Within this handler, the device driver should reset the device hardware, release all allocated resources and close the bus connection invoking the close() routine. Note that the BUS_DEV_SHUTDOWN event may be used by a bus driver in order to confiscate (or to re-allocate) bus resources.
Notifies a device driver that the device has been removed from the bus and therefore the device driver instance has to be shut down. The actions taken by the driver are similar to the BUS_DEV_SHUTDOWN case except that the device hardware must not be accessed by the driver
Notifies a device driver that the system is going to be shut down. The device driver should reset the device hardware and return from the event handler. Note that the driver should neither notify clients nor free allocated resources.
Notifies a device driver that the interrupt line to which it is attached is defective. The device driver should detach from the defective interrupt line. The interrupt attachment identifier (of type <BusIntrId>) is passed as the third argument when calling the device driver event handler. This argument should be used directly as the parameter to call intr_detach().
The event_cookie argument specifies a device driver cookie. It is an opaque argument for the bus driver. event_cookie is passed back to the device driver when event_handler or load_handler is invoked.
Typically, event_handler is called as an interrupt handler and therefore the handler implementation must be restricted to the API allowed at interrupt level.
The load_handler argument specifies the driver handler which is invoked by the bus driver when a new driver has been dynamically loaded. It is invoked passing dev_cookie as the only argument. Note that this load_handler argument is optional. Typically, it should be used only by bus drivers supporting dynamically loadable device drivers, and should be set to NULL by all other drivers. This type of bus driver handler should manage the newly loaded driver in a similar way to the driver's initialization at boot time. This should lead to the driver being associated with a device node and being initialized, in order to create a running instance of the newly loaded driver.
Upon successful completion, the bus driver returns a BusDevId identifier designating the bus/device connection. BusDevId is opaque for the device driver. It must be passed back to the bus driver as an argument in subsequent invocations of the BusOps service routines.
In case of failure, an error code is returned as described below:
The dev_node argument given is not a valid device tree node.
The dev_node device tree node given is already in use (associated with another driver).
The system is out of memory.
The close() routine releases the bus/driver connection. It must be the last call issued to the bus driver.
Bus resources used by the device are specified as properties attached to the device node. There are three types of bus resource properties which have standard names on buses providing the common bus interface:
Device interrupts
Device I/O registers sets
Device memory regions
Note that the property value format (that is, the bus resource description) is bus class specific. However, the common bus driver API enables bus independent device drivers to use their resources without knowing what those resources are. In order to use a given resource type (for example, "intr"), a bus independent driver obtains a pointer to the corresponding property value using the device tree API.
It then invokes an appropriate bus service routine (for example, intr_attach()) passing as the bus resource descriptor a pointer to the property value. Note that the property value may be an array of bus resource descriptors. In other words, the property value may specify a number of bus resources of the same type (for example, a number of interrupt lines).
In order to enable bus independent device drivers to parse this type of property value without knowing the bus resource descriptor format, the common bus driver interface provides the following service routines:
intr_find()
io_regs_find()
mem_rgn_find()
These routines allow a driver to obtain a pointer to an element within the array of corresponding types, specified by a pointer to the property value.
The intr_attach() service routine connects the device specific handler to a given bus interrupt source. The dev_id argument specifies the given device on the given bus. It is an opaque returned by open(). The dev_intr argument specifies the bus interrupt source. It points to an interrupt resource property value which is bus class specific.
Typically, the bus class API defines a structure (or a basic type) which specifies the value format for an interrupt resource property attached to the device node. A device driver should find such a property (attached to its device node) using the "intr" name, obtain a pointer to the property value and specify it in intr_attach(). In a case where the property value specifies multiple interrupt sources (the property value is an array of the interrupt source descriptors), the driver should use the intr_find() routine in order to obtain a pointer to a given interrupt source descriptor within the array.
The intr_handler argument specifies the interrupt handler invoked by the bus driver when an interrupt occurs.
intr_cookie specifies an argument which is passed back to the interrupt handler. If successful, the bus driver returns an intr_ops / intr_id pair. intr_ops points to the BusIntrOps structure which provides the service routines specific to the interrupt source connected (see section BusIntrOps).
intr_id is opaque for the device driver. It is passed back to the bus driver as an argument in subsequent invocations of the BusIntrOps and intr_detach() service routines.
When the bus driver returns successfully from intr_attach(), the corresponding interrupt source is enabled at bus level. Thus it may be necessary for the device driver to disable device interrupts (at device level) prior to the intr_attach() invocation. Depending on the bus, an interrupt source can be shared between multiple devices (for example, on a PCI bus). In such a case, multiple intr_attach() requests can be issued on the same interrupt source.
The order in which these interrupt handlers are invoked is bus implementation specific. Usually, the handlers are invoked in the same order as they were attached (that is, first attached - first invoked). When the interrupt handler is invoked, the bus driver prevents re-entrance to the interrupt handler. Usually, interrupt acknowledgment at bus level is done by the bus driver once all interrupt handlers (attached to the serviced interrupt source) have been invoked. However, BusIntrOps provides the services allowing interrupts within the interrupt handler to be explicitly enabled and acknowledged (see section BusIntrOps). Note that the explicit interrupt acknowledgement enables re-entrance to the interrupt handler.
An interrupt handler must return a value specified by the BusIntrStatus type. An interrupt handler must return BUS_INTR_UNCLAIMED if the interrupt is unclaimed, indicating that there is no pending interrupt for the device. An interrupt handler must return BUS_INTR_CLAIMED if the interrupt has been claimed, meaning that there was a pending device interrupt which has been serviced by the handler.
In the case where an interrupt handler calls the enable() routine, it must return the value returned by enable(). Note that enable() returns either BUS_INTR_CLAIMED or BUS_INTR_ACKNOWLEDGED.
The interrupt handler return code may potentially be used by the bus driver to detect spurious interrupts which have occurred on the bus. In addition, the return code notifies the bus driver whether the currently serviced interrupt has been acknowledged or not. This allows the bus driver to simplify (or even avoid) the current interrupt state management. In fact, the current interrupt state is kept by the corresponding interrupt handler rather than by the bus driver itself and the bus driver is notified about the interrupt state through the interrupt handler return code.
The intr_detach() service routine disconnects the interrupt handler from the interrupt source. In other words, the corresponding interrupt routine is no longer invoked when interrupt occurs. intr_id specifies the bus driver handle returned by intr_attach().
typedef struct { void (*mask) (BusIntrId intr_id); void (*unmask) (BusIntrId intr_id); BusIntrStatus (*enable) (BusIntrId intr_id); void (*disable) (BusIntrId intr_id); } BusIntrOps;
The mask() service routine masks the interrupt source specified by intr_id. Note that mask() does not guarantee that all other interrupt sources are still unmasked.
The unmask() service routine unmasks the interrupt source previously masked by mask(). Note that unmask() does not guarantee that the interrupt source is unmasked immediately. The real interrupt source unmasking may be deferred.
The mask() / unmask() pair may be used at either base or interrupt level. Note also that the mask() / unmask() pairs must not be nested. The enable() and disable() service routines are dedicated to interrupt handler usage only. In other words, these routines may be called only by an interrupt handler. The enable() service routine enables (and acknowledges) the bus interrupt source specified by intr_id. enable() returns either BUS_INTR_ACKNOWLEDGED or BUS_INTR_CLAIMED. The BUS_INTR_ACKNOWLEDGED return value means that the bus driver has enabled (and acknowledged) interrupts at bus level. The BUS_INTR_CLAIMED return value means that the bus driver has ignored the enable request and therefore the interrupt source is still disabled (and not acknowledged) at bus level.
Note that the bus driver typically refuses an explicit interrupt acknowledgement (issued by an interrupt handler) for the shared interrupts. In the latter case, the bus driver will acknowledge interrupts only when all interrupt handlers have been called. Note that in cases where the enable() routine has been called by an interrupt handler, the handler must return the value which was returned by enable(). Note also that once enable() has been called, the driver should be able to handle an immediate re-entrance in the interrupt handler code.
The disable() service routine disables the interrupt source previously enabled by enable(). If enable() returns BUS_INTR_ACKNOWLEDGED, the driver must call disable() prior to returning from the interrupt handler. When an interrupt occurs, the attached BusIntrHandler is invoked with the interrupt source disabled at bus level. This has exactly the same effect as calling disable() just prior to handler invocation. Note that the interrupt handler must return to the bus driver in the same context as it was called, that is, with the interrupt source disabled at bus level. On the other hand, the interrupt handler called may use the enable()/disable() pair to allow the interrupt to be nested. This feature is typically used by a bus-to-bus bridge driver when the secondary bus interrupts are multiplexed, that is, multiple secondary bus interrupts are reported through the same primary bus interrupt. Typically, an interrupt handler of this type of bus-to-bus bridge driver would take the following actions:
Identify and disable the secondary bus interrupt source
Enable the primary bus interrupt source (enable())
Call handlers attached to the secondary bus interrupt source
Disable the primary bus interrupt source (disable())
Acknowledge (if needed) and enable the secondary bus interrupt source
Return from the interrupt handler
The io_map() service routine maps the contiguous I/O region to the supervisor address space. The dev_id argument specifies the given device on the given bus. It is an opaque returned by bus_open(). The io_regs argument specifies a contiguous set of I/O registers. It points to an I/O registers set resource property value which is bus class specific.
The bus class API defines a structure which specifies the value format of this type of property attached to the device node. A device driver should find this type of property (attached to its device node) using the "io-regs" name, obtain a pointer to the property value and pass it to io_map(). The property typically specifies the bus I/O space (for example, programmed or memory-mapped I/O on PCI bus), the start address and size of the I/O registers set.
When the property value specifies multiple I/O register sets, that is, the property value is an array of the I/O registers' set descriptors, the driver should use the io_regs_find() routine in order to obtain a pointer to a given I/O register set descriptor within the array.
The err_handler argument specifies the access error handler which should be called when a bus error occurs during an access to the I/O region. The err_cookie argument is an opaque for the bus driver and is passed back to the device driver when the bus error handler is invoked.
Typically, a bus error is reported as an interrupt. Thus, the error handler is, in fact, an interrupt handler associated with an I/O region rather than with an interrupt source. When the error handler is invoked, all bus interrupts are disabled.
Note that some hardware does not support the bus error exception. On this type of hardware, the error handler is not invoked, and the system hangs.
If successful, the bus driver returns an io_ops / io_id pair. io_ops points to the BusIoOps structure which provides the access methods to the mapped I/O region.
io_id is opaque for the device driver. It is passed back to the bus driver as an argument in subsequent invocations of the BusIoOps and io_unmap() service routines.
The io_unmap() service routine destroys the mapping previously created by io_map(). The io_id argument specifies the mapped I/O region.
typedef struct { uint8_f (*load_8) (BusIoId io_id, BusSize offset); uint16_f (*load_16) (BusIoId io_id, BusSize offset); uint32_f (*load_32) (BusIoId io_id, BusSize offset); uint64_f (*load_64) (BusIoId io_id, BusSize offset); void (*store_8) (BusIoId io_id, BusSize offset, uint8_f val); void (*store_16) (BusIoId io_id, BusSize offset, uint16_f val); void (*store_32) (BusIoId io_id, BusSize offset, uint32_f val); void (*store_64) (BusIoId io_id, BusSize offset, uint64_f val); void (*read_8) (BusIoId io_id, BusSize off, uint8_f* addr, BusSize cnt); void (*read_16) (BusIoId io_id, BusSize off, uint16_f* addr, BusSize cnt); void (*read_32) (BusIoId io_id, BusSize off, uint32_f* addr, BusSize cnt); void (*read_64) (BusIoId io_id, BusSize off, uint64_f* addr, BusSize cnt); void (*write_8) (BusIoId io_id, BusSize off, uint8_f* addr, BusSize cnt); void (*write_16) (BusIoId io_id, BusSize off, uint16_f* addr, BusSize cnt); void (*write_32) (BusIoId io_id, BusSize off, uint32_f* addr, BusSize cnt); void (*write_64) (BusIoId io_id, BusSize off, uint64_f* addr, BusSize cnt); /* Routines added to BUS_VERSION_1* */ void (*read_swap_16) (BusIoId io_id, BusSize off, uint16_f* addr, BusSize cnt); void (*read_swap_32) (BusIoId io_id, BusSize off, uint32_f* addr, BusSize cnt); void (*read_swap_64) (BusIoId io_id, BusSize off, uint64_f* addr, BusSize cnt); void (*write_swap_16) (BusIoId io_id, BusSize off, uint16_f* addr, BusSize cnt); void (*write_swap_32) (BusIoId io_id, BusSize off, uint32_f* addr, BusSize cnt); void (*write_swap_64) (BusIoId io_id, BusSize off, uint64_f* addr, BusSize cnt); } BusIoOps;
The bus driver provides four service routine sets to access a mapped I/O region:
load_xx()
store_xx()
read_xx()
write_xx()
There are four service routines in each set which deal with I/O registers of different width. xx specifies the I/O register size in bits: 8, 16, 32, 64. In all service routines provided by BusIoOps, the io_id argument specifies the mapped I/O region and the io_offset argument specifies the register offset within the region.
The load_xx() service routine loads a value from the I/O register.
The store_xx() service routine stores a value into the I/O register. The value argument specifies the value being stored.
The read_xx() service routine reads values sequentially from the I/O register and stores them into the memory buffer. The addr argument specifies the buffer start address. The count argument specifies the number of read-write transactions to perform.
The write_xx() service routine reads values sequentially from the memory buffer and writes them into the I/O register. The addr argument specifies the buffer start address. The count argument specifies the number of read-write transactions to perform.
In a case where the CPU endian format differs from the bus endian format, the bus driver performs the endian format conversion.
New service routines have been added to the BUS_VERSION_1. Those six routines systematically swap the bytes of the values passed to them to the inverse of the standard expected by the bus. Except for this aspect, they behave the same way as the non-swap routines do.
The mem_map() service routine is used to map a memory region from a device to the supervisor address space, enabling direct access (via a pointer) to this region.
The dev_id argument specifies the given device on the given bus. It is an opaque returned by open().
The mem_rgn argument specifies a memory region. It points to a memory region resource property value which is bus class specific. The bus class API defines a structure which specifies the value format of this type of property attached to the device node. The device driver identifies this type of property (attached to its device node) by using the "mem-rgn" name, obtains a pointer to the property value and passes it to mem_map().
The property typically specifies the start address and size of the memory region as well as its attributes. In a case where the property value specifies multiple memory regions, that is, the property value is an array of the memory region descriptors, the driver should use the mem_rgn_find() routine in order to obtain a pointer to a given memory region descriptor within the array.
mem_attr specifies MMU attributes used for memory region mapping. A combination of the following flags is allowed:
The memory region is mapped as readable.
The memory region is mapped as writable.
The memory region is mapped as executable.
The memory region is mapped as cacheable.
The memory region is mapped with an inverted endianness.
The BUS_MEM_INVERTED flag may be used by a device driver in order to avoid byte swapping within a memory mapped region. Note that mem_map() returns K_EINVAL if this type of feature is not supported by MMU.
The err_handler argument specifies the access error handler which should be called when a bus error occurs during access to the memory region.
The err_cookie argument is an opaque for the bus driver. It is passed back to the device driver when the bus error handler is invoked.
If successful, the bus driver returns a mem_addr pointer which points to the first byte of the memory region (in the supervisor address space), and mem_id which is an opaque for the device driver. mem_id is passed back to the bus driver as an argument in a subsequent invocation of the mem_unmap() service routine.
The device driver may now access the device memory by dereferencing the returned pointer. Note that in this mode, the driver must handle any endianning problems by itself. Note also that the bus endianness is available as the "byte-order" property attached to the bus node.
The "byte-order" property value is a 32-bit integer constant. Each byte within the constant contains its offset in the memory. In other words, the 0x00010203 and 0x03020100 hexadecimal constants define the big and little endian byte orders respectively.
The mem_unmap() service routine destroys the mapping previously created by mem_map(). The mem_id argument specifies the mapped memory region.
typedef struct { BusErrorCode code; BusSize offset; } BusError;
When either an I/O or memory access is aborted because of an error, the bus driver invokes the error handler which was specified by the device driver in io_map() or mem_map(). err_cookie is passed back to the error handler as the first argument.
The second argument points to the BusError structure which provides additional information about the fault. The code field specifies the fault type which is mainly bus class specific. The common bus driver API specifies only two error codes:
Unknown error type, in other words, the bus driver is unable to determine the reason for the exception.
Invalid (in other words, not supported) because access granularity has been used.
The offset field specifies the fault address offset within the I/O or memory region.
The BusError structure can be extended by a specific bus driver in order to include additional (bus dependent) fields. Typically, a bus error is reported as an interrupt. The error handler is thus an interrupt handler associated with an I/O or memory region rather than with an interrupt source. When the error handler is invoked, all bus interrupts are disabled.
The following table specifies the contexts in which a caller is allowed to invoke each service:
Services | Base level | DKI thread | Interrupt | Blocking |
---|---|---|---|---|
BusOps.open | - | + | - | + |
BusOps.close | - | + | + | + |
BusOps.intr_attach | - | + | - | + |
BusOps.intr_detach | - | + | - | + |
BusOps.io_map | - | + | - | + |
BusOps.io_unmap | - | + | - | + |
BusOps.mem_map | - | + | - | + |
BusOps.mem_unmap | - | + | - | + |
BusOps.intr_find | + | + | - | - |
BusOps.io_regs_find | + | + | - | - |
BusOps.mem_rgn_find | + | + | - | - |
BusIntrOps.mask | + | + | + | - |
BusIntrOps.unmask | + | + | + | - |
BusIntrOps.enable | - | - | + | - |
BusIntrOps.disable | - | - | + | - |
BusIoOps.load_xx | + | + | + | - |
BusIoOps.store_xx | + | + | + | - |
BusIoOps.read_xx | + | + | + | - |
BusIoOps.write_xx | + | + | + | - |
BusIoOps.read_swap_xx | + | + | + | - |
BusIoOps.write_swap_xx | + | + | + | - |
See attributes(5) for descriptions of the following attributes:
ATTRIBUTE TYPE | ATTRIBUTE VALUE |
---|---|
Interface Stability | Evolving |
dtreeNodeRoot(9DKI), svDriverRegister(9DKI), svMemAlloc(9DKI), svPhysAlloc(9DKI), svPhysMap(9DKI), svDkiThreadCall(9DKI), svTimeoutSet(9DKI), usecBusyWait(9DKI), DISABLE_PREEMPT(9DKI)
NAME | SYNOPSIS | API RESTRICTIONS | FEATURES | DESCRIPTION | EXTENDED DESCRIPTION | Allowed Calling Contexts | ATTRIBUTES | SEE ALSO