您的位置:首页 > 其它

两年前实习时的文档——MMC学习总结

2014-05-01 21:16 483 查看



1 概述

驱动程序实际上是硬件与应用程序之间的中间层。在Linux操作系统中,设备驱动程序对各种不同的设备提供了一致的访问接口,把设备映射成一个特殊的设备文件,用户程序可以像其他文件一样对设备文件进行操作。
Linux2.6引入了新的设备管理机制kobject,通过这个数据结构使所有设备在底层都具有统一的接口,kobject提供基本的对象管理,是构成Linux2.6设备模型的核心结构,它与sysfs文件系统紧密联系,每个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录。
在这些内核对象机制的基础上,Linux的设备模型包括设备结构device、驱动程序driver、总线bus和设备类结构class几个关键组件。
一个现实的linux设备和驱动通常都需要挂接在一种总线上,比较常见的总线有USB、PCI总线等。但是,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附于此类总线。基于这样的背景下,2.6内核加入了platform虚拟总线。Platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序使用这些资源时使用统一的接口,这样提高了程序的可移植性。
Platform设备概念的引入是能够更好地描述设备的资源信息。Platform设备是系统中自治的实体,包括基于端口的设备、外围总线和集成入片上系统平台的大多数控制器,它们通常直接通过CPU的总线寻址。每个platform设备被赋予一个名称,并分配一定数量的资源。
Platform总线对加入到该总线的设备和驱动分别封装了结构体——platform_device和platform_driver并且提供了对应的注册函数。

图1 Platform虚拟总线
由上图可知,在platform虚拟总线上我们分别对device和driver进行注册,这样我们能够更加方便的进行驱动设备的管理。这样当有总线或者设备注册到该虚拟总线上时,内核自动的调用platform_match函数将platform_device绑定到platform_driver上。

2 SDIO启动过程

在kernel启动时,内核会自动的调用MODULE_INIT宏对模块进行加载,MODULE_INIT声明了模块的入口函数。在MMC中我们模块的执行顺序如下图所示:



图2 SDIO启动过程
在这个过程中内核首先调用xxx_init函数,init函数对device进行注册,无论什么设备在内核中都会调用driver_register函数,driver_register函数经过一系列的调用,最终会调用探测函数probe(后面详细讲解)。probe函数会对模块进行探测工作,不断的对SD/MMC/SDIO卡进行扫描。

3 platform相关

3.1 platform数据结构

Linux在启动的时候就注册了platform总线,看内核源码:
struct bus_type platform_bus_type = {

.name ="platform",

.dev_attrs = platform_dev_attrs,

.match =platform_match,

.uevent =platform_uevent,

.pm =&platform_dev_pm_ops,

};

可以看到,总线中定义了.match函数,当有总线或者设备注册到platform总线时,内核自动调用.match函数,判断device和driver的name是否一致。
platform_device的结构体定义如下:
struct platform_device {

constchar * name;//设备名字,这将代替device->dev_id,用作sys/device下显示目录名

int id;//设备ID,用于给插入该总线并且具有相同name的设备编号,如果只有一个设备的话填-1

structdevice dev;//结构体中内嵌的device结构体

u32 num_resources;//资源数

structresource * resource;//用于存放资源的数组



conststruct platform_device_id *id_entry;



/*arch specific additions */

structpdev_archdata archdata;

};

可以看出,在platform_device中定义了name,并且内嵌了structdevice结构体。另外,包含的structresource如下:
struct resource {

resource_size_tstart;

resource_size_tend;

constchar *name;

unsignedlong flags;

structresource *parent, *sibling, *child;

};

platform_driver的结构体定义如下:
struct platform_driver {

int(*probe)(struct platform_device *);

int(*remove)(struct platform_device *);

void(*shutdown)(struct platform_device *);

int(*suspend)(struct platform_device *, pm_message_t state);

int(*resume)(struct platform_device *);

structdevice_driver driver;

conststruct platform_device_id *id_table;

};

可以看出,在platform_driver中内嵌了structdevice_driver,以及一些回调函数。structdevice_driver的具体定义如下:
struct device_driver {

constchar *name;



int(*probe) (struct device *dev);

int(*remove) (struct device *dev);

void(*shutdown) (struct device *dev);

int(*suspend) (struct device *dev, pm_message_t state);

int(*resume) (struct device *dev);

conststruct attribute_group **groups;



conststruct dev_pm_ops *pm;



structdriver_private *p;

};

此处定义了name用于和platform_device匹配。因为platform_device和platform_driver的匹配就是通过内嵌的structdevice和structdevice_driver的name进行匹配的。
static int platform_match(structdevice *dev, struct device_driver *drv)

{

structplatform_device *pdev = to_platform_device(dev);

structplatform_driver *pdrv = to_platform_driver(drv);



/*match against the id table first */

if(pdrv->id_table)

returnplatform_match_id(pdrv->id_table, pdev) != NULL;



/*fall-back to driver name match */

return(strcmp(pdev->name, drv->name) == 0);

}

在数据结构设计上,总线、设备及驱动三者相互关联platform device包含device,根据device可以获得相应的bus及driver。
设备添加到总线上之后形成一个双向循环链表,根据总线可以获得其上挂接的所有device,进而获得了platform device。根据device也可以获得驱动该总线上所有设备相应的driver。Platform包含driver,根据driver获得相应的bus,进而获得bus上所有的device,进一步获得platformdevice。根据name对driver和platform device进行匹配,匹配成功后将device与相应的driver关联起来,即实现了platformdevice与platformdriver的关联。
匹配成功后调用driver的probe进而调用platformdriver的probe,在probe里实现驱动特定的功能。
Match函数只是简单的进行字符串匹配,这就是强调platformdevice和platform driver中那么属性需要一致的原因。

3.2 platform device

3.2.1 register

注册一个platform层的设备。注册后,会在sys/device目录下创建一个以name命名的目录,并且创建软连接到/sys/bus/platform/device下。
int platform_device_register(structplatform_device *pdev)

{

device_initialize(&pdev->dev);

returnplatform_device_add(pdev);

}

其中,第一步device_initialize(在kernel文件夹下)用于初始化一个structdevice。函数的定义如下:
void device_initialize(struct device*dev)

{

dev->kobj.kset= devices_kset;

kobject_init(&dev->kobj,&device_ktype);

INIT_LIST_HEAD(&dev->dma_pools);

mutex_init(&dev->mutex);

lockdep_set_novalidate_class(&dev->mutex);

spin_lock_init(&dev->devres_lock);

INIT_LIST_HEAD(&dev->devres_head);

device_pm_init(dev);

set_dev_node(dev,-1);

}

第二步,添加一个platform device到device层 。函数的定义如下:

int platform_device_add(structplatform_device *pdev)

{

inti, ret = 0;

if(!pdev)

return -EINVAL;

if(!pdev->dev.parent)

pdev->dev.parent= &platform_bus;//如果p->dev.parent不存在则赋值&platform_bus

pdev->dev.bus= &platform_bus_type;//设置pdev->dev.bus的bus类型

if(pdev->id != -1) //pdev->id!=-1说明存在多于一个的设备

dev_set_name(&pdev->dev,"%s.%d", pdev->name, pdev->id);

Else //否则对唯一的设备进行命名

dev_set_name(&pdev->dev,"%s", pdev->name);



for(i = 0; i < pdev->num_resources; i++) {

//遍历资源并且资源加入到资源数组中

struct resource *p, *r =&pdev->resource[i];

if (r->name == NULL)

r->name= dev_name(&pdev->dev);

p = r->parent;

if (!p) {

if(resource_type(r) == IORESOURCE_MEM)

p = &iomem_resource;

elseif (resource_type(r) == IORESOURCE_IO)

p = &ioport_resource;

}

if (p && insert_resource(p, r)) {

printk(KERN_ERR

"%s: failed to claim resource%d\n",

dev_name(&pdev->dev), i);

ret= -EBUSY;

gotofailed;

}

}

pr_debug("Registeringplatform device '%s'. Parent at %s\n",

dev_name(&pdev->dev),dev_name(pdev->dev.parent));



ret= device_add(&pdev->dev);//(在core/core.c中定义)

if(ret == 0)

return ret;

failed:

while(--i >= 0) {

struct resource *r =&pdev->resource[i];

unsigned long type = resource_type(r);



if (type == IORESOURCE_MEM || type ==IORESOURCE_IO)

release_resource(r);

}

returnret;

}

3.2.2 unregister

platform_device_unregister用于注销一个platform-leveldevice。函数的定义如下:
voidplatform_device_unregister(struct platform_device *pdev)

{

platform_device_del(pdev);

platform_device_put(pdev);

}

在注销一个设备时,第一步调用platform_device_del函数。此函数的定义如下:
void platform_device_del(struct platform_device*pdev)

{

inti;



if(pdev) {

device_del(&pdev->dev);

//遍历所有的resource如果是IORESOURCE_MEM或者IORESOURCE_IO类型则调用release_resource函数释放掉resource。

for (i = 0; i <pdev->num_resources; i++) {

structresource *r = &pdev->resource[i];

unsignedlong type = resource_type(r);



if(type == IORESOURCE_MEM || type == IORESOURCE_IO)

release_resource(r);

}

}

}

此函数的作用是:移除一个platform-leveldevice。其中的IOSOURCE_MEM和IORESOURCE_IR是CPU对外设I/O端口物理地址的两种编制方式。Resource是一个指向platform资源数组的指针,该数组有num_resource个资源,下面是资源结构体的定义(linux/ioport.h):
struct resource {

resource_size_tstart; //起始地址

resource_size_tend; //终止地址

constchar *name; //名称

unsignedlong flags; //标志

structresource *parent, *sibling, *child;

};

在structplatform_device中可以设置多种资源信息。资源的flags标志包括:
#define IORESOURCE_IO 0x00000100 //IO资源

#define IORESOURCE_MEM 0x00000200 //内存资源

#define IORESOURCE_IRQ 0x00000400 //中断资源

#define IORESOURCE_DMA 0x00000800 //DMA资源

第二步,调用platform_device_put函数。
void platform_device_put(structplatform_device *pdev)

{

if(pdev)

put_device(&pdev->dev);

}

销毁一个platformdevice,并且释放所有与这个platformdevice相关的内存。

3.3 platform driver

3.3.1 register

通过调用函数platform_driver_register实现为platformlevel的设备注册一个驱动。注册成功后,内核会在/sys/bus/platform/driver/目录下创建一个名字为driver->name的目录。具体的函数定义如下:
int platform_driver_register(structplatform_driver *drv)

{

drv->driver.bus= &platform_bus_type;

if(drv->probe)

drv->driver.probe =platform_drv_probe;

if(drv->remove)

drv->driver.remove =platform_drv_remove;

if(drv->shutdown)

drv->driver.shutdown =platform_drv_shutdown;



returndriver_register(&drv->driver);

}

此函数首先对struct platform_driver变量的driver进行赋值。然后调用driver_register并且返回。driver_register函数的定义如下:
int driver_register(structdevice_driver *drv)

{

intret;

structdevice_driver *other;



BUG_ON(!drv->bus->p);

//如果driver的方法和总线上的方法不能匹配则驱动的名称需要更新

if((drv->bus->probe && drv->probe) ||

(drv->bus->remove &&drv->remove) ||

(drv->bus->shutdown &&drv->shutdown))

printk(KERN_WARNING "Driver '%s'needs updating - please use "

"bus_typemethods\n", drv->name);



other= driver_find(drv->name, drv->bus);

if(other) {

put_driver(other);

printk(KERN_ERR "Error: Driver '%s'is already registered, "

"aborting...\n",drv->name);

return -EBUSY;

}



ret= bus_add_driver(drv);//将驱动加载到总线上,如果成功就返回

if(ret)

return ret;

ret= driver_add_groups(drv, drv->groups);

if(ret)

bus_remove_driver(drv);

returnret;

}

