Android bluetooth介紹(二): android 藍牙代碼架構及其uart 到rfcomm流程

關鍵詞:藍牙blueZ  UART  HCI_UART H4  HCI  L2CAP RFCOMM  
版本:基於android4.2之前版本 bluez
內核:linux/linux3.08
系統:android/android4.1.3.4
作者:xubin341719(歡迎轉載,請註明作者,請尊重版權謝謝)
歡迎指正錯誤,共同學習、共同進步!!

Android bluetooth介紹(一):基本概念及硬件接口
Android bluetooth介紹(二): android 藍牙代碼架構及其uart 到rfcomm流程
Android bluetooth介紹(三): 藍牙掃描(scan)設備分析
Android bluetooth介紹(四): a2dp connect流程分析

一、Android Bluetooth Architecture藍牙代碼架構部分(google 官方藍牙框架)


Android的藍牙系統,自下而上包括以下一些內容如上圖所示:
1、串口驅動
Linux的內核的藍牙驅動程、Linux的內核的藍牙協議的層
2、BlueZ的適配器
BlueZ的(藍牙在用戶空間的函式庫)

bluez代碼結構
Bluetooth協議棧BlueZ分爲兩部分:內核代碼和用戶態程序及工具集。
(1)、內核代碼:由BlueZ核心協議和驅動程序組成
Bluetooth協議實現在內核源代碼 kernel/net/bluetooth中。包括hci,l2cap,hid,rfcomm,sco,SDP,BNEP等協議的實現。
(2)、驅動程序:kernel/driver/bluetooth中,包含Linuxkernel對各種接口的
Bluetooth device的驅動,如:USB接口,串口等。
(3)、用戶態程序及工具集:
包括應用程序接口和BlueZ工具集。BlueZ提供函數庫以及應用程序接口,便於程序員開發bluetooth應用程序。BlueZ utils是主要工具集,實現對bluetooth設備的初始化和控制。

3、藍牙相關的應用程序接口
Android.buletooth包中的各個Class(藍牙在框架層的內容-----java)

類名

作用

BluetoothAdapter

本地藍牙設備的適配類,所有的藍牙操作都要通過該類完成

BluetoothClass

用於描述遠端設備的類型,特點等信息

BluetoothDevice

藍牙設備類,代表了藍牙通訊過程中的遠端設備

BluetoothServerSocket

藍牙設備服務端,類似ServerSocket

BluetoothSocket

藍牙設備客戶端,類似Socket

BluetoothClass.Device

藍牙關於設備信息

BluetoothClass.Device.Major

藍牙設備管理

BluetoothClass.Service

藍牙相關服務

同樣下圖也是一張比較經典的藍牙代碼架構圖(google官方提供)


二、藍牙通過Hciattach啓動串口流程:
1、hciattach總體流程


2、展訊hciattach代碼實現流程:

三、具體代碼分析
1、initrc中定義

idh.code\device\sprd\sp8830ec_nwcn\init.sc8830.rc

[html] view plaincopy
  1. service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark  
  2.     socket bluetooth stream 660 bluetooth bluetooth  
  3.     user bluetooth  
  4.     group wifi bluetooth net_bt_admin net_bt inet net_raw net_admin system  
  5.     disabled  
  6. oneshot  

adb 下/dev/ttybt0(不同平臺有所不同)

PS 進程中:hicattch

2、/system/bin/hciattach 執行的Main函數
idh.code\external\bluetooth\bluez\tools\hciattach.c
service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark
傳進兩個參數,/dev/sttybt0 和 sprd_shark

