谨以此文纪念过往的岁月
一.前言
SD卡的大名是耳熟能详,但是SDIO总线确是不为人解,不过说起他的近亲SPI就知道了。我们这里主要是理解SDIO总线,并不去理解SPI总线。也许大家会畏惧其庞大的代码,其实我们并不需要详细理解其具体的实现,我们需要理解其架构。
二.主机(host)
在linux2.6.28中,在sdhci-s3c.c实现对主机的sdhci的驱动,而其设备的添加则在smdk6410_devices中。
在sdhci-s3c.c的sdhci_s3c_probe实现对主机端的驱,对于里面具体的一些实现我们就不看了,主要是看其中通用的code。
static int __devinit sdhci_s3c_probe(structplatform_device *pdev)
{
structsdhci_host*host;
structsdhci_s3c *sc;
host= sdhci_alloc_host(dev, sizeof(struct sdhci_s3c));
sc= sdhci_priv(host);
sdhci_add_host(host);
}
对于上面的函数,主要就是关心上面的几段代码。首先,来看sdhci_host这个结构体,这个是个很重要的东西,这个将会贯穿整个驱动。
struct sdhci_host {
/*Data set by hardware interface driver */
constchar *hw_name; /* Hardware bus name */
unsignedint quirks; /* Deviations from spec. */
int irq; /* Device IRQ */
void__iomem * ioaddr; /* Mapped address */
conststruct sdhci_ops *ops; /* Low level hw interface */
structmmc_host *mmc; /* MMC structure */
u64 dma_mask; /* custom DMA mask */
spinlock_t lock; /* Mutex */
int flags; /* Host attributes */
unsignedint version; /* SDHCI spec. version */
unsignedint max_clk; /* Max possible freq (MHz) */
unsignedint timeout_clk; /* Timeout freq (KHz) */
unsignedint clock; /* Current clock (MHz) */
unsignedshort power; /* Current voltage */
structmmc_request *mrq; /* Current request */
structmmc_command *cmd; /* Current command */
structmmc_data *data; /* Current data request */
unsignedint data_early:1; /* Data finished before cmd */
structsg_mapping_iter sg_miter; /* SG state for PIO */
unsignedint blocks; /* remaining PIO blocks */
int sg_count; /* Mapped sg entries */
u8 *adma_desc; /* ADMA descriptor table */
u8 *align_buffer; /* Bounce buffer */
dma_addr_t adma_addr; /* Mapped ADMA descr. table */
dma_addr_t align_addr; /* Mapped bounce buffer */
structtasklet_struct card_tasklet; /* Tasklet structures */
structtasklet_struct finish_tasklet;
structtimer_list timer; /* Timer for timeouts */
unsignedlong private[0]____cacheline_aligned;
};
上面的结构体主要需要关注mmc_host和两个tasklet_struct。
struct mmc_host {
structdevice *parent;
structdevice class_dev;
int index;
conststruct mmc_host_ops *ops;
unsignedint f_min;
unsignedint f_max;
u32 ocr_avail;
unsignedlong caps; /* Host capabilities */
/*host specific block data */
unsignedint max_seg_size; /* see blk_queue_max_segment_size */
unsignedshort max_hw_segs; /* see blk_queue_max_hw_segments */
unsignedshort max_phys_segs; /* see blk_queue_max_phys_segments */
unsignedshort unused;
unsignedint max_req_size; /* maximum number of bytes in one req*/
unsignedint max_blk_size; /* maximum size of one mmc block */
unsignedint max_blk_count; /* maximum number of blocks in one req */
/*private data */
spinlock_t lock; /* lock for claim and bus ops */
structmmc_ios ios; /* current io bus settings */
u32 ocr; /* the current OCR setting */
/*group bitfields together to minimize padding */
unsignedint use_spi_crc:1;
unsignedint claimed:1; /* host exclusively claimed */
unsignedint bus_dead:1; /* bus has been released */
structmmc_card *card; /* device attached to this host*/
wait_queue_head_t wq;
structdelayed_work detect;
conststruct mmc_bus_ops *bus_ops; /* currentbus driver */
unsignedint bus_refs; /* reference counter */
unsignedint sdio_irqs;
structtask_struct *sdio_irq_thread;
atomic_t sdio_irq_thread_abort;
structdentry *debugfs_root;
unsignedlong private[0]____cacheline_aligned;
};
Mmc_host可以认为是总线设备的一种封装。
继续上面的源码在sdhci_alloc_host->mmc_alloc_host中,会发现我们的申请的mmc_host设备指向一个特殊的类mmc_host_class,在该类中并没有什么特殊的function只有一个mmc_host_classdev_release,从下面的code可知主要是释放在mmc_alloc_host中开辟的空间。为什么需要这样来设计就不知道???
static void mmc_host_classdev_release(structdevice *dev)
{
structmmc_host *host = cls_dev_to_mmc_host(dev);
kfree(host);
}
下面来看sdhci_add_host这个函数。
int sdhci_add_host(struct sdhci_host *host)
{
structmmc_host *mmc;
mmc= host->mmc;
tasklet_init(&host->card_tasklet,sdhci_tasklet_card,(unsigned long)host);
tasklet_init(&host->finish_tasklet,sdhci_tasklet_finish,(unsigned long)host);
setup_timer(&host->timer,sdhci_timeout_timer, (unsigned long)host);
request_irq(host->irq,sdhci_irq, IRQF_SHARED,mmc_hostname(mmc), host);
mmc_add_host(mmc);
return0;
}
也许大家很困惑,为什么只关注这几行code,在该函数中主要是集中在mmc_host成员的初始化赋值。由于中断中需要处理的内容较多故采用底半部的办法来处理部分内容。
card_tasklet用于处理MMC插槽上的状态变化。finish_tasklet用于命令传输完成后的处理。Timer用于等待硬件中断。Irq则是SDHCI的中断处理。
将host添加入mmc管理,其本质即是添加host的class_dev
int mmc_add_host(struct mmc_host *host)
{
device_add(&host->class_dev);
mmc_start_host(host);
return0;
}
void mmc_start_host(structmmc_host *host) –启动host
{
mmc_power_off(host); --关闭MMC
if(host->caps & MMC_CAP_BOOT_ONTHEFLY)
mmc_rescan(&host->detect.work); --重新扫描
else
mmc_detect_change(host,0); --处理状态改变
}
void mmc_detect_change(struct mmc_host *host,unsigned long delay)
{
mmc_schedule_delayed_work(&host->detect,delay);
}
在mmc_alloc_host时,存在下面一行code
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
到此就会发现mmc_start_host一定会执行mmc_rescan只不过一个是延时后执行,一个是没有延时执行。
void mmc_rescan(struct work_struct *work)
{
structmmc_host *host =container_of(work, struct mmc_host, detect.work);
u32ocr;
interr;
mmc_bus_get(host);
if(host->bus_ops == NULL) { --如果为第一次扫描总线上设备。
/*
* Only we can add a new handler, so it's safetorelease the lock here.
*/
mmc_bus_put(host);
if(host->ops->get_cd && host->ops->get_cd(host) == 0)
gotoout;
mmc_claim_host(host); --申请一个host
mmc_power_up(host); --开启host电源
mmc_go_idle(host); --设置host为idle模式
mmc_send_if_cond(host,host->ocr_avail);--是用于验证SD卡接口操作状态的有效性命令(CMD8)。如果 SD_SEND_IF_COND指示为符合SD2.0标准的卡,则设置操作状态寄存器ocrbit30指示能 够处理块地址SDHC卡
/*
* First we search for SDIO...
*/
err= mmc_send_io_op_cond(host, 0, &ocr);
if(!err) {
if(mmc_attach_sdio(host, ocr))
mmc_power_off(host);
gotoout;
}
/*
* ...then normal SD...
*/
err= mmc_send_app_op_cond(host, 0, &ocr);
if(!err) {
if(mmc_attach_sd(host, ocr))
mmc_power_off(host);
gotoout;
}
/*
* ...and finally MMC.
*/
err= mmc_send_op_cond(host, 0, &ocr);
if(!err) {
if(mmc_attach_mmc(host, ocr))
mmc_power_off(host);
gotoout;
}
mmc_release_host(host);
mmc_power_off(host);
}else {
if(host->bus_ops->detect && !host->bus_dead)
host->bus_ops->detect(host);
mmc_bus_put(host);
}
out:
if(host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect,HZ);
}
那先来看mmc_send_if_cond实现,从字面意思上看该函数就是发送一个SD标准命令,亦如usb的标准命令一样。
在这里不得不先说明一点就是在SD子系统中,所有的数据传输均是由host发起。Host发送完一命令则会等待中断的产生,在这里采用完成量来实现进程的阻塞。
void mmc_wait_for_req(struct mmc_host *host,struct mmc_request *mrq)
{
DECLARE_COMPLETION_ONSTACK(complete);
mrq->done_data= &complete;
mrq->done= mmc_wait_done;
mmc_start_request(host,mrq); --开始request
wait_for_completion(&complete);
}
mmc_start_request->host->ops->request(即sdhci_request)
static void sdhci_request(structmmc_host *mmc, struct mmc_request *mrq)
{
structsdhci_host *host;
unsignedlong flags;
host= mmc_priv(mmc);
spin_lock_irqsave(&host->lock,flags);
host->mrq= mrq;
if((mmc->caps & MMC_CAP_ON_BOARD) || (host->flags &SDHCI_DEVICE_ALIVE))
sdhci_send_command(host,mrq->cmd);
else{
if(!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)
||(host->flags & SDHCI_DEVICE_DEAD)) {
host->mrq->cmd->error= -ENOMEDIUM;
tasklet_schedule(&host->finish_tasklet);
}else
sdhci_send_command(host,mrq->cmd);
}
mmiowb();
spin_unlock_irqrestore(&host->lock,flags);
}
sdhci_request-> sdhci_send_command发送命令。
当命令传输完成系统调用中断处理函数sdhci_irq。在其中进行中断完成后的处理。
以SDIO为例其会采用mmc_attach_sdio来实现驱动和设备的匹配,其本质还是根据sdio_bus的匹配规则来实现匹配。在mmc_attach_sdio中首先是mmc匹配一个bus,即采用何种bus来进行mmc bus来处理host。在这里需要理解一点就是在SDIO中,对于SD卡存储器mmc为实体设备,而对于非SD卡存储器,如SDIO接口的设备,则mmc则表征为bus,这个比较重要。除了mmc bus外还存在SDIO_BUS。int mmc_attach_sdio(struct mmc_host *host,u32 ocr)
{
interr;
inti, funcs;
structmmc_card *card;
mmc_attach_bus(host,&mmc_sdio_ops); --host匹配一条mmc bus
card= mmc_alloc_card(host, NULL); --申请一个card实体,类似于总线设备。
card->type= MMC_TYPE_SDIO;
card->sdio_funcs= funcs;
host->card= card;
for(i = 0;i < funcs;i++) {
sdio_init_func(host->card,i + 1);
}
mmc_release_host(host);
mmc_add_card(host->card);
for(i = 0;i < funcs;i++) {
sdio_add_func(host->card->sdio_func[i]);
}
return0;
}
比较难以理解的是func,这个东东其实是一个实体设备的封装,可以认为其是一个设备。
struct sdio_func *sdio_alloc_func(structmmc_card *card)
{
structsdio_func *func;
func= kzalloc(sizeof(struct sdio_func), GFP_KERNEL);
func->card= card;
device_initialize(&func->dev);
func->dev.parent= &card->dev; --很明显card设备为sdio设备的父设备。
func->dev.bus= &sdio_bus_type;
func->dev.release= sdio_release_func;
returnfunc;
}
上面的code一目了然,其就是具体设备实体的封装,其bus类型为sdio_bus. sdio_init_func仅仅是初始化一个设备,而并没有register。在sdio_add_func实现设备的register,同理就是card实体,在mmc_add_card之前并没有注册,在mmc_add_card函数中才实现设备的注册。
到此设备注册也就完成了,其实sdio总线在形式上类似于usb bus,为什么呢?编写过usb驱动的童鞋们应该知道,编写usb驱动仅仅是编写驱动的加载,并没有具体加载设备实体,导致很多童鞋的困惑,为什么没有设备的加载,其实在usb设备插入时,会动态的创建一个usb设备实体,在usb设备实体创建完成后,根据不同设备id调用相匹配的驱动。而SDIO设备设备也是一样的。上面的code比较混乱,总是让人看不出具体的设备的加载。其实在上面的code中,其中包括了mmc host的驱动。
三.驱动加载
我们还是以SDIO驱动为例,注册一个SDIO驱动会调用下面的函数。
int sdio_register_driver(struct sdio_driver*drv)
{
drv->drv.name= drv->name;
drv->drv.bus= &sdio_bus_type;
returndriver_register(&drv->drv);
}
其实很好理解sdio_driver其实是driver的封装,并且该driver的bus为sdio_bus_type。这个设备的驱动很简单。那来看sdio_driver结构
struct sdio_driver{
char*name; --驱动名称
conststruct sdio_device_id *id_table; --驱动设备ID
int(*probe)(struct sdio_func *, const struct sdio_device_id *);
void(*remove)(struct sdio_func *);
structdevice_driver drv;
};
id_table很熟悉吧,嘿嘿在usb的驱动中如何将设备和驱动匹配就是根据这个东西。在SDIO中也是根据该id_table来进行设备和驱动的匹配。
四.驱动和设备匹配
在介绍了设备注册和驱动注册后,那来看这两个是怎样匹配的。记住SDIO驱动之上有两条总线一个mmc bus 一个是SDIO bus。
先来看mmc bus的match
static int mmc_bus_match(struct device *dev,struct device_driver *drv)
{
return1;
}
这个很省事,直接就是1.
那在看sdio bus 的match
static int sdio_bus_match(struct device *dev,struct device_driver *drv)
{
structsdio_func *func = dev_to_sdio_func(dev);
structsdio_driver *sdrv = to_sdio_driver(drv);
if(sdio_match_device(func, sdrv))
return1;
return0;
}
通过查看上面的具体code的实现你就会发现就是根据id_table来实现设备和驱动的匹配。
五.probe
不管在设备注册还是驱动注册时,如果发现存在对应的设备和驱动则会调用对应的驱动。不过记住一点是均会先调用mmc bus的probe其次是sdio bus的probe。其实现的过程与platfrom类似,不多加赘述。
六.总结
SDIO说白了还是一种总线,其本质还是离不开驱动和设备这两者,如果有usb驱动的经验则会很好的理解SDIO总线的驱动。在linux内核是可以触类旁通的。