2017. 3. 2. 20:51 devel/개념

Linux Kernel DMA

주의

작성자 컴맹임

2017. 03. 02 작성. 지속적으로 커널문서가 업데이트 되니 주의 바람

아 빨리 급해요!!!

  1. dma mask bit 설정 및 확인
  2. 목적에 맞는 타입으로 할당 요청 혹은 매핑 요청
  3. 다 썼으면 해제 혹은 언맵
    drivers/base/dma-mapping.c 에서 EXPORT_SYMBOL 된거 골라서 쓰면 된다.
    include/linux/dma-mapping.h에서 골라써도 되고

참조

kernel DMA 관련 문서들 + 뇌내망상 + 구글링 + kernel code
DMA-API-HOWTO.txt, DMA-API.txt 등등

근데 생각해보니까 이렇게 다 짬뽕으로 섞어서 써놓으면 커널 문서 업데이트 되었을 때 어떻게 하지?망했….

CPU and DMA addresses


kernel에서 kmalloc, vmalloc 들을 사용해서 메모리 할당하면 보통 void* 타입의 virtual address 가 할당 된다.

우리가 흔히 어딘가에서 들어본 적 있을법한 TLB나 page table 등등 이런 애들은 virtual memory system이라고 한다. (TLB에서 스크롤을 내렸습…… 잠깐 기다리라)

이들의 역할은 virtual addressCPU physical address로 변환한다.
이 주소는 phys_addr_t 혹은 resource_size_t 같은 데이터 타입을 사용한다

kernel은 이 physical address를 사용해서 장치를 관리한다.
이 주소들을 보고 싶으면 아래의 커맨드를 사용해보시라

cat /proc/iomem

physical address는 곧바로 사용할 수는 없고 ioremap 함수 같은 것으로 virtual address로 매핑을 해서 사용해야 한다.

그런데 I/O 장치들은 bus address라는걸 쓴다.

대부분 다르지만 몇몇 시스템들은 bus addressCPU physical address와 동일하다. IOMMU 와 host bridge가 위의 두 주소를 서로 매핑해준다.

I/O 장치 관점에서 DMA는 bus address space를 사용하지만 일부의 공간만 DMA가 된다. 예를 들어 메모리와 PCI BAR를 64bit 지원하는 시스템이더라도 IOMMU를 사용할 것이고 얘는 32bit DMA address만 사용할꺼다.

예를 들어 PCI 장치는 BAR라는 영역을 가지고 있다.

만약 kernel이 PCI 장치를 읽고 싶으면 BAR의 주소 bus address를 physical address 로 변환하고 그 다음 ioremap 같은 함수로 physical address를 virtual address로 바꾸어 접근을 하면 된다.
그리고 ioread32 같은 함수를 사용하여 bus address의 PCI device 레지스터에 접근할 수도 있다.

만약 장치가 DMA 지원을 한다면 다음과 같이 사용한다.
kmalloc 같은거로 virtual memory 할당을 받아서 kernel 내부 함수 도움을 받아(virtual memory system) physical address즉 시스템 램 주소로 변환을 한다.

일부는 physical address가 bus address와 동일하여 곧바로 DMA가 가능하지만 대부분의 경우 IOMMU가 DMA 주소로 변환을 해야 한다.
IOMMU 제어를 일반인이 다할 수 없으니 kernel이 제공하는 DMA API를 쓰도록.

Linux 는 dynamic DMA mapping 도 된다. 이게 뭔소린지 모르겠는데 실시간으로 kmalloc 할당해서 DMA하고 free로 제거하고 이런게 된다는 이야기인듯.

요약 #1

  • Kernel - virtual address space
  • Virtual Memory System(TLB, page table, 등) - virtual address space <-> physical address 변환
  • CPU - physical address space
  • IOMMU, host bridge - physical address <-> bus address 변환
  • I/O Device - bus address space
  • DMA - bus address space의 일부 영역만 가능

