sdhci ADMA2代码框架分析

ADMA2

1 简介

在SD Host Controller Standard Specification 2.00,定义了新的DMA传输算法ADMA (Advanced DMA)。在1.0协议中定义的DMA叫做SDMA(Single DMA),SDMA的缺点是在每个页面边界会产生DMA中断,从而打断CPU重新编程新的系统地址,由于每个页面边界的中断,SDMA算法产生了性能瓶颈。ADMA采用scatter gather DMA算法,因此能得到更高的传输速度。在系统内存和eMMC卡之间,Host驱动在执行DMA前把一系列的数据传输编程成描述符表(Descriptor Table),使能ADMA传输数据而不中断Host驱动。

ADMA不仅支持32位的系统内存地址,也支持64位系统内存地址,32位地址使用64位地址寄存器中的低32位。

ADMA有两种类型:ADMA1和ADMA2。ADMA1支持在系统内存中4KB对齐的数据传输。ADMA2改善了这个限制,因此系统内存中,任何位置和大小的数据能够被传输。ADMA1和ADMA2有不同的描述表格式,2.0协议定义ADMA2作为标准的ADMA,并推荐支持ADMA2而不是ADMA1。

ADMA2是主控制器与系统内存之间的数据传输机制。在 ADMA2 中,由Host驱动程序在系统内存中创建并维护一个描述符表,每个描述符项包含数据的地址、长度和用于指定描述符操作的属性

1.1 描述符表

img

MMC 控制器与 ADMA2 描述符表

上图中左半部分为 MMC 控制器内部集成的DMA控制器,右边部分为系统内存,储存在系统内存中的描述符表由 Host 驱动来进行管理。DMA 控制器通过 System Address Register 寄存器,指向中描述符表首地址就可以获取整张描述符表,数据传输是通过向 Command register 写入来触发的,ADMA2 会逐行处理描述符表中的表项,直到遇到标有结束(End=1)属性的描述符表项。在数据传输过程中,ADMA2 会管理各种状态和标志,包括中断请求、错误处理以及传输完成的标志。

1.2 描述符项

img

描述符项

每个描述符项遵循特定的格式,例如在 64 位寻址模式下,描述符项由以下部分组成:

  • 保留位(32-Bit Reserved)
  • 64 位地址(64-Bit Address):数据存储的起始地址;
  • 16 位长度(16-Bit Length):从存储地址开始的数据长度;
  • 10 位长度(10-Bit Length):从 4.10 版本开始的扩展数据长度;//因此4.1之前的版本数据长度为16位,每个描述行数据长度小于64KB
  • 属性位(6-Bit Attribute):用来控制和指示描述符的行为和状态。

属性位的组合定义了 ADMA2 控制器如何解释和执行描述符表中的每一行,描述符的属性位通常包括以下几个部分:

  1. Valid(有效位):

    • 此位指示描述符行是否有效。当此位被设置时,ADMA2 控制器会处理该描述符行;当未设置时,表示描述符行不应被处理。
  2. End(结束位):

    • 当此位被设置时,它表示当前描述符行是表中的最后一条有效描述符。ADMA2 控制器在处理到标有结束位的描述符行后会停止处理后续描述符,这标志着数据传输操作的结束。
  3. Int(中断位):

    • 如果此位被设置,ADMA2 控制器在处理完该描述符行后会产生一个中断。这允许系统响应传输完成或处理其他事件。
  4. Act(操作码):

    • 这是由 Act2、Act1、Act0 三个位组成的字段,用来定义描述符行的操作类型。通常的操作类型包括:
      • Nop(无操作): 控制器将忽略该行,并继续处理下一行描述符。

      • Rsv(保留): 与 Nop 相同,这通常是为了未来的扩展保留的。

      • Tran(传输数据): 控制器将执行数据传输操作。

      • Link(链接描述符): 控制器将链接到另一个描述符表,通常用于实现更复杂的传输序列。

1.3 数据地址和数据长度要求

Total Length = Length 1 + Length 2 + … Length n = multiple of Block Size(块大小的倍数)
如果总的数据长度不是块大小的倍数,那么ADMA2传输不会结束,因此传输将由于数据超时而终止。

Block Count寄存器作为16位寄存器被定义,最大传输是65535个块。如果ADMA2操作少于或等于65535块数据,Block Count能够被使用。如果ADMA2操作的数据大于65535个块,那么Block Count寄存器将不被使用并把Block Count Enable阈(Transfer Mode寄存器)设置成“0”,因此总的数据长度将是由描述符表决定,而不是由块数。因此在SD总线上的最后一个块的定时检测可能不同并将影响 Present State寄存器中的Read Transfer Active, Write Transfer Active和DAT line Active。在读操作时,会比要求的多读几个块。如果读操作是内存数据的最后块,Host驱动应该忽略Out Of Range错误。

