device: Provide generic API to handle synchronous calls in drivers
Introduce the device_sync_call_t object type and its API. This object type allows one thread to perform synchronous calls into a driver. Only one thread can do such calls per instance of the device_sync_call_t object since it makes a global record of what type of thread is waiting on it. Based on an idea from Dmitriy Korovkin and Peter Mitsis, moving their proposal to a more generic API provides the solution for all device drivers that exposes synchronous API in an interrupt based implementation. Change-Id: I793fac76645396bf4eb6be38b5a130ac6bde8f73 Signed-off-by: Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
This commit is contained in:
parent
86a0ab478b
commit
55fdd1fd33
3 changed files with 163 additions and 0 deletions
57
doc/kernel/devices/synchronous_call.rst
Normal file
57
doc/kernel/devices/synchronous_call.rst
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
.. _synchrounous_call:
|
||||||
|
|
||||||
|
Synchronous call within interrupt based device drivers
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
Zephyr provides a set of device drivers for various platforms. As a preemptive
|
||||||
|
OS, those are meant to be implemented through an interrupt based mode and not
|
||||||
|
a polling mode unless the driven hardware does not provide any interrupt
|
||||||
|
enabled mechanism.
|
||||||
|
|
||||||
|
The higher level calls accessed through devices's specific API, such as i2c.h
|
||||||
|
or spi.h, are meant to be, most of the time, synchronous. Thus these calls
|
||||||
|
should be blocking.
|
||||||
|
|
||||||
|
However, and due to the fact Zephyr provides 2 types of executions context on
|
||||||
|
a microkernel, drivers need to act accordingly. A nanokernel semaphore cannot
|
||||||
|
be used if the context is a task for instance. Thus, include/device.h exposes
|
||||||
|
an helper API to handle this case transparently, and no other means should be
|
||||||
|
used instead.
|
||||||
|
|
||||||
|
Usage of synchronous_call_* API
|
||||||
|
*******************************
|
||||||
|
|
||||||
|
1 type and 3 inline functions are exposed to solve this issue.
|
||||||
|
|
||||||
|
The device_sync_call_t type
|
||||||
|
===========================
|
||||||
|
|
||||||
|
It provides a nanokernel semaphore, always present in both built types:
|
||||||
|
nanokernel and microkernel. It is meant to be used within a fiber context.
|
||||||
|
Then, and only on a microkernel type, it will provide a kernel semaphore
|
||||||
|
meant to be used within a task context. A boolean marker is used to remember
|
||||||
|
the caller's context when running a microkernel.
|
||||||
|
|
||||||
|
synchronous_call_init()
|
||||||
|
=======================
|
||||||
|
|
||||||
|
It initializes the device_sync_call_t type: its semaphores and the marker.
|
||||||
|
Such function should be used only once in the device driver's instance life
|
||||||
|
time. Thus the driver's initialization function is the best place for calling
|
||||||
|
it.
|
||||||
|
|
||||||
|
synchronous_call_wait()
|
||||||
|
=======================
|
||||||
|
|
||||||
|
This functions will block - i.e. will perform a "take wait" - on the relevant
|
||||||
|
semaphore. This will make the exposed driver's API function using it a blocking
|
||||||
|
function, until the relevant semaphore is released by a give. This is therefore
|
||||||
|
used to start a synchronous call, until being signaled for synchronization.
|
||||||
|
|
||||||
|
synchronous_call_complete()
|
||||||
|
===========================
|
||||||
|
|
||||||
|
This function is used to release the relevant semaphore and thus will unlock
|
||||||
|
the blocking function. This is thus being called in the driver's ISR handler
|
||||||
|
mostly. This is used to signal the completion of the synchronous call, whatever
|
||||||
|
fate would be (error or success).
|
|
@ -14,6 +14,7 @@ and how to use them.
|
||||||
microkernel/microkernel.rst
|
microkernel/microkernel.rst
|
||||||
nanokernel/nanokernel.rst
|
nanokernel/nanokernel.rst
|
||||||
networking/networking.rst
|
networking/networking.rst
|
||||||
|
devices/synchronous_call.rst
|
||||||
|
|
||||||
.. rubric:: Abbreviations
|
.. rubric:: Abbreviations
|
||||||
|
|
||||||
|
|
105
include/device.h
105
include/device.h
|
@ -85,4 +85,109 @@ struct device {
|
||||||
void _sys_device_do_config_level(int level);
|
void _sys_device_do_config_level(int level);
|
||||||
struct device* device_get_binding(char *name);
|
struct device* device_get_binding(char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronous calls API
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <nanokernel.h>
|
||||||
|
#ifdef CONFIG_MICROKERNEL
|
||||||
|
#include <microkernel.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific type for synchronizing calls among the 2 possible contexts
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
/** Nanokernel semaphore used for fiber context */
|
||||||
|
struct nano_sem *f_sem;
|
||||||
|
#ifdef CONFIG_MICROKERNEL
|
||||||
|
/** Microkernel semaphore used for task context */
|
||||||
|
struct _k_sem_struct _t_sem;
|
||||||
|
ksem_t t_sem;
|
||||||
|
bool caller_is_task;
|
||||||
|
#endif
|
||||||
|
} device_sync_call_t;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the context-dependent synchronization data
|
||||||
|
*
|
||||||
|
* @param sync A pointer to a valid devic_sync_call_t
|
||||||
|
*/
|
||||||
|
static inline void synchronous_call_init(device_sync_call_t *sync)
|
||||||
|
{
|
||||||
|
nano_sem_init(sync->f_sem);
|
||||||
|
#ifdef CONFIG_MICROKERNEL
|
||||||
|
sync->_t_sem.waiters = NULL;
|
||||||
|
sync->_t_sem.level = sync->_t_sem.count = 0;
|
||||||
|
sync->t_sem = (ksem_t)&sync->_t_sem;
|
||||||
|
sync->caller_is_task = false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_MICROKERNEL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wait for the isr to complete the synchronous call
|
||||||
|
* Note: In a microkernel built this function will take care of the caller
|
||||||
|
* context and thus use the right attribute to handle the synchronization.
|
||||||
|
*
|
||||||
|
* @param sync A pointer to a valid devic_sync_call_t
|
||||||
|
*/
|
||||||
|
static inline void synchronous_call_wait(device_sync_call_t *sync)
|
||||||
|
{
|
||||||
|
if ((sys_execution_context_type_get() == NANO_CTX_TASK) &&
|
||||||
|
(task_priority_get() < CONFIG_NUM_TASK_PRIORITIES - 1)) {
|
||||||
|
sync->caller_is_task = true;
|
||||||
|
task_sem_take_wait(sync->t_sem);
|
||||||
|
} else {
|
||||||
|
sync->caller_is_task = false;
|
||||||
|
nano_sem_take_wait(sync->f_sem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Signal the caller about synchronization completion
|
||||||
|
* Note: In a microkernel built this function will take care of the caller
|
||||||
|
* context and thus use the right attribute to signale the completion.
|
||||||
|
*
|
||||||
|
* @param sync A pointer to a valid devic_sync_call_t
|
||||||
|
*/
|
||||||
|
static inline void synchronous_call_complete(device_sync_call_t *sync)
|
||||||
|
{
|
||||||
|
if (sync->caller_is_task) {
|
||||||
|
task_sem_give(sync->t_sem);
|
||||||
|
} else {
|
||||||
|
nano_isr_sem_give(sync->f_sem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wait for the isr to complete the synchronous call
|
||||||
|
* Note: It will simply wait on the internal semaphore.
|
||||||
|
*
|
||||||
|
* @param sync A pointer to a valid devic_sync_call_t
|
||||||
|
*/
|
||||||
|
static inline void synchronous_call_wait(device_sync_call_t *sync)
|
||||||
|
{
|
||||||
|
nano_sem_take_wait(sync->f_sem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Signal the caller about synchronization completion
|
||||||
|
* Note: It will simply release the internal semaphore
|
||||||
|
*
|
||||||
|
* @param sync A pointer to a valid devic_sync_call_t
|
||||||
|
*/
|
||||||
|
static inline void synchronous_call_complete(device_sync_call_t *sync)
|
||||||
|
{
|
||||||
|
nano_isr_sem_give(sync->f_sem);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CONFIG_MICROKERNEL || CONFIG_NANOKERNEL */
|
||||||
|
|
||||||
#endif /* _DEVICE_H_ */
|
#endif /* _DEVICE_H_ */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue