patch-2.2.7 linux/drivers/usb/keyboard.c

Next file: linux/drivers/usb/keymap.c
Previous file: linux/drivers/usb/inits.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.2.6/linux/drivers/usb/keyboard.c linux/drivers/usb/keyboard.c
@@ -0,0 +1,226 @@
+#include <linux/kernel.h>
+#include <linux/malloc.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/kbd_ll.h>
+#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;
+};
+
+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)
+    {
+        if(scancode & PCKBD_NEEDS_E0)
+        {
+            handle_scancode(0xe0, 1);
+        }
+        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, void *dev_id)
+{
+    struct usb_keyboard *kbd = (struct usb_keyboard*) dev_id;
+    unsigned long *down = (unsigned long*) buffer;
+
+    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;
+
+    interface = &dev->config[0].interface[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;
+
+        usb_set_configuration(dev, dev->config[0].bConfigurationValue);
+        usb_set_protocol(dev, 0);
+        usb_set_idle(dev, 0, 0);
+        
+        usb_request_irq(dev,
+                        usb_rcvctrlpipe(dev, endpoint->bEndpointAddress),
+                        usb_kbd_irq,
+                        endpoint->bInterval,
+                        kbd);
+
+        list_add(&kbd->list, &usb_kbd_list);
+    }
+
+    return 0;
+}
+
+static void
+usb_kbd_disconnect(struct usb_device *dev)
+{
+    struct usb_keyboard *kbd = (struct usb_keyboard*) dev->private;
+    if(kbd)
+    {
+        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;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)