2 Secure Digital Host Controller Interface driver

在了解了 ADMA2 的结构后,关于 DMA 相关操作的代码逻辑进行梳理如下:

2.1 DMA描述符表分配:

#define SDHCI_MAX_SEGS		128

/*
 * ADMA2 64-bit descriptor. Note 12-byte descriptor can't always be 8-byte
 * aligned.
 */
struct sdhci_adma2_64_desc {
	__le16	cmd;
	__le16	len;
	__le32	addr_lo;
	__le32	addr_hi;
}  __packed __aligned(4);//12字节

sdhci_cdns_probe:
|--->sdhci_pltfm_init:
|   |--->sdhci_alloc_host:
|   |   |---->sdhci_alloc_host:
|   |   |    |	/*
|   |   |    |    * The DMA table descriptor count is calculated as the maximum
|   |   |    |      * number of segments times 2, to allow for an alignment
|   |   |    |      * descriptor for each segment, plus 1 for a nop end descriptor.
|   |   |    |     */
|   |   |    |    host->adma_table_cnt = SDHCI_MAX_SEGS * 2 + 1;//257
|--->sdhci_add_host:
    |--->sdhci_setup_host:{
    |   |---->sdhci_set_dma_mask://if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) 条件下
    |   |    |---->dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));//64BIT mask:~0ULL
    |   |    |if (host->flags & SDHCI_USE_ADMA) {
    |   |    |     dma_addr_t dma;
    |   |    |     void *buf;
    |   |    |     if (!(host->flags & SDHCI_USE_64_BIT_DMA))
    |   |    |         host->alloc_desc_sz = SDHCI_ADMA2_32_DESC_SZ;
    |   |    |     else if (!host->alloc_desc_sz)
    |   |    |         host->alloc_desc_sz = SDHCI_ADMA2_64_DESC_SZ(host);//16Bytes for 128bit Descriptor
    |   |    |
    |   |    |     host->desc_sz = host->alloc_desc_sz;//16
    |   |    |     host->adma_table_sz = host->adma_table_cnt * host->desc_sz;//table size 4112 == 257*16
    |   |    |     host->align_buffer_sz = SDHCI_MAX_SEGS * SDHCI_ADMA2_ALIGN;//
    |   |    |     /*
    |   |    |      * Use zalloc to zero the reserved high 32-bits of 128-bit
    |   |    |      * descriptors so that they never need to be written.
    |   |    |      */
    |   |    |    	//描述符表所占大小:4624 == align_buffer_sz + host->adma_table_sz == 512 + 4112
    |   |    |     buf = dma_alloc_coherent(mmc_dev(mmc), host->align_buffer_sz + host->adma_table_sz,  &dma, GFP_KERNEL);
    |   |    |     host->align_buffer = buf;
    |   |    |     host->align_addr = dma;
    |   |    |     host->adma_table = buf + host->align_buffer_sz;
    |   |    |     host->adma_addr = dma + host->align_buffer_sz;
    |   |    |}//end of if

2.2 scatterlist 申请

//后续再研究,每个requst都会调用blk_mq_init_request去初始化,包括分配scatterlist表项内存,每个request分配mmc->max_segs即128个sg项。
blk_mq_alloc_rqs:
|--->blk_mq_init_request:
|   |---->mmc_mq_init_request:
         |---> __mmc_init_request:
             |---->mmc_alloc_sg:

static struct scatterlist *mmc_alloc_sg(int sg_len, gfp_t gfp)
{
	struct scatterlist *sg;
	sg = kmalloc_array(sg_len, sizeof(*sg), gfp);
	if (sg)
		sg_init_table(sg, sg_len);
	return sg;
}
/**
 * mmc_init_request() - initialize the MMC-specific per-request data
 * @mq: the request queue
 * @req: the request
 * @gfp: memory allocation policy
 */
static int __mmc_init_request(struct mmc_queue *mq, struct request *req,
			      gfp_t gfp)
{
	struct mmc_queue_req *mq_rq = req_to_mmc_queue_req(req);
	struct mmc_card *card = mq->card;
	struct mmc_host *host = card->host;
	mq_rq->sg = mmc_alloc_sg(mmc_get_max_segments(host), gfp);
	if (!mq_rq->sg)
		return -ENOMEM;
	return 0;
}

