]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[usb] Add support for numeric keypad on USB keyboards
authorMichael Brown <mcb30@ipxe.org>
Wed, 6 Jan 2016 16:30:01 +0000 (16:30 +0000)
committerMichael Brown <mcb30@ipxe.org>
Wed, 6 Jan 2016 18:55:08 +0000 (18:55 +0000)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/drivers/usb/usbkbd.c
src/drivers/usb/usbkbd.h
src/include/ipxe/usbhid.h

index 512adfe2f10c4bdef115e69713a462a722633137..76db5771ffe5b246c9ec9a1efe8e079dd4e3a521 100644 (file)
@@ -53,13 +53,14 @@ static LIST_HEAD ( usb_keyboards );
  *
  * @v keycode          Keycode
  * @v modifiers                Modifiers
+ * @v leds             LED state
  * @ret key            iPXE key
  *
  * Key codes are defined in the USB HID Usage Tables Keyboard/Keypad
  * page.
  */
-static unsigned int usbkbd_map ( unsigned int keycode,
-                                unsigned int modifiers ) {
+static unsigned int usbkbd_map ( unsigned int keycode, unsigned int modifiers,
+                                unsigned int leds ) {
        unsigned int key;
 
        if ( keycode < USBKBD_KEY_A ) {
@@ -70,7 +71,8 @@ static unsigned int usbkbd_map ( unsigned int keycode,
                key = ( keycode - USBKBD_KEY_A + 'a' );
                if ( modifiers & USBKBD_CTRL ) {
                        key -= ( 'a' - CTRL_A );
-               } else if ( modifiers & USBKBD_SHIFT ) {
+               } else if ( ( modifiers & USBKBD_SHIFT ) ||
+                           ( leds & USBKBD_LED_CAPS_LOCK ) ) {
                        key -= ( 'a' - 'A' );
                }
        } else if ( keycode <= USBKBD_KEY_0 ) {
@@ -100,7 +102,22 @@ static unsigned int usbkbd_map ( unsigned int keycode,
                        KEY_PPAGE, KEY_DC, KEY_END, KEY_NPAGE, KEY_RIGHT,
                        KEY_LEFT, KEY_DOWN, KEY_UP
                };
-               key = special[ keycode - USBKBD_KEY_CAPSLOCK ];
+               key = special[ keycode - USBKBD_KEY_CAPS_LOCK ];
+       } else if ( keycode <= USBKBD_KEY_PAD_ENTER ) {
+               /* Keypad (unaffected by Num Lock) */
+               key = "\0/*-+\n" [ keycode - USBKBD_KEY_NUM_LOCK ];
+       } else if ( keycode <= USBKBD_KEY_PAD_DOT ) {
+               /* Keypad (affected by Num Lock) */
+               if ( leds & USBKBD_LED_NUM_LOCK ) {
+                       key = "1234567890." [ keycode - USBKBD_KEY_PAD_1 ];
+               } else {
+                       static const uint16_t keypad[] = {
+                               KEY_END, KEY_DOWN, KEY_NPAGE, KEY_LEFT, 0,
+                               KEY_RIGHT, KEY_HOME, KEY_UP, KEY_PPAGE,
+                               KEY_IC, KEY_DC
+                       };
+                       key = keypad[ keycode - USBKBD_KEY_PAD_1 ];
+               };
        } else {
                key = 0;
        }
@@ -124,10 +141,25 @@ static unsigned int usbkbd_map ( unsigned int keycode,
  */
 static void usbkbd_produce ( struct usb_keyboard *kbd, unsigned int keycode,
                             unsigned int modifiers ) {
+       unsigned int leds = 0;
        unsigned int key;
 
+       /* Check for LED-modifying keys */
+       if ( keycode == USBKBD_KEY_CAPS_LOCK ) {
+               leds = USBKBD_LED_CAPS_LOCK;
+       } else if ( keycode == USBKBD_KEY_NUM_LOCK ) {
+               leds = USBKBD_LED_NUM_LOCK;
+       }
+
+       /* Handle LED-modifying keys */
+       if ( leds ) {
+               kbd->leds ^= leds;
+               kbd->leds_changed = 1;
+               return;
+       }
+
        /* Map to iPXE key */
-       key = usbkbd_map ( keycode, modifiers );
+       key = usbkbd_map ( keycode, modifiers, kbd->leds );
 
        /* Do nothing if this keycode has no corresponding iPXE key */
        if ( ! key ) {
@@ -333,6 +365,37 @@ static struct usb_endpoint_driver_operations usbkbd_operations = {
        .complete = usbkbd_complete,
 };
 
+/******************************************************************************
+ *
+ * Keyboard LEDs
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Set keyboard LEDs
+ *
+ * @v kbd              USB keyboard
+ * @ret rc             Return status code
+ */
+static int usbkbd_set_leds ( struct usb_keyboard *kbd ) {
+       struct usb_function *func = kbd->hid.func;
+       int rc;
+
+       DBGC2 ( kbd, "KBD %s setting LEDs to %#02x\n", kbd->name, kbd->leds );
+
+       /* Set keyboard LEDs */
+       if ( ( rc = usbhid_set_report ( func->usb, func->interface[0],
+                                       USBHID_REPORT_OUTPUT, 0, &kbd->leds,
+                                       sizeof ( kbd->leds ) ) ) != 0 ) {
+               DBGC ( kbd, "KBD %s could not set LEDs to %#02x: %s\n",
+                      kbd->name, kbd->leds, strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
 /******************************************************************************
  *
  * USB interface
@@ -400,6 +463,9 @@ static int usbkbd_probe ( struct usb_function *func,
        /* Add to list of USB keyboards */
        list_add_tail ( &kbd->list, &usb_keyboards );
 
+       /* Set initial LED state */
+       usbkbd_set_leds ( kbd );
+
        usb_func_set_drvdata ( func, kbd );
        return 0;
 
@@ -484,10 +550,20 @@ static int usbkbd_iskey ( void ) {
        struct usb_keyboard *kbd;
        unsigned int fill;
 
-       /* Poll all USB keyboards and refill endpoints */
+       /* Poll USB keyboards, refill endpoints, and set LEDs if applicable */
        list_for_each_entry ( kbd, &usb_keyboards, list ) {
+
+               /* Poll keyboard */
                usb_poll ( kbd->bus );
+
+               /* Refill endpoints */
                usb_refill ( &kbd->hid.in );
+
+               /* Update keyboard LEDs, if applicable */
+               if ( kbd->leds_changed ) {
+                       usbkbd_set_leds ( kbd );
+                       kbd->leds_changed = 0;
+               }
        }
 
        /* Check for a non-empty keyboard buffer */
index 7eab24e460e673ce277234490643dd3af2fbd139..cedebfe7131368effd00e78bd5ed9b403a6c784a 100644 (file)
@@ -68,8 +68,20 @@ enum usb_keycode {
        USBKBD_KEY_SPACE = 0x2c,
        USBKBD_KEY_MINUS = 0x2d,
        USBKBD_KEY_SLASH = 0x38,
-       USBKBD_KEY_CAPSLOCK = 0x39,
+       USBKBD_KEY_CAPS_LOCK = 0x39,
+       USBKBD_KEY_F1 = 0x3a,
        USBKBD_KEY_UP = 0x52,
+       USBKBD_KEY_NUM_LOCK = 0x53,
+       USBKBD_KEY_PAD_ENTER = 0x58,
+       USBKBD_KEY_PAD_1 = 0x59,
+       USBKBD_KEY_PAD_DOT = 0x63,
+};
+
+/** USB keyboard LEDs */
+enum usb_keyboard_led {
+       USBKBD_LED_NUM_LOCK = 0x01,
+       USBKBD_LED_CAPS_LOCK = 0x02,
+       USBKBD_LED_SCROLL_LOCK = 0x04,
 };
 
 /** Keyboard idle duration (in 4ms units)
@@ -120,6 +132,11 @@ struct usb_keyboard {
        /** Autorepeat hold-off time (in number of completions reported) */
        unsigned int holdoff;
 
+       /** Keyboard LED state */
+       uint8_t leds;
+       /** Keyboard LEDs changed */
+       uint8_t leds_changed;
+
        /** Keyboard buffer
         *
         * This stores iPXE key values.
index fe9d844554e767353b4fd357fd0d64446c762a34..233534e0f88b3b8afd3793b12450fa344b260af5 100644 (file)
@@ -33,6 +33,20 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
        ( USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE |          \
          USB_REQUEST_TYPE ( 0x0a ) )
 
+/** Set report */
+#define USBHID_SET_REPORT                                              \
+       ( USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE |          \
+         USB_REQUEST_TYPE ( 0x09 ) )
+
+/** Input report type */
+#define USBHID_REPORT_INPUT 0x01
+
+/** Output report type */
+#define USBHID_REPORT_OUTPUT 0x02
+
+/** Feature report type */
+#define USBHID_REPORT_FEATURE 0x03
+
 /** A USB human interface device */
 struct usb_hid {
        /** USB function */
@@ -97,6 +111,26 @@ usbhid_set_idle ( struct usb_device *usb, unsigned int interface,
                             interface, NULL, 0 );
 }
 
+/**
+ * Set report
+ *
+ * @v usb              USB device
+ * @v interface                Interface number
+ * @v type             Report type
+ * @v report           Report ID
+ * @v data             Report data
+ * @v len              Length of report data
+ * @ret rc             Return status code
+ */
+static inline __attribute__ (( always_inline )) int
+usbhid_set_report ( struct usb_device *usb, unsigned int interface,
+                   unsigned int type, unsigned int report, void *data,
+                   size_t len ) {
+
+       return usb_control ( usb, USBHID_SET_REPORT, ( ( type << 8 ) | report ),
+                            interface, data, len );
+}
+
 extern int usbhid_open ( struct usb_hid *hid );
 extern void usbhid_close ( struct usb_hid *hid );
 extern int usbhid_refill ( struct usb_hid *hid );