DMA 자격증

이미 알겠지만 아무거나 DMA가 되는게 아니다.
일단 DMA는 dma_addr_t 타입의 주소로 가능하다고 정했다.

page allocator를 통해 메모리를 할당 받은 경우
예 : __get_free_page, kmalloc, kmem_cache_alloc 된다

vmalloc을 통해 할당 받은 경우는 안된다.
핵심은 물리적으로 선형이 아니라서 그러는데 뭐라고 설명해야 할지 모르겠다 원문 참조 바람

This means specifically that you may _not_ use the memory/addresses
returned from vmalloc() for DMA.  It is possible to DMA to the
_underlying_ memory mapped into a vmalloc() area, but this requires
walking page tables to get the physical addresses, and then
translating each of those pages back to a kernel address using
something like __va().  [ EDIT: Update this when we integrate
Gerd Knorr's generic code which does this. ]

vmalloc 에서 dma가 가능하도록 scatter gathering 처럼 각각 physical address를 획득 한 다음 IOMMU로 선형주소 인 것 처럼 붙여서 bus address에 매핑한다는 건지 잘 모르겠다 작업중이라는거 보니 꽤 이런 유사한 요구가 많았나보다

물론 kernel image address 즉 data, text, bss segment들도 당연히 안된다. 이런걸 하려고 시도 한게 변태 아닌가? stack, module image address 역시 안된다.

DMA addressing limitations

기본으로 kernel은 32bit DMA를 다 다룰 수 있다고 가정한다. 만약 64bit 가능한 장비를 위해서는 증가시켜야 할 수도 있겠다만 장비 자체 스펙 한계로 일부 영역으로 줄여야 할 수도 있다.
말이 헷갈리는데 64bit kernel 시스템이라고 해서 64bit DMA가 되는게 아니다.

PCI-X 는 IO bus가 PCI-X mode로 운영되기 위해서 64bit addressing(DAC) 가 필요하다.
DMA controller에게 설정을 교류하는 방법들이 있다.

int dma_set_mask_and_coherent(struct device *dev, u64 mask);
int dma_set_mask(struct device *dev, u64 mask);
int dma_set_coherent_mask(struct device *dev, u64 mask);
//dev - device, mask - 너의 장비 몇 bit 지원하니?

이런 함수들은 보통 다음과 같이 사용된다
pdev, pci 등등 probe -> &pdev->device를 인자로 사용, mask 를 시스템 환경에 맞게 설정
걍 kernel 에서 샘플 코드 보는게 빠를듯

    if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) {
        dev_warn(dev, "mydev: No suitable DMA available\n");
        goto ignore_this_device;
    }

if success
return 0
else

/* 보통 실패하는 원인들 */
1. DMA mask 설정 잘못함
2. DMA mode 자체 지원 안 함
3. 무시하고 쓰지마! -> 진짜 이렇게 나와있음 -.-;;;

64bit는 다음과 같은 형식으로 접근해보란다.

64bit streaming DMA 예제

    int using_dac;

    if (!dma_set_mask(dev, DMA_BIT_MASK(64))) {
        using_dac = 1;
    } else if (!dma_set_mask(dev, DMA_BIT_MASK(32))) {
        using_dac = 0;
    } else {
        dev_warn(dev, "mydev: No suitable DMA available\n");
        goto ignore_this_device;
    }

64bit consistent allocation DMA 예제

    int using_dac, consistent_using_dac;

    if (!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) {
        using_dac = 1;
        consistent_using_dac = 1;
    } else if (!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) {
        using_dac = 0;
        consistent_using_dac = 0;
    } else {
        dev_warn(dev, "mydev: No suitable DMA available\n");
        goto ignore_this_device;
    }

24bit DMA 예제

    if (dma_set_mask(dev, DMA_BIT_MASK(24))) {
        dev_warn(dev, "mydev: 24-bit DMA addressing not available\n");
        goto ignore_this_device;
    }

Types of DMA mappings

A. Consistent DMA mapping

Think of “consistent” as “synchronous” or “coherent”.
일반적으로 드라이버의 초기화에 매핑되고 끝날 때 언맵 된다. 이를 위해 하드웨어는 반드시 다음과 같은 것들을 보장해야 한다. device와 CPU는 data를 병렬로 access할 수 있어야 하고 둘 다 어떠한 명시적 software flushing 없이 데이터 업데이트를 확인할 수 있어야 한다.
(예를 들면 linux의 sync 명령어 같은 처리를 안 해도 되도록)

Consistent DMA mappings which are usually mapped at driver initialization, unmapped at the end and for which the hardware should   guarantee that the device and the CPU can access the data in parallel and will see updates made by each other without any explicit software flushing.

현재 기본은 DMA space의 하위 32bit consistent memory를 return 하는 것이다.
그러나 미래 호환성을 위해서 지금 잘 동작하더라도 consistent mask를 설정 해 두도록.
머라는건지……64bit 체크루틴 넣어두라고?

 The current default is to return consistent memory in the low 32
  bits of the DMA space.  However, for future compatibility you should
  set the consistent mask even if this default is fine for your
  driver.

Good examples of what to use consistent mappings for are:

- Network card DMA ring descriptors.
- SCSI adapter mailbox command data structures.
- Device firmware microcode executed out of main memory.

cpu가 명령어를 reorder 할 수 있으니까 순서가 중요한 코드의 경우 memory barrier를 잘 써라 이런 경고 인거 같음.

IMPORTANT: Consistent DMA memory does not preclude the usage of
proper memory barriers.  The CPU may reorder stores to consistent memory just as it may normal memory.  

Example: if it is important for the device to see the first word
of a descriptor updated before the second, you must do something like:

    desc->word0 = address;
    wmb();
    desc->word1 = DESC_VALID;

in order to get correct behavior on all platforms.

Also, on some platforms your driver may need to flush CPU write
buffers in much the same way as it needs to flush write buffers
found in PCI bridges (such as by reading a register's value
after writing it).

B. Streaming DMA mapping

Think of “streaming” as “asynchronous” or “outside the coherency domain”.
1회의 DMA 전송을 위해 일반적으로 사용된다는데? 사용 끝나면 언맵하고
하드웨어는 sequential access를 위해 최적화 할 수 있다(뭘 최적화???)

Good examples of what to use streaming mappings for are:

- Networking buffers transmitted/received by a device.
- Filesystem buffers written/read by a SCSI device.

이 매핑타입의 인터페이스의 경우 하드웨어에 하고 싶은거 다 해서 성능을 높일 수 있다.
이런 내용인거 같다

  The interfaces for using this type of mapping were designed in
  such a way that an implementation can make whatever performance
  optimizations the hardware allows.  To this end, when using
  such mappings you must be explicit about what you want to happen.

위의 두 DMA 매핑 타입들은 bus로부터 오는 alignment 제한은 없다. 물론 있는 모델도 있지만 말야.
또한 buffer가 cache line을 다른 데이터와 공유하지 않을 때 시스템과 DMA-coherent 하지 않은 cache는 더 일 잘할꺼임.

Also, systems with caches that aren't DMA-coherent will work better
when the underlying buffers don't share cache lines with other data.

뭔 소린지 잘 모르겠는데 예를 들어 네트워크 카드로부터 트래픽 데이터가 시스템 메모리로 들어오는데 cache에 계속 등록된다면 되려 system 전체 퍼포먼스에 악영향을 줄 수도 있다 이런 맥락으로 보면 되려나? 왜냐면 그 데이터는 cache update하는 의미가 없으니까
그래서 저래 써놓은건가 싶다.

이 링크를 참조해본다
http://junbenchmarking.tistory.com/entry/Linux-%EC%BB%A4%EB%84%90%EC%97%90%EC%84%9C-CPU-%EC%99%80-DMA-%EC%82%AC%EC%9D%B4

