基于S3C2440的嵌入式Linux驱动——MMC/SD子系统解读(二)

时间:2022-11-26 18:53:06

在阅读本文之前,请先掌握以下基本知识,不然请略过本文。

预备知识

熟读LDD3前十章节的内容。

熟悉内核驱动模型(sysfs)和platform总线。

简要了解过SD卡规范。


本文的内容基于如下硬件和软件平台:

目标平台:TQ2440

CPU:s3c2440

内核版本:3.12.5

基于SD规范4.10,即《SD Specifications Part 1 Physical Layer Simplified Specification Version 4.10》。


在阅读MMC子系统时,一个问题随之就会产生:当我们插入一张SD卡时,系统是如何识别到这张SD卡并将它注册进系统的呢?

这一过程,源于MMC控制器驱动的不懈努力。。。。。。下面,我们从控制器驱动开始,来深入挖掘这一过程。

1. MMC控制器驱动

1.1 MMC控制器入口函数及probe方法

本文以三星的s3c2440上的MMC控制器驱动为例,来进行简要的说明。

从MMC控制器驱动的入口函数开始,如下:

下列代码位于:linux/drivers/mmc/host/s3cmci.c

static struct platform_driver s3cmci_driver = {
	.driver	= {
		.name	= "s3c-sdi",
		.owner	= THIS_MODULE,
		.pm	= s3cmci_pm_ops,
	},
	.id_table	= s3cmci_driver_ids,
	.probe		= s3cmci_probe,
	.remove		= s3cmci_remove,
	.shutdown	= s3cmci_shutdown,
};

module_platform_driver(s3cmci_driver);

这里直接调用了platform的驱动注册函数来注册一个名为s3c-sdi的驱动,该驱动将绑定mmc主控制器设备。

为了让该驱动成功绑定MMC主控制器设备,需要先进行移植操作,具体可见:S3C2440 Linux驱动移植——SD卡驱动

绑定成功后,立即会调用probe方法,也就是s3cmci_probe函数,我们来看下:

static int s3cmci_probe(struct platform_device *pdev)
{
	struct s3cmci_host *host;
	struct mmc_host	*mmc;
	int ret;
	int is2440;
	int i;

	/* */
	is2440 = platform_get_device_id(pdev)->driver_data;

	/* 分配struct mmc_host和struct s3cmci_host */
	mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
	if (!mmc) {
		ret = -ENOMEM;
		goto probe_out;
	}

	/* 申请IO管脚*/
	for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) {
		ret = gpio_request(i, dev_name(&pdev->dev));
		if (ret) {
			dev_err(&pdev->dev, "failed to get gpio %d\n", i);

			for (i--; i >= S3C2410_GPE(5); i--)
				gpio_free(i);

			goto probe_free_host;
		}
	}

	host = mmc_priv(mmc);	/* 获取struct s3cmci_host指针*/
	host->mmc 	= mmc;	/* 保存mmc指针*/
	host->pdev	= pdev;	/* 保存平台设备指针*/
	host->is2440	= is2440; /* 是否为2440*/

	host->pdata = pdev->dev.platform_data; /* 保存板级设备信息*/
	if (!host->pdata) {
		/* 如果没有板级设备信息,给出默认的*/
		pdev->dev.platform_data = &s3cmci_def_pdata;
		host->pdata = &s3cmci_def_pdata;
	}

	/* 初始化自旋锁*/
	spin_lock_init(&host->complete_lock);
	/* 初始化1个tasklet,执行pio_tasklet*/
	tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);

	if (is2440) {
		host->sdiimsk	= S3C2440_SDIIMSK;
		host->sdidata	= S3C2440_SDIDATA;
		host->clk_div	= 1;
	} else {
		host->sdiimsk	= S3C2410_SDIIMSK;
		host->sdidata	= S3C2410_SDIDATA;
		host->clk_div	= 2;
	}

	host->complete_what 	= COMPLETION_NONE;
	host->pio_active 	= XFER_NONE;

