/* Fixes: 1999/09/04 - Arnaldo Carvalho de Melo Handle states in usb_kbd_irq 1999/09/06 - Arnaldo Carvalho de Melo rmmod usb-keyboard doesn't crash the system anymore: the irq handlers are correctly released with usb_release_irq */ #include #include #include #include #include #include #include #include #include "usb.h" #define PCKBD_PRESSED 0x00 #define PCKBD_RELEASED 0x80 #define PCKBD_NEEDS_E0 0x80 #define USBKBD_MODIFIER_BASE 120 #define USBKBD_KEYCODE_OFFSET 2 #define USBKBD_KEYCODE_COUNT 6 #define USBKBD_VALID_KEYCODE(key) ((unsigned char)(key) > 3) #define USBKBD_FIND_KEYCODE(down, key, count) \ ((unsigned char*) memscan((down), (key), (count)) < ((down) + (count))) #define USBKBD_REPEAT_DELAY (HZ / 4) #define USBKBD_REPEAT_RATE (HZ / 20) struct usb_keyboard { struct usb_device *dev; unsigned long down[2]; unsigned char repeat_key; struct timer_list repeat_timer; struct list_head list; unsigned int irqpipe; void *irq_handler; /* host controller's IRQ transfer handle */ }; extern unsigned char usb_kbd_map[]; static int usb_kbd_probe(struct usb_device *dev); static void usb_kbd_disconnect(struct usb_device *dev); static void usb_kbd_repeat(unsigned long dummy); static LIST_HEAD(usb_kbd_list); static struct usb_driver usb_kbd_driver = { "keyboard", usb_kbd_probe, usb_kbd_disconnect, {NULL, NULL} }; static void usb_kbd_handle_key(unsigned char key, int down) { int scancode = (int) usb_kbd_map[key]; if(scancode) { #ifndef CONFIG_MAC_KEYBOARD if(scancode & PCKBD_NEEDS_E0) { handle_scancode(0xe0, 1); } #endif /* CONFIG_MAC_KEYBOARD */ handle_scancode((scancode & ~PCKBD_NEEDS_E0), down); } } static void usb_kbd_repeat(unsigned long dev_id) { struct usb_keyboard *kbd = (struct usb_keyboard*) dev_id; unsigned long flags; save_flags(flags); cli(); if(kbd->repeat_key) { usb_kbd_handle_key(kbd->repeat_key, 1); /* reset repeat timer */ kbd->repeat_timer.function = usb_kbd_repeat; kbd->repeat_timer.expires = jiffies + USBKBD_REPEAT_RATE; kbd->repeat_timer.data = (unsigned long) kbd; kbd->repeat_timer.prev = NULL; kbd->repeat_timer.next = NULL; add_timer(&kbd->repeat_timer); } restore_flags(flags); } static int usb_kbd_irq(int state, void *buffer, int len, void *dev_id) { struct usb_keyboard *kbd = (struct usb_keyboard*) dev_id; unsigned long *down = (unsigned long*) buffer; /* * USB_ST_NOERROR is the normal case. * USB_ST_REMOVED occurs if keyboard disconnected * On other states, ignore */ switch (state) { case USB_ST_REMOVED: case USB_ST_INTERNALERROR: printk(KERN_DEBUG "%s(%d): Suspending\n", __FILE__, __LINE__); return 0; /* disable */ case USB_ST_NOERROR: break; default: return 1; /* ignore */ } if(kbd->down[0] != down[0] || kbd->down[1] != down[1]) { unsigned char *olddown, *newdown; unsigned char modsdelta, key; int i; /* handle modifier change */ modsdelta = (*(unsigned char*) down ^ *(unsigned char*) kbd->down); if(modsdelta) { for(i = 0; i < 8; i++) { if(modsdelta & 0x01) { int pressed = (*(unsigned char*) down >> i) & 0x01; usb_kbd_handle_key( i + USBKBD_MODIFIER_BASE, pressed); } modsdelta >>= 1; } } olddown = (unsigned char*) kbd->down + USBKBD_KEYCODE_OFFSET; newdown = (unsigned char*) down + USBKBD_KEYCODE_OFFSET; /* handle released keys */ for(i = 0; i < USBKBD_KEYCODE_COUNT; i++) { key = olddown[i]; if(USBKBD_VALID_KEYCODE(key) && !USBKBD_FIND_KEYCODE(newdown, key, USBKBD_KEYCODE_COUNT)) { usb_kbd_handle_key(key, 0); } } /* handle pressed keys */ kbd->repeat_key = 0; for(i = 0; i < USBKBD_KEYCODE_COUNT; i++) { key = newdown[i]; if(USBKBD_VALID_KEYCODE(key) && !USBKBD_FIND_KEYCODE(olddown, key, USBKBD_KEYCODE_COUNT)) { usb_kbd_handle_key(key, 1); kbd->repeat_key = key; } } /* set repeat timer if any keys were pressed */ if(kbd->repeat_key) { del_timer(&kbd->repeat_timer); kbd->repeat_timer.function = usb_kbd_repeat; kbd->repeat_timer.expires = jiffies + USBKBD_REPEAT_DELAY; kbd->repeat_timer.data = (unsigned long) kbd; kbd->repeat_timer.prev = NULL; kbd->repeat_timer.next = NULL; add_timer(&kbd->repeat_timer); } kbd->down[0] = down[0]; kbd->down[1] = down[1]; } return 1; } static int usb_kbd_probe(struct usb_device *dev) { struct usb_interface_descriptor *interface; struct usb_endpoint_descriptor *endpoint; struct usb_keyboard *kbd; int ret; if (dev->descriptor.bNumConfigurations < 1) return -1; interface = &dev->config[0].interface[0].altsetting[0]; endpoint = &interface->endpoint[0]; if(interface->bInterfaceClass != 3 || interface->bInterfaceSubClass != 1 || interface->bInterfaceProtocol != 1) { return -1; } printk(KERN_INFO "USB HID boot protocol keyboard detected.\n"); kbd = kmalloc(sizeof(struct usb_keyboard), GFP_KERNEL); if(kbd) { memset(kbd, 0, sizeof(*kbd)); kbd->dev = dev; dev->private = kbd; if (usb_set_configuration(dev, dev->config[0].bConfigurationValue)) { printk (KERN_INFO " Failed usb_set_configuration: kbd\n"); goto probe_err; } usb_set_protocol(dev, 0); usb_set_idle(dev, 0, 0); kbd->irqpipe = usb_rcvctrlpipe(dev, endpoint->bEndpointAddress); ret = usb_request_irq(dev, kbd->irqpipe, usb_kbd_irq, endpoint->bInterval, kbd, &kbd->irq_handler); if (ret) { printk(KERN_INFO "usb-keyboard failed usb_request_irq (0x%x)\n", ret); goto probe_err; } list_add(&kbd->list, &usb_kbd_list); return 0; } probe_err: if (kbd) kfree (kbd); return -1; } static void usb_kbd_disconnect(struct usb_device *dev) { struct usb_keyboard *kbd = (struct usb_keyboard*) dev->private; if (kbd) { usb_release_irq(dev, kbd->irq_handler, kbd->irqpipe); dev->private = NULL; list_del(&kbd->list); del_timer(&kbd->repeat_timer); kfree(kbd); } printk(KERN_INFO "USB HID boot protocol keyboard removed.\n"); } int usb_kbd_init(void) { usb_register(&usb_kbd_driver); return 0; } void usb_kbd_cleanup(void) { struct list_head *cur, *head = &usb_kbd_list; cur = head->next; while (cur != head) { struct usb_keyboard *kbd = list_entry(cur, struct usb_keyboard, list); cur = cur->next; list_del(&kbd->list); INIT_LIST_HEAD(&kbd->list); if (kbd->irq_handler) { usb_release_irq(kbd->dev, kbd->irq_handler, kbd->irqpipe); /* never keep a reference to a released IRQ! */ kbd->irq_handler = NULL; } } usb_deregister(&usb_kbd_driver); } #ifdef MODULE int init_module(void) { return usb_kbd_init(); } void cleanup_module(void) { usb_kbd_cleanup(); } #endif