藍牙驅動分析 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中預先定義了設備的信息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM