• 蓝牙驱动分析 linux


    蓝牙驱动分析

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

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

    首先每次插入设备和拔出设备驱动都会通过终端打印相应的信息,判断在sd卡槽中肯定是触发中断的,通过看硬件原理图和数据手册中的SDMMC控制器可知用于mmc的中断号分别为5657,回到代码中。在内核启动过程中会调用到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正是由于这个长度为0long型数组在内存分配时记录下了上层结构体的地址。有点跑题,回到驱动上来,接着分析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、设备IDsdio接口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中预先定义了设备的信息。

  • 相关阅读:
    每天进步一点点-->函数fseek() 使用方法
    几种更新(Update语句)查询的方法
    hibernate批量删除和更新数据
    Android ViewPager使用具体解释
    Linux curses库使用
    安装numpy、nltk问题汇总
    android widget 开发实例 : 桌面便签程序的实现具体解释和源代码 (上)
    Eclipse中SVN的安装步骤(两种)和用法
    Intent用法
    Tomcat全攻略
  • 原文地址:https://www.cnblogs.com/Ph-one/p/6373678.html
Copyright © 2020-2023  润新知