利用mmc_test.c研究mmc模块

本文详细介绍了如何通过创建测试模块来获取MMC子系统中的sdhci_host结构体,进而实现对SD卡的基本读写操作。通过注册并修改sdhci_host文件,实现了获取合法mmc_card指针的方法。接着,利用测试模块对MMC控制器和卡进行测试,包括基本读写、性能测试等。最后,讨论了如何简化测试模块的探查过程,并展示了如何利用mmc_test.c文件进行实际的数据读写操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        近日开始研究mmc模块。老是看代码不免困意来袭,为了克服困意,可以通过一些小的试验来加深对该模块的理解。

        第一步:

        首先,自己创建了一个小的测试模块,通过获取mmc模块初始化时sdhci_host结构体的指针(初始化时就将这些sdhci_host结构的指针保存在一个数组,并提供接口导出该数组首地址供其它模块使用)为神秘的mmc子系统打开一扇窗口。


注册sdhci_host的文件中需要做如下改动:

<xxx-sdhci-fix.c>
static unsigned int host_addr[5] = {0}; 
static unsigned int host_count = 0; 

static int xxx_sdhci_host_probe(struct platform_device *pdev)
{
...
        host = sdhci_alloc_host(&pdev->dev, sizeof(struct xxx_sdhci_host));
        host_addr[host_count ++] = host;
...

}

unsigned int *export_host_addr()
{
        return host_addr;
}
EXPORT_SYMBOL(export_host_addr);


测试文件获取到sdhci_host结构体的方法:

 

<test.c>
extern unsigned int *export_host_addr();

int test_init()
{
...
        unsigned int *host_addr = export_host_addr();
        struct sdhci_host *host = NULL;
        struct mmc_host *mmc = NULL;
        struct mmc_card *card = NULL;
...
        host = (struct sdhci_host *)host_addr[0];//至此,我们已经成功获取到sdhci_host
        mmc = host->mmc;
        card = mmc->card;
...
}


 

 

        通过上述代码可以看出,我们获取到sdhci_host结构体之后,可以进一步获取到对应的mmc_host 结构体,进而获取到和该控制器对应的mmc_card结构体。我当前的mmc子系统用到3个mmc_host(mmc/sdio/sd),对应两个mmc_card:mmc——emmc,sd——外接sd卡。

 

        第二步:

        获取到这些重要的结构体后,我们就可以参考mmc_init的流程,向mmc发送一些指令,如go_idle/send_op_cond/send_cid/send_csd/send_ext_csd(遵循mmc_init流程,其他指令都能顺利执行,唯独send_ext_csd指令会超时,至今原因不明,哪位朋友知道请告知,不胜感激)等,掌握mmc控制器和card交互的最基本指令。

 

        第三步: 

        接下来,想研究一下I/O接口的调用方法,毕竟emmc/sd卡最重要的作用是用来存储数据。恰好,在mmc/card/目录下发现了文件mmc_test.c,打开一看,好像有用来测试读写接口的方法,真是天助我也!果断将该文件编译为动态模块,但加载后发现并未执行任何实际操作,仅仅添加了一个测试驱动。继续看代码,发现只有触发probe函数才会做一些具体的事情:在debugfs中创建两个文件节点test和testlist,供用户通过"echo/cat"指令进行操作——事实上所有实际工作都是用户通过操纵在debug文件系统中创建的两个节点来进行。我们知道,想要触发probe函数,还需要注册一个和驱动名一致的设备。难道我们为了创建这两个文件节点,一定要注册一个名为"mmc_test"的设备才行吗?

 

        答案是不需要如此麻烦!我们完全可以将这两个创建文件节点的接口移到init函数中嘛,或者直接在init函数中就去调用probe函数,问题不久迎刃而解了吗?

 

        再来看probe函数,只需要给它传入一个合法的mmc_card指针就足够了,而我们在第一步已经介绍过如何获取到合法的mmc_card指针,这里直接拿来用就可以了(mmc = sdhci_host->mmc, card = mmc->card)。

 

        万事俱备,开始试验!

