Linux mmc

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值