usb3sun, a usb input adapter for sun workstations

SPARCstations have a unique serial-like interface for keyboard and mouse input, using a single 8-pin mini-din port. i have a bunch of these workstations and a keyboard, but no mouse, and surprisingly this can be a fatal blocker for just installing solaris!

sun peripherals are rare and expensive nowadays, so it would be nice to be able to use any old usb keyboard and mouse. making this adapter has been a fun and useful first electronics project, but now i’m pretty much happy with my prototype.

previously i found that the rp2040-based raspberry pi pico was (bootlicking vendor notwithstanding) a pretty powerful platform that would probably work well for this project. it has a bunch of gpio, native uart + usb + i2c support, and a cool programmable i/o feature for protocols that are too fast to bit-bang in software.

i also found that while no one really sells this kind of adapter anymore, someone has designed one and released the code for it, and it’s based on the pico too! USB2Sun has keyboard and mouse support, though there are no led indicators (num + caps + scroll + compose), audible indicators (bell + click), or cold boot with the power key.

i could have just flashed that and called it a day, but where’s the fun in that? and could we design one that’s a bit more ambitious?

tl;dr

  • i can use usb keyboards and mice with my SPARCstations
  • two usb host ports driven by programmable i/o
  • passive piezo buzzer for click and bell features
  • 128x32 oled display for led indicators (and settings menu)
  • soft power key support if powered externally

if that sounds interesting to you, here’s another 3700 words about this project, and all the things i learned about electronics, usb, sun machines, and programming for the rp2040.

https://www.youtube.com/watch?v=kvl7X9Ww6q4

toc

sun interface

there are two ground pins, two 5V pins, keyboard rx, keyboard tx, mouse tx, and a pin for the power key. the data pins are all 5V serial with negative logic at 1200 8N1, with the five-byte serial mouse protocol (aka Mouse Systems protocol) and sun’s own keyboard protocol (documented thoroughly in the sparc keyboard specification).

the sun side was pretty straightforward, though the sun and usb keyboard protocols use fundamentally different ways of representing key presses, so converting the latter to the former was non-trivial.

as for the mouse, most sources i could find say that the Mouse Systems protocol is 1200 8N1, and this works, but it yields a sluggish cursor that’s very annoying to actually use.

i cheated a bit and looked at the sun mouse driver in the linux kernel. turns out sun mice can run at anywhere from 1200 up to 9600 baud, and the driver detects this on the fly!

usb interface

the rp2040 has native usb host support, but i didn’t have the necessary otg adapter or usb hub to use it with the pico, and i didn’t want to lose access to it as a usb device, because serial over cdc was convenient for debugging. but more about debugging later.

it turns out we can run a usb host (or two!) over gpio using programmable i/o! and my bucket of front panel bits did have several

two-port usb breakouts!

one small challenge with these breakouts, and most of the usb header cables that they come with, is that the pinout is two-dimensional and hence not really breadboard compatible. at best we end up bridging the D+ pins and the D- pins between the two ports, so we can only use one port at a time.

even if you can get your hands on a rare usb header cable that’s split on one end (see below), the cables also tend to have proper twisted pairs and possibly shielding too. that’s great if you want a long cable with usb 2.0 signal integrity, but otherwise it’s just annoying having a cable that’s both too stiff and too long.

i solved this at first by soldering jumper wires directly to the pads of the header.

this worked well enough, but hear me out, wacky zany idea here, what if we made it breadboard compatible by uhh… “rerouting” the header pins a bit? that way we can move our prototype around as a single unit!

this was a bad idea that worked for about two (2) minutes. plugging in the breakout board and plugging in usb devices (and unplugging them) puts a lot of force on the board, the rigid coupling directs that force to the joints between the pins and the board, and removing the black plastic “backbone” that held the pins together at an even 0.1″ pitch was union busting. weakened and alone, the solder joints and pads soon ripped off the traces.

the best way i found to solve this problem was to make my own header cables with a dupont crimp and housing kit.

on the surface, talking to a usb keyboard and mouse was pretty straightforward. the HID_device_report.ino example for tinyusb gets us 50% of the way there. define a few callbacks and you get hid reports, tinyusb handles the rest.

the callback for unplugging a hid device (tuh_hid_umount_cb) didn’t work for some reason, so maybe we would leak resources if we unplugged a device and plugged another one into the same port? the callback for unplugging any device (tuh_umount_cb) works fine though. oh well, we can deal with that later.

more worryingly, many of the usb keyboards and mice i tried either didn’t work properly or didn’t work at all!

usb device compatibility

the first keyboard i tried appeared to be dead. it works fine elsewhere, but with our adapter, no tinyusb callbacks, hid or otherwise. it’s the microsoft wired keyboard 600 (045E:0750), so maybe it might be the fancy media keys or how it shows up as a composite device?

the second keyboard i tried went exactly the same. it’s the microsoft wired keyboard 400 (045E:0752), which has no fancy media keys and shows up as a single hid device, so maybe these microsoft keyboards are just non-standard somehow, i mean doesn’t linux need a separate hid-microsoft driver for them?

only the third keyboard i tried actually worked. most of the mice i tried worked, but my main mouse, the endgame gear xm1r (3367:1903), was sending some

weird looking reports.

they were longer than the 3 octets i was expecting, and the dx and dy values seemed to be signed 16-bit values, not signed 8-bit.

this is because hid devices can send reports in any format they like, as long as they can describe that format in the descriptor. mice can control the width and range of dx and dy, among other things, while the things keyboards can control include:

  • whether to send “relative” (make/break) or “absolute” (currently pressed keys)
  • whether to send currently pressed keys as a list of key codes or a bitmap
  • how long the list of key codes can be (this is one way of doing >6kro!)

but many devices can or will send reports in one particular format:

  • for keyboards, bitmap of 8 modifiers, 00h, up to six absolute key codes
  • for mice, bitmap of 8 buttons, 8-bit dx, 8-bit dy (both representing [-127,127])

this format is known as the

boot interface,

a fixed report format defined in the hid spec for usb hosts that don’t want to bother implementing the flexible but complicated report descriptor stuff. this includes pc firmware, as well as tinyusb’s host driver for hid. reading the tinyusb code suggests that we ask keyboards and mice to use the boot interface, but i guess they can just… decide not to?

so that part’s working as intended. if we want to support keyboards and mice that don’t support the boot interface, we need to write a hid report (and descriptor) parser. now at this point, you may be wondering about those microsoft keyboards. those i didn’t figure out until the day i wrote this chost, because of

an actual fucking heisenbug.

when i plugged in a microsoft wired keyboard 600 or 400, tinyusb gave us none of the usual callbacks for that device or any device i was also plugging into the other port. it was like the usb stack crashed (well the host part at least).

ok then, let’s debug that. tinyusb has a bunch of debug logging you can toggle by defining CFG_TUSB_DEBUG to a level between 0 and 3 inclusive. long story short, it goes to UART0 (aka Serial1), so we need to disable any code that uses it for sun i/o and point our terminal at that with a picoprobe or just another pico that pipes Serial1 (UART0) to Serial (cdc).

here’s the thing right. get this. you ready? are you in the right headspace to receive information that could possibly hurt you?

both keyboards worked fine when the debug level was 2 or 3.

this screamed race condition. we were doing something Too Fast for these keyboards. i set the debug level to 2, then started binary searching tinyusb’s usbh.c, commenting out half of the TU_LOG2{,_INT,_MEM,_VAR} lines, then half of those that remained, and so on.

eventually i narrow it down to this line in tuh_control_xfer

TU_LOG2_VAR(xfer->setup);

where i added a

osal_task_delay(100);

and voilà, the keyboards now worked without any debug logging.

