linux驱动移植-USB鼠标接口驱动
一、 知识回顾
在前面的几篇博客中,我们已经介绍了如下内容:
- usb子系统的初始化;
- usb主机控制器驱动的创建;
- 根hub设备的创建和注册,匹配hub接口驱动hub_driver,并执行hub_probe;
- 开启根hub端口监测,usb主机控制器通过定时轮询判断根hub端口是否有usb设备插入;
- 如果有新设备接入触发hub_irq函数,将会为新的usb设备分配usb_device结构,并注册到内核,匹配对应的驱动;
然后我们再来回顾一下根hub端口监测的细节部分:
- 根hub可以监测到usb设备的插入,并为其创建usb_device设备,匹配通用usb驱动程序generic_probe;
- 在generic_probe中遍历usb_device每一个接口,为其匹配usb接口驱动;
内核已经将usb主机控制器的驱动程序编写好了,因此我们不需要直接和usb主机控制器打交道了。
那问题来了,在之前我们介绍过usb接口驱动负责实现usb设备的功能,那接口驱动从哪里来呢?当然是由驱动开发工程师实现了。
由于usb核心层向上为usb设备驱动提供可编程接口,向下为usb主机控制器驱动提供编程接口,因此我们编写的usb接口驱动只需要使用usb核心层提供的usb设备读写函数即可。
二、接收USB鼠标数据准备工作
在前面我们已经介绍过,linux内核中usb设备驱动通过urb和所有的usb设备进行通信,关于struct urb结构体的定义也已经在linux驱动移植-usb驱动基础介绍过。
写数据:usb设备驱动发送urb请求给usb设备,usb设备不需要返回数据;
读数据:usb设备驱动发送urb请求给usb设备,usb设备需要返回数据;
urb以一种异步的方式同一个特定usb设备的特定端点发送或接受数据。一个 usb设备驱动可根据驱动的需要,分配多个urb给一个端点或重用单个urb给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。
一个urb的典型生命周期如下:
- 被创建;
- 被分配给一个特定的usb设备的特定端点;
- 被提交给usb核心;
- 被usb核心提交给usb设备的usb主机控制器驱动;
- 被usb主机控制器驱动处理,并传送给usb设备;
- 以上操作完成后,usb主机控制器驱动通知usb设备驱动;
2.1 创建urb
创建urb结构体的函数为usb_alloc_urb,定义在drivers/usb/core/urb.c:
/** * usb_alloc_urb - creates a new urb for a USB driver to use * @iso_packets: number of iso packets for this urb * @mem_flags: the type of memory to allocate, see kmalloc() for a list of * valid options for this. * * Creates an urb for the USB driver to use, initializes a few internal * structures, increments the usage counter, and returns a pointer to it. * * If the driver want to use this urb for interrupt, control, or bulk * endpoints, pass '0' as the number of iso packets. * * The driver must call usb_free_urb() when it is finished with the urb. * * Return: A pointer to the new urb, or %NULL if no memory is available. */ struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags) { struct urb *urb; urb = kmalloc(struct_size(urb, iso_frame_desc, iso_packets), mem_flags); if (!urb) return NULL; usb_init_urb(urb); return urb; }
参数:
- iso_packets:是这个urb 应当包含的等时数据包的数目,若为0表示不创建等时数据包;
- mem_flags:参数是分配内存的标志,和kmalloc函数的分配标志参数含义相同。如果分配成功,该函数返回一个urb 结构体指针,否则返回0;
2.2 释放urb
usb_free_urb和usb_alloc_urb是相反的:
/** * usb_free_urb - frees the memory used by a urb when all users of it are finished * @urb: pointer to the urb to free, may be NULL * * Must be called when a user of a urb is finished with it. When the last user * of the urb calls this function, the memory of the urb is freed. * * Note: The transfer buffer associated with the urb is not freed unless the * URB_FREE_BUFFER transfer flag is set. */ void usb_free_urb(struct urb *urb) { if (urb) kref_put(&urb->kref, urb_destroy); }
2.3 填充urb
usb数据有四种传输类型:
- 控制传输:控制传输用于配置设备、获取设备信息、发送命令到设备、获取设备的状态。每个USB设备都有端点0的控制端点,当USB设备插入到USB主机拓扑网络中时,USB主机就通过端点0与USB设备通信,对USB设备进行配置,便于后续的数据传输。USB协议保证控制传输有足够的带宽。控制传输可靠,时间有保证,但传输的数据量不大。如USB设备的枚举过程就采用的是控制传输;
- 中断传输:当USB主机请求USB设备传输数据时,中断传输以一个固定的速率传送少量的数据。中断端点的数据传输方式为中断传输,数据传输可靠,实时性高,这里的中断并不是USB设备产生中断,而是USB主机每隔一个固定的时间主动查询USB设备是否有数据要传输,以轮询的方式提高实时性。如USB鼠标采用的是中断传输;
- 批量传输:批量传输用于传输大量数据。USB协议不保证这些数据传输可以在特定的时间内完成,但保证数据的准确性。如果总线上的带宽不足以发送整个批量包,则将数据拆分为多个包传输。批量传输数据可靠,但实时性较低。如USB硬盘、打印机等设备就采用的是批量传输方式;
- 等时传输:等时传输也可以传输大量数据,但数据的可靠性无法保证。采用等时传输的USB设备更加注重保持一个恒定的数据传输速度,对数据的可靠性要求不高。如USB摄像头就使用的是等时传输方式;
填充urb函数(初始化特定usb设备的特定端点)也分为4种。
2.3.1 中断类型(usb鼠标使用的就是该类型)
/** * usb_fill_int_urb - macro to help initialize a interrupt urb * @urb: pointer to the urb to initialize. * @dev: pointer to the struct usb_device for this urb. * @pipe: the endpoint pipe * @transfer_buffer: pointer to the transfer buffer * @buffer_length: length of the transfer buffer * @complete_fn: pointer to the usb_complete_t function * @context: what to set the urb context to. * @interval: what to set the urb interval to, encoded like * the endpoint descriptor's bInterval value. * * Initializes a interrupt urb with the proper information needed to submit * it to a device. * * Note that High Speed and SuperSpeed(+) interrupt endpoints use a logarithmic * encoding of the endpoint interval, and express polling intervals in * microframes (eight per millisecond) rather than in frames (one per * millisecond). * * Wireless USB also uses the logarithmic encoding, but specifies it in units of * 128us instead of 125us. For Wireless USB devices, the interval is passed * through to the host controller, rather than being translated into microframe * units. */ static inline 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_fn, void *context, int interval) { urb->dev = dev; urb->pipe = pipe; urb->transfer_buffer = transfer_buffer; urb->transfer_buffer_length = buffer_length; urb->complete = complete_fn; urb->context = context; if (dev->speed == USB_SPEED_HIGH || dev->speed >= USB_SPEED_SUPER) { /* make sure interval is within allowed range */ interval = clamp(interval, 1, 16); urb->interval = 1 << (interval - 1); } else { urb->interval = interval; } urb->start_frame = -1; }
usb_fill_int_urb定义在include/linux/usb中,参数如下:
- urb:指向要被初始化的urb的指针;
- dev:指向这个urb要被发送到的usb设备;
- pipe:是这个urb要被发送到的usb设备的特定端点;
- transfer_buffer:是指向发送数据或接收数据的缓冲区的指针,和urb一样,它也不能是静态缓冲区,必须使用kmalloc()来分配;
- buffer_length:是transfer_buffer指针所指向缓冲区的大小;
- complete:指针指向当这个urb完成时被调用的完成处理函数;
- context:是完成处理函数的“上下文;
- interval:是这个urb应当被调度的间隔。
2.3.2 批量类型
/** * usb_fill_bulk_urb - macro to help initialize a bulk urb * @urb: pointer to the urb to initialize. * @dev: pointer to the struct usb_device for this urb. * @pipe: the endpoint pipe * @transfer_buffer: pointer to the transfer buffer * @buffer_length: length of the transfer buffer * @complete_fn: pointer to the usb_complete_t function * @context: what to set the urb context to. * * Initializes a bulk urb with the proper information needed to submit it * to a device. */ static inline 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_fn, void *context) { urb->dev = dev; urb->pipe = pipe; urb->transfer_buffer = transfer_buffer; urb->transfer_buffer_length = buffer_length; urb->complete = complete_fn; urb->context = context; }
usb_fill_bulk_urb定义在include/linux/usb中, 除了没有对应于调度间隔的interval参数以外,该函数的参数和usb_fill_int_urb()函数的参数含义相同。上述函数参数中的pipe使用usb_sndbulkpipe()或者usb_rcvbulkpipe()函数来创建。
2.3.3 控制类型
/** * usb_fill_control_urb - initializes a control urb * @urb: pointer to the urb to initialize. * @dev: pointer to the struct usb_device for this urb. * @pipe: the endpoint pape * @setup_packet: pointer to the setup_packet buffer * @transfer_buffer: pointer to the transfer buffer * @buffer_length: length of the transfer buffer * @complete_fn: pointer to the usb_complete_t function * @context: what to set the urb context to. * * Initializes a control urb with the proper information needed to submit * it to a device. */ static inline void usb_fill_control_urb(struct urb *urb, struct usb_device *dev, unsigned int fer_buf ipe, unsigned char *setup_packet, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context) { urb->dev = dev; urb->pipe = pipe; urb->setup_packet = setup_packet; urb->transfer_buffer = transfer_buffer; urb->transfer_buffer_length = buffer_length; urb->complete = complete_fn; urb->context = context; }
usb_fill_control_urb定义在include/linux/usb中, 除了增加了新的setup_packet参数以外,该函数的参数和usb_fill_bulk_urb()函数的参数含义相同。setup_packet参数指向即将被发送到端点的设置数据包。
2.3.4 等时类型
针对等时型端点的urb,需要手动初始化。
2.4 pipe初始化
pipe = usb_rcvintpipe(dev,endpoint);
通过usb_rcvintpipe函数创建一个接受接收(rcv)中断类型的端点管道(pipe),用来端点和数据缓冲区之间的连接;参数如下:
- dev: usb_device设备结构体;
- endpoint:为端点描述符的成员endpoint->bEndpointAddress;
对于控制类型的端点管道使用: usb_sndctrlpipe/usb_rcvctrlpipe;
对于实时类型的端点管道使用: usb_sndisocpipe/usb_sndisocpipe;
对于批量类型的端点管道使用: usb_sndbulkpipe/usb_rcvbulkpipe:
这些宏定义在include/linux/usb.h文件中:
/* * For various legacy reasons, Linux has a small cookie that's paired with * a struct usb_device to identify an endpoint queue. Queue characteristics * are defined by the endpoint's descriptor. This cookie is called a "pipe", * an unsigned int encoded as: * * - direction: bit 7 (0 = Host-to-Device [Out], * 1 = Device-to-Host [In] ... * like endpoint bEndpointAddress) * - device address: bits 8-14 ... bit positions known to uhci-hcd * - endpoint: bits 15-18 ... bit positions known to uhci-hcd * - pipe type: bits 30-31 (00 = isochronous, 01 = interrupt, * 10 = control, 11 = bulk) * * Given the device address and endpoint descriptor, pipes are redundant. */ /* NOTE: these are not the standard USB_ENDPOINT_XFER_* values!! */ /* (yet ... they're the values used by usbfs) */ #define PIPE_ISOCHRONOUS 0 #define PIPE_INTERRUPT 1 #define PIPE_CONTROL 2 #define PIPE_BULK 3 #define usb_pipein(pipe) ((pipe) & USB_DIR_IN) #define usb_pipeout(pipe) (!usb_pipein(pipe)) #define usb_pipedevice(pipe) (((pipe) >> 8) & 0x7f) #define usb_pipeendpoint(pipe) (((pipe) >> 15) & 0xf) #define usb_pipetype(pipe) (((pipe) >> 30) & 3) #define usb_pipeisoc(pipe) (usb_pipetype((pipe)) == PIPE_ISOCHRONOUS) #define usb_pipeint(pipe) (usb_pipetype((pipe)) == PIPE_INTERRUPT) #define usb_pipecontrol(pipe) (usb_pipetype((pipe)) == PIPE_CONTROL) #define usb_pipebulk(pipe) (usb_pipetype((pipe)) == PIPE_BULK) static inline unsigned int __create_pipe(struct usb_device *dev, unsigned int endpoint) { return (dev->devnum << 8) | (endpoint << 15); } /* Create various pipes... */ #define usb_sndctrlpipe(dev, endpoint) \ ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint)) #define usb_rcvctrlpipe(dev, endpoint) \ ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) #define usb_sndisocpipe(dev, endpoint) \ ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint)) #define usb_rcvisocpipe(dev, endpoint) \ ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) #define usb_sndbulkpipe(dev, endpoint) \ ((PIPE_BULK << 30) | __create_pipe(dev, endpoint)) #define usb_rcvbulkpipe(dev, endpoint) \ ((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) #define usb_sndintpipe(dev, endpoint) \ ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint)) #define usb_rcvintpipe(dev, endpoint) \ ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
这里以usb_rcvintpipe为例,假设usb设备地址dev->devnum=1,端点号endpoint=1,那么值最终为:1<<30 | 1<< 8 | 1<< 15 | 1<<7。
static inline __u16 usb_maxpacket(struct usb_device *udev, int pipe, int is_out) { struct usb_host_endpoint *ep; unsigned epnum = usb_pipeendpoint(pipe); if (is_out) { WARN_ON(usb_pipein(pipe)); ep = udev->ep_out[epnum]; } else { WARN_ON(usb_pipeout(pipe)); ep = udev->ep_in[epnum]; } if (!ep) return 0; /* NOTE: only 0x07ff bits are for packet size... */ return usb_endpoint_maxp(&ep->desc); }
而usb_endpoint_maxp函数就是通过端点描述符的wMaxPacketSize成员获取最大包大小:
/** * usb_endpoint_maxp - get endpoint's max packet size * @epd: endpoint to be checked * * Returns @epd's max packet bits [10:0] */ static inline int usb_endpoint_maxp(const struct usb_endpoint_descriptor *epd) { return __le16_to_cpu(epd->wMaxPacketSize) & USB_ENDPOINT_MAXP_MASK; }
2.5 数据缓冲区初始化
char *usb_alloc_coherent(struct usb_device *dev,size_t size,gfp_t mem_flags,dma_addr_t *dma);
分配一个usb缓冲区,该缓存区的物理地址会与虚拟地址的数据一致,分配成功返回一个char型缓冲区虚拟地址;
- *dev: usb_device设备结构体;
- size:分配的缓冲区大小,这里填端点描述符的成员endpoint->wMaxPacketSize //端点最大包长
- mem_flags:分配内存的参数,这里填GFP_ATOMIC,表示从不睡眠;
- dma:分配成功则会返回一个DMA缓冲区物理地址;
void usb_free_coherent(struct usb_device *dev,size_t size,void *addr,dma_addr_t dma);
注销分配的usb缓冲区,在usb_driver的disconnect成员函数中使用:
- addr:要注销的缓冲区虚拟地址;
- dma: 要注销的DMA缓冲区虚拟地址;
2.6 提交urb
在完成第上面两步的创建和初始化urb后,urb便可以提交给usb核心,通过usb_submit_urb()函数来完成,函数定义在drivers/usb/core/urb.c:

/** * usb_submit_urb - issue an asynchronous transfer request for an endpoint * @urb: pointer to the urb describing the request * @mem_flags: the type of memory to allocate, see kmalloc() for a list * of valid options for this. * * This submits a transfer request, and transfers control of the URB * describing that request to the USB subsystem. Request completion will * be indicated later, asynchronously, by calling the completion handler. * The three types of completion are success, error, and unlink * (a software-induced fault, also called "request cancellation"). * * URBs may be submitted in interrupt context. * * The caller must have correctly initialized the URB before submitting * it. Functions such as usb_fill_bulk_urb() and usb_fill_control_urb() are * available to ensure that most fields are correctly initialized, for * the particular kind of transfer, although they will not initialize * any transfer flags. * * If the submission is successful, the complete() callback from the URB * will be called exactly once, when the USB core and Host Controller Driver * (HCD) are finished with the URB. When the completion function is called, * control of the URB is returned to the device driver which issued the * request. The completion handler may then immediately free or reuse that * URB. * * With few exceptions, USB device drivers should never access URB fields * provided by usbcore or the HCD until its complete() is called. * The exceptions relate to periodic transfer scheduling. For both * interrupt and isochronous urbs, as part of successful URB submission * urb->interval is modified to reflect the actual transfer period used * (normally some power of two units). And for isochronous urbs, * urb->start_frame is modified to reflect when the URB's transfers were * scheduled to start. * * Not all isochronous transfer scheduling policies will work, but most * host controller drivers should easily handle ISO queues going from now * until 10-200 msec into the future. Drivers should try to keep at * least one or two msec of data in the queue; many controllers require * that new transfers start at least 1 msec in the future when they are * added. If the driver is unable to keep up and the queue empties out, * the behavior for new submissions is governed by the URB_ISO_ASAP flag. * If the flag is set, or if the queue is idle, then the URB is always * assigned to the first available (and not yet expired) slot in the * endpoint's schedule. If the flag is not set and the queue is active * then the URB is always assigned to the next slot in the schedule * following the end of the endpoint's previous URB, even if that slot is * in the past. When a packet is assigned in this way to a slot that has * already expired, the packet is not transmitted and the corresponding * usb_iso_packet_descriptor's status field will return -EXDEV. If this * would happen to all the packets in the URB, submission fails with a * -EXDEV error code. * * For control endpoints, the synchronous usb_control_msg() call is * often used (in non-interrupt context) instead of this call. * That is often used through convenience wrappers, for the requests * that are standardized in the USB 2.0 specification. For bulk * endpoints, a synchronous usb_bulk_msg() call is available. * * Return: * 0 on successful submissions. A negative error number otherwise. * * Request Queuing: * * URBs may be submitted to endpoints before previous ones complete, to * minimize the impact of interrupt latencies and system overhead on data * throughput. With that queuing policy, an endpoint's queue would never * be empty. This is required for continuous isochronous data streams, * and may also be required for some kinds of interrupt transfers. Such * queuing also maximizes bandwidth utilization by letting USB controllers * start work on later requests before driver software has finished the * completion processing for earlier (successful) requests. * As of Linux 2.6, all USB endpoint transfer queues support depths greater * than one. This was previously a HCD-specific behavior, except for ISO * transfers. Non-isochronous endpoint queues are inactive during cleanup * after faults (transfer errors or cancellation). * * Reserved Bandwidth Transfers: * * Periodic transfers (interrupt or isochronous) are performed repeatedly, * using the interval specified in the urb. Submitting the first urb to * the endpoint reserves the bandwidth necessary to make those transfers. * If the USB subsystem can't allocate sufficient bandwidth to perform * the periodic request, submitting such a periodic request should fail. * * For devices under xHCI, the bandwidth is reserved at configuration time, or * when the alt setting is selected. If there is not enough bus bandwidth, the * configuration/alt setting request will fail. Therefore, submissions to * periodic endpoints on devices under xHCI should never fail due to bandwidth * constraints. * * Device drivers must explicitly request that repetition, by ensuring that * some URB is always on the endpoint's queue (except possibly for short * periods during completion callbacks). When there is no longer an urb * queued, the endpoint's bandwidth reservation is canceled. This means * drivers can use their completion handlers to ensure they keep bandwidth * they need, by reinitializing and resubmitting the just-completed urb * until the driver longer needs that periodic bandwidth. * * Memory Flags: * * The general rules for how to decide which mem_flags to use * are the same as for kmalloc. There are four * different possible values; GFP_KERNEL, GFP_NOFS, GFP_NOIO and * GFP_ATOMIC. * * GFP_NOFS is not ever used, as it has not been implemented yet. * * GFP_ATOMIC is used when * (a) you are inside a completion handler, an interrupt, bottom half, * tasklet or timer, or * (b) you are holding a spinlock or rwlock (does not apply to * semaphores), or * (c) current->state != TASK_RUNNING, this is the case only after * you've changed it. * * GFP_NOIO is used in the block io path and error handling of storage * devices. * * All other situations use GFP_KERNEL. * * Some more specific rules for mem_flags can be inferred, such as * (1) start_xmit, timeout, and receive methods of network drivers must * use GFP_ATOMIC (they are called with a spinlock held); * (2) queuecommand methods of scsi drivers must use GFP_ATOMIC (also * called with a spinlock held); * (3) If you use a kernel thread with a network driver you must use * GFP_NOIO, unless (b) or (c) apply; * (4) after you have done a down() you can use GFP_KERNEL, unless (b) or (c) * apply or your are in a storage driver's block io path; * (5) USB probe and disconnect can use GFP_KERNEL unless (b) or (c) apply; and * (6) changing firmware on a running storage or net device uses * GFP_NOIO, unless b) or c) apply * */ int usb_submit_urb(struct urb *urb, gfp_t mem_flags) { int xfertype, max; struct usb_device *dev; struct usb_host_endpoint *ep; int is_out; unsigned int allowed; if (!urb || !urb->complete) return -EINVAL; if (urb->hcpriv) { WARN_ONCE(1, "URB %pK submitted while active\n", urb); return -EBUSY; } dev = urb->dev; if ((!dev) || (dev->state < USB_STATE_UNAUTHENTICATED)) return -ENODEV; /* For now, get the endpoint from the pipe. Eventually drivers * will be required to set urb->ep directly and we will eliminate * urb->pipe. */ ep = usb_pipe_endpoint(dev, urb->pipe); if (!ep) return -ENOENT; urb->ep = ep; urb->status = -EINPROGRESS; urb->actual_length = 0; /* Lots of sanity checks, so HCDs can rely on clean data * and don't need to duplicate tests */ xfertype = usb_endpoint_type(&ep->desc); if (xfertype == USB_ENDPOINT_XFER_CONTROL) { struct usb_ctrlrequest *setup = (struct usb_ctrlrequest *) urb->setup_packet; if (!setup) return -ENOEXEC; is_out = !(setup->bRequestType & USB_DIR_IN) || !setup->wLength; } else { is_out = usb_endpoint_dir_out(&ep->desc); } /* Clear the internal flags and cache the direction for later use */ urb->transfer_flags &= ~(URB_DIR_MASK | URB_DMA_MAP_SINGLE | URB_DMA_MAP_PAGE | URB_DMA_MAP_SG | URB_MAP_LOCAL | URB_SETUP_MAP_SINGLE | URB_SETUP_MAP_LOCAL | URB_DMA_SG_COMBINED); urb->transfer_flags |= (is_out ? URB_DIR_OUT : URB_DIR_IN); if (xfertype != USB_ENDPOINT_XFER_CONTROL && dev->state < USB_STATE_CONFIGURED) return -ENODEV; max = usb_endpoint_maxp(&ep->desc); if (max <= 0) { dev_dbg(&dev->dev, "bogus endpoint ep%d%s in %s (bad maxpacket %d)\n", usb_endpoint_num(&ep->desc), is_out ? "out" : "in", __func__, max); return -EMSGSIZE; } /* periodic transfers limit size per frame/uframe, * but drivers only control those sizes for ISO. * while we're checking, initialize return status. */ if (xfertype == USB_ENDPOINT_XFER_ISOC) { int n, len; /* SuperSpeed isoc endpoints have up to 16 bursts of up to * 3 packets each */ if (dev->speed >= USB_SPEED_SUPER) { int burst = 1 + ep->ss_ep_comp.bMaxBurst; int mult = USB_SS_MULT(ep->ss_ep_comp.bmAttributes); max *= burst; max *= mult; } if (dev->speed == USB_SPEED_SUPER_PLUS && USB_SS_SSP_ISOC_COMP(ep->ss_ep_comp.bmAttributes)) { struct usb_ssp_isoc_ep_comp_descriptor *isoc_ep_comp; isoc_ep_comp = &ep->ssp_isoc_ep_comp; max = le32_to_cpu(isoc_ep_comp->dwBytesPerInterval); } /* "high bandwidth" mode, 1-3 packets/uframe? */ if (dev->speed == USB_SPEED_HIGH) max *= usb_endpoint_maxp_mult(&ep->desc); if (urb->number_of_packets <= 0) return -EINVAL; for (n = 0; n < urb->number_of_packets; n++) { len = urb->iso_frame_desc[n].length; if (len < 0 || len > max) return -EMSGSIZE; urb->iso_frame_desc[n].status = -EXDEV; urb->iso_frame_desc[n].actual_length = 0; } } else if (urb->num_sgs && !urb->dev->bus->no_sg_constraint && dev->speed != USB_SPEED_WIRELESS) { struct scatterlist *sg; int i; for_each_sg(urb->sg, sg, urb->num_sgs - 1, i) if (sg->length % max) return -EINVAL; } /* the I/O buffer must be mapped/unmapped, except when length=0 */ if (urb->transfer_buffer_length > INT_MAX) return -EMSGSIZE; /* * stuff that drivers shouldn't do, but which shouldn't * cause problems in HCDs if they get it wrong. */ /* Check that the pipe's type matches the endpoint's type */ if (usb_urb_ep_type_check(urb)) dev_WARN(&dev->dev, "BOGUS urb xfer, pipe %x != type %x\n", usb_pipetype(urb->pipe), pipetypes[xfertype]); /* Check against a simple/standard policy */ allowed = (URB_NO_TRANSFER_DMA_MAP | URB_NO_INTERRUPT | URB_DIR_MASK | URB_FREE_BUFFER); switch (xfertype) { case USB_ENDPOINT_XFER_BULK: case USB_ENDPOINT_XFER_INT: if (is_out) allowed |= URB_ZERO_PACKET; /* FALLTHROUGH */ default: /* all non-iso endpoints */ if (!is_out) allowed |= URB_SHORT_NOT_OK; break; case USB_ENDPOINT_XFER_ISOC: allowed |= URB_ISO_ASAP; break; } allowed &= urb->transfer_flags; /* warn if submitter gave bogus flags */ if (allowed != urb->transfer_flags) dev_WARN(&dev->dev, "BOGUS urb flags, %x --> %x\n", urb->transfer_flags, allowed); /* * Force periodic transfer intervals to be legal values that are * a power of two (so HCDs don't need to). * * FIXME want bus->{intr,iso}_sched_horizon values here. Each HC * supports different values... this uses EHCI/UHCI defaults (and * EHCI can use smaller non-default values). */ switch (xfertype) { case USB_ENDPOINT_XFER_ISOC: case USB_ENDPOINT_XFER_INT: /* too small? */ switch (dev->speed) { case USB_SPEED_WIRELESS: if ((urb->interval < 6) && (xfertype == USB_ENDPOINT_XFER_INT)) return -EINVAL; /* fall through */ default: if (urb->interval <= 0) return -EINVAL; break; } /* too big? */ switch (dev->speed) { case USB_SPEED_SUPER_PLUS: case USB_SPEED_SUPER: /* units are 125us */ /* Handle up to 2^(16-1) microframes */ if (urb->interval > (1 << 15)) return -EINVAL; max = 1 << 15; break; case USB_SPEED_WIRELESS: if (urb->interval > 16) return -EINVAL; break; case USB_SPEED_HIGH: /* units are microframes */ /* NOTE usb handles 2^15 */ if (urb->interval > (1024 * 8)) urb->interval = 1024 * 8; max = 1024 * 8; break; case USB_SPEED_FULL: /* units are frames/msec */ case USB_SPEED_LOW: if (xfertype == USB_ENDPOINT_XFER_INT) { if (urb->interval > 255) return -EINVAL; /* NOTE ohci only handles up to 32 */ max = 128; } else { if (urb->interval > 1024) urb->interval = 1024; /* NOTE usb and ohci handle up to 2^15 */ max = 1024; } break; default: return -EINVAL; } if (dev->speed != USB_SPEED_WIRELESS) { /* Round down to a power of 2, no more than max */ urb->interval = min(max, 1 << ilog2(urb->interval)); } } return usb_hcd_submit_urb(urb, 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;
- 在提交urb到USB核心后,直到完成函数被调用之前,不要访问urb中的任何成员。如果usb_submit_urb()调用成功,即urb的控制权被移交给usb核心,该函数返回0,否则返回错误号;
2.7 urb处理完成
完成上面的步骤,当usb鼠标被按下,将会触发usb_fill_int_urb()填充urb函数中第6个参数传入的urb处理完成函数,在该函数里面对数据处理,就能得到usb鼠标的操作数据了。
2.8 接收到数据的含义
接收到的数据存在usb_fill_int_urb填充urb函数中的第4个参数传入的缓冲区中,鼠标操作数据都存在该缓冲区内,每次传输的数据为4个字节,BYTE0、BYTE1、BYTE2、BYTE3,定义分别是:
BYTE0 | BYTE1 | BYTE2 | BYTE3 | |
[7] |
X坐标的坐标变化量 负数表示左移 正数表示右移 |
Y坐标变化量 负数表示向上移 正数表示向下移 |
滚轮变化 | |
[6] | ||||
[5] | ||||
[4] | 1:表示EXTRA键按下 0:表示松开 | |||
[3] | 1:表示SIDE键按下 0:表示松开 | |||
[2] | 1:表示中键按下 0:表示松开 | |||
[1] | 1:表示右键按下 0:表示松开 | |||
[0] | 1:表示左键按下 0:表示松开 |
2.9 使用输入子系统上报事件
在urb处理完成函数中使用输入子系统将事件上报。
2.10 usb鼠标和接口驱动匹配
我们知道usb总线对应的match函数是 usb_device_match函数,对于usb接口,先用is_usb_device_driver来进行判断,如果不是usb设备驱动则继续判断,否则退出;然后再通过usb_match_id函数来判断接口和接口驱动中的usb_device_id是否匹配。
static int usb_device_match(struct device *dev, struct device_driver *drv) { /* devices and interfaces are handled separately */ if (is_usb_device(dev)) { /* interface drivers never match devices */ if (!is_usb_device_driver(drv)) return 0; /* TODO: Add real matching code */ return 1; } else if (is_usb_interface(dev)) { struct usb_interface *intf; struct usb_driver *usb_drv; const struct usb_device_id *id; /* device drivers never match interfaces */ if (is_usb_device_driver(drv)) return 0; intf = to_usb_interface(dev); usb_drv = to_usb_driver(drv); id = usb_match_id(intf, usb_drv->id_table); if (id) return 1; id = usb_match_dynamic_id(intf, usb_drv); if (id) return 1; } return 0; }
其中usb_match_id函数的一个参数为usb接口,第二个参数为struct usb_device_id类型,那usb接口驱动usb_drv的成员id_table应该怎么设置呢。
我们参考drivers/hid/usbhid/usbmouse.c(内核自带的usb鼠标驱动)是如何使用的,如下:
static const struct usb_device_id usb_mouse_id_table[] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, usb_mouse_id_table); static struct usb_driver usb_mouse_driver = { .name = "usbmouse", .probe = usb_mouse_probe, .disconnect = usb_mouse_disconnect, .id_table = usb_mouse_id_table, };
我们发现数组usb_mouse_id_table是通过USB_INTERFACE_INFO这个宏定义的,该宏如下所示:
/** * USB_INTERFACE_INFO - macro used to describe a class of usb interfaces * @cl: bInterfaceClass value * @sc: bInterfaceSubClass value * @pr: bInterfaceProtocol value * * This macro is used to create a struct usb_device_id that matches a * specific class of interfaces. */ #define USB_INTERFACE_INFO(cl, sc, pr) \ .match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \ .bInterfaceClass = (cl), \ .bInterfaceSubClass = (sc), \ .bInterfaceProtocol = (pr)
其中:
- cl:接口类,我们USB鼠标为HID类,所以填入0x03,也就是USB_INTERFACE_CLASS_HID;HID类是属于人机交互的设备,比如:USB键盘,USB鼠标,USB触摸板,USB游戏操作杆都要填入0x03;
- sc:接口子类为启动设备,填入USB_INTERFACE_SUBCLASS_BOOT;
- pr:接口协议为鼠标协议,填入USB_INTERFACE_PROTOCOL_MOUSE,等于2;当设置为USB_INTERFACE_PROTOCOL_KEYBOARD时,表示USB键盘的协议,值为1;
bInterfaceClass:用于指定USB设备所属的接口类,比如USB_INTERFACE_CLASS_HID、USB_CLASS_VIDEO、USB_CLASS_APP_SPEC、USB_CLASS_COMM、USB_CLASS_MASS_STORAGE、USB_CLASS_COMM等。
bInterfaceSubClass,用于指定接口子类;
bInterfaceProtocol :用于指定接口协议;
以接口类USB_INTERFACE_CLASS_HID为例,定义有接口子类和协议,在include/uapi/linux/hid.h文件:
/* * USB HID interface subclass and protocol codes */ #define USB_INTERFACE_SUBCLASS_BOOT 1 #define USB_INTERFACE_PROTOCOL_KEYBOARD 1 #define USB_INTERFACE_PROTOCOL_MOUSE 2
三、usb鼠标接口驱动编写
新建项目13.usb_mouse,编写usb鼠标接口驱动程序,代码放在mouse_dev.c文件。
代码参考drivers/hid/usbhid/usbmouse.c。
3.1 初始化usb接口驱动
首先定义struct usb_driver 类型的全局变量,并初始化成员name、probe、disconnect、id_table:
3.2 编写驱动模块入口函数
在模块入口函数,调用usb_register注册usb_driver结构体:
3.3 编写usb_mouse_probe
3.3.1 动态分配input_device设备
- 我们首先通过input_allocate_device动态创建struct input_dev结构对象dev;
- 通过input_set_capability设置input设备可以上报哪些输入事件;
- 然后调用input_register_device注册这个设备;
3.3.2 设置usb数据传输
- 通过usb_rcvintpipe创建一个接收中断类型的端点管道,用来端点和数据缓冲区之间的连接;
- 通过usb_alloc_coherent申请usb缓冲区;
- 通过usb_alloc_urb申请urb结构体;
- 通过usb_fill_int_urb填充urb结构体;当鼠标点击时触发urb处理完成函数,在urb处理完成函数进行输入事件上报;
- 因为我们S3C2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址;
- 使用usb_submit_urb提交urb;
3.4 编写usb_mouse_disconnect
- 使用usb_kill_urb杀掉提交到内核中的urb;
- 使用usb_free_urb释放urb;
- 使用usb_free_coherent释放usb缓存区;
- 使用input_unregister_device函数注销input_dev;
- 使用input_free_device函数释放input_dev;
3.5 编写驱动模块出口函数
在模块出口函数,调用usb_deregister注销usb_driver结构体:
3.6 完整代码mouse_dev.c
#include <linux/module.h> #include <linux/fs.h> #include <linux/usb/input.h> #include <linux/hid.h> /* * usb鼠标设备 */ struct usb_mouse{ char name[128]; /* 鼠标设备的名称,包括生产厂商、产品类别、产品等信息 */ char phys[64]; /* 设备节点名称 */ /* usb 鼠标是一种 usb设备,需要内嵌一个usb设备结构体来描述其usb属性 */ struct usb_device *usbdev; /* usb鼠标同时又是一种输入设备,需要内嵌一个输入设备结构体来描述其输入设备的属性 */ struct input_dev *dev; /** urb请求包结构体,用于传送数据 */ struct urb *urb; /** 虚拟缓冲区地址 */ char *data; /** DMA缓冲区 */ dma_addr_t data_dma; }; /** * urb完成时被调用的完成处理函数 */ static void usb_mouse_irq(struct urb *urb) { /* 私有数据 */ struct usb_mouse *mouse = urb->context; /* 接收到的数据 */ char *data = mouse->data; /* input_dev */ struct input_dev *dev = mouse->dev; int status; /* urb的当前状态 */ switch (urb->status) { case 0: /* success */ break; case -ECONNRESET: /* unlink */ case -ENOENT: case -ESHUTDOWN: return; /* -EPIPE: should clear the halt */ default: /* error */ goto resubmit; } /* * usb鼠标数据含义 * 向输入子系统汇报鼠标事件情况,以便作出反应。 * data 数组的第0个字节:bit 0、1、2、3、4分别代表左、右、中、SIDE、EXTRA键的按下情况; * data 数组的第1个字节:表示鼠标的水平位移; * data 数组的第2个字节:表示鼠标的垂直位移; * data 数组的第3个字节:REL_WHEEL位移。 */ input_report_key(dev, BTN_LEFT, data[0] & 0x01); input_report_key(dev, BTN_RIGHT, data[0] & 0x02); input_report_key(dev, BTN_MIDDLE, data[0] & 0x04); input_report_key(dev, BTN_SIDE, data[0] & 0x08); input_report_key(dev, BTN_EXTRA, data[0] & 0x10); input_report_rel(dev, REL_X, data[1]); input_report_rel(dev, REL_Y, data[2]); input_report_rel(dev, REL_WHEEL, data[3]); /*上报同步事件,通知系统有事件上报 */ input_sync(dev); /* * 系统需要周期性不断地获取鼠标的事件信息,因此在 urb 回调函数的末尾再次提交urb请求块,这样又会调用新的回调函数,周而复始。 * 在回调函数中提交urb一定只能是 GFP_ATOMIC 优先级的,因为 urb 回调函数运行于中断上下文中,在提 * 交 urb 过程中可能会需要申请内存、保持信号量,这些操作或许会导致 USB core 睡眠,一切导致睡眠的行 * 为都是不允许的。 */ resubmit: status = usb_submit_urb (urb, GFP_ATOMIC); if (status) dev_err(&mouse->usbdev->dev, "can't resubmit intr, %s-%s/input0, status %d\n", mouse->usbdev->bus->bus_name, mouse->usbdev->devpath, status); } /** * 用户usb接口设备和usb接口驱动匹配 */ static struct usb_device_id usb_mouse_id_table[] = { { USB_INTERFACE_INFO( USB_INTERFACE_CLASS_HID, //接口类:hid类 USB_INTERFACE_SUBCLASS_BOOT, //子类:启动设备类 USB_INTERFACE_PROTOCOL_MOUSE) }, //USB协议:鼠标协议 }; /* * 打开input_dev设备 */ static int usb_mouse_open(struct input_dev *dev) { /* 获取驱动数据 */ struct usb_mouse *mouse = input_get_drvdata(dev); mouse->urb->dev = mouse->usbdev; /* 使用usb_submit_urb提交urb */ if (usb_submit_urb(mouse->urb, GFP_KERNEL)) return -EIO; return 0; } /* 关闭input_dev设备 */ static void usb_mouse_close(struct input_dev *dev) { /* 获取驱动数据 */ struct usb_mouse *mouse = input_get_drvdata(dev); /* 杀掉提交到内核中的urb */ usb_kill_urb(mouse->urb); } /** * 当usb接口驱动和usb接口匹配成功之后,就会调用probe函数 * 可以参考hub_probe实现 */ static void usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id) { /* 获取usb设备 */ struct usb_device *dev = interface_to_usbdev(intf); /* 当前激活的接口配置 */ struct usb_host_interface *interface; /* 当前usb接口下的端点0的端点描述符 */ struct usb_endpoint_descriptor *endpoint; /* usb鼠标设备 */ struct usb_mouse *mouse; /* input_dev */ struct input_dev *input_dev; /* 端点管道 */ int pipe,maxp; int error = -ENOMEM; /* 当前激活的接口配置 */ interface = intf->cur_altsetting; /* 端点个数,鼠标只有1个 */ if (interface->desc.bNumEndpoints != 1) return -ENODEV; /* 当前usb接口下的端点0的端点描述符 */ endpoint = &interface->endpoint[0].desc; if (!usb_endpoint_is_int_in(endpoint)) return -ENODEV; // 打印VID,PID printk("VID=%x,PID=%x\n",dev->descriptor.idVendor,dev->descriptor.idProduct); /* 通过usb_rcvintpipe创建一个端点管道 */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /* 获取本端点接受或发送的最大信息包的大小 */ maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); /* 动态分配内存 */ mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); /* 分配一个input_dev结构体 */ input_dev = input_allocate_device(); if (!mouse || !input_dev) goto fail1; /* 初始化 */ mouse->usbdev = dev; mouse->dev = input_dev; /* * 申请内存空间用于数据传输,data 为指向该空间的地址,data_dma 则是这块内存空间的 dma 映射, * 即这块内存空间对应的 dma 地址。在使用 dma 传输的情况下,则使用 data_dma 指向的 dma 区域, * 否则使用 data 指向的普通内存区域进行传输。 * GFP_ATOMIC 表示不等待,GFP_KERNEL 是普通的优先级,可以睡眠等待,由于鼠标使用中断传输方式, * 不允许睡眠状态,data 又是周期性获取鼠标事件的存储区,因此使用 GFP_ATOMIC 优先级,如果不能 * 分配到内存则立即返回 0。 */ mouse->data = usb_alloc_coherent(dev,8,GFP_ATOMIC,&mouse->data_dma); if (!mouse->data) goto fail1; /* 分配一个urb数据结构体 */ mouse->urb = usb_alloc_urb(0,GFP_KERNEL); if (!mouse->urb) goto fail2; /* 获取鼠标设备的名称 */ if (dev->manufacturer) strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name)); if (dev->product) { if(dev->manufacturer) strlcat(mouse->name, " ", sizeof(mouse->name)); strlcat(mouse->name, dev->product, sizeof(mouse->name)); } /* 如果鼠标名没有 */ if (!strlen(mouse->name)) snprintf(mouse->name, sizeof(mouse->name), "USB HIDBP Mouse %04x:%04x", le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); /* * 填充鼠标设备结构体中的节点名。usb_make_path 用来获取 usb设备在 sysfs 中的路径,格式为:usb-usb 总线号-路径名。 */ usb_make_path(dev, mouse->phys, sizeof(mouse->phys)); strlcat(mouse->phys, "/input0", sizeof(mouse->phys)); /* 将鼠标设备的名称赋给鼠标设备内嵌的输入子系统结构体 */ input_dev->name = mouse->name; /* 将鼠标设备的设备节点名赋给鼠标设备内嵌的输入子系统结构体 */ input_dev->phys = mouse->phys; /* * input_dev 中的 input_id 结构体,用来存储厂商、设备类型和设备的编号,这个函数是将设备描述符 * 中的编号赋给内嵌的输入子系统结构体 */ usb_to_input_id(dev, &input_dev->id); input_dev->dev.parent = &intf->dev; /* 设置上报事件,类型 */ set_bit(EV_KEY,input_dev->evbit); // 支持按键事件 set_bit(EV_REL,input_dev->evbit); // 支持相对坐标事件 input_set_capability(input_dev,EV_KEY,BTN_LEFT); // 鼠标左键点击 按下值为1,抬起值为0 input_set_capability(input_dev,EV_KEY,BTN_RIGHT); // 鼠标右键点击 按下值为1,抬起值为0 input_set_capability(input_dev,EV_KEY,BTN_MIDDLE); // 鼠标中键点击 按下值为1,抬起值为0 input_set_capability(input_dev,EV_KEY,BTN_SIDE); input_set_capability(input_dev,EV_KEY,BTN_EXTRA); input_set_capability(input_dev,EV_REL,REL_X); input_set_capability(input_dev,EV_REL,REL_Y); input_set_capability(input_dev,EV_REL,REL_WHEEL); /* 设置input_dev->dev->driver_data = mouse*/ input_set_drvdata(input_dev, mouse); /* 初始化input_dev */ input_dev->open = usb_mouse_open; input_dev->close = usb_mouse_close; /* 填充urb */ usb_fill_int_urb (mouse->urb , //urb结构体 mouse->usbdev, //usb设备 pipe, //端点管道 mouse->data, //缓存区地址 maxp, //数据长度 usb_mouse_irq, //中断函数 mouse, //urb完成函数上下文 endpoint->bInterval); //中断间隔时间 /* 因为我们S3C2440支持DMA,所以要告诉urb结构体,使用DMA缓冲区地址 */ mouse->urb->transfer_dma = mouse->data_dma; //设置DMA地址 mouse->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; //设置使用DMA地址 /* 注册input_dev */ error = input_register_device(mouse->dev); if (error) { printk("input device usb mouse device registered failed\n"); goto fail3; } else { printk("input device usb mouse device registered successfully\n"); } /* 设置intf->dev->driver_data = mouse */ usb_set_intfdata(intf, mouse); return 0; fail3: /* 释放urb */ usb_free_urb(mouse->urb); fail2: /* 释放usb缓存区 */ usb_free_coherent(dev, 8, mouse->data, mouse->data_dma); fail1: /* 释放inpyt_dev */ input_free_device(input_dev); kfree(mouse); return error; } /* * 卸载usb接口驱动时执行 */ static usb_mouse_disconnect(struct usb_interface *intf) { /* 获取intf->dev->driver_data */ struct usb_mouse *mouse = usb_get_intfdata (intf); usb_set_intfdata(intf, NULL); if(mouse){ /* 杀掉提交到内核中的urb */ usb_kill_urb(mouse->urb); /* 注销内核中的input_dev */ input_unregister_device(mouse->dev); /* 释放urb */ usb_free_urb(mouse->urb); /* 释放usb缓存区 */ usb_free_coherent(mouse->usbdev,8, mouse->data,mouse->data_dma); /* 释放input_dev */ input_free_device(mouse->dev); kfree(mouse); } } /** * usb鼠标接口驱动 */ static struct usb_driver usb_mouse_driver = { .name = "usbmouse", .probe = usb_mouse_probe, .disconnect = usb_mouse_disconnect, .id_table = usb_mouse_id_table, }; /* * usb鼠标接口驱动模块入口 */ static int mouse_init(void) { int ret; ret = usb_register(&usb_mouse_driver); if (ret){ printk("usb interface driver registered failed\n"); }else{ printk("usb interface driver registered successfully\n"); } return ret; } /* * usb鼠标接口驱动模块出口 */ static void __exit mouse_exit(void) { usb_deregister(&usb_mouse_driver); printk("usb interface driver deregistered successfully\n"); } module_init(mouse_init); module_exit(mouse_exit); MODULE_LICENSE("GPL");
四、测试
4.1 编译usb鼠标接口驱动
在13.usb_mouse路径下编译:
root@zhengyang:/work/sambashare/drivers/13.usb_mouse# cd /work/sambashare/drivers/13.usb_mouse root@zhengyang:/work/sambashare/drivers/13.usb_mouse# make
拷贝驱动文件到nfs文件系统:
root@zhengyang:/work/sambashare/drivers/13.usb_mouse# cp /work/sambashare/drivers/13.usb_mouse/mouse_dev.ko /work/nfs_root/rootfs/
4.2 安装驱动
重启开发板,加载usb鼠标接口驱动,执行如下命令:
insmod mouse_dev.ko
运行结果如下:
[root@zy:/]# insmod mouse_dev.ko mouse_dev: loading out-of-tree module taints kernel. VID=0,PID=538 input: USB OPTICAL MOUSE as /devices/platform/s3c2410-ohci/usb1/1-1/1-1:1.0/input/input0 input device usb mouse device registered successfully usbmouse: probe of 1-1:1.0 failed with error 53 usbcore: registered new interface driver usbmouse usb interface driver registered successfully
查看设备节点文件:
[root@zy:/]# ls /dev/input -l total 0 crw-rw---- 1 0 0 13, 64 Jan 1 00:00 1 /dev/input/event0
4.3 编写应用程序测试
新建test文件夹,编写测试程序。
4.3.1 main.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <linux/input.h> int main(int argc, char const *argv[]) { //打开设备文件 int fd; int retval; fd_set readfds; struct timeval tv; if((fd = open("/dev/input/event0", O_RDONLY)) == -1) { perror("open error"); return -1; } //读取文件内容 struct input_event mykey; while(1){ FD_ZERO(&readfds); FD_SET(fd, &readfds); if((retval = select(fd+1, &readfds, NULL, NULL, &tv)) == 1) { if(read(fd, &mykey, sizeof(mykey)) == sizeof(mykey)){ // 事件类型 鼠标或者按键 if(mykey.type == EV_KEY) { printf("--------------------\n"); printf("type = %u.\n", mykey.type); printf("code = %u.\n", mykey.code); printf("value = %u.\n", mykey.value); /* 按键是按下还是释放,0释放、1按下、2长按 */ // 按下状态 if(mykey.value == 1) { printf("type:%#x, code:%d, value:%#x\n", mykey.type, mykey.code, mykey.value); switch (mykey.code) { case 0x110: puts("mouse left button down"); break; case 0x111: puts("mouse right button down"); break; case 0x112: puts("mouse middle button down"); break; } } } } } } return 0; }
4.3.2 Makefile
all: arm-linux-gcc -march=armv4t -o main main.c clean: rm -rf *.o main
4.3.3 编译下载
root@zhengyang:/work/sambashare/drivers/13.usb_mouse/test# make arm-linux-gcc -march=armv4t -o main main.c root@zhengyang:/work/sambashare/drivers/13.usb_mouse/test# cp main /work/nfs_root/rootfs/
在开发板串口工具输入:
[root@zy:/]# ./main
随便按下鼠标左键、右键、中间键,输出如下:
-------------------- type = 1. code = 272. value = 1. type:0x1, code:272, value:0x1 mouse left button down -------------------- type = 1. code = 272. value = 0. -------------------- type = 1. code = 272. value = 1. type:0x1, code:272, value:0x1 mouse left button down -------------------- type = 1. code = 272. value = 0. -------------------- type = 1. code = 273. value = 1. type:0x1, code:273, value:0x1 mouse right button down -------------------- type = 1. code = 273. value = 0.
4.4 问题排查
如果usb鼠标工作一段时间就自动断开:
USB disconnect, device number xx
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步