linux sd卡 应用程序,Linux SD卡驱动开发(六) —— SD卡启动过程总体分析

一、工作流程

mmc驱动主要文件包括

drivers/mmc/card/block.c

drivers/mmc/card/queue.c

drivers/mmc/core/core.c

drivers/mmc/core/host.c

drivers/mmc/core/

内核启动时,首先执行core/core.c的mmc_init,注册mmc、sd总线,以及一个host class设备。接着执行card/block.c中,申请一个块设备。

二、:

这里涉及三种总线

1. platform bus//MMC host controller 作为一种 platform device, 它是需要注册到 platform bus上 的

driver/base/platform.c

structbus_type platform_bus_type = {

.name        = "platform",

.dev_attrs    = platform_dev_attrs,

.match        = platform_match,

.uevent        = platform_uevent,

.pm        = &platform_dev_pm_ops,

};

2. mmc bus type  //在mmc_init()中被创建的.通过调用 mmc_register_bus() 来注册 MMC 总线

drivers\mmc\core\bus.c

staticstructbus_type mmc_bus_type = {

.name        = "mmc",

.dev_attrs    = mmc_dev_attrs,

.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,

};

3. sdio bus type    //在mmc_init()中被创建的.通过调用sdio_register_bus() 来注册 SDIO 总线

drivers\mmc\core\sdio_bus.c

staticstructbus_type sdio_bus_type = {

.name        = "sdio",

.dev_attrs    = sdio_dev_attrs,

.match        = sdio_bus_match,

.uevent        = sdio_bus_uevent,

.probe        = sdio_bus_probe,

.remove        = sdio_bus_remove,

.pm        = SDIO_PM_OPS_PTR,

};

其中mmc总线操作相关函数,由于mmc卡支持多种总数据线,如SPI、SDIO、8LineMMC而不同的总线的操作控制方式不尽相同,所以通过此结构与相应的总线回调函数相关联。

//总线操作结构

structmmc_bus_ops {

void(*remove)(structmmc_host *);

void(*detect)(structmmc_host *);

int(*sysfs_add)(structmmc_host *,structmmc_card *card);

void(*sysfs_remove)(structmmc_host *,structmmc_card *card);

void(*suspend)(structmmc_host *);

void(*resume)(structmmc_host *);

};

//  mmc卡的总线操作 core/mmc.c

staticconststructmmc_bus_ops mmc_ops = {

.remove = mmc_remove,

.detect = mmc_detect,

.sysfs_add = mmc_sysfs_add,

.sysfs_remove = mmc_sysfs_remove,

.suspend = mmc_suspend,

.resume = mmc_resume,

};

// sd卡的总线操作 core/sd.c

staticconststructmmc_bus_ops mmc_sd_ops = {

.remove = mmc_sd_remove,

.detect = mmc_sd_detect,

.sysfs_add = mmc_sd_sysfs_add,

.sysfs_remove = mmc_sd_sysfs_remove,

.suspend = mmc_sd_suspend,

.resume = mmc_sd_resume,

};

// sdio的总线操作 core/sdio.c

staticconststructmmc_bus_ops mmc_sdio_ops = {

.remove = mmc_sdio_remove,

.detect = mmc_sdio_detect,

};

关于总线操作的函数:

.detect,驱动程序经常需要调用此函数去检测mmc卡的状态,具体实现是发送CMD13命令,并读回响应,如果响应错误,则依次调用.remove、detach_bus来移除卡及释放总线。

三、总体

1、kernel启动时,先后执行mmc_init()及mmc_blk_init(),以对mmc设备及mmc块模块进行初始化

mmc/core/core.c

staticint__init mmc_init(void)

workqueue = alloc_ordered_workqueue("kmmcd", 0);//建立了一个工作队列workqueue,这个工作队列的作用主要是用来支持热插拔

ret = mmc_register_bus();//注册一个mmc总线

ret = mmc_register_host_class();//注册了一个 mmc_host 类

ret = sdio_register_bus();//注册了一个 sdio_bus_type

*******

mmc/card/block.c

staticint__init mmc_blk_init(void)

res = register_blkdev(MMC_BLOCK_MAJOR, "mmc");//注册一个块设备

res = mmc_register_driver(&mmc_driver);//注册一个mmc设备驱动

staticstructmmc_driver mmc_driver =

.probe      = mmc_blk_probe,

staticintmmc_blk_probe(structmmc_card *card)

mmc_set_bus_resume_policy(card->host, 1);//*host 该指针指向一个mmc主机实例,块设备中的读写操作就是调用这个mmc主机的操作函数host->ops->request来实现对实际硬件的操作。

2、core部分会做两件事

a -- 取得总线

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

voidmmc_attach_bus(structmmc_host *host,conststructmmc_bus_ops *ops)

{

...

host->bus_ops = ops;

...

}

3、然后在挂载mmc设备驱动时,执行驱动程序中的xx_mmc_probe(),检测host设备中挂载的sd设备

kernel\arch\arm\configs\msm9625_defconfig

CONFIG_MMC_MSM=y

kernel\drivers\mmc\host\Makefile

obj-$(CONFIG_MMC_MSM)        += msm_sdcc.o

msm_sdcc.c (drivers\mmc\host)

//系统初始化时扫描 platform 总线上是否有名为该SD主控制器名字"msm_sdcc"的设备,如果有, 驱动程序将主控制器挂载到 platform 总线上,并注册该驱动程序

staticint__init msmsdcc_init(void)

platform_driver_register(&msmsdcc_driver);    //注册 platform driver

staticstructplatform_driver msmsdcc_driver = {

.probe        = msmsdcc_probe,

.remove        = msmsdcc_remove,

.driver        = {

.name    = "msm_sdcc",

.pm    = &msmsdcc_dev_pm_ops,

.of_match_table = msmsdcc_dt_match,

},

};

//整个设备驱动的 probe()函数,其本质就是是为设备建立起数据结构并对其赋初值

//msmsdcc_probe 所有赋值中,我们重点关注从 platform_device *pdev里得到的数据,即设备树里的数据

//platform_device *pdev是在系统初始化的时候扫描 platform 总线发现SD主控制器后所得到的数据

staticintmsmsdcc_probe(structplatform_device *pdev)

{

//初始化设备的数据结构

if(pdev->dev.of_node) {

plat = msmsdcc_populate_pdata(&pdev->dev);        //获取设备树信息

of_property_read_u32((&pdev->dev)->of_node,"cell-index", &pdev->id);

} else{

plat = pdev->dev.platform_data;

}

//为主设备控制器建立数据结构,建立kobject,并初始化等待队列,工作队列,以及一些控制器的配置

mmc = mmc_alloc_host(sizeof(structmsmsdcc_host), &pdev->dev);            ---- 1

//实现设备驱动的功能函数,如mmc->ops = &pxamci_ops;

mmc->ops = &msmsdcc_ops;

//申请中断函数 request_irq()

ret = request_irq(core_irqres->start, msmsdcc_irq, IRQF_SHARED,DRIVER_NAME " (cmd)", host);

//注册设备,即注册kobject,建立sys文件,发送uevent等

mmc_add_host(mmc);                                                        ---- 2

//其他需求,如在/proc/driver下建立用户交互文件等

ret = device_create_file(&pdev->dev, &host->auto_cmd21_attr);

}

4、此时probe函数会创建一个host设备,然后开启一个延时任务mmc_rescan()。

1:

core/host.c

//重要函数mmc_alloc_host , 用于分配mmc_host结构体指针的内存空间大小

structmmc_host *mmc_alloc_host(intextra,structdevice *dev)----创建一个 mmc_host 和 mmc_spi_host ,且mmc_host的最后一个成员指针private指向mmc_spi_host

//建立数据结构

structmmc_host *host;

host = kzalloc(sizeof(structmmc_host) + extra, GFP_KERNEL);

//建立kobject

host->parent = dev;

host->class_dev.parent = dev;

host->class_dev.class= &mmc_host_class;

device_initialize(&host->class_dev);

//初始化等待队列,工作队列

init_waitqueue_head(&host->wq);

INIT_DELAYED_WORK(&host->detect, mmc_rescan);    //建立了一个工作队列任务 structdelayed_work detect。工作队列任务执行的函数为mmc_rescan

//配置控制器

host->max_segs = 1;

host->max_seg_size = PAGE_CACHE_SIZE;

returnhost;

5、驱动挂载成功后,mmc_rescan()函数被执行,然后对卡进行初始化(步骤后面详细讲述)

core/core.c

//mmc_rescan 函数是需要重点关注的,因为SD卡协议中的检测,以及卡识别等都是在此函数中实现

voidmmc_rescan(structwork_struct *work)

if(host->bus_ops && host->bus_ops->detect && !host->bus_dead && !(host->caps & MMC_CAP_NONREMOVABLE))//存在热插拔卡,不包括emmc,调用探测函数

host->bus_ops->detect(host);

mmc_bus_put(host);    //减少引用技术,就释放

mmc_bus_get(host);    //增加bus引用计数

if(host->bus_ops != NULL) {

mmc_bus_put(host);    //如果卡仍然存在,减少引用计数,不必探测了

gotoout;

}

if(host->ops->get_cd && host->ops->get_cd(host) == 0)//有卡,退出

gotoout;

mmc_claim_host(host);                   //用于检测host是否被占用,占用则退出,否则标记成占用

if(!mmc_rescan_try_freq(host, host->f_min))

初始化卡接以下流程初始化:

a、发送CMD0使卡进入IDLE状态

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

c、发送CMD5读取OCR寄存器。

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

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

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

假如扫描到总线上挂有有效的设备,就调用相对应的函数把设备装到系统中,mmc_attach_sdio()、mmc_attach_sd()、mmc_attach_mmc()这三个函数分别是装载sdio设备,sd卡和mmc卡的。

在 sd卡中,驱动循环发送ACMD41、CMD55给卡,读取OCR寄存器,成功后,依次发送CMD2(读CID)、CMD3(得到RCA)、CMD9(读 CSD)、CMD7(选择卡)。后面还有几个命令分别是ACMD41&CMD51,使用CMD6切换一些功能,如切换到高速模式。

经过上述步骤,已经确定当前插入的卡是一张有效、可识别的存储卡。然后调用mmc_add_card()把存储卡加到系统中。正式与系统驱动连接在一起。

staticintmmc_rescan_try_freq(structmmc_host *host, unsigned freq)

host->f_init = freq;                 //设置某一个时钟频率

mmc_power_up(host);                     //与 mmc_power_off 类似,不过设置了启动时需要的 ios

mmc_go_idle(host);          ----1a      //CMD0 ,SD卡从 inactive 到 idle

mmc_send_if_cond(host, host->ocr_avail);//检测SD卡是否支持SD2.0

if(!mmc_attach_sd(host))   ----1b//然后对mmc或者sd发送一些命令进行探测,这里以 sd 为例

1a:

intmmc_go_idle(structmmc_host *host)

structmmc_command cmd = {0};

cmd.opcode = MMC_GO_IDLE_STATE; //即CMD0

cmd.arg = 0;                    //此命令无参数

err = mmc_wait_for_cmd(host, &cmd, 0)

intmmc_wait_for_cmd(structmmc_host *host,structmmc_command *cmd,intretries)

memset(cmd->resp, 0, sizeof(cmd->resp));//调用了 mmc_start_request,

cmd->retries = retries;

mrq.cmd = cmd;

mmc_wait_for_req(host, &mrq);

voidmmc_wait_for_req(structmmc_host *host,structmmc_request *mrq)   ----重要函数