root@scx35_sp7731gea:/sys/kernel/debug/mmc1/mmc1:1234 # ls
ls
state
status
test
testlist


        加载该测试模块后可以看到,在该目录下创建了2个测试节点:test和testlist。执行第25项测试的指令如下:
root@scx35_sp7731gea:/sys/kernel/debug/mmc1/mmc1:1234 # echo 25 > test


      测试结果如下:
<6>[ 2479.634882] c1 mmc_test: mmc1: Starting tests of card mmc1:1234...
<6>[ 2479.634912] c1 mmc_test: mmc1: Test case 25. Best-case read performance into scattered pages...
<6>[ 2479.748526] c2 mmc_test: mmc1: Transfer of 1 x 8 sectors (1 x 4 KiB) took 0.000564193 seconds (7259 kB/s, 7089 KiB/s, 1772.44 IOPS, sg_len 1)
<6>[ 2479.748570] c2 mmc_test: mmc1: Result: OK
<6>[ 2479.748633] c2 mmc_test: mmc1: Tests completed.

 

       下述指令,可以看出test模块支持的测试项:
root@scx35_sp7731gea:/sys/kernel/debug/mmc1/mmc1:1234 # cat testlist
cat testlist
1:      Basic write (no data verification)
2:      Basic read (no data verification)
3:      Basic write (with data verification)
4:      Basic read (with data verification)
5:      Multi-block write
6:      Multi-block read
7:      Power of two block writes
8:      Power of two block reads
9:      Weird sized block writes
10:     Weird sized block reads
11:     Badly aligned write
12:     Badly aligned read
13:     Badly aligned multi-block write
14:     Badly aligned multi-block read
15:     Correct xfer_size at write (start failure)
16:     Correct xfer_size at read (start failure)
17:     Correct xfer_size at write (midway failure)
18:     Correct xfer_size at read (midway failure)
19:     Highmem write
20:     Highmem read
21:     Multi-block highmem write
22:     Multi-block highmem read
23:     Best-case read performance
24:     Best-case write performance
25:     Best-case read performance into scattered pages
26:     Best-case write performance from scattered pages
27:     Single read performance by transfer size
28:     Single write performance by transfer size
29:     Single trim performance by transfer size
30:     Consecutive read performance by transfer size
31:     Consecutive write performance by transfer size
32:     Consecutive trim performance by transfer size
33:     Random read performance by transfer size
34:     Random write performance by transfer size
35:     Large sequential read into scattered pages
36:     Large sequential write from scattered pages
37:     Write performance with blocking req 4k to 4MB
38:     Write performance with non-blocking req 4k to 4MB
39:     Read performance with blocking req 4k to 4MB
40:     Read performance with non-blocking req 4k to 4MB
41:     Write performance blocking req 1 to 512 sg elems
42:     Write performance non-blocking req 1 to 512 sg elems
43:     Read performance blocking req 1 to 512 sg elems
44:     Read performance non-blocking req 1 to 512 sg elems
45:     eMMC hardware reset

        由此,可以打开一个操作mmc/sd卡的入口,再结合代码,很轻松就可以弄清楚各个指令是如何调用。

