蓝牙驱动分析

这个驱动分析的是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. UvaLive 4917 Abstract Extract (模拟)

    题意: 给定一篇文章, 文章中有段落, 段落中有句子. 句子只会以'!' , '.' , '?' 结尾, 求出每段中含有与他下面同样是该段落中相同单词数最多的句子, 注意, 单词忽略大小写, 重复的单 ...

  2. numpy——基础数组与计算

    In [1]: import numpy as np In [11]: # 创建数组 a = np.array([1,2,3,4,5]) In [12]: a Out[12]: array([1, 2 ...

  3. 【03】Html书写规范

    [03]   Html书写规范   1.推荐使用html5的文档声明 <!DOCTYPE HTML> 2.必须申明文档的编码charset,且与文件本身编码保持一致,推荐使用UTF-8编码 ...

  4. 转盘抽奖 canvas & 抽奖 H5 源码

    转盘抽奖 canvas https://github.com/givebest/wechat-turntalbe-canvas https://blog.givebest.cn/GB-canvas-t ...

  5. android开发里跳过的坑——listview不显示

    在蓝牙回调接口public void onLeScan(BluetoothDevice device, int arg1, byte[] arg2)里面调用adpter.notifyDataSetCh ...

  6. vagrant的学习 之 ThinkPHP5.1

    vagrant的学习 之 ThinkPHP5.1 本文根据慕课网的视频教程练习,感谢慕课网! 慕课视频学习地址:https://www.imooc.com/video/14218. 慕课的参考文档地址 ...

  7. 自定义View实现跟随手指的小球

    package com.pingyijinren.test; import android.content.Context; import android.graphics.Canvas; impor ...

  8. 洛谷 P1018 乘积最大

    P1018 乘积最大 题目描述 今年是国际数学联盟确定的“ 20002000 ――世界数学年”,又恰逢我国著名数学家华罗庚先生诞辰 9090 周年.在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学 ...

  9. MySQL集群之五大常见的MySQL高可用方案(转)

    1. 概述 我们在考虑MySQL数据库的高可用的架构时,主要要考虑如下几方面: 如果数据库发生了宕机或者意外中断等故障,能尽快恢复数据库的可用性,尽可能的减少停机时间,保证业务不会因为数据库的故障而中 ...

  10. Oracle表空间 ORA-01653:

    --1.查看表空间USERS使用情况SELECT T.TABLESPACE_NAME,D.FILE_NAME, D.AUTOEXTENSIBLE,D.BYTES,D.MAXBYTES,D.STATUS ...