Hello,

   I like physical buttons, knobs, dials.
   As an extension of that, I quite enjoy
   messing with input devices. of course,
   that includes keyboards.

                        For the longest time, I used a unicomp
                        pc122 clone with buckling springs. But
                        recently I traded it in for a pinnacle
                        DEKO fast action keyboard.

   W a n t   S o m e    ---------------------------

             F F F F F F F A  S  T    -------------------

                             A C T I O N ? --------------------

   Right, so, this thing has the obiquious
   cherry switches, which,  while inferior
   to the buckling springs of the unicomp,
   it is vastly easier to find them fancy
   custom keycaps for these switches.

                          By default,  the DEKO keeb comes with
                          cherry MX black switches but they are
                          easy enough to switch out  should you
                          want.

   It has (quite a lot) more buttons than
   the PC122 (which already has more than
   your standard 102 key keyboard).

                      On top of that,  the  majority  of  those
                      extra keys have individually controllable
                      LEDs.

   AND on top of THAT, it has a built-in VFD
   display. This is reminicent of the old,
   more well-known logitech g15 keyboard,
   which came with a built-in LCD screen, but
   let's face it - vacuum fluorescence is way
   cooler than liquid crystals :)

                    All of the extra LEDS and the VFD are
                    controllable by sending PS2 commands to the
                    keyboard (right, it's a PS2 keyboard, none
                    of that USB nonesense. hardware interrupts,
                    baby!)

   Before I dig into the technical details on
   this thing, I'd like to point out just how
   nice it is to have dedicated buttons to
   launch various applications, control window
   manager functions, etc,... Iconify (minimize),
   Maximize, Close, various tiling ops, etc,
   are just so much quicker to execute from a
   keystroke, especially if it's not a layered
   combination of keys but literally a single
   button press. Definitively no need for none
   of that rodent crap (sorrynotsorry Rob Pike).

                     Moreover, all the extra keys come in handy
                     for applications like blender, where it's
                     useful to map various operations like edge
                     and loop cuts to dedicated keys, or send
                     cursor to origin, send cursor to selected,
                     etc... no more navigating through menus or
                     having to memorize combinations of key
                     sequences. Imagine showing the position in
                     3d space of the selected object in blender
                     on the vfd screen at all times, and the
                     number of tri's. I just love this stuff.



                                            .````.---_   |o|
   A bunch of years ago (I think I phlogged'     .    `.
   about it at the time) at vcfmw we went to      .___   \    |o|
   go visit the vector general that created ..... /   \   |,
   the Death Star trench animation in the        | ( ) |   |  <o>
   Star Wars films. The Vector General was a      \___/    |
   graphics terminal, which they had attached............../
   to a PDP11. They were using this thing for  .   .  .  .`
   CAD and 3d modeling, all the way back in `..   .  ._.`  /\
   1969! It supported a light pen, and had    ``````` |    ||    |
   physical buttons for rotating/panning the          |=-\[..]/-=|
   model etc,... Two things stuck with me              --/'``'\--
   from having actually had the chance to use
   this thing:


   1) Our modern software is not better, but
      actually much worse at many things. 3d
      graphics is not new. Obviousy, we now
      have texturing, lighting, etc,... and
      hardware that can handle an insane
      amount of triangles. But the actual
      modeling workflow back then seemed a lot
      more intuitive.

   2) Having physical buttons to rot/pan/tilt
   PHYSICAL knobs is actually REALLY handy and
   overall pretty damn awesome.

   So yes, knobs buttons, dials, sliders, they
   are good things to have! Physical things you
   can touch with a dedicated real meaning.

   All of this also contributes to how I loathe
   using mobile phones or even laptops. I like
   to do my computing on something that feels
   like an actual machine. A computer ought to
   be an art installation! :)

   Anyhow!

   Back to this keyboard... we hit a snag, right?
   How DO we send these fancy PS2 commands to
   the keyboard? After all, it's not like the
   OS just exposes the ps2 bus, does it?

   Right, typically, no, it doesn't.
   On GNU+Linux it can sort of be exposed, via
   the serio_raw interface, but it comes with the
   caveat that for as long as /dev/serio_raw exists,
   you can't actually type on the keyboard. You see,
   the Linux kernel serio driver is used for all
   communication to the ps2 bus, but only one device
   can have access to a bus at a time. So either the
   atkbd keyboard driver has access to it so you can
   type, or the serio_raw driver has access to it so
   you can send your commands, but not both at the
   same time. Nevertheless, you can sort-of-kind-of
   get around that problem with a little shell script:

==[ BEGIN SOURCE DUMP: vfdwrite.sh ]==============================.

#!/usr/bin/env bash

set -e

# Helper functions -------------------------------------------------

function load_module {
   set +e
   lsmod|grep serio_raw > /dev/null
   set -e
   if [ $? == 1 ]; then
       modprobe serio_raw
   fi
}

function get_serio_port {
   grep -H atkbd /sys/bus/serio/devices/*/uevent |\
   awk -F '/devices/' '{print $2}' |\
   cut -d/ -f1 |\
   tr -d '[a-zA-Z]'
}

function get_serio_raw_dev {
   ls -1v /dev/serio_raw* | tail -n1
}

function enable_serio_raw {
   PORT="${1}"
   echo -n "serio_raw" > /sys/bus/serio/devices/serio${PORT}/drvctl
}

function disable_serio_raw {
   PORT="${1}"
   echo -n "atkbd" > /sys/bus/serio/devices/serio${PORT}/drvctl
}

function vfd_write {
   DEV="${1}"
   INPUT_STR=$2
   OUTCMD="\xe4\x01"
   for (( i=0; i<${#INPUT_STR}; i++ )); do
       OUTCMD="${OUTCMD}\xe6${INPUT_STR:$i:1}"
   done
   echo -e -n "${OUTCMD}" > ${DEV}
}

# Entry point ------------------------------------------------------

INPUT_STR="$@"

if [ "${UID}" -ne 0 ]; then
   echo "WARNING: you probably should be running this as root."
   echo "Will likely fail."
fi

load_module
PORT="$(get_serio_port)"
enable_serio_raw "${PORT}"
RAW_DEV=$(get_serio_raw_dev)

vfd_write "${RAW_DEV}" "${INPUT_STR}"

disable_serio_raw "${PORT}"

/etc/local.d/setkeys.start
xmodmap ~/.xmodmap

`==[ END SOURCE DUMP ]============================================='


   So, what's with the last 2 lines?
   WELL, it turns out, when you detach the keyboard, and
   then reattach it, we lose the kernel keymap set, and
   X also loses it's keyboard map. So we have to reload
   them.

   In order for some of the 'extra' keys to register on
   Linux I use setkeys in /etc/local.d/setkeys.start to
   map them to... something. And then in turn, I map those
   somethings to other things in my ~/.xmodmap

   Here's what setkeys.start looks like:

==[ BEGIN SOURCE DUMP: setkeyus.start ]===========================.

#!/usr/bin/env bash

# Menu key
setkeycodes 6c 127
# Super key
setkeycodes 6d 125
# etc...

`==[ END SOURCE DUMP ]============================================='

All of this is kinda hacky,   but it was good enough for me to live
with for a while.   The one sad thing about this setup is that when
the linux keyboard driver retakes control after unloading serio_raw
it re-inits the keyboard,  which in turn turns off all the LEDS, so
that effectively made the LEDS uncontrollable on Linux.  Thankfully
the text on the VFD persists at least.   So, I already knew that at
some point, I was going to have to write a driver.

     [FF] Fast Forward to a couple of weeks ago. I've been wanting
          to move my main desktop machine away from GNU+Linux to
          FreeBSD for a while now, and I finally started the work
          on that.
.___.
|   |                  There is a couple of reasons why I want
| U |                  to move my main desktop to FreeBSD. The
| N |=                 biggest reason being that I've been kind
| H |                  of getting more and more increasingly
| I |                  dissapointed with the GNU+Linux direction
| N |                  and communities as a whole. Most GNU+Linux
| G |                  conversation and development seems to have
| E |                  been taken over by pro-corporate opensource
| D |                  people, dead set on scorched earth when it
|   |                  comes to replacing tried and true /working/
| R |                  tools with shitty 'modern' implementations.
| A |                  It started with systemd, then pulseaudio,
| N |                  then for whatever reason, ifconfig had to
| T |                  be replaced with 'ip', a tool with less
|   |                  usability and undeniably worse output, now
| W |                  wayland, and fuck iptables too I guess. The
| A |                  list just goes on and on. Instead of fixing
| R |                  problems and tech debt in existing working
| N |                  software, these kids' egos are just so
| I |                  inflated that they think that THEIR version
| N |=                 is going to fix all the problems without
| G |                  re-implementing all the pitfalls. Whatever.
|___|                  All of this is driven by companies like
'   '                  redhat who dictate the direction of a lot
                       of this stuff.

   That's not to say that there isn't problems
   with the things they replace, but just because
   something has problems, that doesn't mean you
   have to replace it. You fix those problems over
   time. That's what software maintenance is. But
   maintenance is not exciting, I guess. Writing
   something new is more fun, especially if you can
   slap your name on it, whatever,... I'm just venting
   at this point, as I like to do. :) The problem
   is of course beyond all that. It's cultural.

                       The BSD's in general move a bit slower,
                       and tend to have less of a "let's just
                       throw some shit over the wall" vibe
                       when it comes to their releases. Usually
                       there's actually proper documentation
                       included with packages, etc,... and the
                       tooling is often more coherent and in
                       general just... nicer.

   Of course, there's also less
   people contributing, which
   means less hardware support,
   and less packages, and more
   hoops to jump through to try
   and get stuff working that
   you need to interact with the
   norms. :P But since I'm not
   having to worry about work crap
   anymore lately, I really don't
   have anything keeping me from
   switching anylonger.              Moreover, a lot of people
                                     I interact with online are
                                     users of some flavour of BSD.
   This includes screwtape and
   prahou who also want to use
   my 3d engine, so as such it
   is better for a BSD to be my
   main OS, so I can make sure
   everything always compiles
   nicely on it.                  Out of all of the BSD flavours
                                  FreeBSD is probably the most
                                  popular, and the most Linux-like
                                  which is good and bad. Good for
                                  hardware support, bad because
                                  it's quicker to inherit Linux'
                                  flaws. For me it strikes a
                                  usable middle ground. I wanted
                                  to go for NetBSD first, but my
                                  video card gets no hardware
                                  acceleration on it at all, which
                                  is problematic given that I'm
   Moreover, the linux-emulation  working on a 3d engine.
   will come in handy when I do
   have to interact with the
   other side. The irony that
   the BSD's tend to not use
   copyleft, yet tend to have a
   more hacker-friendly community
   is not lost on me.             I'm not the first one to notice
                                  or complain about this change in
                                  culture on the Linux-side, and
                                  not the first to notice the
                                  strengthening corporate grip on
                                  the ecosystem, which, to be fair
                                  has always been there. Linus had
                                  always embraced and sought out
                                  commercial use.

   Not that I'm against commercial
   use in of itself, but I am
   against huge corporations
   hijacking communities and
   manipulating the conversations
   and abusing 'open source' for
   free labour. This is why I
   like copyleft. At least with
   the GPL, anything they grab and
   change, they have to release
   back into the wild. That of
   course is also broken now that
   AI copyright laundring is a thing.
   In any event, many other hackers
   have fled to the bsd's over the
   years, yet the irony is not lost
   on me that the BSD's have
   historically been anti-copyleft.
   It's quite a strange situation
   we find ourselves in now. And
   It isn't exactly great. Someone
   ought to write a fresh copyleft-
   licensed BSD perhaps. ;) Or maybe
   we need a second HURD, done properly
   this time. Or maybe all this computer
   stuff was just a huge mistake and
   I should just go learn woodworking or
   something, whateverrrrr... in the end,
   I did finally end up installing FreeBSD
   on this machine, aaand:

                                  The first thing I noticed was
                                  that many of the 'extra' keys
                                  on my fancy deko keyboard were
                                  not sending keycodes. Not in X,
                                  and not in the vt console.
   Indeed, kbdscan was giving me
   nothing|nada for a lot of the
   keys. That's a big problem,
   because I rely on a lot of
   those keys for my normal
   workflow. As such, I started
   work on a driver for the deko
   keyboard.

   It took quite a bit of reverse engineering, trying to figure
   out how the FreeBSD kernel handles PS2 keyboards. And it's not
   all pretty. (FWIW it's not all pretty in Linux either).

   PS2 keyboards on both freebsd and linux are handled by a driver
   called 'atkbd'. I guess both keyboards with an AT connector and
   keyboards with a PS2 connector are handled by the same thing.
   And the name ought to be a clue towards how old and crufty some
   of this code is going to be.

   Diving in, the first thing I see on top of the atkbd.c file is
   a 1999 copyright - this is going to be fun.

   In the Linux kernel, the atkbd driver uses the serio facility
   to communicate with the ps2 port. Looking over the atkbd source
   I saw no equivalent on FreeBSD. Instead, the atkbd driver uses
   the atkbdc (the at keyboard controller driver) bus to
   communicate over the ps2 bus. And atkbdc in turn uses the
   bus_space (1) functions to talk to the port. Okay. Not too bad
   so far. We can work with this. I can write an atkbd replacement
   and still hook it into atkbdc to communicate with the ps2 bus.
   Another thing I noticed right away was this bit of code:

==[ BEGIN SOURCE DUMP: atkbd.c ]==================================.

switch (state->ks_prefix) {
       case 0x00:      /* normal scancode */
               switch(scancode) {
       // .... stuff
               case 0xE0:
               case 0xE1:
                       state->ks_prefix = scancode;
                       goto next_code;
               }
               break;
       case 0xE0:              /* 0xE0 prefix */
               state->ks_prefix = 0;
               switch (keycode) {
               case 0x1C:      /* right enter key */
                       keycode = 0x59;
                       break;
               case 0x1D:      /* right ctrl key */
                       keycode = 0x5A;
                       break;
               // .... more stuff
               default:        /* ignore everything else */
                       goto next_code;
               }
               break;

`==[ END SOURCE DUMP ]============================================='

So, it turns out, all of the keys that weren't working on this
keyboard were keys that send a 0xE0 prefix. The FreeBSD atkbd
driver has a bunch of hard-coded cases for some keys with this
prefix, but obviously not any for this funky keyboard, and anything
not handled by their hard-coded cases gets... ignored. So there was
just no chance of me ever seeing a keycode for these keys in
userland.

I copied atkbd.c into my own driver, recompiled my kernel with
atkbd disabled, and made sure my custom module loads on boot.
Then I added some extra case statemens for some of my special keys.

Aaannd...                                       N O P E

Nothing. Didn't work. After a bunch of debugging, I found that the
function returns before ever even reaching this case statement.
What's going on?! Welll.... it returns here:


==[ BEGIN SOURCE DUMP: atkbd.c ]==================================.

       /* return the byte as is for the K_RAW mode */
       if (state->ks_mode == K_RAW)
               return scancode;

`==[ END SOURCE DUMP ]============================================='

So we are in raw mode? I wrote a little userland C program that
dumps the keyboard mode, and another that switches it to raw mode.
If I switch the keyboard to raw mode from userland, All keys just
produce garbage. If I read back what mode the keyboard is in, it is
NOT in raw mode. HUH. so userland thinks the keyboard is NOT in raw
mode, yet my atkbd driver clearly thinks it is.

Well. It turns there's more to the puzzle of how keyboards are
hanled on FreeBSD. Quite a bit more. You see, atkbd and atkbdc do
not act alone. Nay! They are in turn hooked into the kbd facility
and attached to a kbdmux. The kbdmux man page (2) states:

       "The kbdmux keyboard driver switches all slave keyboards
    into K_RAW mode. Thus all slave keyboards attached to
    the kbdmux keyboard share the same state. The kbdmux
    keyboard is logically equivalent to one keyboard with lots of
    duplicated keys."

AH. Okay, so that kind of makes sense. atkbd is in raw mode, but
the kbdmux is not. That explains the mismatch between what we see
in the atkbd driver versus userspace.

Having a quick glance into kbdmux.c I quickly noticed that it
duplicates a lot of the atkbdc code, including our big switch
statement with the hardcoded cases for the 0xE0 prefixed keys.

That's.... a problem. Certainly I don't want to go modify both
atkbd AND kbdmux. The keyboard code being spread out all over the
kernel like that with hardcoded keycodes was giving me a sinking
feeling that writing a driver for this keyboard was going to be
very difficult if not impossible. Certainly it would be near
impossible to make a clean self-contained module. That's what I
thought until.... I had an idea.

What if, we tell the kbdmux a little white lie. Instead of passing
it the raw scancode, we can pass it a manipulated scan code, with
the prefixed keys already translated to a single byte keycode and
then recomposed into a scancode. That would bypass the 0xE0 case
and would avoid the keys being ignored.

Finally progress. I got this working, and kbdscan now finally saw
unique keycodes for my 'extra' 0xE0 prefixed keys. But as soon as I
fired up xorg, I was back to not having scancodes again for those
keys, even if I mapped them with kbdcontrol and an appropriate
keymap. What's going on?

Turns out there is ANOTHER mechanism that wants to translate
keycodes into scancodes: evdev.

On FreeBSD evdev is the thing that feeds events to libinput. And
libinput is typically the thing that xorg and wayland get their
events from for keyboard, mouse, and other input devices.

Looking into atkbd.c (and my new driver) we see that there's some
special code that generates evdev events:

==[ BEGIN SOURCE DUMP: atkbd.c ]==================================.

#ifdef EVDEV_SUPPORT
 // ... stuff
 keycode = evdev_scancode2key(&state->ks_evdev_state, scancode);
 if (keycode != KEY_RESERVED)
 {
       evdev_push_event(state->ks_evdev, EV_KEY,
                       (uint16_t)keycode, scancode & 0x80 ? 0 : 1);
       evdev_sync(state->ks_evdev);
 }
 // ... more stuff
#endif

`==[ END SOURCE DUMP ]============================================='

scancode 2 key.... sigh... so, yeah. evdev has it's own key codes,
distinct from the vt key codes, and I had to add more code to
translate each special key to a unique evdev key code. After that,
it still didn't work until I added evdev_support_key calls for
every special key in the bit where it sets up the evdev events as
well. But after THAT I finally got all keys to produce unique
scancodes in xorg. What. A. Ride.

And after all that, honestly, it was relatively trivial to write up
some code to handle the VFD display and the extra leds. Thanks to
the kbd facility in FreeBSD, those drivers can 'search' for the
keyboard by keyboard driver name, get a handle to the keyboard
state, which has a mutex we can lock to prevent polling while we
send commands to the port. It's actually kind of nice. Simpler than
the serio setup on Linux, yet very usable.

My dekovfd driver sets up a /dev/dekovfd device which you can write
to in order to write to the display, or you can cat it to read back
what's on the display.

The dekoleds driver sets up a /dev/dekoleds device, which has some
ioctl's to set led status for all the extra leds. They can be set
to on, off, or blinking. I wrote up a userland utility to control
the leds, so you can control them in shell scripts, and I included
some sample C programs which use the ioctl.

All in all, while the keyboard handling code is a bit of a mess, I
found that writing device drivers for FreeBSD is actually quite a
pleasant experience compared to Linux. The bsd.kmod.mk Makefile
makes setting up a build pipeline extremely easy. The module loader
not needing an exact match between kernel and the module like on
Linux made my life easier as it led to less complete kernel
recompiling. The various driver macro's like DRIVER_MODULE and
KEYBOARD_DRIVER make registering your drivers super easy.

Obviously, it would be better if there were no hard-coded scancode
handling in the kernel, and if that same code wasn't duplicated a
bunch of times (Oh, I forgot to mention, that same atkbd code is
also duplicated AGAIN in the bhyve code whaaaaaaaa), and I think
a lot of that code has some cobwebs and organic growth on it, but
given that inspite of that, I was still able to write a small self
contained module, it isn't all that bad really. It would be nice
if there were some type of syntax to handle multi-byte scancodes
in the keymaps themselves, so no kernel hacking would be required.
It would also be nice if there was a userland-only way to send
commands to the ps2 port. But I think, in the end, having proper
drivers now is 10000% nicer than the hacky shell script crap I had
going on in Linux.

That concludes my FreeBSD kernel driver / keyboard / rant story. :)

If you would like to take a stab at FreeBSD driver development,
they actually have really good offical documentation on all that!
Check these out:

* https://docs.freebsd.org/en/books/arch-handbook/driverbasics/
* https://wiki.freebsd.org/CDevModule

I also recorded a video demonstration where I go over some of this,
and demo what the keyboard looks like when using the driver. It is
on SDF toobnix here:

* https://toobnix.org/w/dXMkWYitfejhPsxoydpCts

The source code for the drivers can be found here:

* https://linkerror.com:11175/jns/dekodrv/src/branch/main/dekokbd

___________________________________________________________________

1: https://man.freebsd.org/cgi/man.cgi?query=bus_space
2: https://man.freebsd.org/cgi/man.cgi?kbdmux