分析的目的很简单:希望支持掉电安全,或者说具有奔溃一致性特性的文件系统,他的读写速度能得到提升。如果了解了瓶颈所在,也可触类旁通。
1 概述
已支持的API接口:
-
sysio(系统I/O):
- 系统I/O通常指的是直接使用操作系统提供的系统调用来进行文件I/O操作。
- 它提供了对文件描述符(file descriptors)的操作,这是Unix-like系统中的核心概念。
- 系统I/O函数,如
open
、read
、write
、close
等,是底层I/O操作的基础,它们直接与内核交互。 - 系统I/O通常比标准I/O更快,因为它减少了中间层,但它也要求程序员处理更多的细节,如错误处理和缓冲管理。
-
stdio(标准I/O):
- 标准I/O是C语言标准库提供的一组高级I/O函数,如
fopen
、fprintf
、fscanf
、fclose
等。 - 它基于系统I/O,但提供了一种更方便、更高级的接口。标准I/O库会处理缓冲,减少对系统调用的直接调用,从而提高性能。
- 标准I/O还包括对格式化输入输出的支持,这使得处理文本数据更加容易。
- 标准I/O流(如
stdin
、stdout
、stderr
)是预定义的,可以方便地用于程序的输入和输出。
- 标准I/O是C语言标准库提供的一组高级I/O函数,如
-
posixio(POSIX I/O):
- POSIX I/O实际上并不是一个独立的I/O模型,而是指遵循POSIX标准的I/O操作。
- POSIX是一套标准化的接口,主要用于Unix-like操作系统,旨在提高软件的源代码级别的可移植性。
- POSIX I/O包括了系统I/O的函数,同时也包括了其他一些标准的I/O操作,如文件和目录操作、进程控制等。
- POSIX I/O接口通常包括在
unistd.h
头文件中,它们确保了在不同的Unix-like系统上提供一致的I/O操作。
已支持的文件系统:
vfs
devfs
rootfs
fatfs
littlefs
yaffs
tmpfs
shmfs
nfs
2 执行流程
2.1 示意图
![[fs-fk1.png]]
2.2 测试用例
#define MAX_FILE_COUNT 8
#define MAX_FILE_SIZE (10*1024*1024)
#define FILE_BUF_SIZE (10*1024*1024)
for (files = 0; files < MAX_FILE_COUNT; files++) {
if (!test_create_file_speed(&start_time,&write_time)) {
return false;
}
}
static bool test_create_file_speed(os_tick_t *start_time, os_tick_t *write_time)
{
int fd = open(test_files[file_index].name, O_WRONLY | O_CREAT | O_TRUNC,S_IRUSR|S_IWUSR);
if (fd == -1)
{
return false;
}
while (written != test_files[file_index].size)
{
uint32_t remaining = test_files[file_index].size - written;
test_files[file_index].checksum += fill_test_data(test_buf, remaining);
*start_time = os_tick_get_value();
if (write(fd, test_buf, remaining) != remaining)
{
return false;
}
*write_time += os_tick_get_value() - *start_time;
written += remaining;
}
total_written += written;
return close(fd) == 0;
}
2.3 lfs_vfs适配层
static const struct vfs_fops _lfs_fops = {
.f_open = vfs_lfs_open,
.f_write = vfs_lfs_write,
.f_read = vfs_lfs_read,
.f_close = vfs_lfs_close,
.f_lseek = vfs_lfs_lseek,
.f_sync = vfs_lfs_sync,
.f_ftruncate = vfs_lft_ftruncate,
};
2.4 lfs_io适配层
int lfs_flash_write(const struct lfs_config *cfg,
lfs_block_t block,
lfs_off_t off,
const void *buffer,
lfs_size_t size)
{
uint32_t len;
os_device_t *dev;
uint32_t block_ct;
uint32_t hw_blocksize;
LFS_ASSERT(cfg != NULL);
LFS_ASSERT(cfg->context != NULL);
LFS_ASSERT(buffer != NULL);
hw_blocksize = cfg->block_size / LFS_BLOCK_SIZE_FACTOR;
block = (block * cfg->block_size + off) / hw_blocksize;
block_ct = size/hw_blocksize+(size%hw_blocksize?1:0);
dev = (os_device_t *)((lfs_device_info_t *)cfg->context)->dev;
len = os_device_write_nonblock(dev, block, buffer, block_ct);
if (len != block_ct)
{
return LFS_ERR_IO;
}
return LFS_ERR_OK;
}
2.5 lfs_config配置
static int vfs_lfs_cfg_init(lfs_part_info_t *part_info, os_device_t *dev)
{
int ret;
struct os_blk_geometry geometry;
if(OS_FAILURE == os_device_control(dev, OS_DEVICE_CTRL_BLK_GETGEOME, &geometry))
ret = -ENODEV;
else
{
memset(&part_info->lfs, 0, sizeof(lfs_t));
memset(&part_info->config, 0 ,sizeof(struct lfs_config));
part_info->dev_info.dev = (void *)dev;
part_info->config.context = &part_info->dev_info;
part_info->config.read = g_lfs_dev_ops.read;
part_info->config.prog = g_lfs_dev_ops.prog;
part_info->config.erase = g_lfs_dev_ops.erase;
part_info->config.sync = g_lfs_dev_ops.sync;
//从flash中读取数据的最小size,并且起始地址需要按照这个size对齐
part_info->config.read_size = geometry.block_size*LFS_READ_SIZE_FACTOR;
//往flash中写入数据的最小size,并且起始地址需要按照这个size对齐
part_info->config.prog_size = geometry.block_size*LFS_PROG_SIZE_FACTOR;
//可擦除块的大小,可大于物理擦除大小,但必须是读取和程序大小的倍数
part_info->config.block_size = geometry.block_size*LFS_BLOCK_SIZE_FACTOR;
part_info->config.block_count = geometry.capacity / (geometry.block_size*LFS_BLOCK_SIZE_FACTOR);
//是用于预读的块数量,以优化读取性能
part_info->config.block_cycles = LFS_BLOCK_CYCLE;
//文件系统的读写时,数据在内存中的缓存大小
part_info->config.cache_size = geometry.block_size*LFS_BLOCK_SIZE_FACTOR;
//空闲块滑窗的大小(in bytes),littlefs用一个滑窗来管理所有的空闲块
part_info->config.lookahead_size = LFS_LOOKAHEAD_SIZE;
part_info->config.read_buffer = OS_NULL;
part_info->config.prog_buffer = OS_NULL;
part_info->config.lookahead_buffer = OS_NULL;
ret = 0;
}
return ret;
}
3 对比测试
本次分析,使用了大量的对比测试:
littlefs | 写(KB/S) | 读(KB/S) | 分析 |
---|---|---|---|
lfs初始速度 read_size=512, prog_size=512, block_size=512, lookahead_size=8 | 0.765178101 | 18.980361328 | 初始速度 |
lookahead_size=256 | 18.695521484 | 18.400935547 | ⬆ |
更新littlefs到最新版本,lookahead_size=256 | 18.781650391 | 18.402720703 | - |
lookahead_size=8192 | 34.649105469 | 18.399744141 | ⬆ |
block_cycles从-1改为1000 | 34.795378906 | 18.393941406 | - |
lookahead_size=8k ,block_size=120k=240sector | 3729.2665 | 3441.53175 | 需要同步更改lfs_io文件 |
lookahead_size=8k,block_size=1024sector | 5603.085 | 7898.793 | ⬆ |
取消fal | 9439.222 | 7844.319 | 取消fal对读速度影响很大,对写没有影响 |
直接使用块驱动接口,不使用驱动框架 | 9323.166 | 7871.461 | - |
更改测试用例 | 11524.075 | 8597.326 | ⬆ |
read_size=128sector, prog_size=1024sector, block_size=1024sector | 13287.461 | 19012.166 | 减小了readsize |
emmc:HS200 | 20705.880 | 32309.614 | 提升了emmc的时钟频率 |
直接调用驱动接口os_device_write_nonblock/os_device_read_nonblock
:
驱动接口 | 写(KB/S) | 读(KB/S) | 分析 |
---|---|---|---|
1024block,emmc:HS | 25.87 MB/s | 28.57 MB/s | 直接调用驱动接口os_device_write_nonblock |
4block,emmc:HS | 0.25MB/s | 0.33MB/s | - |
1024block,emmc:HS200 | 37.97 MB/s | 53.71 MB/s | 稳定性 |
4block,emmc:HS200 | 0.26 MB/s | 0.33 MB/s |
在uboot上使用命令mmcinfo
:
Device: sdhci@fe310000
Manufacturer ID: 45
OEM: 100
Name: DG401
Timing Interface: HS200
Tran Speed: 200000000
Rd Block Len: 512
MMC version 5.1
High Capacity: Yes
Capacity: 14.7 GiB
Bus Width: 8-bit
Erase Group Size: 512 KiB
HC WP Group Size: 8 MiB
User Capacity: 14.7 GiB WRREL
Boot Capacity: 4 MiB ENH
RPMB Capacity: 4 MiB ENH
eMMC 5.1:最大读写速率为200MB/s(HS200模式)
linux下使用iozone测试emmc上的ext4 | MB/s | 备注 |
---|---|---|
iozone -i 0 -r 1024KB -s 200M -f /w_test3 -w -I | 81.461 | 写操作:Record Size 1024 kB |
iozone -i 0 -r 2KB -s 200M -f /w_test3 -w -I | 1.805 | 写操作:Record Size 2 kB |
iozone -i 1 -r 1024KB -s 200M -f /w_test3 -w -I | 138.197 | 读操作:Record Size 1024 kB |
dd if=/dev/mmcblk0 of=/dev/null bs=1M count=1024 | 190 | 读操作,接近emmc最大读写 |
4 性能差异对比
4.1 修改驱动初始化
eMMC规范定义了多种操作模式,每种模式对应一个最大时钟频率。例如,eMMC的HS200模式支持高达200MHz的时钟频率,而HS400模式支持高达200MHz的时钟频率,并且是双数据速率。
在Linux内核中,mmc
结构体的 clock
成员通常是一个整数值,表示当前控制器的时钟频率。这个值是以kHz为单位的,因此如果一个 mmc
结构体的 clock
成员值为 52000,那么它表示控制器的当前时钟频率是 52 MHz。
通过在uboot上使用命令mmcinfo
,发现uboot下,emmc使用的模式是HS200模式,而OneOS使用的模式是HS,即52 MHz。在rockchip_sdhci.c
的接口arasan_sdhci_probe
中:配置host->host_caps |= MMC_MODE_HS200;
,可让读写速率提升。
4.2 一次性读取多个块
为何驱动接口一次性读取更多的block会更快?
在eMMC存储中,一次性读取更多的数据块可能会更快的因素包括:
- 减少命令开销:每次读取操作之前,主机需要向eMMC发送命令。如果一次性读取更多的数据块,可以减少命令的发送次数,从而降低命令传输的开销。
- 提高数据传输带宽利用率:eMMC接口支持高速数据传输,一次性读取更多的数据块可以更充分地利用接口的带宽,从而提高数据传输的效率。
- 利用eMMC的缓存:eMMC设备内部通常包含一定大小的缓存,用于优化数据传输。一次性读取更多的数据块可以更有效地利用这个缓存,减少访问实际闪存介质的次数。
- 减少事务处理次数:eMMC的读写操作是通过事务来管理的,每个事务包括命令、响应和数据传输。减少事务处理的次数可以提高整体的传输效率。
- 优化闪存操作:eMMC使用的是闪存存储技术,闪存的读取操作在处理大量数据时更为高效。一次性读取更多的数据块可以减少对闪存的编程/擦除操作,从而提高读取速度。
- 减少接口切换:eMMC接口支持多种操作模式,包括数据传输模式和命令模式。一次性读取更多的数据块可以减少在这些模式之间切换的次数,从而节省时间。
4.3 lfs读写操作
为何lfs的读写操作要比直接调用驱动接口更慢?
- 抽象层开销:LFS作为文件系统,提供了文件和目录的抽象。这意味着在进行读写操作时,LFS需要处理文件系统的元数据,如目录结构、文件属性等,这增加了额外的开销。
- 缓存和缓冲:文件系统通常会使用缓存来优化对存储设备的访问。LFS可能有自己的缓存策略,这可能会增加读写操作的复杂性,尤其是在缓存失效或需要刷新缓存到存储设备时。
- 事务日志:LFS可能使用事务日志来确保文件系统的原子性和一致性。这意味着每次写操作可能需要额外的步骤来记录日志,这会增加写操作的延迟。
- 额外的写入操作:为了维护文件系统的结构,LFS可能需要进行额外的写入操作,比如更新文件系统的分配位图或者索引数据结构。
- 碎片处理:文件系统可能会随着时间的推移产生碎片,这需要LFS进行额外的管理工作,如动态地分配和回收存储空间,这可能会导致读写操作变慢。
- 同步和异步操作:直接调用驱动接口的读写操作可能更直接地控制同步和异步行为。而文件系统可能会为了数据的完整性而默认使用同步操作,这会阻塞直到数据完全写入或读取。
- 文件系统的一致性检查:LFS可能需要在启动或特定操作时执行一致性检查,以确保文件系统的完整性,这可能会导致额外的延迟。
- 系统调用开销:使用文件系统通常涉及到系统调用,这比直接在用户空间操作硬件驱动接口要慢,因为系统调用涉及到用户空间与内核空间之间的上下文切换。
总的来说,LFS提供了丰富的文件管理功能,但这些功能在提供便利的同时也带来了额外的性能开销。对于需要极高读写性能的应用,直接操作存储设备驱动接口可能会更高效,但这通常需要更复杂的错误处理和设备管理逻辑。
4.4 fal速率慢
为何fal让写速率变慢?
int fal_blk_write_block(os_blk_device_t *blk, uint32_t block_addr, const uint8_t *buff, uint32_t block_nr)
{
int ret = OS_FAILURE;
int count;
struct fal_blk_device *fal_blk_dev = (struct fal_blk_device *)blk;
fal_part_t *part = fal_blk_dev->fal_part;
const struct fal_flash *fal_flash = part->flash;
uint32_t start_addr = blk->geometry.block_size * block_addr;
uint32_t end_addr = blk->geometry.block_size * (block_addr + block_nr) - 1;
uint32_t fal_block_start_addr = start_addr & ~(fal_flash->block_size - 1);
uint32_t fal_block_end_addr = end_addr & ~(fal_flash->block_size - 1);
uint32_t fal_block_size = fal_block_end_addr - fal_block_start_addr + fal_flash->block_size;
uint8_t *fal_block_buff = os_calloc(1, fal_block_size);
count = fal_part_read(part, fal_block_start_addr, fal_block_buff, fal_block_size);
memcpy(fal_block_buff + start_addr % fal_flash->block_size, buff, block_nr * blk->geometry.block_size);
count = fal_part_erase(part, fal_block_start_addr, fal_block_size);
count = fal_part_write(part, fal_block_start_addr, fal_block_buff, fal_block_size);
ret = OS_SUCCESS;
os_free(fal_block_buff);
return ret;
}
在接口fal_blk_write_block
中调用了fal_part_erase
擦除接口,他会执行擦除操作,把所有的位都置1。
但其实对于eMMC存储设备,开发者通常不需要手动调用擦除接口。eMMC设备的控制器已经对底层的闪存操作进行了抽象和封装,包括必要的擦除操作。这意味着当应用程序写入数据到eMMC时,控制器会自动处理闪存块的管理,包括在必要时擦除和重写数据。
4.5 提高lfs_config
为何提高block_size,read_size,prog_size会让性能提升?
修改它们的同时,需要同步修改lfs_io.c
文件,之间的lfs_flash_write/lfs_flash_read
只能一次写入一个block,导致性能较弱。提高size
大小,最终对应到os_device_write_nonblock
,可以让驱动接口一次性读写多个块,从而提升性能。
直接从存储介质读取:如果数据既不在预缓存也不在读缓存中,且满足一定条件(如:大小大于等于缓存提示值并且读取位置对齐),则跳过缓存,直接从存储介质读取数据到用户缓冲区。
选择数据块:如果当前文件不在读取模式或当前块已读完,函数会找到包含文件当前位置的数据块。对于非内联文件,使用lfs_ctz_find
查找数据块。
加载数据到读缓存:如果上述条件不满足,将数据加载到读缓存中,确保读取位置对齐,并读取尽可能多的数据,但不超过缓存大小和块大小。
为何提高littlefs的lookahead_size,能让性能提升?
- 减少查找开销:当LittleFS需要分配新的存储块时,它会使用
lookahead buffer
来预先检查和标记一系列可用块。一个更大的lookahead buffer
意味着可以预先检查更多的块,从而减少在实际写入数据时寻找可用块所需的时间和开销。这在连续分配多个块时特别有效,因为它减少了对闪存的重复扫描。 - 优化块分配策略:由于
lookahead buffer
是按位图形式组织的,更大的尺寸意味着能够覆盖更多的物理块状态。这样,文件系统可以在更宽的范围内进行块分配选择,有助于实现更好的磨损平衡,避免某些块过早达到擦写极限,从而延长存储介质的寿命。 - 减少写入放大:通过更有效的块分配,可以减少不必要的擦除和重写操作(写入放大),因为文件系统可以更好地规划数据的存放位置,避免频繁地移动数据来腾出空间。这对于基于闪存的存储介质(如eMMC、NAND Flash)尤为重要,因为这些介质的擦写操作相比读取操作要慢得多且寿命有限。
- 提高顺序写入速度:对于连续的大块数据写入,较大的
lookahead_size
可以确保文件系统有足够多的连续空闲块来满足需求,从而减少碎片化,提高顺序写入的速度。
5 瓶颈总结
基于块设备的文件系统读写速率,影响其速率的瓶颈在于:
- 文件系统本身的配置,以
littlefs
为例,提高littlefs的lookahead_size,block_size,read_size,prog_size会让性能提升。 - 对块设备提供的接口使用:os_device_write_nonblock/os_device_read_nonblock,每次写多个sector,要比写一个快很多倍。
- 驱动的设置:通过更改eMMC的操作模式从HS改为HS200模式,可显著提升性能。
6 附录
6.1 dd命令
额外对照linux
的dd
命令:
[root@ok3568:/]# dd if=/dev/mmcblk0 of=/dev/null bs=1M count=10240
10240+0 records in
10240+0 records out
10737418240 bytes (11 GB, 10 GiB) copied, 56.8519 s, 189 MB/s
[root@ok3568:/]#
[root@ok3568:/]#
[root@ok3568:/]# dd if=/dev/mmcblk0 of=/dev/null bs=10k count=10240
10240+0 records in
10240+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0.0671878 s, 1.6 GB/s
[root@ok3568:/]# dd if=/dev/mmcblk0 of=/dev/null bs=100k count=10240
10240+0 records in
10240+0 records out
1048576000 bytes (1.0 GB, 1000 MiB) copied, 0.431398 s, 2.4 GB/s
[root@ok3568:/]# dd if=/dev/mmcblk0 of=/dev/null bs=1k count=10240
10240+0 records in
10240+0 records out
10485760 bytes (10 MB, 10 MiB) copied, 0.0223845 s, 468 MB/s
在linux
上,使用 dd
命令测试 eMMC 的读写速度时,得到的结果可能远大于预期,这可能是由于几个原因造成的:
- 缓存:操作系统或存储设备可能会使用缓存来临时存储数据,这样
dd
命令在读写时可能会直接与缓存交互,而不是实际的 eMMC 设备。这可能导致测试结果偏大,因为缓存的速度通常远高于存储设备的速度。 - 内存映射文件系统:在某些情况下,测试的文件可能会被存储在内存中,而不是直接写入 eMMC。例如,如果测试文件位于一个内存映射的文件系统(如tmpfs)上,
dd
命令的测试结果将反映内存的速度,而不是 eMMC 的实际速度。 - 测试方法:
dd
命令的测试方法可能会影响结果。例如,如果测试时使用的块大小(bs选项)很小,那么可能会得到更高的速度,因为dd
需要执行更多的I/O操作,这可能会受到系统其他活动的影响。 - 系统负载:如果系统在测试期间运行其他任务,这些任务可能会影响
dd
命令的性能,导致测试结果不准确。 - eMMC 特性:eMMC 设备可能具有不同的性能模式,例如 HS200、HS400 等。如果设备支持更高的性能模式,但在测试时没有正确配置,可能会导致测试结果低于设备的实际性能。
- 文件系统缓存:文件系统可能会缓存写操作,直到它们被刷新到存储设备上。如果
dd
命令在测试写操作后没有正确关闭文件描述符或同步文件系统,可能会导致测试结果偏大。
6.2 iozone命令
在linux
上,使用 iozone
命令测试 文件系统的读写速度时,得到的结果可能远大于预期,这和dd命令的原因非常相近,最大的问题其实是缓存,为了避免缓存影响使用 iozone
或 dd
命令时的测试结果,可以采取以下措施:
-
使用
sync
和fsync
: 在运行iozone
或dd
命令之前,使用sync
命令确保所有缓冲区和缓存中的数据都被写入到磁盘。对于iozone
,可以在测试之前使用fsync
函数来确保文件系统缓冲区被刷新到存储设备。 -
使用
direct
I/O: 对于dd
命令,可以使用conv=direct
选项来执行直接 I/O,这将绕过操作系统缓存,直接对存储设备进行读写操作。dd if=/dev/zero of=/path/to/file bs=1M count=1024 conv=direct
-
使用
oflag
和iflag
参数: 在dd
命令中,可以使用oflag=sync
和iflag=sync
参数来同步 I/O,确保每次读写操作都直接与存储设备交互。dd if=/dev/zero of=/path/to/file bs=1M count=1024 oflag=sync
-
使用
iozone
的适当选项:iozone
命令有一些选项可以用来控制缓存的影响,例如-I
选项可以用来执行直接 I/O 测试。确保在运行iozone
时使用了正确的选项。 -
关闭文件系统的缓存: 对于某些文件系统,可以通过挂载选项临时关闭缓存。例如,在 Linux 中,可以使用
mount
命令的sync
选项来挂载文件系统。mount -o remount,sync /mount/point
-
使用专用的性能测试工具: 使用专门设计用于存储性能测试的工具,如
fio
,它可以提供更细粒度的控制,包括如何处理缓存和同步。 -
测试前清理缓存: 在运行测试之前,可以使用
echo 3 > /proc/sys/vm/drop_caches
命令(在 Linux 系统上)来清理页面缓存、目录项缓存和inode缓存。
因此,在linux下使用iozone测试emmc下的文件系统ext4:
MB/s | ||
---|---|---|
iozone -i 0 -r 1024KB -s 200M -f /w_test3 -w -I | 81.461 | 写操作:Record Size 1024 kB |
iozone -i 0 -r 2KB -s 200M -f /w_test3 -w -I | 1.805 | 写操作:Record Size 2 kB |
iozone -i 1 -r 1024KB -s 200M -f /w_test3 -w -I | 138.197 | 读操作:Record Size 1024 kB |
6.3 emmc配置分析
# fdisk -l /dev/mmcblk0
Number Start (sector) End (sector) Size Name
1 16384 24575 4096K uboot
2 24576 32767 4096K misc
3 32768 98303 32.0M boot
4 98304 163839 32.0M recovery
5 163840 229375 32.0M backup
6 229376 12812287 6144M rootfs
7 12812288 13074431 128M oem
8 13074432 13139967 32.0M boot_b
9 13139968 30777310 8611M userdata
6.4 uboot上的mmcinfo
在uboot上使用命令mmcinfo
:
Device: sdhci@fe310000
Manufacturer ID: 45
OEM: 100
Name: DG401
Timing Interface: HS200
Tran Speed: 200000000
Rd Block Len: 512
MMC version 5.1
High Capacity: Yes
Capacity: 14.7 GiB
Bus Width: 8-bit
Erase Group Size: 512 KiB
HC WP Group Size: 8 MiB
User Capacity: 14.7 GiB WRREL
Boot Capacity: 4 MiB ENH
RPMB Capacity: 4 MiB ENH
在驱动的mmc_init
里面添加打印mmc
的结构体成员:
const char *timing[] = {
"Legacy", "High Speed", "High Speed", "SDR12",
"SDR25", "SDR50", "SDR104", "DDR50",
"DDR52", "HS200", "HS400", "HS400 Enhanced Strobe"};
printf("-------Timing Interface: %s-------\r\n", timing[mmc->timing]);
printf("-------mmc->capacity:%d---------\r\n",mmc->capacity);
printf("-------mmc->high_capacity:%d---------\r\n",mmc->high_capacity);
printf("-------mmc->bus_width:%d---------\r\n",mmc->bus_width);
printf("-------Tran Speed: %d-------\r\n", mmc->clock);
printf("-------Rd Block Len: %d-------\r\n", mmc->read_bl_len);
#define IS_SD(x) ((x)->version & SD_VERSION_SD)
#define IS_MMC(x) ((x)->version & MMC_VERSION_MMC)
#define EXTRACT_SDMMC_MAJOR_VERSION(x) \
(((u32)(x) >> 16) & 0xff)
#define EXTRACT_SDMMC_MINOR_VERSION(x) \
(((u32)(x) >> 8) & 0xff)
#define EXTRACT_SDMMC_CHANGE_VERSION(x) \
((u32)(x) & 0xff)
printf("%s version %d.%d", IS_SD(mmc) ? "SD" : "MMC",
EXTRACT_SDMMC_MAJOR_VERSION(mmc->version),
EXTRACT_SDMMC_MINOR_VERSION(mmc->version));
if (EXTRACT_SDMMC_CHANGE_VERSION(mmc->version) != 0)
printf(".%d", EXTRACT_SDMMC_CHANGE_VERSION(mmc->version));
printf("\r\n");
结果如下:
Timing Interface: High Speed
mmc->capacity:-1421869056
mmc->high_capacity:1
mmc->bus_width:8
Tran Speed: 52000000
Rd Block Len: 512
MMC version 5.1