### SD卡文件系统驱动实现 要通过 `sdmmc_sd.h`、`ff.h`、`ff_gen_drv.h` 和 `sd_diskio.h` 文件实现SD卡的文件系统驱动,以下是详细的说明: #### 1. **SDMMC模块初始化** SDMMC模块负责与SD卡通信。通常需要调用硬件抽象层(HAL)或标准外设库中的API来完成初始化。 ```c #include "stm32f4xx_hal.h" #include "sdmmc_sd.h" void MX_SDMMC_Init(void) { HAL_StatusTypeDef ret; // 初始化SDMMC控制器 hsd.Instance = SDMMC1; hsd.Init.ClockEdge = SDMMC_CLOCK_EDGE_RISING; hsd.Init.ClockBypass = SDMMC_CLOCK_BYPASS_DISABLE; hsd.Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_DISABLE; hsd.Init.BusWide = SDMMC_BUS_WIDE_4B; // 使用4线模式 hsd.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_DISABLE; hsd.Init.ClockDiv = 0; ret = HAL_SD_Init(&hsd); if (ret != HAL_OK) { Error_Handler(); } } ``` 这段代码展示了如何使用STM32 HAL库初始化SDMMC控制器[^5]。 --- #### 2. **FATFS文件系统的配置** FATFS是一个轻量级的文件系统库,用于管理SD卡上的文件操作。需要定义并注册磁盘I/O接口。 ##### a. 定义磁盘I/O结构体 在 `sd_diskio.h` 中定义了一个名为 `DSTATUS` 的枚举类型以及 `disk_initialize()` 等函数原型。这些函数实现了底层的SD卡访问逻辑。 ```c #include "ff_gen_drv.h" #include "sd_diskio.h" DRESULT disk_initialize(BYTE drv) { // 调用HAL库初始化SD卡 if (drv == 0 && HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_TRANSFER) { return RES_OK; } else { return RES_ERROR; } } DRESULT disk_read(BYTE drv, BYTE *buff, LBA_t sector, UINT count) { if (drv == 0 && HAL_SD_ReadBlocks_DMA(&hsd, buff, sector, count) == HAL_OK) { return RES_OK; } else { return RES_ERROR; } } DRESULT disk_write(BYTE drv, const BYTE *buff, LBA_t sector, UINT count) { if (drv == 0 && HAL_SD_WriteBlocks_DMA(&hsd, buff, sector, count) == HAL_OK) { return RES_OK; } else { return RES_ERROR; } } ``` 以上代码片段实现了 `disk_initialize()`、`disk_read()` 和 `disk_write()` 函数,分别对应于SD卡的初始化、读取和写入操作[^5]。 --- ##### b. 注册FATFS驱动 为了将上述磁盘I/O接口与FATFS关联起来,需调用 `ff_gen_drv.c` 提供的功能。 ```c #include "ff_gen_drv.h" #include "sd_diskio.h" // FATFS对象声明 FATFS fs; FIL fil; UINT br, bw; int main(void) { FRESULT res; // 初始化SDMMC和FATFS MX_SDMMC_Init(); // 将自定义的磁盘I/O接口绑定到FATFS res = f_mount(&fs, "", 1); // 挂载根目录 if (res != FR_OK) { Error_Handler(); } // 创建文件并写入数据 res = f_open(&fil, "test.txt", FA_CREATE_ALWAYS | FA_WRITE); if (res == FR_OK) { f_write(&fil, "Hello World!", strlen("Hello World!"), &bw); f_close(&fil); } // 读取文件内容 res = f_open(&fil, "test.txt", FA_READ); if (res == FR_OK) { char buffer[50]; f_read(&fil, buffer, sizeof(buffer), &br); buffer[sizeof(buffer)-1] = '\0'; // 添加字符串结束符 printf("%s\n", buffer); f_close(&fil); } } ``` 此代码展示如何挂载FATFS文件系统,并执行基本的文件创建、写入和读取操作[^2]。 --- #### 3. **设备树配置** 如果目标平台基于Linux,则可能还需要调整设备树以适配SD卡控制器。 ```plaintext &sdio0 { status = "okay"; max-frequency = <15000000>; // 设置最大频率为15 MHz bus-width = <4>; // 使用4线模式 cap-sd-highspeed; // 支持高速模式 }; ``` 上述设备树节点描述了SDIO控制器的基本属性,确保其能够正常识别和支持SD卡的操作[^3]。 --- #### 4. **注意事项** - 如果遇到性能瓶颈,可尝试优化DMA传输参数或启用更高版本的UHS-I协议。 - 对于嵌入式开发板,建议查阅具体的芯片手册(如S3C2410),了解SD/MMC控制器的工作细节[^1]。 --- ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值