Linux MMC驱动架构浅析
Posted HW.LI
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux MMC驱动架构浅析相关的知识,希望对你有一定的参考价值。
Linux MMC驱动架构浅析
MMC驱动模型
Linux内核设计了MMC子系统,用于管理MMC/SD等设备,MMC/SD存储设备是一种典型的块设备。MMC子系统的框架结构如下图所示。
块设备(MMC_BLOCK)
块设备的相关驱动,即实现块设备的驱动程序,负责驱动MMC core抽象出来的虚拟的card设备,并对接内核其它的framework(例如块设备、TTY、wifi)等实现具体的功能。如MMC/SD卡设备驱动按照 linux块设备驱动程序的框架实现一个MMC/SD卡的块设备驱动,在 block.c 当中我们可以看到写一个块设备驱动程序时需要的 block_device_operations 结构体变量的定义,其中有 open/release/request 函数的实现,而 queue.c 则是对内核提供的请求队列的封装。
核心层(CORE)
整个MMC的核心层,这部分是不同协议和规范的实现,为MMC控制器(host)层和块设备(card)驱动层提供接口函数。核心层封装了 MMC/SD 卡的命令(CMD),例如存储卡的识别、设置、读写、识别、设置等命令。core.c 文件是由 sd.c 、 mmc.c 两个文件支撑的, core.c 把 MMC 卡、 SD 卡的共性抽象出来,它们的差别由 sd.c 和 sd_ops.c 、 mmc.c 和 mmc_ops.c 来完成。
控制器层(host)
主机端MMC控制器的驱动,主机控制器则是依赖于平台。针对不同芯片,实现控制器对应的驱动代码。例如中断函数注册,控制器寄存器初始化等等。然后它会向 核心(core)层注册一个主机( host ),用结构 mmc_host_ops 描述,这样核心层就可以拿着这个 host 来操作对应的卡控制器,对核心(core)层是不透明的。
MMC核心(CORE)
MMC协议是一个总线协议,因此包括Host controller、Bus、Card三类实体。相应的,MMC framework抽象出了host、bus、card三个软件实体,以便和硬件一一对应。
Host:负责驱动Host controller,提供诸如访问card的寄存器、检测card的插拔、读写card等操作方法。从设备模型的角度看,host会检测卡的插入,并向bus注册MMC card设备。
Bus:是MMC bus的虚拟抽象,以标准设备模型的方式,收纳MMC card(device)以及对应的MMC driver(driver);
Card:抽象具体的MMC卡,由对应的MMC driver驱动(从这个角度看,可以忽略MMC的技术细节,只需关心一个个具有特定功能的卡设备,如存储卡、WIFI卡、GPS卡等等)。
块设备层(MMC_BLOCK)
块设备针对不同客户端的设备驱动程序。如SD卡、T-flash卡、SDIO接口的GPS和wi-fi等设备驱动。
驱动模形数据结构
mmc_host:表示一个MMC控制器
mmc_card:MMC设备描述
mmc_driver:MMC操作函数
MMC 模块总线模型
mmc子系统涉及到三条总线:
platform_bus_type:
host设备被封装成platform_device注册到Linux驱动模型中,Host驱动相应的driver和device挂载在Linux内核内置的虚拟抽象总线platform_bus_type。两者的匹配采用名称匹配的方式,即driver和device两者的name一样则认为该device对应该driver
mmc_bus_type:
Card驱动相应的driver和device挂载在mmc自己创建的虚拟总线mmc_bus_type下,直接匹配。
sdio_bus_type:
Sdio驱动相应的driver和device挂载在mmc自己创建的虚拟总线sdio_bus_type下,ID匹配。
瑞芯微MMC总线、驱动、设备框图
瑞芯微MMC设备源码流程分析
瑞芯微MMC驱动源码分析
概述
从上面总线设备驱动之间的关系图以及流程图可知,设备启动时,首先调用subsys_initcall向linux系统注册了mmc_bus和sdio_bus两条总线,用来管理块设备和sdio接口类型的设备。接着调用module_init向系统注册了一条mmc_rpmb_bus总线、一个mmc块设备和MMC driver。最后调用module_platform_driver,把mmc controler注册到platform总线,同时扫描一次挂载到mmc控制器上的设备。
这里有几个重要的对象,即mmc_host、mmc_card、mmc driver。针对mmc controller,该子系统抽象为mmc_host,用于描述一个mmc 控制器;针对mmc、sd、tf卡等,该子系统抽象为mmc_card,用于描述卡信息;针对通信总线,抽象出mmc_bus;针对mmc、sd、tf,mmc子系统完成了统一的mmc driver,针对mmc总线规范以及SD规范,其已经详细的定义了一个存储卡的通信方式、通信命令,因此LINUXmmc子系统定义了mmc driver,用于和mmc、sd、tf等卡的通信,而不需要驱动开发人员来开发卡驱动。
从上面的图中看到,在mmc总线模型中只注册了一个mmc driver,该驱动适配所有的mmc card。一个mmc host与一个mmc card绑定。Mmc card属于热插拔的设备,而mmc card的创建主要由mmc host负责探测与创建,mmc host根据卡在位检测引脚,当检测到mmc card的存在后,即创建mmc card,同时注册至mmc bus上,并完成与mmc driver的绑定操作。
因mmc_card一般均是存储设备,因此针对该设备的访问即需要借助LINUX内核的块设备模型,因此mmc子系统也必须要实现块设备驱动,借助该块设备驱动模型,将mmc card与vfs完成了关联,即可通过系统调用借助VFS模型实现对块设备的读写访问操作。
sdio总线驱动模型和mmc类型,结构体上的区别为其driver类型为sdio_driver,并增加了sdio_func结构体变量(该结构体包含了该sdio设备相关的厂商id、设备id,同时包含了mmc_card),与mmc总线驱动模型的区别为:因sdio主要突出接口概念,其设备端可以连接wifi、gps等设备,因此其外设备驱动需要由驱动工程师自己实现,sdio驱动模块不提供对应的驱动。
MMC bus的定义及其接口分析
MMC bus数据结构
mmc_dev_groups : 定义了dev_attrs属性,针对所有注册到mmc bus上的设备,均会创建mmc_dev_attrs中定义的属性文件
mmc_bus_match :match接口用于实现mmc card与mmc driver的匹配检测,(始终返回1)
mmc_bus_uevent :uevent接口主要用于添加该mmc bus的uevent参数(在调用device_add时,会调用kobject_uevent向应用层发送设备添加相关的事件(通过netlink 发送和 call_usermodehelper机制(向/proc/sys/kernel/hotplug中写入usermodehelper机制调用的应用层程序)),而kobject_uevent会调用该device所属bus和class的uevent接口,添加需要发送到应用的event参数);
mmc_bus_probe :probe接口主要用于mmc card与mmc driver匹配成功后,调用该mmc bus的probe接口实现探测操作
mmc_bus_pm_ops :pm是电源管理相关的接口
mmc_dev_attrs:该属性为所有注册至该总线上的设备所默认创建的,其定义如下,主要提供只读的设备属性,该属性用于提供mmc card的类型,在具体的mmc card目录下(sysfs),会创建type的文件,该文件只读,读取该文件可以获取mmc card的类型,目前支持的类型包括mmc、sd、sdio、sd combo。
mmc_bus_match:该接口主要用于mmc card、mmc driver的匹配检测, mmc bus的mmc driver由mmc 子系统实现,且针对mmc/sd/emmc等存储卡设备,其均与mmc子系统实现的mmc driver匹配,因此此处的mmc_bus_match并没有进行匹配检测,直接返回1,表示mmc 子系统实现的mmc driver可匹配所有注册至mmc bus上的mmc card。
mmc_bus_probe/mmc_bus_remove:这两个接口均直接调用mmc 子系统注册的mmc driver的probe/remove接口,实现简单。
mmc_bus_uevent:该接口主要是提供mmc bus添加的event env,该接口主要提供“MMC_TYPE”、“MMC_NAME”、“MODALIAS”这三个env。若应用层的udev、mdev需要关注mmc card的这三个env,则可以添加对应这三个env的规则即可。
因mmc driver仅有一个,因此针对mmc bus而言其match函数直接返回1,而不需要像spi/i2c总线的match接口那样对设备和驱动进行匹配检测;
因一个mmc控制器仅与一个mmc card关联,且mmc子系统提供了mmc rescan接口,可以实现对mmc host下的mmc card进行扫描并进行mmc card的创建、与mmc host的绑定以及注册至mmc总线上;因此不需要像上述2中所述的i2c/spi那样,将所有已注册的控制器设备链接在一起方便查找从而实现i2c/spi设备的注册与绑定;
因mmc/emmc协议规范已经规定了访问mmc card的命令格式以及相应的寄存器定义,所有mmc/sd/tf/emmc设备均需要遵守,因此mmc子系统针对访问mmc card的命令抽象出统一的接口,包括设备状态设置、卡使能去使能、sleep/awake、poweroff notify、cid/rca/dsr/csd寄存器读写、通过mmc switch命令实现与extend csd寄存器的读写等接口
续待......
Linux 下wifi 驱动开发—— SDIO接口WiFi驱动浅析
SDIO-Wifi模块是基于SDIO接口的符合wifi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈。可以实现用户主平台数据通过SDIO口到无线网络之间的转换。SDIO具有数据传输快,兼容SD、MMC接口等特点。
对于SDIO接口的wifi,首先,它是一个sdio的卡的设备。然后具备了wifi的功能。所以。注冊的时候还是先以sdio的卡的设备去注冊的。
然后检測到卡之后就要驱动他的wifi功能了。显然,他是用sdio的协议,通过发命令和数据来控制的。以下先简单回想一下SDIO的相关知识:
一、SDIO相关基础知识解析
1、SDIO接口
SDIO 故名思义,就是 SD 的 I/O 接口(interface)的意思,只是这样解释可能还有点抽像。更详细的说明。SD 本来是记忆卡的标准,可是如今也能够把 SD 拿来插上一些外围接口使用,这种技术便是 SDIO。
所以 SDIO 本身是一种相当单纯的技术。透过 SD 的 I/O 接脚来连接外部外围。并且透过 SD 上的 I/O 数据接位与这些外围数据传输,并且 SD 协会会员也推出非常完整的 SDIO stack 驱动程序,使得 SDIO 外围(我们称为SDIO 卡)的开发与应用变得相当热门。
如今已经有很多的手机或是手持装置都支持 SDIO 的功能(SD 标准原本就是针对 mobile device 而制定)。并且很多 SDIO 外围也都被开发出来。让手机外接外围更加easy,并且开发上更有弹性(不须要内建外围)。
眼下常见的 SDIO 外围(SDIO 卡)有:
· Wi-Fi card(无线网络卡)
· CMOS sensor card(照相模块)
· GPS card
· GSM/GPRS modem card
· Bluetooth card
SDIO 的应用将是未来嵌入式系统最重要的接口技术之中的一个。而且也会代替眼下 GPIO 式的 SPI 接口。
2、SDIO总线
SDIO总线 和 USB总线 类似,SDIO也有两端。当中一端是HOST端,还有一端是device端。全部的通信都是由HOST端 发送 命令 開始的。Device端仅仅要能解析命令,就能够相互通信。
CLK信号:HOST给DEVICE的 时钟信号,每一个时钟周期传输一个命令。
CMD信号:双向 的信号。用于传送 命令 和 反应。
DAT0-DAT3 信号:四条用于传送的数据线。
VDD信号:电源信号。
VSS1,VSS2:电源地信号。
3、SDIO热插拔原理
方法:设置一个 定时器检查 或 插拔中断检測
硬件:假如GPG10(EINT18)用于SD卡检測
GPG10 为高电平 即没有插入SD卡
GPG10为低电平 即插入了SD卡
4、SDIO命令
SDIO总线上都是HOST端发起请求。然后DEVICE端回应请求。
sdio命令由6个字节组成。
a -- Command:用于開始传输的命令,是由HOST端发往DEVICE端的。当中命令是通过CMD信号线传送的。
b -- Response:回应是DEVICE返回的HOST的命令,作为Command的回应。
也是通过CMD线传送的。
c -- Data:数据是双向的传送的。能够设置为1线模式,也能够设置为4线模式。数据是通过DAT0-DAT3信号线传输的。
SDIO的每次操作都是由HOST在CMD线上发起一个CMD。对于有的CMD,DEVICE须要返回Response,有的则不须要。
对于读命令,首先HOST会向DEVICE发送命令,紧接着DEVICE会返回一个握手信号,此时,当HOST收到回应的握手信号后,会将数据放在4位的数据线上。在传送数据的同一时候会尾随着CRC校验码。
当整个读传送完成后,HOST会再次发送一个命令,通知DEVICE操作完成,DEVICE同一时候会返回一个响应。
对于写命令,首先HOST会向DEVICE发送命令,紧接着DEVICE会返回一个握手信号,此时。当HOST收到回应的握手信号后,会将数据放在4位的数据线上,在传送数据的同一时候会尾随着CRC校验码。当整个写传送完成后,HOST会再次发送一个命令。通知DEVICE操作完成,DEVICE同一时候会返回一个响应。
二、SDIO接口驱动
前面讲到,SDIO接口的wifi,首先。它是一个sdio的卡的设备。然后具备了wifi的功能,所以SDIO接口的WiFi驱动就是在wifi驱动外面套上了一个SDIO驱动的外壳,SDIO驱动仍然符合设备驱动的分层与分离思想:
设备驱动层(wifi 设备)
|
核心层(向上向下提供接口)
|
主机驱动层 (实现SDIO驱动)
以下先分析SDIO接口驱动的实现。看几个重要的数据结构(用于核心层与主机驱动层 的数据交换处理)。
[ /include/linux/mmc/host.h ]
struct mmc_host 用来描写叙述卡控制器
struct mmc_card 用来描写叙述卡
struct mmc_driver 用来描写叙述 mmc 卡驱动
struct sdio_func 用来描写叙述 功能设备
struct mmc_host_ops 用来描写叙述卡控制器操作接口函数功能,用于从 主机控制器层向 core 层注冊操作函数,从而将core 层与详细的主机控制器隔离。也就是说 core 要操作主机控制器。就用这个 ops 其中给的函数指针操作,不能直接调用详细主控制器的函数。
HOST层驱动分析在 前面的系列文章中 Linux SD卡驱动开发(二) —— SD 卡驱动分析HOST篇 有具体阐述。以下仅仅简单回想一下一些重要函数处理
1、编写Host层驱动
这里參考的是S3C24XX的HOST驱动程序 /drivers/mmc/host/s3cmci.c
static struct platform_driver s3cmci_driver = { .driver = { .name = "s3c-sdi", //名称和平台设备定义中的相应 .owner = THIS_MODULE, .pm = s3cmci_pm_ops, }, .id_table = s3cmci_driver_ids, .probe = s3cmci_probe, //平台设备探測接口函数 .remove = __devexit_p(s3cmci_remove), .shutdown = s3cmci_shutdown, }; s3cmci_probe(struct platform_device *pdev) { //.... struct mmc_host *mmc; mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); //分配mmc_host结构体 //..... } /*注冊中断处理函数s3cmci_irq,来处理数据收发过程引起的各种中断*/ request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host) //注冊中断处理函数s3cmci_irq /*注冊中断处理s3cmci_irq_cd函数,来处理热拨插引起的中断。中断触发的形式为上升沿、下降沿触发*/ request_irq(host->irq_cd, s3cmci_irq_cd,IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING, DRIVER_NAME, host) mmc_add_host(mmc); //initialise host hardware //向MMC core注冊host驱动 ----> device_add(&host->class_dev); //加入设备到mmc_bus_type总线上的设备链表中 ----> mmc_start_host(host); //启动mmc host /*MMC drivers should call this when they detect a card has been inserted or removed.检測sd卡是否插上或移除*/ ---->mmc_detect_change(host, 0); /*Schedule delayed work in the MMC work queue.调度延时工作队列*/ mmc_schedule_delayed_work(&host->detect, delay);
搜索host->detected得到下面信息:
[/drivers/mmc/core/host.c]
NIT_DELAYED_WORK(&host->detect, mmc_rescan); mmc_rescan(struct work_struct *work) ---->mmc_bus_put(host);//card 从bus上移除时,释放它占有的总线空间 /*推断当前mmc host控制器是否被占用,当前mmc控制器假设被占用,那么 host->claimed = 1;否则为0 *假设为1,那么会在while(1)循环中调用schedule切换出自己,当占用mmc控制器的操作完毕之后,运行 *mmc_release_host()的时候,会激活登记到等待队列&host->wq中的其它 程序获得mmc主控制器的使用权 */ mmc_claim_host(host); mmc_rescan_try_freq(host, max(freqs[i], host->f_min); static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) { … /* Order's important: probe SDIO, then SD, then MMC */ if (!mmc_attach_sdio(host)) return 0; if (!mmc_attach_sd(host)) return 0; if (!mmc_attach_mmc(host)) return 0; …. } mmc_attach_sdio(struct mmc_host *host) //匹配sdio接口卡 --->mmc_attach_bus(host, &mmc_sdio_ops); /*当card与总线上的驱动匹配,就初始化card*/ mmc_sdio_init_card(host, host->ocr, NULL, 0); --->card = mmc_alloc_card(host, NULL);//分配一个card结构体 mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); //设置mmc_bus的工作模式 struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; //SDIO functions (devices) sdio_init_func(host->card, i + 1); --->func = sdio_alloc_func(card); //分配struct sdio_fun(sdio功能设备)结构体 mmc_io_rw_direct(); card->sdio_func[fn - 1] = func; mmc_add_card(host->card); //将详细的sdio设备挂载到mmc_bus_types 总线 sdio_add_func(host->card->sdio_func[i]); //将sdio功能设备挂载到sdio_bus_types总线
这里一系列函数调用在前面的SD驱动蚊帐中已经阐述过了,不再具体阐述
2、SDIO设备的热插拔
当插拔SDIO设备,会触发中断通知到CPU,然后运行卡检測中断处理函数在这个中断服务函数中。mmc_detect_change->mmc_schedule_delayed_work(&host->detect,delay), INIT_DELAYED_WORK(&host->detect, mmc_rescan)会调度mmc_rescan函数延时调度工作队列,这样也会触发SDIO设备的初始化流程,检測到有效的SDIO设备后,会将它注冊到系统中去。
static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id) { struct s3cmci_host *host = (struct s3cmci_host *)dev_id; ........ mmc_detect_change(host->mmc, msecs_to_jiffies(500)); return IRQ_HANDLED; }
三、wifi 驱动部分解析
wifi驱动的通用的软件架构
1. 分为两部分,上面为主机端驱动。以下是我们之前所说的firmware
2. 当中固件部分的主要工作是:由于天线接受和发送回来的都是802.11帧的帧,而主机接受和传送出来的数据都必须是802.3的帧,所以必须由firmware来负责802.3的帧和802.11帧之间的转换
3. 当天线收到数据。并被firmware处理好后会放在一个buffer里。并产生一个中断。主机在收到中断后就去读这个buffer。
SDIO设备的驱动由sdio_driver结构体定义,sdio_driver事实上是driver的封装。通过sdio_register_driver函数将SDIO设备驱动载入进内核,事实上就是挂载到sdio_bus_type总线上去。
1、设备驱动的注冊与匹配
[Drivers/net/wireless/libertas/if_sdio.c]
/* SDIO function device driver*/ struct sdio_driver { char *name; //设备名 const struct sdio_device_id *id_table; //设备驱动ID int (*probe)(struct sdio_func *, const struct sdio_device_id *);//匹配函数 void (*remove)(struct sdio_func *); struct device_driver drv; };
以下是详细函数的填充:
/*if_sdio.c*/ static struct sdio_driver if_sdio_driver = { .name = "libertas_sdio", .id_table = if_sdio_ids, //用于设备与驱动的匹配 .probe = if_sdio_probe, .remove = if_sdio_remove, .drv = { .pm = &if_sdio_pm_ops, } };
设备注冊函数
/** * sdio_register_driver - register a function driver * @drv: SDIO function driver */ int sdio_register_driver(struct sdio_driver *drv) { drv->drv.name = drv->name; drv->drv.bus = &sdio_bus_type; //设置driver的bus为sdio_bus_type return driver_register(&drv->drv); }
总线函数
static struct bus_type sdio_bus_type = { .name = "sdio", .dev_attrs = sdio_dev_attrs, .match = sdio_bus_match, .uevent = sdio_bus_uevent, .probe = sdio_bus_probe, .remove = sdio_bus_remove, .pm = SDIO_PM_OPS_PTR, };
注意:设备或者驱动注冊到系统中的过程中,都会调用对应bus上的匹配函数来进行匹配合适的驱动或者设备。对于sdio设备的匹配是由sdio_bus_match和sdio_bus_probe函数来完毕。
static int sdio_bus_match(struct device *dev, struct device_driver *drv) { struct sdio_func *func = dev_to_sdio_func(dev); struct sdio_driver *sdrv = to_sdio_driver(drv); if (sdio_match_device(func, sdrv)) return 1; return 0; } static const struct sdio_device_id *sdio_match_device(struct sdio_func *func, struct sdio_driver *sdrv) { const struct sdio_device_id *ids; ids = sdrv->id_table; if (sdio_match_one(func, ids)) return ids; }
由以上匹配过程来看,通过匹配id_table 和 sdio_driver设备驱动中id。来匹配合适的驱动或设备。终于会调用.probe函数。来完毕相关操作。
2、If_sdio_probe函数
当检測到sdio卡插入了之后就会调用If_sdio_probe。而当卡被移除后就会调用If_sdio_remove。
以下先看下If_sdio_probet函数,if_sdio_prob 函数 主要做了两件事
static struct sdio_driver if_sdio_driver = { .name = "libertas_sdio", .id_table = if_sdio_ids, //用于设备和驱动的匹配 .probe = if_sdio_probe, .remove = if_sdio_remove, .drv = { .pm = &if_sdio_pm_ops, }, }; 1 //定义一个 if_sdio card的结构体 struct if_sdio_card *card; struct if_sdio_packet *packet; //sdio 包的结构体 struct mmc_host *host = func->card->host; // 查询是否有指定的功能寄存器在mmc //_sdio_card中 for (i = 0;i < func->card->num_info;i++) { if (sscanf(func->card->info[i], "802.11 SDIO ID: %x", &model) == 1) //在这里进行片选 选择到我们使用的marvell 8686 的设备 case MODEL_8686: card->scratch_reg = IF_SDIO_SCRATCH; //创建sdio 的工作队列 card->workqueue = create_workqueue("libertas_sdio"); //调用以下的函数 INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker); //主机到卡的工作队列 static void if_sdio_host_to_card_worker(struct work_struct *work) /* Check if we support this card 选择我们所支持的卡的类型*/ //赋值为sd8686_helper.bin sd8686.bin /*fw_table 中的 MODEL_8686, "sd8686_helper.bin", "sd8686.bin" },?/ for (i = 0; i < ARRAY_SIZE(fw_table); i++) { if (card->model == fw_table[i].model) break; } { MODEL_8688, "libertas/sd8688_helper.bin", "libertas/sd8688.bin" }, //申请一个host sdio_claim_host(func); //使能sdio 的功能 寄存器 ret = sdio_enable_func(func); if (ret) goto release; 2//申请 sdio 的中断 当有数据 ,命令 或者是事件 的时间运行中断 ret = sdio_claim_irq(func, if_sdio_interrupt); ret = if_sdio_card_to_host(card); //从无线网卡接收到数据 或者说是上报数据 ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4); //接收数据的处理 ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4); //处理申请的命令中断 ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4);//处理申请的事件中断 //加入网络结构体 分配设备并注冊 priv = lbs_add_card(card, &func->dev); //分配Ethernet设备并注冊 wdev = lbs_cfg_alloc(dmdev); //802无线网的详细的操作函数 wdev->wiphy = wiphy_new(&lbs_cfg80211_ops, sizeof(struct lbs_private)); //分配网络设备是整个网络部分操作的 //的核心结构体 dev = alloc_netdev(0, "wlan%d", ether_setup); //实例化wlan0的属性 dev->ieee80211_ptr = wdev; dev->ml_priv = priv; //设置设备的物理地址 SET_NETDEV_DEV(dev, dmdev); wdev->netdev = dev; priv->dev = dev; //初始化网络设备 ops. 看门狗 dev->netdev_ops = &lbs_netdev_ops; //网络设备的详细的操作函数 dev->watchdog_timeo = 5 * HZ; dev->ethtool_ops = &lbs_ethtool_ops; dev->flags |= IFF_BROADCAST | IFF_MULTICAST; //广播或者多播 //启动一个内核线程来管理这个网络设备的数据发送,事件的处理(卡的拔出)和一些命令的处理 priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main"); //初始化相关的工作队列 priv->work_thread = create_singlethread_workqueue("lbs_worker"); INIT_WORK(&priv->mcast_work, lbs_set_mcast_worker); priv->wol_criteria = EHS_REMOVE_WAKEUP; priv->wol_gpio = 0xff; priv->wol_gap = 20; priv->ehs_remove_supported = true; //设置私有变量 //设置主机发送数据到卡 priv->hw_host_to_card = if_sdio_host_to_card; priv->enter_deep_sleep = if_sdio_enter_deep_sleep; priv->exit_deep_sleep = if_sdio_exit_deep_sleep; priv->reset_deep_sleep_wakeup = if_sdio_reset_deep_sleep_wakeup; sdio_claim_host(func); //启动卡设备 ret = lbs_start_card(priv); if (lbs_cfg_register(priv)) ret = register_netdev(priv->dev); err = register_netdevice(dev); //详细的wifi设备驱动功能 //网络设备操作的详细函数 static const struct net_device_ops lbs_netdev_ops = { .ndo_open = lbs_dev_open, //打开 .ndo_stop = lbs_eth_stop, //停止 .ndo_start_xmit = lbs_hard_start_xmit, //開始发送数据 .ndo_set_mac_address = lbs_set_mac_address, //设置mac地址 .ndo_tx_timeout = lbs_tx_timeout, //发送超时 .ndo_set_multicast_list = lbs_set_multicast_list, //多播地址 .ndo_change_mtu = eth_change_mtu, //最大传输单元 .ndo_validate_addr = eth_validate_addr, //推断地址的有效性
3、数据的接收,通过中断的方式来解决
网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数推断中断的类型,假设为接收中断。则读取接收到的数据。分配sk_buff数据结构和数据缓冲区。并将接收的数据拷贝到数据缓存区。并调用netif_rx()函数将sk_buff传递给上层协议。
搜索if_sdio_interrupt,可知道它是在if_sdio.c文件里if_sdio_probe()函数中sdio_claim_irq(func, if_sdio_interrupt) ,func->irq_handler = if_sdio_interrupt。当s3cmci_irq中断处理函数的S3C2410_SDIIMSK_SDIOIRQ 中断被触发时将调用if_sdio_interrupt()函数,进行接收数据。
static void if_sdio_interrupt(struct sdio_func *func) ret = if_sdio_card_to_host(card); //从无线网卡接收到数据 或者说是上报数据 //读取端口上的数据 ,放到card的buffer中 ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk); 1.在这里一方面处理中断 还有2 switch (type) { //处理cmd data event的请求 case MVMS_CMD: ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4); //处理申请的命令中断 if (ret) goto out; break; case MVMS_DAT: ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4);//处理申请的数据中断 if (ret) goto out; break; case MVMS_EVENT: ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4);//处理申请的事件中断 //读取包的过程 lbs_process_rxed_packet(card->priv, skb); //假设是中断 ,就把skb这个包提交给协议层,这个函数是 //协议层提供的 netif_rx(skb) if (in_interrupt()) netif_rx(skb); //提交给协议层 2//读取端口上的数据 ,放到card的buffer中 ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk); //读取地址,目的地址,数量 等 int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr, int count) return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count); ret = mmc_io_rw_extended(func->card, write,func->num, addr, incr_addr, buf,blocks, func->cur_blksize); cmd.arg = write ? 0x80000000 : 0x00000000; //wait for request mmc_wait_for_req(card->host, &mrq); 開始应答 mmc_start_request(host, mrq); wait_for_completion(&complete); host->ops->request(host, mrq);
4、数据发送
//IP层通过dev_queue_xmit()将数据交给网络设备协议接口层,网络接口层通过netdevice中的注冊函数的数据发送函数 int dev_queue_xmit(struct sk_buff *skb) if (!netif_tx_queue_stopped(txq)) { __this_cpu_inc(xmit_recursion); //设备硬件開始发送 rc = dev_hard_start_xmit(skb, dev, txq); //调用wifi网络中的ops rc = ops->ndo_start_xmit(skb, dev); dev->netdev_ops = &lbs_netdev_ops; //设备的操作函数 //处理sdio firware数据和内核的数据main_thread 主线程 priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main"); //调用host_to_card 即if_sdio_card_to_host函数。 int ret = priv->hw_host_to_card(priv, MVMS_DAT,priv->tx_pending_buf,priv->tx_pending_len); 为什么是if_sdio_to_host呢 ?由于在prob函数中定义了这一个 //设置主机发送数据到卡 priv->hw_host_to_card = if_sdio_host_to_card; static int if_sdio_host_to_card(struct lbs_private *priv,u8 type, u8 *buf, u16 nb) //把buf中的数据 copy到sdio 包中,在对sdio 的包进行处理 memcpy(packet->buffer + 4, buf, nb); //创建工作队列 queue_work(card->workqueue, &card->packet_worker); //初始化队列 INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker); //sdio的写数据 ret = sdio_writesb(card->func, card->ioport, packet->buffer, packet->nb); //mmc写扩展口 ret = mmc_io_rw_extended(func->card, write,func->num, addr, incr_addr, buf,blocks, func->cur_blksize); //wait for request mmc_wait_for_req(card->host, &mrq); mrq->done_data = &complete; mrq->done = mmc_wait_done; mmc_start_request(host, mrq); //完毕等待 写数据结束 wait_for_completion(&complete); host->ops->request(host, mrq); //究竟结束 发送数据
5、移除函数
当sdio卡拔除时。驱动会调用该函数。完毕对应操作。
如释放占有的资源。禁止func功能函数。释放host。
if_sdio_remove(struct sdio_func *func) ---->lbs_stop_card(card->priv); lbs_remove_card(card->priv); ---->kthread_stop(priv->main_thread); //终止内核线程 lbs_free_adapter(priv); lbs_cfg_free(priv); free_netdev(dev); flush_workqueue(card->workqueue); //刷新工作队列 destroy_workqueue(card->workqueue); sdio_claim_host(func); sdio_release_irq(func); sdio_disable_func(func); sdio_release_host(func);
以上是关于Linux MMC驱动架构浅析的主要内容,如果未能解决你的问题,请参考以下文章