linux的sd/mmc驱动保存在源代码的\drivers\mmc目录下,而卡驱动的在mmc模块的card层的block文件中。
一、mmc_driver(card.h)结构
/*
* MMC device driver (e.g., Flash card, I/O card...)
*/
struct mmc_driver {
struct device_driver drv;
int (*probe)(struct mmc_card *);
void (*remove)(struct mmc_card *);
int (*suspend)(struct mmc_card *);
int (*resume)(struct mmc_card *);
void (*shutdown)(struct mmc_card *);
};
这个结构是对内核块设备驱动device_driver的一个封装,除了device_driver,还定义了几个函数指针;
其实在device_driver中,这些函数指针它都有,不妨看一下:
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
所以,在看代码的时候要注意,不要混淆了这两者的函数。
本文只讨论mmc_driver结构的相关函数指针的实现。
二、mmc_driver(block.c)变量
在block.c中定义了一个mmc_driver类型的mmc_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,
.shutdown = mmc_blk_shutdown,
};
那下面将一一分析每个函数指针指向的函数,如果我前面系列的文章中已经讲过的函数,这里会指出在哪篇文章讲过,就不在这里分析了,但是会总结函数的功能。
2.1、mmc_blk_probe
static int mmc_blk_probe(struct mmc_card *card)
{
struct mmc_blk_data *md, *part_md;
char cap_str[10];
/*
* Check that the card supports the command class(es) we need.
*/
if (!(card->csd.cmdclass & CCC_BLOCK_READ))
return -ENODEV;
md = mmc_blk_alloc(card);
if (IS_ERR(md))
return PTR_ERR(md);
string_get_size((u64)get_capacity(md->disk) << 9, STRING_UNITS_2,
cap_str, sizeof(cap_str));
pr_info("%s: %s %s %s %s\n",
md->disk->disk_name, mmc_card_id(card), mmc_card_name(card),
cap_str, md->read_only ? "(ro)" : "");
if (mmc_blk_alloc_parts(card, md))
goto out;
mmc_set_drvdata(card, md);
mmc_fixup_device(card, blk_fixups);
if (mmc_add_disk(md))
goto out;
list_for_each_entry(part_md, &md->part, part) {
if (mmc_add_disk(part_md))
goto out;
}
pm_runtime_set_autosuspend_delay(&card->dev, 3000);
pm_runtime_use_autosuspend(&card->dev);
/*
* Don't enable runtime PM for SD-combo cards here. Leave that
* decision to be taken during the SDIO init sequence instead.
*/
if (card->type != MMC_TYPE_SD_COMBO) {
pm_runtime_set_active(&card->dev);
pm_runtime_enable(&card->dev);
}
return 0;
out:
mmc_blk_remove_parts(card, md);
mmc_blk_remove_req(md);
return 0;
}
这个函数是一个比较核心的函数,在
emmc/sd 区块层解析的第二节中花了很大篇幅去分析,这里总结一下:
1、创建消息对列及消息对列处理函数;
2、创建了gendisk,并进行初始化;
3、将磁盘及分区添加进系统内核。
2.2、mmc_blk_remove
static void mmc_blk_remove(struct mmc_card *card)
{
struct mmc_blk_data *md = mmc_get_drvdata(card);
mmc_blk_remove_parts(card, md);
pm_runtime_get_sync(&card->dev);
mmc_claim_host(card->host);
mmc_blk_part_switch(card, md);
mmc_release_host(card->host);
if (card->type != MMC_TYPE_SD_COMBO)
pm_runtime_disable(&card->dev);
pm_runtime_put_noidle(&card->dev);
mmc_blk_remove_req(md);
mmc_set_drvdata(card, NULL);
}
第3行:通过card获取
mmc_blk_data,通过前面的系列文章(
emmc/sd 区块层解析的第二节)我们知道
mmc_blk_data包含了消息对列、磁盘等基本所有要用到的数据。
第6行:调用mmc_blk_remove_parts来删除磁盘及分区,我们展开看一下:
static void mmc_blk_remove_parts(struct mmc_card *card,
struct mmc_blk_data *md)
{
struct list_head *pos, *q;
struct mmc_blk_data *part_md;
__clear_bit(md->name_idx, name_use);
list_for_each_safe(pos, q, &md->part) {
part_md = list_entry(pos, struct mmc_blk_data, part);
list_del(pos);
mmc_blk_remove_req(part_md);
}
}
mmc_blk_remove_parts的第8行:遍历本驱动对应的分区链表指针;
mmc_blk_remove_parts的第9行:获取指针对应的分区;
mmc_blk_remove_parts的第10行:删除分区在链表中的数据;
mmc_blk_remove_parts的第11行:从内核删除该分区及消息对列,在下面会将到;
回到mmc_blk_remove函数:
第7行:调用内核函数pm_runtime_get_sync增加设备应用计数;
第8行:获取host;
第9行:向主磁盘发送切盘命令;
第10行:释放host;
第11--12行:将设备从运行时列表中删除;(当设备出于运行时列表中时,如果设备很久没用,可以运行时休眠)。
第13行:递减设备的使用计数,和第7行对应;
第14行:调用mmc_blk_remove_req,上面也有调用到该函数,这里分析一下:
static void mmc_blk_remove_req(struct mmc_blk_data *md)
{
struct mmc_card *card;
if (md) {
/*
* Flush remaining requests and free queues. It
* is freeing the queue that stops new requests
* from being accepted.
*/
card = md->queue.card;
mmc_cleanup_queue(&md->queue);
if (md->flags & MMC_BLK_PACKED_CMD)
mmc_packed_clean(&md->queue);
if (md->disk->flags & GENHD_FL_UP) {
device_remove_file(disk_to_dev(md->disk), &md->force_ro);
if ((md->area_type & MMC_BLK_DATA_AREA_BOOT) &&
card->ext_csd.boot_ro_lockable)
device_remove_file(disk_to_dev(md->disk),
&md->power_ro_lock);
del_gendisk(md->disk);
}
mmc_blk_put(md);
}
}
mmc_blk_remove_req的第12行:清除对列,并停止对列处理函数,释放存放数据的聚散链表;
mmc_blk_remove_req的第13--14行:这里涉及设备的命令打包特性:就是一段连续的读块或者一段连续的写块操作可以打包成一个多块的操作命令,这样可以提高设备的性能(JESD84-B50的6.6.30这一节有介绍)。回到代码,这里如果设备支持打包,则清除打包的命令。
mmc_blk_remove_req的第15--20行:删除创建的设备文件;
mmc_blk_remove_req的第22行:从内核删除磁盘,做和add_disk相反的操作。
mmc_blk_remove_req的第24行:mmc_blk_put函数又调用put_disk,释放在内核中注册的object对象。
那么mmc_blk_remove_req讲完了,该函数完成的功能:
删除主设备的队列,删除主设备跟系统相关的文件,从内核删除设备,删除注册的object对象。
回到第二节的mmc_blk_remove函数,第15行:将指向card的驱动设备置为空。
那么mmc_blk_remove函数,讲完了,总结一下:
1、从内核删除分区及消息对列;
2、切换到主分区;
3、从删除主分区及消息对列;
4、取消设备和驱动模块的关系。
2.3、mmc_blk_suspend
static int mmc_blk_suspend(struct mmc_card *card)
{
return _mmc_blk_suspend(card);
}
_mmc_blk_suspend展开如下:
static int _mmc_blk_suspend(struct mmc_card *card)
{
struct mmc_blk_data *part_md;
struct mmc_blk_data *md = mmc_get_drvdata(card);
if (md) {
mmc_queue_suspend(&md->queue);
list_for_each_entry(part_md, &md->part, part) {
mmc_queue_suspend(&part_md->queue);
}
}
return 0;
}
从代码可以,知道_mmc_blk_suspend对主分区和其它分区调用了mmc_queue_suspend函数,展开:
/**
* mmc_queue_suspend - suspend a MMC request queue
* @mq: MMC queue to suspend
*
* Stop the block request queue, and wait for our thread to
* complete any outstanding requests. This ensures that we
* won't suspend while a request is being processed.
*/
void mmc_queue_suspend(struct mmc_queue *mq)
{
struct request_queue *q = mq->queue;
unsigned long flags;
if (!(mq->flags & MMC_QUEUE_SUSPENDED)) {
mq->flags |= MMC_QUEUE_SUSPENDED;
spin_lock_irqsave(q->queue_lock, flags);
blk_stop_queue(q);
spin_unlock_irqrestore(q->queue_lock, flags);
down(&mq->thread_sem);
}
}
这个函数,判断当前分区的消息对列是否挂载,如果已经挂载则退出函数,否则先将挂载标记设为挂载,在调用blk_stop_queue函数取消消息对列的消息处理。
/**
* blk_stop_queue - stop a queue
* @q: The &struct request_queue in question
*
* Description:
* The Linux block layer assumes that a block driver will consume all
* entries on the request queue when the request_fn strategy is called.
* Often this will not happen, because of hardware limitations (queue
* depth settings). If a device driver gets a 'queue full' response,
* or if it simply chooses not to queue more I/O at one point, it can
* call this function to prevent the request_fn from being called until
* the driver has signalled it's ready to go again. This happens by calling
* blk_start_queue() to restart queue operations. Queue lock must be held.
**/
void blk_stop_queue(struct request_queue *q)
{
cancel_delayed_work(&q->delay_work);
queue_flag_set(QUEUE_FLAG_STOPPED, q);
}
回到mmc_blk_suspend函数,总结一下:
1、调用_mmc_blk_suspend函数停止各个分区的消息处理。
2.4、mmc_blk_resume
static int mmc_blk_resume(struct mmc_card *card)
{
struct mmc_blk_data *part_md;
struct mmc_blk_data *md = mmc_get_drvdata(card);
if (md) {
/*
* Resume involves the card going into idle state,
* so current partition is always the main one.
*/
md->part_curr = md->part_type;
mmc_queue_resume(&md->queue);
list_for_each_entry(part_md, &md->part, part) {
mmc_queue_resume(&part_md->queue);
}
}
return 0;
}
这个函数的代码结构和2.3、mmc_blk_suspend的代码结构基本一样的,不过是用mmc_queue_resume替换成了mmc_queue_suspend,mmc_queue_resume的操作正好和mmc_queue_suspend的操作相反,这里就不再贴代码了。
总结:1、mmc_blk_resume激活磁盘各个分区的消息处理。
2.5、mmc_blk_shutdown
static void mmc_blk_shutdown(struct mmc_card *card)
{
_mmc_blk_suspend(card);
}
我们再看一眼2.3、mmc_blk_suspend函数的实现,发现一模一样。
那么sd/mmc的卡驱动模块函数就分析完了,下此如果在哪里用到,就到这里来看一下吧。