__mmc_start_req(host, mrq);

staticint__mmc_start_req(structmmc_host *host,structmmc_request *mrq)

mmc_start_request(host, mrq);

staticvoidmmc_start_request(structmmc_host *host,structmmc_request *mrq)

host->ops->request(host, mrq);    //即 msmsdcc_request, MMC 核心与核HOST 层握手了

1b:

core/mmc.c

intmmc_attach_sd(structmmc_host *host)//完成匹配,和初始化卡的功能

err = mmc_send_app_op_cond(host, 0, &ocr);      ----1b1 //检测是否是支持SD卡

host->ocr = mmc_select_voltage(host, ocr);               //设置MMC电压

err = mmc_init_card(host, host->ocr, NULL);              //对mmc卡进行初始化,主要是读取mmc卡里的一些寄存器信息,且对这些寄存器的值进行设置

err = mmc_sd_init_card(host, host->ocr, NULL);   ----1b2

err = mmc_add_card(host->card);                  ----1b3 //调用 mmc_add_card 来把 mmc_card 挂载到 mmc_bus_type 总线去

1b1:

intmmc_send_app_op_cond(structmmc_host *host, u32 ocr, u32 *rocr)

cmd.opcode = SD_APP_OP_COND;    //ACMD41,获取 SDcard 的允许电压范围值,保存在 ocr 中. 所有发送它之前需要发送 CMD_55 命令。执行完后 card 状态变为 READY

1b2:

staticintmmc_sd_init_card(structmmc_host *host, u32 ocr,structmmc_card *oldcard)

err = mmc_sd_get_cid(host, ocr, cid, &rocr);        //发送 CMD2 ,获取卡的身份信息,进入到身份状态

card = mmc_alloc_card(host, &sd_type);              //分配一张 SD 类型的 card 结构

err = mmc_send_relative_addr(host, &card->rca);      //获取卡的相对地址,注意一前卡和主机通信都采用默认地址,现在有了自己的地址了,进入到 stand_by 状态

err = mmc_sd_get_csd(host, card); ----mmc_send_csd(card, card->raw_csd);//CMD9, 获取 CSD 寄存器的信息,包括 block 长度,卡容量等信息

err = mmc_select_card(card);                        //发送 CMD7, 选中目前 RADD 地址上的卡,任何时候总线上只有一张卡被选中,进入了传输状态

err = mmc_sd_setup_card(host, card, oldcard != NULL);

intmmc_sd_setup_card(structmmc_host *host,structmmc_card *card,boolreinit)

mmc_app_send_scr(card, card->raw_scr);   //发送命令 ACMD51 获取 SRC 寄存器的内容,进入到 SENDING-DATA 状态

if(host->ops->get_ro(host) > 0 )// get_ro(host) 即是 msmsdcc_get_ro

mmc_card_set_readonly(card);        //是否写保护,如果是的,将 card 状态设置为只读状态

1b3:

core/bus.c

intmmc_add_card(structmmc_card *card)//  /sys/devices/msm_sdcc.2/mmc_host/mmc0

ret = device_add(&card->dev);

drivers/base/core.c

intdevice_add(structdevice *dev)

dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);//

bus_probe_device(dev);

voidbus_probe_device(structdevice *dev)

if(bus->p->drivers_autoprobe)

ret = device_attach(dev);           //这样,在总线 mmc_bus_type 中就有了 mmc 设备 mmc_card 了

***********

2:

//完成kobject的注册,并调用 mmc_rescan,目的在于在系统初始化的时候就扫描SD总线查看是否存在SD卡

intmmc_add_host(structmmc_host *host)

err = device_add(&host->class_dev);//将设备注册进linux设备模型,最终的结果就是在 sys/bus/platform/devices 目录下能见到 mmc 设备节点

mmc_start_host(host);

voidmmc_start_host(structmmc_host *host)

mmc_power_off(host);                ----2a

mmc_detect_change(host, 0);         ----2b

2a:

voidmmc_power_off(structmmc_host *host)

host->ios.power_mode = MMC_POWER_OFF;    //对 ios 进行了设置

...

mmc_set_ios(host);

voidmmc_set_ios(structmmc_host *host)

host->ops->set_ios(host, ios);            // set_ios 实际上就是 mmc_host_ops 的 .set_ios  = msmsdcc_set_ios,

2b:

voidmmc_detect_change(structmmc_host *host, unsignedlongdelay)

mmc_schedule_delayed_work(&host->detect, delay); //实际上就是调用我们前面说的延时函数 mmc_rescan

6、卡设备加到系统中后,通知mmc块设备驱动。块设备驱动此时调用probe函数,即mmc_blk_probe()函数,mmc_blk_probe()首 先分配一个新的mmc_blk_data结构变量,然后调用mmc_init_queue,初始化blk队列。然后建立一个线程 mmc_queue_thread()。

7、然后就可以进行传输命令和数据了

structmmc_host_ops {

//用于SD卡命令的传输,比如发送和接收命令,CMD0,CMD8,ACMD41诸如此类的都是在这个函数去实现

void(*request)(structmmc_host *host,structmmc_request *req);

}

staticconststructmmc_host_ops msmsdcc_ops = {

.enable     = msmsdcc_enable,

.disable    = msmsdcc_disable,

.pre_req        = msmsdcc_pre_req,

.post_req       = msmsdcc_post_req,

.request    = msmsdcc_request,

.set_ios    = msmsdcc_set_ios,

.get_ro     = msmsdcc_get_ro,

.enable_sdio_irq = msmsdcc_enable_sdio_irq,

.start_signal_voltage_switch = msmsdcc_switch_io_voltage,

.execute_tuning = msmsdcc_execute_tuning,

.hw_reset = msmsdcc_hw_reset,

.stop_request = msmsdcc_stop_request,

.get_xfer_remain = msmsdcc_get_xfer_remain,

.notify_load = msmsdcc_notify_load,

};

/*这个函数实现了命令和数据的发送和接收,

当 CORE 部分需要发送命令或者传输数据时,都会调用这个函数,并传递 mrq 请求*/

staticvoidmsmsdcc_request(structmmc_host *mmc,structmmc_request *mrq)

mmc_request_done(mmc, mrq);             // 如果卡不存在,就终止请求

msmsdcc_request_start(host, mrq);

staticvoidmsmsdcc_request_start (structmsmsdcc_host *host,structmmc_request *mrq)

if((mrq->data->flags & MMC_DATA_READ) ||host->curr.use_wr_data_pend)//判断发送数据还是命令

msmsdcc_start_data(host, mrq->data,mrq->sbc ? mrq->sbc : mrq->cmd,0);   //发送数据

else

msmsdcc_start_command(host,mrq->sbc ? mrq->sbc : mrq->cmd,0);          //发送命令

staticvoidmsmsdcc_start_data(structmsmsdcc_host *host,structmmc_data *data,structmmc_command *cmd, u32 c)

//对某些 寄存器进行设置, 使能某些中断, 如 pio_irqmask

...

if(is_dma_mode(host) && (datactrl & MCI_DPSM_DMAENABLE))//采用 DMA 进行数据传输还是采用 FIFO 进行数据传输

msmsdcc_start_command_deferred(host, cmd, &c);          //启动了数据传输模式

else

msmsdcc_start_command(host, cmd, c)

staticvoidmsmsdcc_start_command(structmsmsdcc_host *host,structmmc_command *cmd, u32 c)

{

msmsdcc_start_command_deferred(host, cmd, &c);

msmsdcc_start_command_exec(host, cmd->arg, c);

}

staticvoidmsmsdcc_start_command_deferred(structmsmsdcc_host *host,structmmc_command *cmd, u32 *c)

cmd->opcode ----对应SD卡命令 ,如 CMD0:复位SD 卡

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值