十五、Linux驱动之USB鼠标驱动

2023-05-16

1. 如何编写USB鼠标驱动

    结合十四、Linux驱动之USB驱动分析中的分析,我们开始写一个USB鼠标驱动。
     USB的驱动可以分为3类:SoCUSB控制器的驱动,主机端USB设备的驱动,设备上的USB Gadget驱动,通常,对于USB这种标准化的设备,内核已经将主机控制器的驱动编写好了,设备上的Gadget驱动通常只运行固件程序而不是基于Linux, 所以驱动工程师的主要工作就是编写主机端的USB设备驱动。也就是说在开发板上接上一个USB鼠标后,我们不必关系USB鼠标是如何检测事件如何发送数据,只需要懂得在主机端(即对应开发板)如何接收USB鼠标发来的数据与知道接收到的数据的含义即可。

1.1 如何接收USB鼠标发来的数据

    linux 内核中的USB代码和所有的USB设备通讯使用称为USB请求块(USB request block,urb)URBUSB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构,非常类似于网络设备驱动中的sk_buff结构体。
    URB数据结构如下:

struct urb {
/* 私有的:只能由USB 核心和主机控制器访问的字段 */
struct kref kref; /*urb 引用计数 */
void *hcpriv; /* 主机控制器私有数据 */
atomic_t use_count; /* 并发传输计数 */
u8 reject; /* 传输将失败*/
int unlink; /* unlink 错误码 */
 /* 公共的: 可以被驱动使用的字段 */
 struct list_head urb_list; /* 链表头*/
struct usb_anchor *anchor;
 struct usb_device *dev; /* 关联的USB 设备 */
 struct usb_host_endpoint *ep;
unsigned int pipe; /* 管道信息 */
 int status; /* URB 的当前状态 */
 unsigned int transfer_flags; /* URB_SHORT_NOT_OK | ...*/
 void *transfer_buffer; /* 发送数据到设备或从设备接收数据的缓冲区 */
 dma_addr_t transfer_dma; /*用来以DMA 方式向设备传输数据的缓冲区 */
 int transfer_buffer_length;/*transfer_buffer 或transfer_dma 指向缓冲区的大小 */
 
 int actual_length; /* URB 结束后,发送或接收数据的实际长度 */
 unsigned char *setup_packet; /* 指向控制URB 的设置数据包的指针*/
 dma_addr_t setup_dma; /*控制URB 的设置数据包的DMA 缓冲区*/
 int start_frame; /*等时传输中用于设置或返回初始帧*/
 int number_of_packets; /*等时传输中等时缓冲区数量 */
 int interval; /* URB 被轮询到的时间间隔(对中断和等时urb 有效) */
 int error_count; /* 等时传输错误数量 */
 void *context; /* completion 函数上下文 */
 usb_complete_t complete; /* 当URB 被完全传输或发生错误时,被调用 */
 /*单个URB 一次可定义多个等时传输时,描述各个等时传输 */
 struct usb_iso_packet_descriptor iso_frame_desc[0];
};

1.1.1 创建URB

创建URB结构体的函数为:

struct urb *usb_alloc_urb(int iso_packets, int mem_flags);

参数:
    iso_packets:是这个urb 应当包含的等时数据包的数目,若为0 表示不创建等时数据包。
    mem_flags:参数是分配内存的标志,和kmalloc()函数的分配标志参数含义相同。如果分配成功,该函数返回一个urb 结构体指针,否则返回0。
    usb_alloc_urb()
的“反函数”为:

void usb_free_urb(struct urb *urb);    注销urb

1.1.2 填充URB

    在十四、Linux驱动之USB驱动分析中介绍了USB数据有四种传输类型,填充URB函数(初始化特定USB设备的特定端点)也分为4种:
