MMC/SD设备驱动在Linux中的结构层次
在Linux中MMC/SD卡的记忆体都当作块设备。drivers\mmc 分别有card、core和host三个文件夹
- card层 要把操作的数据以块设备的处理方式写到记忆体上或从记忆体上读取;
因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的SD 卡如何实现为块设备的。 - core层 则是将数据以何种格式,何种方式在 MMC/SD主机控制器与MMC/SD卡的记 忆体(即块设备)之间进行传递,这种格式、方式被称之为规范或协议.
整个MMC 的核心存,这部分完成了不同协议和规范的实现,并为HOST 层的驱动提供了接口函数。 - host层 下的代码就是你要动手实现的具体MMC/SD设备驱动了,包括RAM芯片中的 SDI控制器(支持对MMC/SD卡的控制,俗称MMC/SD主机控制器)和SDI控制器与MMC/SD卡的硬件接口电路。
针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的。其中,card(区块层) 与core(核心层)是linux系统封装好了部分,我们不需要修改,host(主控制器层)中提供与各芯片构架相关的文件,这才是我们所要开发的部分.
hdj@M425:/workspace/kernel/drivers/mmc$ ls
built-in.o card core host Kconfig Makefile modules.builtin modules.order
核心层根据需要构造各种MMC/SD命令,这些命令怎么发送给MMC/SD卡呢?这通过主机控制器层来实现。
设置MMC/SD/SDIO控制器使用到的GPIO引脚、使能控制器、注册中断处理函数等,然后向上面的核心层增加一个主机(Host),这样核心层就能调用host/dw_mmc-rockchip.c(以rk平台为例子)提供的函数来识别、使用具体存储卡了。
结构体
struct mmc_host 用来描述卡控制器
struct mmc_card 用来描述卡
struct mmc_driver 用来描述 mmc 卡驱动
struct sdio_func 用来描述 功能设备
struct mmc_host_ops 用来描述卡控制器操作接口函数功能,用于从 主机控制器层向 core 层注册操作函数,从而将core 层与具体的主机控制器隔离。也就是说 core 要操作主机控制器,就用这个 ops 当中给的函数指针操作,不能直接调用具体主控制器的函数。
host代码解析
根据rk平台sdmmc 对应dw_mmc-rockchip.c
下面列出识别存储卡、区块层发起操作请求两种情况下函数的主要调用关系:
添加识别卡
dw_mci_rockchip_probe //(host/dw_mmc-rockchip.c) 匹配节点
dw_mci_pltfm_register //(host/dw_mmc-pltfm.c ) 为 struct dw_mci *host赋值
dw_mci_probe //(host/dw_mmc.c) 解析dts配置信息,解析在https://blog.csdn.net/h_8410435/article/details/105426863
dw_mci_init_slot //初始化配置的几个slot.
mmc_alloc_host //(core/host.c) 创建mmc_host(对象),在mmc_alloc_host()中进行一些通用设置的初始化
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
mmc_rescan //(core/core.c) ******插入卡后执行此处,添加识别卡*****
mmc_rescan_try_freq
mmc_go_idle //发送CMD0
mmc_set_chip_select
mmc_set_ios
mmc_attach_sd //(mmc_attach_sd) 匹配sd卡 sd卡入口点
mmc_send_app_op_cond //发送SD_APP_OP_COND(ACMD41)命令用来判断卡的工作电压是否符合,如果不符合的话,卡应该放弃总线操作
mmc_select_voltage //电压选择和判断,下面解释
mmc_sd_init_card//初始化一个 card ,涉及内容挺多,包括get_cid,协议相关内容.参考:https://www.cnblogs.com/fengeryi/p/3469782.html sdi时钟和波特率的有关设置
mmc_sd_get_cid //从卡取cid
mmc_alloc_card //申请卡
host->ops->init_card //init card
mmc_sd_switch_hs //设置模式
mmc_set_clock //设置clok
mmc_add_card
mmc_of_parse //解析dts配置信息,将配置信息赋值给host->caps
dw_mci_get_cd //得到卡
mmc_add_host //加到host
mmc_start_host
_mmc_detect_change
mc_schedule_delayed_work // 下面有解释
dw_mci_enable_cd //使能card detect
总结:主要两个mmc_alloc_host 申请一个mmc_host.
mmc_add_host 添加一个mmc_host.
- mc_schedule_delayed_work // workqueue 这个工作队列当中添加一个延迟的工作任务,而这个工作任务就是由 host->detect 来描述的,在随后的 delay 个 jiffies 后会有一个记录在 host->detect 里面的函数被执行,workqueue 这个工作队列还在忙,不一会儿它就会调用 host->detect 里面那个函数,这个函数到底是哪个函数,对应的为INIT_DELAYED_WORK(&host->detect, mmc_rescan); 这个在检测是否插入sd卡,我们平时用的 SD/MMC 卡就是一个卡,如果要操作它得用 SD/MMC 卡控制器才行,所以可以看到有 struct mmc_card,struct mmc_host 的区分。
卡的检测
参考:https://www.cnblogs.com/yanghong-hnu/p/4671352.html
dw_mci_probe
devm_request_irq(host->dev, host->irq, dw_mci_interrupt,host->irq_flags, "dw-mci", host);
dw_mci_interrupt
dw_mci_handle_cd(host);
mmc_detect_change
mmc_schedule_delayed_work(&host->detect, delay);
匹配执行INIT_DELAYED_WORK(&host->detect, mmc_rescan);中的mmc_rescan进行card的扫描
我们平时用的 SD/MMC 卡就是一个卡,如果要操作它得用 SD/MMC 卡控制器才行,所以可以看到有 struct mmc_card,struct mmc_host 的区分。
到这里了,来回忆一下 dw_mci_init_slot 这个函数做的事情,大概就是准备一个 mmc_host 结构,然后添加一个主控制器设备到内核,最后又调用了一下 mmc_rescan 来检测是不是有卡插入了。
如果有卡插入了还好,可以去操作卡了,那如果没有卡插入呢? mmc_rescan 不是白调用了一次吗?是啊,的确是白调用了一次。可是卡插入时为什么 PC 还是能检测到呢?看来卡检测的动作不光是在 probe 的最后一步做了一次,其它地方也有做。卡插入一般都是人为地随时去插入的,像这种情况一般都是会用中断机制去提供系统有外来侵入,然后再去采取行动。
中断函数如上
扫描函数
mmc_rescan
mmc_rescan_try_freq
mmc_go_idle(host); //sd卡协议,发送CMD0
mmc_send_if_cond(host, host->ocr_avail); //发送CMD8,等待接收sd卡回应,以此判断是sd. sdio,mmc
mmc_attach_sdio(host) //以sdio为例子
mmc_send_io_op_cond(host, 0, &ocr); //发送了CMD5,搞到了ocr的值.这个值不知道咋来的
mmc_sdio_init_card(host, rocr, NULL, 0); //初始化一个 card ,这个 card 就用 struct mmc_card 结构来描述,然后又调用 mmc_add_card 将卡设备添加到了内核
mmc_alloc_card
struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type)
{
struct mmc_card *card;
card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);
if (!card)
return ERR_PTR(-ENOMEM);
card->host = host;
device_initialize(&card->dev);
card->dev.parent = mmc_classdev(host);
card->dev.bus = &mmc_bus_type;
card->dev.release = mmc_release_card;
card->dev.type = type;
return card;
}
struct mmc_card 结构里面包含了一个 struct device 结构, mmc_alloc_card 不但申请了内存,而且还填充了 struct device 中的几个成员,尤其 card->dev.bus = &mmc_bus_type; 这一句要重点对待。
static struct bus_type mmc_bus_type = {
.name = "mmc",
.dev_groups = mmc_dev_groups,
.match = mmc_bus_match,
.uevent = mmc_bus_uevent,
.probe = mmc_bus_probe,
.remove = mmc_bus_remove,
.shutdown = mmc_bus_shutdown,
.pm = &mmc_bus_pm_ops,
};
申请一个 mmc_card 结构,并简单初始化后, mmc_init_card 的使命就完成了,然后再调用 mmc_add_card 将这个 card 设备添加到内核。 mmc_add_card 其实很简单,就是调用 device_add 将 card->dev 添加到内核当中去。
device_add 里面,设备对应的总线会拿着你这个设备和挂在这个总线上的所有驱动程序去匹配( match ),此时会调用 match 函数,如果匹配到了就会调用总线的 probe 函数或驱动的 probe 函数
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
{
return 1;
}
看来 match 永远都能成功,那就去执行 probe 吧:
static int mmc_bus_probe(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = dev_to_mmc_card(dev);
return drv->probe(card);
}
这里就有点麻烦了,在这个函数里面又调用了一下 drv->probe() ,那这个 drv 是什么呢?上面
struct mmc_driver*drv=to_mmc_driver(dev->driver);
match 函数总是返回 1 ,那看来只要是挂在这条总线上的 driver 都有可能跑到这里来了,事实的确也是这样的,不过好在挂在这条总线上的 driver 只有一个,它是这样定义的:
static struct mmc_driver mmc_driver = {
.drv = {
.name = "mmcblk",
},
.probe = mmc_blk_probe,
.remove = mmc_blk_remove,
.suspend = mmc_blk_suspend,
.resume = mmc_blk_resume,
};
看到这里时, card/core/host 几个已经全部被扯进来了,边看 mmc_driver 中的几个函数,他们几个如何联系起来也就慢慢明白了。
card代码分析
static int mmc_blk_probe(struct mmc_card *card) // 来自 card/block.c
{
struct mmc_blk_data *md;
int err;
……
md = mmc_blk_alloc(card); //主要函数
if (IS_ERR(md))
return PTR_ERR(md);
……
add_disk(md->disk);
return 0;
out:
mmc_blk_put(md);
return err;
}
块设备驱动的整个套路了:
- 分配、初始化请求队列,并绑定请求队列和请求函数。
- 分配,初始化 gendisk ,给 gendisk 的 major , fops , queue 等成员赋值,最后添加 gendisk 。
- 注册块设备驱动。
MMC 卡驱动程序套路:
- mmc_init_queue 初始了队列,并将 mmc_blk_issue_rq; 函数绑定成请求函数;
- alloc_disk 分配了 gendisk 结构,并初始化了 major , fops ,和 queue ;
- 最后调用 add_disk 将块设备加到 KERNEL 中去。
电压选择
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)
{
int bit;
/*
* Sanity check the voltages that the card claims to
* support.
*/
if (ocr & 0x7F) { //根据协议保留前七位,查看下面ocr 电压
dev_warn(mmc_dev(host),
"card claims to support voltages below defined range\n");
ocr &= ~0x7F;
}
ocr &= host->ocr_avail; //判断host实际所支持的电压与card所需要的电压是否匹配,如果匹配,那么ocr的值就非0
if (!ocr) {
dev_warn(mmc_dev(host), "no support for card's volts\n");
return 0;
}
if (host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) {
bit = ffs(ocr) - 1; // 它的作用就是返回参数中第一个为1的bit的位置(ffs(0)=0,ffs(1)=1,ffs(8)=4),那么该函数用在这里的作用就是取出card需要的实际电压是多少
ocr &= 3 << bit;
mmc_power_cycle(host, ocr); //供电
} else {
bit = fls(ocr) - 1;
ocr &= 3 << bit;
if (bit != host->ios.vdd)
dev_warn(mmc_dev(host), "exceeding card's volts\n");
}
return ocr;
}
void mmc_power_cycle(struct mmc_host *host, u32 ocr)
{
mmc_power_off(host);
/* Wait at least 1 ms according to SD spec */
mmc_delay(1);
mmc_power_up(host, ocr); //就是上电,不添加代码了
}
##总结
分析到这里, MMC/SD 卡的驱动整个构架基本也就很明析了,说简单了就是做了两件事:
卡的检测;
卡数据的读取。
1.卡的检测
mmc_alloc_host(core/core.c)
mmc_rescan(core/core.c)
mmc_attach_mmc(core/mmc.c)
mmc_init_card(core/mmc.c)
mmc_add_card(core/bus.c)
device_add
mmc_bus_match(core/bus.c)
mmc_bus_probe(core/bus.c)
mmc_blk_probe(card/block.c)
alloc_disk/add_disk
2.读写数据
`mmc_blk_issue_rq ( card/block.c )
mmc_wait_for_req(core/core.c)
mmc_start_request(core/core.c)
host->ops->request(host, mrq)