Qualcomm usb modem驅動小結
前段時間再為我們公司的子產品産品sim5210寫linux下的驅動, sim5210使用的是qualcomm的6280晶片, 該平台提供了USB功能, 并再USB之上提供了modem, diag, nmea等裝置接口, 即再usb之上我們的子產品實作了modem功能, diag(診斷)功能, nmea(gps)功能, 由于在linux下有現成的标準CDC modem的驅動, 是以我們以此為參考實作了我們的非标準modem, 和用于收發at command的diag的驅動. 下面我們重點總結一下modem驅動的編寫方法, diag類似.
我們的modem驅動是tty+usb的層次關系, 即上層跟使用者互動的接口使用tty驅動類型, 初始化時先注冊一個裝置檔案, 以後使用者使用時直接通過這個裝置檔案與裝置打交道, 當使用者的操作傳遞到我們的tty驅動層後, 在我們的驅動中在把這些操作(read, write)轉化為對usb裝置的操作, 最後通過usb總線與usb裝置互動, 整個驅動結構如下:
下面詳細講解這個驅動的整個流程:
1 通過module_init() 和 module_exit()注冊我們驅動的init和exit函數, init函數将會在驅動加載時被調用, exit函數将會在驅動解除安裝時被調用.
2 我們的init函數是
static int __init qcmdm_init()
{
return 0;
}
通過這一步, 我們注冊了usb驅動對象, 和tty驅動對象, 但這時裝置檔案還沒建立好,是以這個tty驅動還沒法用, 當modem查上後linux核心會調用我們先前注冊的usb驅動對象中的probe函數(整個調用流程可以參考我寫的另一篇文章<<usb裝置probe全過程>>)
我們的exit函數是:
static void __exit qcmdm_exit()
{
}
3 我們qcmdm_usb_driver的probe函數:
static int qcmdm_probe(struct usb_interface *intf,
const struct usb_device_id *id )
{
}
此外,我們還可以在probe中儲存額外的一些資訊, 如調用usb_set_intfdata(intf, mdm)來将mdm儲存在intf的相關字段中, 這樣在以後使用中可以直接通過usb_get_intfdata()來擷取mdm指針.
4我們qcmdm_usb_driver的disconnect函數(在裝置拔出時調用): 這個函數主要做一些和probe相反的工作
static void qcmdm_disconnect(struct usb_interface *intf)
{
}
總之在probe中申請了什麼資源應該在disconnect中釋放掉, 因為下次裝置連上後,又會調用probe, 此時又會去配置設定資源.
5 打開函數: 這個函數是tty driver的一部分, 當打開我們建立的裝置檔案時會調用這個函數, 它的工作主要也是做一些初始化工作, 并調用usb_submit_urb()送出一個中斷類型的urb(因為我們的裝置上有個interrupt的端點, 用于裝置上報一些自身狀态的改變資訊, 是以這裡我們應該準備好接收這些狀态資訊)
6 write函數: 這也是tty driver的一部分, 在我們的驅動中儲存了一個用于寫usb的緩沖連結清單, 每次要寫時就去擷取一個空閑表項, 并把端點,資料等資訊填充号這個表項中的urb, 特别的一般我們還會填充一個我們自己的用于完成後的回調函數. 然後調用usb_submit_urb送出這個寫操作, 在usb寫完或錯誤後會調用我們的回調函數(注意這是運作在中斷上下文的), 這樣我們就知道成功與否.
7 read函數: 這也是tty driver的一部分跟write相似,也有一個用于讀的連結清單, 每個讀操作也是先填充urb, 然後在送出,在完成後調用我們的回調函數, 此時我們得到了資料, 可以通過tty_insert_flip_string()和tty_flip_buffer_push()把資料送出到tty核心,在由tty核心交給使用者. 由于在usb核心收到資料後應該快速的傳回給使用者, 是以在讀的部分, 我們使用了tasklet, 以加快資料讀出,又可避免直接在中斷上下文讀資料.
8 其他函數相對簡單,在此不作介紹