요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
=======================
STM32 DMA-MDMA chaining
=======================
Introduction
------------
This document describes the STM32 DMA-MDMA chaining feature. But before going
further, let's introduce the peripherals involved.
To offload data transfers from the CPU, STM32 microprocessors (MPUs) embed
direct memory access controllers (DMA).
STM32MP1 SoCs embed both STM32 DMA and STM32 MDMA controllers. STM32 DMA
request routing capabilities are enhanced by a DMA request multiplexer
(STM32 DMAMUX).
**STM32 DMAMUX**
STM32 DMAMUX routes any DMA request from a given peripheral to any STM32 DMA
controller (STM32MP1 counts two STM32 DMA controllers) channels.
**STM32 DMA**
STM32 DMA is mainly used to implement central data buffer storage (usually in
the system SRAM) for different peripheral. It can access external RAMs but
without the ability to generate convenient burst transfer ensuring the best
load of the AXI.
**STM32 MDMA**
STM32 MDMA (Master DMA) is mainly used to manage direct data transfers between
RAM data buffers without CPU intervention. It can also be used in a
hierarchical structure that uses STM32 DMA as first level data buffer
interfaces for AHB peripherals, while the STM32 MDMA acts as a second level
DMA with better performance. As a AXI/AHB master, STM32 MDMA can take control
of the AXI/AHB bus.
Principles
----------
STM32 DMA-MDMA chaining feature relies on the strengths of STM32 DMA and
STM32 MDMA controllers.
STM32 DMA has a circular Double Buffer Mode (DBM). At each end of transaction
(when DMA data counter - DMA_SxNDTR - reaches 0), the memory pointers
(configured with DMA_SxSM0AR and DMA_SxM1AR) are swapped and the DMA data
counter is automatically reloaded. This allows the SW or the STM32 MDMA to
process one memory area while the second memory area is being filled/used by
the STM32 DMA transfer.
With STM32 MDMA linked-list mode, a single request initiates the data array
(collection of nodes) to be transferred until the linked-list pointer for the
channel is null. The channel transfer complete of the last node is the end of
transfer, unless first and last nodes are linked to each other, in such a
case, the linked-list loops on to create a circular MDMA transfer.
STM32 MDMA has direct connections with STM32 DMA. This enables autonomous
communication and synchronization between peripherals, thus saving CPU
resources and bus congestion. Transfer Complete signal of STM32 DMA channel
can triggers STM32 MDMA transfer. STM32 MDMA can clear the request generated
by the STM32 DMA by writing to its Interrupt Clear register (whose address is
stored in MDMA_CxMAR, and bit mask in MDMA_CxMDR).
.. table:: STM32 MDMA interconnect table with STM32 DMA
+--------------+----------------+-----------+------------+
| STM32 DMAMUX | STM32 DMA | STM32 DMA | STM32 MDMA |
| channels | channels | Transfer | request |
| | | complete | |
| | | signal | |
+==============+================+===========+============+
| Channel *0* | DMA1 channel 0 | dma1_tcf0 | *0x00* |
+--------------+----------------+-----------+------------+
| Channel *1* | DMA1 channel 1 | dma1_tcf1 | *0x01* |
+--------------+----------------+-----------+------------+
| Channel *2* | DMA1 channel 2 | dma1_tcf2 | *0x02* |
+--------------+----------------+-----------+------------+
| Channel *3* | DMA1 channel 3 | dma1_tcf3 | *0x03* |
+--------------+----------------+-----------+------------+
| Channel *4* | DMA1 channel 4 | dma1_tcf4 | *0x04* |
+--------------+----------------+-----------+------------+
| Channel *5* | DMA1 channel 5 | dma1_tcf5 | *0x05* |
+--------------+----------------+-----------+------------+
| Channel *6* | DMA1 channel 6 | dma1_tcf6 | *0x06* |
+--------------+----------------+-----------+------------+
| Channel *7* | DMA1 channel 7 | dma1_tcf7 | *0x07* |
+--------------+----------------+-----------+------------+
| Channel *8* | DMA2 channel 0 | dma2_tcf0 | *0x08* |
+--------------+----------------+-----------+------------+
| Channel *9* | DMA2 channel 1 | dma2_tcf1 | *0x09* |
+--------------+----------------+-----------+------------+
| Channel *10* | DMA2 channel 2 | dma2_tcf2 | *0x0A* |
+--------------+----------------+-----------+------------+
| Channel *11* | DMA2 channel 3 | dma2_tcf3 | *0x0B* |
+--------------+----------------+-----------+------------+
| Channel *12* | DMA2 channel 4 | dma2_tcf4 | *0x0C* |
+--------------+----------------+-----------+------------+
| Channel *13* | DMA2 channel 5 | dma2_tcf5 | *0x0D* |
+--------------+----------------+-----------+------------+
| Channel *14* | DMA2 channel 6 | dma2_tcf6 | *0x0E* |
+--------------+----------------+-----------+------------+
| Channel *15* | DMA2 channel 7 | dma2_tcf7 | *0x0F* |
+--------------+----------------+-----------+------------+
STM32 DMA-MDMA chaining feature then uses a SRAM buffer. STM32MP1 SoCs embed
three fast access static internal RAMs of various size, used for data storage.
Due to STM32 DMA legacy (within microcontrollers), STM32 DMA performances are
bad with DDR, while they are optimal with SRAM. Hence the SRAM buffer used
between STM32 DMA and STM32 MDMA. This buffer is split in two equal periods
and STM32 DMA uses one period while STM32 MDMA uses the other period
simultaneously.
::
dma[1:2]-tcf[0:7]
.----------------.
____________ ' _________ V____________
| STM32 DMA | / __|>_ \ | STM32 MDMA |
|------------| | / \ | |------------|
| DMA_SxM0AR |<=>| | SRAM | |<=>| []-[]...[] |
| DMA_SxM1AR | | \_____/ | | |
|____________| \___<|____/ |____________|
STM32 DMA-MDMA chaining uses (struct dma_slave_config).peripheral_config to
exchange the parameters needed to configure MDMA. These parameters are
gathered into a u32 array with three values:
* the STM32 MDMA request (which is actually the DMAMUX channel ID),
* the address of the STM32 DMA register to clear the Transfer Complete
interrupt flag,
* the mask of the Transfer Complete interrupt flag of the STM32 DMA channel.
Device Tree updates for STM32 DMA-MDMA chaining support
-------------------------------------------------------
**1. Allocate a SRAM buffer**
SRAM device tree node is defined in SoC device tree. You can refer to it in
your board device tree to define your SRAM pool.
::
&sram {
my_foo_device_dma_pool: dma-sram@0 {
reg = <0x0 0x1000>;
};
};
Be careful of the start index, in case there are other SRAM consumers.
Define your pool size strategically: to optimise chaining, the idea is that
STM32 DMA and STM32 MDMA can work simultaneously, on each buffer of the
SRAM.
If the SRAM period is greater than the expected DMA transfer, then STM32 DMA
and STM32 MDMA will work sequentially instead of simultaneously. It is not a
functional issue but it is not optimal.
Don't forget to refer to your SRAM pool in your device node. You need to
define a new property.
::
&my_foo_device {
...
my_dma_pool = &my_foo_device_dma_pool;
};
Then get this SRAM pool in your foo driver and allocate your SRAM buffer.
**2. Allocate a STM32 DMA channel and a STM32 MDMA channel**
You need to define an extra channel in your device tree node, in addition to
the one you should already have for "classic" DMA operation.
This new channel must be taken from STM32 MDMA channels, so, the phandle of
the DMA controller to use is the MDMA controller's one.
::
&my_foo_device {
[...]
my_dma_pool = &my_foo_device_dma_pool;
dmas = <&dmamux1 ...>, // STM32 DMA channel
<&mdma1 0 0x3 0x1200000a 0 0>; // + STM32 MDMA channel
};
Concerning STM32 MDMA bindings:
1. The request line number : whatever the value here, it will be overwritten
by MDMA driver with the STM32 DMAMUX channel ID passed through
(struct dma_slave_config).peripheral_config
2. The priority level : choose Very High (0x3) so that your channel will
take priority other the other during request arbitration
3. A 32bit mask specifying the DMA channel configuration : source and
destination address increment, block transfer with 128 bytes per single
transfer
4. The 32bit value specifying the register to be used to acknowledge the
request: it will be overwritten by MDMA driver, with the DMA channel
interrupt flag clear register address passed through
(struct dma_slave_config).peripheral_config
5. The 32bit mask specifying the value to be written to acknowledge the
request: it will be overwritten by MDMA driver, with the DMA channel
Transfer Complete flag passed through
(struct dma_slave_config).peripheral_config
Driver updates for STM32 DMA-MDMA chaining support in foo driver
----------------------------------------------------------------
**0. (optional) Refactor the original sg_table if dmaengine_prep_slave_sg()**
In case of dmaengine_prep_slave_sg(), the original sg_table can't be used as
is. Two new sg_tables must be created from the original one. One for
STM32 DMA transfer (where memory address targets now the SRAM buffer instead
of DDR buffer) and one for STM32 MDMA transfer (where memory address targets
the DDR buffer).
The new sg_list items must fit SRAM period length. Here is an example for
DMA_DEV_TO_MEM:
::
/*
* Assuming sgl and nents, respectively the initial scatterlist and its
* length.
* Assuming sram_dma_buf and sram_period, respectively the memory
* allocated from the pool for DMA usage, and the length of the period,
* which is half of the sram_buf size.
*/
struct sg_table new_dma_sgt, new_mdma_sgt;
struct scatterlist *s, *_sgl;
dma_addr_t ddr_dma_buf;
u32 new_nents = 0, len;
int i;
/* Count the number of entries needed */
for_each_sg(sgl, s, nents, i)
if (sg_dma_len(s) > sram_period)
new_nents += DIV_ROUND_UP(sg_dma_len(s), sram_period);
else
new_nents++;
/* Create sg table for STM32 DMA channel */
ret = sg_alloc_table(&new_dma_sgt, new_nents, GFP_ATOMIC);
if (ret)
dev_err(dev, "DMA sg table alloc failed\n");
for_each_sg(new_dma_sgt.sgl, s, new_dma_sgt.nents, i) {
_sgl = sgl;
sg_dma_len(s) = min(sg_dma_len(_sgl), sram_period);
/* Targets the beginning = first half of the sram_buf */
s->dma_address = sram_buf;
/*
* Targets the second half of the sram_buf
* for odd indexes of the item of the sg_list
*/
if (i & 1)
s->dma_address += sram_period;
}
/* Create sg table for STM32 MDMA channel */
ret = sg_alloc_table(&new_mdma_sgt, new_nents, GFP_ATOMIC);
if (ret)
dev_err(dev, "MDMA sg_table alloc failed\n");
_sgl = sgl;
len = sg_dma_len(sgl);
ddr_dma_buf = sg_dma_address(sgl);
for_each_sg(mdma_sgt.sgl, s, mdma_sgt.nents, i) {
size_t bytes = min_t(size_t, len, sram_period);
sg_dma_len(s) = bytes;
sg_dma_address(s) = ddr_dma_buf;
len -= bytes;
if (!len && sg_next(_sgl)) {
_sgl = sg_next(_sgl);
len = sg_dma_len(_sgl);
ddr_dma_buf = sg_dma_address(_sgl);
} else {
ddr_dma_buf += bytes;
}
}
Don't forget to release these new sg_tables after getting the descriptors
with dmaengine_prep_slave_sg().
**1. Set controller specific parameters**
First, use dmaengine_slave_config() with a struct dma_slave_config to
configure STM32 DMA channel. You just have to take care of DMA addresses,
the memory address (depending on the transfer direction) must point on your
SRAM buffer, and set (struct dma_slave_config).peripheral_size != 0.
STM32 DMA driver will check (struct dma_slave_config).peripheral_size to
determine if chaining is being used or not. If it is used, then STM32 DMA
driver fills (struct dma_slave_config).peripheral_config with an array of
three u32 : the first one containing STM32 DMAMUX channel ID, the second one
the channel interrupt flag clear register address, and the third one the
channel Transfer Complete flag mask.
Then, use dmaengine_slave_config with another struct dma_slave_config to
configure STM32 MDMA channel. Take care of DMA addresses, the device address
(depending on the transfer direction) must point on your SRAM buffer, and
the memory address must point to the buffer originally used for "classic"
DMA operation. Use the previous (struct dma_slave_config).peripheral_size
and .peripheral_config that have been updated by STM32 DMA driver, to set
(struct dma_slave_config).peripheral_size and .peripheral_config of the
struct dma_slave_config to configure STM32 MDMA channel.
::
struct dma_slave_config dma_conf;
struct dma_slave_config mdma_conf;
memset(&dma_conf, 0, sizeof(dma_conf));
[...]
config.direction = DMA_DEV_TO_MEM;
config.dst_addr = sram_dma_buf; // SRAM buffer
config.peripheral_size = 1; // peripheral_size != 0 => chaining
dmaengine_slave_config(dma_chan, &dma_config);
memset(&mdma_conf, 0, sizeof(mdma_conf));
config.direction = DMA_DEV_TO_MEM;
mdma_conf.src_addr = sram_dma_buf; // SRAM buffer
mdma_conf.dst_addr = rx_dma_buf; // original memory buffer
mdma_conf.peripheral_size = dma_conf.peripheral_size; // <- dma_conf
mdma_conf.peripheral_config = dma_config.peripheral_config; // <- dma_conf
dmaengine_slave_config(mdma_chan, &mdma_conf);
**2. Get a descriptor for STM32 DMA channel transaction**
In the same way you get your descriptor for your "classic" DMA operation,
you just have to replace the original sg_list (in case of
dmaengine_prep_slave_sg()) with the new sg_list using SRAM buffer, or to
replace the original buffer address, length and period (in case of
dmaengine_prep_dma_cyclic()) with the new SRAM buffer.
**3. Get a descriptor for STM32 MDMA channel transaction**
If you previously get descriptor (for STM32 DMA) with
* dmaengine_prep_slave_sg(), then use dmaengine_prep_slave_sg() for
STM32 MDMA;
* dmaengine_prep_dma_cyclic(), then use dmaengine_prep_dma_cyclic() for
STM32 MDMA.
Use the new sg_list using SRAM buffer (in case of dmaengine_prep_slave_sg())
or, depending on the transfer direction, either the original DDR buffer (in
case of DMA_DEV_TO_MEM) or the SRAM buffer (in case of DMA_MEM_TO_DEV), the
source address being previously set with dmaengine_slave_config().
**4. Submit both transactions**
Before submitting your transactions, you may need to define on which
descriptor you want a callback to be called at the end of the transfer
(dmaengine_prep_slave_sg()) or the period (dmaengine_prep_dma_cyclic()).
Depending on the direction, set the callback on the descriptor that finishes
the overall transfer:
* DMA_DEV_TO_MEM: set the callback on the "MDMA" descriptor
* DMA_MEM_TO_DEV: set the callback on the "DMA" descriptor
Then, submit the descriptors whatever the order, with dmaengine_tx_submit().
**5. Issue pending requests (and wait for callback notification)**
As STM32 MDMA channel transfer is triggered by STM32 DMA, you must issue
STM32 MDMA channel before STM32 DMA channel.
If any, your callback will be called to warn you about the end of the overall
transfer or the period completion.
Don't forget to terminate both channels. STM32 DMA channel is configured in
cyclic Double-Buffer mode so it won't be disabled by HW, you need to terminate
it. STM32 MDMA channel will be stopped by HW in case of sg transfer, but not
in case of cyclic transfer. You can terminate it whatever the kind of transfer.
**STM32 DMA-MDMA chaining DMA_MEM_TO_DEV special case**
STM32 DMA-MDMA chaining in DMA_MEM_TO_DEV is a special case. Indeed, the
STM32 MDMA feeds the SRAM buffer with the DDR data, and the STM32 DMA reads
data from SRAM buffer. So some data (the first period) have to be copied in
SRAM buffer when the STM32 DMA starts to read.
A trick could be pausing the STM32 DMA channel (that will raise a Transfer
Complete signal, triggering the STM32 MDMA channel), but the first data read
by the STM32 DMA could be "wrong". The proper way is to prepare the first SRAM
period with dmaengine_prep_dma_memcpy(). Then this first period should be
"removed" from the sg or the cyclic transfer.
Due to this complexity, rather use the STM32 DMA-MDMA chaining for
DMA_DEV_TO_MEM and keep the "classic" DMA usage for DMA_MEM_TO_DEV, unless
you're not afraid.
Resources
---------
Application note, datasheet and reference manual are available on ST website
(STM32MP1_).
Dedicated focus on three application notes (AN5224_, AN4031_ & AN5001_)
dealing with STM32 DMAMUX, STM32 DMA and STM32 MDMA.
.. _STM32MP1: https://www.st.com/en/microcontrollers-microprocessors/stm32mp1-series.html
.. _AN5224: https://www.st.com/resource/en/application_note/an5224-stm32-dmamux-the-dma-request-router-stmicroelectronics.pdf
.. _AN4031: https://www.st.com/resource/en/application_note/dm00046011-using-the-stm32f2-stm32f4-and-stm32f7-series-dma-controller-stmicroelectronics.pdf
.. _AN5001: https://www.st.com/resource/en/application_note/an5001-stm32cube-expansion-package-for-stm32h7-series-mdma-stmicroelectronics.pdf
:Authors:
- Amelie Delaunay <[email protected]>
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
STM32 DMA-MDMA 체이닝
1-7이 문서는 `GPL-2.0` SPDX 라이선스 식별자를 사용하는 STM32 DMA-MDMA chaining 설명서입니다.
소개
8-20이 문서는 STM32 DMA-MDMA 체이닝 기능을 설명합니다. 먼저 이 기능에 관여하는 주변장치를 소개합니다.
STM32 마이크로프로세서(MPU)는 CPU에서 데이터 전송 작업을 덜어 내기 위해 직접 메모리 접근 컨트롤러(DMA)를 내장합니다.
STM32MP1 SoC에는 STM32 DMA와 STM32 MDMA 컨트롤러가 모두 들어 있습니다. STM32 DMA의 요청 라우팅 기능은 DMA request multiplexer인 STM32 DMAMUX가 확장합니다.
STM32 DMAMUX
21-25STM32 DMAMUX는 특정 주변장치에서 발생한 임의의 DMA 요청을 STM32 DMA 컨트롤러의 임의 채널로 라우팅합니다. STM32MP1에는 STM32 DMA 컨트롤러가 두 개 있습니다.
STM32 DMA
26-32STM32 DMA는 주로 여러 주변장치의 중앙 데이터 버퍼 저장소를 구현하는 데 사용하며, 이 버퍼는 보통 시스템 SRAM에 둡니다. 외부 RAM에도 접근할 수 있지만 AXI 부하를 최적화하는 적절한 burst transfer를 생성하는 능력은 없습니다.
STM32 MDMA
33-42STM32 MDMA(Master DMA)는 CPU 개입 없이 RAM 데이터 버퍼 사이를 직접 전송하는 데 주로 사용합니다.
계층형 구조에서는 STM32 DMA가 AHB 주변장치를 위한 1단계 데이터 버퍼 인터페이스를 맡고, 성능이 더 높은 STM32 MDMA가 2단계 DMA를 맡을 수 있습니다. AXI/AHB master인 STM32 MDMA는 AXI/AHB 버스의 제어권을 가져올 수 있습니다.
체이닝 원리
43-68STM32 DMA-MDMA 체이닝은 두 컨트롤러의 장점을 함께 사용합니다.
STM32 DMA에는 순환 Double Buffer Mode(DBM)가 있습니다. 각 트랜잭션이 끝나 `DMA_SxNDTR` 데이터 카운터가 0에 도달하면 `DMA_SxSM0AR`와 `DMA_SxM1AR`로 구성한 메모리 포인터를 서로 바꾸고 데이터 카운터를 자동으로 다시 적재합니다. 따라서 STM32 DMA가 한 메모리 영역을 채우거나 사용하는 동안 소프트웨어 또는 STM32 MDMA가 다른 영역을 처리할 수 있습니다.
STM32 MDMA linked-list mode에서는 요청 하나가 노드 모음인 데이터 배열의 전송을 시작하고, 채널의 linked-list pointer가 null이 될 때까지 계속합니다. 마지막 노드의 channel transfer complete가 전체 전송의 끝입니다. 첫 노드와 마지막 노드를 서로 연결하면 linked list가 반복되어 순환 MDMA 전송이 됩니다.
STM32 MDMA는 STM32 DMA와 직접 연결됩니다. 이 연결은 주변장치 사이의 통신과 동기화를 자율화하여 CPU 자원 사용과 버스 혼잡을 줄입니다. STM32 DMA 채널의 Transfer Complete 신호가 STM32 MDMA 전송을 트리거할 수 있습니다.
STM32 MDMA는 `MDMA_CxMAR`에 저장된 주소의 DMA Interrupt Clear register에 `MDMA_CxMDR`의 bit mask를 기록하여 STM32 DMA가 생성한 요청을 지울 수 있습니다.
STM32 DMA와 MDMA의 인터커넥트
69-109DMAMUX 채널 0~15는 DMA1·DMA2의 각 채널 Transfer Complete 신호와 MDMA 요청 0x00~0x0F에 일대일로 연결됩니다.
| STM32 DMAMUX channel | STM32 DMA channel | Transfer complete signal | STM32 MDMA request |
|---|---|---|---|
| Channel 0 | DMA1 channel 0 | `dma1_tcf0` | `0x00` |
| Channel 1 | DMA1 channel 1 | `dma1_tcf1` | `0x01` |
| Channel 2 | DMA1 channel 2 | `dma1_tcf2` | `0x02` |
| Channel 3 | DMA1 channel 3 | `dma1_tcf3` | `0x03` |
| Channel 4 | DMA1 channel 4 | `dma1_tcf4` | `0x04` |
| Channel 5 | DMA1 channel 5 | `dma1_tcf5` | `0x05` |
| Channel 6 | DMA1 channel 6 | `dma1_tcf6` | `0x06` |
| Channel 7 | DMA1 channel 7 | `dma1_tcf7` | `0x07` |
| Channel 8 | DMA2 channel 0 | `dma2_tcf0` | `0x08` |
| Channel 9 | DMA2 channel 1 | `dma2_tcf1` | `0x09` |
| Channel 10 | DMA2 channel 2 | `dma2_tcf2` | `0x0A` |
| Channel 11 | DMA2 channel 3 | `dma2_tcf3` | `0x0B` |
| Channel 12 | DMA2 channel 4 | `dma2_tcf4` | `0x0C` |
| Channel 13 | DMA2 channel 5 | `dma2_tcf5` | `0x0D` |
| Channel 14 | DMA2 channel 6 | `dma2_tcf6` | `0x0E` |
| Channel 15 | DMA2 channel 7 | `dma2_tcf7` | `0x0F` |
SRAM 이중 버퍼 파이프라인
110-127STM32 DMA-MDMA 체이닝은 SRAM 버퍼를 사용합니다. STM32MP1 SoC는 데이터 저장용으로 크기가 서로 다른 고속 내부 정적 RAM 세 개를 내장합니다.
마이크로컨트롤러에서 유래한 STM32 DMA는 DDR 성능이 좋지 않고 SRAM에서는 최적 성능을 냅니다. 이 때문에 STM32 DMA와 STM32 MDMA 사이에 SRAM 버퍼를 둡니다. 버퍼를 같은 크기의 period 두 개로 나누어 STM32 DMA와 STM32 MDMA가 서로 다른 period를 동시에 사용합니다.
DMA Transfer Complete 신호가 MDMA를 트리거하며 두 컨트롤러는 SRAM의 반대쪽 period를 번갈아 처리합니다.
MDMA 구성 매개변수 전달
128-136STM32 DMA-MDMA 체이닝은 `(struct dma_slave_config).peripheral_config`를 사용해 MDMA 구성에 필요한 매개변수를 교환합니다. 매개변수는 다음 세 값을 담은 `u32` 배열로 묶습니다.
- STM32 MDMA request. 실제 값은 DMAMUX channel ID입니다.
- Transfer Complete interrupt flag를 지울 STM32 DMA register의 주소입니다.
- STM32 DMA channel의 Transfer Complete interrupt flag mask입니다.
Device Tree에서 SRAM 버퍼 할당
137-170SoC Device Tree에 정의된 SRAM 노드를 보드 Device Tree에서 참조하여 전용 SRAM pool을 정의합니다.
&sram {
my_foo_device_dma_pool: dma-sram@0 {
reg = <0x0 0x1000>;
};
};
다른 SRAM 소비자가 있을 수 있으므로 시작 인덱스를 주의해야 합니다. STM32 DMA와 STM32 MDMA가 SRAM의 각 버퍼에서 동시에 작업할 수 있도록 pool 크기를 전략적으로 정하십시오.
SRAM period가 예상 DMA 전송보다 크면 두 컨트롤러가 동시에 움직이지 않고 순차적으로 동작합니다. 기능상 문제는 아니지만 최적 상태는 아닙니다.
장치 노드에서 SRAM pool을 참조하는 새 속성도 정의해야 합니다.
&my_foo_device {
...
my_dma_pool = &my_foo_device_dma_pool;
};
그런 다음 foo 드라이버에서 이 SRAM pool을 얻어 DMA용 SRAM 버퍼를 할당합니다.
Device Tree에서 DMA와 MDMA 채널 할당
171-209기존의 "classic" DMA 동작용 채널에 더해 장치 노드에 채널 하나를 추가해야 합니다. 새 채널은 STM32 MDMA 채널에서 가져오므로 사용할 DMA controller phandle은 MDMA 컨트롤러의 phandle입니다.
&my_foo_device {
[...]
my_dma_pool = &my_foo_device_dma_pool;
dmas = <&dmamux1 ...>, // STM32 DMA channel
<&mdma1 0 0x3 0x1200000a 0 0>; // + STM32 MDMA channel
};
STM32 MDMA binding의 셀은 다음 의미를 갖습니다.
- 1. Request line number: 여기 지정한 값과 관계없이 MDMA 드라이버가 `(struct dma_slave_config).peripheral_config`로 전달된 STM32 DMAMUX channel ID로 덮어씁니다.
- 2. Priority level: 요청 중재에서 다른 채널보다 우선하도록 Very High(`0x3`)를 선택합니다.
- 3. DMA channel configuration을 지정하는 32-bit mask: source·destination address increment와 단일 전송당 128 bytes인 block transfer를 지정합니다.
- 4. Request acknowledge에 사용할 register를 지정하는 32-bit 값: MDMA 드라이버가 `.peripheral_config`로 받은 DMA channel interrupt flag clear register 주소로 덮어씁니다.
- 5. Request acknowledge를 위해 기록할 값을 지정하는 32-bit mask: MDMA 드라이버가 `.peripheral_config`로 받은 DMA channel Transfer Complete flag로 덮어씁니다.
드라이버의 scatter-gather 표 재구성
210-289`dmaengine_prep_slave_sg()`를 사용한다면 원래 `sg_table`을 그대로 사용할 수 없습니다. 원본에서 새 `sg_table` 두 개를 만들어야 합니다. STM32 DMA용 표의 메모리 주소는 DDR 대신 SRAM 버퍼를 가리키고, STM32 MDMA용 표의 메모리 주소는 DDR 버퍼를 가리킵니다.
새 `sg_list` 항목은 SRAM period 길이에 맞아야 합니다. 다음은 `DMA_DEV_TO_MEM` 방향의 예제입니다. `sgl`과 `nents`는 원래 scatterlist와 그 길이이고, `sram_dma_buf`와 `sram_period`는 DMA용 pool에서 할당한 메모리 및 전체 `sram_buf`의 절반인 period 길이라고 가정합니다.
/*
* Assuming sgl and nents, respectively the initial scatterlist and its
* length.
* Assuming sram_dma_buf and sram_period, respectively the memory
* allocated from the pool for DMA usage, and the length of the period,
* which is half of the sram_buf size.
*/
struct sg_table new_dma_sgt, new_mdma_sgt;
struct scatterlist *s, *_sgl;
dma_addr_t ddr_dma_buf;
u32 new_nents = 0, len;
int i;
/* Count the number of entries needed */
for_each_sg(sgl, s, nents, i)
if (sg_dma_len(s) > sram_period)
new_nents += DIV_ROUND_UP(sg_dma_len(s), sram_period);
else
new_nents++;
/* Create sg table for STM32 DMA channel */
ret = sg_alloc_table(&new_dma_sgt, new_nents, GFP_ATOMIC);
if (ret)
dev_err(dev, "DMA sg table alloc failed\n");
for_each_sg(new_dma_sgt.sgl, s, new_dma_sgt.nents, i) {
_sgl = sgl;
sg_dma_len(s) = min(sg_dma_len(_sgl), sram_period);
/* Targets the beginning = first half of the sram_buf */
s->dma_address = sram_buf;
/*
* Targets the second half of the sram_buf
* for odd indexes of the item of the sg_list
*/
if (i & 1)
s->dma_address += sram_period;
}
/* Create sg table for STM32 MDMA channel */
ret = sg_alloc_table(&new_mdma_sgt, new_nents, GFP_ATOMIC);
if (ret)
dev_err(dev, "MDMA sg_table alloc failed\n");
_sgl = sgl;
len = sg_dma_len(sgl);
ddr_dma_buf = sg_dma_address(sgl);
for_each_sg(mdma_sgt.sgl, s, mdma_sgt.nents, i) {
size_t bytes = min_t(size_t, len, sram_period);
sg_dma_len(s) = bytes;
sg_dma_address(s) = ddr_dma_buf;
len -= bytes;
if (!len && sg_next(_sgl)) {
_sgl = sg_next(_sgl);
len = sg_dma_len(_sgl);
ddr_dma_buf = sg_dma_address(_sgl);
} else {
ddr_dma_buf += bytes;
}
}
`dmaengine_prep_slave_sg()`로 descriptor를 얻은 뒤에는 새로 만든 두 `sg_table`을 반드시 해제해야 합니다.
컨트롤러별 매개변수 구성
290-333먼저 `struct dma_slave_config`와 `dmaengine_slave_config()`로 STM32 DMA 채널을 구성합니다. 전송 방향에 따른 memory address가 SRAM 버퍼를 가리키게 하고 `(struct dma_slave_config).peripheral_size != 0`으로 설정합니다.
STM32 DMA 드라이버는 `.peripheral_size`를 확인해 체이닝 사용 여부를 판단합니다. 체이닝이면 `.peripheral_config`에 `u32` 세 개짜리 배열을 채웁니다. 순서대로 STM32 DMAMUX channel ID, channel interrupt flag clear register 주소, channel Transfer Complete flag mask입니다.
그 다음 별도의 `struct dma_slave_config`로 STM32 MDMA 채널을 구성합니다. 전송 방향에 따른 device address는 SRAM 버퍼를, memory address는 기존 "classic" DMA에서 사용하던 원본 버퍼를 가리켜야 합니다.
STM32 DMA 드라이버가 갱신한 이전 구성의 `.peripheral_size`와 `.peripheral_config`를 STM32 MDMA용 `struct dma_slave_config`의 같은 필드에 전달합니다. 아래 예제의 변수명과 필드는 원문 그대로 보존했습니다.
struct dma_slave_config dma_conf;
struct dma_slave_config mdma_conf;
memset(&dma_conf, 0, sizeof(dma_conf));
[...]
config.direction = DMA_DEV_TO_MEM;
config.dst_addr = sram_dma_buf; // SRAM buffer
config.peripheral_size = 1; // peripheral_size != 0 => chaining
dmaengine_slave_config(dma_chan, &dma_config);
memset(&mdma_conf, 0, sizeof(mdma_conf));
config.direction = DMA_DEV_TO_MEM;
mdma_conf.src_addr = sram_dma_buf; // SRAM buffer
mdma_conf.dst_addr = rx_dma_buf; // original memory buffer
mdma_conf.peripheral_size = dma_conf.peripheral_size; // <- dma_conf
mdma_conf.peripheral_config = dma_config.peripheral_config; // <- dma_conf
dmaengine_slave_config(mdma_chan, &mdma_conf);
STM32 DMA 트랜잭션 descriptor
334-341기존 "classic" DMA 동작과 같은 방식으로 STM32 DMA descriptor를 얻되 SRAM 버퍼를 사용하도록 입력을 바꿉니다. `dmaengine_prep_slave_sg()`에서는 원래 `sg_list`를 새 SRAM용 `sg_list`로 교체하고, `dmaengine_prep_dma_cyclic()`에서는 원래 buffer address, length, period를 새 SRAM 버퍼에 맞게 교체합니다.
STM32 MDMA 트랜잭션 descriptor
342-355STM32 DMA descriptor를 `dmaengine_prep_slave_sg()`로 준비했다면 STM32 MDMA에도 `dmaengine_prep_slave_sg()`를 사용하고, `dmaengine_prep_dma_cyclic()`을 사용했다면 MDMA에도 같은 cyclic API를 사용합니다.
Scatter-gather 방식에서는 SRAM 버퍼를 사용하는 새 `sg_list`를 사용합니다. Cyclic 방식은 전송 방향에 따라 `DMA_DEV_TO_MEM`이면 원래 DDR 버퍼를, `DMA_MEM_TO_DEV`이면 SRAM 버퍼를 사용합니다. Source address는 앞서 `dmaengine_slave_config()`로 설정합니다.
두 트랜잭션 제출과 callback
356-368트랜잭션을 제출하기 전에 전체 전송(`dmaengine_prep_slave_sg()`) 또는 period(`dmaengine_prep_dma_cyclic()`)가 끝날 때 어느 descriptor의 callback을 호출할지 정해야 할 수 있습니다. 전체 전송을 마지막으로 완료하는 descriptor에 callback을 둡니다.
- `DMA_DEV_TO_MEM`: "MDMA" descriptor에 callback을 설정합니다.
- `DMA_MEM_TO_DEV`: "DMA" descriptor에 callback을 설정합니다.
그 다음 순서와 관계없이 `dmaengine_tx_submit()`으로 두 descriptor를 제출합니다.
Pending 요청 시작과 종료 처리
369-381STM32 MDMA 채널 전송은 STM32 DMA가 트리거하므로 STM32 DMA 채널보다 STM32 MDMA 채널을 먼저 issue해야 합니다.
Callback이 있다면 전체 전송 종료 또는 period 완료 시 호출됩니다.
두 채널 모두 종료하는 것을 잊지 마십시오. STM32 DMA 채널은 순환 Double-Buffer mode이므로 하드웨어가 비활성화하지 않아 드라이버가 종료해야 합니다. STM32 MDMA 채널은 scatter-gather 전송이면 하드웨어가 멈추지만 cyclic 전송이면 멈추지 않습니다. 전송 종류와 관계없이 명시적으로 종료해도 됩니다.
DMA_MEM_TO_DEV 특수 사례
382-398`DMA_MEM_TO_DEV` 방향의 STM32 DMA-MDMA 체이닝은 특수 사례입니다. STM32 MDMA가 DDR 데이터를 SRAM 버퍼에 공급하고 STM32 DMA가 SRAM에서 읽기 때문에 STM32 DMA가 읽기 시작할 때 첫 period 데이터가 이미 SRAM에 있어야 합니다.
STM32 DMA 채널을 일시 정지하여 Transfer Complete 신호를 만들고 MDMA를 트리거하는 방법은 첫 DMA 읽기 데이터가 잘못될 수 있습니다. 올바른 방법은 `dmaengine_prep_dma_memcpy()`로 첫 SRAM period를 미리 준비한 뒤 이 첫 period를 scatter-gather 또는 cyclic 전송 범위에서 제거하는 것입니다.
이 복잡성 때문에 특별히 감수할 이유가 없다면 STM32 DMA-MDMA 체이닝은 `DMA_DEV_TO_MEM`에 사용하고 `DMA_MEM_TO_DEV`에는 "classic" DMA를 유지하는 편이 좋습니다.
자료와 작성자
399-415Application note, datasheet, reference manual은 ST의 STM32MP1 웹사이트에서 제공합니다. AN5224, AN4031, AN5001은 각각 STM32 DMAMUX, STM32 DMA, STM32 MDMA를 집중적으로 다룹니다.
- STM32MP1 series
https://www.st.com/en/microcontrollers-microprocessors/stm32mp1-series.html - AN5224 - STM32 DMAMUX
https://www.st.com/resource/en/application_note/an5224-stm32-dmamux-the-dma-request-router-stmicroelectronics.pdf - AN4031 - STM32 DMA
https://www.st.com/resource/en/application_note/dm00046011-using-the-stm32f2-stm32f4-and-stm32f7-series-dma-controller-stmicroelectronics.pdf - AN5001 - STM32 MDMA
https://www.st.com/resource/en/application_note/an5001-stm32cube-expansion-package-for-stm32h7-series-mdma-stmicroelectronics.pdf
작성자: Amelie Delaunay <[email protected]>
요약과 해설
stm32-dma-mdma-chaining.rst:1-415STM32 DMA는 주변장치와 고속 SRAM 사이의 1단계 전송을 맡고, STM32 MDMA는 SRAM과 DDR 사이의 2단계 전송을 맡습니다. DMA의 Double Buffer Mode와 MDMA linked list를 결합하면 한 SRAM period를 DMA가 사용하는 동안 다른 period를 MDMA가 처리해 CPU 개입과 AXI 버스 부하를 줄일 수 있습니다.
구현의 핵심은 DMAMUX channel ID와 DMA interrupt-clear 정보를 `.peripheral_config`로 MDMA에 전달하고, SRAM period 크기에 맞춘 descriptor 두 세트를 만든 뒤 MDMA를 DMA보다 먼저 시작하는 것입니다. `DMA_MEM_TO_DEV`는 첫 SRAM period를 미리 채워야 하므로 일반적으로 classic DMA가 더 단순합니다.
각 계층이 담당하는 주소 영역, 트리거, 동시 실행 조건을 한눈에 정리합니다.