持续更新中…
Linux下SDIO驱动架构分析
最近在写一个SDIO的driver,参考了Linux下的SDIO driver这边做一个总结:
首先是源码存放的位置,Linux中和驱动相关的代码放在/driver
中,其中SDIO 相关的code 放在 /driver/mmc中
注:这边需要提一下mmc是一种存储卡协议,EMMC 使用的就是mmc 协议,而sd 协议则是另一种协议(两者类似),由于历史原因Linux 将两者归结到一起,放在mmc 文件夹下。
其中,card用于构建一个块设备作为上层与mmc子系统沟通的桥梁;core抽象了mmc,sd,sdio三者的通用操作;host则是各类平台上的host驱动代码 。
本文主要是针对 x86 平台进行讲解,在host中则是 sdhci.c、sdhci.h、sdhci-pci.c 以及sdhci-pci.h文件
一、host 注册过程:
1) SDIO 设备 在 PCI 下的注册
首先SDIO driver , 作为一个PCI 设备是需要连接在PCI 总线上,因此我们需要先注册PCI 驱动程序。
为了正确的注册到内核,所有的PCI驱动都需要创建一个结构体:
struct pci_driver
在sdhci-pci-core.c 中定义如下:
为了把struct pci_driver (sdhci_driver) 注册到PCI 核心中, 需要调用以struct pci_driver 指针为参数的 pci_register_driver函数,通常在PCI驱动程序的模块初始化代码中完成该工作:
static init __init pci_skel_init(void)
{
return pci_register_driver(&pci_driver);
}
pci_register_driver 会调用 __pci_register_driver
driver_register()函数,driver_register首先会检查struct kset drivers链表中有没有对应名称的dirver: driver_find(drv->name, drv->bus); 如果已经有了,则重新把对应的driver加载到struct kset drivers中,如果没有,则会执行bus_add_drivers函数,把当前的驱动加载到struct kset drivers中,这边感兴趣的童鞋可以自行查看Linux 内核代码。
到此 PCI 驱动的注册就算完成了。
最后,在PCI 驱动程序的探测函数中,在驱动程序可以访问PCI 设备的任何设备资源之前(I/O区域或者中断),驱动程序必须调用pci_enable_device函数:
该函数实际地激活设备,它把设备唤醒,在某些情况下还指派它的中断线和 I/O区域。
熟悉Linux 内核的人都知道,上面进行的PCI 驱动注册,只是对PCI进行了注册,我们实际的device 还是需要在内核中进行注册,这两个注册不要混淆。
在对sdhci_driver进行注册的过程中,系统会根据sdhci_driver->driver.name
成员变量在pci_bus 总线上寻找同名字的pci_dvice(这个过程称之为“探测”),探测成功后,就会调用sdhci_driver.probe函数 -> sdhci_pci_probe
,这个函数至关重要,在整个驱动注册过程中起着核心作用,接下来我们就来讲解这个函数。
2)sdhci_pci_probe 函数详解
下面我们先简要的概括一下这个函数的功能:
1、 sdhci_pci_probe 第一件事是访问PCI 的地址空间, 获取slot 和 first_bar, 之后调用pcim_enable_device
来激活PCI 设备,这一点很重要。
2、之后会调用sdhci_alloc_host,来构造出一个host,它是一个struct sdhci_host类型的结构体。同时,也通过mmc_alloc_host函数构造了一个struct mmc_host的结构体变量mmc。
3、初始化host的时钟,设置host的gpio等等其他一些参数初始化(这边感兴趣的童鞋可以自己看看)。
4、通过sdhci_add_host
函数来注册host。
下面将详细讲解:
1、访问PCI 的地址空间
linux 提供了访问配置空间的标准接口:
函数原型定义在Linux/pci.h
中:
int pci_read_config_byte (struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word (struct pci_dev *dev, int where, u16 *val);
int pci_read_config_dword (struct pci_dev *dev, int where, u32 *val);
2、调用sdhci_alloc_host 来构造出一个host,它是一个struct sdhci_host类型的结构体。同时,也通过mmc_alloc_host函数构造了一个struct mmc_host的结构体变量mmc。
最后,着重讲一下通过sdhci_add_host
函数来注册host
首先sdhci_add_host 会先获取SDIO 的capacity。但是该函数主要是对mmc的注册,同样mmc也有很多的参数,先来看看他的操作函数集mmc->ops = &sdhci_ops。
之后sdhci_add_host会注册中断函数 sdhci_irq。
最后sdhci_add_host会调用mmc_add_host这个函数,它的功能就是通过device_add函数将设备注册进linux设备模型,最终的结果就是在sys/bus/pci/devices目录下能见到设备节点。
认卡流程
插卡后,产生插卡中断进入中断函数sdhci_irq
, linux中中断都会分为顶半部和底半部,
顶半部在中断函数中处理(速度要快),判断中断类型,设置一些Flag,清除中断类型。
之后通过tasklet_schedule(&host->card_tasklet)
, 调用底半部。
底半部:sdhci_tasklet_card
中对card 的cmd 和 data 进行 reset, 并通过mmc_detect_change
中的mmc_schedule_delayed_work(&host->detect, delay);
调用 mmc_rescan
扫卡流程:
mmc_rescan
:是整个扫卡过程的入口,这个函数完成扫卡前的配置工作(如扫卡频率:默认是400K,如果失败就会选择更低的频率进行扫描),而具体的扫卡工作交给了 mmc_rescan_try_freq
:
在mmc_rescan_try_freq()中先发送复位命令(通过CMD52 写I/O card reset, 不过该命令只有SDIO类型的卡才能够识别),然后发送CMD0,让设备进入IDLE模式,紧接着发送CMD8,获取该卡所支持的电压值,最后check 是SDIO、MMC 还是SD