LINUX-DMA-子系统

目录

1. DMA子系统概述

2. DMA子系统软件框架

2.1 DMA子系统内部实现

3. DMA 地址 

4. DMA类型

4.1.  Coherent DMA

4.1.1 Coherent DMA使用

4.2.  Streaming DMA

4.2.1. Streaming DMA使用

4.2.2. scatterlist streaming DMA

4.2.2. streaming DMA sync cache

5. DMA方向

6. DMA API

6.1. dma_alloc_coherent

6.2. dma_pool*

6.3 dma_set_mask*

6.4 dma_map_single

6.6 dma_map_sg

6.7 dma_map_page

6.8 dma_sync_single_for*

6.9 dma_request_chan

6.10 dmaengine_slave_config

2.11 dmaengine_prep_slave_sg

6.12 dmaengine_prep_dma_cyclic

6.13 dmaengine_prep_interleaved_dma

6.14 dmaengine_submit

6.15 dma_async_issue_pending

6.16 dmaengine_terminate*

6.17 dmaengine_pause

6.18 dmaengine_resume

6.19 dma_async_is_tx_complete

7. DMA engine usage


1. DMA子系统概述

DMA子系统为不同的DMA控制器提供统一的注册接口,为DMA使用者提供屏蔽DMA控制器实现细节的统一接口。

2. DMA子系统软件框架

DMA子系统由四大部分构成:

1. dmaengine: 实现DMA子系统的DMA设备注册,为DMA使用者提供屏蔽DMA控制器实现细节的统一接口。

2. virt-dma:为DMA子系统提供虚拟DMA channel的支持

3. of-dma:为DMA子系统提供DMA 设备树的支持

4. dma driver:为DMA控制器的实现

2.1 DMA子系统内部实现

 以全志平台为例。

DMA内部主要数据结构已经流出如上图所示。

3. DMA 地址 

              CPU                  CPU                  Bus
             Virtual              Physical             Address
             Address              Address               Space
              Space                Space

            +-------+             +------+             +------+
            |       |             |MMIO  |   Offset    |      |
            |       |  Virtual    |Space |   applied   |      |
          C +-------+ --------> B +------+ ----------> +------+ A
            |       |  mapping    |      |   by host   |      |
  +-----+   |       |             |      |   bridge    |      |   +--------+
  |     |   |       |             +------+             |      |   |        |
  | CPU |   |       |             | RAM  |             |      |   | Device |
  |     |   |       |             |      |             |      |   |        |
  +-----+   +-------+             +------+             +------+   +--------+
            |       |  Virtual    |Buffer|   Mapping   |      |
          X +-------+ --------> Y +------+ <---------- +------+ Z
            |       |  mapping    | RAM  |   by IOMMU
            |       |             |      |
            |       |             |      |
            +-------+             +------+

DMA中会涉及到几类型的地址:

1. 虚拟地址:内核通常使用的地址就是虚拟地址,大部分是由kmalloc、vmalloc以及类似的接口申请并返回。

2. 物理地址:DRAM空间的地址

3. 总线地址:系统mapping出来的地址空间

如上图中

A-->B-->C的过程表示设备寄存器的访问过程

X-->Y-->Z的过程表示DMA访问设备FIFO的过程

在某些系统(U-boot)DMA直接访问物理地址,但是在许多系统,是通过 IOMMU来实现物理地址的转换(Z-->Y)。

例如:通过提供虚拟地址X给dma_map_single(),dma_map_single()通过该IOMMU映射以及返回DMA 可以访问的总线地址Z,以及IOMMU映射Z到Y(系统RAM,例如DRAM空间地址)

Tips:

  1. 1). 在Linux系统中虚拟地址通常存储在void *,物理地址通常存储在phys_addr_t或者resource_size_t。

    2). 在某些平台上总线地址=物理地址,但通常不这么做,IOMMUs和主机网桥可以在物理地址和总线地址之间生成任意映射

    3). 总线地址A通过内部桥转换为CPU物理地址B,Ioremap()实现物理地址B到虚拟地址C的映射,然后可以ioread32(C)来访问总线地址A

    4).DMA地址应该使用dma_addr_t类型

