蓝牙驱动分析

这个驱动分析的是OK6410开发板自带的内核版本是linux3.0.1,所支持的wifi和蓝牙一体芯片是marvell的8688和8787.根据开发板的设计,芯片与主机之间是通过sdio协议接口通信的,所以驱动也是通过sdio的方式写的。

个人分析驱动的过程是从插入设备驱动的动作开始的。

首先每次插入设备和拔出设备驱动都会通过终端打印相应的信息,判断在sd卡槽中肯定是触发中断的,通过看硬件原理图和数据手册中的SDMMC控制器可知用于mmc的中断号分别为56和57,回到代码中。在内核启动过程中会调用到smdk6410_machine_init()函数来初始化smdk6410这个机器平台,在这个函数中会通过调用s3c_sdhci0_set_platdata()函数来设置sdhci0这个设备的的平台数据,最后会依次注册smdk6410_devices全局变量中的每个设备其中就包括s3c_device_hsmmc0这个平台设备,设备是注册好了可是驱动有时什么时候注册的呢?通过设备名在代码中查找驱动位于驱动目录下的mmc/host目录下的sdhci-s3c.c文件中。通过该驱动的module_init可知由系统自动运行的是sdhci_s3c_init()函数而在该函数中就是一个sdhci_s3c_driver平台驱动的注册。由平台设备驱动注册的原理知道驱动注册的时候会通过驱动名去匹配挂在平台总线上的设备,而之前我们已经将设备注册过了,所以会匹配到同名的设备,匹配到之后就会调用设备驱动的probe函数,接下来我们来分析sdhci_s3c_driver的probe函数。

在函数的刚开始是通过platform_get_irq()和platform_get_resource()两个函数来获取在设备中分配的中断资源和内存资源,接着就是创建分配内存给struct sdhci_s3c *sc;这个指针,说到这个结构体的内存分配就牵扯到到linux中一个经典的嵌套结构体如何分配到连续内存空间的方法,在这里简单介绍下想分配struct sdhci_s3c结构体就牵扯到struct sdhci_host和struct mmc_host 结构体,因为他们是用指针嵌套的关系通过查看结构体的成员我们可以看到结构体的最后一个变量是unsigned long private[0] ____cacheline_aligned正是由于这个长度为0的long型数组在内存分配时记录下了上层结构体的地址。有点跑题,回到驱动上来,接着分析probe函数接着就是对分配结构体里面的变量依次初始化了,初始化结束后通过sdhci_add_host()函数将主控制器的结构体添加到sdhci中,接着分析sdhci_add_host()函数。这个函数很长主要是对struct mmc_host结构体的初始化,接着我们可以看到如下的代码:

tasklet_init(&host->card_tasklet,

sdhci_tasklet_card, (unsigned long)host);

tasklet_init(&host->finish_tasklet,

sdhci_tasklet_finish, (unsigned long)host);

初始化了两个中断的底半部处理函数,接着代码通过request_irq()就进行了中断的申请和注册中断函数sdhci_irq(),到此我们知道每次当我们插入设备的时候就会触发这个中断并运行中断处理函数,在中断处理函数中我们看到tasklet_schedule(&host->card_tasklet);这个语句将任务交给底半部sdhci_tasklet_card函数去处理,在底半部中最后通过mmc_detect_change(host->mmc, msecs_to_jiffies(200));函数延时200毫秒后调用host->mmc->detect()函数即,可是detect函数指针的实体是哪个函数呢?我们想应该是在申请初始化mmc结构体的时候初始化的查看代码是在mmc_alloc_host函数中通过

INIT_DELAYED_WORK(&host->detect, mmc_rescan);

语句初始化延时工作的。由此可知中断底半部实际上调用的是mmc_rescan()函数接着我们分析这个函数,在这个函数中首先通过传入的指针经结构体的转换获取struct mmc_host的指针,接着会判断bus_ops指针是否为空,由于之前并没有对bus_ops赋值,所以程序会继续运行到mmc_rescan_try_freq()函数,在mmc_rescan_try_freq函数中首先通过mmc控制器给mmc设备上电,上电之后发送SD_SEND_IF_COND 几CMD8命令给sd看是否有回应来判断是sd 2.0的卡还是1.0的卡,接着我们可以看到如下几行代码

if (!mmc_attach_sdio(host))

return 0;

if (!mmc_attach_sd(host))

return 0;

if (!mmc_attach_mmc(host))

return 0;

从函数名的字面意思我们可以看出是依次来匹配插入的设备是sdio存储卡还是sd卡还是spi的设备。我们的蓝牙wifi模块使用的是sdio的接口因此在mmc_attach_sdio函数中就应该匹配到并返回0值,但是具体怎么匹配的我们进入到mmc_attach_sdio函数中看。在mmc_attach_sdio函数中首先会发送CMD5命令给设备通过判断是否有回应和回应的标志位来判断是不是sdio设备,如果发送CMD5命令后有回应则可判断为sdio设备函数继续执行否则就会返回,接着函数会调用mmc_attach_bus函数来初始化bus_ops指针为mmc_sdio_ops总线操作集。接着设置和插入设备相匹配的电压,接着调用mmc_sdio_init_card函数来申请初始化struct card结构体,接着调用sdio_init_func函数来初始化card结构体里面的sdio_func结构体就是我们所对应sdio的设备。初始化结束后我们看到通过mmc_add_card(host->card);函数将card设备注册到内核的mmc_bus_type总线上,接着会调用sdio_add_func函数将func设备即我们的sdio设备注册到sdio_bus_type总线上,到目前为止从我们的设备插入到卡槽后内核所做的动作基本完成,虽然我们将具体的sdio设备注册到了内核中可是又怎么匹配到我们预先的驱动的呢?我们知道插入设备最后我们是将该sdio的设备注册到sdio_bus_type总线上的,我们知道在设备注册时会去在总线上匹配相应的驱动是通过总线的match函数即sdio_bus_match函数,通过分析该函数我们知道该总线的匹配原则是判断驱动的sdio_device_id结构体的sdio接口ID、设备的厂商ID和设备ID与插入设备的厂商ID、设备ID和sdio接口ID是否相等来匹配驱动的。可是在设备插入的时候是怎么知道该设备的厂商信息和设备ID的呢,继续返回代码查看得知在调用sdio_init_func函数的时候在函数里面调用sdio_read_func_cis函数去读取设备的CIS寄存器从而获得设备的信息与驱动进行匹配。

好了我们回到marvell的蓝牙wifi一体芯片上来,驱动文件在驱动目录下的bluetooth中文件为btmrvl_sdio.c,模块的初始化函数为btmrvl_sdio_init_module该函数比较简短如下

static int __init btmrvl_sdio_init_module(void)

{

if (sdio_register_driver(&bt_mrvl_sdio) != 0) {

BT_ERR("SDIO Driver Registration Failed");

return -ENODEV;

}

user_rmmod = 0;

return 0;

}

我们看红色标志的函数如下:

int sdio_register_driver(struct sdio_driver *drv)

{

drv->drv.name = drv->name;

drv->drv.bus = &sdio_bus_type;

return driver_register(&drv->drv);

}

比较简单看到了我们之前注册插入设备的总线(红色部分),这样驱动和设备都注册到了sdio_bus_type总线上并且通过设备的信息相互匹配。我们再来看看驱动定义设备信息是在什么地方,我们看注册的驱动结构体bt_mrvl_sdio

static struct sdio_driver bt_mrvl_sdio = {

.name = "btmrvl_sdio",

.id_table = btmrvl_sdio_ids,

.probe = btmrvl_sdio_probe,

.remove = btmrvl_sdio_remove,

};

static const struct sdio_device_id btmrvl_sdio_ids[] = {

{ SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x9105),

.driver_data = (unsigned long) &btmrvl_sdio_sd6888 },

{ SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x911A),

.driver_data = (unsigned long) &btmrvl_sdio_sd8787 },

{ }

};

可知在btmrvl_sdio_ids中预先定义了设备的信息。

蓝牙驱动分析 linux的更多相关文章

  1. Linux 串口、usb转串口驱动分析(2-2) 【转】

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26807463&id=4186852 Linux 串口.usb转 ...

  2. Linux 串口、usb转串口驱动分析(2-1) 【转】

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26807463&id=4186851 Linux 串口.usb转 ...

  3. linux驱动基础系列--Linux mmc sd sdio驱动分析

    前言 主要是想对Linux mmc子系统(包含mmc sd sdio)驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.块设备驱动.设备模型等也不进行详细说明原 ...

  4. linux驱动基础系列--Linux 串口、usb转串口驱动分析

    前言 主要是想对Linux 串口.usb转串口驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如字符设备驱动.平台驱动等也不进行详细说明原理.如果有任何错误地方,请指出, ...

  5. linux的串口驱动分析

    1.串口驱动中的数据结构 • UART驱动程序结构:struct uart_driver  驱动 • UART端口结构: struct uart_port  串口 • UART相关操作函数结构: st ...

  6. linux内核SPI总线驱动分析(一)(转)

    linux内核SPI总线驱动分析(一)(转) 下面有两个大的模块: 一个是SPI总线驱动的分析            (研究了具体实现的过程) 另一个是SPI总线驱动的编写(不用研究具体的实现过程) ...

  7. linux串口驱动分析

    linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作 ...

  8. linux i2c驱动架构-dm368 i2c驱动分析

      linux i2c驱动架构-dm368 i2c驱动分析   在阅读本文最好先熟悉一种i2c设备的驱动程序,并且浏览一下i2c-core.c以及芯片提供商的提供的i2c总线驱动(i2c-davinc ...

  9. Linux SD/MMC/SDIO驱动分析_转

    转自:Linux SD/MMC/SDIO驱动分析    https://www.cnblogs.com/cslunatic/p/3678045.html#3053341 一.SD/MMC/SDIO概念 ...

随机推荐

  1. 对拍 bat命令快速模板

    对拍.bat @echo off :loop maker.exe > in.in wq.exe < in.in > out.out std.exe < in.in >st ...

  2. jq ajax请求error: Maximum call stack size exceeded

    原因是data中参数iconUrl这个变量未声明导致的.jq在内部循环时报错

  3. c++基础_时间转换

    #include <iostream> using namespace std; int main(){ int n; cin>>n; ,b=,c=; )!=){ a=n/; ...

  4. 集训第四周(高效算法设计)L题 (背包贪心)

    Description   John Doe is a famous DJ and, therefore, has the problem of optimizing the placement of ...

  5. [K/3Cloud] 在插件中为辅助资料赋值

    因为辅助资料其实是一种特殊的基础资料,其赋值方法跟基础资料类似 this.Model.SetItemValueByNumber("FAssistant1", "Ameri ...

  6. 杜教筛--51nod1239 欧拉函数之和

    求$\sum_{i=1}^{n}\varphi (i)$,$n\leqslant 1e10$. 这里先把杜教筛的一般套路贴一下: 要求$S(n)=\sum_{i=1}^{n}f(i)$,而现在有一数论 ...

  7. 18.9.23 PION模拟赛

    U32670 小凯的数字 题目背景 NOIP2018 原创模拟题T1 NOIP DAY1 T1 or DAY 2 T1 难度 是否发现与NOIP2017 DAY1 T1 有异曲同工之妙 说明:#10, ...

  8. C#中使用 Oracle的事务与存储过程

    1 存储过程 1.1 不带参数,没有返回值 创建表 create table test (ID number, NAME varchar2(), SEX varchar2(), AGE number, ...

  9. java读取大文本文件

    原文:http://blog.csdn.net/k21325/article/details/53886160 小文件当然可以直接读取所有,然后放到内存中,但是当文件很大的时候,这个方法就行不通了,内 ...

  10. DELPHI、FLASH、AS3、FLEX使用Protobuf(google Protocol Buffers)的具体方法

    最近因为工作需要,需要在不同的开发环境中应用Protobuf,特此,我专门研究了一下.为了防止自己忘记这些事情,现在记录在这里!需要的朋友可以借鉴一些,因为这些东西在GOOGLE和百度上搜索起来真的很 ...