2.3 数据传输(读/写)

  1. data prep 数据准备:
mmc_blk_mq_issue_rq:
|--->mmc_blk_mq_issue_rw_rq:
    |---->mmc_blk_rw_rq_prep:
    |	 |--->mmc_blk_data_prep:
   	|    |   |   brq->data.blksz = 512;
   	|    |   |   brq->data.blocks = blk_rq_sectors(req);
   	|    |   |   brq->data.blk_addr = blk_rq_pos(req);
   	|    |   |   brq->data.sg = mqrq->sg;对每个sg设置了sg->page_link指向了BIO的page结构指针sg_set_page(*sg, page, len, offset);
    |    |   |    /*调用blk_rq_map_sg,遍历request中BIO,并将其数据page结构指针存至散列表page_link里,后面dma映射会用到这些page结构
    |    |   |    *见sg_set_page(*sg, page, len, offset)操作,最后返回sg表长度(sg个数)。
    |    |   |     */
   	|    |   |    brq->data.sg_len = mmc_queue_map_sg(mq, mqrq);
   	|    |   brq->cmd.arg = blk_rq_pos(req);
   	|    |   brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
    |    |   brq->cmd.opcode = rq_data_dir(req) == READ ? readcmd : writecmd;
   	|    |   if ((md->flags&MMC_BLK_CMD23) && mmc_op_multi(brq->cmd.opcode) &&(card->quirks & MMC_QUIRK_BLK_NO_CMD23) {
   	|    |	     brq->sbc.opcode = MMC_SET_BLOCK_COUNT;  /* for Auto-CMD23: Can do SET_BLOCK_COUNT for multiblock */
   	|    |       brq->sbc.arg = brq->data.blocks |(do_rel_wr ? (1 << 31) : 0) |(do_data_tag ? (1 << 29) : 0);
   	|    |   }//end if
   	|    mqrq->brq.mrq.done = mmc_blk_mq_req_done;//准备request结束的善后处理回调
    |	 /*mmc_blk_mq_req_donerequest结束的善后处理回调,如果当前请求未完成,则wait阻塞,否则调用mmc_blk_mq_post_req
    |	 *mmc_blk_mq_post_req最终调用host->ops->post_req即sdhci_post_req做sg的umap:
    |	 * dma_unmap_sg(mmc_dev, data->sg, data->sg_len 与下面的pre_req相反的操作
    |	 */
   	|---->mmc_pre_req:
   	|    |--->host->ops->pre_req(host, mrq);//call sdhci_pre_req -->sdhci_pre_dma_transfer 见下面单独分析
    |    mmc_blk_rw_wait //如果前一个request未完成,则wait 等待
    |    mmc_start_request//发起传输请求
static void sdhci_pre_req(struct mmc_host *mmc, struct mmc_request *mrq)
{
	struct sdhci_host *host = mmc_priv(mmc);

	mrq->data->host_cookie = COOKIE_UNMAPPED;

	/*
	 * No pre-mapping in the pre hook if we're using the bounce buffer,
	 * for that we would need two bounce buffers since one buffer is
	 * in flight when this is getting called.
	 */
    //在sdhci_setup_host里面mmc->max_segs == 1时才会申请bounce_buffer,干嘛用的? 后面再研究
	if (host->flags & SDHCI_REQ_USE_DMA && !host->bounce_buffer)//bounce_buffer == NULL
		sdhci_pre_dma_transfer(host, mrq->data, COOKIE_PRE_MAPPED);
}
static int sdhci_pre_dma_transfer(struct sdhci_host *host, struct mmc_data *data, int cookie)
{
    //.....中间略
    /* Just access the data directly from memory */
    /* dma_map_sg最终会调用到dma_direct_map_sg,路径:dma_map_sg_attrs->dma_direct_map_sg */
    sg_count = dma_map_sg(mmc_dev(host->mmc),data->sg, data->sg_len, mmc_get_dma_dir(data));
    data->sg_count = sg_count;
    data->host_cookie = cookie;
	return sg_count;
}
/* dma_direct_map_sg:循环遍历scatterlist *sgl表
 * 配置好sg->dma_address物理地址,和长度sg_dma_len(sg)。
 * 物理地址dma_address通过dma_direct_map_page映射得来。
 * 在mmc_blk_data_prep中已经对每个sg设置了sg->page_link指向了BIO的page结构指针sg_set_page(*sg, page, len, offset);
 * 此处通过sg_page(sg)获得page结构指针做映射,见dma_direct_map_page
 */
int dma_direct_map_sg(struct device *dev, struct scatterlist *sgl, int nents,enum dma_data_direction dir, unsigned long attrs)
{
	int i;
	struct scatterlist *sg;
	for_each_sg(sgl, sg, nents, i) {
		sg->dma_address = dma_direct_map_page(dev, sg_page(sg), sg->offset, sg->length, dir, attrs);
		sg_dma_len(sg) = sg->length;
	}
	return nents;
	.....略....伪代码
	return 0;
}

#define page_to_phys(page)	(pfn_to_phys(page_to_pfn(page)))
static inline dma_addr_t dma_direct_map_page(struct device *dev,
		struct page *page, unsigned long offset, size_t size,
		enum dma_data_direction dir, unsigned long attrs)
{
    //通过page结构和偏移获得物理地址,见上面宏
	phys_addr_t phys = page_to_phys(page) + offset;
    //跟平台相关,NB2是空操作,直接类型转换为dma_addr_t
	dma_addr_t dma_addr = phys_to_dma(dev, phys);

    /*cache一致性的处理:
     * 对于dma_alloc_coherent申请的内存,它页表属性设置是uncache的,不存在cache一致性问题,此处什么都不做
     * MMC传输的内存buff由上层scatterlist散列表得来,显然不是dma_coherent类型的, 需要做cache一致性的同步
     * cache一致性同步,对于读做cache invalidate,对于写做cache clean,见__dma_sync
     * 详细见:https://jjze1kxe5m.feishu.cn/docx/KsbedjPSvo4VenxoGOQccD4HnJe
     */
	if (!dev_is_dma_coherent(dev) && !(attrs & DMA_ATTR_SKIP_CPU_SYNC))
		arch_sync_dma_for_device(phys, size, dir);
	return dma_addr;
}

arch_sync_dma_for_device -->__dma_sync
static void __dma_sync(phys_addr_t paddr, size_t size, enum dma_data_direction dir)
{
	if ((dir == DMA_FROM_DEVICE) && (dma_cache_sync->cache_invalidate))
		dma_cache_sync->cache_invalidate(paddr, size);
	else if ((dir == DMA_TO_DEVICE) && (dma_cache_sync->cache_clean))
		dma_cache_sync->cache_clean(paddr, size);
	else if ((dir == DMA_BIDIRECTIONAL) && dma_cache_sync->cache_flush)
		dma_cache_sync->cache_flush(paddr, size);
}
  1. start request//发起传输请求
mmc_start_request:
|--->mmc_mrq_pr_debug //debug打印
|--->mmc_mrq_prep	  //mrq->cmd,data, sbc等数据结构预处理
|--->__mmc_start_request:
    |--->trace_mmc_request_start(host, mrq);  //trace event日志
    |--->host->ops->request(host, mrq);//call sdhci_request 执行host 层request
        |--->sdhci_request:
        |--->cmd = sdhci_manual_cmd23(host, mrq) ? mrq->sbc : mrq->cmd;//不支持 Auto-CMD23则sbc,否则cmd,NB2支持Auto-CMD23
        |--->sdhci_send_command_retry(host, cmd, flags):
            |--->sdhci_send_command://真正操作host controller寄存器执行发命令和数据,见下面单独分析
                
static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
{
	//略.....
	if (cmd->data) {
		if (host->use_external_dma)
			sdhci_external_dma_prepare_data(host, cmd);
		else
			sdhci_prepare_data(host, cmd); //见下面,主要是DMA配置(DMA描述表等)、中断使能及传输的block size/count等的配置
	}
    sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);//写寄存器,设置cmd->arg

	sdhci_set_transfer_mode(host, cmd);//传输模式的配置,auto_cmd23、block count等

    sdhci_mod_timer(host, cmd->mrq, timeout);//配置定时器,data_timer或timer

	if (host->use_external_dma)
		sdhci_external_dma_pre_transfer(host, cmd);

    //设置命令,启动传输
	sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);

	return true;
}
    