4. DMA类型

这里说的DMA类型是从DMA地址的角度出发的,分为两类:

1. Coherent(consistent) DMA(一致性DMA)

2. Streaming DMA(流式DMA)

4.1.  Coherent DMA

Coherent DMA在访问DDR的时候不经过cpu cache。

Coherent DMA确保设备和CPU可以并行访问数据,并在不进行任何显式软件刷新的情况下看到彼此进行的更新

Tips: 

  1. 1). 通常在驱动初始化时候map,在卸载或者结束的时候unmap
  2. 2). 如果要确保设备按顺序看到你的配置可以使用内存屏障来保证内存排序:
  3. e.g.
  4.         
    desc->word0 = address;
    wmb(); 
    desc->word1 = DESC_VALID;

    3). 在某些平台上,驱动程序可能需要刷新CPU写缓冲区(跟CPU架构有关,ARM cortex_a53 没有write buffer, ARMv9有write buffer),其方式与刷新PCI桥中的写缓冲区的方式大致相同

  5. 4.1.1 Coherent DMA使用

  6. 1). 如果申请和映射的Coherent DMA域大于PAGE_SIZE(e.g. 4KB)使用如下接口:
  7.  dma_alloc_coherent。

  8. e.g. 

  9. /*a. alloc coherent */
    dma_addr_t dma_handle;
    cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, gfp); 
    
    @cpu_addr:CPU角度访问的虚拟地址,按PAGE_SIZE对齐,意味着大于等于申请大小
    @dma_handle: DMA角度访问的物理地址,按PAGE_SIZE对齐,意味着大于等于申请大小
    
    
    /*b. free coherent*/
    dma_free_coherent(dev, size, cpu_addr, dma_handle);
    

    注意:

    1.         alloc出来的coherent dma域按PAGE_SIZE对齐。

    2. 2). 如果申请和映射的Coherent DMA域小于PAGE_SIZE(e.g. 4KB)使用如下接口:

    3. /*dma pool*/
      /*a.Create a dma_pool*/
      struct dma_pool *pool = dma_pool_create(name, dev, size, align, boundary);  
      @size:必须对齐2
      
      /*b.Allocate memory from a DMA pool*/
      cpu_addr = dma_pool_alloc(pool, flags, &dma_handle)
      @ flag:GFP_KERNEL
      
      /*c.Free memory*/
      dma_pool_free(pool, cpu_addr, dma_handle);
      
      /*Destroy a dma_pool*/
      dma_pool_destroy(pool)
      调用之前先释放所有在dma pool中申请的内存

  10. 4.2.  Streaming DMA

  11. Streaming DMA在访问DDR的时候经cpu 过cache。

  12. Streaming DMA流式DMA通常在某一次的传输中使用,使用之后取消。

  13. 4.2.1. Streaming DMA使用

有两类型的streaming DMA映射:single、scatterlist

1). Single streaming DMA

不使用HIGHMEM:

/*a. map a single region*/
dma_handle = dma_map_single(dev, addr, size, direction)
@dma_handle:DMA操作的物理地址
@addr:需要map的虚拟地址

/*b. unmap, DMA传输结束调用*/
dma_unmap_single(dev, dma_handle, size, direction);
  1. 使用HIGHMEM:

  2. dma_handle = dma_map_page(dev, page, offset, size, direction);
    @offset: offset within the given page (unit by Byte)
    
    
    
    dma_unmap_page(dev, dma_handle, size, direction);

    4.2.2. scatterlist streaming DMA

  3. /*map*/
    count = dma_map_sg(dev, sglist, nents, direction)
    
    /*unmap*/
    dma_unmap_sg(dev, sglist, nents, direction);
    @nents:与传入dma_map_sg()的nents相同,而不是返回值count。

    4.2.2. streaming DMA sync cache

  4. 如果需要使用streaming DMA region多次,并且在这之间需要访问数据,为了确保CPU和device看到的都是一致的,且是最新的,应该按照如下弄:(DMA传输后,CPU看到的是最新的)

  5. 读:

    第一:首先调用dma_map_{single,sg}()

    第二:在每个DMA传输后dma_sync_single_for_cpu(dev, dma_handle, size, direction);

    或者dma_sync_sg_for_cpu(dev, sglist, nents, direction); 

  6. 如果需要让DMA在CPU访问数据后得到的是最新的数据,可以按如下操作:

    在CPU访问完数据后调用dma_sync_single_for_device(dev, dma_handle, size, direction);

    或者dma_sync_sg_for_device(dev, sglist, nents, direction);

  7. 5. DMA方向

1). DMA_BIDIRECTIONAL

如果不清楚数据的方向就用DMA_BIDIRECTIONAL,需要具体的平台支持,会影响性能。

读写内存之前都要sync一下。

2). DMA_TO_DEVICE

数据从内存写道设备,在软件最后一次操作内存之后,在DMA传给设备之前,必须sync一下。

3). DMA_FROM_DEVICE

数据从设备读到内存,在软件读内存之前必须sync一下。

4). DMA_NONE

用于DEBUG

Tips:

streaming 映射需要指定方向,consistent映射已经隐式地设置了方向为DMA_BIDIRECTIONAL

  1. 6. DMA API

  2. 6.1. dma_alloc_coherent


    一致性内存是指设备或处理器的写操作可以被处理器或设备立即读取而不必担心缓存效果的内存。不过,您可能需要确保在告知之前刷新处理器的写入缓冲区(ARM cortex-a53没有写入缓冲区,ARMv9 有)。

  3. 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_alloc_coherent().返回空间清零。

    Void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,dma_addr_t dma_handle) // 释放申请的coherent内存

  4. 6.2. dma_pool*


  5. /* 用于需要申请很多小(小于PAGE_SIZE的大小)coherent内存*/

    struct dma_pool *dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t alloc);

    @name:用于辨别这个dma pool

    @aling:2的倍数对齐

    /*从dma pool中申请coherent内存*/

    void *dma_pool_alloc(struct dma_pool *pool, gfp_t gfp_flags, dma_addr_t *dma_handle);// 返回CPU的地址

    @dma_handle:接收DMA地址

    void *dma_pool_zalloc(struct dma_pool *pool, gfp_t mem_flags,dma_addr_t *handle)//类似dma_pool_alloc()

    /*把alloc的内存交回给dma pool*/

    void dma_pool_free(struct dma_pool *pool, void *vaddr,dma_addr_t addr);

    /*释放dma pool*/

    void dma_pool_destroy(struct dma_pool *pool);

    6.3 dma_set_mask*


    Int dma_set_mask_and_coherent(struct device *dev, u64 mask) //检查并更新streaming & coherent DMA的地址位宽

    @0 return 表示成功

    Int dma_set_mask(struct device *dev, u64 mask)//检查并更新streaming  DMA的地址位宽

    @0 return 表示成功

    Int dma_set_coherent_mask(struct device *dev, u64 mask) // 检查并更新coherent DMA的地址位宽

    @0 return 表示成功

  6. if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) {
    dev_warn(dev, "mydev: No suitable DMA available\n");
    goto ignore_this_device;
    }
    
    /*有时候设置64位掩码可能失败,不是因为64位寻址不支持,而是因为内核觉得32位更有效。这个时候对于64位设备可以这么设置:*/
    
    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;
    }

    6.4 dma_map_single


    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)

    //用此接口必须保证虚拟地址在物理地址上也是连续的,例如kmalloc出来的虚拟地址

    @direction:

    DMA_NONE,

    DMA_TO_DEVICE,

    DMA_FROM_DEVICE,

    DMA_BIDIRECTIONAL

  7. 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)) {
    goto map_error_handling;
    }
    /*调用dma_mapping_error()来检查dma_map_single()是否失败,但是并不是所有的DMA实现都支持dma_mapping_error()*/

    6.6 dma_map_sg


    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);
    }
    @nents:sglist 的entries个数
    ... 
    dma_unmap_sg(dev, sglist, nents, direction);
    @nents:与传入dma_map_sg()的nents相同,而不是返回值count。

    6.7 dma_map_page


    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)

    int dma_get_cache_alignment(void)

    //返回处理器缓存对齐。这是映射内存或进行部分刷新时必须遵守的绝对最小对齐*和*宽度

    void dma_cache_sync(struct device *dev, void *vaddr, size_t size, enum dma_data_direction direction)

    /sync部分内存,且这个内存是由dma_alloc_noncoherent()申请的。

    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);

    6.8 dma_sync_single_for*


    void dma_sync_single_for_cpu(dev, dma_handle, size, direction)// 在DMA把数据从device搬到DDR后,在cpu 访问DDR之前调用,目的是为了让cpu看到最新的数据

    void dma_sync_single_for_device(dev, dma_handle, size, direction)// 在DMA把数据从DDR搬到device之前调用,目的是为了让device看到最新的数据

    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);
    }
    
    }

    6.9 dma_request_chan


    查找并返回与该设备关联的名为name的DMA通道,这种关联是通过设备树来实现的。

    通过该接口申请的channel直到使用dma_release_channel()释放为止都是独占该通道的。

    struct dma_chan *dma_request_chan(struct device *dev, const char *name)

    6.10 dmaengine_slave_config


    传递指定的信息给DMA驱动,这些信息大部分已经集成在struct dma_slave_config中。

    如果需要传递更多的信息(DMA控制器需要更多的信息),可以将struct dma_slave_config内嵌到控制器的指定结构体中,这种就可以传递更多参数了。

    int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config)

    2.11 dmaengine_prep_slave_sg


    散列表的形式传输数据,在调用dmaengine_prep_slave_sg()之前需要使用散列表的映射,并且必须在DMA操作完成之前不能释放。如果需要同步,请调用dma_sync_*_for_*()。

    struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(

    struct dma_chan *chan, struct scatterlist *sgl,

    unsigned int sg_len, enum dma_data_direction direction,

    unsigned long flags);

    nr_sg = dma_map_sg(chan->device->dev, sgl, sg_len);
    if (nr_sg == 0)
    /* error */
    desc = dmaengine_prep_slave_sg(chan, sgl, nr_sg, direction, flags);
    
    注意:
    一旦获取到描述符desc ,就必须尽快submit,因为有些DMA engine会在获取desc后持有spinlock。

    6.12 dmaengine_prep_dma_cyclic


    循环模式传输数据

    struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(

    struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,

    size_t period_len, enum dma_data_direction direction);

    6.13 dmaengine_prep_interleaved_dma


    交叉模式传输数据

    struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma(

    struct dma_chan *chan, struct dma_interleaved_template *xt,

    unsigned long flags);

  8. 6.14 dmaengine_submit


    把描述符添加到挂起的队列中来。

    dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)

    6.15 dma_async_issue_pending


    激活挂起队列中的事务,一旦一个事务完成,队列中的下一个事务就开启,如果设置了回调,还会调用回调通知完成。

    void dma_async_issue_pending(struct dma_chan *chan)

  9. 6.16 dmaengine_terminate*


    终止传输,会丢失还未传输的数据

    int dmaengine_terminate_sync(struct dma_chan *chan)
    int dmaengine_terminate_async(struct dma_chan *chan)
    int dmaengine_terminate_all(struct dma_chan *chan) /* DEPRECATED */

    6.17 dmaengine_pause


  10. 暂停channel传输,不会导致丢失数据

    int dmaengine_pause(struct dma_chan *chan)

    6.18 dmaengine_resume


    唤醒当前暂停的DMA通道

    int dmaengine_resume(struct dma_chan *chan)

    6.19 dma_async_is_tx_complete


    检查channel的状态,例如传输是否完成。

    enum dma_status dma_async_is_tx_complete(struct dma_chan *chan,

    dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used)

    6.20 dmaengine_synchronize


    Synchronize the termination of the DMA channel to the current context.

    该函数应该在调用dmaengine_terminate_async()后使用,这个函数将等待传输完成并且调用回调完成。

    void dmaengine_synchronize(struct dma_chan *chan)

    7. DMA engine usage

  11. The slave DMA usage consists of following steps:
    1.Allocate a DMA slave channel
    2.Set slave and controller specific parameters
    3.Get a descriptor for transaction
    4.Submit the transaction
    5.Issue pending requests and wait for callback notification

1.Allocate a DMA slave channel

struct dma_chan *dma_request_chan(struct device *dev, const char *name)

查找并返回与该设备关联的名为name的DMA通道,这种关联是通过设备树来实现的。

通过该接口申请的channel直到使用dma_release_channel()释放为止都是独占该通道的。


2.Set slave and controller specific parameters

int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config)

传递指定的信息给DMA驱动,这些信息大部分已经集成在struct dma_slave_config中。

如果需要传递更多的信息(DMA控制器需要更多的信息),可以将struct dma_slave_config内嵌到控制器的指定结构体中,这种就可以传递更多参数了。


3.Get a descriptor for transaction

DMA Engine支持多种从机传输模式。

1). single传输

struct dma_async_tx_descriptor *dmaengine_prep_slave_single(

struct dma_chan *chan, dma_addr_t buf, size_t len, enum dma_transfer_direction dir, unsigned long flags)

2). scatterlist传输

struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(

struct dma_chan *chan, struct scatterlist *sgl,

unsigned int sg_len, enum dma_data_direction direction,

unsigned long flags);

如果使用散列表的形式,在调用dmaengine_prep_slave_sg()之前需要使用散列表的映射,并且必须在DMA操作完成之前不能释放。如果需要同步,请调用dma_sync_*_for_*()。通常的操作如下:

nr_sg = dma_map_sg(chan->device->dev, sgl, sg_len);

if (nr_sg == 0)

/* error */

desc = dmaengine_prep_slave_sg(chan, sgl, nr_sg, direction, flags);

注意:

一旦获取到描述符desc ,就必须尽快submit,因为有些DMA engine会在获取desc后持有spinlock。

3). dma_cyclic传输

struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(

struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,

size_t period_len, enum dma_data_direction direction);

4). interleaved_dma传输

struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma(

struct dma_chan *chan, struct dma_interleaved_template *xt,

unsigned long flags);


4. Submit the transaction

一旦desc一获取以及回调信息添加了,就必须将它到达DMA engine驱动队列中来。

dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)

dmaengine_submit()不会开启一个DMA的操作,它仅仅是把它添加到挂起的队列中来。


5. Issue pending DMA requests and wait for callback notification

void dma_async_issue_pending(struct dma_chan *chan);

通过dma_async_issue_pending()来激活挂起队列中的事务,一旦一个事务完成,队列中的下一个事务就开启,如果设置了回调,还会调用回调通知完成。

  • 5
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 内核的 DMA 子系统是一种提供直接内存访问(Direct Memory Access,DMA)功能的机制,可以实现设备之间的数据传输,减少 CPU 的负担,提高系统性能。DMA 子系统包括了 DMA 控制器、DMA 缓冲区管理、中断处理等多个部分,其中比较重要的 API 包括以下几个: 1. dma_alloc_coherent():分配 DMA 缓冲区的 API,用于分配一段连续的物理内存,用于 DMA 数据传输。 2. dma_map_single() 和 dma_unmap_single():将一个单独的 DMA 缓冲区映射到物理地址空间,并在 DMA 操作完成后释放映射。 3. dma_sync_single_for_cpu() 和 dma_sync_single_for_device():同步 DMA 缓冲区和物理地址空间之间的数据,以确保数据的正确性和一致性。 4. dmaengine_submit() 和 dma_async_issue_pending():提交 DMA 操作请求,启动 DMA 数据传输。 5. dmaengine_terminate_all():停止所有正在进行的 DMA 操作。 DMA 的工作原理是通过 DMA 控制器实现的。控制器可以直接访问系统总线,将数据从设备中读取到 DMA 缓冲区中,或者从 DMA 缓冲区中将数据传输到设备中。在进行 DMA 操作时,需要设置 DMA 控制器的寄存器,包括源地址、目的地址、传输长度等信息。DMA 控制器会负责处理数据传输的过程,传输完成后会产生中断信号通知 CPU。 需要注意的是,Linux 内核的 DMA 子系统需要硬件支持,即需要设备具有 DMA 控制器接口。如果设备没有 DMA 控制器接口,也可以通过 CPU 进行数据传输,但效率会降低。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值