diff options
author | Maxim Devaev <[email protected]> | 2023-07-31 02:17:23 +0300 |
---|---|---|
committer | Maxim Devaev <[email protected]> | 2023-07-31 02:17:23 +0300 |
commit | 1a8f98a64f9480c1062225e0fc994ceac4ba346d (patch) | |
tree | 934ac95c6c0774d6ac512860905d4d2454b0b2aa /hid/arduino | |
parent | cf44668af998b114fbddc8fa41b47193b606c064 (diff) |
moved arduino hid to hid/arduino
Diffstat (limited to 'hid/arduino')
48 files changed, 3283 insertions, 0 deletions
diff --git a/hid/arduino/.gitignore b/hid/arduino/.gitignore new file mode 100644 index 00000000..05b843bc --- /dev/null +++ b/hid/arduino/.gitignore @@ -0,0 +1,6 @@ +/.platformio/ +/.pio/ +/.current +/.vscode/ +/.config +/platformio.ini diff --git a/hid/arduino/Makefile b/hid/arduino/Makefile new file mode 100644 index 00000000..652ddd6e --- /dev/null +++ b/hid/arduino/Makefile @@ -0,0 +1,51 @@ +serial: + make _build E=serial C=avr +spi: + make _build E=spi C=avr +aum: + make _build E=aum C=avr +stm32: + platformio run --environment patch --project-conf platformio-stm32.ini + make _build E=serial C=stm32 +_build: + rm -f .current .config + platformio run --environment $(E) --project-conf platformio-$(C).ini + echo -n $(E) > .current + echo -n $(C) > .config + +# Added to easy test all builds +_build_all: aum spi serial stm32 + rm -f .current .config + +install: upload +upload: + $(eval $@_CURRENT := $(shell cat .current)) + $(eval $@_CONFIG := $(shell cat .config)) + bash -ex -c " \ + current=`cat .current`; \ + if [ '$($@_CURRENT)' == 'spi' ] || [ '$($@_CURRENT)' == 'aum' ]; then \ + gpioset 0 25=1; \ + gpioset 0 25=0; \ + fi \ + " + platformio run --environment '$($@_CURRENT)' --project-conf 'platformio-$($@_CONFIG).ini' --target upload + + +bootloader-spi: install-bootloader-spi +install-bootloader-spi: upload-bootloader-spi +upload-bootloader-spi: + platformio run --environment bootloader_spi --project-conf platformio-avr.ini --target bootloader + + +update: + platformio platform update + + +clean-all: clean + rm -rf .platformio +clean: + rm -rf .pio .current .config platformio.ini + + +help: + @ cat Makefile diff --git a/hid/arduino/avrdude-rpi.conf b/hid/arduino/avrdude-rpi.conf new file mode 100644 index 00000000..8a6f5460 --- /dev/null +++ b/hid/arduino/avrdude-rpi.conf @@ -0,0 +1,7 @@ +programmer + id = "rpi"; + desc = "RPi SPI programmer"; + type = "linuxspi"; + reset = 25; + baudrate = 400000; +; diff --git a/hid/arduino/avrdude.py b/hid/arduino/avrdude.py new file mode 100644 index 00000000..3ae227f9 --- /dev/null +++ b/hid/arduino/avrdude.py @@ -0,0 +1,54 @@ +# https://docs.platformio.org/en/latest/projectconf/advanced_scripting.html + + +from os import rename +from os import symlink +from os.path import exists +from os.path import join + +import platform + +Import("env") + + +# ===== +def _get_tool_path() -> str: + path = env.PioPlatform().get_package_dir("tool-avrdude") + assert exists(path) + return path + + +def _fix_ld_arm() -> None: + tool_path = _get_tool_path() + flag_path = join(tool_path, ".fix-ld-arm.done") + + if not exists(flag_path): + def patch(*_, **__) -> None: + symlink("/usr/lib/libtinfo.so.6", join(tool_path, "libtinfo.so.5")) + open(flag_path, "w").close() + + env.Execute(patch) + + +def _replace_to_system(new_path: str) -> None: + tool_path = _get_tool_path() + flag_path = join(tool_path, ".replace-to-system.done") + + if not exists(flag_path): + def patch(*_, **__) -> None: + old_path = join(tool_path, "avrdude") + bak_path = join(tool_path, "_avrdude_bak") + rename(old_path, bak_path) + symlink(new_path, old_path) + open(flag_path, "w").close() + + env.Execute(patch) + + +# ===== +if "arm" in platform.machine(): + _fix_ld_arm() + +_path = "/usr/bin/avrdude" +if exists(_path): + _replace_to_system(_path) diff --git a/hid/arduino/lib/.gitignore b/hid/arduino/lib/.gitignore new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/hid/arduino/lib/.gitignore diff --git a/hid/arduino/lib/drivers-avr/eeprom.h b/hid/arduino/lib/drivers-avr/eeprom.h new file mode 100644 index 00000000..bac642a7 --- /dev/null +++ b/hid/arduino/lib/drivers-avr/eeprom.h @@ -0,0 +1,40 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#include <avr/eeprom.h> + +#include "storage.h" + + +namespace DRIVERS { + struct Eeprom : public Storage { + using Storage::Storage; + + void readBlock(void *dest, const void *src, size_t size) override { + eeprom_read_block(dest, src, size); + } + + void updateBlock(const void *src, void *dest, size_t size) override { + eeprom_update_block(src, dest, size); + } + }; +} diff --git a/hid/arduino/lib/drivers-avr/factory.cpp b/hid/arduino/lib/drivers-avr/factory.cpp new file mode 100644 index 00000000..801e7a2f --- /dev/null +++ b/hid/arduino/lib/drivers-avr/factory.cpp @@ -0,0 +1,94 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#include "usb/hid.h" +#include "ps2/hid.h" +#include "factory.h" +#include "eeprom.h" +#include "serial.h" +#include "spi.h" + +#ifndef ARDUINO_ARCH_AVR +# error "Only AVR is supported" +#endif + + +namespace DRIVERS { + Keyboard *Factory::makeKeyboard(type _type) { + switch (_type) { +# ifdef HID_WITH_USB + case USB_KEYBOARD: + return new UsbKeyboard(); +# endif + +# ifdef HID_WITH_PS2 + case PS2_KEYBOARD: + return new Ps2Keyboard(); +# endif + + default: + return new Keyboard(DUMMY); + } + } + + Mouse *Factory::makeMouse(type _type) { + switch (_type) { +# ifdef HID_WITH_USB + case USB_MOUSE_ABSOLUTE: + case USB_MOUSE_ABSOLUTE_WIN98: + return new UsbMouseAbsolute(_type); + case USB_MOUSE_RELATIVE: + return new UsbMouseRelative(); +# endif + default: + return new Mouse(DRIVERS::DUMMY); + } + } + + Storage *Factory::makeStorage(type _type) { + switch (_type) { +# ifdef HID_DYNAMIC + case NON_VOLATILE_STORAGE: + return new Eeprom(DRIVERS::NON_VOLATILE_STORAGE); +# endif + default: + return new Storage(DRIVERS::DUMMY); + } + } + + Board *Factory::makeBoard(type _type) { + switch (_type) { + default: + return new Board(DRIVERS::DUMMY); + } + } + + Connection *Factory::makeConnection(type _type) { +# ifdef CMD_SERIAL + return new Serial(); +# elif defined(CMD_SPI) + return new Spi(); +# else +# error CMD phy is not defined +# endif + } +} diff --git a/hid/arduino/lib/drivers-avr/ps2/hid.h b/hid/arduino/lib/drivers-avr/ps2/hid.h new file mode 100644 index 00000000..ecd64d16 --- /dev/null +++ b/hid/arduino/lib/drivers-avr/ps2/hid.h @@ -0,0 +1,95 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <Arduino.h> +#include <ps2dev.h> + +#include "keyboard.h" +#include "keymap.h" + +// #define HID_PS2_KBD_CLOCK_PIN 7 +// #define HID_PS2_KBD_DATA_PIN 5 + + +class Ps2Keyboard : public DRIVERS::Keyboard { + // https://wiki.osdev.org/PS/2_Keyboard + + public: + Ps2Keyboard() : DRIVERS::Keyboard(DRIVERS::PS2_KEYBOARD), _dev(HID_PS2_KBD_CLOCK_PIN, HID_PS2_KBD_DATA_PIN) {} + + void begin() override { + _dev.keyboard_init(); + } + + void periodic() override { + _dev.keyboard_handle(&_leds); + } + + void sendKey(uint8_t code, bool state) override { + Ps2KeyType ps2_type; + uint8_t ps2_code; + + keymapPs2(code, &ps2_type, &ps2_code); + if (ps2_type != PS2_KEY_TYPE_UNKNOWN) { + // Не отправлялась часть нажатий. Когда clock на нуле, комп не принимает ничего от клавы. + // Этот костыль понижает процент пропущенных нажатий. + while (digitalRead(HID_PS2_KBD_CLOCK_PIN) == 0) {}; + if (state) { + switch (ps2_type) { + case PS2_KEY_TYPE_REG: _dev.keyboard_press(ps2_code); break; + case PS2_KEY_TYPE_SPEC: _dev.keyboard_press_special(ps2_code); break; + case PS2_KEY_TYPE_PRINT: _dev.keyboard_press_printscreen(); break; + case PS2_KEY_TYPE_PAUSE: _dev.keyboard_pausebreak(); break; + case PS2_KEY_TYPE_UNKNOWN: break; + } + } else { + switch (ps2_type) { + case PS2_KEY_TYPE_REG: _dev.keyboard_release(ps2_code); break; + case PS2_KEY_TYPE_SPEC: _dev.keyboard_release_special(ps2_code); break; + case PS2_KEY_TYPE_PRINT: _dev.keyboard_release_printscreen(); break; + case PS2_KEY_TYPE_PAUSE: break; + case PS2_KEY_TYPE_UNKNOWN: break; + } + } + } + } + + bool isOffline() override { + return false; + } + + KeyboardLedsState getLeds() override { + periodic(); + KeyboardLedsState result = { + .caps = _leds & 0b00000100, + .scroll = _leds & 0b00000001, + .num = _leds & 0b00000010, + }; + return result; + } + + private: + PS2dev _dev; + uint8_t _leds = 0; +}; diff --git a/hid/arduino/lib/drivers-avr/ps2/keymap.h b/hid/arduino/lib/drivers-avr/ps2/keymap.h new file mode 100644 index 00000000..65a5d7a1 --- /dev/null +++ b/hid/arduino/lib/drivers-avr/ps2/keymap.h @@ -0,0 +1,152 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + + +enum Ps2KeyType : uint8_t { + PS2_KEY_TYPE_UNKNOWN = 0, + PS2_KEY_TYPE_REG = 1, + PS2_KEY_TYPE_SPEC = 2, + PS2_KEY_TYPE_PRINT = 3, + PS2_KEY_TYPE_PAUSE = 4, +}; + + +void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) { + *ps2_type = PS2_KEY_TYPE_UNKNOWN; + *ps2_code = 0; + + switch (code) { + case 1: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 28; return; // KeyA + case 2: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 50; return; // KeyB + case 3: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 33; return; // KeyC + case 4: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 35; return; // KeyD + case 5: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 36; return; // KeyE + case 6: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 43; return; // KeyF + case 7: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 52; return; // KeyG + case 8: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 51; return; // KeyH + case 9: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 67; return; // KeyI + case 10: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 59; return; // KeyJ + case 11: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 66; return; // KeyK + case 12: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 75; return; // KeyL + case 13: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 58; return; // KeyM + case 14: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 49; return; // KeyN + case 15: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 68; return; // KeyO + case 16: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 77; return; // KeyP + case 17: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 21; return; // KeyQ + case 18: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 45; return; // KeyR + case 19: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 27; return; // KeyS + case 20: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 44; return; // KeyT + case 21: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 60; return; // KeyU + case 22: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 42; return; // KeyV + case 23: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 29; return; // KeyW + case 24: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 34; return; // KeyX + case 25: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 53; return; // KeyY + case 26: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 26; return; // KeyZ + case 27: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 22; return; // Digit1 + case 28: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 30; return; // Digit2 + case 29: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 38; return; // Digit3 + case 30: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 37; return; // Digit4 + case 31: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 46; return; // Digit5 + case 32: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 54; return; // Digit6 + case 33: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 61; return; // Digit7 + case 34: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 62; return; // Digit8 + case 35: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 70; return; // Digit9 + case 36: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 69; return; // Digit0 + case 37: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 90; return; // Enter + case 38: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 118; return; // Escape + case 39: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 102; return; // Backspace + case 40: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 13; return; // Tab + case 41: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 41; return; // Space + case 42: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 78; return; // Minus + case 43: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 85; return; // Equal + case 44: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 84; return; // BracketLeft + case 45: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 91; return; // BracketRight + case 46: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 93; return; // Backslash + case 47: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 76; return; // Semicolon + case 48: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 82; return; // Quote + case 49: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 14; return; // Backquote + case 50: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 65; return; // Comma + case 51: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 73; return; // Period + case 52: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 74; return; // Slash + case 53: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 88; return; // CapsLock + case 54: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 5; return; // F1 + case 55: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 6; return; // F2 + case 56: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 4; return; // F3 + case 57: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 12; return; // F4 + case 58: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 3; return; // F5 + case 59: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 11; return; // F6 + case 60: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 131; return; // F7 + case 61: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 10; return; // F8 + case 62: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 1; return; // F9 + case 63: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 9; return; // F10 + case 64: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 120; return; // F11 + case 65: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 7; return; // F12 + case 66: *ps2_type = PS2_KEY_TYPE_PRINT; *ps2_code = 255; return; // PrintScreen + case 67: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 112; return; // Insert + case 68: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 108; return; // Home + case 69: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 125; return; // PageUp + case 70: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 113; return; // Delete + case 71: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 105; return; // End + case 72: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 122; return; // PageDown + case 73: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 116; return; // ArrowRight + case 74: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 107; return; // ArrowLeft + case 75: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 114; return; // ArrowDown + case 76: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 117; return; // ArrowUp + case 77: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 20; return; // ControlLeft + case 78: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 18; return; // ShiftLeft + case 79: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 17; return; // AltLeft + case 80: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 31; return; // MetaLeft + case 81: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 20; return; // ControlRight + case 82: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 89; return; // ShiftRight + case 83: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 17; return; // AltRight + case 84: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 39; return; // MetaRight + case 85: *ps2_type = PS2_KEY_TYPE_PAUSE; *ps2_code = 255; return; // Pause + case 86: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 126; return; // ScrollLock + case 87: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 119; return; // NumLock + case 88: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 47; return; // ContextMenu + case 89: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 74; return; // NumpadDivide + case 90: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 124; return; // NumpadMultiply + case 91: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 123; return; // NumpadSubtract + case 92: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 121; return; // NumpadAdd + case 93: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 90; return; // NumpadEnter + case 94: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 105; return; // Numpad1 + case 95: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 114; return; // Numpad2 + case 96: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 122; return; // Numpad3 + case 97: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 107; return; // Numpad4 + case 98: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 115; return; // Numpad5 + case 99: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 116; return; // Numpad6 + case 100: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 108; return; // Numpad7 + case 101: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 117; return; // Numpad8 + case 102: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 125; return; // Numpad9 + case 103: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 112; return; // Numpad0 + case 104: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 113; return; // NumpadDecimal + case 105: *ps2_type = PS2_KEY_TYPE_SPEC; *ps2_code = 94; return; // Power + case 106: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 97; return; // IntlBackslash + case 107: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 106; return; // IntlYen + case 108: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 81; return; // IntlRo + case 109: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 19; return; // KanaMode + case 110: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 100; return; // Convert + case 111: *ps2_type = PS2_KEY_TYPE_REG; *ps2_code = 103; return; // NonConvert + } +} diff --git a/hid/arduino/lib/drivers-avr/ps2/keymap.h.mako b/hid/arduino/lib/drivers-avr/ps2/keymap.h.mako new file mode 100644 index 00000000..75dfd8c2 --- /dev/null +++ b/hid/arduino/lib/drivers-avr/ps2/keymap.h.mako @@ -0,0 +1,44 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + + +enum Ps2KeyType : uint8_t { + PS2_KEY_TYPE_UNKNOWN = 0, + PS2_KEY_TYPE_REG = 1, + PS2_KEY_TYPE_SPEC = 2, + PS2_KEY_TYPE_PRINT = 3, + PS2_KEY_TYPE_PAUSE = 4, +}; + +<%! import operator %> +void keymapPs2(uint8_t code, Ps2KeyType *ps2_type, uint8_t *ps2_code) { + *ps2_type = PS2_KEY_TYPE_UNKNOWN; + *ps2_code = 0; + + switch (code) { +% for km in sorted(keymap, key=operator.attrgetter("mcu_code")): + case ${km.mcu_code}: *ps2_type = PS2_KEY_TYPE_${km.ps2_key.type.upper()}; *ps2_code = ${km.ps2_key.code}; return; // ${km.web_name} +% endfor + } +} diff --git a/hid/arduino/lib/drivers-avr/spi.cpp b/hid/arduino/lib/drivers-avr/spi.cpp new file mode 100644 index 00000000..f9cab4ac --- /dev/null +++ b/hid/arduino/lib/drivers-avr/spi.cpp @@ -0,0 +1,83 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#include "spi.h" + +#ifdef CMD_SPI + + +static volatile uint8_t _spi_in[8] = {0}; +static volatile uint8_t _spi_in_index = 0; + +static volatile uint8_t _spi_out[8] = {0}; +static volatile uint8_t _spi_out_index = 0; + + +namespace DRIVERS { + void Spi::begin() { + pinMode(MISO, OUTPUT); + SPCR = (1 << SPE) | (1 << SPIE); // Slave, SPI En, IRQ En + } + + void Spi::periodic() { + if (!_spi_out[0] && _spi_in_index == 8) { + _data_cb((const uint8_t *)_spi_in, 8); + } + } + + void Spi::write(const uint8_t *data, size_t size) { + // Меджик в нулевом байте разрешает начать ответ + for (int index = 7; index >= 0; --index) { + _spi_out[index] = data[index]; + } + } +} + +ISR(SPI_STC_vect) { + uint8_t in = SPDR; + if (_spi_out[0] && _spi_out_index < 8) { + SPDR = _spi_out[_spi_out_index]; + if (!(SPSR & (1 << WCOL))) { + ++_spi_out_index; + if (_spi_out_index == 8) { + _spi_out_index = 0; + _spi_in_index = 0; + _spi_out[0] = 0; + } + } + } else { + static bool receiving = false; + if (!receiving && in != 0) { + receiving = true; + } + if (receiving && _spi_in_index < 8) { + _spi_in[_spi_in_index] = in; + ++_spi_in_index; + } + if (_spi_in_index == 8) { + receiving = false; + } + SPDR = 0; + } +} + +#endif diff --git a/hid/arduino/lib/drivers-avr/spi.h b/hid/arduino/lib/drivers-avr/spi.h new file mode 100644 index 00000000..368895c2 --- /dev/null +++ b/hid/arduino/lib/drivers-avr/spi.h @@ -0,0 +1,40 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <Arduino.h> + +#include "connection.h" + + +namespace DRIVERS { + struct Spi : public Connection { + Spi() : Connection(CONNECTION) {} + + void begin() override; + + void periodic() override; + + void write(const uint8_t *data, size_t size) override; + }; +} diff --git a/hid/arduino/lib/drivers-avr/usb/hid.h b/hid/arduino/lib/drivers-avr/usb/hid.h new file mode 100644 index 00000000..aa7b7ba0 --- /dev/null +++ b/hid/arduino/lib/drivers-avr/usb/hid.h @@ -0,0 +1,230 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <Arduino.h> +#include <HID-Project.h> + +#include "keyboard.h" +#include "mouse.h" +#include "tools.h" +#include "usb-keymap.h" +#ifdef AUM +# include "aum.h" +#endif + +using namespace DRIVERS; + +// ----------------------------------------------------------------------------- +#ifdef HID_USB_CHECK_ENDPOINT +// https://github.com/arduino/ArduinoCore-avr/blob/2f67c916f6ab6193c404eebe22efe901e0f9542d/cores/arduino/USBCore.cpp#L249 +// https://sourceforge.net/p/arduinomidilib/svn/41/tree/branch/3.1/Teensy/teensy_core/usb_midi/usb_api.cpp#l103 +# ifdef AUM +# define CHECK_AUM_USB { if (!aumIsUsbConnected()) { return true; } } +# else +# define CHECK_AUM_USB +# endif +# define CLS_IS_OFFLINE(_hid) \ + bool isOffline() override { \ + CHECK_AUM_USB; \ + uint8_t ep = _hid.getPluggedEndpoint(); \ + uint8_t intr_state = SREG; \ + cli(); \ + UENUM = ep & 7; \ + bool rw_allowed = UEINTX & (1 << RWAL); \ + SREG = intr_state; \ + if (rw_allowed) { \ + return false; \ + } \ + return true; \ + } +# define CHECK_HID_EP { if (isOffline()) return; } + +#else +# define CLS_IS_OFFLINE(_hid) \ + bool isOffline() override { \ + return false; \ + } +# define CHECK_HID_EP + +#endif + + +class UsbKeyboard : public DRIVERS::Keyboard { + public: + UsbKeyboard() : DRIVERS::Keyboard(DRIVERS::USB_KEYBOARD) {} + + void begin() override { + _kbd.begin(); + } + + void periodic() override { +# ifdef HID_USB_CHECK_ENDPOINT + static unsigned long prev_ts = 0; + if (is_micros_timed_out(prev_ts, 50000)) { + static bool prev_online = true; + bool online = !isOffline(); + if (!_sent || (online && !prev_online)) { + _sendCurrent(); + } + prev_online = online; + prev_ts = micros(); + } +# endif + } + + void clear() override { + _kbd.releaseAll(); + } + + void sendKey(uint8_t code, bool state) override { + enum KeyboardKeycode usb_code = keymapUsb(code); + if (usb_code > 0) { + if (state ? _kbd.add(usb_code) : _kbd.remove(usb_code)) { + _sendCurrent(); + } + } + } + + CLS_IS_OFFLINE(_kbd) + + KeyboardLedsState getLeds() override { + uint8_t leds = _kbd.getLeds(); + KeyboardLedsState result = { + .caps = leds & LED_CAPS_LOCK, + .scroll = leds & LED_SCROLL_LOCK, + .num = leds & LED_NUM_LOCK, + }; + return result; + } + + private: + BootKeyboard_ _kbd; + bool _sent = true; + + void _sendCurrent() { +# ifdef HID_USB_CHECK_ENDPOINT + if (isOffline()) { + _sent = false; + } else { +# endif + _sent = (_kbd.send() >= 0); +# ifdef HID_USB_CHECK_ENDPOINT + } +# endif + } +}; + +#define CLS_SEND_BUTTONS \ + void sendButtons( \ + bool left_select, bool left_state, \ + bool right_select, bool right_state, \ + bool middle_select, bool middle_state, \ + bool up_select, bool up_state, \ + bool down_select, bool down_state \ + ) override { \ + if (left_select) _sendButton(MOUSE_LEFT, left_state); \ + if (right_select) _sendButton(MOUSE_RIGHT, right_state); \ + if (middle_select) _sendButton(MOUSE_MIDDLE, middle_state); \ + if (up_select) _sendButton(MOUSE_PREV, up_state); \ + if (down_select) _sendButton(MOUSE_NEXT, down_state); \ + } + +class UsbMouseAbsolute : public DRIVERS::Mouse { + public: + UsbMouseAbsolute(DRIVERS::type _type) : Mouse(_type) {} + + void begin() override { + _mouse.begin(); + _mouse.setWin98FixEnabled(getType() == DRIVERS::USB_MOUSE_ABSOLUTE_WIN98); + } + + void clear() override { + _mouse.releaseAll(); + } + + CLS_SEND_BUTTONS + + void sendMove(int x, int y) override { + CHECK_HID_EP; + _mouse.moveTo(x, y); + } + + void sendWheel(int delta_y) override { + // delta_x is not supported by hid-project now + CHECK_HID_EP; + _mouse.move(0, 0, delta_y); + } + + CLS_IS_OFFLINE(_mouse) + + private: + SingleAbsoluteMouse_ _mouse; + + void _sendButton(uint8_t button, bool state) { + CHECK_HID_EP; + if (state) _mouse.press(button); + else _mouse.release(button); + } +}; + +class UsbMouseRelative : public DRIVERS::Mouse { + public: + UsbMouseRelative() : DRIVERS::Mouse(DRIVERS::USB_MOUSE_RELATIVE) {} + + void begin() override { + _mouse.begin(); + } + + void clear() override { + _mouse.releaseAll(); + } + + CLS_SEND_BUTTONS + + void sendRelative(int x, int y) override { + CHECK_HID_EP; + _mouse.move(x, y, 0); + } + + void sendWheel(int delta_y) override { + // delta_x is not supported by hid-project now + CHECK_HID_EP; + _mouse.move(0, 0, delta_y); + } + + CLS_IS_OFFLINE(_mouse) + + private: + BootMouse_ _mouse; + + void _sendButton(uint8_t button, bool state) { + CHECK_HID_EP; + if (state) _mouse.press(button); + else _mouse.release(button); + } +}; + +#undef CLS_SEND_BUTTONS +#undef CLS_IS_OFFLINE +#undef CHECK_HID_EP diff --git a/hid/arduino/lib/drivers-stm32/README.md b/hid/arduino/lib/drivers-stm32/README.md new file mode 100644 index 00000000..93de3312 --- /dev/null +++ b/hid/arduino/lib/drivers-stm32/README.md @@ -0,0 +1,22 @@ +This is WIP. Use AVR version as reference. + +It was tested with bluepill. Most boards are clones. +If you have problem with USB please check https://stm32duinoforum.com/forum/wiki_subdomain/index_title_Blue_Pill.html for pull up resistor. If it still does not work check another board or cable. + +TODO: +- [x] Serial communication +- [x] USB keyboard +- [x] USB keyboard - add scroll status +- [x] USB keyboard - key sending +- [x] USB keyboard - test key mapping +- [x] Persistent storage +- [ ] SPI communication +- [ ] PS2 keyboard +- [x] USB absolute mouse +- [x] USB absolute mouse - add whele +- [x] USB relative mouse +- [x] USB relative mouse - add whele +- [ ] USB mouses - up down button +- [ ] WIN98 USB mouse +- [x] undefine SERIAL_USB +- [ ] boot keyboard diff --git a/hid/arduino/lib/drivers-stm32/backup-register.h b/hid/arduino/lib/drivers-stm32/backup-register.h new file mode 100644 index 00000000..196e4e1c --- /dev/null +++ b/hid/arduino/lib/drivers-stm32/backup-register.h @@ -0,0 +1,53 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + +# pragma once + + +#include <stm32f1_rtc.h> + +#include "storage.h" + + +namespace DRIVERS { + struct BackupRegister : public Storage { + BackupRegister() : Storage(NON_VOLATILE_STORAGE) { + _rtc.enableClockInterface(); + } + + void readBlock(void *dest, const void *src, size_t size) override { + uint8_t *dest_ = reinterpret_cast<uint8_t*>(dest); + for(size_t index = 0; index < size; ++index) { + dest_[index] = _rtc.getBackupRegister(reinterpret_cast<uintptr_t>(src) + index + 1); + } + } + + void updateBlock(const void *src, void *dest, size_t size) override { + const uint8_t *src_ = reinterpret_cast<const uint8_t*>(src); + for(size_t index = 0; index < size; ++index) { + _rtc.setBackupRegister(reinterpret_cast<uintptr_t>(dest) + index + 1, src_[index]); + } + } + + private: + STM32F1_RTC _rtc; + }; +} diff --git a/hid/arduino/lib/drivers-stm32/bluepill_sch.png b/hid/arduino/lib/drivers-stm32/bluepill_sch.png Binary files differnew file mode 100644 index 00000000..956448dd --- /dev/null +++ b/hid/arduino/lib/drivers-stm32/bluepill_sch.png diff --git a/hid/arduino/lib/drivers-stm32/board-stm32.h b/hid/arduino/lib/drivers-stm32/board-stm32.h new file mode 100644 index 00000000..1602edaa --- /dev/null +++ b/hid/arduino/lib/drivers-stm32/board-stm32.h @@ -0,0 +1,103 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include "board.h" +#include <libmaple/iwdg.h> + + +namespace DRIVERS { + class BoardStm32 : public Board { + public: + BoardStm32() : Board(BOARD){ + //2 sec timeout + iwdg_init(IWDG_PRE_16, 0xFFF); + pinMode(LED_BUILTIN, OUTPUT); + } + + void reset() override { + nvic_sys_reset(); + } + + void periodic() override { + iwdg_feed(); + if (is_micros_timed_out(_prev_ts, 100000)) { + switch(_state) { + case 0: + digitalWrite(LED_BUILTIN, LOW); + break; + case 2: + if(_rx_data) { + _rx_data = false; + digitalWrite(LED_BUILTIN, LOW); + } + break; + case 4: + if(_keyboard_online) { + _keyboard_online = false; + digitalWrite(LED_BUILTIN, LOW); + } + break; + case 8: + if(_mouse_online) { + _mouse_online = false; + digitalWrite(LED_BUILTIN, LOW); + } + break; + case 1: // heartbeat off + case 3: // _rx_data off + case 7: // _keyboard_online off + case 11: // _mouse_online off + digitalWrite(LED_BUILTIN, HIGH); + break; + case 19: + _state = -1; + break; + } + ++_state; + _prev_ts = micros(); + } + } + + void updateStatus(status status) override { + switch (status) { + case RX_DATA: + _rx_data = true; + break; + case KEYBOARD_ONLINE: + _keyboard_online = true; + break; + case MOUSE_ONLINE: + _mouse_online = true; + break; + } + } + + private: + unsigned long _prev_ts = 0; + uint8_t _state = 0; + bool _rx_data = false; + bool _keyboard_online = false; + bool _mouse_online = false; + }; +} diff --git a/hid/arduino/lib/drivers-stm32/factory.cpp b/hid/arduino/lib/drivers-stm32/factory.cpp new file mode 100644 index 00000000..8f20e0ff --- /dev/null +++ b/hid/arduino/lib/drivers-stm32/factory.cpp @@ -0,0 +1,94 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#include "factory.h" +#include "usb/keyboard-stm32.h" +#include "usb/hid-wrapper-stm32.h" +#include "usb/mouse-absolute-stm32.h" +#include "usb/mouse-relative-stm32.h" +#include "backup-register.h" +#include "board-stm32.h" +#include "serial.h" + +#ifndef __STM32F1__ +# error "Only STM32F1 is supported" +#endif +#ifdef SERIAL_USB +# error "Disable random USB enumeration" +#endif + + +namespace DRIVERS { + HidWrapper _hidWrapper; + + Keyboard *Factory::makeKeyboard(type _type) { + switch (_type) { +# ifdef HID_WITH_USB + case USB_KEYBOARD: + return new UsbKeyboard(_hidWrapper); +# endif + default: + return new Keyboard(DUMMY); + } + } + + Mouse *Factory::makeMouse(type _type) { + switch(_type) { +# ifdef HID_WITH_USB + case USB_MOUSE_ABSOLUTE: + return new UsbMouseAbsolute(_hidWrapper); + case USB_MOUSE_RELATIVE: + return new UsbMouseRelative(_hidWrapper); +# endif + default: + return new Mouse(DRIVERS::DUMMY); + } + } + + Storage *Factory::makeStorage(type _type) { + switch (_type) { +# ifdef HID_DYNAMIC + case NON_VOLATILE_STORAGE: + return new BackupRegister(); +# endif + default: + return new Storage(DRIVERS::DUMMY); + } + } + + Board *Factory::makeBoard(type _type) { + switch (_type) { + case BOARD: + return new BoardStm32(); + default: + return new Board(DRIVERS::DUMMY); + } + } + + Connection *Factory::makeConnection(type _type) { +# ifdef CMD_SERIAL + return new Serial(); +# else +# error CMD phy is not defined +# endif + } +} diff --git a/hid/arduino/lib/drivers-stm32/usb/hid-wrapper-stm32.h b/hid/arduino/lib/drivers-stm32/usb/hid-wrapper-stm32.h new file mode 100644 index 00000000..62f58213 --- /dev/null +++ b/hid/arduino/lib/drivers-stm32/usb/hid-wrapper-stm32.h @@ -0,0 +1,72 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <USBComposite.h> + + +namespace DRIVERS { + class HidWrapper { + public: + void begin() { + if (_init) { + return; + } + _init = true; + + _report_descriptor_length = 0; + for (unsigned index = 0; index < _count; ++index) { + _report_descriptor_length += _descriptors_size[index]; + } + + _report_descriptor = new uint8[_report_descriptor_length]; + + size_t offset = 0; + for (unsigned index = 0; index < _count; ++index) { + memcpy(_report_descriptor + offset, _report_descriptors[index], _descriptors_size[index]); + offset += _descriptors_size[index]; + } + + usbHid.begin(_report_descriptor, _report_descriptor_length); + } + + void addReportDescriptor(const uint8_t *report_descriptor, uint16_t report_descriptor_length) { + _report_descriptors[_count] = report_descriptor; + _descriptors_size[_count] = report_descriptor_length; + ++_count; + } + + USBHID usbHid; + + private: + bool _init = false; + + static constexpr uint8_t MAX_USB_DESCRIPTORS = 2; + const uint8_t *_report_descriptors[MAX_USB_DESCRIPTORS]; + uint8_t _descriptors_size[MAX_USB_DESCRIPTORS]; + + uint8_t _count = 0; + uint8_t *_report_descriptor; + uint16_t _report_descriptor_length; + }; +} diff --git a/hid/arduino/lib/drivers-stm32/usb/keyboard-stm32.h b/hid/arduino/lib/drivers-stm32/usb/keyboard-stm32.h new file mode 100644 index 00000000..22dec04d --- /dev/null +++ b/hid/arduino/lib/drivers-stm32/usb/keyboard-stm32.h @@ -0,0 +1,92 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <USBComposite.h> + +#include "tools.h" +#include "keyboard.h" +#include "usb-keymap.h" +#include "hid-wrapper-stm32.h" + + +namespace DRIVERS { + const uint8_t reportDescriptionKeyboard[] = { + HID_KEYBOARD_REPORT_DESCRIPTOR(), + }; + + class UsbKeyboard : public Keyboard { + public: + UsbKeyboard(HidWrapper& _hidWrapper) : Keyboard(USB_KEYBOARD), + _hidWrapper(_hidWrapper), _keyboard(_hidWrapper.usbHid) { + _hidWrapper.addReportDescriptor(reportDescriptionKeyboard, sizeof(reportDescriptionKeyboard)); + } + + void begin() override { + _hidWrapper.begin(); + _keyboard.begin(); + } + + void clear() override { + _keyboard.releaseAll(); + } + + void sendKey(uint8_t code, bool state) override { + uint16_t usb_code = keymapUsb(code); + if (usb_code == 0) { + return; + } + + // 0xE0 is a prefix from HID-Project keytable + if (usb_code >= 0xE0 && usb_code <= 0xE7) { + usb_code = usb_code - 0xE0 + 0x80; + } else { + usb_code += KEY_HID_OFFSET; + } + + if (state) { + _keyboard.press(usb_code); + } else { + _keyboard.release(usb_code); + } + } + + bool isOffline() override { + return (USBComposite == false); + } + + KeyboardLedsState getLeds() override { + uint8_t leds = _keyboard.getLEDs(); + KeyboardLedsState result = { + .caps = leds & 0b00000010, + .scroll = leds & 0b00000100, + .num = leds & 0b00000001, + }; + return result; + } + + private: + HidWrapper& _hidWrapper; + HIDKeyboard _keyboard; + }; +} diff --git a/hid/arduino/lib/drivers-stm32/usb/mouse-absolute-stm32.h b/hid/arduino/lib/drivers-stm32/usb/mouse-absolute-stm32.h new file mode 100644 index 00000000..65195c78 --- /dev/null +++ b/hid/arduino/lib/drivers-stm32/usb/mouse-absolute-stm32.h @@ -0,0 +1,86 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <USBComposite.h> + +#include "mouse.h" +#include "hid-wrapper-stm32.h" + + +namespace DRIVERS { + const uint8_t reportDescriptionMouseAbsolute[] = { + HID_ABS_MOUSE_REPORT_DESCRIPTOR() + }; + + class UsbMouseAbsolute : public Mouse { + public: + UsbMouseAbsolute(HidWrapper& _hidWrapper) : Mouse(USB_MOUSE_ABSOLUTE), + _hidWrapper(_hidWrapper), _mouse(_hidWrapper.usbHid) { + _hidWrapper.addReportDescriptor(reportDescriptionMouseAbsolute, sizeof(reportDescriptionMouseAbsolute)); + } + + void begin() override { + _hidWrapper.begin(); + } + + void clear() override { + _mouse.release(0xFF); + } + + void sendButtons ( + bool left_select, bool left_state, + bool right_select, bool right_state, + bool middle_select, bool middle_state, + bool up_select, bool up_state, + bool down_select, bool down_state) override { + +# define SEND_BUTTON(x_low, x_up) { \ + if (x_low##_select) { \ + if (x_low##_state) _mouse.press(MOUSE_##x_up); \ + else _mouse.release(MOUSE_##x_up); \ + } \ + } + SEND_BUTTON(left, LEFT); + SEND_BUTTON(right, RIGHT); + SEND_BUTTON(middle, MIDDLE); +# undef SEND_BUTTON + } + + void sendMove(int x, int y) override { + _mouse.move(x, y); + } + + void sendWheel(int delta_y) override { + _mouse.move(0, 0, delta_y); + } + + bool isOffline() override { + return (USBComposite == false); + } + + private: + HidWrapper& _hidWrapper; + HIDAbsMouse _mouse; + }; +} diff --git a/hid/arduino/lib/drivers-stm32/usb/mouse-relative-stm32.h b/hid/arduino/lib/drivers-stm32/usb/mouse-relative-stm32.h new file mode 100644 index 00000000..4524e20b --- /dev/null +++ b/hid/arduino/lib/drivers-stm32/usb/mouse-relative-stm32.h @@ -0,0 +1,86 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <USBComposite.h> + +#include "mouse.h" +#include "hid-wrapper-stm32.h" + + +namespace DRIVERS { + const uint8_t reportDescriptionMouseRelative[] = { + HID_MOUSE_REPORT_DESCRIPTOR() + }; + + class UsbMouseRelative : public Mouse { + public: + UsbMouseRelative(HidWrapper& _hidWrapper) : Mouse(USB_MOUSE_RELATIVE), + _hidWrapper(_hidWrapper), _mouse(_hidWrapper.usbHid) { + _hidWrapper.addReportDescriptor(reportDescriptionMouseRelative, sizeof(reportDescriptionMouseRelative)); + } + + void begin() override { + _hidWrapper.begin(); + } + + void clear() override { + _mouse.release(0xFF); + } + + void sendButtons ( + bool left_select, bool left_state, + bool right_select, bool right_state, + bool middle_select, bool middle_state, + bool up_select, bool up_state, + bool down_select, bool down_state) override { + +# define SEND_BUTTON(x_low, x_up) { \ + if (x_low##_select) { \ + if (x_low##_state) _mouse.press(MOUSE_##x_up); \ + else _mouse.release(MOUSE_##x_up); \ + } \ + } + SEND_BUTTON(left, LEFT); + SEND_BUTTON(right, RIGHT); + SEND_BUTTON(middle, MIDDLE); +# undef SEND_BUTTON + } + + void sendRelative(int x, int y) override { + _mouse.move(x, y); + } + + void sendWheel(int delta_y) override { + _mouse.move(0, 0, delta_y); + } + + bool isOffline() override { + return (USBComposite == false); + } + + private: + HidWrapper& _hidWrapper; + HIDMouse _mouse; + }; +} diff --git a/hid/arduino/lib/drivers/aum.h b/hid/arduino/lib/drivers/aum.h new file mode 100644 index 00000000..3d407d12 --- /dev/null +++ b/hid/arduino/lib/drivers/aum.h @@ -0,0 +1,48 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <digitalWriteFast.h> + + +inline void aumInit() { + pinModeFast(AUM_IS_USB_POWERED_PIN, INPUT); + pinModeFast(AUM_SET_USB_VBUS_PIN, OUTPUT); + pinModeFast(AUM_SET_USB_CONNECTED_PIN, OUTPUT); + digitalWriteFast(AUM_SET_USB_CONNECTED_PIN, HIGH); +} + +inline void aumProxyUsbVbus() { + bool vbus = digitalReadFast(AUM_IS_USB_POWERED_PIN); + if (digitalReadFast(AUM_SET_USB_VBUS_PIN) != vbus) { + digitalWriteFast(AUM_SET_USB_VBUS_PIN, vbus); + } +} + +inline void aumSetUsbConnected(bool connected) { + digitalWriteFast(AUM_SET_USB_CONNECTED_PIN, connected); +} + +inline bool aumIsUsbConnected() { + return digitalReadFast(AUM_SET_USB_CONNECTED_PIN); +} diff --git a/hid/arduino/lib/drivers/board.h b/hid/arduino/lib/drivers/board.h new file mode 100644 index 00000000..cc431d62 --- /dev/null +++ b/hid/arduino/lib/drivers/board.h @@ -0,0 +1,41 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include "driver.h" + + +namespace DRIVERS { + enum status { + RX_DATA = 0, + KEYBOARD_ONLINE, + MOUSE_ONLINE, + }; + + struct Board : public Driver { + using Driver::Driver; + virtual void reset() {} + virtual void periodic() {} + virtual void updateStatus(status status) {} + }; +} diff --git a/hid/arduino/lib/drivers/connection.h b/hid/arduino/lib/drivers/connection.h new file mode 100644 index 00000000..7a9beb7b --- /dev/null +++ b/hid/arduino/lib/drivers/connection.h @@ -0,0 +1,54 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include "driver.h" +#include "stdint.h" + + +namespace DRIVERS { + typedef void (*DataHandler)(const uint8_t *data, size_t size); + typedef void (*TimeoutHandler)(); + + struct Connection : public Driver { + using Driver::Driver; + + virtual void begin() {} + + virtual void periodic() {} + + void onTimeout(TimeoutHandler cb) { + _timeout_cb = cb; + } + + void onData(DataHandler cb) { + _data_cb = cb; + } + + virtual void write(const uint8_t *data, size_t size) = 0; + + protected: + TimeoutHandler _timeout_cb = nullptr; + DataHandler _data_cb = nullptr; + }; +} diff --git a/hid/arduino/lib/drivers/driver.h b/hid/arduino/lib/drivers/driver.h new file mode 100644 index 00000000..af60b112 --- /dev/null +++ b/hid/arduino/lib/drivers/driver.h @@ -0,0 +1,49 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <stdint.h> + + +namespace DRIVERS { + enum type { + DUMMY = 0, + USB_MOUSE_ABSOLUTE, + USB_MOUSE_RELATIVE, + USB_MOUSE_ABSOLUTE_WIN98, + USB_KEYBOARD, + PS2_KEYBOARD, + NON_VOLATILE_STORAGE, + BOARD, + CONNECTION, + }; + + class Driver { + public: + Driver(type _type) : _type(_type) {} + uint8_t getType() { return _type; } + + private: + type _type; + }; +} diff --git a/hid/arduino/lib/drivers/factory.h b/hid/arduino/lib/drivers/factory.h new file mode 100644 index 00000000..116a6c84 --- /dev/null +++ b/hid/arduino/lib/drivers/factory.h @@ -0,0 +1,39 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + +#pragma once + +#include "keyboard.h" +#include "mouse.h" +#include "storage.h" +#include "board.h" +#include "connection.h" + + +namespace DRIVERS { + struct Factory { + static Keyboard *makeKeyboard(type _type); + static Mouse *makeMouse(type _type); + static Storage *makeStorage(type _type); + static Board *makeBoard(type _type); + static Connection *makeConnection(type _type); + }; +} diff --git a/hid/arduino/lib/drivers/keyboard.h b/hid/arduino/lib/drivers/keyboard.h new file mode 100644 index 00000000..1128def9 --- /dev/null +++ b/hid/arduino/lib/drivers/keyboard.h @@ -0,0 +1,68 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <stdint.h> + +#include "driver.h" + + +namespace DRIVERS { + typedef struct { + bool caps; + bool scroll; + bool num; + } KeyboardLedsState; + + struct Keyboard : public Driver { + using Driver::Driver; + + virtual void begin() {} + + /** + * Release all keys + */ + virtual void clear() {} + + /** + * Sends key + * @param code ??? + * @param state true pressed, false released + */ + virtual void sendKey(uint8_t code, bool state) {} + + virtual void periodic() {} + + /** + * False if online or unknown. Otherwise true. + */ + virtual bool isOffline() { + return false; + } + + virtual KeyboardLedsState getLeds() { + KeyboardLedsState result = {0}; + return result; + } + }; +} diff --git a/hid/arduino/lib/drivers/mouse.h b/hid/arduino/lib/drivers/mouse.h new file mode 100644 index 00000000..83216e29 --- /dev/null +++ b/hid/arduino/lib/drivers/mouse.h @@ -0,0 +1,51 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <stdint.h> + +#include "driver.h" + + +namespace DRIVERS { + struct Mouse : public Driver { + using Driver::Driver; + virtual void begin() {} + + /** + * Release all keys + */ + virtual void clear() {} + virtual void sendButtons( + bool left_select, bool left_state, + bool right_select, bool right_state, + bool middle_select, bool middle_state, + bool up_select, bool up_state, + bool down_select, bool down_state) {} + virtual void sendMove(int x, int y) {} + virtual void sendRelative(int x, int y) {} + virtual void sendWheel(int delta_y) {} + virtual bool isOffline() { return false; } + virtual void periodic() {} + }; +} diff --git a/hid/arduino/lib/drivers/serial.h b/hid/arduino/lib/drivers/serial.h new file mode 100644 index 00000000..32ec5613 --- /dev/null +++ b/hid/arduino/lib/drivers/serial.h @@ -0,0 +1,68 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#ifdef CMD_SERIAL +#include "connection.h" + + +namespace DRIVERS { +#ifdef Serial +# undef Serial +#endif + struct Serial : public Connection { + Serial() : Connection(CONNECTION) {} + + void begin() override { + CMD_SERIAL.begin(CMD_SERIAL_SPEED); + } + + void periodic() override { + if (CMD_SERIAL.available() > 0) { + _buffer[_index] = (uint8_t)CMD_SERIAL.read(); + if (_index == 7) { + _data_cb(_buffer, 8); + _index = 0; + } else { + _last = micros(); + ++_index; + } + } else if (_index > 0) { + if (is_micros_timed_out(_last, CMD_SERIAL_TIMEOUT)) { + _timeout_cb(); + _index = 0; + } + } + } + + void write(const uint8_t *data, size_t size) override { + CMD_SERIAL.write(data, size); + } + + private: + unsigned long _last = 0; + uint8_t _index = 0; + uint8_t _buffer[8]; + }; +} +#endif diff --git a/hid/arduino/lib/drivers/storage.h b/hid/arduino/lib/drivers/storage.h new file mode 100644 index 00000000..3b676905 --- /dev/null +++ b/hid/arduino/lib/drivers/storage.h @@ -0,0 +1,35 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include "driver.h" +#include "stdlib.h" + + +namespace DRIVERS { + struct Storage : public Driver { + using Driver::Driver; + virtual void readBlock(void *dest, const void *src, size_t size) {} + virtual void updateBlock(const void *src, void *dest, size_t size) {} + }; +} diff --git a/hid/arduino/lib/drivers/tools.cpp b/hid/arduino/lib/drivers/tools.cpp new file mode 100644 index 00000000..a6585245 --- /dev/null +++ b/hid/arduino/lib/drivers/tools.cpp @@ -0,0 +1,32 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#include "tools.h" + + +bool is_micros_timed_out(unsigned long start_ts, unsigned long timeout) { + unsigned long now = micros(); + return ( + (now >= start_ts && now - start_ts > timeout) + || (now < start_ts && ((unsigned long)-1) - start_ts + now > timeout) + ); +} diff --git a/hid/arduino/lib/drivers/tools.h b/hid/arduino/lib/drivers/tools.h new file mode 100644 index 00000000..34f88022 --- /dev/null +++ b/hid/arduino/lib/drivers/tools.h @@ -0,0 +1,28 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +#include <Arduino.h> + + +bool is_micros_timed_out(unsigned long start_ts, unsigned long timeout); diff --git a/hid/arduino/lib/drivers/usb-keymap.h b/hid/arduino/lib/drivers/usb-keymap.h new file mode 100644 index 00000000..5a86337f --- /dev/null +++ b/hid/arduino/lib/drivers/usb-keymap.h @@ -0,0 +1,141 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + + +uint8_t keymapUsb(uint8_t code) { + switch (code) { + case 1: return 4; // KeyA + case 2: return 5; // KeyB + case 3: return 6; // KeyC + case 4: return 7; // KeyD + case 5: return 8; // KeyE + case 6: return 9; // KeyF + case 7: return 10; // KeyG + case 8: return 11; // KeyH + case 9: return 12; // KeyI + case 10: return 13; // KeyJ + case 11: return 14; // KeyK + case 12: return 15; // KeyL + case 13: return 16; // KeyM + case 14: return 17; // KeyN + case 15: return 18; // KeyO + case 16: return 19; // KeyP + case 17: return 20; // KeyQ + case 18: return 21; // KeyR + case 19: return 22; // KeyS + case 20: return 23; // KeyT + case 21: return 24; // KeyU + case 22: return 25; // KeyV + case 23: return 26; // KeyW + case 24: return 27; // KeyX + case 25: return 28; // KeyY + case 26: return 29; // KeyZ + case 27: return 30; // Digit1 + case 28: return 31; // Digit2 + case 29: return 32; // Digit3 + case 30: return 33; // Digit4 + case 31: return 34; // Digit5 + case 32: return 35; // Digit6 + case 33: return 36; // Digit7 + case 34: return 37; // Digit8 + case 35: return 38; // Digit9 + case 36: return 39; // Digit0 + case 37: return 40; // Enter + case 38: return 41; // Escape + case 39: return 42; // Backspace + case 40: return 43; // Tab + case 41: return 44; // Space + case 42: return 45; // Minus + case 43: return 46; // Equal + case 44: return 47; // BracketLeft + case 45: return 48; // BracketRight + case 46: return 49; // Backslash + case 47: return 51; // Semicolon + case 48: return 52; // Quote + case 49: return 53; // Backquote + case 50: return 54; // Comma + case 51: return 55; // Period + case 52: return 56; // Slash + case 53: return 57; // CapsLock + case 54: return 58; // F1 + case 55: return 59; // F2 + case 56: return 60; // F3 + case 57: return 61; // F4 + case 58: return 62; // F5 + case 59: return 63; // F6 + case 60: return 64; // F7 + case 61: return 65; // F8 + case 62: return 66; // F9 + case 63: return 67; // F10 + case 64: return 68; // F11 + case 65: return 69; // F12 + case 66: return 70; // PrintScreen + case 67: return 73; // Insert + case 68: return 74; // Home + case 69: return 75; // PageUp + case 70: return 76; // Delete + case 71: return 77; // End + case 72: return 78; // PageDown + case 73: return 79; // ArrowRight + case 74: return 80; // ArrowLeft + case 75: return 81; // ArrowDown + case 76: return 82; // ArrowUp + case 77: return 224; // ControlLeft + case 78: return 225; // ShiftLeft + case 79: return 226; // AltLeft + case 80: return 227; // MetaLeft + case 81: return 228; // ControlRight + case 82: return 229; // ShiftRight + case 83: return 230; // AltRight + case 84: return 231; // MetaRight + case 85: return 72; // Pause + case 86: return 71; // ScrollLock + case 87: return 83; // NumLock + case 88: return 101; // ContextMenu + case 89: return 84; // NumpadDivide + case 90: return 85; // NumpadMultiply + case 91: return 86; // NumpadSubtract + case 92: return 87; // NumpadAdd + case 93: return 88; // NumpadEnter + case 94: return 89; // Numpad1 + case 95: return 90; // Numpad2 + case 96: return 91; // Numpad3 + case 97: return 92; // Numpad4 + case 98: return 93; // Numpad5 + case 99: return 94; // Numpad6 + case 100: return 95; // Numpad7 + case 101: return 96; // Numpad8 + case 102: return 97; // Numpad9 + case 103: return 98; // Numpad0 + case 104: return 99; // NumpadDecimal + case 105: return 102; // Power + case 106: return 100; // IntlBackslash + case 107: return 137; // IntlYen + case 108: return 135; // IntlRo + case 109: return 136; // KanaMode + case 110: return 138; // Convert + case 111: return 139; // NonConvert + default: return 0; + } +} diff --git a/hid/arduino/lib/drivers/usb-keymap.h.mako b/hid/arduino/lib/drivers/usb-keymap.h.mako new file mode 100644 index 00000000..5ae01421 --- /dev/null +++ b/hid/arduino/lib/drivers/usb-keymap.h.mako @@ -0,0 +1,37 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + +<%! import operator %> +uint8_t keymapUsb(uint8_t code) { + switch (code) { +% for km in sorted(keymap, key=operator.attrgetter("mcu_code")): + % if km.usb_key.is_modifier: + case ${km.mcu_code}: return ${km.usb_key.arduino_modifier_code}; // ${km.web_name} + % else: + case ${km.mcu_code}: return ${km.usb_key.code}; // ${km.web_name} + % endif +% endfor + default: return 0; + } +} diff --git a/hid/arduino/patch.py b/hid/arduino/patch.py new file mode 100644 index 00000000..c7674759 --- /dev/null +++ b/hid/arduino/patch.py @@ -0,0 +1,49 @@ +# https://docs.platformio.org/en/latest/projectconf/advanced_scripting.html + + +from os.path import exists +from os.path import join +from os.path import basename + +from typing import Dict + +Import("env") + + +# ===== +def _get_pkg_path(name: str) -> str: + path = env.PioPlatform().get_package_dir(name) + assert exists(path) + return path + + +def _get_libs() -> Dict[str, str]: + return { + builder.name: builder.path + for builder in env.GetLibBuilders() + } + + +def _patch(path: str, patch_path: str) -> None: + assert exists(path) + flag_path: str = join(path, f".{basename(patch_path)}.done") + if not exists(flag_path): + # TODO check for failure + env.Execute(f"patch -p1 -d {path} < {patch_path}") + env.Execute(lambda *_, **__: open(flag_path, "w").close()) + + +# ===== +if env.GetProjectOption("platform") == "ststm32": + _patch(_get_pkg_path("framework-arduinoststm32-maple"), "patches/platformio-stm32f1-no-serial-usb.patch") +elif env.GetProjectOption("platform") == "atmelavr": + _patch(_get_pkg_path("framework-arduino-avr"), "patches/arduino-main-no-usb.patch") + _patch(_get_pkg_path("framework-arduino-avr"), "patches/arduino-optional-cdc.patch") + _patch(_get_pkg_path("framework-arduino-avr"), "patches/arduino-get-plugged-endpoint.patch") + + _libs = _get_libs() + _patch(_libs["HID-Project"], "patches/hid-shut-up.patch") + _patch(_libs["HID-Project"], "patches/hid-no-singletones.patch") + _patch(_libs["HID-Project"], "patches/hid-win98.patch") +else: + assert(False) diff --git a/hid/arduino/patches/arduino-get-plugged-endpoint.patch b/hid/arduino/patches/arduino-get-plugged-endpoint.patch new file mode 100644 index 00000000..c32131ff --- /dev/null +++ b/hid/arduino/patches/arduino-get-plugged-endpoint.patch @@ -0,0 +1,11 @@ +--- a/cores/arduino/PluggableUSB.h 2019-05-16 15:52:01.000000000 +0300 ++++ b/cores/arduino/PluggableUSB.h 2020-11-14 20:57:30.942432544 +0300 +@@ -31,6 +31,8 @@ + numEndpoints(numEps), numInterfaces(numIfs), endpointType(epType) + { } + ++ uint8_t getPluggedEndpoint() { return pluggedEndpoint; } ++ + protected: + virtual bool setup(USBSetup& setup) = 0; + virtual int getInterface(uint8_t* interfaceCount) = 0; diff --git a/hid/arduino/patches/arduino-main-no-usb.patch b/hid/arduino/patches/arduino-main-no-usb.patch new file mode 100644 index 00000000..ab10c9a8 --- /dev/null +++ b/hid/arduino/patches/arduino-main-no-usb.patch @@ -0,0 +1,24 @@ +diff --git a/cores/arduino/main.cpp b/cores/arduino/main.cpp +index 434cd40..7aba76f 100644 +--- a/cores/arduino/main.cpp ++++ b/cores/arduino/main.cpp +@@ -36,15 +36,15 @@ int main(void) + + initVariant(); + +-#if defined(USBCON) +- USBDevice.attach(); +-#endif ++// #if defined(USBCON) ++// USBDevice.attach(); ++// #endif + + setup(); + + for (;;) { + loop(); +- if (serialEventRun) serialEventRun(); ++ // if (serialEventRun) serialEventRun(); + } + + return 0; diff --git a/hid/arduino/patches/arduino-optional-cdc.patch b/hid/arduino/patches/arduino-optional-cdc.patch new file mode 100644 index 00000000..08d8c078 --- /dev/null +++ b/hid/arduino/patches/arduino-optional-cdc.patch @@ -0,0 +1,141 @@ +From 8e823d276f939d79b2d323fad675fb8442a718c2 Mon Sep 17 00:00:00 2001 +From: Daniel Gibson <[email protected]> +Date: Tue, 5 Jan 2021 13:48:43 +0100 +Subject: [PATCH] Allow disabling CDC with -DCDC_DISABLED + +https://github.com/arduino/ArduinoCore-avr/pull/383 + +Sometimes Arduino-based USB devices don't work because some hardware +(like KVM switches) gets confused by the CDC sub-devices. +This change makes it relatively easy to disable CDC at compiletime. +Disabling it of course means that the serial console won't work anymore, +so you need to use the reset button when flashing. + +CDC_DISABLED is also used in ArduinoCore-samd for the same purpose. + +based on +https://github.com/gdsports/usb-metamorph/tree/master/USBSerPassThruLine + +See also https://github.com/NicoHood/HID/issues/225 and +https://github.com/arduino/Arduino/issues/6387 and +https://forum.arduino.cc/index.php?topic=545288.msg3717028#msg3717028 +--- + cores/arduino/CDC.cpp | 8 ++++++++ + cores/arduino/USBCore.cpp | 18 +++++++++++++++++- + cores/arduino/USBDesc.h | 17 +++++++++++++++++ + 3 files changed, 42 insertions(+), 1 deletion(-) + +diff --git a/cores/arduino/CDC.cpp b/cores/arduino/CDC.cpp +index 4ff6b9b4..7d5afaab 100644 +--- a/cores/arduino/CDC.cpp ++++ b/cores/arduino/CDC.cpp +@@ -22,6 +22,13 @@ + + #if defined(USBCON) + ++#ifndef CDC_ENABLED ++ ++#warning "! Disabled serial console via USB (CDC)!" ++#warning "! With this change you'll have to use the Arduino's reset button/pin to flash (upload)!" ++ ++#else // CDC not disabled ++ + typedef struct + { + u32 dwDTERate; +@@ -299,4 +306,5 @@ int32_t Serial_::readBreak() { + + Serial_ Serial; + ++#endif /* if defined(CDC_ENABLED) */ + #endif /* if defined(USBCON) */ +diff --git a/cores/arduino/USBCore.cpp b/cores/arduino/USBCore.cpp +index dc6bc387..93352387 100644 +--- a/cores/arduino/USBCore.cpp ++++ b/cores/arduino/USBCore.cpp +@@ -69,8 +69,18 @@ const u8 STRING_MANUFACTURER[] PROGMEM = USB_MANUFACTURER; + #define DEVICE_CLASS 0x02 + + // DEVICE DESCRIPTOR ++ ++#ifdef CDC_ENABLED + const DeviceDescriptor USB_DeviceDescriptorIAD = + D_DEVICE(0xEF,0x02,0x01,64,USB_VID,USB_PID,0x100,IMANUFACTURER,IPRODUCT,ISERIAL,1); ++#else // CDC_DISABLED ++// The default descriptor uses USB class OxEF, subclass 0x02 with protocol 1 ++// which means "Interface Association Descriptor" - that's needed for the CDC, ++// but doesn't make much sense as a default for custom devices when CDC is disabled. ++// (0x00 means "Use class information in the Interface Descriptors" which should be generally ok) ++const DeviceDescriptor USB_DeviceDescriptorIAD = ++ D_DEVICE(0x00,0x00,0x00,64,USB_VID,USB_PID,0x100,IMANUFACTURER,IPRODUCT,ISERIAL,1); ++#endif + + //================================================================== + //================================================================== +@@ -328,10 +338,12 @@ int USB_Send(u8 ep, const void* d, int len) + u8 _initEndpoints[USB_ENDPOINTS] = + { + 0, // Control Endpoint +- ++ ++#ifdef CDC_ENABLED + EP_TYPE_INTERRUPT_IN, // CDC_ENDPOINT_ACM + EP_TYPE_BULK_OUT, // CDC_ENDPOINT_OUT + EP_TYPE_BULK_IN, // CDC_ENDPOINT_IN ++#endif + + // Following endpoints are automatically initialized to 0 + }; +@@ -373,10 +385,12 @@ void InitEndpoints() + static + bool ClassInterfaceRequest(USBSetup& setup) + { ++#ifdef CDC_ENABLED + u8 i = setup.wIndex; + + if (CDC_ACM_INTERFACE == i) + return CDC_Setup(setup); ++#endif + + #ifdef PLUGGABLE_USB_ENABLED + return PluggableUSB().setup(setup); +@@ -466,7 +480,9 @@ static u8 SendInterfaces() + { + u8 interfaces = 0; + ++#ifdef CDC_ENABLED + CDC_GetInterface(&interfaces); ++#endif + + #ifdef PLUGGABLE_USB_ENABLED + PluggableUSB().getInterface(&interfaces); +diff --git a/cores/arduino/USBDesc.h b/cores/arduino/USBDesc.h +index c0dce079..b55ac20b 100644 +--- a/cores/arduino/USBDesc.h ++++ b/cores/arduino/USBDesc.h +@@ -26,8 +26,25 @@ + + #define ISERIAL_MAX_LEN 20 + ++// Uncomment the following line or pass -DCDC_DISABLED to the compiler ++// to disable CDC (serial console via USB). ++// That's useful if you want to create an USB device (like an USB Boot Keyboard) ++// that works even with problematic devices (like KVM switches). ++// Keep in mind that with this change you'll have to use the Arduino's ++// reset button to be able to flash it. ++//#define CDC_DISABLED ++ ++#ifndef CDC_DISABLED ++#define CDC_ENABLED ++#endif ++ ++#ifdef CDC_ENABLED + #define CDC_INTERFACE_COUNT 2 + #define CDC_ENPOINT_COUNT 3 ++#else // CDC_DISABLED ++#define CDC_INTERFACE_COUNT 0 ++#define CDC_ENPOINT_COUNT 0 ++#endif + + #define CDC_ACM_INTERFACE 0 // CDC ACM + #define CDC_DATA_INTERFACE 1 // CDC Data diff --git a/hid/arduino/patches/hid-no-singletones.patch b/hid/arduino/patches/hid-no-singletones.patch new file mode 100644 index 00000000..85298448 --- /dev/null +++ b/hid/arduino/patches/hid-no-singletones.patch @@ -0,0 +1,66 @@ +diff -u -r a/src/SingleReport/BootKeyboard.cpp b/src/SingleReport/BootKeyboard.cpp +--- a/src/SingleReport/BootKeyboard.cpp 2019-07-13 21:16:23.000000000 +0300 ++++ b/src/SingleReport/BootKeyboard.cpp 2020-11-17 18:59:36.618815374 +0300 +@@ -206,6 +206,6 @@ + } + + +-BootKeyboard_ BootKeyboard; ++//BootKeyboard_ BootKeyboard; + + +diff -u -r a/src/SingleReport/BootKeyboard.h b/src/SingleReport/BootKeyboard.h +--- a/src/SingleReport/BootKeyboard.h 2019-07-13 21:16:23.000000000 +0300 ++++ b/src/SingleReport/BootKeyboard.h 2020-11-17 19:00:54.967113649 +0300 +@@ -80,6 +80,6 @@ + uint8_t* featureReport; + int featureLength; + }; +-extern BootKeyboard_ BootKeyboard; ++//extern BootKeyboard_ BootKeyboard; + + +diff -u -r a/src/SingleReport/BootMouse.cpp b/src/SingleReport/BootMouse.cpp +--- a/src/SingleReport/BootMouse.cpp 2019-07-13 21:16:23.000000000 +0300 ++++ b/src/SingleReport/BootMouse.cpp 2020-11-17 18:59:22.859113905 +0300 +@@ -139,6 +139,6 @@ + } + } + +-BootMouse_ BootMouse; ++//BootMouse_ BootMouse; + + +diff -u -r a/src/SingleReport/BootMouse.h b/src/SingleReport/BootMouse.h +--- a/src/SingleReport/BootMouse.h 2019-07-13 21:16:23.000000000 +0300 ++++ b/src/SingleReport/BootMouse.h 2020-11-17 19:01:04.076915591 +0300 +@@ -48,6 +48,6 @@ + + virtual void SendReport(void* data, int length) override; + }; +-extern BootMouse_ BootMouse; ++//extern BootMouse_ BootMouse; + + +diff -u -r a/src/SingleReport/SingleAbsoluteMouse.cpp b/src/SingleReport/SingleAbsoluteMouse.cpp +--- a/src/SingleReport/SingleAbsoluteMouse.cpp 2020-11-17 18:39:35.314843889 +0300 ++++ b/src/SingleReport/SingleAbsoluteMouse.cpp 2020-11-17 18:59:12.189345326 +0300 +@@ -139,6 +139,6 @@ + USB_Send(pluggedEndpoint | TRANSFER_RELEASE, data, length); + } + +-SingleAbsoluteMouse_ SingleAbsoluteMouse; ++//SingleAbsoluteMouse_ SingleAbsoluteMouse; + + +diff -u -r a/src/SingleReport/SingleAbsoluteMouse.h b/src/SingleReport/SingleAbsoluteMouse.h +--- a/src/SingleReport/SingleAbsoluteMouse.h 2019-07-13 21:16:23.000000000 +0300 ++++ b/src/SingleReport/SingleAbsoluteMouse.h 2020-11-17 19:01:21.356539808 +0300 +@@ -49,6 +49,6 @@ + + virtual inline void SendReport(void* data, int length) override; + }; +-extern SingleAbsoluteMouse_ SingleAbsoluteMouse; ++//extern SingleAbsoluteMouse_ SingleAbsoluteMouse; + + diff --git a/hid/arduino/patches/hid-shut-up.patch b/hid/arduino/patches/hid-shut-up.patch new file mode 100644 index 00000000..1de9defb --- /dev/null +++ b/hid/arduino/patches/hid-shut-up.patch @@ -0,0 +1,16 @@ +diff --git a/src/KeyboardLayouts/ImprovedKeylayouts.h b/src/KeyboardLayouts/ImprovedKeylayouts.h +index 03b92a0..ee0bab4 100644 +--- a/src/KeyboardLayouts/ImprovedKeylayouts.h ++++ b/src/KeyboardLayouts/ImprovedKeylayouts.h +@@ -51,9 +51,9 @@ enum KeyboardLeds : uint8_t { + #ifndef HID_CUSTOM_LAYOUT + #define HID_CUSTOM_LAYOUT + #define LAYOUT_US_ENGLISH +- #pragma message "Using default ASCII layout for keyboard modules" ++ //#pragma message "Using default ASCII layout for keyboard modules" + #else +- #pragma message "Using custom layout for keyboard modules" ++ //#pragma message "Using custom layout for keyboard modules" + #endif + + // Hut1_12v2.pdf diff --git a/hid/arduino/patches/hid-win98.patch b/hid/arduino/patches/hid-win98.patch new file mode 100644 index 00000000..29670720 --- /dev/null +++ b/hid/arduino/patches/hid-win98.patch @@ -0,0 +1,82 @@ +From 82c8aee0ae3739eb22ae11b8f527d0c50b0ced31 Mon Sep 17 00:00:00 2001 +From: Maxim Devaev <[email protected]> +Date: Sat, 14 Aug 2021 10:10:06 +0300 +Subject: [PATCH] Fixed absolute mouse positioning in Windows 98 + +Windows 98 contains a buggy HID driver. It allows you to move only +on the upper right quarter of the screen. Yes, this is indeed exceeding +the maximum value from the HID report. Yes, it really should work that way. + +VirtualBox has a similar fix, but with a shift in the other direction +(I didn't dig the details). I suppose it can be fixed somehow with a special +HID descriptor, but the proposed fix is simpler and really works. + +Related: https://github.com/pikvm/pikvm/issues/159 +--- + src/HID-APIs/AbsoluteMouseAPI.h | 3 +++ + src/HID-APIs/AbsoluteMouseAPI.hpp | 17 ++++++++++++++++- + 2 files changed, 19 insertions(+), 1 deletion(-) + +diff --git a/src/HID-APIs/AbsoluteMouseAPI.h b/src/HID-APIs/AbsoluteMouseAPI.h +index 66dbe42..c99e6a5 100644 +--- a/src/HID-APIs/AbsoluteMouseAPI.h ++++ b/src/HID-APIs/AbsoluteMouseAPI.h +@@ -57,6 +57,7 @@ class AbsoluteMouseAPI + int16_t xAxis; + int16_t yAxis; + uint8_t _buttons; ++ bool win98_fix; + inline void buttons(uint8_t b); + + inline int16_t qadd16(int16_t base, int16_t increment); +@@ -65,6 +66,8 @@ class AbsoluteMouseAPI + inline AbsoluteMouseAPI(void); + inline void begin(void); + inline void end(void); ++ inline void setWin98FixEnabled(bool enabled); ++ inline bool isWin98FixEnabled(void); + + inline void click(uint8_t b = MOUSE_LEFT); + inline void moveTo(int x, int y, signed char wheel = 0); +diff --git a/src/HID-APIs/AbsoluteMouseAPI.hpp b/src/HID-APIs/AbsoluteMouseAPI.hpp +index 0913c8a..0b9aaa2 100644 +--- a/src/HID-APIs/AbsoluteMouseAPI.hpp ++++ b/src/HID-APIs/AbsoluteMouseAPI.hpp +@@ -51,7 +51,7 @@ int16_t AbsoluteMouseAPI::qadd16(int16_t base, int16_t increment) { + } + + AbsoluteMouseAPI::AbsoluteMouseAPI(void): +-xAxis(0), yAxis(0), _buttons(0) ++xAxis(0), yAxis(0), _buttons(0), win98_fix(false) + { + // Empty + } +@@ -66,6 +66,14 @@ void AbsoluteMouseAPI::end(void){ + moveTo(xAxis, yAxis, 0); + } + ++void AbsoluteMouseAPI::setWin98FixEnabled(bool enabled){ ++ win98_fix = enabled; ++} ++ ++bool AbsoluteMouseAPI::isWin98FixEnabled(void){ ++ return win98_fix; ++} ++ + void AbsoluteMouseAPI::click(uint8_t b){ + _buttons = b; + moveTo(xAxis, yAxis, 0); +@@ -84,6 +92,13 @@ void AbsoluteMouseAPI::moveTo(int x, int y, signed char wheel){ + // See detauls in AbsoluteMouse sources and here: https://github.com/NicoHood/HID/pull/306 + report.xAxis = ((int32_t)x + 32768) / 2; + report.yAxis = ((int32_t)y + 32768) / 2; ++ if (win98_fix) { ++ // Windows 98 contains a buggy driver. ++ // Yes, this is indeed exceeding the maximum value from the HID report. ++ // Yes, it really should work that way. ++ report.xAxis <<= 1; ++ report.yAxis <<= 1; ++ } + report.wheel = wheel; + SendReport(&report, sizeof(report)); + } diff --git a/hid/arduino/patches/platformio-stm32f1-no-serial-usb.patch b/hid/arduino/patches/platformio-stm32f1-no-serial-usb.patch new file mode 100644 index 00000000..547db168 --- /dev/null +++ b/hid/arduino/patches/platformio-stm32f1-no-serial-usb.patch @@ -0,0 +1,11 @@ +--- aaa/tools/platformio-build-stm32f1.py 2022-07-16 18:54:42.536695468 +0200
++++ bbb/tools/platformio-build-stm32f1.py 2022-07-16 19:03:10.988056751 +0200
+@@ -121,7 +121,7 @@
+ env.Append(
+ CPPDEFINES=[
+ ("CONFIG_MAPLE_MINI_NO_DISABLE_DEBUG", 1),
+- "SERIAL_USB"
++ # "SERIAL_USB"
+ ])
+
+ is_generic = board.startswith("generic") or board == "hytiny_stm32f103t"
diff --git a/hid/arduino/platformio-avr.ini b/hid/arduino/platformio-avr.ini new file mode 100644 index 00000000..8484ef50 --- /dev/null +++ b/hid/arduino/platformio-avr.ini @@ -0,0 +1,119 @@ +# http://docs.platformio.org/page/projectconf.html +[platformio] +core_dir = ./.platformio/ + +[env] +platform = atmelavr +board = micro +framework = arduino +lib_deps = + git+https://github.com/NicoHood/HID#2.8.2 + git+https://github.com/Harvie/ps2dev#v0.0.3 + drivers-avr +extra_scripts = + pre:avrdude.py + post:patch.py +platform_packages = + tool-avrdude + +[_common] +build_flags = + -DHID_USB_CHECK_ENDPOINT +# ----- The default config with dynamic switching ----- + -DHID_DYNAMIC + -DHID_WITH_USB + -DHID_SET_USB_KBD + -DHID_SET_USB_MOUSE_ABS +# ----- The USB ABS fix for Windows 98 (https://github.com/pikvm/pikvm/issues/159) ----- +# -DHID_WITH_USB_WIN98 +# ----- PS2 keyboard only ----- +# -DHID_WITH_PS2 +# -DHID_SET_PS2_KBD +# ----- PS2 keyboard + USB absolute mouse ----- +# -DHID_WITH_USB +# -DHID_WITH_PS2 +# -DHID_SET_PS2_KBD +# -DHID_SET_USB_MOUSE_ABS +# ----- PS2 keyboard + USB relative mouse ----- +# -DHID_WITH_USB +# -DHID_WITH_PS2 +# -DHID_SET_PS2_KBD +# -DHID_SET_USB_MOUSE_REL + +[_non_aum_pinout] = +build_flags = + -DHID_PS2_KBD_CLOCK_PIN=7 + -DHID_PS2_KBD_DATA_PIN=5 + + +# ===== Serial ===== +[env:serial] +extends = + _common + _non_aum_pinout +build_flags = + ${_common.build_flags} + ${_non_aum_pinout.build_flags} + -DCMD_SERIAL=Serial1 + -DCMD_SERIAL_SPEED=115200 + -DCMD_SERIAL_TIMEOUT=100000 +upload_port = /dev/ttyACM0 + + +# ===== RPi SPI ===== +[env:bootloader_spi] +upload_protocol = rpi +upload_flags = + -C + +avrdude-rpi.conf + -P + /dev/spidev0.0:/dev/gpiochip0 +extra_scripts = + pre:avrdude.py + +[_common_spi] +extends = + _common +build_flags = + ${_common.build_flags} + -DCMD_SPI + -DCDC_DISABLED +upload_protocol = custom +upload_flags = + -C + $PROJECT_PACKAGES_DIR/tool-avrdude/avrdude.conf + -C + +avrdude-rpi.conf + -P + /dev/spidev0.0:/dev/gpiochip0 + -c + rpi + -p + $BOARD_MCU +upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i + +[env:spi] +extends = + _common_spi + _non_aum_pinout +build_flags = + ${_common_spi.build_flags} + ${_non_aum_pinout.build_flags} + +[env:aum] +extends = + _common_spi +build_flags = + ${_common_spi.build_flags} + -DAUM + -DAUM_IS_USB_POWERED_PIN=A4 + -DAUM_SET_USB_VBUS_PIN=11 + -DAUM_SET_USB_CONNECTED_PIN=A5 + -DHID_PS2_KBD_VBUS_PIN=8 + -DHID_PS2_KBD_CLOCK_PIN=10 + -DHID_PS2_KBD_DATA_PIN=5 + -DHID_PS2_MOUSE_VBUS_PIN=6 + -DHID_PS2_MOUSE_CLOCK_PIN=9 + -DHID_PS2_MOUSE_DATA_PIN=13 diff --git a/hid/arduino/platformio-stm32.ini b/hid/arduino/platformio-stm32.ini new file mode 100644 index 00000000..94fd44d4 --- /dev/null +++ b/hid/arduino/platformio-stm32.ini @@ -0,0 +1,52 @@ +# http://docs.platformio.org/page/projectconf.html +[platformio] +core_dir = ./.platformio/ + +[env] +framework = arduino +platform = ststm32 +board = genericSTM32F103C8 +board_build.core = maple +extra_scripts = + post:patch.py + +[_common] +lib_deps = + git+https://github.com/ZulNs/STM32F1_RTC#v1.1.0 + git+https://github.com/arpruss/USBComposite_stm32f1#3c58f97eb006ee9cd1fb4fd55ac4faeeaead0974 + drivers-stm32 +build_flags = +# ----- The default config with dynamic switching ----- + -DHID_DYNAMIC + -DHID_WITH_USB + -DHID_SET_USB_KBD + -DHID_SET_USB_MOUSE_ABS + +[_serial] +extends = + _common +build_flags = + ${_common.build_flags} + -DCMD_SERIAL=Serial1 + -DCMD_SERIAL_SPEED=115200 + -DCMD_SERIAL_TIMEOUT=100000 +# ===== Serial ===== +[env:serial] +extends = + _serial +upload_flags = -c set CPUTAPID 0x2ba01477 +debug_tool= stlink +debug_build_flags = -Og -ggdb3 -g3 +debug_server = + .platformio/packages/tool-openocd/bin/openocd + -s .platformio/packages/tool-openocd/scripts + -f interface/stlink.cfg + -c "transport select hla_swd" + -c "set CPUTAPID 0x2ba01477" + -f target/stm32f1x.cfg + -c "reset_config none" +; build_type = debug +[env:patch] +; platformio-stm32f1-no-serial-usb.patch requires to running build again +; fake target was added to avoid error during first build +src_filter = -<src/> diff --git a/hid/arduino/src/main.cpp b/hid/arduino/src/main.cpp new file mode 100644 index 00000000..c1539106 --- /dev/null +++ b/hid/arduino/src/main.cpp @@ -0,0 +1,253 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#include <Arduino.h> + +#include "tools.h" +#include "proto.h" +#include "board.h" +#include "outputs.h" +#ifdef AUM +# include "aum.h" +#endif + + +static DRIVERS::Connection *_conn; +static DRIVERS::Board *_board; +static Outputs _out; + +#ifdef HID_DYNAMIC +# define RESET_TIMEOUT 500000 +static bool _reset_required = false; +static unsigned long _reset_timestamp; +#endif + + +// ----------------------------------------------------------------------------- +#ifdef HID_DYNAMIC +static void _resetRequest() { + _reset_required = true; + _reset_timestamp = micros(); +} +#endif + +static void _cmdSetKeyboard(const uint8_t *data) { // 1 bytes +# ifdef HID_DYNAMIC + _out.writeOutputs(PROTO::OUTPUTS1::KEYBOARD::MASK, data[0], false); + _resetRequest(); +# endif +} + +static void _cmdSetMouse(const uint8_t *data) { // 1 bytes +# ifdef HID_DYNAMIC + _out.writeOutputs(PROTO::OUTPUTS1::MOUSE::MASK, data[0], false); + _resetRequest(); +# endif +} + +static void _cmdSetConnected(const uint8_t *data) { // 1 byte +# ifdef AUM + aumSetUsbConnected(data[0]); +# endif +} + +static void _cmdClearHid(const uint8_t *_) { // 0 bytes + _out.kbd->clear(); + _out.mouse->clear(); +} + +static void _cmdKeyEvent(const uint8_t *data) { // 2 bytes + _out.kbd->sendKey(data[0], data[1]); +} + +static void _cmdMouseButtonEvent(const uint8_t *data) { // 2 bytes +# define MOUSE_PAIR(_state, _button) \ + _state & PROTO::CMD::MOUSE::_button::SELECT, \ + _state & PROTO::CMD::MOUSE::_button::STATE + _out.mouse->sendButtons( + MOUSE_PAIR(data[0], LEFT), + MOUSE_PAIR(data[0], RIGHT), + MOUSE_PAIR(data[0], MIDDLE), + MOUSE_PAIR(data[1], EXTRA_UP), + MOUSE_PAIR(data[1], EXTRA_DOWN) + ); +# undef MOUSE_PAIR +} + +static void _cmdMouseMoveEvent(const uint8_t *data) { // 4 bytes + // See /kvmd/apps/otg/hid/keyboard.py for details + _out.mouse->sendMove( + PROTO::merge8_int(data[0], data[1]), + PROTO::merge8_int(data[2], data[3]) + ); +} + +static void _cmdMouseRelativeEvent(const uint8_t *data) { // 2 bytes + _out.mouse->sendRelative(data[0], data[1]); +} + +static void _cmdMouseWheelEvent(const uint8_t *data) { // 2 bytes + // Y only, X is not supported + _out.mouse->sendWheel(data[1]); +} + +static uint8_t _handleRequest(const uint8_t *data) { // 8 bytes + _board->updateStatus(DRIVERS::RX_DATA); + // FIXME: See kvmd/kvmd#80 + // Should input buffer be cleared in this case? + if (data[0] == PROTO::MAGIC && PROTO::crc16(data, 6) == PROTO::merge8(data[6], data[7])) { +# define HANDLE(_handler) { _handler(data + 2); return PROTO::PONG::OK; } + switch (data[1]) { + case PROTO::CMD::PING: return PROTO::PONG::OK; + case PROTO::CMD::SET_KEYBOARD: HANDLE(_cmdSetKeyboard); + case PROTO::CMD::SET_MOUSE: HANDLE(_cmdSetMouse); + case PROTO::CMD::SET_CONNECTED: HANDLE(_cmdSetConnected); + case PROTO::CMD::CLEAR_HID: HANDLE(_cmdClearHid); + case PROTO::CMD::KEYBOARD::KEY: HANDLE(_cmdKeyEvent); + case PROTO::CMD::MOUSE::BUTTON: HANDLE(_cmdMouseButtonEvent); + case PROTO::CMD::MOUSE::MOVE: HANDLE(_cmdMouseMoveEvent); + case PROTO::CMD::MOUSE::RELATIVE: HANDLE(_cmdMouseRelativeEvent); + case PROTO::CMD::MOUSE::WHEEL: HANDLE(_cmdMouseWheelEvent); + case PROTO::CMD::REPEAT: return 0; + default: return PROTO::RESP::INVALID_ERROR; + } +# undef HANDLE + } + return PROTO::RESP::CRC_ERROR; +} + + +// ----------------------------------------------------------------------------- +static void _sendResponse(uint8_t code) { + static uint8_t prev_code = PROTO::RESP::NONE; + if (code == 0) { + code = prev_code; // Repeat the last code + } else { + prev_code = code; + } + + uint8_t response[8] = {0}; + response[0] = PROTO::MAGIC_RESP; + if (code & PROTO::PONG::OK) { + response[1] = PROTO::PONG::OK; +# ifdef HID_DYNAMIC + if (_reset_required) { + response[1] |= PROTO::PONG::RESET_REQUIRED; + if (is_micros_timed_out(_reset_timestamp, RESET_TIMEOUT)) { + _board->reset(); + } + } + response[2] = PROTO::OUTPUTS1::DYNAMIC; +# endif + if (_out.kbd->getType() != DRIVERS::DUMMY) { + if(_out.kbd->isOffline()) { + response[1] |= PROTO::PONG::KEYBOARD_OFFLINE; + } else { + _board->updateStatus(DRIVERS::KEYBOARD_ONLINE); + } + DRIVERS::KeyboardLedsState leds = _out.kbd->getLeds(); + response[1] |= (leds.caps ? PROTO::PONG::CAPS : 0); + response[1] |= (leds.num ? PROTO::PONG::NUM : 0); + response[1] |= (leds.scroll ? PROTO::PONG::SCROLL : 0); + switch (_out.kbd->getType()) { + case DRIVERS::USB_KEYBOARD: + response[2] |= PROTO::OUTPUTS1::KEYBOARD::USB; + break; + case DRIVERS::PS2_KEYBOARD: + response[2] |= PROTO::OUTPUTS1::KEYBOARD::PS2; + break; + } + } + if (_out.mouse->getType() != DRIVERS::DUMMY) { + if(_out.mouse->isOffline()) { + response[1] |= PROTO::PONG::MOUSE_OFFLINE; + } else { + _board->updateStatus(DRIVERS::MOUSE_ONLINE); + } + switch (_out.mouse->getType()) { + case DRIVERS::USB_MOUSE_ABSOLUTE_WIN98: + response[2] |= PROTO::OUTPUTS1::MOUSE::USB_WIN98; + break; + case DRIVERS::USB_MOUSE_ABSOLUTE: + response[2] |= PROTO::OUTPUTS1::MOUSE::USB_ABS; + break; + case DRIVERS::USB_MOUSE_RELATIVE: + response[2] |= PROTO::OUTPUTS1::MOUSE::USB_REL; + break; + } + } // TODO: ps2 +# ifdef AUM + response[3] |= PROTO::OUTPUTS2::CONNECTABLE; + if (aumIsUsbConnected()) { + response[3] |= PROTO::OUTPUTS2::CONNECTED; + } +# endif +# ifdef HID_WITH_USB + response[3] |= PROTO::OUTPUTS2::HAS_USB; +# ifdef HID_WITH_USB_WIN98 + response[3] |= PROTO::OUTPUTS2::HAS_USB_WIN98; +# endif +# endif +# ifdef HID_WITH_PS2 + response[3] |= PROTO::OUTPUTS2::HAS_PS2; +# endif + } else { + response[1] = code; + } + PROTO::split16(PROTO::crc16(response, 6), &response[6], &response[7]); + + _conn->write(response, 8); +} + +static void _onTimeout() { + _sendResponse(PROTO::RESP::TIMEOUT_ERROR); +} + +static void _onData(const uint8_t *data, size_t size) { + _sendResponse(_handleRequest(data)); +} + +void setup() { + _out.initOutputs(); + +# ifdef AUM + aumInit(); +# endif + + _conn = DRIVERS::Factory::makeConnection(DRIVERS::CONNECTION); + _conn->onTimeout(_onTimeout); + _conn->onData(_onData); + _conn->begin(); + + _board = DRIVERS::Factory::makeBoard(DRIVERS::BOARD); +} + +void loop() { +# ifdef AUM + aumProxyUsbVbus(); +# endif + + _out.kbd->periodic(); + _out.mouse->periodic(); + _board->periodic(); + _conn->periodic(); +} diff --git a/hid/arduino/src/outputs.h b/hid/arduino/src/outputs.h new file mode 100644 index 00000000..dd9008f3 --- /dev/null +++ b/hid/arduino/src/outputs.h @@ -0,0 +1,122 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#include "factory.h" +#include "proto.h" + + +class Outputs { + public: + void writeOutputs(uint8_t mask, uint8_t outputs, bool force) { + int old = 0; + if (!force) { + old = _readOutputs(); + if (old < 0) { + old = 0; + } + } + uint8_t data[8] = {0}; + data[0] = PROTO::MAGIC; + data[1] = (old & ~mask) | outputs; + PROTO::split16(PROTO::crc16(data, 6), &data[6], &data[7]); + _storage->updateBlock(data, 0, 8); + } + + void initOutputs() { +# ifdef HID_DYNAMIC + _storage = DRIVERS::Factory::makeStorage(DRIVERS::NON_VOLATILE_STORAGE); +# else + _storage = DRIVERS::Factory::makeStorage(DRIVERS::DUMMY); +# endif + + int outputs = _readOutputs(); + if (outputs < 0) { + outputs = 0; +# if defined(HID_WITH_USB) && defined(HID_SET_USB_KBD) + outputs |= PROTO::OUTPUTS1::KEYBOARD::USB; +# elif defined(HID_WITH_PS2) && defined(HID_SET_PS2_KBD) + outputs |= PROTO::OUTPUTS1::KEYBOARD::PS2; +# endif +# if defined(HID_WITH_USB) && defined(HID_SET_USB_MOUSE_ABS) + outputs |= PROTO::OUTPUTS1::MOUSE::USB_ABS; +# elif defined(HID_WITH_USB) && defined(HID_SET_USB_MOUSE_REL) + outputs |= PROTO::OUTPUTS1::MOUSE::USB_REL; +# elif defined(HID_WITH_PS2) && defined(HID_SET_PS2_MOUSE) + outputs |= PROTO::OUTPUTS1::MOUSE::PS2; +# elif defined(HID_WITH_USB) && defined(HID_WITH_USB_WIN98) && defined(HID_SET_USB_MOUSE_WIN98) + outputs |= PROTO::OUTPUTS1::MOUSE::USB_WIN98; +# endif + writeOutputs(0xFF, outputs, true); + } + + uint8_t kbd_type = outputs & PROTO::OUTPUTS1::KEYBOARD::MASK; + switch (kbd_type) { + case PROTO::OUTPUTS1::KEYBOARD::USB: + kbd = DRIVERS::Factory::makeKeyboard(DRIVERS::USB_KEYBOARD); + break; + case PROTO::OUTPUTS1::KEYBOARD::PS2: + kbd = DRIVERS::Factory::makeKeyboard(DRIVERS::PS2_KEYBOARD); + break; + default: + kbd = DRIVERS::Factory::makeKeyboard(DRIVERS::DUMMY); + break; + } + + uint8_t mouse_type = outputs & PROTO::OUTPUTS1::MOUSE::MASK; + switch (mouse_type) { + case PROTO::OUTPUTS1::MOUSE::USB_ABS: + mouse = DRIVERS::Factory::makeMouse(DRIVERS::USB_MOUSE_ABSOLUTE); + break; + case PROTO::OUTPUTS1::MOUSE::USB_WIN98: + mouse = DRIVERS::Factory::makeMouse(DRIVERS::USB_MOUSE_ABSOLUTE_WIN98); + break; + case PROTO::OUTPUTS1::MOUSE::USB_REL: + mouse = DRIVERS::Factory::makeMouse(DRIVERS::USB_MOUSE_RELATIVE); + break; + default: + mouse = DRIVERS::Factory::makeMouse(DRIVERS::DUMMY); + break; + } + +# ifdef ARDUINO_ARCH_AVR + USBDevice.attach(); +# endif + + kbd->begin(); + mouse->begin(); + } + + DRIVERS::Keyboard *kbd = nullptr; + DRIVERS::Mouse *mouse = nullptr; + + private: + int _readOutputs(void) { + uint8_t data[8]; + _storage->readBlock(data, 0, 8); + if (data[0] != PROTO::MAGIC || PROTO::crc16(data, 6) != PROTO::merge8(data[6], data[7])) { + return -1; + } + return data[1]; + } + + DRIVERS::Storage *_storage = nullptr; +}; diff --git a/hid/arduino/src/proto.h b/hid/arduino/src/proto.h new file mode 100644 index 00000000..5fbdd49c --- /dev/null +++ b/hid/arduino/src/proto.h @@ -0,0 +1,142 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2023 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +#pragma once + + +namespace PROTO { + const uint8_t MAGIC = 0x33; + const uint8_t MAGIC_RESP = 0x34; + + namespace RESP { // Plain responses + // const uint8_t OK = 0x20; // Legacy + const uint8_t NONE = 0x24; + const uint8_t CRC_ERROR = 0x40; + const uint8_t INVALID_ERROR = 0x45; + const uint8_t TIMEOUT_ERROR = 0x48; + }; + + namespace PONG { // Complex response + const uint8_t OK = 0x80; + const uint8_t CAPS = 0b00000001; + const uint8_t SCROLL = 0b00000010; + const uint8_t NUM = 0b00000100; + const uint8_t KEYBOARD_OFFLINE = 0b00001000; + const uint8_t MOUSE_OFFLINE = 0b00010000; + const uint8_t RESET_REQUIRED = 0b01000000; + }; + + namespace OUTPUTS1 { // Complex request/responce flags + const uint8_t DYNAMIC = 0b10000000; + namespace KEYBOARD { + const uint8_t MASK = 0b00000111; + const uint8_t USB = 0b00000001; + const uint8_t PS2 = 0b00000011; + }; + namespace MOUSE { + const uint8_t MASK = 0b00111000; + const uint8_t USB_ABS = 0b00001000; + const uint8_t USB_REL = 0b00010000; + const uint8_t PS2 = 0b00011000; + const uint8_t USB_WIN98 = 0b00100000; + }; + }; + + namespace OUTPUTS2 { // Complex response + const uint8_t CONNECTABLE = 0b10000000; + const uint8_t CONNECTED = 0b01000000; + const uint8_t HAS_USB = 0b00000001; + const uint8_t HAS_PS2 = 0b00000010; + const uint8_t HAS_USB_WIN98 = 0b00000100; + } + + namespace CMD { + const uint8_t PING = 0x01; + const uint8_t REPEAT = 0x02; + const uint8_t SET_KEYBOARD = 0x03; + const uint8_t SET_MOUSE = 0x04; + const uint8_t SET_CONNECTED = 0x05; + const uint8_t CLEAR_HID = 0x10; + + namespace KEYBOARD { + const uint8_t KEY = 0x11; + }; + + namespace MOUSE { + const uint8_t MOVE = 0x12; + const uint8_t BUTTON = 0x13; + const uint8_t WHEEL = 0x14; + const uint8_t RELATIVE = 0x15; + namespace LEFT { + const uint8_t SELECT = 0b10000000; + const uint8_t STATE = 0b00001000; + }; + namespace RIGHT { + const uint8_t SELECT = 0b01000000; + const uint8_t STATE = 0b00000100; + }; + namespace MIDDLE { + const uint8_t SELECT = 0b00100000; + const uint8_t STATE = 0b00000010; + }; + namespace EXTRA_UP { + const uint8_t SELECT = 0b10000000; + const uint8_t STATE = 0b00001000; + }; + namespace EXTRA_DOWN { + const uint8_t SELECT = 0b01000000; + const uint8_t STATE = 0b00000100; + }; + }; + }; + + uint16_t crc16(const uint8_t *buffer, unsigned length) { + const uint16_t polinom = 0xA001; + uint16_t crc = 0xFFFF; + + for (unsigned byte_count = 0; byte_count < length; ++byte_count) { + crc = crc ^ buffer[byte_count]; + for (unsigned bit_count = 0; bit_count < 8; ++bit_count) { + if ((crc & 0x0001) == 0) { + crc = crc >> 1; + } else { + crc = crc >> 1; + crc = crc ^ polinom; + } + } + } + return crc; + } + + inline int merge8_int(uint8_t from_a, uint8_t from_b) { + return (((int)from_a << 8) | (int)from_b); + } + + inline uint16_t merge8(uint8_t from_a, uint8_t from_b) { + return (((uint16_t)from_a << 8) | (uint16_t)from_b); + } + + inline void split16(uint16_t from, uint8_t *to_a, uint8_t *to_b) { + *to_a = (uint8_t)(from >> 8); + *to_b = (uint8_t)(from & 0xFF); + } +}; |