Skip to content

Commit

Permalink
dma: building exchange api (#58)
Browse files Browse the repository at this point in the history
As defined in DMA wiki, adding a new event queue specific to DMA streams
vents.
As DMA streams events are specific, consolidated, events types, they
should be pushed up to userspace tasks as such, instead of pushing bare
IRQn.
Bare IRQn are useless to userspace, as DMA IRQ acknowledge is under the
kernel control. Instead pushing consolidated event is useful for user
task, as they can directly manipulate such event for functional
implementation.

A DMA event is then received as a specific event of the
`wait_for_event()` syscall. In that case, the event received looks like
the following tuple:
```
typedef enum dma_chan_state {
    GPDMA_STATE_IDLE                    = 1, /**< DMA channel idle (not set or unused) */
    GPDMA_STATE_RUNNING                 = 2, /**< DMA channel is running */
    GPDMA_STATE_ABORTED                 = 3, /**< DMA stream aborted on SW request */
    GPDMA_STATE_SUSPENDED               = 4, /**< DMA stream suspended on SW request*/
    GPDMA_STATE_TRANSMISSION_FAILURE    = 5, /**< DMA transmission failure */
    GPDMA_STATE_CONFIGURATION_FAILURE   = 6, /**< DMA channel configuration failure */
    GPDMA_STATE_OVERRUN                 = 7, /**< DMA transmission overrun */
    GPDMA_STATE_TRANSFER_COMPLETE       = 8, /**< DMA transfer complete for this channel */
    GPDMA_STATE_HALF_TRANSFER           = 9, /**< DMA transfer half-complete for this channel */
} dma_chan_state_t;

{ dma_handle, dma_event_state }
```
This tuple allows to directly associate the handle with a userspace
context, and the DMA event type used to react to various events as
required.

The DMA event structure pushed by the kernel is the following,
respecting the exchange_event_t standard Sentry header:

```c
{
    header.type = EVENT_TYPE_DMA, 
    header.length = 1, /* 1 data byte: the DMA event type */
    header.magic = 0x4242, /* hardcoded by now */
    header.source = dma_stream_handle,
    header.data[0] = dma_stream_event;  
}
```

This allows such a usage:

```c
uint8_t dma_event_buf[sizeof(exchange_event_t)+sizeof(uint8_t)];

sys_dma_start(dmah);
if (sys_wait_for_event(EVENT_TYPE_DMA, WAIT_FOREVER) == STATUS_OKAY) {
     copy_to_user(dma_event_buf, sizeof(exchange_event_t)+sizeof(uint8_t));

    /* manipulate dma event info locally */
    exchange_event_t *event = &dma_event_buf[0];
    printf("dma event %d, from handle %lx\n", event->data[0], event->source);
    switch (event->data[0]) {
      case GPDMA_STATE_TRANSMISSION_FAILURE:
        //
      case GPDMA_STATE_USER_FAILURE:
       //
      case GPDMA_STATE_TRANSFER_COMPLETE:
       //
    }
}
```

- [x] push DMA event type to uapi types.h header
- [x] add support for DMA event queue in task context
- [x] add support for DMA event queue push interface
- [x] make DMA IRQn handler pushing the event to the corresponding
stream owner
- [x] Add DMA event to the `wait_for_event()` events list
- [x] add support for DMA event pull interface from task context
- [x] add support for DMA event at `wait_for_event()` syscall level
- [x] update autotest to support this new API

depends on #44 
closes #60
  • Loading branch information
pthierry-ledger authored Oct 1, 2024
2 parents 8b17def + 031464b commit 76bf28b
Show file tree
Hide file tree
Showing 13 changed files with 344 additions and 52 deletions.
15 changes: 15 additions & 0 deletions autotest/src/tests/test_dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@ static void test_dma_assign_unassign_stream(dmah_t stream)
TEST_END();
}

static void test_dma_start_n_wait_stream(dmah_t stream)
{
Status res;
TEST_START();
res = sys_dma_assign_stream(stream);
ASSERT_EQ(res, STATUS_OK);
res = sys_dma_start_stream(stream);
ASSERT_EQ(res, STATUS_OK);
/* wait 50ms for DMA event, should rise in the meantime */
res = sys_wait_for_event(EVENT_TYPE_DMA, 50);
ASSERT_EQ(res, STATUS_OK);
TEST_END();
}

#endif

void test_dma(void)
Expand All @@ -107,6 +121,7 @@ void test_dma(void)
test_dma_get_handle_inval();
test_dma_manipulate_stream_badhandle();
test_dma_assign_unassign_stream(stream);
test_dma_start_n_wait_stream(stream);
test_dma_start_stream(stream);
test_dma_get_stream_status(stream);
test_dma_stop_stream(stream);
Expand Down
14 changes: 5 additions & 9 deletions kernel/include/bsp/drivers/dma/gpdma.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,15 @@

#include <sentry/zlib/compiler.h>
#include <dt-bindings/dma/stm32_dma.h>
#include <uapi/types.h>