[html] view plaincopy
  1. nt main(int argc, char *argv[])  
  2. {  
  3. ………………  
  4.     for (n = 0; optind < argc; n++, optind++) {  
  5.         char *opt;  
  6.   
  7.         opt = argv[optind];  
  8.   
  9.         switch(n) {  
  10.         case 0://(1)、解析驅動的位置;  
  11.             dev[0] = 0;  
  12.             if (!strchr(opt, '/'))  
  13.                 strcpy(dev, "/dev/");  
  14.             strcat(dev, opt);  
  15.             break;  
  16.   
  17.         case 1://(2)、解析串口的配置相關參數;  
  18.             if (strchr(argv[optind], ',')) {  
  19.                 int m_id, p_id;  
  20.                 sscanf(argv[optind], "%x,%x", &m_id, &p_id);  
  21.                 u = get_by_id(m_id, p_id);  
  22.             } else {  
  23.                 u = get_by_type(opt);  
  24.             }  
  25.   
  26.             if (!u) {  
  27.                 fprintf(stderr, "Unknown device type or id\n");  
  28.                 exit(1);  
  29.             }  
  30.   
  31.             break;  
  32.   
  33.         case 2://(3)、通過對前面參數的解析,把uart[i]中的數值初始化;  
  34.             u->speed = atoi(argv[optind]);  
  35.             break;  
  36.   
  37.         case 3:  
  38.             if (!strcmp("flow", argv[optind]))  
  39.                 u->flags |=  FLOW_CTL;  
  40.             else  
  41.                 u->flags &= ~FLOW_CTL;  
  42.             break;  
  43.   
  44.         case 4:  
  45.             if (!strcmp("sleep", argv[optind]))  
  46.                 u->pm = ENABLE_PM;  
  47.             else  
  48.                 u->pm = DISABLE_PM;  
  49.             break;  
  50.   
  51.         case 5:  
  52.             u->bdaddr = argv[optind];  
  53.             break;  
  54.         }  
  55.     }  
  56.   
  57. ………………  
  58.     if (init_speed)//初始化串口速率;  
  59.         u->init_speed = init_speed;  
  60. ………………  
  61.     n = init_uart(dev, u, send_break, raw);//(4)、初始化串口;  
  62. ………………  
  63.   
  64.     return 0;  
  65. }  

(1)、解析驅動的位置;

[html] view plaincopy
  1.             if (!strchr(opt, '/'))  
  2.                 strcpy(dev, "/dev/");  
  3. service hciattach /system/bin/hciattach -n /dev/sttybt0 sprd_shark  
  4. dev = /dev/ttyb0  

(2)、解析串口的配置相關參數;獲取參數對應的結構體;

[html] view plaincopy
  1.     u = get_by_id(m_id, p_id);  
  2. static struct uart_t * get_by_id(int m_id, int p_id)  
  3. {  
  4.     int i;  
  5.     for (i = 0; uart[i].type; i++) {  
  6.         if (uart[i].m_id == m_id && uart[i].p_id == p_id)  
  7.             return &uart[i];  
  8.     }  
  9.     return NULL;  
  10. }  

這個函數比較簡單,通過循環對比,如傳進了的參數sprd_shark和uart結構體中的對比,找到對應的數組。如果是其他藍牙芯片,如博通、RDA、BEKN等着到其相對應的初始化配置函數。

[html] view plaincopy
  1. struct uart_t uart[] = {  
  2.     { "any",        0x0000, 0x0000, HCI_UART_H4,   115200, 115200,  
  3.                 FLOW_CTL, DISABLE_PM, NULL, NULL     },  
  4.     { "sprd_shark",        0x0000, 0x0000, HCI_UART_H4,   115200, 115200,  
  5.                 FLOW_CTL, DISABLE_PM, NULL, init_sprd_config     },  
  6.   
  7.     { "ericsson",   0x0000, 0x0000, HCI_UART_H4,   57600,  115200,  
  8.                 FLOW_CTL, DISABLE_PM, NULL, ericsson },  
  9.   
  10. ………………  
  11.     { "bk3211",    0x0000, 0x0000, HCI_UART_BCSP,   115200, 921600, 0, DISABLE_PM,   NULL, beken_init, NULL},  
  12.     { NULL, 0 }  
  13. };  

注意:init_sprd_config這個函數在uart_init中用到,這個函數其實對我們具體芯片的初始化配置。
註釋:HCI_UART_H4和HCI_UART_BCSP的區別如下圖。

(3)、通過對前面參數的解析,把uart[i]中的數值初始化;

[html] view plaincopy
  1. u->speed = atoi(argv[optind]);  
  2. break;  

(4)、初始化串口;