(1) 中断类型(USB鼠标使用的就是该类型

void usb_fill_int_urb(struct urb *urb, 
                      struct usb_device *dev,
                      unsigned int pipe, 
                      void *transfer_buffer,
                      int buffer_length,
                      usb_complete_t complete,
                      void *context, 
                      int interval);

参数:
    urb:指向要被初始化的urb的指针;
    dev:指向这个urb要被发送到的USB设备;
    pipe:是这个urb要被发送到的USB设备的特定端点;
    transfer_buffer:是指向发送数据或接收数据的缓冲区的指针,和urb一样,它也不能是静态缓冲区,必须使用kmalloc()来分配;
    buffer_length:是transfer_buffer指针所指向缓冲区的大小;
    complete:指针指向当这个urb完成时被调用的完成处理函数;
    context:是完成处理函数的“上下文”;
    interval:是这个urb应当被调度的间隔。

(2) 批量类型

void usb_fill_bulk_urb(struct urb *urb, 
                       struct usb_device *dev,
                       unsigned int pipe, 
                       void *transfer_buffer,
                       int buffer_length, 
                       usb_complete_t complete,
                       void *context);

    除了没有对应于调度间隔的interval参数以外,该函数的参数和usb_fill_int_urb()函数的参数含义相同。上述函数参数中的pipe使用usb_sndbulkpipe()或者usb_rcvbulkpipe()函数来创建。
(3) 控制类型

void usb_fill_control_urb(struct urb *urb, 
                          struct usb_device *dev,
                          unsigned int pipe, 
                          unsigned char *setup_packet,
                          void *transfer_buffer, 
                          int buffer_length,
                          usb_complete_t complete, 
                          void *context);

    除了增加了新的setup_packet参数以外,该函数的参数和usb_fill_bulk_urb()函数的参数含义相同。setup_packet参数指向即将被发送到端点的设置数据包。
(4) 等时类型
   
针对等时型端点的urb,需要手动初始化。

1.1.3 提交URB

    在完成第上面两步的创建和初始化urb后,urb便可以提交给USB核心,通过usb_submit_urb()函数来完成,函数原型如下:

int usb_submit_urb(struct urb *urb, int mem_flags);

参数:
    urb:是指向urb 的指针;
    mem_flags:与传递给kmalloc()函数参数的意义相同,它用于告知USB 核心如何在此时分配内存缓冲区。

    usb_submit_urb()在原子上下文和进程上下文中都可以被调用,mem_flags变量需根据调用环境进行相应的设置,如下所示:
    GFP_ATOMIC:在中断处理函数、底半部、tasklet、定时器处理函数以及urb完成函数中,在调用者持有自旋锁或者读写锁时以及当驱动将current→state 修改为非 TASK_RUNNING 时,应使用此标志;
    GFP_NOIO:在存储设备的块I/O和错误处理路径中,应使用此标志;
    GFP_KERNEL:如果没有任何理由使用GFP_ATOMIC 和GFP_NOIO,就使用GFP_KERNEL
    在提交urbUSB核心后,直到完成函数被调用之前,不要访问urb中的任何成员。如果usb_submit_urb()调用成功,即urb的控制权被移交给USB核心,该函数返回0,否则返回错误号。

1.1.4 URB处理完成

    完成上面的步骤,当USB鼠标被按下,将会触发usb_fill_int_urb()填充URB函数中第6个参数传入的urb处理完成函数,在该函数里面对数据处理,就能得到USB鼠标的操作数据了。

1.2 接收到数据的含义

    接收到的数据存在usb_fill_int_urb()填充URB函数中的第4个参数传入的缓冲区(使用kmalloc()来分配)中,鼠标操作数据都存在该缓冲区内,缓冲区内数据含义如下:
    bit0表示鼠标左键, 该位为1表示按下, 为0表示松开
    bit1表示鼠标右键, 该位为1表示按下, 为0表示松开
    bit2表示鼠标中键, 该位为1表示按下, 为0表示松开

1.3 使用输入子系统进行上报事件

    在urb处理完成函数中使用输入子系统将事件上报

2. 编写代码

2.1 代码框架

代码的流程框架如下:

2.1.1 在入口函数中

    通过usb_register()函数注册usb_driver结构体。

2.1.2 在usb_driver的probe函数中

    1. 分配一个input_dev结构体
    2. 设置input_dev支持L、S、回车、3个按键事件
    3. 注册input_dev结构体
    4. 设置USB数据传输:
        4.1 通过usb_rcvintpipe()创建一个接收中断类型的端点管道,用来端点和数据缓冲区之间的连接
        4.2 通过usb_buffer_alloc()申请USB缓冲区
        4.3 申请并初始化urb结构体,urb:用来传输数据
        4.4 因为我们2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址
        4.5 使用usb_submit_urb()提交urb

2.1.3 在鼠标中断函数中

    1. 判断缓存区数据是否改变,若改变则上传鼠标事件
    2. 使用usb_submit_urb()提交urb

2.1.4 在usb_driver的disconnect函数中

    1. 通过usb_kill_urb()杀掉提交到内核中的urb
    2. 释放urb
    3. 释放USB缓存区
    4. 注销input_device,释放input_device

2.1.5 在出口函数中

    通过usb_deregister()函数注销usb_driver结构体。

2.2 编写代码

    USB鼠标驱动程序(鼠标按键模拟键盘按键l、s、回车键)如下:

/*
 * 参考drivers\hid\usbhid\usbmouse.c
 */
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>

static struct input_dev *uk_dev;
static char *usb_buf;
static dma_addr_t usb_buf_phys;
static int len;
static struct urb *uk_urb;

static struct usb_device_id usbmouse_as_key_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	{ }	/* Terminating entry */
};

static void usbmouse_as_key_irq(struct urb *urb)
{
	static unsigned char pre_val;

	/* USB鼠标数据含义
	 * data[1]: bit0-左键, 1-按下, 0-松开
	 *          bit1-右键, 1-按下, 0-松开
	 *          bit2-中键, 1-按下, 0-松开 
	 *
     */
	if ((pre_val & (1<<0)) != (usb_buf[1] & (1<<0)))
	{
		/* 左键发生了变化,上报键值KEY_L */
		input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[1] & (1<<0)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<1)) != (usb_buf[1] & (1<<1)))
	{
		/* 右键发生了变化,上报键值KEY_S*/
		input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[1] & (1<<1)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<2)) != (usb_buf[1] & (1<<2)))
	{
		/* 中键发生了变化,上报键值KEY_ENTER */
		input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[1] & (1<<2)) ? 1 : 0);
		input_sync(uk_dev);
	}
	
	pre_val = usb_buf[1];

	/* 重新提交urb */
	usb_submit_urb(uk_urb, GFP_KERNEL);
}

static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	int pipe;
	
	interface = intf->cur_altsetting;
	endpoint = &interface->endpoint[0].desc;

	/* a. 分配一个input_dev */
	uk_dev = input_allocate_device();
	
	/* b. 设置 */
	/* b.1 能产生哪类事件 */
	set_bit(EV_KEY, uk_dev->evbit);
	set_bit(EV_REP, uk_dev->evbit);
	
	/* b.2 能产生哪些事件 */
	set_bit(KEY_L, uk_dev->keybit);
	set_bit(KEY_S, uk_dev->keybit);
	set_bit(KEY_ENTER, uk_dev->keybit);
	
	/* c. 注册 */
	input_register_device(uk_dev);
	
	/* d. 硬件相关操作 */
	/* 数据传输3要素: 源,目的,长度 */
	/* 源: USB设备的某个端点 */
	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

	/* 长度: */
	len = endpoint->wMaxPacketSize;

	/* 目的: */
	usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);

	/* 使用"3要素" */
	/* 分配usb request block */
	uk_urb = usb_alloc_urb(0, GFP_KERNEL);
	/* 使用"3要素设置urb" */
	usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
	uk_urb->transfer_dma = usb_buf_phys;
	uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	/* 使用URB */
	usb_submit_urb(uk_urb, GFP_KERNEL);
	
	return 0;
}

static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
	struct usb_device *dev = interface_to_usbdev(intf);

	//printk("disconnect usbmouse!\n");
	usb_kill_urb(uk_urb);
	usb_free_urb(uk_urb);

	usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
	input_unregister_device(uk_dev);
	input_free_device(uk_dev);
}