speaking of debugging,

beeping crashed the usb host stack,

once again with no observable symptoms. i was able to work around this for a while by using analogWrite, but i wanted to use tone.

@ariashark and i spent a whole day getting to the bottom of this one, and that’s with a picoprobe, which basically turns a second pico into a gdb relay and usb-to-uart adapter, at the expense of slow firmware uploads.

long story short, we found that the pico pio usb driver was using pio state machines without actually claiming them. tone uses programmable i/o too, so it ends up clobbering one of the state machines that was being used for the usb host.

// claim state machines so that tone() doesn’t clobber them
// pio0 and pio1 based on PIO_USB_DEFAULT_CONFIG and logic in pio_usb_bus_init
// https://github.com/sekigon-gonnoc/Pico-PIO-USB/blob/52805e6d92556e67d3738bd8fb10227a45b13a08/src/pio_usb.c#L277
pio_cfg.sm_tx = pio_claim_unused_sm(pio0, true);
pio_cfg.sm_rx = pio_claim_unused_sm(pio1, true);

keymap

sun keyboards and usb keyboards have different sets of keys, and they encode keys differently.

sun keys send a make code when pressed and a break code when released, while usb keyboards (at least under the boot interface) send a list of usage ids or selectors (Sel) for the keys that are currently pressed. usb boot keyboards send eight modifier keys as a bitmap of dynamic flags (DV), rather than by their usage ids.

defining a mapping between these keyboards is not so simple, and there’s more than one right answer.

but the question we first need to answer is,

what keys are there anyway?

for sun keyboards, there’s the sparc keyboard spec, which pretty much just documents how the sun type 4 keyboard works, no more, no less. both conveniently and annoyingly, that’s the keyboard i already have.

but the sun type 5 and sun type 6, which were also available in the sun keyboard interface, add a bunch of new keys, complementing Delete with the full nav cluster of Insert Home End Page Up Page Down, dedicated arrow keys , as well as a ⏻ (power) key.

since i don’t have either of these keyboards, i had to piece together the codes they represented from:

you might have noticed the strange LF(n) TF(n) RF(n) BF(n) notation in the links above. this seems to be a historical artifact of the sun type 3 keyboard, which had “left function” keys and “right function” keys, in addition to the usual “(top) function” keys. many of these have since been renamed, but they still live on. here’s a type 4 annotated with the equivalents:

yes, that means in sun land, the numpad is made up of “right function keys” and “bottom function keys”.

usb keyboards support far more keys than you would find on a typical 104-key pc keyboard, and the full repertoire is defined in the hid usage tables spec.

this includes most, but not all, of the keys unique to sun keyboards. for example, we’ll want to map the usb Help key to the sun Help key, in case someone finds a keyboard that actually has it, but we’ll also need to find an alternative.

most of the keys on a sun keyboard have

obvious equivalents

in usb land.

  • commonly found on pc keyboards:
    Esc F1F12
    ` ~ 1 !9 ( 0 ) - _ = + Backspace
    Tab QP [ { ] } \ |
    Caps Lock AL ; : ' "
    left Shift ZM , < . > / ? right Shift
    spacebar Delete
    Num Lock / * -
    Home 7 ↑ 8 PgUp 9 +
    ← 4 5 → 6
    End 1 ↓ 2 PgDn 3
    Ins 0 Del .
  • rarely found on pc keyboards:
    Stop Again Undo Copy Paste Find Paste Find Cut Help

there are also some

not-so-obvious equivalents.

for sun Props and =, the latter being the numpad version, we have usb CrSel/Props and Keypad =, though those are rarely found on pc keyboards. sun has Return and Enter, the latter being the numpad version, while usb has three keys with similar names:

  • Return (ENTER) (28h) is the main Enter on pc keyboards → sun Return
  • Keypad ENTER (58h) is the numpad Enter on pc keyboards → sun Enter
  • Return (9Eh) is neither, and i have no idea if it’s “return” as in “carriage return” or “return” as some kind of gui function, but i mapped it to sun Return just in case

for sun Power, we have usb Power, though that’s rarely found on pc keyboards. the footnote also insists that it’s “not a physical key”, the same treatment given to key codes like ErrorRollOver (01h), which is hid-speak for “too many keys or ambiguous”.

for sun Pause, Pr Sc, and Break Scroll Lock, we have usb Pause, PrintScreen, and Scroll Lock. but on most pc keyboards, Pause is Pause Break and Scroll Lock has no Break. since the sun type 5 keyboard moves Break to the Pause key, like on a pc keyboard, i figured sun clearly didn’t care too much which one was Break.

for sun Control, we have usb LeftControl or RightControl. sun keyboards have always had one Control, located in the spot where Caps Lock or left Control would go on pc keyboards, so i chose LeftControl. for sun Alt and Graph Alt, popular convention[citation needed] maps these to usb LeftAlt and RightAlt respectively.

as for the

triangle keys?

wait what? the fuck is a triangle key?

i think i need to retake primary school maths, because apparently diamonds are triangles now.

anyway these are more commonly known in the unix world as meta keys, and usb calls them Left GUI and Right GUI. helpfully the footnotes for those clarifies that they are equivalent to the windows keys, apple keys (command), and sun meta keys.

those two keys also share a footnote with usb Application, or the context menu key on pc keyboards, that reads “Windows key for Windows 95, and Compose”. so by elimination, this is the key for sun Compose.

several sun keys have no equivalent commonly found on pc keyboards, or no equivalent in usb land at all, and for these we need

alternate key bindings.

how can we distinguish our alternate bindings from other keys? ideally we want a key that isn’t being used by anything else, like RightControl… but as far as i can tell we’re also free to use the non-numpad Insert Home End Page Up Page Down .

those nav cluster and arrow keys exist on the sun type 5, which is not available in usb, so you would think sun assigned them new sparc key codes right? but the type 4 keymaps in illumos, which are also used for sun type 5 (and non-usb version of type 6), have no dedicated slots for them.

does the Insert key on the sun type 5 just… type a 0 when num lock is on? i mean if they’re the same key code it would have to, unless the keyboard sends a Num Lock on either side?

unlike sun keyboards, which are apparently guaranteed to be nkro by the sparc keyboard spec, cheap pc keyboards tend to be 2kro plus any combination of modifiers, so let’s use RightControl as the basis for all of our bindings.

now this is where things become more art than science.

most in our position, like USB2Sun and this sun-compatible kvm switch, use a spatially accurate layout, which is ideal for users with muscle memory from actual sun keyboards. i didn’t grow up with any sun workstations though, so i went with a more windows-like layout:

  • Power becomes RightCtrl+P (P stands for power)
  • Stop becomes RightCtrl+. (pun on “[full] stop”)
  • Line Feed becomes RightCtrl+Return (return types a line feed)
  • = becomes RightCtrl+= (both type an equals sign)
  • Front becomes RightCtrl+Esc (windows Alt+Esc)
  • Help becomes RightCtrl+F1 (windows F1)
  • Props becomes RightCtrl+F4 (visual studio F4 properties)
  • Again becomes RightCtrl+Y (office redo/repeat)
  • you can probably guess Undo Copy Open Paste Find Cut

but no matter what choices we make, alternate bindings that contain real sun keys have an

inherent flaw:

you can’t press key combinations involving both the target key (like Again) and the keys that represent it (like Y). worse still, combinations of two alternate bindings can become ambiguous. does RightCtrl+F1+Y mean Help+Again or Help+Y or F1+Again?

the fundamental way to solve this is to know which combinations are likely or unlikely, which only really comes with experience (and scouring docs). for example, combinations of Stop and various letters are used by the SPARCstation 5 firmware (§ 2.1):

  • Stop+A breaks out of the operating system to an ok prompt
  • Stop+D on boot sets diag-switch? to true
  • Stop+N sets nvram defaults

power key

sun type 5 keyboards have a power key. this works like an ordinary key if the machine is already on, but it can also switch on some machines, like the SPARCstation 5.

since it didn’t exist on the sun type 4, it’s not documented in the sparc keyboard spec, and the SS5 service manual (§ B.6) just tells us pin 7 is “Power Key In”. so how does it work?

`