/* 板级数据可以决定是否使用DMA,也可以通过配置来决定*/
#ifdef CONFIG_MMC_S3C_PIODMA
	host->dodma		= host->pdata->use_dma;  
#endif

	/* 获取寄存器资源*/
	host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!host->mem) {
		dev_err(&pdev->dev,
			"failed to get io memory region resource.\n");

		ret = -ENOENT;
		goto probe_free_gpio;
	}
	/* 申请IO内存空间*/
	host->mem = request_mem_region(host->mem->start,
				       resource_size(host->mem), pdev->name);

	if (!host->mem) {
		dev_err(&pdev->dev, "failed to request io memory region.\n");
		ret = -ENOENT;
		goto probe_free_gpio;
	}

	/* 映射寄存器*/
	host->base = ioremap(host->mem->start, resource_size(host->mem));
	if (!host->base) {
		dev_err(&pdev->dev, "failed to ioremap() io memory region.\n");
		ret = -EINVAL;
		goto probe_free_mem_region;
	}

	/*获取IRQ	*/
	host->irq = platform_get_irq(pdev, 0);
	if (host->irq == 0) {
		dev_err(&pdev->dev, "failed to get interrupt resource.\n");
		ret = -EINVAL;
		goto probe_iounmap;
	}

	/* 注册IRQ*/
	if (request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)) {
		dev_err(&pdev->dev, "failed to request mci interrupt.\n");
		ret = -ENOENT;
		goto probe_iounmap;
	}

	/* We get spurious interrupts even when we have set the IMSK
	 * register to ignore everything, so use disable_irq() to make
	 * ensure we don't lock the system with un-serviceable requests. */

	/* 禁止中断并设置中断状态*/
	disable_irq(host->irq);
	host->irq_state = false;

	/* 使用detect功能,则申请gpio */
	if (!host->pdata->no_detect) {
		ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect");
		if (ret) {
			dev_err(&pdev->dev, "failed to get detect gpio\n");
			goto probe_free_irq;
		}
		/* 根据gpio获取中断号*/
		host->irq_cd = gpio_to_irq(host->pdata->gpio_detect);

		/* 中断号有效则注册该中断*/
		if (host->irq_cd >= 0) {
			if (request_irq(host->irq_cd, s3cmci_irq_cd,
					IRQF_TRIGGER_RISING |
					IRQF_TRIGGER_FALLING,
					DRIVER_NAME, host)) {
				dev_err(&pdev->dev,
					"can't get card detect irq.\n");
				ret = -ENOENT;
				goto probe_free_gpio_cd;
			}
		} else {
			dev_warn(&pdev->dev,
				 "host detect has no irq available\n");
			gpio_direction_input(host->pdata->gpio_detect);
		}
	} else
		host->irq_cd = -1;

	
	/* 使用wprotect功能,则申请gpio */
	if (!host->pdata->no_wprotect) {
		ret = gpio_request(host->pdata->gpio_wprotect, "s3cmci wp");
		if (ret) {
			dev_err(&pdev->dev, "failed to get writeprotect\n");
			goto probe_free_irq_cd;
		}
		
		gpio_direction_input(host->pdata->gpio_wprotect);
	}

	/* depending on the dma state, get a dma channel to use. */

	/* 如果使用DMA则申请相应的物理DMA通道*/
	if (s3cmci_host_usedma(host)) {
		/* 返回值为通道号*/
		host->dma = s3c2410_dma_request(DMACH_SDI, &s3cmci_dma_client,
						host);
		if (host->dma < 0) {
			/* DMA申请失败,尝试使用IO操作*/
			dev_err(&pdev->dev, "cannot get DMA channel.\n");
			if (!s3cmci_host_canpio()) {
				ret = -EBUSY;
				goto probe_free_gpio_wp;
			} else {
				dev_warn(&pdev->dev, "falling back to PIO.\n");
				host->dodma = 0;	/* 表示不使用DMA?/
			}
		}
	}
	/* 获取clk*/
	host->clk = clk_get(&pdev->dev, "sdi");
	if (IS_ERR(host->clk)) {
		dev_err(&pdev->dev, "failed to find clock source.\n");
		ret = PTR_ERR(host->clk);
		host->clk = NULL;
		goto probe_free_dma;
	}

	/*使能clk*/
	ret = clk_enable(host->clk);
	if (ret) {
		dev_err(&pdev->dev, "failed to enable clock source.\n");
		goto clk_free;
	}

	host->clk_rate = clk_get_rate(host->clk);  /* 保存时钟频率*/

	/*开始初始化mmc当中的字段*/
	mmc->ops 	= &s3cmci_ops;  /* 给出控制器operation函数集*/
	mmc->ocr_avail	= MMC_VDD_32_33 | MMC_VDD_33_34;
#ifdef CONFIG_MMC_S3C_HW_SDIO_IRQ
	mmc->caps	= MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
#else
	mmc->caps	= MMC_CAP_4_BIT_DATA;
#endif
	/* 计算最大和最小的工作频率*/
	mmc->f_min 	= host->clk_rate / (host->clk_div * 256);
	mmc->f_max 	= host->clk_rate / host->clk_div;

	if (host->pdata->ocr_avail)
		mmc->ocr_avail = host->pdata->ocr_avail;

	mmc->max_blk_count	= 4095;	/* 一个请求的最大block数*/
	mmc->max_blk_size	= 4095;	/* block的最大容量*/
	mmc->max_req_size	= 4095 * 512;  /* 一个请求的最大字节数*/
	mmc->max_seg_size	= mmc->max_req_size;

	mmc->max_segs		= 128;

	dbg(host, dbg_debug,
	    "probe: mode:%s mapped mci_base:%p irq:%u irq_cd:%u dma:%u.\n",
	    (host->is2440?"2440":""),
	    host->base, host->irq, host->irq_cd, host->dma);

	ret = s3cmci_cpufreq_register(host);
	if (ret) {
		dev_err(&pdev->dev, "failed to register cpufreq\n");
		goto free_dmabuf;
	}

	/* 注册该mmc 控制器到子系统中*/
	ret = mmc_add_host(mmc);
	if (ret) {
		dev_err(&pdev->dev, "failed to add mmc host.\n");
		goto free_cpufreq;
	}

	s3cmci_debugfs_attach(host);

	/* 设置驱动数据*/
	platform_set_drvdata(pdev, mmc);
	dev_info(&pdev->dev, "%s - using %s, %s SDIO IRQ\n", mmc_hostname(mmc),
		 s3cmci_host_usedma(host) ? "dma" : "pio",
		 mmc->caps & MMC_CAP_SDIO_IRQ ? "hw" : "sw");

	return 0;

 free_cpufreq:
	s3cmci_cpufreq_deregister(host);

 free_dmabuf:
	clk_disable(host->clk);

 clk_free:
	clk_put(host->clk);

 probe_free_dma:
	if (s3cmci_host_usedma(host))
		s3c2410_dma_free(host->dma, &s3cmci_dma_client);

 probe_free_gpio_wp:
	if (!host->pdata->no_wprotect)
		gpio_free(host->pdata->gpio_wprotect);

 probe_free_gpio_cd:
	if (!host->pdata->no_detect)
		gpio_free(host->pdata->gpio_detect);

 probe_free_irq_cd:
	if (host->irq_cd >= 0)
		free_irq(host->irq_cd, host);

 probe_free_irq:
	free_irq(host->irq, host);

 probe_iounmap:
	iounmap(host->base);

 probe_free_mem_region:
	release_mem_region(host->mem->start, resource_size(host->mem));

 probe_free_gpio:
	for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++)
		gpio_free(i);

 probe_free_host:
	mmc_free_host(mmc);

 probe_out:
	return ret;
}

  这个函数想当的长,差不多300行,我们来简要分析下:

第一步,该函数首先调用mmc_alloc_host分配了struct mmc_host和truct s3cmci_host和两个结构体的内存空间,其中前者包含后者,而后者有一个指针指向前者。

该函数的详细的实现如下:

/**
 *	mmc_alloc_host - initialise the per-host structure.
 *	@extra: sizeof private data structure
 *	@dev: pointer to host device model structure
 *
 *	Initialise the per-host structure.
 */
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
	int err;
	struct mmc_host *host;

	host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
	if (!host)
		return NULL;

	/* scanning will be enabled when we're ready */
	host->rescan_disable = 1;
	idr_preload(GFP_KERNEL);
	/* 获取一个idr,通过自旋锁进行互斥保护*/
	spin_lock(&mmc_host_lock);
	err = idr_alloc(&mmc_host_idr, host, 0, 0, GFP_NOWAIT);
	if (err >= 0)
		host->index = err;
	spin_unlock(&mmc_host_lock);
	idr_preload_end();
	if (err < 0)
		goto free;

	/* 设置设备名*/
	dev_set_name(&host->class_dev, "mmc%d", host->index);

	host->parent = dev;
	host->class_dev.parent = dev; /* 设置父设备*/
	host->class_dev.class = &mmc_host_class;  /* 设置设备所属的类*/
	device_initialize(&host->class_dev);

	mmc_host_clk_init(host);

	mutex_init(&host->slot.lock);
	host->slot.cd_irq = -EINVAL;

	spin_lock_init(&host->lock);
	init_waitqueue_head(&host->wq);
	INIT_DELAYED_WORK(&host->detect, mmc_rescan);   /* 创建延时工作队列*/
#ifdef CONFIG_PM
	host->pm_notify.notifier_call = mmc_pm_notify; /* 通知链回调函数*/
#endif

	/*
	 * By default, hosts do not support SGIO or large requests.
	 * They have to set these according to their abilities.
	 */
	host->max_segs = 1;
	host->max_seg_size = PAGE_CACHE_SIZE;

	host->max_req_size = PAGE_CACHE_SIZE;
	host->max_blk_size = 512;
	host->max_blk_count = PAGE_CACHE_SIZE / 512;

	return host;

free:
	kfree(host);
	return NULL;
}

