天天看點

編寫Linux usb 鍵盤驅動的筆記

編寫linux usb 裝置驅動參考:

    \\10.150.50.230\zhangjiaqi\linux-5.8.5\Documentation\driver-api\usb\writing_usb_driver.rst

    \\10.150.50.230\zhangjiaqi\linux-5.8.5\drivers\usb\usb-skeleton.c    //usb裝置驅動标準架構,這是一個usb磁盤驅動程式,使用者app通過open,wrire,read讀寫磁盤(u盤);

寫linux device driver的最好方法是:研究Documentation裡的說明+核心裡有關這個驅動的例程代碼;

usb_write:驅動将資料寫到device(usb SD卡驅動)

要構造urb,并且填充要寫的data到urb,然後usb_submit_urb(skel->write_urb);啟動寫資料

usb_read:驅動從device讀資料到driver(輸入裝置,比如滑鼠)

讀要分為兩種:1.要不斷的讀資料,read動作的觸發起源于硬體裝置,比如滑鼠移動就會自動read資料到driver;                           

              2.不需要不斷的讀資料,read動作的觸發起源于驅動代碼本身,驅動要讀資料才去讀,

                這種情況不需要使用urb,可使用函數usb_bulk_msg(...)進行單次的讀資料;

              3.這段話摘自核心:

              ` usb_bulk_msg` function can be very useful for doing single reads

                or writes to a device; however, if you need to read or write constantly to

                a device, it is recommended to set up your own urbs and submit them to

                the USB subsystem.

              4.實時讀寫資料:usb driver需要自己構造urb,并且填充,并且送出urb;

                非實時讀寫資料:usb driver可以使用函數usb_bulk_msg()來讀寫資料;

              5.寫資料一般要構造urb,讀分為兩種:實時資料要構造urb,非實時調用函數usb_bulk_msg(...)進行讀;

              6.urb可以重複填充,也可以重複送出

              7.usb讀與usb寫的最大不同是讀跟寫的端點不同,端點裡包含的端點的具體位址也不同,具體表現為:

                struct usb_endpoint_descriptor *bulk_in, *bulk_out;//輸入端口,輸出端口

                usb_find_common_endpoints(interface->cur_altsetting,&bulk_in, &bulk_out, NULL, NULL);//分别擷取輸入,輸出端點。

                bulk_in->bEndpointAddress不同于bulk_out->bEndpointAddress,導緻填充各自的urb時産生差異得以區分讀寫過程。

                in_urb:建立一次,反複填充;

                out_urb:每寫一次建立一次并且填充一次且送出一次,即時建立即時銷毀;

              8.linux核心提供的usb裝置驅動編寫(包括讀寫過程)的架構為:linux-5.8.5\drivers\usb\usb-skeleton.c

              9.usb攝像頭(視訊,音頻)是一個實時傳輸data的usb裝置,跟滑鼠類似->單向實時usb輸入裝置,u盤屬于非實時usb存貯裝置(有讀有寫)。

            10.usb裝置分為兩種,1.裝置發起讀寫請求,如usb滑鼠,usb鍵盤;

                                2.驅動程式發起讀寫請求,如usb磁盤;

                                3.讀要遵循一個規則,urb将資料從裝置傳輸回來之後,要驅動程式處理完資料了,才可以再次送出urb,這個送出的動作可以發生在回掉函數裡,也可以發生在回掉函數之外;反正就是要再次送出才可以再次運輸新的資料回來;

                                4.寫要遵循的規則,

            11.\\10.150.50.230\zhangjiaqi\linux-5.8.5\drivers\usb\usb-skeleton.c ,這個是寫usb磁盤的參考資料,但有很多細節可以參考;

            12.usb總線每傳輸完一次資料就會産生一次回調中斷,這裡面可以做資料處理,也可以重新送出urb;

            13.usb裝置驅動分為兩種一種是每次傳輸少量資料并且是硬體端發起的實時傳輸請求的如滑鼠,鍵盤,觸摸屏等,這類可以用中斷型:usb_fill_int_urb

                                  另外一種是每次傳輸大量資料并且由驅動程式發起的非實時傳輸其你去如u盤,usb-lcd螢幕等,這類可以用塊usb_fill_bulk_urb

                                  主要的差別是中斷型的在fill的時候會配置dev->speed,中斷型的傳輸速率會要求比較高;

測試usb鍵盤驅動

  Device Drivers > 

        HID support > 

            USB HID support > 

                < > USB HID transport layer//取消選中這個,要不然就會編譯linux核心自帶的鍵盤驅動,輪不到自己寫的鍵盤驅動進行控制鍵盤;

  對于tty(輸入輸出)裝置的了解

  tty0表示目前使用的控制終端

  tty1對應/dev/input/event0(輸入裝置0)

  tty2對應/dev/input/event1(輸入裝置1)

  tty3對應/dev/input/event2(輸入裝置2)

  是以要确定自己注冊的輸入裝置是event*幾;

  簡單測試

  cat /dev/tty1  //這個測試按下了對應的字母按鍵之後,要再次按下enter鍵才可以顯示之前按下的按鍵;

  正式測試

  exec 0</dev/tty1   //将開發闆的控制台改為自己的鍵盤以及自己開發的驅動,這樣可以正确測試鍵盤的輸入以及直接用控制台測試;

  對exec測試指令的解釋:

  對于做驅動經常會使用exec來試驗驅動,通過exec将-sh程序下的描述符指向我們的驅動,來實作調試

    -sh程序常用描述符号:

    0:标準輸入

    1:标準輸出

    2:錯誤資訊

    5:中斷服務

    exec指令使用:

    挂載:  exec [描述符号]<[裝置節點路徑] 

    解除安裝:  exec [描述符号]<&-

    執行個體:

    exec 0</dev/tty1 //将本開發闆的第1個輸入裝置(第一個注冊的input_device,就是鍵盤驅動)挂接到-sh下的0号描述符,也就是标準輸入來自/dev/tty1,也就是将标準輸入從預設改為我們自己寫的輸入裝置;

    exec 0<&-        //調用/dev/tty1驅動的解除安裝函數,系統的标準輸入不要用我們自己注冊的輸入裝置;

下面是整理的有關Linux usb裝置驅動的其他資料

現象:把USB裝置接到PC

1. 右下角彈出"發現android phone"

2. 跳出一個對話框,提示你安裝驅動程式

問1. 既然還沒有"驅動程式",為何能知道是"android phone"

答1. windows裡已經有了USB的總線驅動程式,接入USB裝置後,是"總線驅動程式"知道你是"android phone"

     提示你安裝的是"裝置驅動程式"

     USB總線驅動程式負責:識别USB裝置, 給USB裝置找到對應的驅動程式

問2. USB裝置種類非常多,為什麼一接入電腦,就能識别出來?

答2. PC和USB裝置都得遵守一些規範。

     比如:USB裝置接入電腦後,PC機會發出"你是什麼"?

           USB裝置就必須回答"我是xxx", 并且回答的語言必須是中文

     USB總線驅動程式會發出某些指令想擷取裝置資訊(描述符),

     USB裝置必須傳回"描述符"給PC

問3. PC機上接有非常多的USB裝置,怎麼分辨它們?

     USB接口隻有4條線: 5V,GND,D-,D+

答3. 每一個USB裝置接入PC時,USB總線驅動程式都會給它配置設定一個編号

     接在USB總線上的每一個USB裝置都有自己的編号(位址)

     PC機想通路某個USB裝置時,發出的指令都含有對應的編号(位址)

問4. USB裝置剛接入PC時,還沒有編号;那麼PC怎麼把"配置設定的編号"告訴它?

答4. 新接入的USB裝置的預設編号是0,在未配置設定新編号前,PC使用0編号和它通信。

問5. 為什麼一接入USB裝置,PC機就能發現它?

答5. PC的USB口内部,D-和D+接有15K的下拉電阻,未接USB裝置時為低電平

     USB裝置的USB口内部,D-或D+接有1.5K的上拉電阻;它一接入PC,就會把PC USB口的D-或D+拉高,從硬體的角度通知PC有新裝置接入

其他概念:

1. USB是主從結構的

   所有的USB傳輸,都是從USB主機這方發起;USB裝置沒有"主動"通知USB主機的能力。

   例子:USB滑鼠滑動一下立刻産生資料,但是它沒有能力通知PC機來讀資料,隻能被動地等得PC機來讀。

2. USB的傳輸類型:

a. 控制傳輸:可靠,時間有保證,比如:USB裝置的識别過程

b. 批量傳輸: 可靠, 時間沒有保證, 比如:U盤

c. 中斷傳輸:可靠,實時,比如:USB滑鼠

d. 實時傳輸:不可靠,實時,比如:USB攝像頭

3. USB傳輸的對象:端點(endpoint)

   我們說"讀U盤"、"寫U盤",可以細化為:把資料寫到U盤的端點1,從U盤的端點2裡讀出資料

   除了端點0外,每一個端點隻支援一個方向的資料傳輸

   端點0用于控制傳輸,既能輸出也能輸入

4. 每一個端點都有傳輸類型,傳輸方向

5. 術語裡、程式裡說的輸入(IN)、輸出(OUT) "都是" 基于USB主機的立場說的。

   比如滑鼠的資料是從滑鼠傳到PC機, 對應的端點稱為"輸入端點"

6. USB總線驅動程式的作用

    a. 識别USB裝置

    b. 查找并安裝對應的裝置驅動程式

    c. 提供USB讀寫函數

USB驅動程式架構:

app:   

-------------------------------------------

          USB裝置驅動程式      // 知道資料含義

核心 --------------------------------------

          USB總線驅動程式      // 1. 識别, 2. 找到比對的裝置驅動, 3. 提供USB讀寫函數 (它不知道資料含義)

-------------------------------------------

           USB主機控制器

           UHCI OHCI EHCI

硬體        -----------

              USB裝置

UHCI: intel,     低速(1.5Mbps)/全速(12Mbps)

OHCI: microsoft  低速/全速

EHCI:            高速(480Mbps)

USB總線驅動程式的作用

1. 識别USB裝置

1.1 配置設定位址

1.2 并告訴USB裝置(set address)

1.3 發出指令擷取描述符

描述符的資訊可以在include\linux\usb\Ch9.h看到

2. 查找并安裝對應的裝置驅動程式

3. 提供USB讀寫函數

hub_probe

    INIT_WORK(&hub->events, hub_event);    

    hub_configure

        hub_activate    

            kick_hub_wq

                queue_work(hub_wq, &hub->events)

                    hub_event

                        port_event

                            hub_port_connect_change

                                hub_port_connect

                                    hub_port_init

                                        if (udev->speed < USB_SPEED_SUPER)

                                            dev_info(&udev->dev,"%s %s USB device number %d using %s\n",(udev->config) ? "reset" : "new", speed,devnum, driver_name);

USB裝置驅動的核心有兩點

1.裝置驅動的架構

  usb_device: usb總線驅動根據插入的usb裝置,1:從這個裝置中讀取裝置描述符(包括裝置類型是滑鼠還是鍵盤,還有存貯晶片的大小,廠家id,裝置id等等),

              2:并且總線驅動給插入的usb裝置配置設定編号(slave id),總線驅動根據1,2兩點資訊為該裝置構造了一個usb_device并且挂到usb_bus_type總線的

                 dev連結清單上。當我們驅動開發人員編寫好usb_driver并且挂到usb_bus_type的drv連結清單上時,兩者比對成功會調用drv的probe函數。

  usb_driver: 這個由裝置開發人員編寫,主要作用是處理:晶片的usb_controller傳給usb_bus的資料再傳給usb_driver的資料

2.處理usb總線驅動傳給裝置驅動的資料:usb_controller -> usb_bus_driver -> usb_driver(處理硬體通過usb總線傳上來的資料)

  最重要的是處理資料,這也是USB裝置驅動的核心工作。

  usb裝置是單向給usb_controller傳輸資料的,當usb裝置插上接口,核心會自動識别此裝置擷取裝置描述符,并且自動根據裝置描述符

  建立一個usb_device,當操作usb裝置時會産生各種資料(比如位移資料,坐标資料),并且将這些資料傳輸給usb總線驅動,usb總線驅動

  會有一個函數不斷查詢usb裝置的接口是否傳來資料,如果有就會将這些資料提取進總線并且傳遞給對應的裝置驅動進行資料處理。usb

  裝置不可以産生中斷,但是usb controller會根據裝置驅動傳給它的資料産生中斷,中斷到cpu。

使用滑鼠作為USB鍵盤的時候必須make menuconfig 去掉:

Device Drivers  --->

    HID support  --->

        USB HID support  --->

        < > USB HID transport layer//取消這個,否則不會調用我們自己寫的滑鼠驅動,而會調用核心裡自帶的滑鼠驅動

usb總線将來自usb裝置的資料寫到buf之後會産生一個中斷,每重新整理一次buf就産生一個中斷,這個中斷是usb controller産生的;

usb裝置驅動使用usb總線提供的讀寫函數讀寫usb裝置的核心是:端點描述符;

usb device傳輸過來的資料要先放在buf裡面,然後再從buf裡面提取出來填充urb,urb作為資料通訊的個單元(兩岸通訊的小船:drv和dev通訊的小船);

   urb必須通過buf來填充;

   usb_alloc_urb:申請urb;

   usb_fill_int_urb(少量資料用中斷)/usb_fill_bulk_urb(大量資料)/usb_fill_control_urb:填充urb:linux-5.8.5\include\linux\usb.h

   這些urb被填充之後需要送出給一個usb_device;

       dev->umk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;        //這點很重要

    //寫滑鼠驅動時,填充URB時,

      usb_fill_int_urb(dev->umk_urb, udev,

             usb_rcvintpipe(udev,endpoint->bEndpointAddress),

             dev->umk_urb_data, dev->buflen,

             usb_mouse_key_irq_trackpad, dev,endpoint->bInterval);    

    //因為忘記添加這兩句話,白忙活兩天

    dev->umk_urb->transfer_dma = dev->buf_phy_addr;

    dev->umk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //這點很重要

繼續閱讀