SDMC 使用指南
1. 模块介绍
1.1. 术语定义
术语 | 定义 | 注释说明 |
---|---|---|
SD | Secure Digital | Flash存储卡的一种标准,即常见的SD卡 |
SDCard | Secure Digital Memory Card | 安全数码卡,同“SD卡” |
MMC | Multi Media Card | 多媒体卡 |
eMMC | Embedded Multi Media Card | 内嵌(在板卡上)式多媒体卡 |
SDIO | Secure Digital Input and Output | 安全数字输入输出接口 |
SDMC | SDCard & SDIO & eMMC Host Contollor | SD卡/eMMC 主控制器 |
CMD | Command | SD/eMMC 协议的命令 |
DMA | Direct Memory Access | 直接存储器访问 |
SPI | Serial Peripheral Interface | 串行外设接口 |
其中:
- MMC是最早的记忆卡标准,1997年由西门子和SanDisk推出,现在市场上已经很少见了
- SDCard是1998年由松下、东芝和SanDisk合作发布
- SDCard一开始就兼容MMC协议,和MMC在时序设计上保持一致,读写控制命令也一样
- SDIO是在SD标准的基础上,定义了非存储卡的外设接口,可连接WiFi、BT、摄像头等
- SD卡后续还有SDHC(High Capacity,大容量,最大32GB)和SDXC(eXtended Capacity,最大2TB),本文将SD、SDHC、SDXC统称为SD
- eMMC是MMC框架中的一种,经过多年演化后,从2018年起只留下eMMC了
- eMMC可以看作一个集合,其中包含:Nand Flash、Flash控制器、MMC标准接口封装
1.2. 模块简介
SDMC V1.0可用于访问三种标准协议:SDCard、eMMC设备、SDIO,硬件上提供了三套SDMC,功能定义如下:
SDMC | 支持功能 |
---|---|
SDMC0 | SDCard、 eMMC |
SDMC1 | SDCard |
SDMC2 | SDIO |
1.2.1. SD/SDIO 的传输模式
图 5.13 SD/SDIO 的三种传输模式
SD/SDIO的传输模式有三种:
- SPI模式:为了兼容性考虑,此模式属于required,对硬件要求低,不支持CRC校验。时钟最高 25MHz,读写速度通常低于 3MB/s。广泛用于MP3等对读卡速度要求不高的场景。
- 1bit模式:时钟最高 25MHz,最高速率 12.5MB/s
- 4bit模式:读写时钟最高可达 50MHz,最高速率 25MB/s,是 SD的主要模式
其中“1bit模式”和“4bit模式”又可以统称为“SD模式”。
下表是三种传输模式对应的接口信号线定义:
图 5.14 SD/SDIO 三种传输模式的接口定义
1.2.2. MMC 的工作模式
图 5.15 MMC 的工作模式
MMC标准经过了5个大版本的演化,已经发生了很大的变化。大体上可以分为两种工作模式:
- SPI模式:可选模式,属于MMC协议的一个子集,最大速率 20Mbps。主要用于小容量、低速率场景,可降低成本,也有很好的兼容性
- MMC模式:默认模式,具有MMC的全部特性,支持 1/4/8bit 总线模式
MMC模式从传输速率上看又可以分为以下几种:
图 5.16 MMC 工作模式及相应速率
小技巧
- HS400 是在HS200的基础上增加 DDR 模式(信号双边采样),将理论速率提升一倍。
- MMC 上电或者复位后,默认处于 1bit 模式,只使用信号线DAT[0]传输数据,后续通过命令将其配置为4/8bit模式。
- eMMC芯片不支持SPI模式。
1.2.3. SDMC 的功能特性
SDMC支持的最大接口频率为200MHz,支持1/4/8线数据总线模式,满足eMMC标准协议和SDCard/SDIO接口协议。
图 5.17 SDMC 硬件原理示意图
SDMC支持的特性有:
- 支持eMMC5.0,SD3.01和SDIO3.0,向下兼容
- 支持eMMC SDR/DDR模式,接口时钟频率最大200MHz
- 支持3.3V工作电压
- 支持DDR 4线和8线模式
- 使用内部DMA模式,支持单通道、双缓存和描述符链表传输
- 支持FIFO深度为 128,FIFO位宽为32bit
- 支持CRC生成和错误检测
2. 参数配置
2.1. 内核配置
在luban根目录下执行 make kernel-menuconfig,进入kernel的功能配置,按如下选择:
Linux
Device Drivers
<*> MMC/SD/SDIO card support
<*> MMC block device driver
<*> Artinchip Memory Card Interface
2.2. DTS 参数配置
2.2.1. SDMC 的扩展 DTS 参数
SDMC驱动基于Linux内核的MMC子系统,MMC子系统中提供了很多常用的参数,如下表:
参数名称 | 类型 | 取值范围 | 功能说明 |
---|---|---|---|
max-frequenc | 正整数 | > 0 | 控制器可输出的最大频率 |
bus-width | 正整数 | 1, 4, 8 | 控制器的数据位宽 |
sd-uhs-sdr50 | boolean | 有 - 1,无 - 0 | 配置SDCard的SDR50模式 |
no-sd | boolean | 有 - 1,无 - 0 | 关闭SDCard的功能支持 |
no-sdio | boolean | 有 - 1,无 - 0 | 关闭SDIO的功能支持 |
no-mmc | boolean | 有 - 1,无 - 0 | 关闭eMMC的功能支持 |
详见代码 drivers/mmc/core/host.c中的函数 mmc_of_parse()。
SDMC驱动在此基础上扩展了两个关于FIFO设置的参数,如下表:
参数名称 | 类型 | 取值范围 | 功能说明 |
---|---|---|---|
aic,fifo-depth | 正整数 | [1, 128] | 控制器的FIFO深度 |
aic,fifo-watermark-aligned | boolean | 有 - 1,无 - 0 | FIFO水位是按2的整数次幂对齐 |
2.2.2. D211 配置
SDMC V1.0有三个接口:SDMC0、SDMC1、SDMC2,所以在DTS中体现为三个独立的设备节点。
common/d211.dtsi中的参数配置:
sdmc0: sdmc@10440000 {
compatible = "artinchip,aic-sdmc-v1.0";
reg = <0x0 0x10440000 0x0 0x1000>;
interrupts-extended = <&plic0 46 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cmu CLK_SDMMC0>;
clock-names = "ciu";
resets = <&rst RESET_SDMMC0>;
reset-names = "reset";
#address-cells = <1>;
#size-cells = <0>;
max-frequency = <24000000>;
clock-frequency = <48000000>;
bus-width = <4>;
fifo-depth = <128>;
};
sdmc1: sdmc@10450000 {
compatible = "artinchip,aic-sdmc-v1.0";
reg = <0x0 0x10450000 0x0 0x1000>;
interrupts-extended = <&plic0 47 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cmu CLK_SDMMC1>;
clock-names = "ciu";
resets = <&rst RESET_SDMMC1>;
reset-names = "reset";
#address-cells = <1>;
#size-cells = <0>;
max-frequency = <24000000>;
clock-frequency = <48000000>;
bus-width = <4>;
fifo-depth = <128>;
};
sdmc2: sdmc@10460000 {
compatible = "artinchip,aic-sdmc-v1.0";
reg = <0x0 0x10460000 0x0 0x1000>;
interrupts-extended = <&plic0 48 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cmu CLK_SDMMC2>;
clock-names = "ciu";
resets = <&rst RESET_SDMMC2>;
reset-names = "reset";
#address-cells = <1>;
#size-cells = <0>;
};
2.2.3. Board 中的配置
如果按以下功能定义三个控制器:
- SDMC0 - eMMC
- SDMC1 - SDCard
- SDMC2 - SDIO
xxx/board.dts中的配置参数如下:
&sdmc0 {
pinctrl-names = "default";
pinctrl-0 = <&sdmc0_pins>;
no-sd;
no-sdio;
status = "okay";
};
&sdmc1 {
pinctrl-names = "default";
pinctrl-0 = <&sdmc1_pins>;
no-mmc;
no-sdio;
status = "okay";
};
&sdmc2 {
pinctrl-names = "default";
pinctrl-0 = <&sdmc2_pins>;
no-mmc;
no-sd;
status = "disabled";
};
2.2.4. 热插拔配置
小技巧
SDMC1控制器硬件支持热插拔中断,SDMC驱动中默认已使能,无需另外配置,下文主要是针对SDMC0/2控制器。
SDMC热插拔功能基于Linux内核的MMC子系统,MMC子系统提供了两种方式实现热插拔:
- 轮询监控:主要实现每隔一段时间(一般是HZ,1s)扫描一下mmc硬件总线。
- 中断监控:通过card detect(简称为cd)引脚电平变化触发中断,从而告知CPU说sdcard插入状态发生变化。
MMC子系统中提供了以下关于热插拔的参数,如下表:
参数名称 | 类型 | 取值范围 | 功能说明 |
---|---|---|---|
cd-inverted | boolean | 有 - 1,无 - 0 | gpio检测电平翻转 |
cd-debounce-delay-ms | 正整数 | > 0 | gpio消抖(默认200ms) |
broken-cd | boolean | 有 - 1,无 - 0 | 配置为轮询监控功能 |
cd-gpios | array | 同普通gpio配置 | 配置中断监控的gpio值 |
下面为SDMC0控制器轮询监控方式配置热插拔:
xxx/board.dts中的配置参数如下:
&sdmc0 {
pinctrl-names = "default";
pinctrl-0 = <&sdmc0_pins>;
broken-cd;
status = "okay";
};
下面为SDMC2控制器中断监控方式配置热插拔:
xxx/board.dts中的配置参数如下:
&sdmc2 {
pinctrl-names = "default";
pinctrl-0 = <&sdmc2_pins>;
cd-inverted;//根据硬件设计配置
cd-gpios = <&gpio_c 8 GPIO_ACTIVE_HIGH>;//根据硬件设计配置
cd-debounce-delay-ms = <250>;
status = "okay";
};
3. 调试指南
3.1. 调试开关
在luban根目录下执行 make kernel-menuconfig,进入kernel的功能配置,可以打开SDMC模块的DEBUG选项:
Linux
Kernel hacking
Artinchip Debug
[*] SD&MMC Host Controller driver debug
此DEBUG选项打开的影响:
- MMC子系统(含SDMC驱动)以-O0编译
- MMC子系统(含SDMC驱动)的pr_dbg()和dev_dbg()调试信息会被编译
在系统运行时,如果要打印pr_dbg()和dev_dbg()信息,还需要调整loglevel为8,两个方法:
- 在board.dts中修改bootargs,增加“loglevel=8”
- 在板子启动到Linux shell后,执行命令:
echo 8 > /proc/sys/kernel/printk
SDMC驱动中还使用了 dev_vdbg()
调试接口,打开该接口的方法是在 .c 中定义一个宏开关 VERBOSE_DEBUG,然后重新编译,dev_vdbg()
就会转成 dev_dbg()
,否则为空。
如在artinchip_mmc.c中起始位置定义 VERBOSE_DEBUG:
#define VERBOSE_DEBUG
4. 测试指南
4.1. 测试环境
4.1.1. 硬件
- 开发板,或者FPGA板子
- 板子上必须要接有eMMC/SDCard
4.1.2. 软件
- PC端的串口终端软件,用于PC和开发板进行串口通信
- Linux内核原生的mmc_test工具,用于MMC Host驱动的功能测试
- Luban自带的iozone工具,用于块设备的性能测试
4.1.3. 软件配置
4.1.3.1. mmc_test
在luban根目录下执行 make kernel-menuconfig,进入kernel的功能配置,按如下选择:
Linux
Device Drivers
<*> MMC/SD/SDIO card support
< > MMC block device driver
<*> MMC host test driver
注解
mmc_test必须要关闭MMC Block选项,否则mmc_test模块不会被内核加载。
4.1.3.2. iozone
在luban根目录,运行make menuconfig,按如下选择:
Third-party packages
[*] iozone
4.2. mmc_test 测试
mmc_test的主要功能是测试符合MMC子系统的Host驱动功能,源码详见 driversmmccoremmc_test.c。
板子在启动到shell后,首先要挂载debugfs文件系统,然后可以查看mmc_test的测试节点:
[aic@] # mount -t debugfs none /sys/kernel/debug/
[aic@] # cd /sys/kernel/debug/mmc0/mmc0\:0001/
[aic@mmc0:0001] # ls
state test testlist
[aic@mmc0:0001] # cat testlist # 此时会打印测试选项
0: Run all tests
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: Reset test
46: Commands during read - no Set Block Count (CMD23)
47: Commands during write - no Set Block Count (CMD23)
48: Commands during read - use Set Block Count (CMD23)
49: Commands during write - use Set Block Count (CMD23)
50: Commands during non-blocking read - use Set Block Count (CMD23)
51: Commands during non-blocking write - use Set Block Count (CMD23)us
根据需求,选一个测试项,将其编号写入test节点,如使用测试项1:
[aic@mmc0:0001] # echo 1 > test
[ 162.185679] mmc0: Starting tests of card mmc0:0001...
[ 162.190820] mmc0: Test case 1. Basic write (no data verification)...
[ 162.209563] mmc0: Result: OK
[ 162.212462] mmc0: Tests completed.
为了方便测试,可以通过以下脚本 批量运行完所有的mmc_test测试项:
#!/bin/sh
run_one_testcase()
{
echo $1 > test
if [ $? -ne 0 ]; then
echo ERROR: Testcase $i failed!
exit 110
fi
}
run_memtest()
{
echo Prepare the debugfs ...
mount -t debugfs none /sys/kernel/debug/
cd /sys/kernel/debug/mmc0/mmc0:0001
echo
echo Run memtest ...
echo
CNT=1
while true
do
echo
echo ----------------------------------------------------------
echo Run all the testcase, count $CNT ...
echo ----------------------------------------------------------
echo
echo Run in order sequence ...
echo
for i in `seq 0 51`;
do
run_one_testcase $i
done
echo
echo Run in random sequence ...
echo
for i in `seq 0 51`;
do
$TMP=`expr $RANDOM % 52`
run_one_testcase $TMP
done
CNT=`expr $CNT + 1`
done
}
run_memtest()
4.3. iozone测试
iozone测试需要用到块设备,必须要将内核中的MMC Block打开:
Linux
Device Drivers
<*> MMC/SD/SDIO card support
<*> MMC block device driver
iozone本身是用来做性能测试,如果使用脚本长时间的循环运行就可用于稳定性测试。
测试步骤:
- 格式化MMC块设备/SDCard块设备为ext4文件系统;
- 将格式化后的块设备挂载到某个路径;
- 在上述路径中运行iozone工具即可。
小技巧
- iozone运行过程中会在当前目录下创建一个临时文件,该文件的大小会根据测试数据粒度而自动调整。
- iozone支持将性能数据作为结果存储到一个Excel文件中,方便查看。
以下是调用iozone的循环测试脚本:
#!/bin/sh
if [ ! -z $1 ] && [ ! -z $2 ]; then
FILE_MIN_SIZE=$1
FILE_MAX_SIZE=$2
else
FILE_MIN_SIZE=16m
FILE_MAX_SIZE=128m
fi
RESULT_FILE=iozone_result.xls
TMP_FILE=iozone.tmp
run_cmd()
{
echo
echo $1
echo
eval $1
}
mount_mmc()
{
HOSTNAME=`hostname`
if [ $HOSTNAME = "Artinchip" ]; then
WORKSPACE_DIR=/mnt/sdcard
MMC_DEV=/dev/mmcblk0p9
echo Mount $MMC_DEV ...
if [ "$1" != "debug" ]; then
# mkfs.vfat $MMC_DEV 2048000
mount -t vfat $MMC_DEV $WORKSPACE_DIR
fi
if [ $? -ne 0 ]; then
echo ERR: Failed to mount $MMC_DEV
exit 100
fi
else
WORKSPACE_DIR=./mnt/sdcard
echo Use the local path: $WORKSPACE_DIR
fi
}
mount_udisk()
{
WORKSPACE_DIR=/mnt/usb
UDISK_DEV=/dev/sda1
echo Mount $UDISK_DEV ...
mount -t vfat $UDISK_DEV $WORKSPACE_DIR
if [ $? -ne 0 ]; then
echo ERR: Failed to mount $UDISK_DEV
exit 100
fi
}
mount_mtd()
{
WORKSPACE_DIR=/mnt/mtd
if [ ! -d $WORKSPACE_DIR ]; then
mkdir -p $WORKSPACE_DIR
fi
}
umount_all()
{
cd -
umount -f $WORKSPACE_DIR
}
check_stop()
{
echo
echo Press \'any key\' + \'EnterKey\' to stop testing
read -t 5 CMD
if [ ! -z $CMD ]; then
umount_all
exit 100
fi
}
if [ -b /dev/sda ]; then
mount_udisk
elif [ -b /dev/mmcblk0 ]; then
mount_mmc $1
elif [ -c /dev/mtd0 ]; then
mount_mtd
else
echo There is no block device in the board!
exit 110
fi
echo Enter workspace $WORKSPACE_DIR
cd $WORKSPACE_DIR
CNT=1
while true
do
echo
echo ----------------------------------------------------------
echo iozone test, count $CNT
echo ----------------------------------------------------------
date
run_cmd "iozone -a -V -n $FILE_MIN_SIZE -g $FILE_MAX_SIZE -q 16m -w -b $RESULT_FILE -f $TMP_FILE"
check_stop
CNT=`expr $CNT + 1`
done
5. 设计说明
5.1. 源码说明
源代码位于 drivers/mmc/host/:
- artinchip-mmc.c,SDMC驱动实现
- artinchip-mmc.h,SDMC的寄存器、数据结构定义
5.2. 模块架构
Linux中提供了MMC子系统,该子系统负责抽象一个块设备提供给通用块层使用,从整个软件的角度来看,架构如下:
图 5.18 Linux MMC子系统架构图
其中:
-
对用户而言,MMC card层提供了一种块设备,和其他块设备使用方法类似
-
-
MMC子系统的核心层的功能有:
对上层请求的处理,其中包括将请求转化为符合MMC协议的逻辑实现对控制器驱动进行管理将外部MMC设备抽象并进行管理
-
-
AIC SDMC控制器驱动:负责通过对寄存器的操作实现MMC子系统传来的请求
5.3. 关键流程设计
5.3.1. 初始化流程
MMC子系统的初始化包括MMC块设备、MMC子系统、MMC控制器驱动、card设备等几条线,初始化顺序:
- 最先进行的是MMC核心初始化
- MC控制器驱动初始化完成后才会对card设备进行初始化
- MMC块设备初始化没有严格的先后顺序
5.3.1.1. MMC 块设备驱动初始化
MMC在使用中,会将其抽象成一个块设备挂载到通用块层当中,通过module_init(mmc_blk_init)完成注册和初始化的操作,主要步骤如下:
- 注册总线(bus_register)
- 将块设备名”mmc”和主设备注册到块层中(register_blkdev)
- 将mmc_driver设备驱动注册到驱动模型中(mmc_register_driver)
- 块设备的初始化及磁盘分区的注册(mmc_blk_probe)
5.3.1.2. MMC 子系统核心初始化
MMC子系统的核心层负责处理block下达的请求,其中关于MMC协议的逻辑主要在此实现,通过subsys_initcall(mmc_init)完成初始化,其步骤如下:
- MMC类型总线注册,(mmc_register_bus)
- 为控制器设备注册一个类,(mmc_register_host_class)
- SDIO类型总线类型注册,(sdio_register_b)