-
Notifications
You must be signed in to change notification settings - Fork 2
DMA management
- channel: A channel is a hardware slot of a DMA controller that aim to execute a given, configured, DMA transaction. A DMA controller usually have multiple channels.
- stream: A stream is the transaction itself, as defined and associated to a given channel. A stream can be configured, started, stopped, or in error
- DMA handle (Sentry specific) : see https://outpost-sentry.readthedocs.io/en/latest/concepts/archi_req.html#micro-kernel-design-handles for more informations
The following hypothesis are considered for the Sentry kernel DMA stack
- HYP1: The DMA considered in Sentry is general purpose DMA (GPDMA, DMA or SDMA depending on the datasheet naming). DMA2D is out of scope
- HYP2: There can be more than one DMA controller
- HYP3: There can be no DMA controller at all
- HYP4: There is a way to strictly associate each DMA channel with an IRQn identifier at interrupt controller level
- HYP5: The DMA controller may have strictly assigned channel for specific purposes (e.g. SPI memory to device copy)
- HYP6: The DMA controller supports, for each channel, interrupt rise for at least Transfer Complete and Error
- HYP7: A given task can manipulate from 0 to multiple DMA streams
- HYP8: A given DMA channel is strictly associated to a given task, and can't be shared
- HYP9: A memory-targetting (or sourcing) DMA stream can only interact with declared Sentry Shared Memories that are tagged as DMA-compatible
- HYP10: DMA streams are strictly build-time defined, and can be identified at run-time
DMA streams are defined through device-tree. A stream holds all required elements so that it can be configured without requiring any supplementary informations from the calling task. It is required that for a given DMA controller, the controller's channel must be explicitely defined. Channel ownership is specified at channel level, instead of stream level.
INFO: This allows to have a clear vision of which task is proprietary of which channel.
&gpdma1 {
status = "okay";
// About channels that are used
gpdma1_1: dma-channel@1 {
status = "okay";
outpost,owner = <0xbabe>; // owner task label
};
};
Once the DMA controller's channel is set, a stream definition looks like the following:
/{
reserved-memory {
shm_autotest_1: memory@2000a000 {
// mappable, dma allowed
reg = <0x2000a000 0x256>;
dma-pool;
outpost,shm;
outpost,label = <0xf00>;
outpost,owner = <0xbabe>;
};
// device-to-memory DMA stream
stream1 {
compatible = "dma-stream";
channel = <&gpdma1_1>;
streamid = <112>; // channel stream alternate-function identifier (see datasheet)
prio = <STM32_DMA_PRIORITY_MEDIUM>;
source = <&usart1>;
dest = <&shm_autotest_1>;
size = <42>; // size to copy, in bytes
circular = <1 0>; // circular source, linear dest
outpost,label = <0xd01>; // task-level unique identifier for this stream
};
};
};
NOTE: some fields, such as streamid
, are typical datasheet related informations, and will not be checked by Sentry kernel. Others, such as target or source size, when targetting SHM, are checked against SHM properties.
As said in HYP9, a DMA stream that interracts with memory can only target a declared shared memory. There is no other way to transfer to/from memory.
DMA-compatible shared memory must hold the dma-pool
attribute.
Although, there is no runtime-check of the SHM ownership/user-access between a DMA stream owner and a SHM owner or user. It is considered that:
- ownership is DTS-based declared, and under the project integrator responsability, like the inclusion of the
dma-pool
attribute - assigning or starting a DMA stream that targets a SHM that is, at build time, not under the task ownership is not considered. This is mostly because this would include complex kernel algorithm in order to prevent SHM credentials update or user assignation once a DMA stream is assigned or started, making this API both more complex and less efficient. As a consequence, the control of which task can spawn DMA streams targetting a
dma-pool
SHM stays under the integrator's responsability.
- a GPDMA driver, at bsp level, with a generic high level API. From one IP to another, the BSP part can be substituted without impacting its upper API, for portability. This is the single place where IP-Specific, and sometimes SoC-specific elements can be explicitely denoted. The GPDMA driver has no context
- a DMA manager, responsible for associating GPDMA driver with syscalls gate, so that various DMA-related syscalls are properly handled and emitted to driver level. The manager must be SoC and arch generic. The DMA manager holds the stream list, their current state, and the ownership informations
- DMA-related gate (kernel) and UAPI (user): implementation of the userspace/kernelspace interactions. The gate only interract with the manager, manipulating exclusiverly DMA handles
- a DMA interrupt subsystem, that is decomposed between:
- GPDMA driver: low level interrtupt handling at GPDMA IP level
- per-arch (e.g. ASM-Cortex-M) level: IT handler entrypoint link
By now, there is a single userisr_handler()
IRQ handle, held by Interrupt manager, for all non-kernel IT. When the IRQn is a DMA-related IRQ. Sentry:
- Gets back the channel and controller associated to it
- Gets back the associated task handle, owner of the current stream
- calls the kernel DMA-stack interrupt-related action
- Pushes the DMA event to the task's input DMA events vector for userspace bottom-half execution
- if the task was not eligible, schedules the task (scheduling does not mean electing, see https://outpost-sentry.readthedocs.io/en/latest/sw_architecture/schedulers.html about sheduling)
As DMA events, such as Error or Transfer complete are consolidated events, the effective interrupt identifier (i.e. IRQn), is not pushed up to the task. Instead, the kernel push the { dmah, event_type }
couple, so that the userspace task immediatly which stream as emitted the given event, without having to resolve any HW-related IRQ identifier.
This interface is highly efficient when using memory-to-memory streams, for which the notion of IRQn is perfectly useless at task level.
DMA events can be listened with the sys_wait_for_event()
syscall using the newly declared EVENT_TYPE_DMA (see https://outpost-sentry.readthedocs.io/en/latest/uapi/syscalls.html#sys-wait-for-event).
Manipulating DMA requires the capability CAP_DEV_DMA, as this capa is also owned by all streams.
Configuring and setting streams is based on the following UAPI definition (Rust implementation and C export):
-
sys_get_dma_stream_handle(dma_label: u32) -> Status
:Get back the dynamically forged handle associated with the build-time define label used for the stream in its device-tree definition (using the
outpost,label
field). The handle is received in the SVC Exchange area when Status is Ok. -
sys_dma_assign_stream(dmah: dmah_t) -> Status
:Assign a given stream. The stream is assigned to the corresponding HW channel. Not started.
-
sys_dma_start_stream(dmah: dmah_t) -> Status
:Start the previously assigned DMA stream. TC (and potentially Error) interrupts will rise
-
sys_dma_get_stream_status(dmah: dmah_t) -> Status
:Get back informations about the dynamic state of the current stream. Error flags are defined as generic and not IP-specific for portability. This API is made to be performant and use in userspace bottom-halves.
-
sys_dma_get_stream_info(dmah: dmah_t) -> Status
:Get back informations about the stream declaration. All these infos are mostly build-time forged, and avoid any devicetree parsing at application level, using this structure instead. This API is not made to be highly performant, in opposition with the
sys_dma_get_stream_status()
one. -
sys_dma_suspend_stream(dmah: dmah_t) -> Status
:Suspend a previously started DMA stream. Useful for circular DMA streams for e.g. or for SW/HW sync requirement
-
sys_dma_resume_stream(dmah: dmah_t) -> Status
:Resume a previously suspended DMA stream. Useful for circular DMA streams for e.g. or for SW/HW sync requirement
-
sys_dma_unassign_stream(dmah: dmah_t) -> Status
:Unassign current stream for the associated DMA controller's channel. This allows a given task to consecutively spawn different DMA streams using the same channel. If the stream was assigned, it only unassign it. If the stream was suspended, it is reset before unassign.