1.linux MMC
- 内核:linux 4.9
1.1 分析mxs-mmc.c
从别人的驱动程序分析是最好入手的。直接找到mxs_mmc_probe
来进行分析:
static int mxs_mmc_probe(struct platform_device *pdev)
{
.....
struct mxs_mmc_host *host;
struct mmc_host *mmc;
.....
mmc = mmc_alloc_host(sizeof(struct mxs_mmc_host), &pdev->dev);
if (!mmc)
return -ENOMEM;
.....
mmc->ops = &mxs_mmc_ops;
.....
ret = mmc_add_host(mmc);
}
把代码简化到这样再分析,其他被简化的部分基本上都是一些硬件的初始化操作,每个厂家都不一定是一样的,只需要分析重点即可。
1.1.2 mmc_alloc_host
mmc_alloc_host
顾名思义,就是给mmc控制器申请一个结构体:
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
·····
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
·····
spin_lock_init(&host->lock);
init_waitqueue_head(&host->wq);
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
setup_timer(&host->retune_timer, mmc_retune_timer, (unsigned long)host);
·····
}
这里面除了给struct mmc_host申请一块内存外,还创建了一个延时的工作队列,当调用了queue_delayed_work(workqueue, work, delay)
时,在随后的 delay 个 jiffies 后会有一个记录在 host->detect 里面的函数被执行,也就是mmc_rescan
函数。
1.1.3 mmc_add_host
将创建并初始化好的host加入到内核中。
int mmc_add_host(struct mmc_host *host)
{
......
err = device_add(&host->class_dev);
......
mmc_start_host(host);
if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
mmc_register_pm_notifier(host);
return 0;
}
重点看一看mmc_start_host
函数,进到里面最终可以看到在_mmc_detect_change
里面调用了 mmc_schedule_delayed_work(&host->detect, delay);
如果再进去看就会发现其实调用的是queue_delayed_work(workqueue, work, delay)
,刚刚我们分析过了,最终会导致mmc_rescan
被调用。也就是说,当调用mmc_add_host
时,会去调用mmc_rescan
。那么这里面究竟做了什么?
1.1.4 mmc_rescan
void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
int i;
.......
for (i = 0; i < ARRAY_SIZE(freqs); i++) {
if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))
break;
if (freqs[i] <= host->f_min)
break;
}
......
}
大部分是一些上电掉电之类的操作,我们不管,重点看mmc_rescan_try_freq
这个函数:
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
......
/* Order's important: probe SDIO, then SD, then MMC */
if (!(host->caps2 & MMC_CAP2_NO_SDIO))
if (!mmc_attach_sdio(host))
return 0;
if (!(host->caps2 & MMC_CAP2_NO_SD))
if (!mmc_attach_sd(host))
return 0;
if (!(host->caps2 & MMC_CAP2_NO_MMC))
if (!mmc_attach_mmc(host))
return 0;
......
}
这里面就是检测三种设备,我们挑mmc_attach_sd
,看看它到底是怎么检测的:
int mmc_attach_sd(struct mmc_host *host)
{
......
err = mmc_send_app_op_cond(host, 0, &ocr);
if (err)
return err;
......
err = mmc_sd_init_card(host, rocr, NULL);
if (err)
goto err;
......
err = mmc_add_card(host->card);
......
}
检测的函数是`mmc_send_app_op_cond`,这个函数是向发送一些命令的,特定的命令只有特定的设备会回。那么是怎么发送呢?在`mmc_send_app_op_cond`里面会调用 `mmc_wait_for_app_cmd`,最终又会调用`host->ops->request(host, mrq);`,ops是结构体`mmc_host_ops`,这个是在nxp已经实现好的操作集:
static const struct mmc_host_ops mxs_mmc_ops = {
.request = mxs_mmc_request,
.get_ro = mmc_gpio_get_ro, //获取写状态
.get_cd = mxs_mmc_get_cd, //卡是否存在
.set_ios = mxs_mmc_set_ios, //设置参数
.enable_sdio_irq = mxs_mmc_enable_sdio_irq,
};
所以最终是调用mxs_mmc_request
,来向设备发送命令的。
如果是成功的话,那么接下来就是调用mmc_sd_init_card
进行初始化了,这个函数里面会调用mmc_alloc_card
申请一个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->dev.bus = &mmc_bus_type;
card->dev.release = mmc_release_card;
card->dev.type = type;
return card;
}
这里面的card->dev.bus = &mmc_bus_type
很重要,接下来card就会挂载到mmc_bus_type总线上。初始化之后,调用mmc_add_card
:
int mmc_add_card(struct mmc_card *card)
{
······
ret = device_add(&card->dev);
······
}
最终就会调用device_add
,那么此时会发生什么?现在这个card会被挂载到mmc_bus_type总线上,那么就会触发总线的match函数:
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,
};
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 = mmc_dev_to_card(dev);
return drv->probe(card);
}
drv->probe(card)实际上是mmc_blk_probe
:
static struct mmc_driver mmc_driver = {
.drv = {
.name = "mmcblk",
.pm = &mmc_blk_pm_ops,
},
.probe = mmc_blk_probe,
.remove = mmc_blk_remove,
.shutdown = mmc_blk_shutdown,
};
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;
mmc_fixup_device(card, blk_fixups);
md = mmc_blk_alloc(card);
if (IS_ERR(md))
return PTR_ERR(md);
string_get_size((u64)get_capacity(md->disk), 512, 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;
dev_set_drvdata(&card->dev, md);
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;
}
这里就是关于块设备的内容了,功力不够,没办法分析。
1.1.4.1 什么时候会调用mmc_rescan
- 从上面可以分析出,在调用
mmc_add_host
时,就会调用一次mmc_rescan
。 - 其实在插入卡的时候就会触发一个中断,在s3cmci.c中
s3cmci_irq_cd
就是这个中断的处理函数了,这里面是会调用mmc_detect_change
,那么最终是会调用mmc_rescan
。
2.linux sdhc
- 内核版本:4.1
sdhc实际上是基于mmc来实现的,引入了一个新的结构体sdhci_host
。具体看代码sdhci-esdhc-imx.c的probe:
static int sdhci_esdhc_imx_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
of_match_device(imx_esdhc_dt_ids, &pdev->dev);
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_host *host;
int err;
struct pltfm_imx_data *imx_data;
host = sdhci_pltfm_init(pdev, &sdhci_esdhc_imx_pdata, 0);
if (IS_ERR(host))
return PTR_ERR(host);
........
err = sdhci_add_host(host);
if (err)
goto disable_clk;
.........
}
在sdhci_pltfm_init
里面调用了sdhci_alloc_host
:
struct sdhci_host *sdhci_alloc_host(struct device *dev,
size_t priv_size)
{
struct mmc_host *mmc;
struct sdhci_host *host;
WARN_ON(dev == NULL);
mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);
if (!mmc)
return ERR_PTR(-ENOMEM);
host = mmc_priv(mmc);
host->mmc = mmc;
return host;
}
可以看到实际上还是使用了mmc_alloc_host
,之前说过了,只要使用了mmc_alloc_host
就会伴随着mmc_rescan
的调用。
接下来看sdhci_add_host
里面做了什么:
int sdhci_add_host(struct sdhci_host *host)
{
struct mmc_host *mmc;
u32 caps[2] = {0, 0};
u32 max_current_caps;
unsigned int ocr_avail;
unsigned int override_timeout_clk;
u32 max_clk;
int ret;
..........
/*
* Set host parameters.
*/
mmc->ops = &sdhci_ops;
max_clk = host->max_clk;
...........
sdhci_init(host, 0);
ret = request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,
IRQF_SHARED, mmc_hostname(mmc), host);
if (ret) {
pr_err("%s: Failed to request IRQ %d: %d\n",
mmc_hostname(mmc), host->irq, ret);
goto untasklet;
}
.........
mmc_add_host(mmc);
.........
}
static const struct mmc_host_ops sdhci_ops = {
.request = sdhci_request,
.post_req = sdhci_post_req,
.pre_req = sdhci_pre_req,
.set_ios = sdhci_set_ios,
.get_cd = sdhci_get_cd,
.get_ro = sdhci_get_ro,
.hw_reset = sdhci_hw_reset,
.enable_sdio_irq = sdhci_enable_sdio_irq,
.start_signal_voltage_switch = sdhci_start_signal_voltage_switch,
.prepare_hs400_tuning = sdhci_prepare_hs400_tuning,
.execute_tuning = sdhci_execute_tuning,
.select_drive_strength = sdhci_select_drive_strength,
.card_event = sdhci_card_event,
.card_busy = sdhci_card_busy,
};
static struct sdhci_ops sdhci_esdhc_ops = {
.read_l = esdhc_readl_le,
.read_w = esdhc_readw_le,
.write_l = esdhc_writel_le,
.write_w = esdhc_writew_le,
.write_b = esdhc_writeb_le,
.set_clock = esdhc_pltfm_set_clock,
.get_max_clock = esdhc_pltfm_get_max_clock,
.get_min_clock = esdhc_pltfm_get_min_clock,
.get_max_timeout_count = esdhc_get_max_timeout_count,
.get_ro = esdhc_pltfm_get_ro,
.set_timeout = esdhc_set_timeout,
.set_bus_width = esdhc_pltfm_set_bus_width,
.set_uhs_signaling = esdhc_set_uhs_signaling,
.reset = esdhc_reset,
.hw_reset = esdhc_hw_reset,
};
首先是把sdhci_ops
传给mmc->ops
,sdhci_ops实际上是这个mmc_host_ops类型的结构体,里面的方法都实现好了,并且这里面最终会调用到sdhci_esdhc_ops
的方法,例如.read_l
或.write_l
。
在插入或拔出时,会触发中断,中断处理函数就是sdhci_thread_irq
,里面调用mmc_detect_change
,那么最终就是会去调用mmc_rescan
函数。
最后调用mmc_add_host
函数注册该host