linux mmc 框架源码分析
文章基于
-
内核版本
- linux-3.2.0x CPU
- 芯唐 的 NUC972
- 源码路径 linux-3.10.x\drivers\mmc
文章仅分析 sd卡这类设备在 Linux mmc 框架中的源码
本文基于个人的理解与思考, 如有错误的地方欢迎指正.
linux mmc 框架代码目录结构
源码路径 kernel-3.2\drivers\mmc
mmc/
|-- card
| |-- block.c
| |-- block.o
| |-- queue.c
| |-- queue.h
| `-- sdio_uart.c
|-- core
| |-- bus.c
| |-- bus.h
| |-- core.c
| |-- core.h
| |-- host.c
| |-- host.h
| |-- quirks.c
| |-- sd.c
| |-- sd.h
| |-- sd_ops.c
| `-- sd_ops.h
`-- host
|-- nuc970_emmc.c
`-- nuc970_sd.c
-
card
- mmc card 层代码, 实现块设备的各种功能接口 core
- mmc 核心层代码, 实现检测外接存储(如sd卡)和生成对应设备的一整个流程 host
- mmc host 层代码,对应不同的cpu有不同的 host 驱动,通过对 cpu mmc 相关寄存器控制实现与外接sd卡的命令/数据收发
- 目录下的 nuc970_sd.c 就是 NUC972 芯片的针对 sd 类的设备的适配器驱动
- 而 nuc970_emmc.c 就是 NUC972 芯片的针对 emmc 类的设备的适配器驱动
linux mmc 框架
linux mmc 分为三层,分别是:
- mmc card 层
- mmc 核心层
- mmc host 层
mmc 核心层
mmc 核心层提供一下功能:
- 提供 mmc host 注册接口: int mmc_add_host(struct mmc_host *host)
- 提供 mmc card 驱动注册接口: int mmc_register_driver(struct mmc_driver *drv)
- 负责外接sd卡的检测和检测到sd卡接入后生成对应总线设备的一整个过程
mmc host 层
- 调用核心层接口 mmc_add_host() 注册一个 mmc host
- 提供最外接sd卡的一系列操作函数, 即 struct mmc_host_ops 结构体的一系列函数, 如 .get_cd 函数实现检测sd卡是否在位
- 实现检测sd卡插入/拔出的机制(如中断)
struct mmc_host_ops 结构体包含了一系列对外接sd卡的操作函数, 结构体中比较常用的回调有以下几个:
struct mmc_host_ops {
...
void (*request)(struct mmc_host *host, struct mmc_request *req);
void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
int (*get_ro)(struct mmc_host *host);
int (*get_cd)(struct mmc_host *host);
void (*enable_sdio_irq)(struct mmc_host *host, int enable);
...
};
其中(与sd卡的数据交互包括两种, 一种是命令 CMD, 一种是数据 DATA)
request 函数
: 进行sd卡数据读写时的回调, 需要控制 mmc 寄存器实现数据发送接收
-
set_ios 函数
- 进行sd卡命令收发时的回调, 需要控制 mmc 寄存器实现命令发送接收 get_ro 函数
- 检测sd卡是否为只读模式的回调 get_cd 函数
- 检测sd卡接入/拔出状态的的回调 enable_sdio_irq 函数
- 使能/关闭 cpu mmc 中断功能的回调
mmc card 层
- 调用核心层接口 mmc_register_driver() 注册一个 mmc crad 层的平台驱动, 用来与平台设备匹配之后生成sd卡对应的块设备
- 实现块设备的分区管理和读写管理
mmc host层
host层平台设备
源码: linux-3.10.x\arch\arm\mach-nuc970\dev.c
添加平台设备
void __init nuc970_platform_init(struct platform_device **device, int size)
platform_add_devices(nuc970_public_dev, ARRAY_SIZE(nuc970_public_dev)); /*添加平台设备*/
static struct platform_device *nuc970_public_dev[] __initdata = {
...
&nuc970_device_sdh,
...
}
struct platform_device nuc970_device_sdh = {
.name = "nuc970-sdh",
.id = -1,
.num_resources = ARRAY_SIZE(nuc970_sdh_resource),
.resource = nuc970_sdh_resource,
.dev = {
.dma_mask = &nuc970_device_sdh_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
static struct resource nuc970_sdh_resource[] = {
[0] = {
.start = NUC970_PA_SDH,
.end = NUC970_PA_SDH + NUC970_SZ_SDH - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_SDH,
.end = IRQ_SDH,
.flags = IORESOURCE_IRQ,
}
};
host层平台驱动
源码: linux-3.10.x\drivers\mmc\host\nuc970_sd.c
注册平台驱动
static struct platform_driver nuc970_sd_driver = {
.probe = nuc970_sd_probe,
...
.driver = {
.name = "nuc970-sdh",
.owner = THIS_MODULE,
},
};
module_platform_driver(nuc970_sd_driver);
注册平台驱动和设备后
平台总线根据 name 为 “nuc970-sdh” 进行平台驱动/设备的匹配
匹配后回调进入 .probe = nuc970_sd_probe
static int nuc970_sd_probe(struct platform_device *pdev)
/*获取平台资源*/
platform_get_resource(pdev, IORESOURCE_MEM, 0);
request_mem_region(res->start, res->end - res->start + 1, DRIVER_NAME)
sema_init(&sdh_fmi_sem, 1); /*信号量初始化*/
devm_pinctrl_get_select(&pdev->dev, "sd0"); /*引脚初始化*/
/*时钟初始化*/
clk_prepare(clk_get(NULL, "sdh_hclk"));
...
/*
* 申请 mmc_host 结构体,并申请一段私有数据段存放 struct nuc970_sd_host 结构体
* mmc_host 结构体描述了 mmc 的工作方式
* nuc970_sd_host 结构体是根据 芯唐nuc970平台 host 层驱动逻辑需求多定义的一个结构体
*/
mmc = mmc_alloc_host(sizeof(struct nuc970_sd_host), &pdev->dev);
->进入 mmc 核心层,进入 struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL); /*申请一段内存存放 struct mmc_host*/
host->rescan_disable = 1; /*这个变量在 mmc 扫描是否外接sd卡时用到, 由于mmc host 还没初始化完成,暂时停止扫描*/
...
dev_set_name(&host->class_dev, "mmc%d", host->index); /*设置设备名*/
...
host->class_dev.class = &mmc_host_class; /*设置设备 class 为 mmc_host_class*/
device_initialize(&host->class_dev); /*初始化设备*/
/*设置 host 的时钟门控
* 通过动态调整 mmc 工作时钟,节省功耗
* 例如sd卡空闲时,关闭 mmc 的时钟
* 这部分功能逻辑这里不作分析
*/
mmc_host_clk_init(host);
/*一些锁的初始化*/
...
init_waitqueue_head(&host->wq); /*初始化一个等待队列 host->wq*/
INIT_DELAYED_WORK(&host->detect, mmc_rescan); /*初始化一个延时任务 mmc_rescan, mmc_rescan 用于检测是否有外接设备, 如sd卡*/
/*初始化主机控制器 host 结构体, 填充好默认参数*/
host->max_segs = 1;
host->max_seg_size = PAGE_CACHE_SIZE;
host->max_req_size = PAGE_CACHE_SIZE;
host->max_blk_size = 512;
host->max_blk_count = PAGE_CACHE_SIZE / 512;
/*根据cpu的sd硬件接口,填充 mmc_host 结构体
* struct mmc_host 结构体是 mmc 核心层处理相关逻辑时使用到的
*/
mmc->ops = &nuc970_sd_ops; /*注册 mmc_host 的功能接口,由核心层回调*/
->nuc970_sd_ops 结构体定义
static const struct mmc_host_ops nuc970_sd_ops = {
.request = nuc970_sd_request, /*处理 mmc 请求, 进行外接 mmc 设备的读写, 如sd卡的读写*/
.set_ios = nuc970_sd_set_ios, /*配置 mmc 硬件接口, 根据传进的 ios 信息, 配置 mmc 的开关/mmc 宽度等信息*/
.get_ro = nuc970_sd_get_ro, /*检测外接 mmc 设备是否处于写保护模式*/
.get_cd = nuc970_sd_card_detect, /*通过sd卡检测脚判断是否有sd卡接入*/
.enable_sdio_irq = nuc900_sd_enable_sdio_irq, /*使能/关闭 mmc 的中断寄存器*/
};
mmc->f_min = 300000; /*mmc 控制器支持的最小频率*/
mmc->f_max = 50000000; /*mmc 控制器支持的最大频率*/
mmc->ocr_avail = MMC_VDD_27_28|MMC_VDD_28_29|MMC_VDD_29_30|MMC_VDD_30_31|MMC_VDD_31_32|MMC_VDD_32_33 | MMC_VDD_33_34; /*mmc 控制器支持的有效电压范围*/
mmc->caps |= (MMC_CAP_4_BIT_DATA|MMC_CAP_SDIO_IRQ|MMC_CAP_SD_HIGHSPEED|MMC_CAP_MMC_HIGHSPEED); /*mmc 控制器支持的功能,如4位数据收发等*/
mmc->max_blk_size = MCI_MAXBLKSIZE;
mmc->max_blk_count = MCI_BLKATONCE