现在的Linux内核中,mmc不仅是一个驱动,而是一个子系统。这里通过分析Linux3.2.0内核,结合TI的arm335x平台及omap_hsmmcd host分析下mmc子系统,重点关注sdio及架构在其上的具体sdio IP驱动实现。

1. General overview

1.1 源码概览

Linux kernel把mmc,sd以及sdio三者的驱动代码整合在一起,俗称mmc子系统。源码位于drivers/mmc下。其下有三个子目录,分别是:

其中,card用于构建一个块设备作为上层与mmc子系统沟通的桥梁;core抽象了mmc,sd,sdio三者的通用操作;host则是各类平台上的host驱动代码,包括如TI Omap的omap_hsmmc,三星的s3cmci等。

1.2 硬件层IP对象间的联系

即,cpu要访问slave必须通过host进行,包括slave的中断。omap host的具体组成及其与slave之间的连线如图1.1所示:

图1.1

和别的中断控制器一样,host的MPU中断子系统有一个仲裁机制,分别使用IE和ISE控制是否向CPU报中断以及是否care slave报出来的中断。

Host在数据传输时支持8bit模式,但slave是sdio时仅支持1-bit & 4-bit模式,当支持4-bit模式时,data1和IRQ线复用。

注意:MMCHS的smart-idle wake up line(SWAKEUP)没有连出去,在别的一些host中,它是和PRCM子系统连接,用于在MMCHS处于suspend时自动提醒PRCM给MMCHS提供clock。

2. 从bus,driver,device看mmc子系统

Linux下的任何驱动在内核中最终都抽象为bus, driver以及device三者间的相互作用。

2.1 mmc下的bus,driver,device模型

Mmc子系统涉及到三条总线。

Host驱动相应的driver和device挂载在Linux内核内置的虚拟抽象总线platform_bus_type。两者的匹配采用名称匹配的方式,即driver和device两者的name一样则认为该device对应该driver,这里是”omap_hsmmc”。

Card驱动相应的driver和device挂载在mmc自己创建的虚拟总线mmc_bus_type下,直接匹配。

Sdio驱动相应的driver和device挂载在mmc自己创建的虚拟总线sdio_bus_type下,ID匹配。

注意:Linux内核中,匹配函数默认使用bus注册的匹配函数,如果bus没有注册则使用driver注册的匹配函数。所以,一般自己创建虚拟总线时,其匹配函数和driver的匹配函数都是一致的。

2.2 按时间顺序观察mmc中各bus,driver,device对象初始化流程

2.2.1 Host device对象

Host device对象首先被初始化并挂载到platform_bus_type,但是这个过程不在mmc子系统源码下,它在平台初始化过程中init了,具体的流程参考5.1。

2.2.2 mmc_bus,sdio_bus对象

core初始化时,件core.c中的subsys_initcall(mmc_init)创建这两条mmc自己的虚拟总线。

2.2.3 card driver对象

Card初始化时,文件block.c中module_init(mmc_blk_init);函数创建mmcblk driver对象,并将之挂载到mmc_bus_type总线上。

2.2.4 host driver对象

Host初始化时,文件omap_hsmmc.c中module_init(omap_hsmmc_init);函数创建”omap_hsmmc”driver对象。

2.2.5 card device对象

到2.2.4为止,host的device和driver都已经创建,匹配后调用host对应的probe函数:omap_hsmmc_probe()。该函数将进行检测slave、初始化slave等操作。

检测sdio并创建card device对象。换言之,这个device对象并没有对应的硬件设备,它只是抽象出来用于和他人交互的一个虚拟device。具体的流程参考5.1。

2.2.6 sdio device对象

紧接着2.2.5后面,在函数sdio_init_func()函数中将创建一个device对象并挂载到sdio_bus_type上。具体的流程参考5.1。

2.2.7 小结

到此为止,mmc下三条总线、六个device/driver对象只差一个sdio driver了。这个driver对象对应着具体的sdio IP驱动,比如sdio_wifi, sdio_uart等。这样来看sdio IP驱动其实是构建在mmc子系统之上的。

3. 上层与mmc以及mmc内部模块之间的交互方式

从软件层面看mmc内部模块交互以及外部访问mmc的方式,如图3.1所示,。

图3.1

如之前所言,mmc内部的core模块是各类host、sd及sdio操作的抽象,访问host的通用行为都放在其中,而各类host将其各自独特的行为注册到host规定的接口上。Mmc提供了两种方式供外部访问,mmc子系统本身对外呈现的是一个块设备,应用层通过VFS到具体的FS再到块设备逐层访问,块设备通过core让host和slave进行通信,sd卡的访问就是这种典型方式。另外一种方式是不通过块设备访问core,而是在驱动层新建一个驱动,这个驱动构建在mmc之上,让它有权限访问core,具有sdio接口的IP驱动就可以这么做。至于这个sdio的IP驱动对外以何种方式呈现由它自己决定,抽象为一个块设备或者字符设备都可以。

需要注意的是:core内部访问host的操作有睡眠动作,一般的访问行为是这样的:

1) 获取host资源(mmc_claim_host),可能sleep;

2) 发送访问请求(如读/写);

3) 等待(block)本次访问结果(无论slave是否response,host都会通过中断给出返回值);

4) 释放host资源(mmc_release_host)。

4. sdio

4.1 sdio和sd/mmc的差异

sdio事件通过card interrupt通知host,在host已有的中断处理过程中进行,如图4.1所示。

图4.1

但是sdio的中断事件(以CIRQ bit位表示)的处理和别的事件有点差异。Sdio中断事件的处理可能需要sleep。

比如读操作,它的流程是:1)来一个CIRQ表明sdio中的FIFO已经达到阈值;2)host告诉上层这个事件;3)sdio中断处理例程将去sdio FIFO中读取数据,它是通过host的标准操作进行的,如之前所言,这个过程会阻塞。而同样的读操作对sd卡则不需要,它的流程是:1)host发出读操作;2)slave把准备好的数据放到host的FIFO中并assert中断;3)host处理自己的FIFO并通知上层读操作结束。

所以对于sdio而言,host只是充当sdio和上层的沟通媒介,它不是sdio slave的controller,sdio IP有自己的controller,有自己的FIFO。

4.2 TI Omap平台下的sdio中断处理实现

截至3.12内核版本,Sdio在TI的Omap Soc下仍然没有实现以中断方式处理sdio事件。具体点说,即mmc子系统软件框架已经考虑了sdio中断,但是针对omap平台的host驱动(omap_hsmmc.c)没有支持,它直接忽略了中断寄存器SD_STAT中的CIRQ。

4.2.1为什么host不支持sdio中断?

究其原因,应该是因为某些平台的host没有swakeup line(smart-idle-wakeup),比如arm335x。看一下图4.1所示的mmc子系统的大致硬件架构和host的硬件组成就大致明白了。

Host包括hsmmc和PRCM等子模块,其中PRCM提供clock,电源管理时host可能会进入suspend模式,该模式下PRCM不提供clock,所以此时host将无法处理CIRQ中断,除非上层强制host wake up,否则host不会智能恢复正常工作。而如果有swakeup line的时候,当有CIRQ时,通过swakeup line,PRCM将自动供clock。这一点在图1.1中可以看的更清楚。

4.2.2 mmc子系统如何实现sdio中断方式

假设不考虑上述没有swakeup line,进入suspend状态的hsmmc无法在有卡中断时自动开始恢复工作的情况,或者说我们已经找到了某种解决办法后,考虑下host,sdio等mmc子系统模块如何配合实现使用中断方式处理sdio事件。

图4.2表明了在当前mmc子系统软件架构下可行的中断处理方式。

图4.2

请注意图中给出的注意事项。

5. Appendix

5.1 平台初始化流程

5.1.1 Linux内核平台初始化

Linux内核将平台相关的具体数据和模块的特定初始化函数分别放在两个平台相关的文件中。内核提供一个框架,并调用各平台提供的注册函数。

以omap arm33xx为例,板级初始化相关函数放在文件board-am335xevm.c中,由kernel框架调用;板级各模块特定信息放在omap_hwmod_33xx_data.c中,并由omap_hwmod.c中提供的函数进行调用。

注意:现在Linux下已经不用这种方式存储板级信息,取而代之的是device tree,它用一种更简洁的方式来描述这些特定平台信息,从而让kernel代码更加清晰。

5.1.2 平台环境文件

平台环境文件board-am335xevm.c定义了各模块的初始化函数,以及提供内核初始化必要的平台初始化函数:

MACHINE_START(AM335XEVM, "xxxx")/* Maintainer: Texas Instruments */.atag_offset         = 0x100,.map_io               = am335x_evm_map_io,.init_early  = am33xx_init_early,.init_irq      = ti81xx_init_irq,.handle_irq     = omap3_intc_handle_irq,.timer                  = &omap3_am33xx_timer,.init_machine      = am335x_evm_init,MACHINE_END