DMA API

먼저 DMA API를 사용하고 싶다면 이것부터 하시라

#include <linux/dma-mapping.h>

mask 설정 체크 하는 것은 윗쪽의 DMA addressing limitations 항목을 참고

Consistent DMA mapping

할당

void *
dma_alloc_coherent(struct device *dev, size_t size,
                 dma_addr_t *dma_handle, gfp_t flag)
void *
dma_zalloc_coherent(struct device *dev, size_t size,
                 dma_addr_t *dma_handle, gfp_t flag)

사용 예시

dma_addr_t    dma_handle;
cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, gfp);

이 함수는 RAM을 Bytes size만큼 consistent memory를 할당한다. __get_free_pages() 랑 비슷하다.

arguments

  • dev - 생략
  • size - Bytes 단위. 할당 받고 싶은 크기
  • dma_handle - 아래 return value에서 확인 바람.
  • flag - kmalloc 호출할 때 처럼 사용하는 flag. 심지어 동일하다.

return value

  • virtual address - 할당된 메모리 주소, CPU와 dma_handle을 통해 access할 수 있다
  • NULL - 뭐긴 뭐야 할당에 실패한거지**
  • dma_handle - 이 인자에는 device의 bus width와 동일한 unsigned int형이 type casting 된 DMA address가 전달된다.

CPU virtual address와 DMA address는 요구된 메모리 사이즈에 맞는 최소한의 PAGE_SIZE order 에 align되도록 보장한다.

예를 들어 63KB를 요청해도 PAGE_SIZE order에 align맞춰서 64KB가 할당 된다 이 소리인듯

만약에 page_size 보다 작은 크기가 필요하다면 dma_poll 인터페이스를 사용하시라.
일반적으로 할당에 성공하면 32bit virtual address가 return 된다. dma_pool 역시 마찬가지

몇몇의 플랫폼에서 consistent memory는 굉장히 비쌀 수 있다. 그리고 가장 작은 할당 크기도 PAGE_SIZE 만큼 클 수 있다. 그래서 너 반드시 한 번에 퉁쳐서 요청해야 한다(오역?).

/* you should consolidate your requests for consistent memory as much as possible. */

먼소린지 잘 모르겠는데 1KB 요청해도 PAGE_SIZE로 나올 수 있으니 필요한 자료형 합쳐서 한 번에 크게 할당 받으라는 소리 같은데?

그리고 이 함수는 cache effect를 걱정할 필요 없다. 다시말해 dma로 데이터가 들어왔을 때 cpu의 cache에 이전 데이터가 있지 않을까? 하는 걱정을 할 필요 없다.
단 device에 dma로 write할 때 memory와 cpu cache의 동기화를 위해 flush를 해야할 수도 있음에 유의하라.(아까는 걱정하지 말라매!)

해제

void
dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,
               dma_addr_t dma_handle)

별건 없구요 cpu_addr은 할당 받은 virtual address를 넣어야 한다.

Streaming Mapping

할당 및 해제 #1

dma_addr_t
dma_map_single(struct device *dev, void *cpu_addr, size_t size,
              enum dma_data_direction direction)

void
dma_unmap_single(struct device *dev, dma_addr_t dma_addr, 
                 size_t size, enum dma_data_direction direction)

dma_alloc_coherent와 다르게 이 함수는 메모리를 미리 kmalloc 같은거로 알아서 할당을 시킨 상태로 호출해야 한다. 그러면 DMA 주소가 리턴 된다.

arguments

  • dev - 생략
  • cpu_addr - 매핑할 메모리 주소. kmalloc 처럼 physically contiguous 해야 한다. vmalloc이나 scatter/gather 지원 불가.
  • size - 생략

DMA direction

  • DMA_BIDIRECTIONAL - direction isn’t known
  • DMA_TO_DEVICE - from: main memory. to: device
  • DMA_FROM_DEVICE - from: device. to: main memory
  • DMA_NONE - for debugging