EXPORT_SYMBOL(mmc_alloc_host);
   其大致过程如下:分配相应的结构体,设置标志位rescan_disable为1,表示禁止扫描SD卡。

                         随后利用idr分配了一个编号给该MMC控制器,并初始化了MMC控制器设备对象(device),

                         初始化了控制器时钟信息。

                         很重要的一步,初始化了一个工作(host->detect)为mmc_rescan,该函数将负责执行对SD卡的扫描,该函数将在后面描述。

                         最后,初始化了MMC控制器的访问块大小的能力。

第二步,根据控制器所使用的引脚,向gpio子系统申请该引脚。

第三步,获取板级设备信息(pdev->dev.platform_data),并初始化struct s3cmci_host中的一些字段。

第四步,控制器驱动决定时候需要使用DMA进行传输(host->dodma)。

第五步,和其他驱动一样,获得寄存器空间,申请IO内存,并完成映射工作。

第六步,和其他驱动一样,获取IRQ号,并注册该irq,中断服务程序(ISR)为s3cmci_irq函数,并且关闭中断。

第七步,根据板级设备信息,判断是否使用探测(detect)功能,如果使用则向gpio子系统申请该引脚,并为该gpio注册中断服务程序。

              中断服务程序为s3cmci_irq_cd函数,上下沿有效,该函数就是用来判断SD卡是否插入卡槽中的。。

