版权声明:本文为博主原创文章,转载请注明出处:https://blog.csdn.net/huang_165/article/details/86678814
由上一篇博文《简单分析mmc体系结构》知道,mmc core会注册一个mmc bus并等待card device、card driver的加入。
也简单地介绍了mmc framework三层大概功能、用到那些内核组件。这篇博文是上一篇博文的源码分析版。
内核版本:
#uname -a
Linux rk3399 4.4.154 #62 SMP Sun Jan 27 17:09:48 PST 2019 aarch64 GNU/Linux
sd 3.0协议文档:https://pan.baidu.com/s/1uQextx64UkZmvbIrYSvLvg
我的源码版本:https://github.com/Mr-jinfa/rk3399-project
2019.1.28号版本的
博文解决两个问题:
一:探究mmc framework如何支持sd卡的冷启动、热插拔的?
二:探究上层文件系统-->块io调度层下来的请求是如何作用到sd卡上的(即从上层应用到硬件层的动作)。
解决这两个问题的方法是参看源码调用关系,建议读者结合博主的《简单分析mmc体系结构》来看。
问题一:探究mmc framework如何支持sd卡的冷启动、热插拔的。
card 冷启动时检查卡并加入到系统中的执行路径
dw_mci_rockchip_probe 初始化mmc主机控制器
|->dw_mci_pltfm_register
| |->dw_mci_probe
| | |->dw_mci_init_slot 初始化插槽
| | | |->mmc_alloc_host 申请一个插槽
| | | | |->INIT_DELAYED_WORK(&host->detect, mmc_rescan); host->detect挂一个服务函数--mmc_rescan
| | | |->mmc->ops = &dw_mci_ops; 填充host操作集
| | | (1)|->dw_mci_get_cd 检查下cd gpio看是否有卡插入,card冷启动会检测到,热插拔的话就等mmc_gpio_cd_irqt执行。
| | | |->mmc_add_host 注册一个host驱动--用来和sd卡通信
| | | |->device_add(&host->class_dev)
| | | |->mmc_start_host 启动host包括检测卡是否有效
| | | |->mmc_claim_host(host) 占用host,也就是占用主机控制器
| | | |->mmc_gpiod_request_cd_irq 为cd_gpio挂一个中断isr,方便以后有卡插入能第一时间探测到.
| | | |->devm_request_threaded_irq:mmc_gpio_cd_irqt中断服务函数
| | | |->_mmc_detect_change 主动检测下卡有无插入,
| | | |->mmc_schedule_delayed_work:host->detect 切换上下文,去执行host->detect
请看detect切换点
card 热插拔时检查卡并加入到系统中的执行路径
由上面的冷启动路径可知,在(1)这个位置作为冷启动、热插拔卡的分路,即卡是热插拔的话就等mmc_gpio_cd_irqt,那么我们分析mmc_gpio_cd_irqt。
mmc_gpio_cd_irqt
|->mmc_detect_change
| |->_mmc_detect_change
| | |->if (device_can_wakeup(mmc_dev(host)) sd卡cd gpio具有唤醒cpu功能?
| | | |->pm_wakeup_event
| | |->mmc_schedule_delayed_work:host->detect 切换出去,执行host->detect
请看detect切换点
detect切换点:
host->detect:mmc_rescan
|->host->ops->get_cd(host) 先检查下cd gpio确定是否位卡插入电平
|->mmc_rescan_try_freq 最终调用这个函数来确认sd卡的插入与否
|->mmc_attach_mmc 探测mmc卡
|->mmc_attach_bus
|->mmc_select_voltage 适配电压(mmc协议图示)
|->mmc_init_card 处理这个检测动作、初始化卡
|->mmc_add_card 用驱动模型注册一个新的MMC卡。
|->设置卡的名字、类型
|->mmc_init_context_info 初始化同步上下文,这个上下文将用来同步card driver和host 层
|->init_waitqueue_head(&host->context_info.wait)
|->mmc_of_find_child_device 找到合适的device并增加到mmc bus中。
|->device_add 这样一个card device就生成了,然后mmc bus match、mmc bus probe就开始动作了。
问题二:探究上层文件系统-->块io调度层下来的请求是如何作用到sd卡上的
先找到分析入口:根据博主的博文《Linux下编写一个ramdisk块设备驱动...》提供的源码可知,编写一个块设备驱动需要填充一个请求处理函数。blk_init_queue(sbull_full_request, &dev->lock);sbull_full_request为请求处理函数。
那么在mmc framework中我们在card driver层找到这种请求处理函数就好了,从mmc/card/block.c的probe函数出发。
mmc_blk_probe
|->mmc_blk_alloc 填充一个disk设备
| |->mmc_blk_alloc_req
| | |->md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL) 申请一个块设备用到的数据
| | |->mmc_init_queue:md->queue
| | | |->sema_init(&mq->thread_sem, 1) 线程运行标记-信号量
| | | |->kthread_run:mmc_queue_thread 设置一个线程
| | | |->blk_init_queue:mmc_request_fn 为上层文件系统挂一个请求处理函数
| | |->md->queue.issue_fn = mmc_blk_issue_rq
| | |->md->queue.data = md;
| | |->md->disk->fops = &mmc_bdops;
|->mmc_blk_alloc_parts 设置分区个数
|->mmc_add_disk 分配分区
mmc_request_fn:这个函数是处理请求事件的入口函数-信标
|->wake_up_process:mq->thread 唤醒线程来处理请求
mq->thread:mmc_queue_thread
|->down(&mq->thread_sem)
|->blk_fetch_request 取出一个请求
|->mq->issue_fn(mq, req) 调用mmc_blk_issue_rq-信标
mmc_blk_issue_rq
->mmc_blk_part_switch 选择对应的分区
根据cmd_flags的不同做相应处理:假如是一个普通的读写cmd
->mmc_blk_issue_rw_rq
|->mmc_blk_rw_rq_prep 做些准备工作
|->mmc_start_req 启动传输并从参数返回status
| |->mmc_wait_for_data_req_done 等待sd卡一个respond
| | |->wait_event_interruptible:context_info->wait 在”detect切换点“有初始化这个队列头
| | |->__mmc_start_request 处理请求
| | |->mmc_retune 调整mmc控制器;如时钟速率
| | |->host->ops->request(host, mrq)即dw_mci_request(由dw_mci_init_slot可知)
|->switch (status) 根据返回的status不同做相应处理:假如成功读写
|->mmc_blk_reset_success
|->mmc_blk_simulate_delay
|->mmc_blk_end_packed_req(只发命令,无数据操作的话)
|->blk_end_request(有数据操作
dw_mci_request 这个request是由mmc host 层提供的
|->dw_mci_get_cd先检测下cd gpio看有没有卡
|->dw_mci_queue_request
|->dw_mci_start_request
|->if(host->state == STATE_IDLE) 如果当前主机空闲的话
| @true|->dw_mci_start_request 开始传输
| |->提取cmd
| |->__dw_mci_start_request
| | |->dw_mci_prepare_command 准备命令
| | |->dw_mci_submit_data 提交数据
| | | |->dw_mci_submit_data_dma 提交到mmc host dma处
| | |->dw_mci_start_command 启动dma传输
| @flase|->list_add_tail(&slot->queue_node, &host->queue); 加到host->缓冲队列中
总结一下:
1.mmc framework初始化时会注册一个cd gpio irq中断服务函数、检测下卡有无插入来实现mmc 框架下的sd卡的冷启动、热插拔。
2.mmc framework用一个线程来承接上层request,可以看到request下来的数据如果主机忙就缓存起来,不忙就用dma将数据运载出去。
用线程的好处是:
a.异步处理,在主线程畅快运行时可以通过call "req线程",来让"req线程"做数据操作,req线程卡住也不影响主线程的运行,最多主线程做一些回收工作。
b.主线程、"req线程"都可以休眠,特别是"req线程"它可以在sd卡忙时主动将数据缓存在host->queue中。
c.主线程可以在适当时间做大数据批量传输。
关于mmc framework一些函数说明可以参考:https://blog.csdn.net/zwj0403/article/details/38409061
当然,也可以参考博主github提供的注释。