uboot/Linux下MMC/SD/SDIO阅读记录

发布时间 2023-10-21 08:04:05作者: ArnoldLu

1 uboot下MMC/SD/SDIO

1.1 uboot下MMC/SD/SDIO相关配置

uboot下关于MMC/SD/SDIO驱动以及工具配置:

Device Drivers  
  ->MMC Host controller Support    
    MMC/SD/SDIO card support     
    support for MMC/SD write operations--支持对MMC/SD/SD Card的写访问。    
    Poll for broken card detection case
    Enable MMC controllers using Driver Model
    Enable quirks
    Support for HW partitioning command(eMMC)
    Support eMMC replay protected memory block (RPMB)--支持对RPMB分区的读写和秘钥烧录。
    Support some additional features of the eMMC boot partitions--支持对boot分区的操作。
    Support IO voltage configuration--支持UHS模式的IO电压调整。
    enable HS400 Enhanced Strobe support--支持HS400 Ehanced Strobe模式,支持高达200MHz总线。不需要对IO调优。
    enable HS400 support--支持HS400模式,高达200MHz总线。需要对IO调优。
    enable HS200 support--支持HS200模式,高达200MHz总线。需要对IO调优。
    Output more information about the MMC
    MMC debugging
    Secure Digital Host Controller Interface support
Command line interface
  ->Device access commands
    ->mmc

1.2 uboot下MMC/SD/SDIO代码

 

1.3 uboot下MMC/SD/SDIO工具

 

2 Linux下MMC/SD/SDIO

Linux下MMC/SD/SDIO已经相当成熟,对于大部分主流SDHCI IP已经支持完整。

当对一个IP进行调试时,修改的大部分集中在DTS中:

  • 配置IP基地址。
  • 配置IP中断号。
  • 配置IP所用到的时钟,包括AHB和工作时钟。
  • 配置IP用到的Regulator。
  • 配置IP pinctrl。

然后:

  • 在调试中根据不同规格的卡,可能需要切换IO电压,Core工作电压
  • 如有需要可能要进行IO驱动能力调试。

然后就是可能使用到的ext4文件系统支持,以及一些工具mmc_test、mmc、fdisk、mkfs.ext4、gdisk等使用进行兼容性和性能测试。

2.1 MMC/SD/SDIO配置以及主要文件

Linux下对MMC/SD/SDIO的配置如下:

Device Drivers
  ->MMC/SD/SDIO card support
    ->MMC block device driver
      ->Number of minors per block device
  ->MMC host test driver--支持mmc_test模块,可以对MMC/SD卡进行一系列读写测试。
  ->MMC host drivers debugging--打开CONFIG_MMC_DEBUG。
  
->Secure Digital Host Controller Interface support     ->SDHCI platform and OF driver helper--基于Platform Device和OF驱动实现的SDHCI Host驱动。       ->Command Queue Host Controller Interface support--Arasan SDHCI需要支持Command Queue。
File systems
  ->The Extended 4 (ext4) filesystem--支持ext4文件系统。

MMC/SD文件位于/drvers/mmc下:

drivers/mmc/
├── core
│   ├── block.c--MMC块设备相关API。
│   ├── bus.c--MMC总线注册注销,以及总线上Device/Driver注册注销接口。
│   ├── core.c--
│   ├── debugfs.c--在/sys/kernel/debug/mmcX/下创建Host以及Device的调试信息。
│   ├── host.c--MMC Host一类设备的分配等API。
│   ├── mmc.c--提供mmc_attach_mmc()函数,MMC Card初始化。
│   ├── mmc_ops.c--MMC/SD协议层操作函数。
│   ├── mmc_test.c--创建mmc_test模块。
│   ├── pwrseq.c--MMC Power Sequence管理,提供注册注销接口,以及PrePowerOn/PostPowerOn/PowerOff/Reset等接口调用。
│   ├── pwrseq_emmc.c--emmc PowerSequence。
│   ├── pwrseq_simple.c--simple PowerSequence。
│   ├── queue.c--初始化struct mmc_queue,主要处理Block设备的请求队列。
│   ├── regulator.c--MMC/SD获取Regulator,以及使用Regulator接口函数。
│   ├── sd.c--提供mmc_attach_sd()函数,SD Card初始化。
│   ├── sd_ops.c-- │ ├── sdio_bus.c--SDIO总线注册,以及Device/Driver注册接口。 │ ├── sdio.c--提供mmc_attach_sdio(),SDIO Card初始化。
│   ├── sdio_ops.c--SDIO相关操作函数。 │ ├── sdio_cis.c--Common/Function CIS信息读取和释放。 │ ├── sdio_io.c--SDIO IO层面的操作API。 │ ├── sdio_irq.c--
│ ├── slot-gpio.c--SD Card热插拔相关GPIO操作API。
├── host │ ├── cqhci.c--通用的SDHCI支持Command Queue功能API接口。 │ ├── sdhci.c--对SDHCI提供一系列API接口。 │ ├── sdhci-of-arasan.c--Arasan基于DeviceTree的SDHCI驱动模块。 │ ├── sdhci-pltfm.c--SDHCI作为platform device的通用API接口。

