📄 hid-core.c
字号:
/* * USB HID support for Linux * * Copyright (c) 1999 Andreas Gal * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz> * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc * Copyright (c) 2006-2007 Jiri Kosina *//* * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. */#include <linux/module.h>#include <linux/slab.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/list.h>#include <linux/mm.h>#include <linux/smp_lock.h>#include <linux/spinlock.h>#include <asm/unaligned.h>#include <asm/byteorder.h>#include <linux/input.h>#include <linux/wait.h>#include <linux/usb.h>#include <linux/hid.h>#include <linux/hiddev.h>#include <linux/hid-debug.h>#include <linux/hidraw.h>#include "usbhid.h"/* * Version Information */#define DRIVER_VERSION "v2.6"#define DRIVER_AUTHOR "Andreas Gal, Vojtech Pavlik, Jiri Kosina"#define DRIVER_DESC "USB HID core driver"#define DRIVER_LICENSE "GPL"static char *hid_types[] = {"Device", "Pointer", "Mouse", "Device", "Joystick", "Gamepad", "Keyboard", "Keypad", "Multi-Axis Controller"};/* * Module parameters. */static unsigned int hid_mousepoll_interval;module_param_named(mousepoll, hid_mousepoll_interval, uint, 0644);MODULE_PARM_DESC(mousepoll, "Polling interval of mice");/* Quirks specified at module load time */static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };module_param_array_named(quirks, quirks_param, charp, NULL, 0444);MODULE_PARM_DESC(quirks, "Add/modify USB HID quirks by specifying " " quirks=vendorID:productID:quirks" " where vendorID, productID, and quirks are all in" " 0x-prefixed hex");static char *rdesc_quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };module_param_array_named(rdesc_quirks, rdesc_quirks_param, charp, NULL, 0444);MODULE_PARM_DESC(rdesc_quirks, "Add/modify report descriptor quirks by specifying " " rdesc_quirks=vendorID:productID:rdesc_quirks" " where vendorID, productID, and rdesc_quirks are all in" " 0x-prefixed hex");/* * Input submission and I/O error handler. */static void hid_io_error(struct hid_device *hid);/* Start up the input URB */static int hid_start_in(struct hid_device *hid){ unsigned long flags; int rc = 0; struct usbhid_device *usbhid = hid->driver_data; spin_lock_irqsave(&usbhid->inlock, flags); if (hid->open > 0 && !test_bit(HID_SUSPENDED, &usbhid->iofl) && !test_and_set_bit(HID_IN_RUNNING, &usbhid->iofl)) { rc = usb_submit_urb(usbhid->urbin, GFP_ATOMIC); if (rc != 0) clear_bit(HID_IN_RUNNING, &usbhid->iofl); } spin_unlock_irqrestore(&usbhid->inlock, flags); return rc;}/* I/O retry timer routine */static void hid_retry_timeout(unsigned long _hid){ struct hid_device *hid = (struct hid_device *) _hid; struct usbhid_device *usbhid = hid->driver_data; dev_dbg(&usbhid->intf->dev, "retrying intr urb\n"); if (hid_start_in(hid)) hid_io_error(hid);}/* Workqueue routine to reset the device or clear a halt */static void hid_reset(struct work_struct *work){ struct usbhid_device *usbhid = container_of(work, struct usbhid_device, reset_work); struct hid_device *hid = usbhid->hid; int rc_lock, rc = 0; if (test_bit(HID_CLEAR_HALT, &usbhid->iofl)) { dev_dbg(&usbhid->intf->dev, "clear halt\n"); rc = usb_clear_halt(hid_to_usb_dev(hid), usbhid->urbin->pipe); clear_bit(HID_CLEAR_HALT, &usbhid->iofl); hid_start_in(hid); } else if (test_bit(HID_RESET_PENDING, &usbhid->iofl)) { dev_dbg(&usbhid->intf->dev, "resetting device\n"); rc = rc_lock = usb_lock_device_for_reset(hid_to_usb_dev(hid), usbhid->intf); if (rc_lock >= 0) { rc = usb_reset_composite_device(hid_to_usb_dev(hid), usbhid->intf); if (rc_lock) usb_unlock_device(hid_to_usb_dev(hid)); } clear_bit(HID_RESET_PENDING, &usbhid->iofl); } switch (rc) { case 0: if (!test_bit(HID_IN_RUNNING, &usbhid->iofl)) hid_io_error(hid); break; default: err_hid("can't reset device, %s-%s/input%d, status %d", hid_to_usb_dev(hid)->bus->bus_name, hid_to_usb_dev(hid)->devpath, usbhid->ifnum, rc); /* FALLTHROUGH */ case -EHOSTUNREACH: case -ENODEV: case -EINTR: break; }}/* Main I/O error handler */static void hid_io_error(struct hid_device *hid){ unsigned long flags; struct usbhid_device *usbhid = hid->driver_data; spin_lock_irqsave(&usbhid->inlock, flags); /* Stop when disconnected */ if (usb_get_intfdata(usbhid->intf) == NULL) goto done; /* If it has been a while since the last error, we'll assume * this a brand new error and reset the retry timeout. */ if (time_after(jiffies, usbhid->stop_retry + HZ/2)) usbhid->retry_delay = 0; /* When an error occurs, retry at increasing intervals */ if (usbhid->retry_delay == 0) { usbhid->retry_delay = 13; /* Then 26, 52, 104, 104, ... */ usbhid->stop_retry = jiffies + msecs_to_jiffies(1000); } else if (usbhid->retry_delay < 100) usbhid->retry_delay *= 2; if (time_after(jiffies, usbhid->stop_retry)) { /* Retries failed, so do a port reset */ if (!test_and_set_bit(HID_RESET_PENDING, &usbhid->iofl)) { schedule_work(&usbhid->reset_work); goto done; } } mod_timer(&usbhid->io_retry, jiffies + msecs_to_jiffies(usbhid->retry_delay));done: spin_unlock_irqrestore(&usbhid->inlock, flags);}/* * Input interrupt completion handler. */static void hid_irq_in(struct urb *urb){ struct hid_device *hid = urb->context; struct usbhid_device *usbhid = hid->driver_data; int status; switch (urb->status) { case 0: /* success */ usbhid->retry_delay = 0; hid_input_report(urb->context, HID_INPUT_REPORT, urb->transfer_buffer, urb->actual_length, 1); break; case -EPIPE: /* stall */ clear_bit(HID_IN_RUNNING, &usbhid->iofl); set_bit(HID_CLEAR_HALT, &usbhid->iofl); schedule_work(&usbhid->reset_work); return; case -ECONNRESET: /* unlink */ case -ENOENT: case -ESHUTDOWN: /* unplug */ clear_bit(HID_IN_RUNNING, &usbhid->iofl); return; case -EILSEQ: /* protocol error or unplug */ case -EPROTO: /* protocol error or unplug */ case -ETIME: /* protocol error or unplug */ case -ETIMEDOUT: /* Should never happen, but... */ clear_bit(HID_IN_RUNNING, &usbhid->iofl); hid_io_error(hid); return; default: /* error */ warn("input irq status %d received", urb->status); } status = usb_submit_urb(urb, GFP_ATOMIC); if (status) { clear_bit(HID_IN_RUNNING, &usbhid->iofl); if (status != -EPERM) { err_hid("can't resubmit intr, %s-%s/input%d, status %d", hid_to_usb_dev(hid)->bus->bus_name, hid_to_usb_dev(hid)->devpath, usbhid->ifnum, status); hid_io_error(hid); } }}static int hid_submit_out(struct hid_device *hid){ struct hid_report *report; struct usbhid_device *usbhid = hid->driver_data; report = usbhid->out[usbhid->outtail]; hid_output_report(report, usbhid->outbuf); usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0); usbhid->urbout->dev = hid_to_usb_dev(hid); dbg_hid("submitting out urb\n"); if (usb_submit_urb(usbhid->urbout, GFP_ATOMIC)) { err_hid("usb_submit_urb(out) failed"); return -1; } return 0;}static int hid_submit_ctrl(struct hid_device *hid){ struct hid_report *report; unsigned char dir; int len; struct usbhid_device *usbhid = hid->driver_data; report = usbhid->ctrl[usbhid->ctrltail].report; dir = usbhid->ctrl[usbhid->ctrltail].dir; len = ((report->size - 1) >> 3) + 1 + (report->id > 0); if (dir == USB_DIR_OUT) { hid_output_report(report, usbhid->ctrlbuf); usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0); usbhid->urbctrl->transfer_buffer_length = len; } else { int maxpacket, padlen; usbhid->urbctrl->pipe = usb_rcvctrlpipe(hid_to_usb_dev(hid), 0); maxpacket = usb_maxpacket(hid_to_usb_dev(hid), usbhid->urbctrl->pipe, 0); if (maxpacket > 0) { padlen = (len + maxpacket - 1) / maxpacket; padlen *= maxpacket; if (padlen > usbhid->bufsize) padlen = usbhid->bufsize; } else padlen = 0; usbhid->urbctrl->transfer_buffer_length = padlen; } usbhid->urbctrl->dev = hid_to_usb_dev(hid); usbhid->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | dir; usbhid->cr->bRequest = (dir == USB_DIR_OUT) ? HID_REQ_SET_REPORT : HID_REQ_GET_REPORT; usbhid->cr->wValue = cpu_to_le16(((report->type + 1) << 8) | report->id); usbhid->cr->wIndex = cpu_to_le16(usbhid->ifnum); usbhid->cr->wLength = cpu_to_le16(len); dbg_hid("submitting ctrl urb: %s wValue=0x%04x wIndex=0x%04x wLength=%u\n", usbhid->cr->bRequest == HID_REQ_SET_REPORT ? "Set_Report" : "Get_Report", usbhid->cr->wValue, usbhid->cr->wIndex, usbhid->cr->wLength); if (usb_submit_urb(usbhid->urbctrl, GFP_ATOMIC)) { err_hid("usb_submit_urb(ctrl) failed"); return -1; } return 0;}/* * Output interrupt completion handler. */static void hid_irq_out(struct urb *urb){ struct hid_device *hid = urb->context; struct usbhid_device *usbhid = hid->driver_data; unsigned long flags; int unplug = 0; switch (urb->status) { case 0: /* success */ break; case -ESHUTDOWN: /* unplug */ unplug = 1; case -EILSEQ: /* protocol error or unplug */ case -EPROTO: /* protocol error or unplug */ case -ECONNRESET: /* unlink */ case -ENOENT: break; default: /* error */ warn("output irq status %d received", urb->status); } spin_lock_irqsave(&usbhid->outlock, flags); if (unplug) usbhid->outtail = usbhid->outhead; else usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1); if (usbhid->outhead != usbhid->outtail) { if (hid_submit_out(hid)) { clear_bit(HID_OUT_RUNNING, &usbhid->iofl); wake_up(&hid->wait); } spin_unlock_irqrestore(&usbhid->outlock, flags); return; } clear_bit(HID_OUT_RUNNING, &usbhid->iofl); spin_unlock_irqrestore(&usbhid->outlock, flags); wake_up(&hid->wait);}/* * Control pipe completion handler. */static void hid_ctrl(struct urb *urb){ struct hid_device *hid = urb->context; struct usbhid_device *usbhid = hid->driver_data; unsigned long flags; int unplug = 0; spin_lock_irqsave(&usbhid->ctrllock, flags); switch (urb->status) { case 0: /* success */ if (usbhid->ctrl[usbhid->ctrltail].dir == USB_DIR_IN) hid_input_report(urb->context, usbhid->ctrl[usbhid->ctrltail].report->type, urb->transfer_buffer, urb->actual_length, 0); break; case -ESHUTDOWN: /* unplug */ unplug = 1; case -EILSEQ: /* protocol error or unplug */ case -EPROTO: /* protocol error or unplug */ case -ECONNRESET: /* unlink */ case -ENOENT:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -