2024年4月28日发(作者:)

MMC/SD Memory/SDIO卡mmc子系统驱动架构工作报告

2014-3-11深圳

 目标:

分析整理(MMC/SD Memory/SDIO卡)mmc子系统驱动架构;

 本文要点:

1、mmc子系统后端初始化相关的数据结构;

2、mmc子系统初始化驱动架构;

3、mmc请求处理;

4、基本的sd协议规范;

 SDIO/SD Memory/MMC卡的区别:

 SDIO card is to provide high-speed data I/O with low power consumption for mobile

electronic devices.

 The SDIO (Secure Digital I/O) card is based on and compatible with the SD memory card. This

compatibility includes mechanical, electrical, power, signaling and software.

 SD Memory Card is a memory card that is specifically designed to meet the security,

capacity, performance, and environment requirements inherent in newly emerging audio

and video consumer electronic devices.

The SD Memory Card will include a content protection SD Memory Card

security system uses mutual authentication and a "new cipher algorithm" to protect against

illegal usage of the card content.

A Non-secure access to the user's own content is also available.

 The MMC/eMMC is an universal low cost data storage and communication media. It is

designed to cover a wide area of applications as smart phones, cameras, organizers, PDAs,

digital recorders, MP3 players, pagers, electronic toys, etc. Targeted features are high

mobility and high performance at a low cost price.

These features include low power consumption and high data throughput at the memory

card interface.

 SD Memory卡是从MMC卡发展过来的,它更注重内容保护,SD Memory卡在外形上同

MMC卡保持一致,且兼容MMC卡兼容规范.

所以就发展轨道来看,是MMC卡==>SD Memory卡==>SDIO卡,依次兼容,保持一致

1

/44

 硬件图:(以SD总线为例)

Host

CLK

CLK

VDD

VSS

D0-D3(A)

CMD(A)

VDD

VSS

D0-D3, CMD

SD Memory

Card(A)

CLK

VDD

VSS

D0-D3(B)

CMD(B)

D0-D3, CMD

SD Memory

Card(B)

注意:在默认速度下,SD 总线支持单master(application)与多slaves(cards)连接;而在高速和UHS-I(极

高速)下,SD总线只支持单master与单slave连接;(经分析:当前linux版本代码针对的是单master与单

slave连接模式)

2

/44

 数据结构

1.初始化:

mmc_cid{}

.manfid

... ...

mmc_queue{}

mmc_card{}

.dev

... ...

.cid

.csd

*host

... ...

*card

... ...

(前端)

mmc_csd{}

.max_dtr

.cmdclass

.capacity

... ...

mmc_blk_data{}

.queue

*disk

... ...

gendisk{}

*driverfs_dev

.major

.first_minor

... ...

(前端)

mmc_host_ops{}

*request

*set_ios

*get_ro

... ...

sdhci_host{}

*mmc

... ...

device{}

*bus

... ...

mmc_host{}

*card

mmc_bus_type{}

*match

*probe

... ...

mmc_bus_probe

mmc_bus_match

delayed_work{}

... ...

.class_dev

*ops

.disable

.detect

... ...

.ios

*bus_ops

mmc_ios{}

clock

vdd

bus_mode

... ...

device_driver{}

*bus

... ...

mmc_driver{}

.drv

.probe

mmc_sd_ops{}

mmc_ops{}

mmc_sdio_ops{}

... ...

其中:*ops:表示对控制器的操作,由前端控制器驱动中设定;

*bus_ops:表示对总线上卡的操作,不同类型总线对应的卡有不同的操作,其中mmc_sd_ops{}, 在mmc_sd_attach_bus_ops()

函数中设置; mmc_sdio_ops{} 在mmc_attach_bus()函数中设置; mmc_ops{} 在mmc_attach_bus_ops函数中设置;

3

/44

2.请求处理:

(块层构建的请求)

request{}

queuelist

mmc_blk_data{}

mmc_queue{}

*data

request_queue{}

queue_head

*queuedata

request{}

(当前取出的请求)

queue

... ...

*queue

*thread

*issue_fn

*sg

*req

task_struct{}

... ...

scatterlist{}

... ...

mmc_request()

queuelist

(mmc构建的请求)

注1

(mmc构建的命令)

mmc_blk_request{}

mrq

data

cmd

stop

mmc_request{}

*cmd

*data

*stop

*done

... ...

mmc_data{}

注3

*mrq

*stop

*sg

blksz

blocks

flags

... ...

mmc_command{}

注2

*data

*mrq

opcode

arg

resp[4]

flags

retries

... ...

注1:不论是块层下发的读写请求,还是源自mmc 核心层要发出的命令,都使用

mmc_request{}

进行发送

(原因:保存了所需资源的指针);

注2:有两种命令来源,一种是mmc核心需要发的命令,一种是块层下发的请求也需要由mmc为其生成命令。两个都是在mmc子系统中构建;

注3:mmc核心需要发的命令,以初始化为目的,不传输数据;由块层下发的请求所生成的命令,以数据读写为目的,要用到

mmc_data{};

数据结构成员:

struct mmc_card {

u32

u32

u32

... ...;

struct mmc_cid

struct mmc_csd

struct sd_scr

... ...;

};

mmc_card{}代表了一张MMC卡

struct mmc_cid {

};

struct mmc_csd {

unsigned char mmca_vsn;

cmdclass;

tacc_clks;

tacc_ns;

/*卡版本号*/

/* card支持的命令子集*/

/*用于计算读命令的末位和所读数据的首位间的最

/*用于计算读命令的末位和所读数据的首位间的最

/*最大传输速率*/

/*最大的读数据块长度*/

/*最大的写数据块长度*/

/*卡的存储容量*/

/*读小块尺寸数据(最小1byte)是否允许,SD卡恒

unsigned short

unsigned short

unsigned int

unsigned int

unsigned int

unsigned int

unsigned int

unsigned int

为1*/

... ...

};

mmc_cid{},mmc_csd{}是MMC卡的CID寄存器与CSD寄存器相关的结构

unsigned int

char

unsigned int

... ...

manfid;

serial;

oemid;

/* 制造商id*/

/* 产品名*/

/* 产品序列号*/

/* 产品OID*/

prod_name[8];

cid;

csd;

scr;

/* 卡身份,已解码*/

/* 卡的特殊数据,已解码*/

/* 额外的SD数据,已解码*/

raw_cid[4];

raw_csd[4];

raw_scr[2];

/* 卡cid,未解码 */

/* 卡csd,未解码 */

/* 卡scr,未解码 */

struct mmc_host

struct device

unsigned int

unsigned int

unsigned int

*host;

dev;

rca;

type;

state;

/* 隶属的MMC控制器 */

/* 当前设备*/

/* 卡地址*/

/* 卡的类型: 0=MMC 1=SD 2=SDIO*/

/* 卡状态*/

unsigned short

大时值Nac*/

大时值Nac*/

max_dtr;

read_blkbits;

write_blkbits;

capacity;

read_partial:1,

5

/44

struct sd_scr {

};

struct mmc_blk_data {

};

