Diensteanbieter: Michael Stürmer · <ms@www.mallorn.de> π

Optimus Maximus and Linux

Setup

My keyboard arrived with firmware version 0.61b, and the Configurator (I use version 0.0.0.6) requested an update to 0.62b for which Windows drivers and tools are available from the vendor website. Updating first failed when I tried it on Windows 2000 running inside VMware, but succeded under native Windows Vista.

All further tests were done with the 0.62b firmware, Linux 2.6.23.11 and when necessary Win 2k in a VMware 5.5.4 build-44386 (i686 in 32-bit mode).

Interfacing

Besides a standard USB HID v1.10 Device, a USB Mass Storage devices is available (NXP Semi LPC288x Disk), which provides a FAT file system that simply can be mounted.

I have usbmount running, which picked it up and provided a symlink in '/var/run/usbmount/NXP_Semi_LPC288x_Disk' which is what I will use further. The root directory contains only one entry: a subdirectory named 'vOptimus'.

The 'vOptimus' directory

The vOptimus directory contains:

NameSize
dynamic/dynamic layout images
normal/normal layout images
shift/shifted layout images
layout.sys114dynamic layout selection?
order.sys4 at boot, 512 in win???
present.sys512key display availability?
scancode.sys9216scancodes?
upgrade.sys65536firmware upgrade facility?
version.sys512firmware version string

There are apparently two default image sets for the keyboard: 'normal/' and 'shift/' contain each 114 files with images for each key, unshifted and shifted. 'dynamic' contains a third set of images that can be switched on separately for every key.

'layout.sys' contains one byte for every key:

0x00Normal/shift layout active
0x01Dynamic layout active

'present.sys' could contain a 0x01 for every actually present key display, but as I have a fully populated keyboard, I am not sure.

'order.sys' is still a mystery. I had hoped for brightness and sleeptime control, but the values are inconclusive. On boot, only 4 zero-bytes can be read; after using the configurator, the first 6 bytes seem to have meaning, they are followed by 0x77 until EOF. Interestingly, the values I have seen are in the range of readable ASCII characters (mainly numbers).

'scancode.sys' contains scancodes. There is space for 0x40 bytes for each key. My first guess was that the first byte is the length, but that's wrong. [This part is from the preliminary spec] Every word is a movement:

Byte 1Byte 2
0xFF = end marker
else it's a bit field:
Bit 0 = standard key function
Bit 1 = key make (press)
Bit 2 = key break
Scancode

'upgrade.bin' is a write-only file for firmware upgrades: "_upgrade" + firmware data + checksum [This part is from the preliminary spec]

Images

The image format is simple: 2 bytes for every pixel, 2 * 48 * 48 = 4608, which is the size of a single image file.

The bytes are used like that (green gets one bit more):

hilo
7654321076543210
rrrrrggggggbbbbb

Most of the displays are built in so that the first pixel is top-left, only the numeric '+' and 'enter' key displays are rotated 90 degrees left.

Scancodes

My first guess was that scancodes are stored in order of key numbers, but it doesn't add up.

By trial and error, I discovered that position 76*0x40 in scancode.sys is responsible for the key usually labeled 'L': changing that position to e.g. 0x38 changes the resulting character to a '/'. Originally, that position contains scancode 0x0f, which - surprise - maps to 'L' according to the 'Universal Serial Bus HID Usage Tables' version 1.12 chapter 10 on page 53 (get them from http://www.usb.org/developers/hidpage/).

With the help of 'optimus scancode-discover-map all', I tried to create a scancode table. It shows the key number (found by writing an image in the dynamic image file with that number), the raw scancode xev returns, the corresponding linux scancode (found by subtracting 8 - this is just a guess, but seems to work here; showkey -s returns the same values on the console), the corresponding USB Usage Id (as defined in the linux kernel HID driver) and the byte position in scancode.sys where the id can be found.

I tried to swap undefined and unusual USB Usage Ids by free ones, but none of those did show up.

These are the found mappings:

keynum   1 = xev keycode  90 = linux scancode  82 = usb id 0x62
keynum   2 = xev keycode  91 = linux scancode  83 = usb id 0x63
keynum   3 = xev keycode 108 = linux scancode 100 = usb id 0xe6
keynum   4 = xev keycode  89 = linux scancode  81 = usb id 0x5b = pos 0x71
keynum   5 = xev keycode  88 = linux scancode  80 = usb id 0x5a = pos 0x69
keynum   6 = xev keycode  87 = linux scancode  79 = usb id 0x59 = pos 0x61
keynum   8 = xev keycode  85 = linux scancode  77 = usb id 0x5e = pos 0x51
keynum   9 = xev keycode  84 = linux scancode  76 = usb id 0x5d = pos 0x49
keynum  10 = xev keycode  83 = linux scancode  75 = usb id 0x5c = pos 0x41
keynum  11 = xev keycode  79 = linux scancode  71 = usb id 0x5f = pos 0x21
keynum  12 = xev keycode  80 = linux scancode  72 = usb id 0x60 = pos 0x29
keynum  13 = xev keycode  81 = linux scancode  73 = usb id 0x61 = pos 0x31
keynum  14 = xev keycode  86 = linux scancode  78 = usb id 0x57 = pos 0x39
keynum  15 = xev keycode  82 = linux scancode  74 = usb id 0x56 = pos 0x19
keynum  16 = xev keycode  63 = linux scancode  55 = usb id 0x55 = pos 0x11
keynum  17 = xev keycode 112 = linux scancode 104 = usb id 0x4b = pos 0x2a
keynum  18 = xev keycode  77 = linux scancode  69 = usb id 0x53 = pos 0x01
keynum  19 = xev keycode  99 = linux scancode  91 = usb id 0x93 = pos 0x4f
keynum  20 = xev keycode  97 = linux scancode  89 = usb id 0x87
keynum  21 = xev keycode 106 = linux scancode  98 = usb id 0x54 = pos 0x09
keynum  22 = xev keycode 107 = linux scancode  99 = usb id 0x46 = pos 0x02
keynum  23 = xev keycode 103 = linux scancode  95 = usb id 0x8c
keynum  24 = xev keycode 105 = linux scancode  97 = usb id 0xe4
keynum  25 = xev keycode 102 = linux scancode  94 = usb id 0x8b
keynum  26 = xev keycode 104 = linux scancode  96 = usb id 0x58
keynum  27 = xev keycode  98 = linux scancode  90 = usb id 0x92
keynum  28 = xev keycode 100 = linux scancode  92 = usb id 0x8a
keynum  29 = xev keycode 109 = linux scancode 101
keynum  30 = xev keycode  62 = linux scancode  54 = usb id 0xe5
keynum  31 = no reaction                                                   # right windows key
keynum  32 = xev keycode 113 = linux scancode 105 = usb id 0x50 = pos 0x52
keynum  33 = xev keycode  61 = linux scancode  53 = usb id 0x38 = pos 0x53
keynum  34 = xev keycode  48 = linux scancode  40 = usb id 0x34 = pos 0x5c
keynum  35 = xev keycode  36 = linux scancode  28 = usb id 0x28 = pos 0x64
keynum  36 = xev keycode  51 = linux scancode  43 = usb id 0x32
keynum  37 = xev keycode  35 = linux scancode  27 = usb id 0x30 = pos 0x65
keynum  38 = xev keycode  34 = linux scancode  26 = usb id 0x2f = pos 0x5d
keynum  39 = xev keycode  22 = linux scancode  14 = usb id 0x2a = pos 0x6e
keynum  40 = xev keycode  21 = linux scancode  13 = usb id 0x2e = pos 0x66
keynum  41 = xev keycode  20 = linux scancode  12 = usb id 0x2d = pos 0x5e
keynum  42 = xev keycode  19 = linux scancode  11 = usb id 0x27 = pos 0x56
keynum  43 = xev keycode  32 = linux scancode  24 = usb id 0x12 = pos 0x4d # 'O'
keynum  44 = xev keycode  33 = linux scancode  25 = usb id 0x13 = pos 0x55
keynum  45 = xev keycode  47 = linux scancode  39 = usb id 0x33 = pos 0x54
keynum  46 = xev keycode  46 = linux scancode  38 = usb id 0x0f = pos 0x4c # 'L'
keynum  47 = xev keycode  60 = linux scancode  52 = usb id 0x37 = pos 0x4b
keynum  48 = xev keycode  59 = linux scancode  51 = usb id 0x36 = pos 0x43
keynum  49 = xev keycode  58 = linux scancode  50 = usb id 0x10 = pos 0x3b
keynum  50 = xev keycode  57 = linux scancode  49 = usb id 0x11 = pos 0x33
keynum  51 = xev keycode  44 = linux scancode  36 = usb id 0x0d = pos 0x3c
keynum  52 = xev keycode  45 = linux scancode  37 = usb id 0x0e = pos 0x44
keynum  53 = xev keycode  31 = linux scancode  23 = usb id 0x0c = pos 0x45
keynum  54 = xev keycode  30 = linux scancode  22 = usb id 0x18 = pos 0x3d
keynum  55 = xev keycode  18 = linux scancode  10 = usb id 0x26 = pos 0x4e
keynum  56 = xev keycode  17 = linux scancode   9 = usb id 0x25 = pos 0x46
keynum  57 = xev keycode  16 = linux scancode   8 = usb id 0x24 = pos 0x3e
keynum  58 = xev keycode  15 = linux scancode   7 = usb id 0x23 = pos 0x36
keynum  59 = xev keycode  28 = linux scancode  20 = usb id 0x17 = pos 0x2d
keynum  60 = xev keycode  29 = linux scancode  21 = usb id 0x1c = pos 0x35
keynum  61 = xev keycode  43 = linux scancode  35 = usb id 0x0b = pos 0x34
keynum  62 = xev keycode  42 = linux scancode  34 = usb id 0x0a = pos 0x2c
keynum  63 = xev keycode  55 = linux scancode  47 = usb id 0x19 = pos 0x23
keynum  64 = xev keycode  56 = linux scancode  48 = usb id 0x05 = pos 0x2b
keynum  65 = xev keycode  54 = linux scancode  46 = usb id 0x06 = pos 0x1b
keynum  66 = xev keycode  53 = linux scancode  45 = usb id 0x1b = pos 0x13
keynum  67 = xev keycode  41 = linux scancode  33 = usb id 0x09 = pos 0x24
keynum  68 = xev keycode  40 = linux scancode  32 = usb id 0x07 = pos 0x1c
keynum  69 = xev keycode  26 = linux scancode  18 = usb id 0x08 = pos 0x1d
keynum  70 = xev keycode  27 = linux scancode  19 = usb id 0x15 = pos 0x25
keynum  71 = xev keycode  14 = linux scancode   6 = usb id 0x22 = pos 0x2e
keynum  72 = xev keycode  13 = linux scancode   5 = usb id 0x21 = pos 0x26
keynum  73 = xev keycode  12 = linux scancode   4 = usb id 0x20 = pos 0x1e
keynum  74 = xev keycode  11 = linux scancode   3 = usb id 0x1f = pos 0x16
keynum  75 = xev keycode  25 = linux scancode  17 = usb id 0x1a = pos 0x15
keynum  76 = xev keycode  39 = linux scancode  31 = usb id 0x16 = pos 0x14
keynum  77 = xev keycode  52 = linux scancode  44 = usb id 0x1d = pos 0x0b
keynum  78 = xev keycode  65 = linux scancode  57 = usb id 0x2c = pos 0x6f
keynum  79 = xev keycode  64 = linux scancode  56 = usb id 0xe2
keynum  80 = xev keycode 115 = linux scancode 107 = usb id 0x4d = pos 0x3a
keynum  81 = xev keycode  37 = linux scancode  29 = usb id 0xe0
keynum  82 = xev keycode  50 = linux scancode  42 = usb id 0xe1
keynum  83 = xev keycode  38 = linux scancode  30 = usb id 0x04 = pos 0x0c
keynum  84 = xev keycode  66 = linux scancode  58 = usb id 0x39 = pos 0x04
keynum  85 = xev keycode  23 = linux scancode  15 = usb id 0x2b = pos 0x05
keynum  86 = xev keycode  24 = linux scancode  16 = usb id 0x14 = pos 0x0d
keynum  87 = xev keycode  10 = linux scancode   2 = usb id 0x1e = pos 0x0e
keynum  88 = xev keycode  49 = linux scancode  41 = usb id 0x35 = pos 0x06
keynum  89 = xev keycode 208 = linux scancode 200
keynum  90 = no reaction                                                   " one of the left function keys
keynum  91 = xev keycode 133 = linux scancode 125 = usb id 0xe3
keynum  92 = xev keycode 129 = linux scancode 121 = usb id 0x85
keynum  93 = xev keycode 134 = linux scancode 126 = usb id 0xe7
keynum  94 = xev keycode 160 = linux scancode 152 = usb id 0xf9
keynum  95 = xev keycode 174 = linux scancode 166 = usb id 0xe9
keynum  96 = xev keycode 184 = linux scancode 176 = usb id 0xf7
keynum  97 = xev keycode 162 = linux scancode 154
keynum  98 = xev keycode 211 = linux scancode 203
keynum  99 = xev keycode   9 = linux scancode   1 = usb id 0x29 = pos 0x08
keynum 100 = xev keycode  67 = linux scancode  59 = usb id 0x3a = pos 0x10
keynum 101 = xev keycode  68 = linux scancode  60 = usb id 0x3b = pos 0x18
keynum 102 = xev keycode  69 = linux scancode  61 = usb id 0x3c = pos 0x20
keynum 103 = xev keycode  70 = linux scancode  62 = usb id 0x3d = pos 0x28
keynum 104 = xev keycode  71 = linux scancode  63 = usb id 0x3e = pos 0x30
keynum 105 = xev keycode  72 = linux scancode  64 = usb id 0x3f = pos 0x38
keynum 106 = xev keycode  73 = linux scancode  65 = usb id 0x40 = pos 0x40
keynum 107 = xev keycode  74 = linux scancode  66 = usb id 0x41 = pos 0x48
keynum 108 = xev keycode  75 = linux scancode  67 = usb id 0x42 = pos 0x50
keynum 109 = xev keycode  76 = linux scancode  68 = usb id 0x43 = pos 0x58
keynum 110 = xev keycode  95 = linux scancode  87 = usb id 0x44 = pos 0x60
keynum 111 = xev keycode  96 = linux scancode  88 = usb id 0x45 = pos 0x68
keynum 112 = xev keycode 111 = linux scancode 103 = usb id 0x52 = pos 0x4a
keynum 113 = xev keycode  78 = linux scancode  70 = usb id 0x47 = pos 0x0a
keynum 114 = xev keycode 110 = linux scancode 102 = usb id 0x4a = pos 0x22

These are the codes I tried to swap (keynum means here the byte position in scancode.sys):

replacing scancode 9d with 68 at keynum 7
replacing scancode 9b with 69 at keynum 15
replacing scancode a3 with 6a at keynum 39
replacing scancode 9c with 6b at keynum 47
replacing scancode 9a with 6c at keynum 55
replacing scancode a0 with 6d at keynum 71
replacing scancode 93 with 6e at keynum 79
replacing scancode ff with 6f at keynum 89
replacing scancode ff with 70 at keynum 99
replacing scancode ff with 71 at keynum 106
replacing scancode ff with 76 at keynum 107
replacing scancode ff with 78 at keynum 108
replacing scancode ff with 79 at keynum 112
replacing scancode ff with 7a at keynum 114

The mapping 'USB Usage ID' => 'linux scancode' (output of showkey -s) is defined in '/usr/src/linux/drivers/hid/usbhid/usbkbd.c'.

.../xorg-server-1.4.1~git20080131/hw/kdrive/linux/ contains some stuff for xorg keyboard handling. From here, I have the -8 offset.

Reassigning scancodes of ordinary keys seems to work, but I had no success with reassigning the left function keys.

The configurator 0.0.8 allows to remap keys to other verbs, so after looking at the changes, swapping 'Caps Lock' and 'Ctrl' is as simple as 'optimus scancode-set 4 72 87 39'. Still no luck on the keynum to scancode.sys-position mapping.

Understanding Brightness and Sleep Control

As the interaction of the configurator and keyboard is not so easily guessed, I fired up usbmon (now in binary mode with the usbmon userspace tool) and took this trace while toggleing the 'adjust automatically' option:

01  f7d11700 75.846110 S Bo:2:004:1 - 31 = 55534243 88c87981 00020000 00000a2a 0000000d 84000001 00000000 000000
02  f7d11700 75.846150 C Bo:2:004:1 0 31 >
03  f7d11180 75.848094 S Bo:2:004:1 - 512 = 62303533 41007777 77777777 77777777 77777777 77777777 77777777 77777777
04  f7d11180 75.848149 C Bo:2:004:1 0 512 >
05  f7d11180 75.850084 S Bi:2:004:1 - 512 <
06  f7d11180 75.850900 C Bi:2:004:1 0 13 = 55534253 88c87981 00000000 00
07  f644c500 77.600350 S Bo:2:004:1 - 31 = 55534243 480e7e81 00020000 00000a2a 0000000d 84000001 00000000 000000
08  f644c500 77.600461 C Bo:2:004:1 0 31 >
09  f727cf00 77.602110 S Bo:2:004:1 - 512 = 62303533 00007777 77777777 77777777 77777777 77777777 77777777 77777777
10  f727cf00 77.602206 C Bo:2:004:1 0 512 >
11  f727cf00 77.604093 S Bi:2:004:1 - 512 <
12  f727cf00 77.604831 C Bi:2:004:1 0 13 = 55534253 480e7e81 00000000 00
13  f7d11680 94.981107 S Bo:2:004:1 - 31 = 55534243 280c7e81 00020000 00000a2a 0000000d 84000001 00000000 000000
14  f7d11680 94.981162 C Bo:2:004:1 0 31 >
15  f7d11600 94.983284 S Bo:2:004:1 - 512 = 62303533 41007777 77777777 77777777 77777777 77777777 77777777 77777777
16  f7d11600 94.983409 C Bo:2:004:1 0 512 >
17  f7d11700 94.985093 S Bi:2:004:1 - 512 <
18  f7d11700 94.986159 C Bi:2:004:1 0 13 = 55534253 280c7e81 00000000 00
19  f7d11c00 95.753292 S Bo:2:004:1 - 31 = 55534243 a8cd8981 00020000 00000a2a 0000000d 84000001 00000000 000000
20  f7d11c00 95.753380 C Bo:2:004:1 0 31 >
21  f7d11c00 95.770114 S Bo:2:004:1 - 512 = 62303533 00007777 77777777 77777777 77777777 77777777 77777777 77777777
22  f7d11c00 95.770255 C Bo:2:004:1 0 512 >
23  f644c400 95.786370 S Bi:2:004:1 - 512 <
24  f644c400 95.786505 C Bi:2:004:1 0 13 = 55534253 a8cd8981 00000000 00

Mhm. 'USB Mass Storage BOT' http://www.usb.org/developers/devclass_docs/usbmassbulk_10.pdf (linked from Wikipedia's 'USB mass storage device class' http://en.wikipedia.org/wiki/USB_mass_storage_device_class) gives a clue: 55534243 is the Command Block Wrapper Signature (p. 13) backwards and the length matches, too.

BOT specifies little endian endcoding, but usbmon outputs big endian, so we have to be careful with the values.

0-3      4-7      8-11     12 13 14 15-30
55534243 88c87981 00020000 00 00 0a 2a0000000d8400000100000000000000
^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
!        !        !        !  !  !  !
!        !        !        !  !  !  CBWCB = 
!        !        !        !  !  bCBWCBLength
!        !        !        !  bCBWLUN/Reserved
!        !        !        bmCBWFlags/Reserved, Bit 7 (Direction) = 0 = Data-Out from host to the device
!        !        dCBWDataTransferLength = 200h
!        dCBWTag
dCBWSignature = 43425355h (little endian)

According to the SCSI-3 Block Command Set (http://www.t10.org/ftp/t10/drafts/sbc/sbc-r08c.pdf) p. 57, 2Ah is the WRITE(10) command:

0  1  2-5      6  7-8  9
2a 00 00000d84 00 0001 00 000000000000
^^ ^^ ^^^^^^^^ ^^ ^^^^ ^^
!  !  !        !  !    !
!  !  !        !  !    Control
!  !  !        !  Transfer length = 1 block
!  !  !        Reserved
!  !  Logical Block Address
!  Flags
Operation Code = 2Ah

So, all writes are going to the same block, the replies from the device are Command Status Wrapper informations.

For the filesystem I saw, here are the first sector numbers for the files:

NameSector #
layout.sys3462
order.sys3460
present.sys3458
scancode.sys3464
upgrade.bin3482
version.sys3456

So it's really 'order.sys'! Looking at the written data:

Taking Control

First things first: how to replace the windows logo with tux?

The changes stay as long as the keyboard has power, after a reboot, they are gone.

The next step is probably to create a clock or something and use the dynamic mode to frequently update it.

Optimus Biff

Just to show a nice application I have two scripts to show the state of my mail inbox:

Optimus keyboard with mailbox state

'optimus' tool

For testing purposes I'm providing the 'optimus' tool which modifies the files in the vOptimus directory. It also provides 'png2optimus' and 'optimus2png' (simply do 'ln optimus png2optimus; ln optimus optimus2png')

Requires perl with additional modules: GD, X11::Protocol, Getopt::Long.

WARNING! This stuff comes with NO WARRANTY! It might blow up your keyboard, change voltage settings, burn out the oleds by prohibiting sleep mode, overwrite the firmware, wear the flash, and so on.

Latest version is optimus-20090201.tar.gz (15.70 KiB).

Syntax: optimus [options] [cmd] [arguments]

options are:
  --voptimus=[ path ]                          path to vOptimus directory
  --display=[ name ]                           X display name
  --red                                        display text on red background
  --yellow                                     display text on yellow background
  --green                                      display text on green background
  --led                                        display a small green LED
  --bar=[ 1 - 6 ]                              display a bar with 1 - 6 elements

commands are:
  version                                      reads the version
  brightness [ 0 - 100 [ auto ] ]              set keyboard brightness
  sleep-timeout [ 0 - 9999 | never ]           set sleep timeout
  layout-set [ all | olednum [olednum] ...]    set the layout bit
  layout-clr [ all | olednum [olednum] ...]    clear the layout bit
  scancode-get [ all | keynum [keynum] ...]    reads the scancodes
  scancode-set [ keynum scancode [...] ]       sets the scancodes
  scancode-discover-map [ all | olednum ...]   interactive scancode map disc.
  disp-olednum                                 display oled number on keyboard
  disp-keynum                                  display key number on keyboard
  disp-scancode                                display scancodes on keyboard
  disp-png [ olednum filename [...] ]          display png image on key
  disp-text [ olednum text [...] ]             display text on key (dynamic)
  disp-text-normal [ olednum text [...] ]      display text on key (normal)
  disp-text-shift [ olednum text [...] ]       display text on key (shifted)

olednum is a decimal number between 1 and 114
keynum is a decimal number between 1 and 137
scancode is a string of bytes given in hex, e.g. 0105
  where every word aabb means (aa : 01 = normal function, 02 = mark, 04 = break;
                               bb : scancode, see USB HUT)
  alternatively, a decimal byte value can be prefixed with
    'k'  to try keycode conversion, e.g. k102,
    's'  to take the value as decimal scancode, e.g. s102.

Other noteworthy things


Interne links sind grün, externe links, die nicht unter meiner Kontrolle stehen, sind zusätzlich gestrichelt unterstrichen. Mallorn CA Zertifikat.

Copyright 2002-2011 Michael Stürmer · <ms@www.mallorn.de> · letzte Änderung am 18.05.2011 23:10.