static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
{
    //略.....
    if (host->flags & SDHCI_REQ_USE_DMA) {
        /*前面sdhci_pre_req中已经做了pre map并设置了COOKIE_PRE_MAPPED,此处不会真正映射
         * 而是直接返回已经映射好的data->sg_count个数
         */
		int sg_cnt = sdhci_pre_dma_transfer(host, data, COOKIE_MAPPED);
		if (host->flags & SDHCI_USE_ADMA) {//ADMA
            /*  对于ADMA sg_cnt可以有多个或一个
             *  对于每一个sg,需要配置它的物理地址sg->dma_address到描述符表项中去
             *  每个sg占用一个描述符表项, 格式见1.2节
             */
			sdhci_adma_table_pre(host, data, sg_cnt);
             /* 描述符表首地址写到DMA控制器的DMA地址寄存器中
              * 在sdhci_setup_host中,申请了的描述符表物理地址保存在host->adma_addr指针中
              */
			sdhci_set_adma_addr(host, host->adma_addr);
		} else {//SDMA
            /* 对于SDMA sg_cnt只有一个,它没有描述符表
             * 直接把sg->dma_address设置到DMA地址寄存器中
             */
			WARN_ON(sg_cnt != 1);
			sdhci_set_sdma_addr(host, sdhci_sdma_address(host));
		}
	}

	sdhci_config_dma(host);//配置DMA, 模式(SDMA或ADMA)和位宽(32BIT还是64BIT)
    //如果不支持DMA,配置PIO方式传输
    if (!(host->flags & SDHCI_REQ_USE_DMA)) {
		int flags;
		flags = SG_MITER_ATOMIC;
		if (host->data->flags & MMC_DATA_READ)
			flags |= SG_MITER_TO_SG;
		else
			flags |= SG_MITER_FROM_SG;
		sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
		host->blocks = data->blocks;
	}
    sdhci_set_transfer_irqs(host);//根据传输模式enable DMA irq或PIO irq

    /* 设置传输的block size和block count
     * block size是固定为的,card初始化时从eMMC设备exd_csd寄存器获得
     * block count为本次request传输的数据大小除以block size得到
     */
	sdhci_set_block_info(host, data);
}
static void sdhci_adma_table_pre(struct sdhci_host *host,
	struct mmc_data *data, int sg_count)
{
	struct scatterlist *sg;
	unsigned long flags;
	dma_addr_t addr, align_addr;
	void *desc, *align;
	char *buffer;
	int len, offset, i;
	/*
	 * The spec does not specify endianness of descriptor table.
	 * We currently guess that it is LE.
	 */
	host->sg_count = sg_count;

