NAME | SYNOPSIS | FEATURES | DESCRIPTION | EXTENDED DESCRIPTION | PROPERTIES | ALLOWED CALLING CONTEXTS | ATTRIBUTES | SEE ALSO
#include <ddi/isa/isa.h>
DDI
Provides an API for development of ISA device drivers.
The ISA bus driver offers an API for the ISA device drivers' development. This ISA bus API is an abstraction of the low-level ISA bus services and covers the following ISA functional modules:
ISA interrupts management
PIO and memory-mapped device registers access
Device memory access
DMA management
First of all, an ISA device driver must register itself in the kernel driver registry. This should be done from its main routine using svDriverRegister The driver must set the bus class to "isa" in its registry entry, and specify the lowest version number of ISA bus interface required to run correctly. This registration allows the ISA bus driver to call back the ISA device driver's drv_probe and drv_init routines at bus probing and bus initialization time, respectively.
Within the drv_probe or drv_init routine, the ISA device driver may establish a connection with the parent ISA bus driver as described below. Once such a connection is established, the ISA device driver may use services provided by the ISA bus driver. Note that a connection to the bus driver can be established only within the driver's drv_probe or drv_init routine.
Initialization Connection
If the drv_init routine is defined the ISA bus driver invokes it at bus initialization time.
Note that the drv_init routine is called in the context of a DKI thread which makes it possible to invoke the ISA bus services allowed in the DKI thread context only. The drv_init routine is defined if the drv_init field of the driver registry entry is set to a non NULL value.
The drv_init routine is called with three arguments:
The ISA device's own node, which identifies the node associated with the device in the device tree.
The ISA bus operations structure, which defines the ISA bus API and its version.
The ISA bus identifier, which is an opaque to pass back when calling the operation IsaBusOps.open to connect the device driver to the ISA bus.
From this point, IsaBusOps.open must be the first call issued by the ISA device driver to the ISA bus driver:
KnError (*open) (IsaId busId, DevNode devNode, IsaEventHandler eventHandler, IsaLoadHandler loadHandler, void* cookie, IsaDevId* devId);
This establishes a connection to the ISA bus identified by the busId. The returned parameter devId identifies the new connection.
This devId identifier is an argument for many other services defined in the IsaBusOps structure.
busId and devNode are given by the ISA bus driver as parameters to the driver's drv_init routine.
eventHandler is a handler in the device driver which is invoked by the ISA bus driver, when an ISA bus event occurs. Note that eventHandler is optional and must be set to NULL when it is not implemented by the device driver. The cookie argument is passed back to this handler as first argument.
This ISA bus event handler takes two additionnal parameters.
The first additional parameter is the bus event type, which is one of the following:
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 must neither notify clients nor free allocated resources.
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 should then 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 used resources and close the bus connection invoking IsaBusOps.close. Note that the ISA_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 ISA_DEV_SHUTDOWN case except that the device hardware must not be accessed by the driver.
The second additional parameter is an event type specific structure pointer: this argument is always NULL for ISA bus events.
As the event handler may be executed in the context of an interrupt, its implementation must be restricted to the API allowed at interrupt level.
loadHandler is a handler in the device driver which is invoked by the ISA bus driver, when a new driver appears in the system (for example, a new driver is downloaded at run time). Note that loadHandler is optional and must be set to NULL when it is not implemented by the device driver. The cookie argument is also passed back to this handler as a unique argument. Typically, a leaf ISA device driver is not affected by this type of event. loadHandler is usually used by an ISA nexus driver in order to apply a newly downloaded driver to its child devices which are not yet serviced. Note that loadHandler is called in the DKI thread context.
On success, IsaBusOps.open returns K_OK and a valid identifier is returned in the devId argument. This identifier must be used to call other services defined in the IsaBusOps structure. Some of these services also return an "Ops" structure defining new services on a new object instance, like an I/O or a memory region. Others just perform a simple service for the device driver, without giving access to a subpart of the API.
On failure, IsaBusOps.open returns an error code as follows:
The device node is invalid, that is, the device node is not a child of the bus node.
A connection is already open for the given device node.
The system is out of memory.
To release the connection with the ISA bus, the driver must call the IsaBusOps.close service:
void (*close) (IsaDevId devId);
After being disconnected from the ISA bus, the device driver can no longer use any of the ISA bus API services.
Probing Connection
If the drv_probe routine is defined, the ISA bus driver invokes it at bus probing time. Note that the drv_probe routine is called in the context of the DKI thread which makes it possible to invoke the ISA bus services allowed in the DKI thread context.
The probe routine is not defined if the drv_probe field of DrvRegEntry is set to NULL.
The drv_probe routine is called with three arguments:
The ISA bus node, which identifies the node associated to the parent ISA bus in the device tree.
The ISA bus operations structure, which defines the ISA bus API and its version.
The ISA bus identifier, which is an opaque to pass back when calling the IsaBusOps.open routine to connect the device driver to the ISA bus.
The drv_probe invocation precedes the drv_init invocation. That gives an opportunity to the ISA device driver to discover a device (which can be serviced by the device driver) on the bus and create a device node for this device in the device tree. If drv_probe creates a node corresponding to a device residing on the bus, it should put properties specifying needed bus resources to the device node (for example, "intr, "io-regs"). Looking at the device node properties, the ISA bus driver will allocate bus resources for the device (if needed) and/or will check resource conflicts with other devices residing on the bus.
Note that once such a device is discovered and the device node is created, it is reasonable to bind the driver component to the device. This type of driver initiated binding is done by putting the "driver" property to the device node. The "driver" property value is a NULL terminated ASCII string specifying the driver name. Note that the driver name specified in the property must match the driver name specified in the driver registry entry. Under such conditions, the ISA bus driver will invoke the driver's drv_init routine for this device node as described above. Note that the drv_init routine is invoked by the bus driver if, and only if, the bus resources required for the device are successfully allocated/checked by the bus driver.
Despite the presence of other busses, the ISA bus does not provide a mechanism to identify devices connected on the bus. Only heuristic methods can be used, reading I/O locations, but one cannot be sure that the device identified is the one expected. Therefore it is not recommended to use the drv_probe routine to discover and identify ISA devices. ISA devices are, in general, built statically by the ChorusOS loader.
To connect a handler to an ISA interrupt, an ISA driver should:
Disable the interrupt at the device level, typically by accessing the device registers
Attach a handler to the interrupt
Enable the interrupt at the device level
Use services defined by the attached interrupt object
Disable the interrupt at the device level
Detach the interrupt handler when no longer needed
Attaching a Handler to an ISA Interrupt
An ISA device driver can connect a handler to an ISA interrupt by calling the IsaBusOps.intr_attach service:
KnError (*intr_attach) (IsaDevId devId, IsaPropIntr* intr, IsaIntrHandler intrHandler, void* intrCookie, IsaIntrOps** intrOps, IsaIntrId* intrId);
The devId argument identifies the connection with the ISA bus driver.
The intr argument indicates the ISA interrupt to which the handler will be connected. This property should be retrieved by the driver using the device tree API, prior to calling IsaBusOps.intr_attach. The name of the property used to describe an ISA interrupt is "intr", its value contains an array of IsaPropIntr structures shown below:
typedef struct IsaPropIntr { IsaIntr irq; /* intr number */ IsaIntrType type; /* interrupt type (edge, level) */ uint32_f mask; /* bit mask of unwanted intrs */ } IsaPropIntr;
The irq field specifies the ISA interrupt to attach.
The type field specifies the ISA interrupt type and can take one of the following values:
The interrupt is triggered by driving the line low. The pull-up resistors will then generate the rising edge. This low to high transition registers an interrupt request managed by the interrupt controller.
The interrupt is triggered by a high signal.
ISA devices generally use edge interrupt types, which normally prevents interrupt sharing.
The mask field is used for dynamic interrupt acquisition in the following way: a bit set means that the device is capable of triggering (working with) the corresponding interrupt. For example, in an ISA PnP subsystem, an ISA device driver can acquire an ISA interrupt dynamically; the mask field specifies unwanted interrupts, if any.
The intrHandler argument is a handler in the device driver which is invoked by the ISA bus driver when the corresponding interrupt occurs on the bus:
typedef IsaIntrStatus (*IsaIntrHandler) (void* intrCookie);
The intrCookie is passed back to this interrupt handler as an argument. This type of ISA interrupt handler must return an IsaIntrStatus value which indicates to the bus driver whether the interrupt was claimed by the handler, and how it was handled:
Must be returned by the interrupt handler if there is no pending interrupt for the device.
Must be returned by the interrupt handler if a pending device interrupt has been serviced by the interrupt handler and the interrupt has not been enabled (acknowledged) at ISA bus level (see section Enabling/Disabling a Serviced Interrupt).
Must be returned by the interrupt handler if a pending device interrupt has been serviced by the interrupt handler and the interrupt has been enabled (acknowledged) at ISA bus level (see section Enabling/Disabling an Attached Interrupt).
On success, K_OK is returned and services defined on an attached interrupt object are returned in the intrOps parameter. An identifier for the attached interrupt is also returned in intrId. This identifier must be used as first parameter to further calls to the IsaIntrOps services.
As explained above, the ISA bus architecture does not allow an interrupt to be shared among multiple devices residing on the bus.
Masking/Unmasking an Attached interrupt
The IsaIntrOps.mask service routine masks the interrupt source specified by intrId:
void (*mask) (IsaIntrId intrId);
Note that IsaIntrOps.mask does not guarantee that all other interrupt sources are still unmasked.
The IsaIntrOps.unmask service routine unmasks the interrupt source previously masked by IsaIntrOps.mask:
void (*unmask) (IsaIntrId intrId);
Note that IsaIntrOps.unmask does not guarantee that the interrupt source is unmasked immediately. The real interrupt source unmasking may be deferred.
The IsaIntrOps.mask/IsaIntrOps.unmask pair may be used at either base or interrupt level. Note that the IsaIntrOps.mask/IsaIntrOps.unmask pairs must not be nested.
Enabling/Disabling a Serviced Interrupt
The IsaIntrOps.enable and IsaIntrOps.disable service routines are dedicated to interrupt handler usage only. In other words, these routines may be called only by an interrupt handler.
The IsaIntrOps.enable service routine enables (and acknowledges) the bus interrupt source specified by intrId:
IsaIntrStatus (*enable) (IsaIntrId intrId);
IsaIntrOps.enable returns either ISA_INTR_ACKNOWLEDGED or ISA_INTR_CLAIMED. The ISA_INTR_ACKNOWLEDGED return value means that the ISA bus driver has enabled (and acknowledged) interrupt at bus level. The ISA_INTR_CLAIMED return value means that the ISA bus driver has ignored the enable request and therefore the interrupt source is still disabled (and not acknowledged) at bus level.
Note that in cases where the IsaIntrOps.enable routine has been called by an interrupt handler, the handler must return the value which was returned by IsaIntrOps.enable. Note also, that once IsaIntrOps.enable is called, the driver should be able to handle an immediate re-entrance in the interrupt handler code.
The IsaIntrOps.disable service routine disables the interrupt source previously enabled by IsaIntrOps.enable:
void (*disable) (IsaIntrId intrId);
If IsaIntrOps.enable returns ISA_INTR_ACKNOWLEDGED, the driver must call IsaIntrOps.disable prior to returning from the interrupt handler.
When an interrupt occurs, the attached IsaIntrHandler is invoked with the interrupt source disabled at bus level. This produces the same result as calling IsaIntrOps.disable just prior to the 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 called interrupt handler may use the IsaIntrOps.enable/disable pair to allow the interrupt to be nested.
Detaching an Attached Interrupt
When a driver no longer needs to handle an interrupt, or before closing the connection to the ISA bus, it should detach the handler attached to an interrupt line.
The IsaBusOps.intr_detach is used to detach a handler, previously attached with IsaBusOps.intr_attach, and to release any resources allocated for this attachement:
void (*intr_detach) (IsaIntrId intrId);
The intrId argument identifies the attachement to release.
When the IsaBusOps.intr_detach function is called, the driver can no longer use the identifier intrId.
To perform I/O access to registers of an ISA device, an ISA driver must:
Map the device's registers into an I/O region
Use services defined by the mapped I/O region to access registers
Unmap the region when access is no longer needed
Mapping Device I/O Registers
The IsaBusOps.io_map service is used to map an I/O region from an ISA device to enable access to this region:
KnError (*io_map) (IsaDevId devId, IsaPropIoRegs* ioRegs, IsaErrHandler errHandler, void* errCookie, IsaIoOps** ioOps, IsaIoId* ioId);
devId is returned by IsaBusOps.open.
The ioRegs structure defines the I/O region to map. This structure is an element of the array stored in a property of the device node. This property should be retrieved by the driver using the device tree API prior to calling IsaBusOps.io_map.
The name of the property used to describe device I/O regions is "io-regs", its value contains an array of IsaPropIoRegs structures shown below:
typedef struct IsaPropIoRegs { IsaAddr addr; /* requested/allocated start address */ IsaSize size; /* size */ } IsaPropIoRegs;
The addr field specifies the ISA start address of the register's range.
The size field specifies the register's range size in bytes.
The errHandler argument is a handler in the device driver, which is invoked by the ISA bus driver, when an ISA bus error occurs while accessing the mapped region. Please note that it is not always possible for an ISA bus driver to detect this type of error, and in this case, the errHandler will never be called. errCookie is passed back to the errHandler as first parameter (see section ISA Error Handling).
On success, K_OK is returned and appropriate I/O services are returned in the ioOps parameter. An identifier for the mapped I/O region is also returned in ioId. This identifier must be used as first parameter to further calls to the ISA I/O operations services, as defined in IsaIoOps.
On failure, IsaBusOps.io_map returns an error code as follows:
A size of zero was specified.
Not enough virtual address space to map I/O registers.
The system is out of memory.
Performing I/O Access to a Mapped I/O Region
Once the region is successfully mapped, the driver can use the services defined by the IsaIoOps structure.
ISA bus provides four service routine sets to access a mapped I/O region:
IsaIoOps.load_8/16/32/64
IsaIoOps.store_8/16/32/64
IsaIoOps.read_8/16/32/64
IsaIoOps.write_8/16/32/64
There are three service routines in each set which deal with I/O registers of different widths. The _8, _16, _32 or _64 suffix indicates the data size of the transfer on the ISA bus.
In all service routines provided by IsaIoOps, the ioId argument identifies the mapped I/O region as returned by IsaBusOps.io_map.
The offset argument specifies the register offset, in bytes, within the mapped region.
All these routines handle byte swapping in the case of the endian being different for the ISA bus and CPU.
Load from a Register
The IsaIoOps.load_xx routine set returns a value loaded from a device register:
uintxx_f (*load_xx) (IsaIoId ioId, IsaSize offset);
The size of the returned value and of the data transfer on the ISA bus is specified by the _xx suffix.
Store to a Register
The IsaIoOps.store_xx routine set stores a given value into a device register:
void (*store_xx) (IsaIoId ioId, IsaSize offset, uintxx_f value);
The size of the given value and of the data transfer on the ISA bus is specified by the _xx suffix.
Multiple Read from a Register
The IsaIoOps.read_xx routine set loads values from a specified device register and sequentially writes them into a memory buffer:
void (*read_xx) (IsaIoId ioId, IsaSize offset, uintxx_f* buf, IsaSize count);
The size of each value loaded and of each data transfer on the ISA bus is specified by the _xx suffix.
The count argument specifies the number of read transactions to perform.
The buf argument specifies the address of the memory buffer. The size of this buffer must be at least (count * size of each data transfer).
Multiple Write to a Register
The IsaIoOps.write_xx routine set sequentially reads values from a memory buffer and stores them into a device register:
void (*write_xx) (IsaIoId ioId, IsaSize offset, uintxx_f* buf, IsaSize count);
The size of each value stored and of each data transfer on the ISA bus is specified by the _xx suffix.
The count argument specifies the number of write transactions to perform.
The buf argument specifies the address of the memory buffer. The size of this buffer must be at least (count * size of each data transfer).
Unmapping Device I/O Registers
When a driver no longer needs access to the device I/O register, or before closing the connection to the ISA bus, it should unmap the I/O region used for these devices.
The IsaBusOps.io_unmap is used to unmap an I/O region previously mapped with IsaBusOps.io_map, and to release any resources allocated for this mapping:
void (*io_unmap) (IsaIoId ioId);
The ioId argument identifies the region to unmap.
When the IsaBusOps.io_unmap function is called, the driver is no longer allowed to use any services defined for the unmapped I/O region.
To perform access to the memory of an ISA device, an ISA driver must:
Map device memory to the virtual memory space
Access device memory through the mapped region
Unmap the region when access is no longer needed
The mapped memory region may be accessed directly using the virtual address of the mapped memory region.
Mapping Device Memory
The IsaBusOps.mem_map service is used to map a memory region from an ISA device, enabling access to this region:
KnError (*mem_map) (IsaDevId devId, IsaPropMemRgn* memRgn, IsaMemAttr memAttr, IsaErrHandler errHandler, void* errCookie, void** memAddr, IsaMemId* memId);
devId is the identifier returned by IsaBusOps.open.
The memRgn structure defines the memory region to map. This structure is an element of the array stored in a property of the device node. This property should be retrieved by the driver using the device tree API prior to calling IsaBusOps.mem_map.
The name of the property used to describe device memory regions is "mem-rgn", its value contains an array of IsaPropMemRgn structures shown below:
typedef struct IsaPropMemRgn { IsaAddr addr; /* requested/allocated start address */ IsaSize size; /* size */ } IsaPropMemRgn;
The addr field specifies the ISA start address of the memory region.
The size field specifies the memory region size in bytes.
The memAttr argument specifies the mapping attributes. A combination of the following flags is allowed:
The memory region is readable.
The memory region is writable.
The memory region is executable.
The memory region is cacheable.
errHandler is a handler in the device driver, which is invoked by the ISA bus driver, when an ISA bus error occurs while accessing the mapped region. errCookie is passed back to the errHandler as first parameter (see section ISA Error Handling).
On success, K_OK is returned and the starting virtual address of the mapped memory region is returned in the memAddr argument. An identifier for the mapped memory region is also returned in memId. This identifier must be used to unmap the memory region.
On failure, IsaBusOps.mem_map returns an error code as follows:
A size of zero was specified.
Invalid or unsupported memory mapping attributes.
Not enough virtual address space to map.
The system is out of memory.
Once the region is successfully mapped, the device driver may now access the device memory by dereferencing the returned pointer. Note that in this mode, the driver must handle endianning problems itself.
Unmapping a Memory Region
When a driver no longer needs access to the device memory, or before closing the connection to the ISA bus, it should unmap the memory region.
The IsaBusOps.mem_unmap is used to unmap a memory region, previously mapped with IsaBusOps.mem_map, and to release any resources allocated for this mapping:
void (*mem_unmap) (IsaMemId memId);
The memId argument identifies the memory region to unmap.
Once the memory region is unmapped, the driver can no longer access the memory region.
The ISA bus driver provides an API for a standard DMA chip controller (Intel8237 or compatible).
To perform Direct Memory Access to the system memory, an ISA device driver must:
Attach a DMA channel
Allocate a DMA region on the bus
Use services defined in IsaDmaOps to retrieve the region's virtual and ISA addresses
Program the DMA engine to perform DMA transfers
Release the DMA region when no longer needed
Release the DMA channel
Attaching a DMA Channel
The IsaBusOps.dma_attach service attaches an ISA DMA channel. The specified DMA channel must be in a free state, as DMA channels are not shared.
KnError (*dma_attach) (IsaDevId devId, IsaPropDma* propDma, IsaDmaOps** dmaOps, IsaDmaId* dmaId);
The devId argument is the identifier returned by IsaBusOps.open.
The propDma structure defines the channel to attach.
This structure is an element of the array stored in a property of the device node. This property should be retrieved by the driver using the device tree API prior to calling IsaBusOps.dma_attach.
The name of the property used to describe DMA channels is "dma", and its value contains an array of IsaPropDma structures shown below:
typedef struct IsaPropDma { IsaDmaChan chan; /* DMA channel requested/allocated */ IsaSize max_size; /* Max size of transfer requested/allowed */ } IsaPropDma;
The chan is the channel number.
The max_size contains the maximum size of the transfer in bytes, and may vary depending on the DMA channel.
On success, K_OK is returned and appropriate services are returned in the dmaOps parameter. An identifier for the DMA channel is also returned in dmaId. This identifier must be used as first parameter to further calls to the IsaDmaOps services.
On failure, IsaBusOps.dma_attach returns an error code as follows:
The requested channel is already in use.
The system is out of memory.
Allocating a DMA Region
The IsaDmaOps.mem_alloc service allocates a system memory region of the specified size and contiguously maps it to the supervisor address space:
KnError (*mem_alloc) (IsaDmaId dmaId, IsaSize size, IsaDmaAttr dmaAttr, IsaMemAttr memAttr, IsaErrHandler errHandler, void* errCookie, IsaDmaMemId* dmaMemId);
The dmaId argument is the identifier returned by IsaBusOps.dma_attach.
The size argument is the requested size in bytes to allocate for the DMA memory region.
The dmaAttr argument specifies the DMA transfer type. A combination of the following flags is allowed:
Region is used for DMA read transfer.
Region is used for DMA write transfer.
Allocate a synchronous DMA region for which no synchronization is needed between access from DMA engine and CPU. This attribute may not be supported on certain platforms. In this case, IsaDmaOps.mem_alloc returns K_EINVAL.
The memAttr argument specifies the mapping attributes (see section Mapping Device Memory).
errHandler is a handler in the device driver, which is invoked by the ISA bus driver, when an ISA bus error occurs while accessing the DMA region. errCookie is passed back to the errHandler as first parameter (see section ISA Error Handling).
On success, K_OK is returned and an identifier for the DMA region is returned in dmaMemId. This identifier must be used as first parameter to certain services in IsaDmaOps.
On failure, IsaDmaOps.mem_alloc returns an error code as follows:
A size of zero was specified.
Invalid or unsupported memory mapping attributes.
Not enough virtual address space to map.
The system is out of memory.
Getting the Virtual Address of a DMA Region
The IsaDmaOps.virt_addr routine returns the virtual starting address of a DMA region, given the identifier of the region (dmaMemId):
void* (*virt_addr) (IsaDmaMemId dmaMemId);
The driver uses this address to access the DMA region using CPU instructions. Note that the driver should synchronize the region as appropriate, depending on the attributes used when allocating the region (see section Synchronizing the DMA Region).
Getting the ISA Address of a DMA Region
The IsaDmaOps.phys_addr routine returns the ISA starting address of a DMA region, given the identifier of the region (dmaMemId):
IsaAddr (*phys_addr) (IsaDmaMemId dmaMemId);
The driver uses this address to program the DMA engine. Note that the driver should synchronize the region as appropriate, depending on the attributes used when allocating the region (see section Synchronizing the DMA Region).
Starting a DMA Transfer
Once a DMA channel has been attached, and a DMA region allocated, the device driver can start DMA transfer, using the IsaDmaOps.read or IsaDmaOps.write services.
The IsaDmaOps.read is used to transfer from an ISA memory region to the peripheral device, while IsaDmaOps.write transfers data from the device to the ISA memory region.
KnError (*read) (IsaDmaId dmaId, IsaDmaMemId dmaMemId, IsaSize offset, IsaSize size, IsaDmaMode mode); KnError (*write) (IsaDmaId dmaId, IsaDmaMemId dmaMemId, IsaSize offset, IsaSize size, IsaDmaMode mode);
For both routines:
The dmaId argument identifies the DMA channel, as returned by IsaBusOps.dma_attach.
The dmaMemId argument identifies the DMA region, as returned by IsaDmaOps.mem_alloc.
offset is the offset in bytes from the beginning of the DMA memory region.
size is the size in bytes of the transfer.
mode is a combination of DMA engine specific operation modes, as follows:
A single transfer is requested. The DMA engine relinquishes the bus between each byte or word transferred.
This mode causes the DMA engine to transfer a buffer of data, keeping bus control until the end of the transfer.
This mode is identical to ISA_DMAMODE_BLOCK, but the DMA engine is programmed to relinquish the bus between bursts of transfer.
This mode causes the DMA engine to initialize itself at the end of the current transfer, with the same parameter (offset and size) values. Afterwards, the channel concerned is ready for a new DMA transfer.
On success K_OK is returned. On failure, IsaDmaOps.read and IsaDmaOps.write return an error code as follows:
A size of zero was specified.
Invalid parameter detected. The alignment and size for parameters offset and size should be specified according to the hardware and DMA channel used.
Invalid mode (or not implemented) specified in mode parameter.
The system is out of memory.
Aborting a DMA Transfer
Once a DMA transfer has been started, it is possible to abort it, using the IsaDmaOps.abort service.
KnError (*abort) (IsaDmaId dmaId);
On success, K_OK is returned. Otherwise IsaDmaOps.abort returns the K_ENOTIMP error code.
Synchronizing the DMA Region
If the DMA region was not allocated using the ISA_DMA_SYNC attribute, the device driver should synchronize accesses to the same system memory correctly (the DMA region) from the CPU and the DMA engine. Depending on the platform, these routines handle possible problems of cache coherency, DMA engine buffers, and others.
IsaDmaOps.read_sync is a barrier between CPU writes and DMA reads from a given DMA sub-region:
void (*read_sync) (IsaDmaId dmaId, IsaDmaMemId dmaMemId, IsaSize offset, IsaSize size);
IsaDmaOps.write_sync is a barrier between DMA writes and CPU reads from a given DMA sub-region:
void (*write_sync) (IsaDmaId dmaId, IsaDmaMemId dmaMemId, IsaSize offset, IsaSize size);
For both routines:
The dmaId argument identifies the DMA id, as returned by IsaBusOps.dma_attach.
The dmaMemId argument identifies the DMA region, as returned by IsaDmaOps.mem_alloc.
offset is the offset in bytes from the beginning of the DMA memory region.
offset and size both define the sub-region to synchronize.
Releasing a DMA Region
When a driver no longer needs an allocated DMA region, or before closing the connection to the ISA bus, it should release the DMA region.
The IsaBusOps.dma_free is used to release a DMA region, previously allocated with IsaBusOps.dma_alloc:
void (*dma_free) (IsaDmaId dmaId);
The dmaId argument identifies the DMA region to free.
Once the DMA region is released, the driver can no longer use the DMA region.
Releasing a DMA Channel
The IsaBusOps.dma_detach service detaches a previously attached DMA channel.
void (*dma_detach) (IsaDmaId dmaId);
Once the DMA channel is released, the driver can no longer use any DMA services from IsaDmaOps, and the identifier dmaId.
When mapping ISA bus space, an ISA driver provides an error handler, which is invoked by the ISA bus driver when a bus error occurs.
Error handlers are used for the following services:
IsaBusOps.io_map
IsaBusOps.mem_map
IsaDmaOps.mem_map
When a programmed or direct memory access is aborted because of a bus error, the ISA bus driver invokes the error handler that is connected for the ISA address and space concerned.
Note that depending on hardware, it is not always possible for the ISA bus driver to detect these types of errors. In this case, the ISA bus driver will not invoke device driver error handlers.
An IsaErrHandler is defined as follow:
typedef void (*IsaErrHandler) (void* errCookie, IsaBusError* err);
The errCookie is an opaque given by the caller and passed back when the handler is called.
The err argument points to the IsaBusError structure describing the error as follows:
typedef struct IsaBusError { IsaErrorCode code; /* error type */ IsaSize offset; /* faulted address offset within region */ } IsaBusError;
The code field indicates the type of error that occurred.
The offset field indicates the offset within the associated region at which the error occurred.
The error code can be one of the following::
Unknown error, that is, the ISA bus driver is unable to determine the reason for the error.
Invalid (that is, unsupported) access granularity has been used.
A parity error has been detected on the ISA bus.
As a bus error may be reported in the context of an exception or an interrupt, the implementation of the error handler must be restricted to the interrupt level API.
The ISA Specific Properties table lists the ISA specific node properties. The alias column specifies the alias name which should be used by an ISA bus or device driver to reference the property name. The name column specifies the property name ASCII string. The value column specifies the type of property value. The bus column specifies properties specific to the ISA bus node. The dev column specifies properties specific to the ISA device node. The bus and dev fields can take the following values:
Flags' mandatory properties
Flags' optional properties
Flags' properties which are not applied to a given node type
Alias | Name | Value | Bus | Dev |
---|---|---|---|---|
ISA_PROP_INTR | "intr" | IsaPropIntr[] | - | o |
ISA_PROP_IO_REGS | "io-regs" | IsaPropIoRegs[] | - | o |
ISA_PROP_MEM_RGN | "mem-rgn" | IsaPropMemRgn[] | - | o |
ISA_PROP_DMA_BURST | "dma-burst | IsaPropDmaBurst | o | - |
ISA_PROP_DMA_MIN_SIZE | "dma-min-size" | IsaPropDmaMinSize | o | - |
ISA_PROP_BYTE_ORDER | "byte-order" | IsaPropByteOrder | o | - |
ISA_PROP_CLOCK_FREQ | "clock-freq" | IsaPropClockFreq | o | - |
When the value of the property is an array of (for example, IsaPropIoRegs), the size of this array divided by the size of its element type (for example, sizeof(IsaPropIoRegs)), defines the number of elements in the array. An ISA device driver may then iterate through the array in order to perform an action for each element (for example, to map several I/O register ranges). The size of the array is the size of the property value returned by dtreePropValue.
Dynamic Resource Allocation
The ISA bus driver may support dynamic allocation on the ISA bus resources. Typically, this feature must be supported by an ISA PnP bus driver. In cases where dynamic resource allocation is supported, an ISA device driver may use the IsaBusOps.resource_alloc service routine in order to allocate a bus resource at run time:
KnError (*resource_alloc) (IsaDevId devId, DevProperty prop);
devId is returned by IsaBusOps.open.
prop specifies the bus resource being allocated.
The IsaBusOps.resource_alloc service routine allocates a given bus resource and, if the allocation request is satisfied, updates the device node properties in order to add the newly allocated bus resource.
On success, IsaBusOps.resource_alloc returns K_OK.
On failure, one of the following error codes is returned:
Dynamic resource allocation is not supported by the bus driver.
The property name is unknown.
The property value is invalid.
The bus resource is not available.
The system is out of memory.
When a dynamically allocated bus resource is no longer used, an ISA device driver may release it by calling the IsaBusOps.resource_free service routine:
void (*resource_free) (IsaDevId devId, DevProperty prop);
devId is returned by IsaBusOps.open.
prop specifies the bus resource being released.
The IsaBusOps.resource_free service routine releases a given bus resource and updates the device node properties in order to remove the bus resource being released.
The following bus resource properties may be dynamically allocated and released:
ISA_PROP_INTR
ISA_PROP_IO_REGS
ISA_PROP_MEM_RGN
Note that the IsaBusOps.resource_alloc and IsaBusOps.resource_free routines should not be used by a simple device driver. This type of driver should assume that all resources needed are already allocated and specified as properties in the device node by the bus driver. The driver should only find this type of property and call an appropriate service routine passing a pointer to the property value.
However, it is not always possible to determine all needed resources prior to device initialization. A typical case is a bus-to-bus bridge driver which can discover devices residing on the secondary bus only when the bus bridge hardware has already been initialized. In this case, when drv_init is called, the bus-to-bus bridge node would contain only resources needed for the bus-to-bus bridge device itself (for example, internal bus-to-bus bridge registers). Once devices residing on the secondary bus are discovered, the bus-to-bus bridge driver would request additional primary bus resources in order to satisfy the resource requirements for these devices. Another example is a bus which supports hot-pluggable devices. On this type of bus, the primary bus resources allocated by the hot-pluggable bus driver depend on devices currently plugged into the secondary bus. The resource requirements usually change when a hot-plug insertion/removal occurs.
Note that dynamic resource allocation might be also used by a device driver which implements a lazy resource allocation. In this type of driver, the bus resource allocation might be performed at open time using IsaBusOps.resource_alloc. The dynamically allocated resources might then be released at close time using IsaBusOps.resource_free.
The following table specifies the contexts in which a caller is allowed to invoke each service:
Services | Base level | DKI thread | Interrupt | Blocking |
---|---|---|---|---|
IsaBusOps.open | - | + | - | + |
IsaBusOps.close | - | + | - | + |
IsaBusOps.intr_attach | - | + | - | + |
IsaBusOps.intr_detach | - | + | - | + |
IsaBusOps.io_map | - | + | - | + |
IsaBusOps.io_unmap | - | + | - | + |
IsaBusOps.mem_map | - | + | - | + |
IsaBusOps.mem_unmap | - | + | - | + |
IsaBusOps.dma_attach | - | + | - | + |
IsaBusOps.dma_attach | - | + | - | + |
IsaBusOps.dma_free | - | + | - | + |
IsaBusOps.resource_alloc | - | + | - | + |
IsaBusOps.resource_free | - | + | - | + |
IsaIoOps.load_xx | + | + | + | - |
IsaIoOps.store_xx | + | + | + | - |
IsaIoOps.read_xx | + | + | + | - |
IsaIoOps.write_xx | + | + | + | - |
IsaDmaOps.mem_alloc | - | + | - | + |
IsaDmaOps.mem_free | - | + | - | + |
IsaDmaOps.phys_addr | + | + | + | - |
IsaDmaOps.virt_addr | + | + | + | - |
IsaDmaOps.read | + | + | + | - |
IsaDmaOps.write | + | + | + | - |
IsaDmaOps.read_sync | + | + | + | - |
IsaDmaOps.write_sync | + | + | + | - |
IsaDmaOps.abort | + | + | + | - |
IsaDmaOps.mask | + | + | + | - |
IsaDmaOps.unmask | + | + | + | - |
IsaDmaOps.enable | - | - | + | - |
IsaDmaOps.disable | - | - | + | - |
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 | FEATURES | DESCRIPTION | EXTENDED DESCRIPTION | PROPERTIES | ALLOWED CALLING CONTEXTS | ATTRIBUTES | SEE ALSO