2.2 Linux MMC/SD/SDIO框架

MMC框架大体如下:

 Kernel的MMC框架可以分为3部分:

  • MMC Core:负责Host Controller、MMC/SD/SDIO Card等硬件抽象,提供Platform Device、OF、Regulator、PowerSeq、Clock、debugfs等功能。提供统一的Host Controller驱动API,以及MMC、SD卡驱动。
  • Host Driver:MMC/SD/SDIO Host设备驱动。
  • 外设驱动:包括SDIO外设等,其中SD卡、MMC等驱动在MMC Core中已经提供。

2.3 SD DTS示例

程序的IP驱动开发主要工作量集中在DTS的配置:

sd0-sdhci@xxxxx {
    compatible = "arasan,sdhci-4.9a";
    reg = <0x00000000 0x1000>;--IP基地址以及地址范围配置。
    clock-names = "clk_xin", "clk_ahb";--工作时钟和AHB寄存器时钟。
    clocks = <&clk_xin>, <&clk_ahb>;
  interrupt-parent = <&gic>;
  interrupts = <GIC_SPI xx IRQ_TYPE_LEVEL_HIGH>;--中断。
  vmmc-supply = <&vddsdcore>;--给Core供电Regulator。
  vqmmc-supply = <&vddsdio>;--给IO供电的Regulator。
  bus-width = <4>;--SD总线位宽。
  cap-sd-highspeed;--支持SD高速时序。
  sd-uhs-sdr12;
...
  sd-uhs-ddr50;--支持的UHS类型。
  non-removable;--不支持热插拔。不配置则支持热插拔。
  no-sdio;--初始化时禁止使用SDIO命令。
  no-mmc;--初始化时禁止使用MMC命令。
  pinctrl-name = "XXX";
  pinctrl-0 = <&sdio_sd_pin>;
};

关于DTS的解释在Documentation/devicetree/bindings/mmc/mmc-controller.yaml中有详细解释《mmc-controller.yaml - Documentation/devicetree/bindings/mmc/mmc-controller.yaml - Linux source code (v6.5.8) - Bootlin》。

2.4 Linux MMC/SD/SDIO代码解读

MMC/SD相关模块:

mmc_init注册mmc、sdio总线,以及mmc_host class。

mmc_pwrseq_simple_driver_init:Simple PowerSequence模块初始化,包含pre_power_on/post_power_on/power_off接口。

mmc_pwrseq_emmc_driver_init:MMC的PowerSequence模块初始化,仅包含reset。

mmc_blk_init注册rpmb_bus,rpmb字符设备;祖册MMC块设备号,注册mmc_driver驱动。主要是为后续块设备使用做准备。

mmc_test_init:mmc_test模块,提供对MMC设备的一系列测试功能。

sdhci_drv_init:空白模块,提供SDHCI API。

sdhci_pltfm_drv_init:空白模块,提供Platform Device的API。

sdhci_arasan_driver_initArasan SDHCI控制器的驱动。

其中mmc_bus的总线、设备、驱动关系如下:

MMC相关总线以及Class初始化:

mmc_init
    ->mmc_register_bus--注册mmc总线,创建/sys/bus/mmc。
    ->mmc_register_host_class--创建mmc_host class,位于/sys/class/mmc_host。
    ->sdio_register_bus--注册sdio总线,创建/sys/bus/sdio。
 MMC块设备初始化:
mmc_blk_init
    ->bus_register
    ->alloc_chrdev_region--注册rpmb字符设备。
    ->register_blkdev--注册MMC块设备。
    ->mmc_register_driver

Arasan SDHCI初始化流程: 

sdhci_arasan_probe
    ->sdhci_pltfm_init
        ->devm_platform_ioremap_resource--映射寄存器。
        ->platform_get_irq--获取中断号。
        ->sdhci_alloc_host--分配struct sdhci_host结构体,并注册以及进行设备扫描等。
    ->devm_clk_get--获取clk_ahb和clk_xin两个时钟。clk_ahb是寄存器工作时钟,clk_xin是SD工作时钟。
    ->sdhci_arasan_add_host
    ->sdhci_setup_host--主要填充struct sdhci_host结构体。
      ->mmc_regulator_get_supply--获取vmmc和vqmmc两个Regulator。vmmc为SD Core供电,vqmmc为IO供电。
    ->cqhci_init--CommandQueue功能初始化。
    ->__sdhci_add_host--为SDHCI Host创建工作队列、timer、中断处理等,然后初始化Host。
      ->sdhci_init
      ->mmc_add_host
      ->sdhci_enable_card_detection

MMC/SD/SDIO卡上电流程:

sdhci_pltfm_init->sdhci_alloc_host->mmc_alloc_host(创建delayed_work,1HZ扫描一次)
  -
>mmc_rescan->mmc_rescan_try_req
    ->mmc_power_up--首先上电但不提供时钟。     ->mmc_attach_sd--按照SDIO->SD->MMC的顺序依次尝试挂载。
      ->mmc_attach_bus--配置mmc总线操作函数集为mmc_sd_ops。       ->mmc_sd_init_card--检测并初始化SD卡。         
->mmc_sd_get_cid--从SD卡读取CID。           ->mmc_set_uhs_voltage->mmc_host_set_uhs_voltage->mmc_set_signal_voltage(host->ops->start_signal_voltage_switch)--如果是UHS则设置IO电压。             ->sdhci_start_signal_voltage_switch               ->mmc_regulator_set_vqmmc--调用DTS配置的Regulator配置IO电压。
        ->mmc_alloc_card
        ->mmc_sd_setup_card--读取SD卡的SCR/SSR/Read-Only等属性。
        ->mmc_sd_init_uhs_card--UHS-I类型SD卡初始化。
          ->mmc_execute_tuning
            ->sdhci_execute_tuning(host->ops->execute_tuning)
              ->sdhci_start_tuning
              ->__sdhci_execute_tuning
                ->sdhci_send_tuning
              ->sdhci_end_tuning

 sdhci_add_host/__sdhci_add_host创建一个SDHCI Host,并进行初始化。

__sdhci_add_host
  ->mmc_add_host
    ->mmc_add_host_debugfs
    ->mmc_start_host       ->mmc_power_up->mmc_set_ios->sdhci_set_ios         ->sdhci_arasan_set_power--Arasan驱动的上下电接口。           ->mmc_regulator_set_ocr--根据OCR的VDD配置,调用Regulator API进行电压设置。
          
->sdhci_set_power_noreg       ->__mmc_detect_change

2.5 Linux下MMC工具

2.5.1 mmc_test

使用mmc_test驱动可以对MMC设备进行一系列读写测试。

默认情况下MMC/SD设备和mmcblk驱动绑定:

ls /sys/bus/mmc/drivers/*
/sys/bus/mmc/drivers/mmc_test:
bind    uevent  unbind

/sys/bus/mmc/drivers/mmcblk:
bind       mmc0:0001  uevent     unbind

如果要使用mmc_test对MMC/SD进行测试,首先需要将设备和mmcblk驱动解绑,然后和mmc_test驱动绑定。再进行测试。

步骤如下:

1. 将设备和mmcblk驱动解绑。

 echo mmc0:0001 > /sys/bus/mmc/drivers/mmcblk/unbind

2. 将设备和mmc_test绑定。

# echo mmc0:0001 > /sys/bus/mmc/drivers/mmc_test/bind
# [ 4082.341913] 000: mmc_test_register_dbgfs_file
[ 4082.342008] 000: mmc_test mmc0:0001: Card claimed for testing.

3. 查看设备的测试用例列表。

cat /sys/kernel/debug/mmc0/mmc0:0001/testlist
0:      Run all tests
1:      Basic write (no data verification)
...50:     Commands during non-blocking read - use Set Block Count (CMD23)
51:     Commands during non-blocking write - use Set Block Count (CMD23)

4. 执行测试用例。

echo 5 > /sys/kernel/debug/mmc0/mmc0:0001/test

2.5.2 mmc

2.6 GPT格式说明以及Linux对GPT处理

2.6.1 GPT格式说明

UEFI Specification 2.10》中定义了GPT格式。

LBA(Logical Block Address)0是第一个Logical Block,用于保存MBR或者PMBR。

GPT由GPT Header和GPT Partition Entries组成:

  • GPT Header包括签名、版本号,已经用于校验的CRC32等信息。
  • GPT Partition Entry包含了GUID,以及LBA范围、属性、分区名等信息。

GPT Header如下:

Mnemonic

Byte Offset

Byte Length

Description

Signature

0

8

Identifies EFI-compatible partition table header. This value must contain the ASCII string “EFI PART”, encoded as the 64-bit constant 0x54 52415020494645.

Revision

8

4

The revision number for this header. This revision value is not related to the UEFI Specification version. This header is version 1.0, so the correct value is 0x00010000.

HeaderSize

12

4

Size in bytes of the GPT Header. The HeaderSize must be greater than or equal to 92 and must be less than or equal to the logical block size.

HeaderCRC32

16

4

CRC32 checksum for the GPT Header structure. This value is computed by setting this field to 0, and computing the 32-bit CRC for HeaderSize bytes.

Reserved

20

4

Must be zero.

MyLBA

24

8

The LBA that contains this data structure.

AlternateLBA

32

8

LBA address of the alternate GPT Header.

FirstUsableLBA

40

8

The first usable logical block that may be used by a partition described by a GUID Partition Entry.

LastUsableLBA

48

8

The last usable logical block that may be used by a partition described by a GUID Partition Entry.

DiskGUID

56

16

GUID that can be used to uniquely identify the disk.

Par titionEntryLBA

72

8

The starting LBA of the GUID Partition Entry array.

NumberOfPa rtitionEntries

80

4

The number of Partition Entries in the GUID Partition Entry array.

SizeOf PartitionEntry

84

4

The size, in bytes, of each the GUID Partition Entry structures in the GUID Partition Entry array. This field shall be set to a value of 128 x 2 n where n is an integer greater than or equal to zero (e.g., 128, 256, 512, etc.). NOTE: Previous versions of this specification allowed any multiple of 8..

PartitionE ntryArrayCRC32

88

4

The CRC32 of the GUID Partition Entry array. Starts at Par titionEntryLBA and is computed over a byte length of NumberOfP artitionEntries * SizeOfP artitionEntry.

Reserved

92

BlockSize - 92

The rest of the block is reserved by UEFI and must be zero.

GPT Partition Entry如下:

Mnemonic

Byte Offset

Byte Length

Description

PartitionTypeGUID

0

16

Unique ID that defines the purpose and type of this Partition. A value of zero defines that this partition entry is not being used.

UniquePartitionGUID

16

16

GUID that is unique for every partition entry. Every partition ever created will have a unique GUID. This GUID must be assigned when the GPT Partition Entry is created. The GPT Partition Entry is created whenever the NumberOfPa rtitionEntries in the GPT Header is increased to include a larger range of addresses.

StartingLBA

32

8

Starting LBA of the partition defined by this entry.

EndingLBA

40

8

Ending LBA of the partition defined by this entry.

Attributes

48

8

Attribute bits, all bits reserved by UEFI ( Defined GPT Partition Entry — Partition Type GUIDs.

PartitionName

56

72

Null-terminated string containing a human-readable name of the partition.

Reserved

128

SizeOf PartitionEntry - 128

The rest of the GPT Partition Entry, if any, is reserved by UEFI and must be zero.

GPT一般存在两份:在开始的主分区表和结尾的备份分区表。

GPT Header必须位于LBA1,备份GPT Header必须位于最后一个LBA。

GPT Header定义了First Usable LBA和Last Usable LBA,所有的数据必须在者之间存储。

Primary GPT Partition Entry数组必须在Primary GPT Header之后,First Usable LBA之前。

Backup GPT Partition Entry必须在Backup GPT Header之前,Last Usable LBA之后。

GPT Partition Entry数组可以包括0或者多个GPT PartitionEntry,每个partition必须不交叉覆盖。

必须为GPT Partition Entry数组预留16384字节分区。

如果块大小为512B,则需要预留34及以上块:1(PMBR)+1(GPT Header)+16KB(GPT Partition Entry)。

如果块大小为4096B,则需要预留6及以上块:1(PMBR)+1(GPT Header)+16KB(GPT Partition Entry)。

 2.6.2 Linux GPT处理

在mmcblk驱动中对GPT分区进行了处理:

mmc_blk_probe
    ->mmc_add_disk
        ->device_add_disk->__device_add_dsk
            ->register_disk
                ->blkdev_get
                    ->__blk_dev_get
                        ->bdev_disk_changed
                            ->blkdev_reread_part
                                ->rescan_partitions
                     ->check_partition   
->efi_partition--从设备中读取GPT Header/Entry信息,并填充struct parsed_partitions结构体。
                          ->find_valid_gpt--校验GPT是否有效,如有效则将数据读取到变量中。
                            ->is_pmbr_valid--判断LBA0是否是PMBR。
                            ->is_gpt_valid--读取GPT Header,并通查是否有效;读取GPT Entry方便后续处理。并比较Primary GPT和Backup GPT。
                          ->is_pte_valid--检查PTE的GUID、LBA范围确认是否有效。
                          ->put_partition--将从GPT Entry解析的分区信息写到struct parsed_partitions中。
                    ->add_partition--向通用磁盘分区管理添加一个分区信息。

2.6.3 分区处理工具

fdisk

gdisk

sgdisk

lsblk

partx

2.7 MMC裸烧镜像制作

 

 

参考文档:《Linux MMC framework(1)_软件架构 (wowotech.net)》《Linux MMC介绍》《emmc模块》。