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 描述符表
MMC 控制器与 ADMA2 描述符表
上图中左半部分为 MMC 控制器内部集成的DMA控制器,右边部分为系统内存,储存在系统内存中的描述符表由 Host 驱动来进行管理。DMA 控制器通过 System Address Register 寄存器,指向中描述符表首地址就可以获取整张描述符表,数据传输是通过向 Command register 写入来触发的,ADMA2 会逐行处理描述符表中的表项,直到遇到标有结束(End=1)属性的描述符表项。在数据传输过程中,ADMA2 会管理各种状态和标志,包括中断请求、错误处理以及传输完成的标志。
1.2 描述符项
描述符项
每个描述符项遵循特定的格式,例如在 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 控制器如何解释和执行描述符表中的每一行,描述符的属性位通常包括以下几个部分:
-
Valid(有效位):
- 此位指示描述符行是否有效。当此位被设置时,ADMA2 控制器会处理该描述符行;当未设置时,表示描述符行不应被处理。
-
End(结束位):
- 当此位被设置时,它表示当前描述符行是表中的最后一条有效描述符。ADMA2 控制器在处理到标有结束位的描述符行后会停止处理后续描述符,这标志着数据传输操作的结束。
-
Int(中断位):
- 如果此位被设置,ADMA2 控制器在处理完该描述符行后会产生一个中断。这允许系统响应传输完成或处理其他事件。
-
Act(操作码):
- 这是由 Act2、Act1、Act0 三个位组成的字段,用来定义描述符行的操作类型。通常的操作类型包括:
-
Nop(无操作): 控制器将忽略该行,并继续处理下一行描述符。
-
Rsv(保留): 与 Nop 相同,这通常是为了未来的扩展保留的。
-
Tran(传输数据): 控制器将执行数据传输操作。
-
Link(链接描述符): 控制器将链接到另一个描述符表,通常用于实现更复杂的传输序列。
-
- 这是由 Act2、Act1、Act0 三个位组成的字段,用来定义描述符行的操作类型。通常的操作类型包括:
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 数据传输(读/写)
- 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);
}
- 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 控制器便可以通过描述符表完成数据的交互。