/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
	.name		= "usbmouse_as_key",
	.probe		= usbmouse_as_key_probe,
	.disconnect	= usbmouse_as_key_disconnect,
	.id_table	= usbmouse_as_key_id_table,
};


static int usbmouse_as_key_init(void)
{
	/* 2. 注册 */
	usb_register(&usbmouse_as_key_driver);
	return 0;
}

static void usbmouse_as_key_exit(void)
{
	usb_deregister(&usbmouse_as_key_driver);	
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);

MODULE_LICENSE("GPL");

    Makefile代码如下:

KERN_DIR = /work/system/linux-2.6.22.6    //内核目录

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= usbmouse_as_key.o

3. 测试

内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10

3.1 配置内核

    1. 重新配置内核,去掉内核自带的usbmouse鼠标驱动。在linux-2.6.22.6内核目录下执行:
      make menuconfig
    2. 配置步骤如下:
    Device Drivers  --->
        HID Devices  --->
            < > USB Human Interface Device (full HID) support

3.2 重烧内核

    1. 编译内核与模块
      make uImage
    2. 将linux-2.6.22.6/arch/arm/boot下的uImage烧写到开发板,网络文件系统启动。

3.3 测试

    首先编译自己写的USB鼠标驱动。在驱动文件目录下执行:
      make
   
在开发板上执行:
      insmod usbmouse_as_key.ko:
方法1:
      cat /dev/tty1 (此时按下鼠标左键、右键、中键,串口就会有显示“ls”了)

方法2:
      hexdump /dev/event0    (/dev/event0为对应的usb驱动)


   

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

