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:
Tomasz Bursztyka 2015-11-24 13:29:11 +01:00 committed by Anas Nashif
commit 55fdd1fd33
3 changed files with 163 additions and 0 deletions

View 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).

View file

@ -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

View file

@ -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_ */