/**
* @brief generic state value definition for a DMA channel
*
* This enumerate aliasing UAPI to keep unified gpdma_ prefixed content at driver level
* @see dma_chan_state_t definition for corresponding content.
*/
typedef enum gpdma_chan_state {
GPDMA_STATE_IDLE = 1, /**< DMA channel idle (not set or unused) */
GPDMA_STATE_RUNNING = 2, /**< DMA channel is running */
GPDMA_STATE_ABORTED = 3, /**< DMA stream aborted on SW request */
GPDMA_STATE_SUSPENDED = 4, /**< DMA stream suspended on SW request*/
GPDMA_STATE_TRANSMISSION_FAILURE = 5, /**< DMA transmission failure */
GPDMA_STATE_CONFIGURATION_FAILURE = 6, /**< DMA channel configuration failure */
GPDMA_STATE_OVERRUN = 7, /**< DMA transmission overrun */
} gpdma_chan_state_t;
typedef dma_chan_state_t gpdma_chan_state_t;

/**
* @enum gpdma_chan_trigger
Expand Down
7 changes: 7 additions & 0 deletions kernel/include/sentry/managers/dma.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
extern "C" {
#endif

#if CONFIG_HAS_GPDMA

__STATIC_FORCEINLINE bool mgr_dma_is_irq_owned(int IRQn) {
bool dma_owned = false;
#ifdef CONFIG_HAS_GPDMA
Expand All @@ -24,6 +26,7 @@ return dma_owned;

kstatus_t mgr_dma_init(void);


kstatus_t mgr_dma_watchdog(void);

kstatus_t mgr_dma_get_owner(dmah_t d, taskh_t *owner);
Expand All @@ -36,6 +39,10 @@ kstatus_t mgr_dma_get_handle(uint32_t label, dmah_t * handle);

kstatus_t mgr_dma_get_dmah_from_interrupt(uint16_t IRQn, dmah_t *dmah);

kstatus_t mgr_dma_get_state(dmah_t d, dma_chan_state_t *state);

#endif/* HAS_GPDMA */

/**
* Iterate over the device list, starting with id==id.
* Return the devinfo of the current id increment, or set devinfo to NULL and return K_ERROR_NOENT if
Expand Down
9 changes: 7 additions & 2 deletions kernel/include/sentry/managers/task.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,19 @@ taskh_t mgr_task_get_autotest(void);
kstatus_t mgr_task_autotest(void);
#endif

#if CONFIG_HAS_GPDMA
kstatus_t mgr_task_push_dma_event(taskh_t target, dmah_t dma_stream, dma_chan_state_t dma_event);
kstatus_t mgr_task_load_dma_event(taskh_t target, dmah_t *dma_stream, dma_chan_state_t *dma_event);
#endif

/* specialized event pushing API, do not use directly but instead Generic below */
kstatus_t mgr_task_push_int_event(uint32_t IRQn, taskh_t dest);
kstatus_t mgr_task_push_ipc_event(uint32_t len, taskh_t source, taskh_t dest);
kstatus_t mgr_task_push_sig_event(uint32_t sig, taskh_t source, taskh_t dest);

kstatus_t mgr_task_load_ipc_event(taskh_t context);
kstatus_t mgr_task_load_sig_event(taskh_t context);
kstatus_t mgr_task_load_int_event(taskh_t context);
kstatus_t mgr_task_load_sig_event(taskh_t context, uint32_t *signal, taskh_t *source);
kstatus_t mgr_task_load_int_event(taskh_t context, uint32_t *IRQn);

/* get back peer that has emitted IPC to owner, in iterative way
* if status = K_STATUS_NOENT, no more IPC, if status = OKAY, peer hold the emitter handle
Expand Down
1 change: 1 addition & 0 deletions kernel/src/drivers/usart/stm32-lpuart.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

#include <sentry/io.h>
#include <bsp/drivers/clk/rcc.h>

#include "lpuart_defs.h"
#include "usart_priv.h"
Expand Down
27 changes: 27 additions & 0 deletions kernel/src/managers/dma/dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,26 @@ kstatus_t mgr_dma_init(void)
/* Add entropy to handle initialization */
if (unlikely(mgr_security_entropy_generate(&seed) != K_STATUS_OKAY)) {
panic(PANIC_HARDWARE_INVALID_STATE);
__builtin_unreachable();
}
kdmah.reserved = seed;

dmah_t const *dmah = kdmah_to_dmah(&kdmah);
if (unlikely(dma_stream_get_meta(streamid, &stream_config[streamid].meta) != K_STATUS_OKAY)) {
panic(PANIC_CONFIGURATION_MISMATCH);
__builtin_unreachable();

}
if (unlikely(mgr_task_get_handle(stream_config[streamid].meta->owner, &stream_config[streamid].owner) != K_STATUS_OKAY)) {
panic(PANIC_CONFIGURATION_MISMATCH);
__builtin_unreachable();
}

/*@ assert \valid(dmah); */
/*@ assert \valid_read(stream_config[streamid].meta); */
stream_config[streamid].handle = *dmah;
stream_config[streamid].state = DMA_STREAM_STATE_UNSET; /** FIXME: define status types for streams */
stream_config[streamid].status = GPDMA_STATE_IDLE;
}
#endif
return status;
Expand Down Expand Up @@ -141,6 +146,27 @@ kstatus_t mgr_dma_get_owner(dmah_t d, taskh_t *owner)
return status;
}

/*@
requires \valid(state);
*/
kstatus_t mgr_dma_get_state(dmah_t d, dma_chan_state_t *state)
{
kstatus_t status = K_ERROR_INVPARAM;
/*@ assert \valid(state); */

kdmah_t const *kdmah = dmah_to_kdmah(&d);
if (kdmah->streamid >= STREAM_LIST_SIZE) {
goto end;
}
if (stream_config[kdmah->streamid].handle != d) {
goto end;
}
*state = stream_config[kdmah->streamid].status;
status = K_STATUS_OKAY;
end:
return status;
}

#ifdef CONFIG_BUILD_TARGET_AUTOTEST
kstatus_t mgr_dma_autotest(void)
{
Expand Down Expand Up @@ -178,6 +204,7 @@ kstatus_t mgr_dma_get_dmah_from_interrupt(const uint16_t IRQn, dmah_t *dmah)
/*@ assert \valid_read(cfg); */
if (unlikely(gpdma_get_interrupt(cfg, &stream_irqn) != K_STATUS_OKAY)) {
panic(PANIC_CONFIGURATION_MISMATCH);
__builtin_unreachable();
}
if (stream_irqn == IRQn) {
*dmah = stream_config[stream].handle;
Expand Down
4 changes: 3 additions & 1 deletion kernel/src/managers/dma/dma.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ typedef enum dma_stream_state {
*
* This structure associate a hardware DMA stream configuration (dts-based) to
* the stream owner (also dts-based, using associated channel owner)
*
*/
typedef struct dma_stream_config {
dma_meta_t const * meta; /**< Hardware configuration of the stream */
dmah_t handle; /**< associated DMA handle (opaque format) */
taskh_t owner; /**< stream owner task handle */
dma_stream_state_t state; /**< DMA stream state */
dma_stream_state_t state; /**< DMA stream state (configuration relative state) */
dma_chan_state_t status; /**< DMA channel status, stream-relative dynamic */
} dma_stream_config_t;


Expand Down
45 changes: 44 additions & 1 deletion kernel/src/managers/interrupt/interrupt.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,42 @@ static inline stack_frame_t *devisr_handler(stack_frame_t *frame, int IRQn)
}

#if CONFIG_HAS_GPDMA
/**
* @brief push DMA vent to owner's input queue, and schedule it if all hypothesis are valid
*
* This function is agnostic of the DMA vent properties only push the event to the target task input FIFO.
*/
static inline void dma_push_and_schedule(taskh_t owner, dmah_t handle, dma_chan_state_t event)
{
job_state_t owner_state;

if (unlikely(mgr_task_get_state(owner, &owner_state) != K_STATUS_OKAY)) {
panic(PANIC_KERNEL_INVALID_MANAGER_RESPONSE);
}
/* push the inth event into the task input events queue */
if (unlikely(mgr_task_push_dma_event(owner, handle, event) == K_STATUS_OKAY)) {
/* failed to push IRQ event !!! XXX: what do we do ? */
panic(PANIC_KERNEL_SHORTER_KBUFFERS_CONFIG);
}
if ((owner_state == JOB_STATE_SLEEPING) ||
(owner_state == JOB_STATE_WAITFOREVENT)) {
/* if the job exists in the delay queue (sleep or waitforevent with timeout)
* remove it from the delay queue before schedule
* TODO: use a dedicated state (WAITFOREVENT_TIMEOUT) to call this
* function only if needed
*/
mgr_time_delay_del_job(owner);
mgr_task_set_sysreturn(owner, STATUS_INTR);
mgr_task_set_state(owner, JOB_STATE_READY);
sched_schedule(owner);
}
}

static inline stack_frame_t *dmaisr_handler(stack_frame_t *frame, int IRQn)
{
dmah_t dma;
taskh_t owner = 0;
dma_chan_state_t event;

/* get the dmah owning the interrupt */
if (unlikely(mgr_dma_get_dmah_from_interrupt(IRQn, &dma) != K_STATUS_OKAY)) {
Expand All @@ -89,7 +121,18 @@ static inline stack_frame_t *dmaisr_handler(stack_frame_t *frame, int IRQn)
/* user interrupt with no owning task ???? */
panic(PANIC_KERNEL_INVALID_MANAGER_RESPONSE);
}
int_push_and_schedule(owner, IRQn);
if (unlikely(mgr_dma_get_state(dma, &event) != K_STATUS_OKAY)) {
panic(PANIC_KERNEL_INVALID_MANAGER_RESPONSE);
}

/** TODO: call DMA driver for GPDMA-related interrupt acknowledge first */
/**
* NOTE: no need to check for this API return code, as this function panic() on failure.
* Although, to avoid inter-managers implementation dependencies, we duplicate the return
* check here, as a unlikely, never callable block (dead-code)
*/
dma_push_and_schedule(owner, dma, event);

return frame;
}
#endif
Expand Down
Loading

0 comments on commit 76bf28b

Please sign in to comment.