十五、Linux驱动之USB鼠标驱动 的相关文章

  • XAMPP Windows 上的 Php Cron 作业

    嗯 我是这个词的新手CRON 据我所知 这是一个Unix安排特定操作在定义的时间间隔后执行的概念 我需要运行一个php文件 每小时更新一次数据库 但我的困惑在于安排执行 我在用XAMPP用于 Windows 7 上的本地开发测试 我发现了什
  • 计算 TCP 重传次数

    我想知道在LINUX中是否有一种方法可以计算一个流中发生的TCP重传的次数 无论是在客户端还是服务器端 好像netstat s解决了我的目的
  • 如何在perl中使用O_ASYNC和fcntl?

    我想使用 O ASYNC 选项 当管道可以读取时 SIGIO 的处理程序将运行 但以下代码不起作用 任何人都可以帮助我吗 bin env perl use Fcntl SIG IO sub print catch SIGIO n my fl
  • 如何将 elf 解释器(ld-linux.so.2/ld-2.17.so)构建为静态库?

    如果我的问题不准确 我深表歉意 因为我没有太多 Linux 相关经验 我目前正在构建一个 Linux 从头开始 主要遵循 linuxfromscratch org 版本的指南 7 3 我遇到了以下问题 当我构建可执行文件时 获取一个称为 E
  • Bash - 比较 2 个文件列表及其 md5 校验和

    我有 2 个列表 其中包含带有 md5sum 检查的文件 即使文件相同 列表也具有不同的路径 我想检查每个文件的 md5 和 我们正在讨论数千个文件 这就是为什么我需要脚本来仅显示差异 第一个列表是普通列表 第二个列表是文件的当前状态 我想
  • pthread_self() 返回的线程 ID 与调用 gettid(2) 返回的内核线程 ID 不同

    这句话来自于pthread self 的手册页 http linux die net man 3 pthread self 那么 我应该根据什么来决定是否应该使用pthread self or gettid确定哪个线程正在运行该函数 两者都
  • 套接字发送调用被阻塞很长时间

    我每 10 秒在套接字上发送 2 个字节的应用程序数据 阻塞 但发送调用在下面的最后一个实例中被阻塞超过 40 秒 2012 06 13 12 02 46 653417 信息 发送前 2012 06 13 12 02 46 653457 信
  • Android 10 中没有设备筛选器的 USB_DEVICE_ATTACHED

    我正在开发一个 Android 应用程序 它在清单中为 BroadcastReceiver 注册了四个意图过滤器 这些都是 android hardware usb action USB DEVICE ATTACHED android ha
  • 为什么 call_usermodehelper 大多数时候都会失败?

    从内核模块中 我尝试使用 call usermodehelper 函数来执行可执行文件 sha1 该可执行文件将文件作为参数并将文件的 SHA1 哈希和写入另一个文件 名为输出 可执行文件完美运行 int result 1 name hom
  • Swift 上的 USB 连接委托

    Swift 中是否有一个代表可以让我的班级知道何时通过计算机的 USB 插入新设备 我想知道我的程序何时可以使用新设备 Eric Aya 的答案已经相当不错了 但这里有一个 Swift 3 的改编 我把大部分丑陋的东西包裹在一个USBWat
  • 在用户程序中使用 或在驱动程序模块代码中使用 ...这有关系吗?

    我正在开发一个设备驱动程序模块和关联的用户库来处理ioctl 来电 该库获取相关信息并将其放入一个结构中 该结构被传递到驱动程序模块中并在那里解压 然后进行处理 我省略了很多步骤 但这就是总体思路 一些数据通过结构体传递ioctl is u
  • Apache LOG:子进程 pid xxxx 退出信号分段错误 (11)

    Apache PHP Mysql Linux 注意 子进程 pid 23145 退出信号分段错误 11 tmp 中可能存在 coredump 但 tmp下没有找到任何东西 我怎样才能找到错误 PHP 代码中函数的无限循环导致了此错误
  • 如何指定配置脚本的包含目录

    我的工作场所有一个 Linux 系统 其中包含相当旧的软件包 并且没有 root 访问权限 我正在从源代码编译我需要的包 prefix somewhere in homedir 我的问题是我只是不知道如何说服配置在特定目录中查找头文件 源码
  • 如何通过不同的接口路由 TCP/IP 响应?

    我有两台机器 每台机器都有两个有效的网络接口 一个以太网接口eth0和 tun tap 接口gr0 目标是使用接口在机器 A 上启动 TCP 连接gr0但然后让机器 B 的响应 ACK 等 通过以太网接口返回 eth0 因此 机器 A 发出
  • Unix 中的访问时间是多少

    我想知道访问时间是多少 我在网上搜索但得到了相同的定义 读 被改变 我知道与touch我们可以改变它 谁能用一个例子来解释一下它是如何改变的 有没有办法在unix中获取创建日期 时间 stat结构 The stat 2 结构跟踪所有文件日期
  • 为什么使用signalfd无法捕获SIGSEGV?

    我的系统是ubuntu 12 04 我将示例修改为man 2 signalfd 并添加sigaddset mask SIGSEGV 在示例中 但我无法得到输出SIGSEGV被生成 这是一个错误吗glibc 源代码片段如下 sigemptys
  • C++ Linux GCC 应用程序中的 GUID

    我有很多服务器运行这个 Linux 应用程序 我希望他们能够生成一个碰撞概率较低的 GUID 我确信我可以从 dev urandom 中提取 128 个字节 这可能没问题 但是有没有一种简单易用的方法来生成与 Win32 更等效的 GUID
  • 我可以在 Ubuntu 上使用 Homebrew 吗?

    我只是尝试使用 Homebrew 和 Linuxbrew 在我的 Ubuntu 服务器上安装软件包 但都失败了 这就是我尝试安装它们的方法 sudo apt get install build essential curl git m4 r
  • 通过名称获取进程ID

    我想在 Linux 下获得一个给定其名称的进程 ID 有没有一种简单的方法可以做到这一点 我还没有在 C 上找到任何可以轻松使用的东西 如果追求 易于使用 char buf 512 FILE cmd pipe popen pidof s p
  • 如何在 Ubuntu/Linux 发行版中安装 Tesseract-OCR 3.03?

    我和一个朋友有兴趣为 CV 项目训练 tesseract OCR 引擎 我们尝试使用一些包装器 例如 PyTesser 和 pyocr 但结果目前不如我们需要的那么准确 因此 我们希望尝试训练超立方体以更好地实现我们的目的 即识别食品标签上

随机推荐