streaming mapping만이 direction을 특정화 한다.
consistent mapping은 DMA_BIDIRECTION을 암묵적으로 사용하도록 한다.
direction을 명시화 하여 플랫폼 로우레벨 코드에서 최적화를 할 수 있다는 것 같다.

메모리의 DMA 주소는 반드시 device의 dma_mask이내여야 한다.
무슨 말인고 하니 우리가 cpu_addr로 virtual memory address를 넘겨야 하니까 그 메모리 주소를 미리 잘 device의 특징에 맞게 준비 해놓으라는 말이다.

예를 들면 x86 시스템에서 너가 ISA device 장치를 쓰고 싶어. 그러면 kmalloc으로 메모리 할당할 때 GFP_DMA flag를 써서 처음 16MB 이내 주소의 메모리를 할당받은 다음에 매핑을 요청해! 이런식으로 dma_map_single 호출 하기 전 virtual memory를 할당 받아둘 때 반드시 flag를 잘 설정해서 physical address가 device 스펙에 맞도록 준비를 잘 하자.

또한 위에서 말한 physical contiguity 와 dma_mask 제한은 플랫폼이 IOMMU를 가지고 있다면 적용이 안될 수도 있음

cache cohereny 관련 문제

주의: Memory coherency는 cache line 폭(width) 이라고 불리는 granularity에 의해 동작한다. 이 API에 매핑된 메모리를 올바르게 동작시키기 위해서는 매핑된 영역은 반드시 하나의 cache line boundary에 맞춰 시작해야하고 정확히 끝나야 한다. (하나의 캐시라인에 두개의 독립된 영역이 매핑되는 것을 막기 위해) 근데 문제가 컴파일 타임에는 cache line크기를 알 수 없기 때문에 API는 이 요건 사항을 강제 못한다. 그러므로 cache line 사이즈고뭐고 고려 안 하는 드라이버 개발자는 걍 page boundary에서 시작하고 끝나는 virtual memory 영역만 매핑하는걸 추천한다.

정확하게 이해가 안 가는데 만약에 ioremap등을 통해 물리적으로 선형인 메모리 공간을 얻었는데 이 공간을 2KB로 나누어서 map을 요청하는 변태짓을 했다고 가정하면 cache line width가 4KB라고 가정했을 때 다른 메모리 2개 내용이 한 번에 캐시 되기 때문에 바보된다고 하지 말라는거 같은데??

주의 사항 - direction flag

  • DMA_TO_DEVICE - 메모리에 쓸꺼 다 쓰고 sync 완료 한 뒤 device에 DMA 해야 함. 그 뒤 device에게 메모리는 마치 read-only 인 것 처럼 취급 당한다. 만약에 device가 메모리에 write할 일이 있으면 반드시 DMA_BIDIRECTION으로 설정하도록!
  • DMA_FROM_DEVICE - 반대로 driver가 data에 access해서 읽기 전, 혹은 다 읽어 간 다음 sync 해야 한다. driver입장에서 메모리는 read-only 로 간주된다.
  • DMA_BIDIRECTIONAL - 특별 관리 요망. driver는 device에서 데이터가 발송 된 이후 수정될지 아닐지 쓸지 머할지 확신할 수 없을 때 사용. 그렇기에 항상 device와 메모리 사이에 각각 2회 sync를 해줘야 한다. A. 메모리에서 device로 데이터 발송 되었을 때 sync(확실히 하기 위해 모든 메모리의 변경 사항이 CPU에서도 flush 되야 한다. cache 문제 때문인거 같음.), B. device에서 메모리로 데이터 발송 되었을 때 반드시 sync요망(확실히 하기 위해 device에서 변화가 감지되면 어떤 CPU cache line들이라도 데이터가 업뎃 되야 한다).

걍 원문 보는게 이해가 빠를지도

