编写Linux usb 键盘驱动的笔记


编写linux usb 设备驱动参考:
   linux-5.8.5\Documentation\driver-api\usb\writing_usb_driver.rst
   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.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; //这点很重要

  
  
  
  
  
  
  
  
  
  

猜你喜欢

转载自blog.csdn.net/qq_43418840/article/details/118936647