struct gendisk {

int major;

/* 磁盘的主设备号*/

/* 磁盘的第一个次设备号*/

/* 次设备号数目,即分区个数, 至少为1*/

/* 指向磁盘分区表*/

/* 第一个分区:0分区*/

/* 磁盘块设备操作方法*/

/* 指向磁盘设备的请求队列*/

/* 磁盘的私有数据*/

/* 磁盘类别标志*/

/* 磁盘名*/

/* 对应的当前设备*/

int first_minor;

struct gendisk

unsigned int

... ...;

*disk;

/*通用磁盘*/

/*磁盘设备的请求队列数据结构*/

/*只读标志*/

struct mmc_queue queue;

unsigned char

unsigned char

sda_vsn;

bus_widths;

/*physical layer specification版本号*/

/*数据线的宽度*/

read_only;

int minors;

struct disk_part_tbl *part_tbl;

struct hd_struct part0;

struct block_device_operations *fops;

struct request_queue *queue;

void *private_data;

int flags;

char disk_name[DISK_NAME_LEN];

};

struct mmc_queue {

};

struct mmc_host {

struct device

u32

class_dev;

ocr_avail;

caps;

struct mmc_card

struct task_struct

struct request

void

*card;

*thread;

*req;

struct device *driverfs_dev;

... ...;

/* 对应的卡设备*/

/* 请求队列关联的处理线程*/

/* 取出的一个请求*/

/*这里用来保存mmc_blk_data{}的指针*/

/* 磁盘设备的请求队列*/

/* 磁盘设备的散列表*/

int (*issue_fn)(struct mmc_queue *, struct request *); /* 发送请求的函数*/

*data;

struct request_queue *queue;

struct scatterlist *sg;

... ...;

/* 当前mmc控制器设备*/

/* 控制器的操作方法*/

/* 控制器支持的运行电压*/

/* 控制器支持的传输能力 */

const struct mmc_host_ops *ops;

unsigned long

6

/44

*/

struct delayed_work detect;

unsigned int

... ...;

unsigned long

};

mmc_host{}代表一个MMC控制器

struct mmc_host_ops {

int (*enable)(struct mmc_host *host);

void(*request)(struct mmc_host *host, struct mmc_request *req); /* 控制器处理请求的接口*/

void(*set_ios)(struct mmc_host *host, struct mmc_ios *ios);

... ...;

};

struct mmc_bus_ops {

void (*remove)(struct mmc_host *);

void (*detect)(struct mmc_host *);

int (*suspend)(struct mmc_host *);

int (*resume)(struct mmc_host *);

/* 释放卡*/

/* 查询卡的状态*/

/* 使卡退出传输状态*/

/* 使已存在的卡恢复状态*/

/* 使已存在的卡恢复状态(不使用高速模式)*/

/* 控制器总线设置的接口*/

/* 使控制器进入省电状态*/

/* 使控制器退出省电状态*/ int (*disable)(struct mmc_host *host, int lazy);

private[0] ;

/* 延迟的事务,用于探测 */

/* 当前绑定的总线操作 */

/* 引用计数 */

const struct mmc_bus_ops *bus_ops;

bus_refs;

wait_queue_head_t wq;

struct task_struct

int

/* 声明占用控制器的等待队列头结点*/

/* 占用控制器的进/线程*/

/* 计数 */

*claimer;

claim_cnt;

unsigned int

unsigned int

claimed:1;

bus_dead:1;

/* 控制器占用声明*/

/* 控制器总线被释放*/

struct delayed_work disable;

struct mmc_card *card;

/* 延迟的事物,用于关闭控制器 */

/* 与控制器关联的卡 */

struct mmc_ios ios; /* 当前的总线设置 */

unsigned int

unsigned int

unsigned int

max_req_size;

max_blk_size;

max_blk_count;

/* 一条请求的最大字节数(bytes)*/

/* 一个mmc数据块的最大尺寸 (bytes)*/

/* 一条请求中的数据块数目上限*/

unsigned short max_phys_segs;

unsigned int max_seg_size;

max_hw_segs;

/* 最大的数据段尺寸(bytes)*/

/* 一条请求的硬件扇区数上限(通常以512b为单位)unsigned short

void (*power_restore)(struct mmc_host *);

7

/44

};

struct mmc_ios {

unsigned int clock;

unsigned short vdd;

unsigned char bus_mode;

unsigned char chip_select;

... ...;

unsigned char bus_width;

unsigned char timing;

};

mmc_ios{}存储MMC卡的状态信息

struct bus_type mmc_bus_type = {

.name = "mmc",

.match = mmc_bus_match,

.probe = mmc_bus_probe,

... ...,

};

struct mmc_driver {

struct device_driver drv;

int (*probe)(struct mmc_card *);

... ...

};

struct mmc_command {

u32 opcode;

u32 arg;

u32 resp[4];

unsigned int flags;

unsigned int retries;

unsigned int error;

struct mmc_data *data;

struct mmc_request *mrq;

};

struct mmc_data {

unsigned int timeout_ns;

unsigned int timeout_clks;

unsigned int blksz;

unsigned int blocks;

unsigned int error;

unsigned int flags;

/* 时钟*/

/* 电压*/

/* 命令输出采用的模式 */

/* SPI芯片片选*/

/* 总线宽度(1,4,8)*/

/*时序模式*/

/* bus type 名*/

/* bus type 匹配函数*/

/* bus type 匹配处理*/

/* 当前驱动*/

/* 驱动的匹配处理函数*/

/* 命令操作码*/

/* 命令参数*/

/* 反馈信息*/

/* 期待的反馈类型*/

/* 重发次数 */

/* 命令出错*/

/* 命令关联的数据段 */

/* 关联的请求*/

/* 超时时间 ns (max 80ms) */

/* 超时时钟数 (in clocks) */

/* 块尺寸*/

/* 块数目*/

/* 数据错误*/

/* 读写标志*/

8

/44

};

struct mmc_blk_request {

struct mmc_request mrq; /* 当前请求*/

struct mmc_command *stop;

struct mmc_request *mrq;

unsigned int sg_len;

struct scatterlist *sg;

/* stop命令 */

/* 关联的请求 */

/* 散列表的尺寸*/

/* 散列表指针*/

unsigned int bytes_xfered; /* 待传输的字节数*/

struct mmc_command cmd;

struct mmc_command stop;

struct mmc_data data;

};

struct mmc_request {

struct mmc_command *cmd;

struct mmc_data *data;

struct mmc_command *stop;

void *done_data;

void (*done)(struct mmc_request *);

};

/* 请求对应的命令*/

/* 请求的stop命令*/

/* 请求对应的数据*/

/* 请求对应的命令的指针*/

/* 请求对应的数据的指针*/

/* 请求对应的stop命令的指针*/

/* completion参数 */

/* completion函数*/

9

/44

第一篇:初始化架构:

mmc子系统就初始化而言,主要有三条线、五个环节

第一条线

第二条线

第三条线

说明:

subsys_initcall

1. mmc子系统核心初始化

module_init

2. mmc控制器驱动平台注册

3. mmc控制器设备注册

4.探测、分配和注册card设备

module_init

5.磁盘设备驱动注册

1、最首先进行的是mmc子系统核心初始化,下面的初始化整体框架图只是为了排版的需要位置滞后;

2、第二、三条线的先后顺序无需固定;

初始化整体框架:

10

/44

(见下行)

2. mmc控制器

平台驱动机制流程

sdhci_probe_slot sdhci_probe

platform_driver_registe

module_init

sdhci_drv_init

平台驱动注册

INIT_DELAYED_WORK(&host->detect, mmc_rescan)

mmc_alloc_host

device_add

控制器

mmc_add_host

4.1探测

设备注册

mmc_schedule_delayed_work(&host->detect, ...)

mmc_detect_change

mmc_start_host

//work : &host->detect

... ....(详见下文)

(以SD为例)

queue_delayed_work(workqueue, work, ...) mmc_rescan

mmc_attach_sd

4.2分配和注册

card设备

workqueue = create_singlethread_workqueue("kmmcd")

1. mmc子系统

subsys_initcall

mmc_init

核心初始化

mmc_register_bus

bus_register(&mmc_bus_type)

register_blkdev

5.磁盘设备

module_init

mmc_blk_init

驱动注册

mmc_register_driver(&mmc_driver)

driver_register

struct mmc_driver mmc_driver = {

.drv = {

.name = "mmcblk",

}

},

.probe = mmc_blk_probe,

queue_delayed_work

(见下行)

mmc_sd_attach_bus_ops

mmc_sd_init_card

mmc_add_card

device_add

设备注册并

挂入总线

驱动注册并

挂入总线

struct bus_type mmc_bus_type =

{

.match= mmc_bus_match,

... ...,

.probe = mmc_bus_probe,

}

add_disk

11

/44

一、mmc子系统核心初始化:

struct workqueue_struct *workqueue;

struct wake_lock mmc_delayed_work_wake_lock;

struct bus_type mmc_bus_type = {

};

struct class mmc_host_class = {

};

struct bus_type sdio_bus_type = {

.name

.match

.uevent

= "sdio",

= sdio_dev_attrs,

= sdio_bus_match,

= sdio_bus_uevent,

.dev_attrs

.name = "mmc_host",

= mmc_host_classdev_release, .dev_release

.name

.match

.probe

... ...,

= "mmc",

= mmc_bus_match,

= mmc_bus_probe,

总线类型sdio_bus_type注册(SDIO)

workqueue= create_singlethread_workqueue("kmmcd")

总线类型mmc_bus_type注册(SD/mmc)

subsys_initcall

mmc_init

为控制器设备注册一个类mmc_host_class

12

/44

};

.probe

.remove

= sdio_bus_probe,

= sdio_bus_remove,

android休眠锁:在android机制中,只有有进程拿到wake_lock锁且未释放,

则系统不能进入休眠. 在之后的mmc_rescan()中完成mmc卡的探测,注册之

后,即释放该锁

subsys_initcall(mmc_init)===>

static int __init mmc_init(void)

{

}

int mmc_register_bus(void)

{

}

int mmc_register_host_class(void)

{

}

int sdio_register_bus(void)

{

}

return bus_register(&sdio_bus_type);

return class_register(&mmc_host_class);

return bus_register(&mmc_bus_type);

wake_lock_init(&mmc_delayed_work_wake_lock, WAKE_LOCK_SUSPEND, "mmc_delayed_work");

workqueue = create_singlethread_workqueue("kmmcd");

mmc_register_bus();

mmc_register_host_class();

sdio_register_bus();

13

/44

二、mmc控制器驱动与设备注册:

架构设计思想:

sdhci_probe_slot()

request_irq(host->irq,sdhci_irq, ... ... );

命令数据收发中断的处理

setup_timer(&host->timer, sdhci_timeout_timer,... ... )

命令发送超时的相关处理

sdhci_driver平台驱动注册

sdhci_host{}

INIT_WORK(&host->finish_wq, sdhci_finish_worker)

命令发送完成的相关处理

module_init(sdhci_drv_init)

分配数据结构内存

mmc_host{}、sdhci_host{}内存分配

说明:

sdhci_host{}是对mmc_host{} 的外封装;

INIT_DELAYED_WORK(&host->detect, mmc_rescan)

mmc_power_off(host)

mmc_detect_change(host, 0)

探测设备的具体处理例程

断电的处理例程

启动时第一次探测设备

mmc_host{}

mmc->ops = &sdhci_ops

host->class_ = &mmc_host_class;

函数调用图:

14

/44

sdhci_probe_slot

代码骨架:

(属前端驱动部分,以mx_sdhci.c为例)

struct mmc_host_ops sdhci_ops = {

.request = sdhci_request, //展开见后文

.set_ios = sdhci_set_ios,//展开见后文

... ...,

};

struct platform_driver sdhci_driver = {

.driver = {

.name = "mxsdhci",

},

.probe = sdhci_probe,

... ...,

};

//前端:

module_init(sdhci_drv_init)===>

int __init sdhci_drv_init(void)

mmc_add_host

mmc_start_host

mmc_detect_change

device_add

mmc_power_off

request_irq(host->detect_irq, sdhci_cd_irq)

mmc_alloc_host

INIT_DELAYED_WORK(&host->detect, mmc_rescan, …)

device_initialize

15

/44

平台驱动机制流程

{

return platform_driver_register(&sdhci_driver);

匹配资源调用probe

}

struct platform_device mxcsdhc1_device =

传入设备资源

{ .name = "mxsdhci",

static int sdhci_probe(struct platform_device *pdev)

.id = 0,

{ ... ...;

... ...,

sdhci_probe_slot(pdev, ...);

}

}

static int __devinit sdhci_probe_slot(struct platform_device *pdev, ...)

{

struct mmc_host *mmc;

struct sdhci_host *host;

... ...;

mmc = mmc_alloc_host(sizeof(struct sdhci_host), &pdev->dev);

host = mmc_priv(mmc);//return (void *)host->private

... ...;

host->irq = platform_get_irq(pdev, 0);

host->detect_irq = platform_get_irq(pdev, 1);

mmc->index =pdev->id;

dev_set_name(&mmc->class_dev, "mmc%d", mmc->index); //如mmc0

mmc->ops = &sdhci_ops;

……

//Q:在什么地方执行这个tasklet

//A:在sdhci_irq中断处理程序中

tasklet_init(&host->card_tasklet, sdhci_tasklet_card, (unsigned long)host);

host->workqueue = create_workqueue("esdhc_wq");

INIT_WORK(&host->cd_wq, esdhc_cd_callback);

INIT_WORK(&host->finish_wq, sdhci_finish_worker); //sdhci_finish_worker见第二篇

setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host); //sdhci_timeout_timer见第二篇

setup_timer(&host->cd_timer, sdhci_cd_timer, (unsigned long)host);

... ...;

16

/44

//这个中断是通过GPIO引脚检测SD 卡拔插的中断

ret = request_irq(host->detect_irq, sdhci_cd_irq, 0,pdev->name, host);

//这个中断是用来检测 SD write/read CMD 错误的中断

ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED, pdev->name, host);

mmc_add_host(mmc);

}

struct mmc_host *mmc_alloc_host(int extra, struct device *dev)

{

mmc_rescan接口,主要承担card的探测、分配

struct mmc_host *host;

和注册。展开见后文:

host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);

由mmc_detect_change/mmc_start_host发起

host->class_ = &mmc_host_class;

... ...;

调用

INIT_DELAYED_WORK(&host->detect, mmc_rescan);

device_initialize(&host->class_dev);

名为class_dev,实为mmc/sd memory/sdio卡的

return host;

mmc_host{}设备

}

int mmc_add_host(struct mmc_host *host)

{

device_add(&host->class_dev);

mmc_start_host(host);

}

void mmc_start_host(struct mmc_host *host)

{

mmc_power_off(host);

//这里以控制器启动为契机,引出“三、card设备的探测、分配和注册”(见后)

mmc_detect_change(host, 0);

}

static void mmc_power_off(struct mmc_host *host)

{

host-> = 0;

17

/44

host-> = 0;

host->_mode = MMC_BUSMODE_OPENDRAIN;

host->_select = MMC_CS_DONTCARE;

host->_mode = MMC_POWER_OFF;

host->_width = MMC_BUS_WIDTH_1;

host-> = MMC_TIMING_LEGACY;

mmc_set_ios(host);

}

static inline void mmc_set_ios(struct mmc_host *host)

{

struct mmc_ios *ios = &host->ios;

host->ops->set_ios(host, ios); //进入控制器前端驱动完成具体设置,

//.set_ios = sdhci_set_ios

}

void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)

{

//具体的前端寄存器操作。将设置的数据写入imx51 esdhc控制器使其生效

}

前端驱动中:

mmc_set_timing

mmc_select_voltage mmc_set_bus_width

mmc_set_bus_mode

mmc_host{}->ops = &sdhci_ops;

mmc_power_up

mmc_set_clock

mmc_set_ios

mmc_set_chip_select

mmc_power_down

struct mmc_host_ops sdhci_ops = {

.........,

.set_ios = sdhci_set_ios,

host->ops->set_ios

sdhci_set_ios

}

18

/44

三、card设备的探测、分配和注册:

1) 探测card设备:

探测时机:

1、mmc控制器启动时;

函数链:

module_init==>sdhci_drv_init==>platform_driver_register==>... ...(控制器平台驱动注册,获取设备资源)

==>sdhci_probe==>sdhci_probe_slot==>mmc_add_host==>mmc_start_host==>mmc_detect_change ;

2、mmc控制器启动后,某时刻插入或拔出card设备时,这里涉及到mmc控制器前端驱动的两种处理:

1)、采用中断机制,经过简单的中断转调后,将调用到这里的mmc_detect_change()函数;

准备工作:(以mx_sdhci.c为例)

request_irq(host->detect_irq, sdhci_cd_irq, 0, pdev->name, host);

INIT_WORK(&host->cd_wq, esdhc_cd_callback);

函数链:

插卡/拔卡触发中断==>sdhci_cd_irq==>schedule_work(&host->cd_wq)==>esdhc_cd_callback==>

mmc_detect_change;

使用poll方式轮询探测,也将调用mmc_schedule_delayed_work()函数。

准备工作:前端开启MMC_CAP_NEEDS_POLL

如struct mmc_host host_for_instance;

host_for_ |= MMC_CAP_NEEDS_POLL;

(说明:mx_sdhci.c并没有使用轮询方式,这里对MMC_CAP_NEEDS_POLL的设置仅仅是个示意)

函数链:见后面rescan()函数内部即可;

3、mmc控制器从suspend转为resume时(电源管理)

说明:这里为电源管理服务考虑,在进入suspend之前如果已有卡处于插入态,当控制器resume返回时,需要重新进行确认并进行恢复;

准备工作:(仍以mx_sdhci.c为例)

struct platform_driver sdhci_driver = {

... ...,

.resume = sdhci_resume,

};

platform_driver_register(&sdhci_driver);

函数链:

19

/44

电源管理模块==>sdhci_resume(控制器先)==>mmc_resume_host==>

host->bus_ops->resume (card后)

mmc_detect_change (待控制器和card设备依次resume后,才执行确认性探测工作)

(本部分为了前后连贯突出主干,承接上文,以时机1来引起探测过程,并解析。)

20

/44

架构设计思想:

探测时机1/2/3

mmc_detect_change

mmc_rescan()

函数调用图:

mmc_rescan

(以sd为例)

存在,则提前跳出

如果之前卡已注册

确认卡是否仍存在

不存在,则释放相关资源,释放总线;

在随后的探测流程中也会无果而终

如果没有卡被注册

执行探测

启动控制器(一般预先按照控制器支持的最大电压启动)

遵照卡的协议,通过广播命令进行卡的询问探测

如果询问正常返回,则进入到card的分配注册过程

host->bus_ops->detect

mmc_power_up

mmc_go_idle

mmc_send_if_cond

mmc_send_app_op_cond

mmc_attach_sd

检测确认一下对应的卡是否仍然存在

探测到SDIO卡: mmc_attach_sdio

探测到SD卡: mmc_attach_sd

探测到MMC卡: mmc_attach_mmc

21

/44

代码骨架:

以时机1来引起探测过程:

module_init==>sdhci_drv_init==>platform_driver_register==>... ...(控制器平台驱动注册,获取设备资源)

==>sdhci_probe==>sdhci_probe_slot==>mmc_add_host==>mmc_start_host==>mmc_detect_change

//delay=0

中断触发方式:

插拔卡产生中断

-> schci_cd_irq()

-> schedule_work(&host->cd_wq)

-> csdhc_cd_callback()

-> mmc_detect_change

void mmc_detect_change(struct mmc_host *host, unsigned long delay)

{

mmc_schedule_delayed_work(&host->detect, delay);

mmc_alloc_host()中有:

}

INIT_DELAYED_WORK(&host->detect, mmc_rescan);

void mmc_rescan(struct work_struct *work)

{

如果是以时机1为条件的话这个地方是不会进来的,这里能进来的情况是,时机

struct mmc_host *host = container_of(work, struct mmc_host, );

1/2/3中已经有一个已经完成,在进行时机3的时候会进来,因为bus_ops 是在

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

&& host->bus_ops->detect

mmc_rescan流程中中才赋值的

host->bus_ops->detect(host);//mmc_sd_detect

利用->bus_ops是否非空为检测条件来判定是否已经初始化注册

作用:如果卡已经初始化注册了,则再检测确认一下对应的卡是否仍然存在

void mmc_sd_detect(struct mmc_host *host)

{

err = mmc_send_status(host->card, NULL);

if (err) { //卡被拨出不在了

mmc_sd_remove(host);

//清空host->bus_ops = NULL,表示卡不在了,下面重走初始化探测流程

mmc_detach_bus(host);

}

}

意义:

1、为电源管理服务;在当前mmc控制器,从前端接口调用mmc_resume_host()时,调到

mmc_detect_change()重新进入mmc_rescan(),如果进入suspend之前已有卡注册,则这里

resume返回时需确认一下卡是否还在。如果存在,下面将直接跳过后面的探测与注册流程(卡

在插卡中断或者poll轮询时已经执行过);如果不存在,则将再次执行探测与注册流程。

2、如果使用poll方式轮询探测设备时,在上一次探测并注册了新卡后,后面将在这里通

过下行”goto out”跳出,直至新卡被拔除。只有在前端控制器驱动开启MMC_CAP_NEEDS_POLL

时才会使用到poll方式,不是必须的。

22

/44

这几个CMD设置的意义其

实是为了兼容不同的协议

if (host->bus_ops != NULL) { //卡已经注册初始化过了

goto out;

}

... ...;

mmc_power_up(host);

sdio_reset(host);

mmc_go_idle(host);

mmc_send_if_cond(host, host->ocr_avail); CMD8

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

if (!err) {

mmc_attach_sdio(host, ocr);

..........................;

goto out;

}

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

if (!err) {

mmc_attach_sd(host, ocr);

goto out;

}

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

if (!err) {

mmc_attach_mmc(host, ocr);

goto out;

}

SDIO协议V2.0 4.4 :Reset IO:CMD52

而SD卡之类的都是使用CMD0 Reset

To support SD 2.0 card,we must always invoke

SD_SEND_IF_COND before SD_APP_OP_COND ,This

command will harmlessly fail for SD 1.0 cards

这里发送acmd41 仅仅只是为了获取OCR,见SD

协议

If the voltage window field (bit 23-0) in the

argument is set to zero, it is called "inquiry CMD41"

that does not start initialization and is use for getting

OCR.

我们这里以

MMC进行解说

out:

}

............;

23

/44

2) 分配和注册MMC卡设备:(下面以MMC card为例分析)

架构设计思想:

mmc_card{}

函数调用图:

为控制器绑定具体总线的操作函数

启动卡的初始化

获取卡的cid

修改总线电压为卡的工作电压

分配card描述符

分配card描述符及初始化这张卡

获取卡的rca、csd、使卡进入传输态

注册card设备

调整总线的初始化设置

mmc_rescan==>探测到SD卡后

将新card绑定到控制器

mmc_attach_sd

mmc_sd_attach_bus_ops

mmc_select_voltage

mmc_sd_init_card

mmc_add_card

device_add

mmc_go_idle

mmc_send_if_cond

mmc_send_app_op_cond

mmc_all_send_cid

mmc_alloc_card

mmc_send_relative_addr

mmc_send_csd

mmc_select_card

24

/44

mmc_rescan()检测->bus_ops非空来判

代码骨架:

定是否要检测卡在位否

static const struct mmc_bus_ops mmc_sd_ops = {

.remove = mmc_sd_remove,

.detect = mmc_sd_detect,

................,

};

mmc_rescan==>探测到SD卡后

int mmc_attach_sd(struct mmc_host *host, u32 ocr)

{

mmc_sd_attach_bus_ops(host);

host->ocr = mmc_select_voltage(host, ocr);

mmc_sd_init_card(host, host->ocr, NULL);

...........................;

mmc_add_card(host->card);

}

u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)

{

int bit;

ocr &= host->ocr_avail;

bit = ffs(ocr);

if (bit) {

bit -= 1;

ocr &= 3 << bit;

host-> = bit;

mmc_set_ios(host);

} else

ocr = 0;

void mmc_sd_attach_bus_ops(struct mmc_host *host)

{

const struct mmc_bus_ops *bus_ops;

bus_ops = &mmc_sd_ops;

mmc_attach_bus(host, bus_ops); //host->bus_ops = bus_ops

}

void mmc_attach_bus(struct mmc_host *host,

const struct mmc_bus_ops *ops)

{

host->bus_ops = ops;//&mmc_sd_ops

host->bus_refs = 1;

host->bus_dead = 0;

}

注意:控制器的host->bus_ops任何时刻,只能绑定

一类总线处理操作(sd/mmc/sdio)

int mmc_add_card(struct mmc_card *card)

{

//例子mmc0:0001

dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);

...............................;

device_add(&card->dev);

mmc_card_set_present(card); //更新card->status

}

#define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT)

25

/44

SD

return ocr;

}

mmc_select_voltage: 判断card所需的运行电压条件,能否被当前控制器支持,

如果支持,则重新设置总线电压;

如果card所需的运行电压条件,不能被当前控制器支持,则终止初始化,返回出错码

//按标准的MMC/SD卡规范上面的协议流程来进行卡的初始化

static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, struct mmc_card *oldcard)

{

struct mmc_card *card;

u32 cid[4];

说明:ACMD41有两个用途:

mmc_go_idle(host);

1、

发送一个询问,让潜在的

SD Card

response

方式,提供其运

err = mmc_send_if_cond(host, ocr);

行条件寄存器

(OCR)

的值

,

并存入

ocr

变量;

if (!err)

2、核对SD Card的运行电压条件和容量,检测SD/MMC此卡设备

的正常工作所需的电压能否被卡控制器所支持

ocr |= 1 << 30; ACMD41

mmc_send_app_op_cond(host, ocr, NULL);

这里是第二个功能,核对卡所需电压能否被支持

mmc_all_send_cid(host, cid);//CMD2 GET CID

card = mmc_alloc_card(host, &sd_type);

struct mmc_card *mmc_alloc_card(struct mmc_host *host,

struct device_type *type)

card->type = MMC_TYPE_SD;

{

memcpy(card->raw_cid, cid, sizeof(card->raw_cid));

struct mmc_card *card;

card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);

if (!mmc_host_is_spi(host)) {

... ...;

card-> = &mmc_bus_type;

mmc_send_relative_addr(host, &card->rca);//CMD3

.....................;

mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);

return card;

}

}

mmc_send_csd(card, card->raw_csd);//CMD9

设置busmode为pushpoll 在cmd3之前busmode为opendrain

mmc_select_card(card);//CMD7

host->card = card;

7进行选卡操作

2.卡将进入传输模式,可进行

进行块读块写操作

26

/44

}

Cmd9: send csd

Cmd7:select card and put it into transfer mode只有使用cmd7 选卡之后,

使卡进入传输模式之后,才能发送CMD17/18/24/25进行块读块写操作。

27

/44

四、MMC/SD卡块设备驱动初始化:(这里即card设备对应的驱动)

架构设计思想:

mmc_blk_init

函数调用图:

mmc_blk_probe

mmc_blk_alloc

add_disk

代码骨架:

module_init

register_blkdev

mmc_register_driver

mmc_blk_probe

将块设备名”mmc”和主设备号注册到块层中

将mmc_driver{}设备驱动注册到驱动模型中

块驱动的初始化、及磁盘分区的注册

find_first_zero_bit

kzalloc

alloc_disk

mmc_init_queue

blk_init_queue

kthread_run

28

/44

1) MMC/SD/SDIO块设备驱动注册及初始化:

#define DECLARE_BITMAP(name,bits) unsigned long name[BITS_TO_LONGS(bits)]

#define MMC_SHIFT 3

#define MMC_NUM_MINORS (256 >> MMC_SHIFT)

static DECLARE_BITMAP(dev_use, MMC_NUM_MINORS);//32: SD/MMC/SDIO卡的位图为32,即最多32只卡设备

struct mmc_blk_data {

spinlock_t lock;

struct gendisk *disk;

mmc_rescan:探测到SD卡==>

struct mmc_queue queue;

unsigned int usage;

mmc_attach_sd==>mmc_add_card==>

unsigned int read_only;

device_add(&card->dev): “mmc0:0001”

};

struct mmc_driver mmc_driver = {

.drv = {

.name = "mmcblk",

启动时卡已插入: mmc_add_card

},

.probe = mmc_blk_probe,

mmc_bus_probe

};

启动后插入SD卡:触发中

断..........-->mmc_detect_change()--->mmc_rescan=

1.关联mmc_card{}<-->mmc_blk_data{}

=>mmc_attach_sd/mmc==>mmc_add_card

2.

激活磁盘设备

module_init(mmc_blk_init)===>

static int __init mmc_blk_init(void)

{

//向块层注册块设备名”mmc”和主设备号

register_blkdev(MMC_BLOCK_MAJOR, "mmc");

mmc_register_driver(&mmc_driver);

29

/44

}

int mmc_register_driver(struct mmc_driver *drv)

{

drv-> = &mmc_bus_type;

return driver_register(&drv->drv);//mmc_driver设备驱动注册到驱动模型,挂在mmc_bus_type上面

}

2) MMC/SD/SDIO块驱动的初始化、及磁盘分区的注册:

时机一:启动时卡已插入,且mmc_add_card-->device_add已执行时

则:driver_register()-->bus_add_driver()-->driver_attach()-->bus_for_each_dev()-->

__driver_attach()-->driver_probe_device()====>really_probe()====>mmc_bus_probe ()