第八步,根据板级设备信息,判断是否使用写保护(no_wprotect)功能,如果使用则向gpio子系统申请该引脚。

第九步,如果使用DMA传输,则对DMA进行相关的初始化工作。

第十步,获取clk,并使能,然后获取时钟速率。

第十一步,设置控制器的访问函数集为s3cmci_ops,该结构体包括了MMC控制器行为的抽象,非常重要,我们来看下:

static struct mmc_host_ops s3cmci_ops = {
	.request	= s3cmci_request,
	.set_ios	= s3cmci_set_ios,
	.get_ro		= s3cmci_get_ro,		/* 判断是否只读*/
	.get_cd		= s3cmci_card_present,    /* 判断card是否存在*/
	.enable_sdio_irq = s3cmci_enable_sdio_irq,
};
struct mmc_host_ops其中包含了很多MMC控制器行为的抽象,S3C2440的MMC控制器驱动只使用了5个,功能如下:

    第一个是请求函数,用于向SD卡发送命令,并获取应答。

    第二个用于设置MMC控制器参数。

    第三个用于判断SD卡是否为只读。

    第四个用于判断SD卡是否在卡槽内。

    第五个用户使能sdio中断。

接下来开始初始化struct mmc_host 结构体里的字段,这些字段的具体含义就不细说了。

