diff options
Diffstat (limited to 'hid/arduino/src')
-rw-r--r-- | hid/arduino/src/main.cpp | 253 | ||||
-rw-r--r-- | hid/arduino/src/outputs.h | 122 | ||||
-rw-r--r-- | hid/arduino/src/proto.h | 142 |
3 files changed, 517 insertions, 0 deletions
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); + } +}; |