时机二:启动后插入SD卡:触发detect_irq中断-->esdhc_cd_callback()-->mmc_detect_change()-->mmc_rescan()

-->mmc_attach_sd/mmc()-->mmc_add_card()-->device_add()-->bus_probe_device()-->device_attach()-->

bus_for_each_drv()-->

__device_attach -->driver_probe_device()-->really_probe()-->mmc_bus_probe ()

static int mmc_bus_probe(struct device *dev)

{

struct mmc_driver *drv = to_mmc_driver(dev->driver);

struct mmc_card *card = dev_to_mmc_card(dev);

return drv->probe(card); //mmc_blk_probe

}

static int mmc_blk_probe(struct mmc_card *card)

{

struct mmc_blk_data *md;

md = mmc_blk_alloc(card);

//CMD16 MMC_SET_BLKLEN 设置块长

mmc_blk_set_blksize(md,card);

mmc_set_drvdata(card,md);

add_disk(md->disk);

激活磁盘设备,调用此函数之后就可

以立即对磁盘设备进行操作了

关联mmc_card{}<-->mmc_blk_data{}

30

/44

}

struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card)

{

struct mmc_blk_data *md;

devidx = find_first_zero_bit(dev_use, MMC_NUM_MINORS);

if (devidx >= MMC_NUM_MINORS)

return ERR_PTR(-ENOSPC);

__set_bit(devidx, dev_use);

md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL);

//分配通用gendisk结构体

md->disk = alloc_disk(1 << MMC_SHIFT);

//初始化请求队列

该函数主要完成如下功能:

1. 分配通用gendisk结构,块设备号

2. 初始化请求队列

3. 设置通用磁盘gendisk结构的成员变量

mmc_init_queue(&md->queue, card, &md->lock);

md->_fn = mmc_blk_issue_rq; //mmc_blk_issue_rq展开见第二篇

md->disk->major = MMC_BLOCK_MAJOR;

md->disk->first_minor = devidx << MMC_SHIFT;

md->disk->fops = &mmc_bdops;

md->disk->queue = md->;

md->disk->driverfs_dev = &card->dev;

mmc_read_ext_csd

mmc_attach_mmc

mmc_rescan

//设置磁盘容量,以扇区为单位

set_capacity(md->disk, card->ext_s);

return md;

}

//mmc_bus_probe==>mmc_blk_probe==>mmc_init_queue

int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, spinlock_t *lock)

{

... ...;

mmc_init_card

31

/44

->request_fn=mmc_request

mq->card = card; //mmc_queue{} <-->mmc_card{}

//mq->queue

mq->queue = blk_init_queue(mmc_request, lock); //mmc_request展开见第二篇

mq->queue->queuedata = mq;

mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd"); //mmc_queue_thread展开见第二篇

... ...;

}

struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)

{

struct request_queue *blk_alloc_queue_node(gfp_t gfp_mask, int node_id)

return blk_init_queue_node(rfn, lock, -1);//rfn: mmc_request

{

}

struct request_queue *q;

q = kmem_cache_alloc_node(blk_requestq_cachep,

gfp_mask | __GFP_ZERO, node_id);

struct request_queue * blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)

q->backing_dev__io_fn = blk_backing_dev_unplug;

{

INIT_WORK(&q->unplug_work, blk_unplug_work);

}

struct request_queue *uninit_q, *q;

uninit_q = blk_alloc_queue_node(GFP_KERNEL, node_id);

q = blk_init_allocated_queue_node(uninit_q, rfn, lock, node_id);//rfn: mmc_request

struct request_queue *q =container_of(work,

3ms timeout后,将调用blk_unplug_timeout,

struct request_queue, unplug_work);

return q;

}

q->unplug_fn

struct request_queue * blk_init_allocated_queue_node(struct request_queue *q,

request_fn_proc *rfn, spinlock_t *lock, int node_id)

{

该函数的作用:用来选择一个指定的IO调度器,通常有四种IO调度器(算法):

q->node = node_id;

anticipatory(预期算法), cfq(完全公平队列), deadline(最后期限), NOOP(no

operation) 如果没有指定调度器, 即下面这种情况:内核会首先尝试去看启动

q->request_fn = rfn;// ==> mmc_request

参数中有没有没有设置“elevator=”,如果启动参数没有指定;内核就会去选

................................;

择编译内核时指定的默认调度器,如果前面情况都不成功,才会选择noop调

q->unplug_fn = generic_unplug_device;

度器。

blk_queue_make_request(q, __make_request);

32

/44

if (!elevator_init(q, "noop")) {//将调度算法由cfq改为noopq

NOTES:对SD/iNand这种块设备,哪种调度器更适合? 为什么?

................................;

noop调度器更适合,因为SD/iNand是支持快速随机访问的,不像磁盘设备有一

}

个寻道时间,所以一个复杂的I/O调度器发挥不了什么作用,反而其本身会耗掉

不少CPU和内存。 而noop调度器基本上不会请求做什么附加处理,对SD/INAND

}

这类块设备更适合

//mfn: __make_request q: uninit_q

void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)

{

q->make_request_fn = mfn; //__make_request

void blk_unplug_timeout(unsigned long data)

{

q->unplug_delay = msecs_to_jiffies(3);//在后面用到

struct request_queue *q = (struct request_queue *)data;

.......................;

q->unplug_on = blk_unplug_timeout;

kblockd_schedule_work(q, &q->unplug_work);

q->unplug_ = (unsigned long)q;//uninit_q

}

}

应用读写请求==>bio==>融

合成request==>蓄流

blk_backing_dev_unplug

33

/44

第二篇:请求处理

1).

上层读写请求的处理:

块层及以上: ... ... -->__make_request-->__generic_unplug_device-->q->request_fn() 即mmc_request

(具体怎么调到__make_request请参见附录:块层以上系统读写调用流程)

该函数用于处理高端内存的,涉及内容较多。

static int __make_request(struct request_queue *q, struct bio *bio)

Q. 什么是bounch buffer?

{

A. 在32位体系架构中,物理内存最开始的1G,称为“低端内存”,

超过1G的部分称为"高端内存"。内核要求通往存储设备的数据缓

struct request *req;

存必须位于“低端内存”区域即使APP可以同时使用高端和低端

int rw_flags;

内在也一样。这样来自“低端内存“区域数据缓存的IO请求可以

直接进行内存的存取操作,但是当APP发现的IO请求中包含有

位于”高端内存“的数据缓存时,内核将强行在 ”低端内存“中

blk_queue_bounce(q, &bio); 补充知识

分配一个临时数据缓存,并将位于"高端内存"的应用程序数据拷

贝到此处, 这个数据缓存就相当于一个数据中转的buffer,这就

是bounch buffer.

el_ret = elv_merge(q, &req, bio);

switch (el_ret) {

该函数的用途:

case ELEVATOR_BACK_MERGE: ...

应用发出的读写请求:检测当前这个bio能否融合到已有的

case ELEVATOR_FRONT_MERGE: ...

request中

/* ELV_NO_MERGE: elevator says don't/can't merge. */

ELEVATOR_NO_MERGE: 请求队列中已存在的request不能包含当

前bio,需要创建新的request.

default: ;

}

//确定bio的方向,即是读请求还是写请求

rw_flags = bio_data_dir(bio);

若在上面调用elv_merge()将bio合并到某个请求队列中的某个请求失败,那就要调用下

面的get_request_wait()来为bio申请一个新的request,并且保证能够分配成功。

req = get_request_wait(q, rw_flags, bio);

//分配了一个新的request,根据bio将其初始化

init_request_from_bio(req, bio);

Linux 块设备蓄流和泄流(plug/unplug)技术

//这里就是后面会提到的"蓄流/泄流"技术中的蓄流的地方,主要通过定时器延时来实现。

"蓄流/泄流"的工作方式是:当i/o请求被提

交时,它被储存在一个队列中,这就是蓄流;

if (queue_should_plug(q) && elv_queue_empty(q))

等一定时间或积累一定的i/o请求时,才允

blk_plug_device(q);

许从队列中取出i/o请求这就是泄流。

块i/o子系统之所以不将上层发来的i/o请求

立即发送到底层,允许一定量的i/o请求累

积,主要是为了实现i/o的调度,最终目的

还是对i/o进行合并和排序。这么做的终极

目标是改善基于磁盘的块设备的访问性能。

34

/44

//i/o调度层关键:

//将请求添加到请求队列,何时被处理取决于i/o调度器的调度算法。

add_request(q, req);

out:

if (unplug || !queue_should_plug(q))

__generic_unplug_device(q);

}

void blk_plug_device(struct request_queue *q)

{

//设置q->queue_flags的QUEUE_FLAG_PLUGGED位为1 最终调用

if (!queue_flag_test_and_set(QUEUE_FLAG_PLUGGED, q)) {

超时处理函数 q->unplug_on = blk_unplug_timeout;

当定时器超时,处理函数会调用 kblockd_schedule_work(q, &q->unplug_work);

mod_timer(&q->unplug_timer, jiffies + q->unplug_delay);

q->unplug_work工作队列在下面完成初始化:

.....................;

即blk_init_queue() ==> blk_init_queue_node() ==> blk_alloc_queue_node()中

}

INIT_WORK(&q->unplug_work, blk_unplug_work); blk_unplug_work()又会调用

}

q->unplug_fn(q); 这个q->unplug_fn(q)函数指针的挂接是在blk_init_queue_node()

==> blk_init_allocated_queue_node(); q->unplug_fn = generic_unplug_device;

generic_unplug_device() 又会调用 __generic_unplug_device(q);

那么这里的调用流程是:

void __generic_unplug_device(struct request_queue *q)

超时 ==> blk_unplug_timeout() ==> blk_unplug_work() ==> generic_unplug_device()

{

==> __generic_unplug_device()

..........................

来源:

mmc_blk_probe==>mmc_blk_alloc==>

q->request_fn(q); //<==> mmc_request

mmc_init_queue==>

}

->request_fn=mmc_request

void mmc_request(struct request_queue *q)

{

struct mmc_queue *mq = q->queuedata;

if (!mq->req)

mmc_blk_probe==>mmc_blk_alloc==>mmc_init_queue==

wake_up_process(mq->thread); //唤醒 mmc_queue_thread

=> kthread_run(mmc_queue_thread, mq, "mmcqd");

}

static int mmc_queue_thread(void *d)

35

/44

{

struct mmc_queue *mq = d;

struct request_queue *q = mq->queue;

int ret = 0;

... ...;

do {

struct request *req = NULL;

if (!blk_queue_plugged(q)) //如果队列是非阻塞状态

req = blk_fetch_request(q);

//从请求队列中取出一个请求

mq->req = req;

if (!req) {

if (kthread_should_stop()) {

set_current_state(TASK_RUNNING);

break;

}

schedule();//<==mmc_request()

唤醒它,即有请求时就会唤醒

continue;

}

set_current_state(TASK_RUNNING);

ret = mq->issue_fn(mq, req); // mmc_blk_issue_rq

if (ret == ERR_CARD_REMOVED) {

if (kthread_should_stop()) {

set_current_state(TASK_RUNNING);

break;

}

schedule();//拨卡后,kthread_stop()会唤醒 <== mmc_cleanup_queue()

}

} while (1);

... ...;

}

static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)

36

/44

... ...;

struct mmc_blk_request brq;

int ret = 1

... ...;

do {

... ...;

u32 readcmd, writecmd, status = 0;

... ...;

= &;

= &;

... ...;

if ( > 1) {

... ...;

readcmd = MMC_READ_MULTIPLE_BLOCK;

writecmd = MMC_WRITE_MULTIPLE_BLOCK;

} else {

... ...;

readcmd = MMC_READ_SINGLE_BLOCK;

writecmd = MMC_WRITE_BLOCK;

}

if (rq_data_dir(req) == READ) {

= readcmd;

|= MMC_DATA_READ;

} else {

= writecmd;

|= MMC_DATA_WRITE;

}

... ...;

//此处的后续基本流程见下篇MMC/SD/SDIO命令的处理

mmc_wait_for_req(card->host, &);

37

/44

{

... ...;

ret = __blk_end_request(req, 0, _xfered);

... ...;

} while (ret);

blk_update_request

}

应用发出多个读写请求

blk_delete_timer

blk_account_io_done

req->end_io

__blk_put_request

多个合条件的bio合并成一个request

请求队列蓄流,设定unplug_delay

mmc_request于是应用的读写请求到达MMC/SD/SDIO层

unplug_delay超时到泄流:__generic_unplug_device

转入MMC/SD/SDIO命令的处理

唤醒 mmc_queue_thread

mmc_wait_for_req

38

/44

static const struct mmc_host_ops sdhci_ops = {

.request = sdhci_request,

二:执行命令

发送命令整体流程架构:

mmc_send_if_cond

mmc_wait_for_cmd

异步执行去睡眠等待

(卡已拔出)

(sdhci_finish_worker)

};

sdhci_of_probe==>struct mmc_host *mmc;

mmc = mmc_alloc_host(...); mmc->ops = &sdhci_ops;

mmc_start_request

mmc_wait_for_req

wait_for_completion

睡眠

host->ops->request : sdhci_request

err && (cmd->retries>0)

有错,但重试次数还有

继续重新执行试试看

mmc_request_done

(命令发送出错)

(sdhci_finish_worker)

sdhci_irq

sdhci_cmd_irq

成功

sdhci_finish_command

否则

sdhci_send_comman

(命令发送成功)

(sdhci_finish_worker)

设置定时、操作寄存器

发完命令且收到中断

如果超时

(sdhci_timeout_timer)

(sdhci_finish_worker)

err &&( cmd->retries==0)

唤醒进程/线程

mmc_request_done

mrq->done : mmc_wait_done

complete

39

/44

注:请求的处理,最终都通过发命令来完成。由于包含关系较多,为避免混乱,此仅以mmc命令执行为例,展示一个最基本的流程!上层读写请求的处理也由mmc为其构造

命令,底层处理流程一致,这些命令将会收发数据,可以参照流程具体分析;

40

/44

函数调用图:(同上)

代码骨干:

在第一篇里多处用到mmc命令的发送,除命令的自身差别和这些命令不带数据外,其发送过程都将

调用到mmc_wait_for_cmd(),并统一按照如下流程执行:

下面以mmc_send_if_cond(详细见第一篇)为例,引出mmc_wait_for_cmd():

int mmc_send_if_cond(struct mmc_host *host, u32 ocr)

{

struct mmc_command cmd; //构造一个mmc_command{}

static const u8 test_pattern = 0xAA;

= SD_SEND_IF_COND;

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

= MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR;

mmc_wait_for_cmd(host, &cmd, 0);

... ...;

}

int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)

{

struct mmc_request mrq;

memset(&mrq, 0, sizeof(struct mmc_request));

memset(cmd->resp, 0, sizeof(cmd->resp));

cmd->retries = retries;

= cmd; //关联mmc_request{} --> mmc_command{}

cmd->data = NULL;

mmc_wait_for_req(host, &mrq);

return cmd->error;

}

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;

void mmc_wait_done(struct mmc_request *mrq)

{

mmc_start_request(host, mrq);

complete(mrq->done_data);

wait_for_completion(&complete);

}

}

mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)

{

mrq->cmd->error = 0;

mrq->cmd->mrq = mrq;

if (mrq->data) {

mrq->cmd->data = mrq->data;

mrq->data->error = 0;

mrq->data->mrq = mrq;

if (mrq->stop) {

mrq->data->stop = mrq->stop;

mrq->stop->error = 0;

mrq->stop->mrq = mrq;

41

/44

}

}

//sdhci_request

host->ops->request(host, mrq);//调用前端控制器驱动的接口,执行硬件操作;

}

为进一步展现流程的全貌,

下面以imx51 esdhc控制器对应的前端驱动为例展开:(host->ops->request = sdhci_request)

#define SDHCI_CD_PRESENT (1<<8)

#define SDHCI_INT_RESPONSE 0x00000001

static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)

{

struct sdhci_host *host;

unsigned long flags;

host = mmc_priv(mmc);//return (void *)mmc->private

if (!host->plat_data->clk_flg) {

clk_enable(host->clk);

host->plat_data->clk_flg = 1;

}

... ...;

host->mrq = mrq;

if (!(host->flags & SDHCI_CD_PRESENT)) {

host->mrq->cmd->error = -ENOMEDIUM;

queue_work(host->workqueue, &host->finish_wq);//调用sdhci_finish_worker

} else

sdhci_send_command(host, mrq->cmd);

... ...;

}

//按照控制器的逻辑,操作寄存器最终完成命令发送(函数内部说明从略)

static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)

{

int flags;

u32 mask, mode = 0;

... ...;

mod_timer(&host->timer, jiffies + 10 * HZ); //超时调用sdhci_timeout_timer

host->cmd = cmd;

sdhci_prepare_data(host, cmd->data);

writel(cmd->arg, host->ioaddr + SDHCI_ARGUMENT);

... ...;

mode |= SDHCI_MAKE_CMD(cmd->opcode, flags);

writel(mode, host->ioaddr + SDHCI_TRANSFER_MODE);

}

1、如果等待中断,出现超时时:

static void sdhci_timeout_timer(unsigned long data)

{

struct sdhci_host *host;

host = (struct sdhci_host *)data;

... ...;

if (host->mrq) {

... ...;

if (host->data) {

... ...;

42

/44

}

... ...;

} else {

if (host->cmd)

host->cmd->error = -ETIMEDOUT;

else

host->mrq->cmd->error = -ETIMEDOUT;

queue_work(host->workqueue, &host->finish_wq);//调用sdhci_finish_worker

}

}

2、未超时时,如果命令发送完成收到中断, 调用sdhci_irq

sdhci_irq===>

static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask)

{

... ...;

if (host->cmd->error)

queue_work(host->workqueue, &host->finish_wq);//调用sdhci_finish_worker

else if (intmask & SDHCI_INT_RESPONSE)

sdhci_finish_command(host);

}

static void sdhci_finish_command(struct sdhci_host *host)

{

... ...;

host->cmd->error = 0;

... ...;

if (!host->cmd->data)

queue_work(host->workqueue, &host->finish_wq);//调用sdhci_finish_worker

... ...;

host->cmd = NULL;

}

static void sdhci_finish_worker(struct work_struct *work)

{

struct sdhci_host *host = container_of(work, struct sdhci_host, finish_wq);

struct mmc_request *mrq;

del_timer(&host->timer);

mrq = host->mrq;

host->mrq = NULL;

host->cmd = NULL;

host->data = NULL;

mmc_request_done(host->mmc, mrq);

... ...;

}

void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)

{

struct mmc_command *cmd = mrq->cmd;

int err = cmd->error;

... ...;

if (err && cmd->retries) {

cmd->retries--;

43

/44

cmd->error = 0;

host->ops->request(host, mrq);//重新发送

} else {

... ...;

if (mrq->done)

mrq->done(mrq); //即mmc_wait_done(见上文)

}

}

void mmc_wait_done(struct mmc_request *mrq)

{

complete(mrq->done_data);//唤醒wait_for_completion(&complete);

}

说明:

前面有若干位置均可能会调用到sdhci_finish_worker, 但是只有两种情形才会调用mrq->done

唤醒等待中的线程:1、命令成功发送,2、超出命令的重发次数;而在其他情形下,原因可能是

卡已拔出,等待中断超时,发送命令出错等故障,都会通过host->ops->request重新操作,直至达

到上面两种情形之一;

44

/44

附录: 块层以上系统读写调用流程

块I/O子系统整体框架:

注意:本文以块I/O子系统架构为重点,对于文件系统在此仅理清相关函数调用关系,不作深入分析。

用户空间: 系统调用(read/write)

虚拟文件系统(VFS)

EXT4

UBIFS

NFS

……

文件系统 Page Cache

通用块层(Generic Bloack Layer)

I/O调度层(I/O Scheduler Layer)

块设备驱动层(Block Device Driver Layer)

内核层

硬件

块设备层(Bloack Device Layer)

块i/o子系统对上层传入的i/o请求的一般处理流程如下:

上层通过通用块层提供的接口向i/o子系统提交i/o请求,这些请求会通过i/o调度层的调度队列,

进行排序和合并,最终将通用块层bio请求转换成块设备请求request,发给具体块设备的等待队列,由

块设备的驱动进行处理。

上面各层的简单介绍如下:

虚拟文件系统:是用来屏蔽下层各类具体文件系统操作上的差异,为上层的相关操作提供一个统一的接口。

有了这一层,就可以把设备抽象成文件,使操作设备就像操作文件一样简单。

VFS并不是一种实际的文件系统,它只存在于内存中,在系统启动时建立,系统关闭时消失。

VFS由超级块,inode,dentry,vfsmount等信息组成。

具体文件系统: 有诸如EXT2,EXT4,UBIFS等,每种文件系统的操作都有所不同,都有定义各自的操作集合。

Cache层: 是为了提供linux对磁盘的访问性能,Cache层在内存中缓存了磁盘上的部分数据,当数据请

求到达时,如果Cache中有该数据,且是最新的,则直接将该数据返回给用户程序,免除了对磁

盘的访问,从而提高了性能。

通用块层: 这一层的主要工作是接收来自上层的磁盘读写请求,并最终发现I/O请求。

它隐藏了底层各类硬件块设备的特性,为块设备提供了一个抽象的视图。

I/O调度层:这层是用来接收通过块层发下来的I/O请求,缓存请求并尝试合并邻近请求(两个请求的数

据在磁盘上相邻),以提高效率。并根据设置好的调度算法,回调驱动中提供的请求处理

函数,处理I/O请求。

块设备驱动层: 这一层对应具体的物理块设备,它根据上层传下来的I/O请求中的信息,通过向具体的块

设备控制器发命令的方式,来操纵设备进行数据传输。

块设备层 : 每种块设备都有定义自己的操作规范。

45

/44

协议流程:

46

/44

47

/44

48

/44