第十二步,调用s3cmci_cpufreq_register注册一个通知链,有关通知链的东西就不在这里赘述了。

第十三步,调用mmc_add_host注册MMC控制器,该函数将在1.2小结叙说。

第十三步,调用s3cmci_debugfs_attach向debuf文件系统注册有关访问接口,debugfs有关的我们就略过了。

第十四步,保存mmc至驱动的私有平台数据中(dpev->dev->p->driver_data)。


作为MMC控制器的probe方法,自然而然的需要注册MMC控制器,从上面我们可以看到在进行大量的初始化工作后,最终在第十三步注册该控制器驱动。

下以小结我们来看看这个函数干了点什么。

1.2 函数mmc_add_host的使命

下列代码位于:linux/drivers/mmc/core/host.c

/**
 *	mmc_add_host - initialise host hardware
 *	@host: mmc host
 *
 *	Register the host with the driver model. The host must be
 *	prepared to start servicing requests before this function
 *	completes.
 */
int mmc_add_host(struct mmc_host *host)
{
	int err;

	WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
		!host->ops->enable_sdio_irq);

	/* 注册控制器设备实例*/
	err = device_add(&host->class_dev);
	if (err)
		return err;

	/* 注册一个led trigger*/
	led_trigger_register_simple(dev_name(&host->class_dev), &host->led);

#ifdef CONFIG_DEBUG_FS
	mmc_add_host_debugfs(host);
#endif
	mmc_host_clk_sysfs_init(host);


	mmc_start_host(host);
	
	/* 注册一个通知链*/
	register_pm_notifier(&host->pm_notify);

	return 0;
}

EXPORT_SYMBOL(mmc_add_host);

该函数首先调用device_add注册了一个设备实例,随后注册了一个led trigger。

并调用mmc_host_clk_sysfs_init,后者利用sysfs向用户提供了门控相关的信息,

接着调用mmc_start_host来启动MMC控制器的时钟,并且判断SD卡是否已在卡槽中。

最后,调用register_pm_notifier向注册了一个用于电源管理的通知链。

很明显,这里调用的5个函数,我们需要关心的是mmc_start_host函数。


void mmc_start_host(struct mmc_host *host)
{
	host->f_init = max(freqs[0], host->f_min);
	host->rescan_disable = 0;
	/* 必须重新scan才能上电,则关闭mmc控制器*/
	if (host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)
		mmc_power_off(host);	/* 关闭MMC控制器*/
	else
		mmc_power_up(host);	/* 打卡MMC控制器*/
	mmc_detect_change(host, 0);
}
MMC_CAP2_NO_PRESCAN_POWERUP 表示需要在上电前扫面SD卡,

如果定义了该宏,则需要关闭MMC控制器的电压,否则打开电源,该宏默认是不定义的。

mmc_power_up函数还是比较复杂的,不过它不是我们关心的重点,简单说句,该函数最后会调用在1.1小结中MMC控制器的抽象行为函数中的

set_ios来使能MMC控制器的电源。

该函数最后调用mmc_detect_change来探测SD卡是否存在。

/**
 *    mmc_detect_change - process change of state on a MMC socket
 *    @host: host which changed state.
 *    @delay: optional delay to wait before detection (jiffies)
 *
 *    MMC drivers should call this when they detect a card has been
 *    inserted or removed. The MMC layer will confirm that any
 *    present card is still functional, and initialize any newly
 *    inserted.
 */
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUG
    unsigned long flags;
    spin_lock_irqsave(&host->lock, flags);
    WARN_ON(host->removed);
    spin_unlock_irqrestore(&host->lock, flags);
#endif
    host->detect_change = 1;
    mmc_schedule_delayed_work(&host->detect, delay);
}

最开始的宏中的代码可以直接略过了。
将标志位detect_change置1后,函数紧接着调用了mmc_schedule_delayed_work函数,该函数如下:
 

/*
 * Internal function. Schedule delayed work in the MMC work queue.
 */
static int mmc_schedule_delayed_work(struct delayed_work *work,
				     unsigned long delay)
{
	return queue_delayed_work(workqueue, work, delay);
}

该函数简单调用了queue_delayed_work来添加一个工作到工作队列workqueue中。

基于S3C2440的嵌入式Linux驱动——MMC/SD子系统解读(一)中的第三章,MMC子系统在初始化时就注册了一个工作队列,并保存到全局变量workqueue中,

同时函数将detect工作添加到了该工作队列中,而detect工作的执行函数即为在1.1中分析时说道的mmc_rescan函数。

因此该函数的作用就是将detec表示的工作函数mmc_rescan添加到了工作队列中。

创建完工作队列后,mmc_start_host函数的使命也算结束了,它的存在极其短暂,但是它却调用了一个非常关键的函数mmc_detect_change,

后者创建的工作函数mmc_rescan将会扫描是否有SD卡插入。


2. SD卡扫描函数mmc_rescan

整个mmc_rescan函数执行分为两个部分。

第一部分,探测SD卡是否存在。

第二部分,按照SD规范,初始化SD卡。

2.1 detect SD卡

下列代码位于:linux/drivers/mmc/core/core.c

void mmc_rescan(struct work_struct *work)
{
	struct mmc_host *host =
		container_of(work, struct mmc_host, detect.work);
	int i;

	/* host禁止再次scan,则直接返回*/
	if (host->rescan_disable)
		return;

	/* If there is a non-removable card registered, only scan once */
	/* 是不可插拔的sd卡,则只scan一次*/
	if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered)
		return;
	host->rescan_entered = 1;  /* 表示至少scan一次了*/

	mmc_bus_get(host);	/* 增加总线引用技术*/

	/*
	 * if there is a _removable_ card registered, check whether it is
	 * still present
	 */
	 /* 如果总线提供detect方法则调用*/
	if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
	    && !(host->caps & MMC_CAP_NONREMOVABLE))
		host->bus_ops->detect(host);	

	host->detect_change = 0;

	/*
	 * Let mmc_bus_put() free the bus/bus_ops if we've found that
	 * the card is no longer present.
	 */
	mmc_bus_put(host);	/* 减少总线引用技术*/
	mmc_bus_get(host);

	/* if there still is a card present, stop here */
	/* 有card存在,无需继续了*/
	if (host->bus_ops != NULL) {
		mmc_bus_put(host);
		goto out;
	}

	/*
	 * Only we can add a new handler, so it's safe to
	 * release the lock here.
	 */
	mmc_bus_put(host);

	/* 调用get_cd方法,判断card是否存在*/
	if (host->ops->get_cd && host->ops->get_cd(host) == 0) {
		mmc_claim_host(host);
		mmc_power_off(host);
		mmc_release_host(host);
		goto out;
	}

	
	mmc_claim_host(host);
	/**/
	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_release_host(host);

 out:
 	/* 如果需要再次扫描*/
	if (host->caps & MMC_CAP_NEEDS_POLL)
		mmc_schedule_delayed_work(&host->detect, HZ);
}

该函数首先判断rescan_disable是否为真,如果为真则不用扫描SD卡,直接推出了。

在mmc_start_host时,该变量被置为0,因此这里函数不返回,程序继续往下走。






未完,待续。。。。。。。。。。。