DMA_BIDIRECTIONAL requires special handling: it means that the driver
isn't sure if the memory was modified before being handed off to the
device and also isn't sure if the device will also modify it.  Thus,
you must always sync bidirectional memory twice: once before the
memory is handed off to the device (to make sure all memory changes
are flushed from the processor) and once before the data may be
accessed after being used by the device (to make sure any processor
cache lines are updated with data that the device may have changed).

streaming mapping 예제

To map a single region, you do:

struct device *dev = &my_dev->dev;
dma_addr_t dma_handle;
void *addr = buffer->ptr;
size_t size = buffer->len;

dma_handle = dma_map_single(dev, addr, size, direction);
if (dma_mapping_error(dev, dma_handle)) {
    /*
     * reduce current DMA mapping usage,
     * delay and try again later or
     * reset driver.
     */
    goto map_error_handling;
}

and to unmap it:

dma_unmap_single(dev, dma_handle, size, direction);

dma_mapping_error 함수 안 쓰고 직접 주소값 비교하거나 errno 비교 같은짓 하지 마라

위 예제 처럼 있는 그대로 잘 쓰도록.

할당 및 해제 #2

dma_addr_t
dma_map_page(struct device *dev, struct page *page,
            unsigned long offset, size_t size,
            enum dma_data_direction direction)

void
dma_unmap_page(struct device *dev, dma_addr_t dma_address, 
               size_t size, enum dma_data_direction direction)

page를 직접 연결 시킬 수도 있다.

그런데 offset이랑 size 는 각 page 매핑에서 사용되는 파라메터인데 cache width를 잘 모르겠다 싶으면 걍 이 함수를 쓰지 마

할당 및 해제 #3

dma_addr_t
dma_map_resource(struct device *dev, phys_addr_t phys_addr, 
                 size_t size, enum dma_data_direction dir, unsigned long attrs)

void
dma_unmap_resource(struct device *dev, dma_addr_t addr, size_t size,
           enum dma_data_direction dir, unsigned long attrs)

MMIO device를 다룰 때 쓰시라. RAM은 매핑하면 안된다. 이 말이 바로 이해가 안간다면 MMIO에 대해서 검색 ㄱㄱ

page를 직접 지정할 수도 있다.

다만 HIGHMEM을 가지고는 할 수 없다는데 어차피 64bit에서 돌릴꺼면 신경쓰지 말자

struct device *dev = &my_dev->dev;
dma_addr_t dma_handle;
struct page *page = buffer->page;
unsigned long offset = buffer->offset;
size_t size = buffer->len;

dma_handle = dma_map_page(dev, page, offset, size, direction);
if (dma_mapping_error(dev, dma_handle)) {
    /*
     * reduce current DMA mapping usage,
     * delay and try again later or
     * reset driver.
     */
    goto map_error_handling;
}

...

dma_unmap_page(dev, dma_handle, size, direction);

예제

my_card_setup_receive_buffer(struct my_card *cp, char *buffer, int len)
{
    dma_addr_t mapping;

    mapping = dma_map_single(cp->dev, buffer, len, DMA_FROM_DEVICE);
    if (dma_mapping_error(cp->dev, mapping)) {
        /*
         * reduce current DMA mapping usage,
         * delay and try again later or
         * reset driver.
         */
        goto map_error_handling;
    }

    cp->rx_buf = buffer;
    cp->rx_len = len;
    cp->rx_dma = mapping;

    give_rx_buf_to_card(cp);
}

...

my_card_interrupt_handler(int irq, void *devid, struct pt_regs *regs)
{
    struct my_card *cp = devid;

    ...
    if (read_card_status(cp) == RX_BUF_TRANSFERRED) {
        struct my_card_header *hp;

        /* Examine the header to see if we wish
         * to accept the data.  But synchronize
         * the DMA transfer with the CPU first
         * so that we see updated contents.
         */
        dma_sync_single_for_cpu(&cp->dev, cp->rx_dma,
                    cp->rx_len,
                    DMA_FROM_DEVICE);

        /* Now it is safe to examine the buffer. */
        hp = (struct my_card_header *) cp->rx_buf;
        if (header_is_ok(hp)) {
            dma_unmap_single(&cp->dev, cp->rx_dma, cp->rx_len,
                     DMA_FROM_DEVICE);
            pass_to_upper_layers(cp->rx_buf);
            make_and_setup_new_rx_buf(cp);
        } else {
            /* CPU should not write to
             * DMA_FROM_DEVICE-mapped area,
             * so dma_sync_single_for_device() is
             * not needed here. It would be required
             * for DMA_BIDIRECTIONAL mapping if
             * the memory was modified.
             */
            give_rx_buf_to_card(cp);
        }
    }
}

별건 아니고 dma buffer를 setup하고 packet이 들어와서 interrupt가 뜨면 먼저

Driver에서 더이상 virt_to_bus()나 bus_to_virt() 같은 api를 쓰면 안된다.
더이상 bus_to_virt() 같은 dynamic DMA mapping scheme은 없단다.
반드시 dma_alloc_coherent(), dma_pool_alloc(), dma_map_single() 같은 함수를 써야 한단다. (dma_map_sg()는 하드웨어로 다이나믹 매핑 해주는거고)

Here, “offset” means byte offset within the given page.

스캇…리스트……..가 아니라 scatterlists 기법도 있다.

할당 및 해제 #4 - scatter/gathering

int
dma_map_sg(struct device *dev, struct scatterlist *sg,
        int nents, enum dma_data_direction direction)

void
dma_unmap_sg(struct device *dev, struct scatterlist *sg,
        int nents, enum dma_data_direction direction)

arguments

  • dev - 생략

  • sg - sg_init_table을 통해 할당하고 set_set_page 혹은 set_set_buf를 통해 설정한 sg list 변수로 추정된다. 이 부분은 별도 항목으로 굉장히 길다.

  • nents - 필요한 sglist 엔트리의 수(the number of entries in the sglist.)

  • direction - 생략

return value

정상 : 매핑된 DMA address segments 수

실패 : 0

주의 : 만약 sg list의 몇몇 entry가 물리적으로 혹은 virtually하게 인접해있고 IOMMU가 그들을 하나의 entry로 매핑한다면 인자로 넣은 nents 값 보다 적은 값이 리턴될 수 있다.

sg를 중복해서 매핑하는 변태짓 하지 마라. 이전 매핑 내용 파괴된다.

예제 - scatter/gathering

With scatterlists, you map a region gathered from several regions by:

int i, count = dma_map_sg(dev, sglist, nents, direction);
struct scatterlist *sg;

for_each_sg(sglist, sg, count, i) {
    hw_address[i] = sg_dma_address(sg);
    hw_len[i] = sg_dma_len(sg);
}

혹은 sg_next 같은 API도 있다

lib/scatterlist.c 참고

매우 복잡 예제 - drivers/spi/spi.c

  1. vmalloc인지 kmap을 사용한 제품인지 메모리 종류를 확인한다.
  2. sg_alloc_table을 호출해서 적당히 sg table을 할당 받는다.
  3. 메모리를 page로 변환(vmalloc_to_page 혹은 kmap_to_page)
  4. sg_set_page, sg_set_buf로 sg table의 sg list 설정을 한다
  5. dma_map_sg
  6. 하 시밤 개 복잡

SYNC

void
dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, 
                        size_t size, 
                        enum dma_data_direction direction)
void
dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle, 
                           size_t size, 
                           enum dma_data_direction direction)
void
dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, 
                    int nents, enum dma_data_direction direction)
void
dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
                       int nents, enum dma_data_direction direction)

연속적인 싱글 혹은 sg 매핑을 sync 한다. 파라메터는 single 매핑 api 호출했을 때와 동일 해야 한다. 그리고 dma_handle과 size 파라메터를 사용할 수 있는데 부분 sync를 위해 다르게 사용해도 ok

DMA ATTR

gg……

DMA-attributes.txt 를 직접 확인하기 바란다. 특수한 경우에 사용한다.

DMA_ATTR_NO_KERNEL_MAPPING, DMA_ATTR_NON_CONSISTENT 등등 희안한게 많다.

Handling Errors

Example 1:
    dma_addr_t dma_handle1;
    dma_addr_t dma_handle2;

    dma_handle1 = dma_map_single(dev, addr, size, direction);
    if (dma_mapping_error(dev, dma_handle1)) {
        /*
         * reduce current DMA mapping usage,
         * delay and try again later or
         * reset driver.
         */
        goto map_error_handling1;
    }
    dma_handle2 = dma_map_single(dev, addr, size, direction);
    if (dma_mapping_error(dev, dma_handle2)) {
        /*
         * reduce current DMA mapping usage,
         * delay and try again later or
         * reset driver.
         */
        goto map_error_handling2;
    }

    ...

    map_error_handling2:
        dma_unmap_single(dma_handle1);
    map_error_handling1:
Example 2: (if buffers are allocated in a loop, unmap all mapped buffers when mapping error is detected in the middle)
    dma_addr_t dma_addr;
    dma_addr_t array[DMA_BUFFERS];
    int save_index = 0;

    for (i = 0; i < DMA_BUFFERS; i++) {

        ...

        dma_addr = dma_map_single(dev, addr, size, direction);
        if (dma_mapping_error(dev, dma_addr)) {
            /*
             * reduce current DMA mapping usage,
             * delay and try again later or
             * reset driver.
             */
            goto map_error_handling;
        }
        array[i].dma_addr = dma_addr;
        save_index++;
    }

    ...

    map_error_handling:

    for (i = 0; i < save_index; i++) {

        ...

        dma_unmap_single(array[i].dma_addr);
    }

별건 아니고 이런 형식으로 작성하면 좋다. 에러났을 때 이전까지 매핑했던걸 해제하거라.

Platform Issues

1. Struct scatterlist Requirements

scatterlist 즉 CONFIG_NEED_SG_DMA_LENGTH는 IOMMU가 지원해야 한다.

2. ARCH_DMA_MINALIGN

Architecture는 반드시 kmalloc으로 할당한 buffer는 DMA-safe하도록 확증해야 한다

우리는 할 수 없지만 일단 이렇게 알고 있자. kmalloc 할당된 메모리는 다 DMA 되는구나!

만약 fully DMA-coherent 하지 않는다면. 예를 들어 CPU cache와 main memory의 데이터가 같다고 Architecture가 보증하지 않는 시스템의 경우 ARCH_DMA_MINALIGN 가 반드시 설정 되어야 한다

이 설정을 하면 kmalloc으로 할당한 메모리는 cache line을 다른 것들과 공유하지 않는다.

예제는 arch/arm/include/asm/cache.h 에서 확인 바란다

ARCH_DMA_MINALIGN은 DMA 메모리 alignment constraints에 관한 것이다

일반적인 data alignment constraints에 대해 걱정 ㄴㄴ해

뭐라는건지 모르겠다

cache.h 주석 보면 kmalloc에 의해 할당된 메모리가 DMA에 사용될 수 있으니 우리는 cache aligned 되도록 모든 할당을 맞춰주겠노라. 반면에 관련 되어있지 않은 코드는 DMA 전송이 끝나기 전 cache에 있는 오래된 데이터를 읽어가는 문제를 일으킬 수 있다.

할당하는 메모리 크기를 cache align 맞추는거랑 cache update랑 먼 상관일까?


Posted by 쵸코케키

블로그 이미지
chocokeki
쵸코케키

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.4
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

최근에 올라온 글

최근에 달린 댓글

글 보관함