	desc = host->adma_table;//表的虚拟地址,与上面的host->dma_addr不同
	align = host->align_buffer;//与adma_table区别是adma_table跳过了前面512字节,见上面align_sz

	align_addr = host->align_addr;//与adma_addr区别是adma_addr跳过了前面512字节,见上面align_sz
    for_each_sg(data->sg, sg, host->sg_count, i) {
		addr = sg_dma_address(sg);
		len = sg_dma_len(sg);
         //略过对齐处理
         /* tran, valid */
		if (len)
			__sdhci_adma_write_desc(host, &desc, addr, len, ADMA2_TRAN_VALID);//call sdhci_adma_write_desc
        
    }
    //结束描述项
    if (host->quirks & SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC) {
		/* Mark the last descriptor as the terminating descriptor */
		if (desc != host->adma_table) {
			desc -= host->desc_sz;
			sdhci_adma_mark_end(desc);
		}
	} else {
		/* Add a terminating entry - nop, end, valid */
		__sdhci_adma_write_desc(host, &desc, 0, 0, ADMA2_NOP_END_VALID);
	}
 }
     
void sdhci_adma_write_desc(struct sdhci_host *host, void **desc,
			   dma_addr_t addr, int len, unsigned int cmd)
{
	struct sdhci_adma2_64_desc *dma_desc = *desc;

	/* 32-bit and 64-bit descriptors have these members in same position */
	dma_desc->cmd = cpu_to_le16(cmd);
	dma_desc->len = cpu_to_le16(len);
	dma_desc->addr_lo = cpu_to_le32(lower_32_bits(addr));

	if (host->flags & SDHCI_USE_64_BIT_DMA)
		dma_desc->addr_hi = cpu_to_le32(upper_32_bits(addr));

	*desc += host->desc_sz;
}

在驱动 probe 后会调用 sdhci_add_host() 进而在 sdhci_setup_host() 申请用于存放描述符表的内存空间,最终将申请到的物理地址在 sdhci_set_adma_addr() 中被配置到控制器的寄存器中。

在描述符表的内存空间分配完成后, 调用 sdhci_send_command() 时会首先在 sdhci_pre_dma_transfer() 中申请用于储存数据内存空间,并将地址以及长度在 sdhci_adma_table_pre() 中通过__sdhci_adma_write_desc() 写入到描述符表中。

至此,MMC 控制器便可以通过描述符表完成数据的交互。

参考

posted @ 2024-01-10 18:13  RobertHu  阅读(814)  评论(0编辑  收藏  举报