3.3.2 unregister

通过调用函数platform_driver_unregister函数实现platform_driver级设备的注销。
void platform_driver_unregister(structplatform_driver *drv)

{

driver_unregister(&drv->driver);

}

在此函数中调用了driver_unregister函数,将驱动从系统中移除。函数的具体定义如下:
void driver_unregister(structdevice_driver *drv)

{

if(!drv || !drv->p) {

WARN(1, "Unexpected driverunregister!\n");

return;

}

driver_remove_groups(drv,drv->groups);

bus_remove_driver(drv);

}

分两步走,第一步调用driver_remove_groups。函数的具体定义如下:
static voiddriver_remove_groups(struct device_driver *drv,

const struct attribute_group **groups)

{

inti;



if(groups)

for (i = 0; groups[i]; i++)

sysfs_remove_group(&drv->p->kobj,groups[i]);

}

其中,调用了
static inline voidsysfs_remove_group(struct kobject *kobj,

const struct attribute_group *grp)

{

}

第二步从总线中将驱动删除。调用如下函数:
void bus_remove_driver(structdevice_driver *drv)

{

if(!drv->bus)

return;



if(!drv->suppress_bind_attrs)

remove_bind_files(drv);

driver_remove_attrs(drv->bus,drv);

driver_remove_file(drv,&driver_attr_uevent);

klist_remove(&drv->p->knode_bus);

pr_debug("bus:'%s': remove driver %s\n", drv->bus->name, drv->name);

driver_detach(drv);

module_remove_driver(drv);

kobject_put(&drv->p->kobj);

bus_put(drv->bus);

}

此函数的作用是:将驱动从它控制的设备中卸载,并且从驱动的总线链表中将它移除。


4 probe函数

在kernel加载模块的时候启动了设备注册函数,因为所有的设备的driver都继承自device_driver,所以从driver_register看起,函数的源码如下:
int driver_register(structdevice_driver * drv)

{

if ((drv->bus->probe &&drv->probe) ||

(drv->bus->remove &&drv->remove) ||

(drv->bus->shutdown &&drv->shutdown)) {

printk(KERN_WARNING "Driver '%s'needs updating - please use bus_type methods\n", drv->name);

}

klist_init(&drv->klist_devices,NULL, NULL);

return bus_add_driver(drv);

}

klist_init不相关,不用管他,具体再去看bus_add_driver:

int bus_add_driver(structdevice_driver *drv)

{

1.先kobject_set_name(&drv->kobj,"%s", drv->name);

2.再kobject_register(&drv->kobj)

3.然后调用了:driver_attach(drv)

}

int driver_attach(struct device_driver * drv)

{

return bus_for_each_dev(drv->bus,NULL, drv, __driver_attach);

}

真正起作用的是__driver_attach:

static int __driver_attach(structdevice * dev, void * data)

{

……

if (!dev->driver)

driver_probe_device(drv, dev);

……

}

int driver_probe_device(struct device_driver * drv, struct device * dev)

{

……

//1.先是判断bus是否match:

if (drv->bus->match &&!drv->bus->match(dev, drv))

goto done;

//2.再具体执行probe:

ret = really_probe(dev, drv);

……

}

really_probe才是我们要找的函数:

static int really_probe(struct device *dev, struct device_driver *drv)

{

……

//1.先是调用的驱动所属总线的probe函数:

if (dev->bus->probe) {

ret = dev->bus->probe(dev);

if (ret)

goto probe_failed;

} else if (drv->probe) {

//2.再调用你的驱动中的probe函数:

ret = drv->probe(dev);

if (ret)

goto probe_failed;

}

……

}

其中,drv->probe(dev),才是真正调用的驱动实现的具体的probe函数。从此出开始probe函数正式被调用。
至此,platform成功挂接到platform bus上了,并与特定的设备实现了绑定,并对设备进行了probe处理。

5 request函数

当SDIO设备启动之后,probe函数会调用mmc_alloc_host函数不断的检测连接的MMC/SD/SDIO卡,并且通过device_add完成对设备的添加,当设备添加完成之后,会调用mmc_blk_probe完成驱动定义的特定功能。函数的调用关系图如下:



图3 request调用过程
Request函数的定义如下:
static void

v8sdio_request( struct mmc_host *mmc,struct mmc_request *mrq )

{

unsignedlong iflags = 0;

structv8sdio_host *host = mmc_priv( mmc );//首先将struct mmc_host设备转化成

//struct v8sdio_host

#if IRQ_STAT_DBG

if(host->id == DEBUG_CHN )

{

do_gettimeofday( &tv_now );//获取时间

timersub( &tv_now, &tv_last,&tv_delta );

if( tv_delta.tv_sec >= 5 )

{

u32i = 0;



tv_last.tv_sec = tv_now.tv_sec;

// tv_last.tv_usec= tv_now.tv_usec;



printk("\n" );

printk("[sdio%d_irq]: irq_ALL = %u\n", host->id, host->irq_cnt[0] );

printk("[sdio%d_irq]: irq_ERR = %u\n", host->id, host->irq_cnt[1] );

printk("[sdio%d_irq]: irq_DMA = %u\n", host->id, host->irq_cnt[2] );

printk("[sdio%d_irq]: irq_CMD = %u\n", host->id, host->irq_cnt[3] );

printk("[sdio%d_irq]: irq_TRN = %u\n", host->id, host->irq_cnt[4] );

for(i = 0; i <= MAX_OPCODE; i++ )

{

if( cmd_stats[i] )

{

printk( "[sdio%d_cmd]: cmd[%02u] =%u\n", host->id, i, cmd_stats[i] );

}

}

}

}

#endif



V8LOGV(V8TAG_SDIO, "" );



if(host->mrq )

V8LOGW( V8TAG_SDIO, "[Ch%d]host->mrq is NOT NULL.", host->id );



clk_enable(host->aclk ); //使能主机aclk



host->mrq= mrq; //请求队列赋值

local_irq_save(iflags); //保存本地中断标志

v8sdio_prepare_data(host, mrq ); //开启DMA通道,并且填充lli

v8sdio_start_command(host, mrq->cmd, mrq->data ); //开始执行命令

local_irq_restore(iflags); //重新保存本地中断标志

}


6 rescan过程

内核通过mmc_rescan(drivers/mmc/core/core.c)不断扫描MMC/SD卡:
void mmc_rescan(struct work_struct*work)