[html] view plaincopy
  1. n = init_uart(dev, u, send_break, raw);  
  2. idh.code\external\bluetooth\bluez\tools\hciattach.c  
  3. /* Initialize UART driver */  
  4. int init_uart(char *dev, struct uart_t *u, int send_break)  
  5. {  
  6.  struct termios ti;  
  7.  int  fd, i;  
  8.  fd = open(dev, O_RDWR | O_NOCTTY);//打開串口設備,其中標誌  
  9. //O_RDWR,可以對此設備進行讀寫操作;  
  10. //O_NOCTTY:告訴Unix這個程序不想成爲“控制終端”控制的程序,不說明這個標誌的話,任何輸入都會影響你的程序。  
  11. //O_NDELAY:告訴Unix這個程序不關心DCD信號線狀態,即其他端口是否運行,不說明這個標誌的話,該程序就會在DCD信號線爲低電平時停止。  
  12. //但是不要以控制 tty 的模式,因爲我們並不希望在發送 Ctrl-C  
  13.  後結束此進程  
  14.  if (fd < 0) {  
  15.   perror(“Can’t open serial port”);  
  16.   return -1;  
  17.  }  
  18.  //drop fd’s data;  
  19.  tcflush(fd, TCIOFLUSH);//清空數據線  
  20.  if (tcgetattr(fd, &ti) < 0) {  
  21.   perror(“Can’t get port settings”);  
  22.   return -1;  
  23.  }  
  24.  cfmakeraw(&ti);  
  25. cfmakeraw sets the terminal attributes as follows://此函數設置串口終端的以下這些屬性,  
  26. termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP  
  27. |INLCR|IGNCR|ICRNL|IXON);  
  28. termios_p->c_oflag &= ~OPOST;  
  29. termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);  
  30. termios_p->c_cflag &= ~(CSIZE|PARENB) ;  
  31. termios_p->c_cflag |=CS8;  
  32.  ti.c_cflag |= CLOCAL;//本地連接,無調制解調器控制  
  33.  if (u->flags & FLOW_CTL)  
  34.   ti.c_cflag |= CRTSCTS;//輸出硬件流控  
  35.  else  
  36.   ti.c_cflag &= ~CRTSCTS;  
  37.  if (tcsetattr(fd, TCSANOW, &ti) < 0) {//啓動新的串口設置  
  38.   perror(“Can’t set port settings”);  
  39.   return -1;  
  40.  }  
  41.  /* Set initial baudrate */  
  42.  if (set_speed(fd, &ti, u->init_speed) < 0) {//設置串口的傳輸速率bps, 也可以使  
  43. //用 cfsetispeed 和 cfsetospeed 來設置  
  44.   perror(“Can’t set initial baud rate”);  
  45.   return -1;  
  46.  }  
  47.  tcflush(fd, TCIOFLUSH);//清空數據線  
  48.  if (send_break)  
  49.   tcsendbreak(fd, 0);  
  50. //int tcsendbreak ( int fd, int duration );Sends a break for  
  51. //the given time.在串口線上發送0值,至少維持0.25秒。  
  52. //If duration is 0, it transmits zero-valued bits for at least 0.25 seconds, and  
  53. //not more than 0.5seconds.  
  54.  //where place register u’s init function;  
  55.  if (u->init && u->init(fd, u, &ti) < 0)  
  56. //所有bluez支持的藍牙串口設備類型構成了一個uart結構數組,通過  
  57. //查找對應的uart類型,這個uart的init成員顯示了它的init調用方法;  
  58. struct uart_t uart[] = {  
  59. { "any", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200,FLOW_CTL, DISABLE_PM, NULL, NULL     },  
  60. { "sprd_shark", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200,FLOW_CTL, DISABLE_PM, NULL, init_sprd_config     },  
  61.   
  62. { "ericsson", 0x0000, 0x0000, HCI_UART_H4,   57600,  115200,FLOW_CTL, DISABLE_PM, NULL, ericsson },  
  63. ………………  
  64.     { "bk3211",    0x0000, 0x0000, HCI_UART_BCSP,   115200, 921600, 0, DISABLE_PM,   NULL, beken_init, NULL},  
  65.     { NULL, 0的init函數名爲bcsp,定義在本文件中**;  
  66.   return -1;  
  67.  tcflush(fd, TCIOFLUSH);//清空數據線  
  68.  /* Set actual baudrate */  
  69.  if (set_speed(fd, &ti, u->speed) < 0) {  
  70.   perror(“Can’t set baud rate”);  
  71.   return -1;  
  72.  }  
  73.  /* Set TTY to N_HCI line discipline */  
  74.  i = N_HCI;  
  75.  if (ioctl(fd, TIOCSETD, &i) < 0) {//  
  76. TIOCSETD int *ldisc//改變到 i 行規,即hci行規  
  77. Change to the new line discipline pointed to by ldisc. The available line disciplines are listed in   
  78. /* ioctl (fd, TIOCSERGETLSR, &result) where result may be as below */  
  79. /* line disciplines */  
  80. #define N_TTY  0  
  81. ……  
  82. #define N_HCI  15  /* Bluetooth HCI UART */  
  83.   
  84.   perror(“Can’t set line discipline”);  
  85.   return -1;  
  86.  }  
  87.  if (ioctl(fd, HCIUARTSETPROTO, u->proto) < 0) {  
  88. //設置hci設備的proto操作函數集爲hci_uart操作集;  
  89.   perror(“Can’t set device”);  
  90.   return -1;  
  91.  }  
  92.  return fd;  
  93. }  

這裏一個重要的部分是:u->init指向init_sprd_config
4、uart具體到芯片的初始化init_sprd_config(這部分根據不同的芯片,對應進入其相應初始化部分)
idh.code\external\bluetooth\bluez\tools\hciattach_sprd.c

[html] view plaincopy
  1. int sprd_config_init(int fd, char *bdaddr, struct termios *ti)  
  2. {  
  3.     int i,psk_fd,fd_btaddr,ret = 0,r,size=0,read_btmac=0;  
  4.     unsigned char resp[30];  
  5.     BT_PSKEY_CONFIG_T bt_para_tmp;  
  6.     char bt_mac[30] = {0};  
  7.     char bt_mac_tmp[20] = {0};  
  8.     uint8 bt_mac_bin[32]     = {0};  
  9.   
  10.     fprintf(stderr,"init_sprd_config in \n");  
  11. //(1)、這部分檢查bt_mac,如果存在,從文件中讀取,如果不存在,隨機生成,並寫入相應文件;  
  12.     if(access(BT_MAC_FILE, F_OK) == 0) {//這部分檢查bt_mac  
  13.         LOGD("%s: %s exists",__FUNCTION__, BT_MAC_FILE);  
  14.         fd_btaddr = open(BT_MAC_FILE, O_RDWR);// #define BT_MAC_FILE        "/productinfo/btmac.txt"  
  15.         if(fd_btaddr>=0) {  
  16.             size = read(fd_btaddr, bt_mac, sizeof(bt_mac));//讀取BT_MAC_FILE中的地址;  
  17.             LOGD("%s: read %s %s, size=%d",__FUNCTION__, BT_MAC_FILE, bt_mac, size);  
  18.             if(size == BT_RAND_MAC_LENGTH){  
  19.                         LOGD("bt mac already exists, no need to random it");  
  20.                         fprintf(stderr, "read btmac ok \n");  
  21.                         read_btmac=1;  
  22.             }  
  23. …………  
  24.     }else{//如果不存在,就隨機生成一個bt_mac地址,寫入/productinfo/btmac.txt  
  25.         fprintf(stderr, "btmac.txt not exsit!\n");  
  26.         read_btmac=0;  
  27.         mac_rand(bt_mac);  
  28.         LOGD("bt random mac=%s",bt_mac);  
  29.         printf("bt_mac=%s\n",bt_mac);  
  30.         write_btmac2file(bt_mac);  
  31.   
  32.         fd_btaddr = open(BT_MAC_FILE, O_RDWR);  
  33.         if(fd_btaddr>=0) {  
  34.             size = read(fd_btaddr, bt_mac, sizeof(bt_mac));  
  35.             LOGD("%s: read %s %s, size=%d",__FUNCTION__, BT_MAC_FILE, bt_mac, size);  
  36.             if(size == BT_RAND_MAC_LENGTH){  
  37.                         LOGD("bt mac already exists, no need to random it");  
  38.                         fprintf(stderr, "read btmac ok \n");  
  39.                         read_btmac=1;  
  40.             }  
  41.             close(fd_btaddr);  
  42. …………  
  43.     }  
  44.   
  45.     /* Reset the BT Chip */  
  46.   
  47.     memset(resp, 0, sizeof(resp));  
  48.     memset(&bt_para_tmp, 0, sizeof(BT_PSKEY_CONFIG_T) );  
  49.     ret = getPskeyFromFile(  (void *)(&bt_para_tmp) );//ret = get_pskey_from_file(&bt_para_tmp);//(2)、PSKey參數、射頻參數的設定;  
  50.        if(ret != 0){//參數失敗處理  
  51.             fprintf(stderr, "get_pskey_from_file faill \n");  
  52.             /* Send command from hciattach*/  
  53.             if(read_btmac == 1){  
  54.                 memcpy(bt_para_setting.device_addr, bt_mac_bin, sizeof(bt_para_setting.device_addr));// (3)、讀取失敗,把bt_para_setting中defaut參數寫入;  
  55.             }  
  56.             if (write(fd, (char *)&bt_para_setting, sizeof(BT_PSKEY_CONFIG_T)) != sizeof(BT_PSKEY_CONFIG_T)) {  
  57.                 fprintf(stderr, "Failed to write reset command\n");  
  58.                 return -1;  
  59.             }  
  60.         }else{//getpskey成功處理  
  61.             /* Send command from pskey_bt.txt*/  
  62.             if(read_btmac == 1){  
  63.                 memcpy(bt_para_tmp.device_addr, bt_mac_bin, sizeof(bt_para_tmp.device_addr));  
  64.             }  
  65. …………  
  66.     return 0;  
  67. }  

(1)、這部分檢查bt_mac,如果存在,從文件中讀取,如果不存在,隨機生成,並寫入相應文件/productinfo/btmac.txt
(2)、PSKey參數、射頻參數的設定;
get_pskey_from_file(&bt_para_tmp);這個函數後面分析;
(3)、讀取失敗,把bt_para_setting中defaut參數寫入;頻率、主從設備設定等……

[html] view plaincopy
  1. // pskey file structure default value  
  2. BT_PSKEY_CONFIG_T bt_para_setting={  
  3. 5,  
  4. 0,  
  5. 0,  
  6. 0,  
  7. 0,  
  8. 0x18cba80,  
  9. 0x001f00,  
  10. 0x1e,  
  11. {0x7a00,0x7600,0x7200,0x5200,0x2300,0x0300},  
  12. …………  
  13. };  

5、get_pskey_from_file 解析相關射頻參數
idh.code\external\bluetooth\bluez\tools\pskey_get.c

[html] view plaincopy
  1. int getPskeyFromFile(void *pData)  
  2. {  
  3. …………  
  4.         char *BOARD_TYPE_PATH = "/dev/board_type";//(1)、判斷PCB的版本;  
  5.         int fd_board_type;  
  6.         char board_type_str[MAX_BOARD_TYPE_LEN] = {0};  
  7.         int board_type;  
  8.         char *CFG_2351_PATH_2 = "/productinfo/2351_connectivity_configure.ini";//(2)、最終生成ini文件存儲的位置;  
  9.         char *CFG_2351_PATH[MAX_BOARD_TYPE];  
  10.         (3)、針對不同PCB版本,不同的ini配置文件;  
  11.         CFG_2351_PATH[0] = "/system/etc/wifi/2351_connectivity_configure_hw100.ini";  
  12.         CFG_2351_PATH[1] = "/system/etc/wifi/2351_connectivity_configure_hw102.ini";  
  13.         CFG_2351_PATH[2] = "/system/etc/wifi/2351_connectivity_configure_hw104.ini";  

(4)、下面函數就不做具體分析,大致意識是,根據/dev/board_type中,讀取的PCB類型,設置不同的ini文件。   

[html] view plaincopy
  1. ………………  
  2.     ret = chmod(CFG_2351_PATH_2, 0644);  
  3.     ALOGE("chmod 0664 %s ret:%d\n", CFG_2351_PATH_2, ret);    
  4.     if(pBuf == pBuf2)  
  5.         free(pBuf1);  
  6. ………………  
  7. }  

(1)、判斷PCB的版本;
char *BOARD_TYPE_PATH = "/dev/board_type";

(2)、最終生成ini文件存儲的位置,就是系統運行時讀取ini文件的地方;
char *CFG_2351_PATH_2 ="/productinfo/2351_connectivity_configure.ini";
(3)、針對不同PCB版本,不同的ini配置文件;

[html] view plaincopy
  1. CFG_2351_PATH[0] = "/system/etc/wifi/2351_connectivity_configure_hw100.ini";  
  2. CFG_2351_PATH[1] = "/system/etc/wifi/2351_connectivity_configure_hw102.ini";  
  3. CFG_2351_PATH[2] = "/system/etc/wifi/2351_connectivity_configure_hw104.ini";  

(4)、下面函數就不做具體分析,大致意識是,根據/dev/board_type中,讀取的PCB類型,設置不同的ini文件。         覆蓋到(2)中的文件。
四、HCI_UART_H4和H4層的加入

uart->hci_uart->Uart-H4->hci:從uart開始分析,介紹整個驅動層數據流(涉及tty_uart中斷,   線路層ldisc_bcsp、tasklet、work queue、skb_buffer的等)

這是數據的流動過程,最底層的也就是和硬件打交道的是uart層了,它的存在和起作用是通過串口驅動來保證的,這個請參閱附錄,但是其它的層我們都不知道什麼時候work的,下面來看。

1、idh.code\kernel\drivers\bluetooth\hci_ldisc.c

[html] view plaincopy
  1. static int __init hci_uart_init(void)  
  2. {  
  3.     static struct tty_ldisc_ops hci_uart_ldisc;  
  4.     int err;  
  5.     /* Register the tty discipline */  
  6.   
  7.     memset(&hci_uart_ldisc, 0, sizeof (hci_uart_ldisc));  
  8.     hci_uart_ldisc.magic        = TTY_LDISC_MAGIC;  
  9.     hci_uart_ldisc.name     = "n_hci";  
  10.     hci_uart_ldisc.open     = hci_uart_tty_open;  
  11.     hci_uart_ldisc.close        = hci_uart_tty_close;  
  12.     hci_uart_ldisc.read     = hci_uart_tty_read;  
  13.     hci_uart_ldisc.write        = hci_uart_tty_write;  
  14.     hci_uart_ldisc.ioctl        = hci_uart_tty_ioctl;  
  15.     hci_uart_ldisc.poll     = hci_uart_tty_poll;  
  16.     hci_uart_ldisc.receive_buf  = hci_uart_tty_receive;  
  17.     hci_uart_ldisc.write_wakeup = hci_uart_tty_wakeup;  
  18.     hci_uart_ldisc.owner        = THIS_MODULE;  
  19.   
  20.     if ((err = tty_register_ldisc(N_HCI, &hci_uart_ldisc))) {//(1)、這部分完成ldisc的註冊;  
  21.         BT_ERR("HCI line discipline registration failed. (%d)", err);  
  22.         return err;  
  23.     }  
  24.   
  25. #ifdef CONFIG_BT_HCIUART_H4  
  26.     h4_init();//(2)、我們藍牙芯片用的是H4,這部分完成H4的註冊;  
  27. #endif  
  28. #ifdef CONFIG_BT_HCIUART_BCSP  
  29.     bcsp_init();  
  30. #endif  
  31. ………………  
  32.     return 0;  
  33. }  

(1)、這部分完成ldisc的註冊;
tty_register_ldisc(N_HCI,&hci_uart_ldisc)
註冊了一個ldisc,這是通過把新的ldisc放在一個ldisc的數組裏面實現的,tty_ldiscs是一個全局的ldisc數組裏面會根據序號對應一個ldisc,這個序號就是上層通過ioctl來指定的,比如我們在前面已經看到的:
i = N_HCI;
ioctl(fd, TIOCSETD, &i) < 0
可以看到這裏指定的N_HCI剛好就是這裏註冊的這個號碼15;
(2)、藍牙芯片用的是H4,這部分完成H4的註冊;
         h4_init();
hci_uart_proto結構體的初始化:

idh.code\kernel\drivers\bluetooth\hci_h4.c

[html] view plaincopy
  1. static struct hci_uart_proto h4p = {  
  2.     .id     = HCI_UART_H4,  
  3.     .open       = h4_open,  
  4.     .close      = h4_close,  
  5.     .recv       = h4_recv,  
  6.     .enqueue    = h4_enqueue,  
  7.     .dequeue    = h4_dequeue,  
  8.     .flush      = h4_flush,  
  9. };  

H4的註冊:
idh.code\kernel\drivers\bluetooth\hci_h4.c

[html] view plaincopy
  1. int __init h4_init(void)  
  2. {  
  3.     int err = hci_uart_register_proto(&h4p);  
  4.   
  5.     if (!err)  
  6.         BT_INFO("HCI H4 protocol initialized");  
  7.     else  
  8.         BT_ERR("HCI H4 protocol registration failed");  
  9.   
  10.     return err;  
  11. }  

這是通過hci_uart_register_proto(&bcsp)來完成的,這個函數非常簡單,本質如下:
hup[p->id]= p;其中static struct hci_uart_proto*hup[HCI_UART_MAX_PROTO];也就是說把對應於協議p的id和協議p連接起來,這樣設計的好處是hci uart層本身可以支持不同的協議,包括h4、bcsp等,通過這個數組連接這些協議,等以後有數據的時候調用對應的協議來處理,這裏比較關鍵的是h4裏面的這些函數。
五、HCI層的加入
hci的加入是通過hci_register_dev函數來做的,這時候用戶通過hciconfig就可以看到有一個接口了,通過這個接口用戶可以訪問底層的信息了,hci0已經生成;至於它在何時被加入的,我們再看看hciattach在內核裏面的處理過程;

1、TIOCSEATD的處理流程

Ioctl的作用是設置一個新的ldisc;
2、HCIUARTSETPROTO的處理流程:

這部分比較重要,註冊生成hci0, 初始化3個工作隊列,hci_rx_work、hci_tx_work、hci_cmd_work;完成hci部分數據、命令的接收、發送。
六、數據在驅動的傳遞流程
1、uart數據接收
         這部分流程比較簡單,其實就是註冊一個tty驅動程序和相對應的函數,註冊相應的open\close\ioctl等方法,通過應用open /dev/ttyS*操作,註冊中斷接收函數,接收處理藍牙模塊觸發中斷的數據。

在這個中斷函數裏面會接受到來自於藍牙模塊的數據;在中斷函數裏面會先讀取串口的狀態寄存器判斷是否是data準備好,如果準備好就調用serial_sprd_rx_chars函數來接收數據,下面看看這個函數是如何處理的:

那就是把數據一個個的加入到uart層的緩衝區,直到底層不處於dataready狀態,或者讀了maxcount個數,當讀完後就調用tty層的接口把數據傳遞給tty層,tty層則把數據交給了ldisc,於是控制權也就交給了hci_uart層;

七、Hci_uart的數據接收
它基本上就是要個二傳手,通過:

[html] view plaincopy
  1. spin_lock(&hu->rx_lock);  
  2. hu->proto->recv(hu,(void *) data, count);  
  3. hu->hdev->stat.byte_rx+= count;  
  4. spin_unlock(&hu->rx_lock);  
把數據交給了在它之上的協議層,對於我們的設置來說實際上就交給了h4層;
八、H4層處理
這層主要是通過函數h4_recv來處理的,根據協議處理包頭、CRC等,然後調用更上層的hci_recv_frame來處理已經剝去h4包頭的數據;

如圖:

九、HCI以上的處理

這裏的hci_rx_work前面已經看到它了,它是一個工作隊列用來處理hci層的數據接收的;先看是否有進程打開hci的socket用來監聽數據,如果有的話,就把數據的一個copy發送給它,然後根據包的類型調用不同的處理函數,分別對應於event、acl、sco處理;
hci_event_packet是對於事件的處理,裏面包含有包括掃描,信號,授權,pin碼,總之基本上上層所能收到的事件,基本都是在這裏處理的,它的很多信息都是先存起來,等待上層的查詢然後才告訴上層;
hci_acldata_packet是一個經常的情況,也就是說上層通常都是使用的是l2cap的接口,而l2cap就是基於這個的,如下圖所示:

到這裏如果有基於BTPROTO_L2CAP的socket,那麼這個socket就可以收到數據了;再看看BTPROTO_RFCOMM的流程:

十、 數據流程的總結
簡單總結一下,數據的流程,
|基本上是:
1, uart口取得藍牙模塊的數據;
2, uart口通過ldisc傳給hci_uart;
3, hci_uart傳給在其上的h4;
4, h4傳給hci層;
5, hci層傳給l2cap層
6, l2cap層再傳給rfcomm;

發佈了52 篇原創文章 · 獲贊 28 · 訪問量 83萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章