• 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鼠标数据准备工作

    在前面我们已经介绍过,linux内核中通过urb和所有的usb设备进行通信,关于struct urb结构体的定义也已经在linux驱动移植-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:

    2.5 数据缓冲区初始化

    char *usb_buffer_alloc(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_buffer_free(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个参数传入的缓冲区中,鼠标操作数据都存在该缓冲区内,缓冲区内数据含义如下:

    • bit0表示鼠标左键, 该位为1表示按下, 为0表示松开;
    • bit1表示鼠标右键, 该位为1表示按下, 为0表示松开;
    • bit2表示鼠标中键, 该位为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;

    三、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_struct结构体:

    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_buffer_alloc申请usb缓冲区

    通过usb_alloc_coherent申请urb结构体;

    通过usb_fill_int_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_register_device函数注销input_dev;

    使用input_free_device函数释放input_de;

    3.5 编写驱动模块出口函数

    在模块出口函数,调用usb_deregister注销usb_struct结构体:

    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 24 using s3c2410-ohci
    usb 1-1: device not accepting address 24, error -62
    usb usb1-port1: attempt power cycle
    usb 1-1: new low-speed USB device number 25 using s3c2410-ohci
    usb 1-1: device descriptor read/all, error -32
    usb 1-1: new low-speed USB device number 26 using s3c2410-ohci
    usb 1-1: device descriptor read/8, error -62
    usb 1-1: device descriptor read/8, error -62
    usb usb1-port1: unable to enumerate USB device

    可以尝试如下解决方案。

    3.6.1 解决方案一

    修改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);
    
            }

    五、使用内核自带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=

    修改完配置后,保存文件,输入文件名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 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]

    参考文章

    [1]十五、Linux驱动之USB鼠标驱动

    [2]20.Linux-USB鼠标驱动

    [3]Linux 键盘/鼠标 按键事件 编程

  • 相关阅读:
    不同编码字符所占大小
    期末考点总结--------多元统计分析
    博客网站设计
    java 事件举例
    zookerper总结
    Spring java配置
    Sense and Sensibility
    栈的出栈序列个数
    闭算子
    线性空间结论总结
  • 原文地址:https://www.cnblogs.com/zyly/p/16303804.html
Copyright © 2020-2023  润新知