the USB2Sun readme says “shorting minidin pin 7 to 5V rail” powers on the system, so without thinking about it too hard, i understood that as “pulling pin 7 up to 5V”. it then reads “5V rail is unpowered when the system is off”, but it took me a while to truly understand the implications.

i was using a generic bidirectional level shifter to convert between 3V3 and 5V i/o, and i had one channel free, so i figured i could use that to pull the pin up to 5V. but no matter what i tried, including pulling the pin down to ground instead or replacing the level shifter with an amplifying bjt, i could never avoid spuriously powering on my SS5 for some combination of:

  • when unplugging or replugging the mini-din cable to the sun
  • while the pico was both powered and unpowered
  • while the pico was both under and not under reset
  • when unplugging or replugging the pico’s usb cable
  • when unplugging or replugging the usb charger
  • when teasing the usb charger with the usb cable weary

furthermore the SS5 would power up if pin 7 touched pretty much anything, including the ground pins, the 5V supply pins, the other three pins, the pico’s vbus (a slightly different 5V), the pico’s 3V3 outputs, and possibly my fingers. it turns out the power pin is ~5V2 at all times, and all seven of the other pins are at 0V when the SS5 is off.

@nroach44 probed the output of the level shifter with his oscilloscope and found that it was 5V while the pico was off or under reset, which led us to believe that the pico starts with all gpios high until your program can pull it low. this contradicted the datasheet, but we’ll come back to that.

at some point, i must have forgotten to connect the ground of my pico and something else (possibly my SS5, though it’s fine), and the ~50V dc bias of the apple charger fried my pico. i had a few spare picos, but i still ragequit the project for a few weeks.

my oscilloscope arrived in the mail, and with it a way to see what the fuck was going on without a 120 km return trip. one thing i learned was that it was the level shifter that was high by default, not the pico. but more interestingly, i found that when powering up the pico from cold, at the usb cable or the wall, under reset or otherwise, all of the gpios would briefly spike up to somewhere between 1V5 and 3V (pic below is 10x probe).

the power key seemed to work by checking if current flows from the power pin, or in other words, if there was a potential difference from the pin’s ~5V2. @nroach44 suggested i replace the circuit with an n-channel mosfet with the gpio at the gate, drain to the sun, and source grounded. the gate acts as the switch, controlling whether current flows from drain to source. contrast this with how a bidirectional level shifter works:

When Lx is driven low, the MOSFET turns on and the zero passes through to Hx. When Hx is driven low, Lx is also driven low through the MOSFET’s body diode, at which point the MOSFET turns on. In all other cases, both Lx and Hx are pulled high to their respective logic supply voltages.

sadly this power key circuit only works if we can power the pico somehow, so you’ll need a usb cable and charger rather than just running vbus off the sun. i probed every single hole and pin reachable from outside the SS5, and the only thing powered is the power pin, which we can’t draw any current from without spuriously powering up the machine, so in practice a simple push button would outperform it.

prototype schematic

i guess it’s time for me to learn some pcb design. hi @ariashark!

lessons

buy like five picos. one for your project, one for picoprobe, one as a usb-to-uart adapter, and a couple more in case you fry a pico with 5V i/o (less dangerous) or a flaky ground (more dangerous).

speaking of which, make sure your grounds are thoroughly connected, if not redundantly connected.

i’m sure i’ll think of more and edit them in, but i wanna go to bed first.