zoukankan      html  css  js  c++  java
  • 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
    ",
    		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
    ",
    		       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

  • 相关阅读:
    ADL(C++参数依赖查找)
    Sublime Text3 + Golang搭建开发环境
    Zookeeper使用命令行(转载)
    软链接和硬链接(转载)
    kafka伪集群搭建
    使用librdkafka库实现kafka的生产和消费实例生产者
    vector和map使用erase删除元素
    jquery html函数的一个问题
    贪心类区间问题
    快速幂
  • 原文地址:https://www.cnblogs.com/r1chie/p/14792384.html
Copyright © 2011-2022 走看看