{

structmmc_host *host =

container_of(work, struct mmc_host,detect.work);

u32ocr;

interr;

unsignedlong flags;

intextend_wakelock = 0;



spin_lock_irqsave(&host->lock,flags);



if(host->rescan_disable) {

spin_unlock_irqrestore(&host->lock,flags);

return;

}



spin_unlock_irqrestore(&host->lock,flags);





mmc_bus_get(host); //取得总线



/*如果是个已经注册过的卡, 检查它是否存在 */

if((host->bus_ops != NULL) && host->bus_ops->detect &&!host->bus_dead)

host->bus_ops->detect(host);



/*如果卡已经被移除,总线将被标记为死卡

* —声明唤醒锁

* 使得用户空间能够相应*/

if(host->bus_dead)

extend_wakelock = 1;



mmc_bus_put(host);





mmc_bus_get(host);



/*如果当前还有卡,将它停止*/

if(host->bus_ops != NULL) {

mmc_bus_put(host);

goto out;

}



/*检查新插入的卡 */



/*

* 只有我们能够添加新的处理器, 所以在这里释放锁是安全的。

*/

mmc_bus_put(host);



if(host->ops->get_cd && host->ops->get_cd(host) == 0)

goto out;



mmc_claim_host(host);



mmc_power_up(host);

#ifdef CONFIG_MMC_VC088X

if(host->caps & MMC_CAP_SDIO_IRQ){

sdio_reset(host);

}

#else

sdio_reset(host);

#endif

mmc_go_idle(host);//发送CMD0使卡进入IDLE状态



mmc_send_if_cond(host,host->ocr_avail);



/*

* 首先我们搜索SDIO...

*/

err= mmc_send_io_op_cond(host, 0, &ocr);

if(!err) {

if (mmc_attach_sdio(host, ocr))

mmc_power_off(host);

extend_wakelock = 1;

goto out;

}



/*

* ...然后普通的SD...

*/

err= mmc_send_app_op_cond(host, 0, &ocr);

if(!err) {

if (mmc_attach_sd(host, ocr))

mmc_power_off(host);

extend_wakelock = 1;

goto out;

}



/*

* ...最后MMC.

*/

err= mmc_send_op_cond(host, 0, &ocr);

if(!err) {

if (mmc_attach_mmc(host, ocr))

mmc_power_off(host);

extend_wakelock = 1;

goto out;

}



mmc_release_host(host);

mmc_power_off(host);



out:

if(extend_wakelock)

wake_lock_timeout(&mmc_delayed_work_wake_lock,HZ / 2);

else

wake_unlock(&mmc_delayed_work_wake_lock);



if(host->caps & MMC_CAP_NEEDS_POLL)

mmc_schedule_delayed_work(&host->detect,HZ);

}

在设置完每个卡进入空状态,而不管当前卡是存在于何种状态之后调用了mmc_send_if_cond命令,此命令的定义如下:
int mmc_send_if_cond(struct mmc_host*host, u32 ocr)

{

structmmc_command cmd;

interr;

staticconst u8 test_pattern = 0xAA;

u8result_pattern;

/*

* To support SD 2.0 cards, we must alwaysinvoke SD_SEND_IF_COND

* before SD_APP_OP_COND. This command willharmlessly fail for

* SD 1.0 cards.

*/

cmd.opcode= SD_SEND_IF_COND;

cmd.arg= ((ocr & 0xFF8000) != 0) << 8 | test_pattern;

cmd.flags= MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR;



err= mmc_wait_for_cmd(host, &cmd, 0);

if(err)

return err;



if(mmc_host_is_spi(host))

result_pattern = cmd.resp[1] & 0xFF;

else

result_pattern = cmd.resp[0] & 0xFF;



if(result_pattern != test_pattern)

return -EIO;



return0;

}

流程图如下:

图7 SD卡的状态图

⑴取得总线

⑵检查总线操作结构指针bus_ops,如果为空,则重新利用各总线对端口进行扫描,检测顺序依次为:SDIO、NormalSD、MMC。当检测到相应的卡类型后,就使用mmc_attach_bus()把相对应的总线操作与host连接起来。
voidmmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)

{

...

host->bus_ops = ops;

...

}
⑶初始化卡按以下流程初始化:

①发送CMD0使卡进入IDLE状态

②发送CMD8,检查卡是否SD2.0。SD1.1是不支持CMD8的,因此在SD2.0Spec中提出了先发送CMD8,如响应为无效命令,则卡为SD1.1,否则就是SD2.0(请参考SD2.0Spec)。

③发送CMD5读取OCR寄存器。

④发送ACMD55、CMD41,使卡进入工作状态。MMC卡并不支持ACMD55、CMD41,如果这步通过了,则证明这张卡是SD卡。

⑤如果d步骤错误,则发送CMD1判断卡是否为MMC。SD卡不支持CMD1,而MMC卡支持,这就是SD和MMC类型的判断依据。

⑥如果ACMD41和CMD1都不能通过,那这张卡恐怕就是无效卡了,初始化失败。





















内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: