在阅读本文之前,请先掌握以下基本知识,不然请略过本文。
预备知识:
熟读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控制器驱动的不懈努力。。。。。。下面,我们从控制器驱动开始,来深入挖掘这一过程。
本文以三星的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);
为了让该驱动成功绑定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; }
第一步,该函数首先调用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控制器,从上面我们可以看到在进行大量的初始化工作后,最终在第十三步注册该控制器驱动。
下以小结我们来看看这个函数干了点什么。
下列代码位于: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);
并调用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); }
/* * 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); }
在基于S3C2440的嵌入式Linux驱动——MMC/SD子系统解读(一)中的第三章,MMC子系统在初始化时就注册了一个工作队列,并保存到全局变量workqueue中,
同时函数将detect工作添加到了该工作队列中,而detect工作的执行函数即为在1.1中分析时说道的mmc_rescan函数。
因此该函数的作用就是将detec表示的工作函数mmc_rescan添加到了工作队列中。
创建完工作队列后,mmc_start_host函数的使命也算结束了,它的存在极其短暂,但是它却调用了一个非常关键的函数mmc_detect_change,
后者创建的工作函数mmc_rescan将会扫描是否有SD卡插入。
整个mmc_rescan函数执行分为两个部分。
第一部分,探测SD卡是否存在。
第二部分,按照SD规范,初始化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); }
在mmc_start_host时,该变量被置为0,因此这里函数不返回,程序继续往下走。
未完,待续。。。。。。。。。。。