diff --git a/src/bindings.h b/src/bindings.h index d8200e7..50d12c8 100644 --- a/src/bindings.h +++ b/src/bindings.h @@ -53,6 +53,47 @@ #define SUNK_CAPSLOCK 0x77 #define SUNK_SCROLLLOCK 0x17 #define SUNK_POWER 0x30 +#define SUNK_SHIFT_L 0x63 +#define SUNK_SPACE 0x79 +#define SUNK_RETURN 0x59 +#define SUNK_DASH 0x28 +#define SUNK_DOT 0x6C +#define SUNK_1 0x1E +#define SUNK_2 0x1F +#define SUNK_3 0x20 +#define SUNK_4 0x21 +#define SUNK_5 0x22 +#define SUNK_6 0x23 +#define SUNK_7 0x24 +#define SUNK_8 0x25 +#define SUNK_9 0x26 +#define SUNK_0 0x27 +#define SUNK_Q 0x36 +#define SUNK_W 0x37 +#define SUNK_E 0x38 +#define SUNK_R 0x39 +#define SUNK_T 0x3A +#define SUNK_Y 0x3B +#define SUNK_U 0x3C +#define SUNK_I 0x3D +#define SUNK_O 0x3E +#define SUNK_P 0x3F +#define SUNK_A 0x4D +#define SUNK_S 0x4E +#define SUNK_D 0x4F +#define SUNK_F 0x50 +#define SUNK_G 0x51 +#define SUNK_H 0x52 +#define SUNK_J 0x53 +#define SUNK_K 0x54 +#define SUNK_L 0x55 +#define SUNK_Z 0x64 +#define SUNK_X 0x65 +#define SUNK_C 0x66 +#define SUNK_V 0x67 +#define SUNK_B 0x68 +#define SUNK_N 0x69 +#define SUNK_M 0x6A // sources: // SPARC Keyboard Specification Version1 https://sparc.org/wp-content/uploads/2014/01/KBD.pdf.gz diff --git a/src/main.cc b/src/main.cc index 5f3fb12..ff4ed54 100644 --- a/src/main.cc +++ b/src/main.cc @@ -20,6 +20,7 @@ extern "C" { #include "menu.h" #include "settings.h" #include "state.h" +#include "sunk.h" #include "view.h" #include "splash.xbm" #include "logo.xbm" @@ -351,45 +352,6 @@ void loop1() { buzzer.update(); } -void sunkSend(bool make, uint8_t code) { - static int activeCount = 0; - if (make) { - activeCount += 1; - buzzer.click(); - } else { - activeCount -= 1; - code |= SUNK_BREAK_BIT; - } - -#ifdef SUNK_ENABLE -#ifdef SUNK_VERBOSE - Sprintf("sun keyboard: tx command %02Xh\n", code); -#endif - Serial1.write(code); -#endif - - if (activeCount <= 0) { - activeCount = 0; -#ifdef SUNK_ENABLE -#ifdef SUNK_VERBOSE - Sprintf("sun keyboard: idle\n"); -#endif - Serial1.write(SUNK_IDLE); -#endif - } - - switch (code) { - case SUNK_POWER: - Sprintf("sun power: high\n"); - digitalWrite(POWER_KEY, HIGH); - break; - case SUNK_POWER | SUNK_BREAK_BIT: - Sprintf("sun power: low\n"); - digitalWrite(POWER_KEY, LOW); - break; - } -} - // Invoked when device with hid interface is mounted // Report descriptor is also available for use. // tuh_hid_parse_report_descriptor() can be used to parse common/simple enough diff --git a/src/menu.cc b/src/menu.cc index 076a114..510fefa 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -1,15 +1,20 @@ #include "config.h" #include "menu.h" +#include +#include + #include "buzzer.h" #include "bindings.h" #include "display.h" #include "hostid.h" #include "settings.h" #include "state.h" +#include "sunk.h" #include "view.h" MenuView MENU_VIEW{}; +WaitView WAIT_VIEW{}; template static void drawMenuItem(int16_t &marqueeX, size_t i, bool on, const char *fmt, Args... args); @@ -19,6 +24,8 @@ enum class MenuItem : size_t { ForceClick, ClickDuration, Hostid, + ReprogramIdprom, + WipeIdprom, }; using MenuItemPainter = void (*)(int16_t &marqueeX, size_t i, bool on); @@ -45,6 +52,12 @@ static const MenuItemPainter MENU_ITEM_PAINTERS[] = { settings.hostid()[4], settings.hostid()[5]); }, + [](int16_t &marqueeX, size_t i, bool on) { + drawMenuItem(marqueeX, i, on, "Reprogram idprom"); + }, + [](int16_t &marqueeX, size_t i, bool on) { + drawMenuItem(marqueeX, i, on, "Wipe idprom (AAh)"); + }, }; static const size_t MENU_ITEM_COUNT = sizeof(MENU_ITEM_PAINTERS) / sizeof(MENU_ITEM_PAINTERS[0]); @@ -77,6 +90,16 @@ static void drawMenuItem(int16_t &marqueeX, size_t i, bool on, const char *fmt, } } +unsigned decodeHex(unsigned char digit) { + if (digit >= '0' && digit <= '9') + return digit - '0'; + if (digit >= 'A' && digit <= 'F') + return digit - 'A' + 10; + if (digit >= 'a' && digit <= 'f') + return digit - 'a' + 10; + return 0; +} + void MenuView::open() { if (isOpen) return; @@ -146,6 +169,66 @@ void MenuView::sel(uint8_t usbkSelector) { case (size_t)MenuItem::Hostid: HOSTID_VIEW.open(settings.hostid()); break; + case (size_t)MenuItem::ReprogramIdprom: { + WAIT_VIEW.open("Reprogramming..."); + + unsigned hostid24 = + decodeHex(settings.hostid()[0]) << 20 + | decodeHex(settings.hostid()[1]) << 16 + | decodeHex(settings.hostid()[2]) << 12 + | decodeHex(settings.hostid()[3]) << 8 + | decodeHex(settings.hostid()[4]) << 4 + | decodeHex(settings.hostid()[5]); + + unsigned i = 0; + // https://funny.computer.daz.cat/sun/nvram-hostid-faq.txt + // version 1 + sunkSend("1 %x mkp\n", i++); + + // hostid byte 1/4 (system type) + sunkSend("real-machine-type %x mkp\n", i++); + + // ethernet address oui (always 08:00:20) + sunkSend("8 %x mkp\n", i++); + sunkSend("0 %x mkp\n", i++); + sunkSend("20 %x mkp\n", i++); + + // set ethernet address lower half such that hostid bytes 2/3/4 + // cancels it out in the checksum + sunkSend("%x %x mkp\n", hostid24 >> 16 & 0xFF, i++); + sunkSend("%x %x mkp\n", hostid24 >> 8 & 0xFF, i++); + sunkSend("%x %x mkp\n", hostid24 >> 0 & 0xFF, i++); + + // set date of manufacture such that the system type byte + // cancels it out in the checksum + sunkSend("real-machine-type %x mkp\n", i++); + sunkSend("0 %x mkp\n", i++); + sunkSend("0 %x mkp\n", i++); + sunkSend("0 %x mkp\n", i++); + + // hostid bytes 2/3/4 + sunkSend("%x %x mkp\n", hostid24 >> 16 & 0xFF, i++); + sunkSend("%x %x mkp\n", hostid24 >> 8 & 0xFF, i++); + sunkSend("%x %x mkp\n", hostid24 >> 0 & 0xFF, i++); + + // 01h ^ 08h ^ 20h = 29h + // sunkSend("0 %x 0 do i idprom@ xor loop f mkp\n", i++); + sunkSend("29 %x mkp\n", i++); + + // only needed for SS1000, but harmless otherwise + sunkSend("update-system-idprom\n"); + + sunkSend(".idprom\n"); + sunkSend("banner\n"); + + WAIT_VIEW.close(); + } break; + case (size_t)MenuItem::WipeIdprom: { + WAIT_VIEW.open("Wiping..."); + for (unsigned i = 0; i < 0xF; i++) + sunkSend("aa %x mkp\n", i); + WAIT_VIEW.close(); + } break; default: close(); } @@ -168,3 +251,25 @@ void MenuView::sel(uint8_t usbkSelector) { break; } } + +void WaitView::handlePaint() { + display.setCursor(8, 8); + display.print(message); +} + +void WaitView::handleKey(const UsbkChanges &) {} + +void WaitView::open(const char *message) { + if (isOpen) + return; + isOpen = true; + this->message = message; + View::push(&WAIT_VIEW); +} + +void WaitView::close() { + if (!isOpen) + return; + View::pop(); + isOpen = false; +} diff --git a/src/menu.h b/src/menu.h index 451e1c4..71374a4 100644 --- a/src/menu.h +++ b/src/menu.h @@ -20,6 +20,16 @@ struct MenuView : View { void sel(uint8_t usbkSelector); }; +struct WaitView : View { + bool isOpen = false; + const char *message = ""; + + void handlePaint() override; + void handleKey(const UsbkChanges &) override; + void open(const char *message); + void close(); +}; + extern MenuView MENU_VIEW; #endif diff --git a/src/sunk.cc b/src/sunk.cc new file mode 100644 index 0000000..3275586 --- /dev/null +++ b/src/sunk.cc @@ -0,0 +1,47 @@ +#include "sunk.h" + +#include + +#include + +#include "bindings.h" +#include "buzzer.h" + +void sunkSend(bool make, uint8_t code) { + static int activeCount = 0; + if (make) { + activeCount += 1; + buzzer.click(); + } else { + activeCount -= 1; + code |= SUNK_BREAK_BIT; + } + +#ifdef SUNK_ENABLE +#ifdef SUNK_VERBOSE + Sprintf("sun keyboard: tx command %02Xh\n", code); +#endif + Serial1.write(code); +#endif + + if (activeCount <= 0) { + activeCount = 0; +#ifdef SUNK_ENABLE +#ifdef SUNK_VERBOSE + Sprintf("sun keyboard: idle\n"); +#endif + Serial1.write(SUNK_IDLE); +#endif + } + + switch (code) { + case SUNK_POWER: + Sprintf("sun power: high\n"); + digitalWrite(POWER_KEY, HIGH); + break; + case SUNK_POWER | SUNK_BREAK_BIT: + Sprintf("sun power: low\n"); + digitalWrite(POWER_KEY, LOW); + break; + } +} diff --git a/src/sunk.h b/src/sunk.h new file mode 100644 index 0000000..cdb7ee6 --- /dev/null +++ b/src/sunk.h @@ -0,0 +1,63 @@ +#ifndef USB3SUN_SUNK_H +#define USB3SUN_SUNK_H + +#include "config.h" + +#include + +#include + +#include "bindings.h" + +// internal flags (not part of real keycode) +#define SUNK_SEND_SHIFT 0x100 + +const uint16_t ASCII_TO_SUNK[128] = { + /* 00h */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 08h */ 0, 0, SUNK_RETURN, 0, 0, 0, 0, 0, + /* 10h */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 18h */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 20h */ SUNK_SPACE, 0, 0, 0, 0, 0, 0, 0, + /* 28h */ 0, 0, 0, 0, 0, SUNK_DASH, SUNK_DOT, 0, + /* 30h */ SUNK_0, SUNK_1, SUNK_2, SUNK_3, SUNK_4, SUNK_5, SUNK_6, SUNK_7, + /* 38h */ SUNK_8, SUNK_9, 0, 0, 0, 0, 0, 0, + /* 40h */ SUNK_2 | SUNK_SEND_SHIFT, SUNK_A, SUNK_B, SUNK_C, SUNK_D, SUNK_E, SUNK_F, SUNK_G, + /* 48h */ SUNK_H, SUNK_I, SUNK_J, SUNK_K, SUNK_L, SUNK_M, SUNK_N, SUNK_O, + /* 50h */ SUNK_P, SUNK_Q, SUNK_R, SUNK_S, SUNK_T, SUNK_U, SUNK_V, SUNK_W, + /* 58h */ SUNK_X, SUNK_Y, SUNK_Z, 0, 0, 0, 0, 0, + /* 60h */ 0, SUNK_A, SUNK_B, SUNK_C, SUNK_D, SUNK_E, SUNK_F, SUNK_G, + /* 68h */ SUNK_H, SUNK_I, SUNK_J, SUNK_K, SUNK_L, SUNK_M, SUNK_N, SUNK_O, + /* 70h */ SUNK_P, SUNK_Q, SUNK_R, SUNK_S, SUNK_T, SUNK_U, SUNK_V, SUNK_W, + /* 78h */ SUNK_X, SUNK_Y, SUNK_Z, 0, 0, 0, 0, 0, +}; + +void sunkSend(bool make, uint8_t code); + +template +void sunkSend(const char *fmt, Args... args) { + char result[256]; + size_t len = snprintf(result, sizeof(result) / sizeof(*result), fmt, args...); + if (len >= sizeof(result) / sizeof(*result)) { + Sprintf("sunk: macro buffer overflow"); + return; + } + for (auto i = 0; i < len; i++) { + if (result[i] >= sizeof(ASCII_TO_SUNK) / sizeof(*ASCII_TO_SUNK) || ASCII_TO_SUNK[result[i]] == 0) { + Sprintf("sunk: octet %02Xh not in ASCII_TO_SUNK\n", result[i]); + return; + } + } + Sprintf("sunk: sending macro <"); + Sprintf(fmt, args...); + Sprintf(">\n"); + for (auto i = 0; i < len; i++) { + if (!!(ASCII_TO_SUNK[result[i]] & SUNK_SEND_SHIFT)) + sunkSend(true, SUNK_SHIFT_L); + sunkSend(true, ASCII_TO_SUNK[result[i]] & 0xFF); + sunkSend(false, ASCII_TO_SUNK[result[i]] & 0xFF); + if (!!(ASCII_TO_SUNK[result[i]] & SUNK_SEND_SHIFT)) + sunkSend(false, SUNK_SHIFT_L); + } +} + +#endif