现在的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代码更加清晰,具体的可以参考http://blog.csdn.net/21cnbao/article/details/8457546

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寄存器6CCCR)的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);//设置hostbus操作函数是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,获取slaverca

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的更多相关文章

  1. Linux内核内存管理子系统分析【转】

    本文转载自:http://blog.csdn.net/coding__madman/article/details/51298718 版权声明:本文为博主原创文章,未经博主允许不得转载. 还是那张熟悉 ...

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

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

  3. linux内核中有哪些子系统(框架)呢?

    注意: 分析用的linux内核版本为5.1.3 1. RTC子系统 2. Remote Processor子系统 3. Remote Processor Message子系统 4. SCSI子系统 5 ...

  4. Linux内核简介、子系统及分类

    一.内核简介 内核:在计算机科学中是一个用来管理软件发出的数据I/O(输入与输出)要求的计算机程序,将这些要求转译为数据处理的指令并交由中央处理器(CPU)及计算机中其他电子组件进行处理,是现代操作系 ...

  5. Linux内核笔记--网络子系统初探

    内核版本:linux-2.6.11 本文对Linux网络子系统的收发包的流程进行一个大致梳理,以流水账的形式记录从应用层write一个socket开始到这些数据被应用层read出来的这个过程中linu ...

  6. 基于tiny4412的Linux内核移植 -- PWM子系统学习(八)

    作者信息 作者: 彭东林 邮箱:pengdonglin137@163.com QQ:405728433 平台简介 开发板:tiny4412ADK + S700 + 4GB Flash 要移植的内核版本 ...

  7. 基于tiny4412的Linux内核移植 -- PWM子系统学习(七)

    作者信息 作者: 彭东林 邮箱:pengdonglin137@163.com QQ:405728433 平台简介 开发板:tiny4412ADK + S700 + 4GB Flash 要移植的内核版本 ...

  8. 如何获取linux内核的某个子系统的维护者邮箱?

    答: 如获取pwm子系统的维护者邮箱: ./scripts/get_maintainer.pl drivers/pwm

  9. LXC linux容器简介——在操作系统层次上为进程提供的虚拟的执行环境,限制其使用的CPU和mem等资源,底层是linux内核资源管理的cgroups子系统

    1.LXC是什么? LXC是Linux containers的简称,是一种基于容器的操作系统层级的虚拟化技术. 2.LXC可以做什么? LXC可以在操作系统层次上为进程提供的虚拟的执行环境,一个虚拟的 ...

随机推荐

  1. js如何获取一个月的天数 data javascript

    js如何获取一个月的天数 function days(year,month){ var dayCount; now = new Date(year,month, 0); dayCount = now. ...

  2. C# 数据结构 基础 论述

    问题: 信息世界中,计算机是加工处理的信息的载体,在这个过程中面临着三个问题: 1.如何方便高效的组织数据 2.如何在计算机中存储数据(内存和外存) 3.如何对存储的数据进行高效的操作 目的: 我们都 ...

  3. 利用java实现一个简单的远程监控程序

    一般的远程监控软件都是用c或者c++等语言开发的,而使用java如何来实现相同的功能呢. 首先我们先介绍一下一个简单的远程监控程序的实现原理. 功能一,远程屏幕监视 (1) 必须要有监控端与被监控端, ...

  4. PHPStorm——配置修改

    字体修改: FiraCode字体:https://github.com/tonsky/FiraCode 1.双击安装字体 2. 关闭错别字检测

  5. IndexedDB demo showcase

    var dbGlobals = new Object(); dbGlobals.db = null; dbGlobals.description = "This database is us ...

  6. codeforces C. No to Palindromes!

    http://codeforces.com/contest/465/problem/C 题意:给你一个字符串,然后按照字典序找出下一个字符串,这个字符串中不能含有长度大于等于2的子串为回文串,如果含有 ...

  7. Java语言基础(五) Java原始数据类型的分类以及数据范围

    Java原始数据类型的分类以及数据范围 1.基本数据类型分为:整型(byte, short, int, long),浮点型(float, double),字符型(char),布尔型(boolean) ...

  8. Haskell函数的语法

    本章讲的就是 Haskell 那套独特的语法结构,先从模式匹配开始.模式匹配通过检查数据的特定结构来检查其是否匹配,并按模式从中取得数据. 在定义函数时,你可以为不同的模式分别定义函数本身,这就让代码 ...

  9. Ruby安装和简介

    Ruby下载地址:https://www.ruby-lang.org/zh_cn/downloads/ 我安装的是RubyInstaller.it is a self-contained Window ...

  10. [LeetCode#252] Meeting Rooms

    Problem: Given an array of meeting time intervals consisting of start and end times [[s1,e1],[s2,e2] ...