上面这段代码注册了内核在start_kernel()中会调用的函数,如map_io提供将各IP寄存器地址到内核虚拟地址的转换,init_early()函数由start_kernel()--->setup_arch()--->mdesc->init_early()调用,其余的init_irq,handle_irq()等会在start_kernel()中分别调用。其中init_machine()函数用于初始化各IP模块的device抽象对象,熟悉Linux驱动架构的应该就清楚了,这就是bus,driver,device三剑客之一的device初始化的地方。

举例来说,init_machine()这个函数被调用流程是:start_kernel()--->rest_init()--->kernel_init()-àdo_basic_setup()-àdo_initcalls()--->customize_machine()--->init_machine()。

5.1.3 平台数据文件及其调用者

可以看到init_machine()函数在do_initcalls()中处于第三优先级,第一优先级的是core_init(),它会使用omap_hwmod.c提供的相关函数在device初始化前作一些操作。

omap_hwmod_33xx_data.c大部分内容是定义板子各模块的资源信息如clock、irq号、模块寄存器相应的物理地址等。

do_initcalls()时,core_initcall(omap_hwmod_setup_all)---> _setup()(对每个模块依次作此操作)。

所以平台初始化操作除MACHINE_START提供的几个函数外,还有一个地方就是这里的omap_hwmod_setup_all。

以各模块的sysconfig寄存器初始配置为例:

core_initcall(omap_hwmod_setup_all);

omap_hwmod_for_each(_setup, NULL);//枚举平台omap_hwmod_33xx_data.c中定义的omap_hwmod(每个模块的寄存器设定值)

_setup

++++++++++++ 以下是clock相关 +++++++++

_enable

_enable_sysc

_set_clockactivity

_write_sysconfig

omap_hwmod_write(v, oh, oh->class->sysc->sysc_offs);

注意:omap相关模块寄存器默认值一般情况下并非都为0,以mmc的sysconfig为例,其默认值是0x2015。

5.2 slave检测流程

如2.2.5所言,host的device和driver创建并匹配后调用host对应的probe函数:omap_hsmmc_probe()。其中一个重要的步骤是启用一个工作队列进行slave检测并创建后续相关device/driver对象。

Host驱动为了兼容多种host controller,有些操作对于SDIO是不需要的(下面用删除线mark了),但是给SDIO这样的操作,SDIO不应报错。具体流程如下所示:

mmc_alloc_host

INIT_DELAYED_WORK(&host->detect, mmc_rescan);

mmc_add_host(mmc);

mmc_start_host(host);

mmc_power_off(host);

mmc_detect_change(host, 0);

mmc_schedule_delayed_work(&host->detect, delay);

mmc_rescan

mmc_rescan_try_freq

sdio_reset(host); //使用CMD52设置slave寄存器6(CCCR)的RES bit

mmc_go_idle(host);

mmc_send_if_cond(host, host->ocr_avail);

mmc_attach_sdio(host)

mmc_send_io_op_cond(host, 0, &ocr);//使用CMD5命令,获取OCR值(slave支持的电压范围)

mmc_attach_bus(host, &mmc_sdio_ops);//设置host的bus操作函数是mmc_sdio_ops

mmc_select_voltage(host, ocr);//设置电压为选取电压(slave本身支持的电压范围 & 平台环境选取的电压ocr_avail==ocr_mask)的最小值

mmc_sdio_init_card(host, host->ocr, NULL, 0);

mmc_alloc_card(host, NULL);//device_init,创建card device,指定为mmc_bus_type

host->ops->init_card == omap_hsmmc_init_card //没设置,为空

mmc_send_relative_addr(host, &card->rca);//CMD3,获取slave的rca

mmc_select_card(card);//CMD7,选中rca对应的slave

sdio_read_cccr(card);//read CCCR

sdio_read_common_cis(card);// read CIS

// set high speed, set bus width and so on...

sdio_init_func

sdio_alloc_func//创建device,指定为sdio_bus_type

sdio_read_fbr

sdio_read_func_cis

mmc_add_card//把card device挂载到mmc_bus_type总线

sdio_add_func//把sdio device挂载到sdio_bus_type总线

6. Reference

  1. Linux kernel source code(version3.2.0)
  2. Linux kernel 最新patch
  3. AM335x ARM® Cortex™-A8 Microprocessors(MPUs) Technical Reference Manual
  4. SDIO Simplified Specification
  5. SD Host Controller Simplified Specification

- - 内核技术中文网 - 构建全国最权威的内核技术交流分享论坛

原文地址:浅析Linux内核之mmc子系统-sdio - 操作系统 - 内核技术中文网 - 构建全国最权威的内核技术交流分享论坛(版权归原作者所有,侵删)

浅析Linux内核之mmc子系统-sdio相关推荐

  1. pae扩展内存 linux,浅析linux内核内存管理之PAE

    浅析linux内核内存管理之PAE 早期Intel处理器从80386到Pentium使用32位物理地址,理论上,这样可以访问4GB的RAM.然而,大型服务器需要大于4GB的RAM来同时运行数以千计的进 ...

  2. linux内核分成如下五个子系统,linux内核主要由5个子系统 Linux内核由哪几个子系统组成?...

    1, Linux内核由哪几个子系统组成? Linux内核主要由五个子系统组成:进程调度,内存管理,虚拟文件系统,网络接口,进程间通信.1.进程调度(SCHED):控制进程对CPU的访问.当需要选择下一 ...

  3. linux内核定时器死机,浅析linux内核中timer定时器的生成和sofirq软中断调用流程

    浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(& ...

  4. ARM Linux内核Input输入子系统浅解

    --以触摸屏驱动为例 第一章.了解linux input子系统       Linux输入设备总类繁杂,常见的包括有按键.键盘.触摸屏.鼠标.摇杆等等,他们本身就是字符设备,而linux内核将这些设备 ...

  5. linux 内核配置mmc,Linux内核编译,配置本机驱动

    1.前言 编译Linux内核失败的原因很多时候就是驱动选错,适合自己本机的驱动没编译进去.面对特殊平台(或者有些洁癖者,我就是^_^),要编译精简内核,只要本机驱动,其他都不需要.面对内核里面这么多驱 ...

  6. linux内核分为子系统,Linux内核内存管理子系统分析【转】

    还是那张熟悉的老图:Linux内核子系统简介(由七个部分组成) Linux内存管理模型: 1. 内存管子系统职能: 1>  管理虚拟地址与物理地址的映射 2>  管理物理内存的分配 2. ...

  7. 浅析linux内核中的idr机制

           idr在linux内核中指的就是整数ID管理机制,从本质上来说,这就是一种将整数ID号和特定指针关联在一起的机制.这个机制最早是在2003年2月加入内核的,当时是作为POSIX定时器的一 ...

  8. linux内核的I2C子系统详解1——I2C总线概览、驱动框架概览

    以下内容源于朱有鹏<物联网大讲堂>课程的学习,如有侵权,请告知删除. 1.I2C总线汇总概览 (1)三根通信线:SCL.SDA.GND: (2)同步.串行.电平.低速(几百k).近距离: ...

  9. linux内核input子系统解析,ARM Linux内核Input输入子系统浅解

    struct list_head      node;      //该链表头用于将设备链接到input_dev_list }; Input_dev是一个很强大的结构体,它把所有的input设备(触摸 ...

最新文章

  1. CSS(二)属性--文本设置
  2. MFC 单文档的全局变量
  3. linux传输文件到linux速度慢,linux中往nand(jffs2)中拷贝文件时速度慢的问题
  4. 1337:【例3-2】单词查找树
  5. 深圳当代艺术家的一次聚会
  6. 排序算法之(7)——堆排序
  7. vscode 遇到的迷之bug nvm is not compatible with the npm config prefix
  8. python notebooks_Jupyter Notebooks安装及入门
  9. python true_True关键字,带Python示例
  10. 中移动飞信2010Beta1.0体验版
  11. U盘安装Ubuntu14.04 server版 提示无法挂载cd-rom数据的解决办法
  12. java怎么传入参数到方法当中_java中方法传入参数时:值传递还是址传递?
  13. UE4中Bebavior Tree中Delay及其后面代码失效的原因
  14. 投篮机投篮有技巧吗_卡梅伦·约翰逊:投篮高效,跑位积极,会是太阳队外线新答案吗?...
  15. 利用HTML和浏览器16倍速刷网课
  16. php语言grads画图,grads画图坐标设置.docx
  17. pycharm如何更换背景图片
  18. 2022年一级建造师《工程经济》模拟卷有解析
  19. python中if语句的实例_python的if语句里怎样写两个条件
  20. 经典排序算法之:堆排序

热门文章

  1. 豆瓣高分书!Python编程从入门到进阶都有!
  2. 常用波特率计数查找表
  3. 如何快速定位公式单元格区域
  4. matlab怎么设置视频帧数,matlab无法读取视屏
  5. 解决学习tensorflow的LSTM模型中遇到一个版本不兼容问题
  6. PHP得到汉字的拼音字母
  7. WinRAR备份技巧集合
  8. 51单片机c语言电子琴程序,51单片机超级电子琴(C语言)
  9. “gcc“ not found
  10. 数据结构课程设计之GUI小程序