程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

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。

端点接受或发送的最大信息包的大小可以通过usb_maxpacket函数获取:
复制代码
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);
}
View Code
复制代码

其中参数:

  • 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-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
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-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(1154)  评论(0编辑  收藏  举报
努力加载评论中...
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示