This chapter describes the API that device drivers may use to interact with the kernel and HAL. It is primarily concerned with the control and management of interrupts.
The same API will be present in configurations where the kernel is not present. In this case the functions will be supplied by code acting directly on the HAL.
eCos presents a three level interrupt model to device drivers. This consists of Interrupt Service Routines (ISRs) that are invoked in response to a hardware interrupt; Deferred Service Routines (DSRs) that are invoked in response to a request by an ISR; and threads that are the clients of the driver.
Hardware interrupts are delivered with minimal intervention to an ISR. The HAL decodes the hardware source of the interrupt and calls the ISR of the attached interrupt object. This ISR may manipulate the hardware but is only allowed to make a restricted set of calls on the driver API. When it returns, an ISR may request that its DSR should be scheduled to run.
A DSR will be run when it is safe to do so without interfering with the scheduler. Most of the time the DSR will run immediately after the ISR, but if the current thread is in the scheduler, it will be delayed until the thread is finished. A DSR is allowed to make a larger set of driver API calls, in particular it is able to call
cyg_drv_cond_signal()
to wake up waiting threads.
Finally, threads are able to make all API calls and in particular are allowed to wait on mutexes and condition variables.
For a device driver to receive interrupts it must first define ISR and DSR routines as shown below, and then call
cyg_drv_interrupt_create()
. Using the handle returned, the driver must then call
cyg_drv_interrupt_attach()
to actually attach the interrupt to the hardware vector.
There are three levels of synchronization supported:
cyg_drv_isr_lock()
and
cyg_drv_isr_unlock()
functions. This mechanism should be used sparingly and for short periods only.
cyg_drv_dsr_lock()
and
cyg_drv_dsr_unlock()
functions. As with ISR synchronization, this mechanism should be used sparingly.
ISRs are run with interrupts disabled, so it is not necessary to call
cyg_drv_isr_lock()
in an ISR. Similarly DSRs are run with the scheduler lock taken, so it is not necessary to call
cyg_drv_dsr_lock()
in DSRs.
There are several ways in which device drivers may be built. The exact model chosen will depend on the properties of the device and the behavior desired. There are three basic models that may be adopted.
The first model is to do all device processing in the ISR. When it is invoked the ISR programs the device hardware directly and accesses data to be transferred directly in memory. The ISR should also call
cyg_drv_interrupt_acknowledge()
. When it is finished it may optionally request that its DSR be invoked. The DSR does nothing but call
cyg_drv_cond_signal()
to cause a thread to be woken up. Thread level code must call
cyg_drv_isr_lock()
, or
cyg_drv_interrupt_mask()
to prevent ISRs running while it manipulates shared memory.
The second model is to defer device processing to the DSR. The ISR simply prevents further delivery of interrupts by either programming the device, or by calling
cyg_drv_interrupt_mask()
. It may then call
cyg_drv_interrupt_acknowledge()
to allow other interrupts to be delivered and request that its DSR be called. When the DSR runs it does the majority of the device handling, optionally signals a condition variable to wake a thread, and finishes by calling
cyg_drv_interrupt_unmask()
to re-allow device interrupts. Thread level code uses
cyg_drv_dsr_lock()
to prevent DSRs running while it manipulates shared memory.
The third model is to defer device processing even further to a thread. The ISR behaves exactly as in the previous model and simply blocks and acknowledges the interrupt before request that the DSR run. The DSR itself only calls
cyg_drv_cond_signal()
to wake the thread. When the thread awakens it performs all device processing, and has full access to all kernel facilities while it does so. It should finish by calling
cyg_drv_interrupt_unmask()
to re-allow device interrupts.
The first model is good for devices that need immediate processing and interact infrequently with thread level. The second model trades a little latency in dealing with the device for a less intrusive synchronization mechanism. The last model allows device processing to be scheduled with other threads and permits more complex device handling.
Since it would be dangerous for an ISR or DSR to make a call that might reschedule the current thread (by trying to lock a mutex for example) all functions in this API have an associated synchronization level. These levels are:
This function may only be called from within threads. This is usually the client code that makes calls into the device driver. In a non-kernel configuration, this will be code running at the default non-interrupt level.
This function may be called by either DSR or thread code.
This function may be called from ISR, DSR or thread code.
The following table shows, for each API function, the levels at which is may be called:
Callable from: Function ISR DSR Thread ---------------------------------------------------------------------- cyg_drv_isr_lock X X cyg_drv_isr_unlock X X cyg_drv_dsr_lock X cyg_drv_dsr_unlock X cyg_drv_mutex_init X cyg_drv_mutex_destroy X cyg_drv_mutex_lock X cyg_drv_mutex_trylock X cyg_drv_mutex_unlock X cyg_drv_mutex_release X cyg_drv_cond_init X cyg_drv_cond_destroy X cyg_drv_cond_wait X cyg_drv_cond_signal X X cyg_drv_cond_broadcast X X cyg_drv_interrupt_create X cyg_drv_interrupt_delete X cyg_drv_interrupt_attach X X X cyg_drv_interrupt_detach X X X cyg_drv_interrupt_mask X X X cyg_drv_interrupt_unmask X X X cyg_drv_interrupt_acknowledge X X X cyg_drv_interrupt_configure X X X cyg_drv_interrupt_level X X X
This section details the Driver Kernel Interface. Note that most of these functions are identical to Kernel C API calls, and will in most configurations be wrappers for them. In non-kernel configurations they will be supported directly by the HAL, or by code to emulate the required behavior.
This API is defined in the header file
cyg/hal/drv_api.h
.
void cyg_drv_isr_lock()
Disables delivery of interrupts, preventing all ISRs running. This function maintains a counter of the number of times it is called.
void cyg_drv_isr_unlock()
Re-enables delivery of interrupts, allowing ISRs to run. This function decrements the counter maintained by
cyg_drv_isr_lock()
, and only re-allows interrupts when it goes to zero.
void cyg_drv_dsr_lock()
Disables scheduling of DSRs. This function maintains a counter of the number of times it has been called.
void cyg_drv_dsr_unlock()
Re-enables scheduling of DSRs. This function decrements the counter incremented by
cyg_drv_dsr_lock()
. DSRs are only allowed to be delivered when the counter goes to zero.
void cyg_drv_mutex_init(cyg_drv_mutex *mutex)
mutex - pointer to mutex to initialize
Initialize the mutex pointer to by the mutex argument.
void cyg_drv_mutex_destroy( cyg_drv_mutex *mutex )
mutex - pointer to mutex to destroy
Destroy the mutex pointed to by the mutex argument.
cyg_bool cyg_drv_mutex_lock( cyg_drv_mutex *mutex )
mutex - pointer to mutex to lock
TRUE it the thread has claimed the lock, FALSE otherwise.
Attempt to lock the mutex pointed to by the mutex argument. If the mutex is already locked by another thread then this thread will wait until that thread is finished. If the result from this function is FALSE then the thread was broken out of its wait by some other thread. In this case the mutex will not have been locked.
cyg_bool cyg_drv_mutex_trylock( cyg_drv_mutex *mutex )
mutex - pointer to mutex to lock
TRUE if the mutex has been locked, FALSE otherwise.
Attempt to lock the mutex pointed to by the mutex argument without waiting. If the mutex is already locked by some other thread then this function returns FALSE. If the function can lock the mutex without waiting, then TRUE is returned.
void cyg_drv_mutex_unlock( cyg_drv_mutex *mutex )
mutex - pointer to mutex to unlock
Unlock the mutex pointed to by the mutex argument. If there are any threads waiting to claim the lock, one of them is woken up to try and claim it.
void cyg_drv_mutex_release( cyg_drv_mutex *mutex )
mutex - pointer to mutex to release
Release all threads waiting on the mutex pointed to by the mutex argument. These threads will return from
cyg_drv_mutex_lock()
with a FALSE result and will not have claimed the mutex. This function has no effect on any thread that may have the mutex claimed.
void cyg_drv_cond_init( cyg_drv_cond *cond, cyg_drv_mutex *mutex )
cond--condition variable to initialize
mutex--mutex to associate with this condition variable
Initialize the condition variable pointed to by the cond argument. The mutex argument must point to a mutex with which this condition variable is associated. A thread may only wait on this condition variable when it has already locked the associated mutex. Waiting will cause the mutex to be unlocked, and when the thread is reawakened, it will automatically claim the mutex before continuing.
void cyg_drv_cond_destroy( cyg_drv_cond *cond )
cond - condition variable to destroy
Destroy the condition variable pointed to by the cond argument.
void cyg_drv_cond_wait( cyg_drv_cond *cond )
cond - condition variable to wait on
Wait for a signal on the condition variable pointed to by the cond argument. The thread must have locked the associated mutex before waiting on this condition variable. While the thread waits, the mutex will be unlocked, and will be re-locked before this function returns. It is possible for threads waiting on a condition variable to occasionally wake up spuriously. For this reason it is necessary to use this function in a loop that re-tests the condition each time it returns.
void cyg_drv_cond_signal( cyg_drv_cond *cond )
cond - condition variable to signal
Signal the condition variable pointed to by the cond argument. If there are any threads waiting on this variable at least one of them will be awakened. Note that in some configurations there may not be any difference between this function and
cyg_drv_cond_broadcast()
.
void cyg_drv_cond_broadcast( cyg_drv_cond *cond )
cond - condition variable to broadcast to
Signal the condition variable pointed to by the cond argument. If there are any threads waiting on this variable they will all be awakened.
void cyg_drv_interrupt_create( cyg_vector_t vector, cyg_priority_t priority, cyg_addrword_t data, cyg_ISR_t *isr, cyg_DSR_t *dsr, cyg_handle_t *handle, cyg_interrupt *intr )
isr--interrupt service routine
intr--put interrupt object here
Create an interrupt object and returns a handle to it. The object contains information about which interrupt vector to use and the ISR and DSR that will be called after the interrupt object is attached. The interrupt object will be allocated in the memory passed in the intr parameter. The interrupt object is not immediately attached; it must be attached with the
cyg_interrupt_attach()
call.
void cyg_drv_interrupt_delete( cyg_handle_t interrupt )
interrupt--interrupt to delete
Detach the interrupt from the vector and free the memory passed in the intr argument to
cyg_drv_interrupt_create()
for reuse.
void cyg_drv_interrupt_attach( cyg_handle_t interrupt )
interrupt--interrupt to attach
Attach the interrupt to the vector so that interrupts will be delivered to the ISR when the interrupt occurs.
void cyg_drv_interrupt_detach( cyg_handle_t interrupt )
interrupt--interrupt to detach
Detach the interrupt from the vector so that interrupts will no longer be delivered to the ISR.
void cyg_drv_interrupt_mask(cyg_vector_t vector )
Program the interrupt controller to stop delivery of interrupts on the given vector. On architectures which implement interrupt priority levels this may also disable all lower priority interrupts.
void cyg_drv_interrupt_unmask(cyg_vector_t vector )
Program the interrupt controller to re-allow delivery of interrupts on the given vector.
void cyg_drv_interrupt_acknowledge( cyg_vector_t vector )
Perform any processing required at the interrupt controller and in the CPU to cancel the interrupt request. An ISR may also need to program the hardware of the device to prevent an immediate re-triggering of the interrupt.
void cyg_drv_interrupt_configure( cyg_vector_t vector, cyg_bool_t level, cyg_bool_t up )
level--level or edge triggered
up--rising/falling edge, high/low level
Program the interrupt controller with the characteristics of the interrupt source. The level argument chooses between level- or edge-triggered interrupts. The up argument chooses between high and low level for level triggered interrupts or rising and falling edges for edge triggered interrupts. This function only works with interrupt controllers that can control these parameters.
void cyg_drv_interrupt_level( cyg_vector_t vector, cyg_priority_t level )
Program the interrupt controller to deliver the given interrupt at the supplied priority level. This function only works with interrupt controllers that can control this parameter.
typedef cyg_uint32 cyg_ISR_t( cyg_vector_t vector, cyg_addrword_t data )
vector--vector being delivered
data--data value supplied by client
Bit mask indicating whether interrupt was handled and whether the DSR should be called.
Interrupt Service Routines definition. A pointer to a function with this prototype is passed to
cyg_interrupt_create()
when an interrupt object is created. When an interrupt is delivered the function will be called with the vector number and the data value that was passed to
cyg_interrupt_create()
.
The return value is a bit mask containing one or both of the following bits:
indicates that the interrupt was handled by this ISR. It is a configuration option whether this will prevent further ISR being run.
causes the DSR that was passed to
cyg_interrupt_create()
to be scheduled to be called.
typedef void cyg_DSR_t( cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data )
vector--vector being delivered
count--number of times DSR has been scheduled
data--data value supplied by client
Deferred Service Routine definition. A pointer to a function with this prototype is passed to
cyg_interrupt_create()
when an interrupt object is created. When the ISR request the scheduling of its DSR, this function will be called at some later point. In addition to the vector and data arguments, which will be the same as those passed to the ISR, this routine is also passed a count of the number of times the ISR has requested that this DSR be scheduled. This counter is zeroed each time the DSR actually runs, so it indicates how many interrupts have occurred since it last ran.
Device Driver Interface to the Kernel | ||
---|---|---|