Linux下SDIO驱动架构分析

持续更新中…

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


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值