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
考虑电源为电源问题,可以为usb口外接电源,或者开发板电源单独供电,不要使用电脑usb供电。
如果开发板插入鼠标后,出现如下错误:
usb 1-1: new low-speed USB device number 2 using s3c2410-ohci usb 1-1: device descriptor read/64, error -62 usb 1-1: device descriptor read/64, error -62 usb usb1-port1: unable to enumerate USB device
这个错误实际发生在usb设备插入之后,usb主机控制器在识别usb设备的过程中。
这里的错误是什么意思呢?这些错误信息来自于hub_port_init函数的执行过程中:
/* Reset device, (re)assign address, get device descriptor. * Device connection must be stable, no more debouncing needed. * Returns device in USB_STATE_ADDRESS, except on error. * * If this is called for an already-existing device (as part of * usb_reset_and_verify_device), the caller must own the device lock and * the port lock. For a newly detected device that is not accessible * through any global pointers, it's not necessary to lock the device, * but it is still necessary to lock the port. */ static int hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, int retry_counter) { struct usb_device *hdev = hub->hdev; struct usb_hcd *hcd = bus_to_hcd(hdev->bus); struct usb_port *port_dev = hub->ports[port1 - 1]; int retries, operations, retval, i; unsigned delay = HUB_SHORT_RESET_TIME; enum usb_device_speed oldspeed = udev->speed; const char *speed; int devnum = udev->devnum; const char *driver_name; /* root hub ports have a slightly longer reset period * (from USB 2.0 spec, section 7.1.7.5) */ if (!hdev->parent) { delay = HUB_ROOT_RESET_TIME; if (port1 == hdev->bus->otg_port) hdev->bus->b_hnp_enable = 0; } /* Some low speed devices have problems with the quick delay, so */ /* be a bit pessimistic with those devices. RHbug #23670 */ if (oldspeed == USB_SPEED_LOW) delay = HUB_LONG_RESET_TIME; mutex_lock(hcd->address0_mutex); /* Reset the device; full speed may morph to high speed */ /* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */ retval = hub_port_reset(hub, port1, udev, delay, false); if (retval < 0) /* error or disconnect */ goto fail; /* success, speed is known */ retval = -ENODEV; /* Don't allow speed changes at reset, except usb 3.0 to faster */ if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed && !(oldspeed == USB_SPEED_SUPER && udev->speed > oldspeed)) { dev_dbg(&udev->dev, "device reset changed speed!\n"); goto fail; } oldspeed = udev->speed; /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ... * it's fixed size except for full speed devices. * For Wireless USB devices, ep0 max packet is always 512 (tho * reported as 0xff in the device descriptor). WUSB1.0[4.8.1]. */ switch (udev->speed) { case USB_SPEED_SUPER_PLUS: case USB_SPEED_SUPER: case USB_SPEED_WIRELESS: /* fixed at 512 */ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512); break; case USB_SPEED_HIGH: /* fixed at 64 */ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64); break; case USB_SPEED_FULL: /* 8, 16, 32, or 64 */ /* to determine the ep0 maxpacket size, try to read * the device descriptor to get bMaxPacketSize0 and * then correct our initial guess. */ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64); break; case USB_SPEED_LOW: /* fixed at 8 */ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8); break; default: goto fail; } if (udev->speed == USB_SPEED_WIRELESS) speed = "variable speed Wireless"; else speed = usb_speed_string(udev->speed); /* * The controller driver may be NULL if the controller device * is the middle device between platform device and roothub. * This middle device may not need a device driver due to * all hardware control can be at platform device driver, this * platform device is usually a dual-role USB controller device. */ if (udev->bus->controller->driver) driver_name = udev->bus->controller->driver->name; else driver_name = udev->bus->sysdev->driver->name; 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); // 输出日志信息 /* Set up TT records, if needed */ if (hdev->tt) { udev->tt = hdev->tt; udev->ttport = hdev->ttport; } else if (udev->speed != USB_SPEED_HIGH && hdev->speed == USB_SPEED_HIGH) { if (!hub->tt.hub) { dev_err(&udev->dev, "parent hub has no TT\n"); retval = -EINVAL; goto fail; } udev->tt = &hub->tt; udev->ttport = port1; } /* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way? * Because device hardware and firmware is sometimes buggy in * this area, and this is how Linux has done it for ages. * Change it cautiously. * * NOTE: If use_new_scheme() is true we will start by issuing * a 64-byte GET_DESCRIPTOR request. This is what Windows does, * so it may help with some non-standards-compliant devices. * Otherwise we start with SET_ADDRESS and then try to read the * first 8 bytes of the device descriptor to get the ep0 maxpacket * value. */ for (retries = 0; retries < GET_DESCRIPTOR_TRIES; (++retries, msleep(100))) { // 多次尝试获取设备描述符 bool did_new_scheme = false; if (use_new_scheme(udev, retry_counter, port_dev)) { // 如果use_new_schem为true,将发送64字节的获取设备描述符请求 struct usb_device_descriptor *buf; // 存放usb设备描述符 int r = 0; did_new_scheme = true; retval = hub_enable_device(udev); if (retval < 0) { dev_err(&udev->dev, "hub failed to enable device, error %d\n", retval); goto fail; } #define GET_DESCRIPTOR_BUFSIZE 64 buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO); // 分配64字节空间 if (!buf) { retval = -ENOMEM; continue; } /* Retry on all errors; some devices are flakey. * 255 is for WUSB devices, we actually need to use * 512 (WUSB1.0[4.8.1]). */ for (operations = 0; operations < 3; ++operations) { buf->bMaxPacketSize0 = 0; r = usb_control_msg(udev, usb_rcvaddr0pipe(), // 获取64字节的usb设备描述符,返回结果存放在buf缓冲区 USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, USB_DT_DEVICE << 8, 0, buf, GET_DESCRIPTOR_BUFSIZE, initial_descriptor_timeout); switch (buf->bMaxPacketSize0) { // 获取端点0最大包大小 case 8: case 16: case 32: case 64: case 255: if (buf->bDescriptorType == USB_DT_DEVICE) { // 判断是不是设备描述符类型,如果是r=0 r = 0; break; } /* FALL THROUGH */ default: if (r == 0) r = -EPROTO; break; } /* * Some devices time out if they are powered on * when already connected. They need a second * reset. But only on the first attempt, * lest we get into a time out/reset loop */ if (r == 0 || (r == -ETIMEDOUT && retries == 0 && udev->speed > USB_SPEED_FULL)) break; } udev->descriptor.bMaxPacketSize0 = buf->bMaxPacketSize0; kfree(buf); retval = hub_port_reset(hub, port1, udev, delay, false); if (retval < 0) /* error or disconnect */ goto fail; if (oldspeed != udev->speed) { dev_dbg(&udev->dev, "device reset changed speed!\n"); retval = -ENODEV; goto fail; } if (r) { // 如果r!=0,抛出异常信息 if (r != -ENODEV) dev_err(&udev->dev, "device descriptor read/64, error %d\n", r); retval = -EMSGSIZE; continue; } #undef GET_DESCRIPTOR_BUFSIZE } /* * If device is WUSB, we already assigned an * unauthorized address in the Connect Ack sequence; * authorization will assign the final address. */ if (udev->wusb == 0) { for (operations = 0; operations < SET_ADDRESS_TRIES; ++operations) { // 多次尝试设置设备地址 retval = hub_set_address(udev, devnum); if (retval >= 0) break; msleep(200); } if (retval < 0) { if (retval != -ENODEV) dev_err(&udev->dev, "device not accepting address %d, error %d\n", devnum, retval); goto fail; } if (udev->speed >= USB_SPEED_SUPER) { devnum = udev->devnum; dev_info(&udev->dev, "%s SuperSpeed%s%s USB device number %d using %s\n", (udev->config) ? "reset" : "new", (udev->speed == USB_SPEED_SUPER_PLUS) ? "Plus Gen 2" : " Gen 1", (udev->rx_lanes == 2 && udev->tx_lanes == 2) ? "x2" : "", devnum, driver_name); } /* cope with hardware quirkiness: * - let SET_ADDRESS settle, some device hardware wants it * - read ep0 maxpacket even for high and low speed, */ msleep(10); /* use_new_scheme() checks the speed which may have * changed since the initial look so we cache the result * in did_new_scheme */ if (did_new_scheme) break; } retval = usb_get_device_descriptor(udev, 8); // 获取设备描述符,8是指定读取的描述符长度,函数返回接收到的字节数 if (retval < 8) { if (retval != -ENODEV) dev_err(&udev->dev, "device descriptor read/8, error %d\n", retval); if (retval >= 0) retval = -EMSGSIZE; } else { u32 delay; retval = 0; delay = udev->parent->hub_delay; udev->hub_delay = min_t(u32, delay, USB_TP_TRANSMISSION_DELAY_MAX); retval = usb_set_isoch_delay(udev); if (retval) { dev_dbg(&udev->dev, "Failed set isoch delay, error %d\n", retval); retval = 0; } break; } } if (retval) goto fail; /* * Some superspeed devices have finished the link training process * and attached to a superspeed hub port, but the device descriptor * got from those devices show they aren't superspeed devices. Warm * reset the port attached by the devices can fix them. */ if ((udev->speed >= USB_SPEED_SUPER) && (le16_to_cpu(udev->descriptor.bcdUSB) < 0x0300)) { dev_err(&udev->dev, "got a wrong device descriptor, " "warm reset device\n"); hub_port_reset(hub, port1, udev, HUB_BH_RESET_TIME, true); retval = -EINVAL; goto fail; } if (udev->descriptor.bMaxPacketSize0 == 0xff || udev->speed >= USB_SPEED_SUPER) i = 512; else i = udev->descriptor.bMaxPacketSize0; if (usb_endpoint_maxp(&udev->ep0.desc) != i) { if (udev->speed == USB_SPEED_LOW || !(i == 8 || i == 16 || i == 32 || i == 64)) { dev_err(&udev->dev, "Invalid ep0 maxpacket: %d\n", i); retval = -EMSGSIZE; goto fail; } if (udev->speed == USB_SPEED_FULL) dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i); else dev_warn(&udev->dev, "Using ep0 maxpacket: %d\n", i); udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i); usb_ep0_reinit(udev); } retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); if (retval < (signed)sizeof(udev->descriptor)) { if (retval != -ENODEV) dev_err(&udev->dev, "device descriptor read/all, error %d\n", retval); if (retval >= 0) retval = -ENOMSG; goto fail; } usb_detect_quirks(udev); if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) { retval = usb_get_bos_descriptor(udev); if (!retval) { udev->lpm_capable = usb_device_supports_lpm(udev); usb_set_lpm_parameters(udev); } } retval = 0; /* notify HCD that we have a device connected and addressed */ if (hcd->driver->update_device) hcd->driver->update_device(hcd, udev); hub_set_initial_usb2_lpm_policy(udev); fail: if (retval) { hub_port_disable(hub, port1, 0); update_devnum(udev, devnum); /* for disconnect processing */ } mutex_unlock(hcd->address0_mutex); return retval; }
我们大致分析一下该函数的执行过程:
- 假设use_new_scheme返回true,将会获取64字节的设备面描述符;如果获取设备描述符失败将会抛出错误提示,如上述的超时错误device descriptor read/64, error -62.
- 调用hub_set_address为低速usb设备分配了一个设备地址为2;如果超时将会抛出 device not accepting address 8, error -62;
该步骤在函数hub_port_init中执行;其中获取设备描述符时候,出现了超时错误错误码为-62,其中read/64代表,读取的描述符长度为64。
超时错误状态码定义在include/uapi/asm-generic/errno.h文件:
#define ETIME 62 /* Timer expired */
4.4.1 解决方案一
关于这个错误,网上给出的原因大多为USB时钟频率设置有问题,通过修改UPLLCON寄存器解决。为什么这么可以解决问题,实际上并没有去深究,关于linux时钟频率设定这块,我们下一节单独介绍。
修改drivers/usb/host/ohci-s3c2410.c,设置UPLL时钟频率为48MHZ(为USB提供工作频率),添加头文件:
#include <mach/regs-clock.h>
在函数s3c2410_start_hc开头添加:
unsigned long upllvalue = (0x78<< 12) | (0x02<< 4) | (0x03); while (upllvalue != __raw_readl(S3C2410_UPLLCON)) { __raw_writel(upllvalue, S3C2410_UPLLCON); mdelay(1); }
4.4.2 解决方案二
考虑是开发板电源电流不够,比如我们的圆孔充电线是连接在电脑的usb接口上,电脑的usb 2.0接口最多提供500mA的电流,可能无法满足我们开发板供电要求的,我用万用表测了一下电源的输入电流最大的时候只有350mA。后来我又买了买一个5V、2A+ 3.5mm的圆孔充电器给Mini2440开发板供电,结果还是一样。
五、使用内核自带usb鼠标驱动
5.1 配置内核
我们需要重新配置内核,使用内核自带的usb鼠标驱动,配置原理参考Linux驱动学习(2) 从usb驱动到input子系统4,具体步骤如下。
我们切换到linux内核目录下:
root@zhengyang:~# cd /work/sambashare/linux-5.2.8/
查看drivers/hid/usbhid/Kconfig:
menu "USB HID Boot Protocol drivers" depends on USB!=n && USB_HID!=y && EXPERT config USB_KBD tristate "USB HIDBP Keyboard (simple Boot) support" depends on USB && INPUT ---help--- Say Y here only if you are absolutely sure that you don't want to use the generic HID driver for your USB keyboard and prefer to use the keyboard in its limited Boot Protocol mode instead. This is almost certainly not what you want. This is mostly useful for embedded applications or simple keyboards. To compile this driver as a module, choose M here: the module will be called usbkbd. If even remotely unsure, say N. config USB_MOUSE tristate "USB HIDBP Mouse (simple Boot) support" depends on USB && INPUT ---help--- Say Y here only if you are absolutely sure that you don't want to use the generic HID driver for your USB mouse and prefer to use the mouse in its limited Boot Protocol mode instead. This is almost certainly not what you want. This is mostly useful for embedded applications or simple mice. To compile this driver as a module, choose M here: the module will be called usbmouse. If even remotely unsure, say N. endmenu
如果要使用内核自带的usb鼠标驱动,USB配置为y,USB_HID不为y,必须要有EXPERT配置。我们编辑drivers/hid/usbhid/Kconfig文件,删除EXPERT。
在linux内核根目录下执行,生成默认配置文件.config:
make distclean make s3c2440_defconfig # 这个是之前我之前配置的
进行内核配置:
root@zhengyang:/work/sambashare/linux-5.2.8# make menuconfig
配置步骤如下:
Device Drivers --->
- HID support --->
- USB HID support
- USB HID Boot Protocol drivers
- <*>USB HIDBP Mouse (simple Boot) support=
- USB HID Boot Protocol drivers
- USB HID support
修改完配置后,保存文件,输入文件名s3c2440_defconfig,在当前路径下生成s3c2440_defconfig:存档:
mv s3c2440_defconfig ./arch/arm/configs/
此时重新执行:
make s3c2440_defconfig
5.2 编译内核和模块
如果修改了内核代码,则需要重新编译内核:
root@zhengyang:~# cd /work/sambashare/linux-5.2.8/ make V=1 uImage
将uImage复制到tftp服务器路径下:
cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/
5.3 烧录内核
开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,可以通过print查看uboot中已经设置的环境变量。
设置开发板ip地址,从而可以使用网络服务:
SMDK2440 # set ipaddr 192.168.0.105 SMDK2440 # save Saving Environment to NAND... Erasing NAND... Erasing at 0x40000 -- 100% complete. Writing to NAND... OK SMDK2440 # ping 192.168.0.200 dm9000 i/o: 0x20000000, id: 0x90000a46 DM9000: running in 16 bit mode MAC: 08:00:3e:26:0a:5b operating at unknown: 0 mode Using dm9000 device host 192.168.0.200 is alive
设置tftp服务器地址,也就是我们ubuntu服务器地址:
set serverip 192.168.0.200 save
下载内核到内存,并写NAND FLASH:
tftp 30000000 uImage nand erase.part kernel nand write 30000000 kernel bootm
usb鼠标插入后,开发板内核启动后,输出usb鼠标相关信息:
usb 1-1: new low-speed USB device number 2 using s3c2410-ohci input: USB OPTICAL MOUSE as /devices/platform/s3c2410-ohci/usb1/1-1/1-1:1.0/input/input0
六、代码下载
Young / s3c2440_project[drivers]
参考文章
[4]USB-HOST调试纪实(usb驱动问题集合)