diff options
author | Maxim Devaev <[email protected]> | 2023-08-03 05:47:27 +0300 |
---|---|---|
committer | Maxim Devaev <[email protected]> | 2023-08-03 05:47:27 +0300 |
commit | c96057772c8d845c90ad7429e51689eeffb63d71 (patch) | |
tree | c376d9715169b9248aa8b7ef70b8a6506297f982 | |
parent | 1a8f98a64f9480c1062225e0fc994ceac4ba346d (diff) |
rp2040 hid
29 files changed, 1933 insertions, 11 deletions
diff --git a/.dockerignore b/.dockerignore index e0435ada..ae2e9675 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,8 +8,14 @@ /testenv/.tox/ /testenv/.mypy_cache/ /testenv/.ssl/ -/hid/.pio/ -/hid/.platformio/ +/hid/arduino/.pio/ +/hid/arduino/.platformio/ +/hid/pico/.pico-sdk.tmp/ +/hid/pico/.pico-sdk/ +/hid/pico/.tinyusb.tmp/ +/hid/pico/.tinyusb/ +/hid/pico/.build/ +/hid/pico/*.uf2 /.git/ /v*.tar.gz /*.pkg.tar.xz @@ -213,6 +213,7 @@ keymap: testenv && ./genmap.py keymap.csv kvmd/keyboard/mappings.py.mako kvmd/keyboard/mappings.py \ && ./genmap.py keymap.csv hid/arduino/lib/drivers/usb-keymap.h.mako hid/arduino/lib/drivers/usb-keymap.h \ && ./genmap.py keymap.csv hid/arduino/lib/drivers-avr/ps2/keymap.h.mako hid/arduino/lib/drivers-avr/ps2/keymap.h \ + && ./genmap.py keymap.csv hid/pico/src/ph_usb_keymap.h.mako hid/pico/src/ph_usb_keymap.h \ " @@ -251,10 +252,12 @@ clean: rm -rf testenv/run/*.{pid,sock} build site dist pkg src v*.tar.gz *.pkg.tar.{xz,zst} *.egg-info kvmd-*.tar.gz find kvmd testenv/tests -name __pycache__ | xargs rm -rf make -C hid/arduino clean + make -C hid/pico clean clean-all: testenv clean make -C hid/arduino clean-all + make -C hid/pico clean-all - $(DOCKER) run --rm \ --volume `pwd`:/src \ -it $(TESTENV_IMAGE) bash -c "cd src && rm -rf testenv/{.ssl,.tox,.mypy_cache,.coverage}" diff --git a/hid/pico/.gitignore b/hid/pico/.gitignore new file mode 100644 index 00000000..c4c20b04 --- /dev/null +++ b/hid/pico/.gitignore @@ -0,0 +1,6 @@ +/.pico-sdk.tmp/ +/.pico-sdk/ +/.tinyusb.tmp/ +/.tinyusb/ +/.build/ +/*.uf2 diff --git a/hid/pico/CMakeLists.txt b/hid/pico/CMakeLists.txt new file mode 100644 index 00000000..bd771016 --- /dev/null +++ b/hid/pico/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.13) + +set(PICO_SDK_PATH ${CMAKE_CURRENT_LIST_DIR}/.pico-sdk) +set(PICO_TINYUSB_PATH ${CMAKE_CURRENT_LIST_DIR}/.tinyusb) + +# For TinyUSB +set(FAMILY rp2040) + +# Include pico_sdk_import.cmake from pico-sdk (instead of copying) +include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake) + +# Generic setup +set(PROJECT hid) +project(${PROJECT}) + +# Initialize Pico-SDK +pico_sdk_init() + +# Set the path to the source code to build +set(SRC_TO_BUILD_PATH ${CMAKE_CURRENT_LIST_DIR}/src) +add_subdirectory(${SRC_TO_BUILD_PATH}) diff --git a/hid/pico/Makefile b/hid/pico/Makefile new file mode 100644 index 00000000..409f8067 --- /dev/null +++ b/hid/pico/Makefile @@ -0,0 +1,36 @@ +all: .pico-sdk .tinyusb + rm -f hid.uf2 + cmake -B .build + cmake --build .build --config Release -- -j + ln .build/src/hid.uf2 . + + +upload: install +install: all + sudo mount /dev/sda1 /mnt + sudo cp hid.uf2 /mnt + sudo umount /mnt + + +clean: + rm -rf .build hid.uf2 +clean-all: clean + rm -rf .pico-sdk.tmp .pico-sdk .tinyusb.tmp .tinyusb + + +.pico-sdk: + rm -rf .pico-sdk.tmp + git clone https://github.com/raspberrypi/pico-sdk .pico-sdk.tmp + cd .pico-sdk.tmp \ + && git checkout 62201a83e2693ea165fdc7669b4ab2f3b4f43c36 \ + && git submodule update --init + mv .pico-sdk.tmp .pico-sdk + + +.tinyusb: + rm -rf .tinyusb.tmp + git clone https://github.com/hathach/tinyusb .tinyusb.tmp + cd .tinyusb.tmp \ + && git checkout c998e9c60bc76894006c3bd049d661124a9bfbfd \ + && git submodule update --init + mv .tinyusb.tmp .tinyusb diff --git a/hid/pico/src/CMakeLists.txt b/hid/pico/src/CMakeLists.txt new file mode 100644 index 00000000..e8b7583e --- /dev/null +++ b/hid/pico/src/CMakeLists.txt @@ -0,0 +1,25 @@ +set(target_name hid) +add_executable(${target_name}) + +target_sources(${target_name} PRIVATE + main.c + ph_outputs.c + ph_usb.c + ph_usb_kbd.c + ph_usb_mouse.c + ph_cmds.c + ph_spi.c + ph_debug.c +) +target_link_options(${target_name} PRIVATE -Xlinker --print-memory-usage) +target_compile_options(${target_name} PRIVATE -Wall -Wextra) +target_include_directories(${target_name} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${target_name} PRIVATE + pico_stdlib + pico_unique_id + hardware_spi + hardware_watchdog + tinyusb_device +) +pico_add_extra_outputs(${target_name}) diff --git a/hid/pico/src/main.c b/hid/pico/src/main.c new file mode 100644 index 00000000..69508689 --- /dev/null +++ b/hid/pico/src/main.c @@ -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 "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/watchdog.h" + +#include "ph_types.h" +#include "ph_tools.h" +#include "ph_outputs.h" +#include "ph_usb.h" +#include "ph_spi.h" +#include "ph_proto.h" +#include "ph_cmds.h" +#include "ph_debug.h" + + +static bool _reset_required = false; + + +static u8 _handle_request(const u8 *data) { // 8 bytes + // FIXME: See kvmd/kvmd#80 + // Should input buffer be cleared in this case? + if (data[0] == PH_PROTO_MAGIC && ph_crc16(data, 6) == ph_merge8_u16(data[6], data[7])) { +# define HANDLE(x_handler, x_reset) { \ + x_handler(data + 2); \ + if (x_reset) { _reset_required = true; } \ + return PH_PROTO_PONG_OK; \ + } + switch (data[1]) { + case PH_PROTO_CMD_PING: return PH_PROTO_PONG_OK; + case PH_PROTO_CMD_SET_KBD: HANDLE(ph_cmd_set_kbd, true); + case PH_PROTO_CMD_SET_MOUSE: HANDLE(ph_cmd_set_mouse, true); + case PH_PROTO_CMD_SET_CONNECTED: return PH_PROTO_PONG_OK; // Arduino AUM + case PH_PROTO_CMD_CLEAR_HID: HANDLE(ph_cmd_send_clear, false); + case PH_PROTO_CMD_KBD_KEY: HANDLE(ph_cmd_kbd_send_key, false); + case PH_PROTO_CMD_MOUSE_BUTTON: HANDLE(ph_cmd_mouse_send_button, false); + case PH_PROTO_CMD_MOUSE_ABS: HANDLE(ph_cmd_mouse_send_abs, false); + case PH_PROTO_CMD_MOUSE_REL: HANDLE(ph_cmd_mouse_send_rel, false); + case PH_PROTO_CMD_MOUSE_WHEEL: HANDLE(ph_cmd_mouse_send_wheel, false); + case PH_PROTO_CMD_REPEAT: return 0; + } +# undef HANDLE + return PH_PROTO_RESP_INVALID_ERROR; + } + return PH_PROTO_RESP_CRC_ERROR; +} + +static void _send_response(u8 code) { + static u8 prev_code = PH_PROTO_RESP_NONE; + if (code == 0) { + code = prev_code; // Repeat the last code + } else { + prev_code = code; + } + + u8 resp[8] = {0}; + resp[0] = PH_PROTO_MAGIC_RESP; + + if (code & PH_PROTO_PONG_OK) { + resp[1] = PH_PROTO_PONG_OK; + if (_reset_required) { + resp[1] |= PH_PROTO_PONG_RESET_REQUIRED; + } + resp[2] = PH_PROTO_OUT1_DYNAMIC; + + resp[1] |= ph_cmd_get_offlines(); + resp[1] |= ph_cmd_kbd_get_leds(); + resp[2] |= ph_g_outputs_active; + resp[3] |= ph_g_outputs_avail; + } else { + resp[1] = code; + } + + ph_split16(ph_crc16(resp, 6), &resp[6], &resp[7]); + + ph_spi_write(resp); + + if (_reset_required) { + watchdog_reboot(0, 0, 100); // Даем немного времени чтобы отправить ответ, а потом ребутимся + } +} + +static void _spi_data_handler(const u8 *data) { + _send_response(_handle_request(data)); +} + + +int main(void) { + ph_debug_init(false); // No UART + ph_outputs_init(); + ph_usb_init(); + ph_spi_init(_spi_data_handler); + + while (true) { + ph_usb_task(); + if (!_reset_required) { + ph_spi_task(); + ph_debug_act_pulse(50); + } + } + return 0; +} diff --git a/hid/pico/src/ph_cmds.c b/hid/pico/src/ph_cmds.c new file mode 100644 index 00000000..11359638 --- /dev/null +++ b/hid/pico/src/ph_cmds.c @@ -0,0 +1,105 @@ +/* ========================================================================= # +# # +# 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 "tusb.h" + +#include "ph_types.h" +#include "ph_tools.h" +#include "ph_proto.h" +#include "ph_outputs.h" +#include "ph_usb.h" +#include "ph_usb_keymap.h" + + +u8 ph_cmd_kbd_get_leds(void) { + u8 retval = 0; + if (PH_O_IS_KBD_USB) { +# define GET(x_mod) ((ph_g_usb_kbd_leds & KEYBOARD_LED_##x_mod##LOCK) ? PH_PROTO_PONG_##x_mod : 0) + retval = GET(CAPS) | GET(SCROLL) | GET(NUM); +# undef GET + } + return retval; +} + +u8 ph_cmd_get_offlines(void) { + u8 retval = 0; + if (PH_O_IS_KBD_USB) { + if (!ph_g_usb_kbd_online) { + retval |= PH_PROTO_PONG_KBD_OFFLINE; + } + } + if (PH_O_IS_MOUSE_USB) { + if (!ph_g_usb_mouse_online) { + retval |= PH_PROTO_PONG_MOUSE_OFFLINE; + } + } + return retval; +} + +void ph_cmd_set_kbd(const u8 *args) { // 1 byte + ph_outputs_write(PH_PROTO_OUT1_KBD_MASK, args[0], false); +} + +void ph_cmd_set_mouse(const u8 *args) { // 1 byte + ph_outputs_write(PH_PROTO_OUT1_MOUSE_MASK, args[0], false); +} + +void ph_cmd_send_clear(const u8 *args) { // 0 bytes + (void)args; + ph_usb_send_clear(); +} + +void ph_cmd_kbd_send_key(const u8 *args) { // 2 bytes + const u8 key = ph_usb_keymap(args[0]); + if (key > 0) { + ph_usb_kbd_send_key(key, args[1]); + } +} + +void ph_cmd_mouse_send_button(const u8 *args) { // 2 bytes +# define HANDLE(x_byte_n, x_button) { \ + if (args[x_byte_n] & PH_PROTO_CMD_MOUSE_##x_button##_SELECT) { \ + const bool m_state = !!(args[x_byte_n] & PH_PROTO_CMD_MOUSE_##x_button##_STATE); \ + ph_usb_mouse_send_button(MOUSE_BUTTON_##x_button, m_state); \ + } \ + } + HANDLE(0, LEFT); + HANDLE(0, RIGHT); + HANDLE(0, MIDDLE); + HANDLE(1, BACKWARD); + HANDLE(1, FORWARD); +# undef HANDLE +} + +void ph_cmd_mouse_send_abs(const u8 *args) { // 4 bytes + ph_usb_mouse_send_abs( + ph_merge8_s16(args[0], args[1]), + ph_merge8_s16(args[2], args[3])); +} + +void ph_cmd_mouse_send_rel(const u8 *args) { // 2 bytes + ph_usb_mouse_send_rel(args[0], args[1]); +} + +void ph_cmd_mouse_send_wheel(const u8 *args) { // 2 bytes + ph_usb_mouse_send_wheel(args[0], args[1]); +} diff --git a/hid/pico/src/ph_cmds.h b/hid/pico/src/ph_cmds.h new file mode 100644 index 00000000..549b65e3 --- /dev/null +++ b/hid/pico/src/ph_cmds.h @@ -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/>. # +# # +# ========================================================================= */ + + +#include "ph_types.h" + + +u8 ph_cmd_kbd_get_leds(void); +u8 ph_cmd_get_offlines(void); + +void ph_cmd_set_kbd(const u8 *args); +void ph_cmd_set_mouse(const u8 *args); + +void ph_cmd_send_clear(const u8 *args); +void ph_cmd_kbd_send_key(const u8 *args); +void ph_cmd_mouse_send_button(const u8 *args); +void ph_cmd_mouse_send_abs(const u8 *args); +void ph_cmd_mouse_send_rel(const u8 *args); +void ph_cmd_mouse_send_wheel(const u8 *args); diff --git a/hid/pico/src/ph_debug.c b/hid/pico/src/ph_debug.c new file mode 100644 index 00000000..5063c0d8 --- /dev/null +++ b/hid/pico/src/ph_debug.c @@ -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/>. # +# # +*****************************************************************************/ + + +#include "pico/stdlib.h" +#include "hardware/gpio.h" + +#include "ph_types.h" + + +#define _UART uart0 +#define _SPEED 3000000 +#define _RX_PIN -1 // 1 - No stdin +#define _TX_PIN 0 +#define _ACT_PIN 25 + + +void ph_debug_init(bool enable_uart) { + if (enable_uart) { + stdio_uart_init_full(_UART, _SPEED, _TX_PIN, _RX_PIN); + } + gpio_init(_ACT_PIN); + gpio_set_dir(_ACT_PIN, GPIO_OUT); +} + +void ph_debug_act_pulse(u64 delay_ms) { + static bool flag = false; + static u64 next_ts = 0; + const u64 now_ts = time_us_64(); + if (now_ts >= next_ts) { + gpio_put(_ACT_PIN, flag); + flag = !flag; + next_ts = now_ts + (delay_ms * 1000); + } +} diff --git a/hid/pico/src/ph_debug.h b/hid/pico/src/ph_debug.h new file mode 100644 index 00000000..ef8afdda --- /dev/null +++ b/hid/pico/src/ph_debug.h @@ -0,0 +1,27 @@ +/***************************************************************************** +# # +# 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 "ph_types.h" + + +void ph_debug_init(bool enable_uart); +void ph_debug_act_pulse(u64 delay_ms); diff --git a/hid/pico/src/ph_outputs.c b/hid/pico/src/ph_outputs.c new file mode 100644 index 00000000..40d10983 --- /dev/null +++ b/hid/pico/src/ph_outputs.c @@ -0,0 +1,137 @@ +/* ========================================================================= # +# # +# 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 "ph_outputs.h" + +#include "hardware/gpio.h" +#include "hardware/structs/watchdog.h" + +#include "ph_types.h" +#include "ph_tools.h" +#include "ph_proto.h" + + +#define _PS2_ENABLED_PIN 2 +#define _PS2_SET_KBD_PIN 3 +#define _PS2_SET_MOUSE_PIN 4 + +#define _USB_DISABLED_PIN 6 +#define _USB_ENABLE_W98_PIN 7 +#define _USB_SET_MOUSE_REL_PIN 8 +#define _USB_SET_MOUSE_W98_PIN 9 + + +u8 ph_g_outputs_active = 0; +u8 ph_g_outputs_avail = 0; + + +static int _read_outputs(void); + + +void ph_outputs_init(void) { +# define INIT_SWITCH(x_pin) { gpio_init(x_pin); gpio_set_dir(x_pin, GPIO_IN); gpio_pull_down(x_pin); } + INIT_SWITCH(_PS2_ENABLED_PIN); + INIT_SWITCH(_PS2_SET_KBD_PIN); + INIT_SWITCH(_PS2_SET_MOUSE_PIN); + + INIT_SWITCH(_USB_DISABLED_PIN); + INIT_SWITCH(_USB_ENABLE_W98_PIN); + INIT_SWITCH(_USB_SET_MOUSE_REL_PIN); + INIT_SWITCH(_USB_SET_MOUSE_W98_PIN); +# undef INIT_SWITCH + + const bool o_ps2_enabled = gpio_get(_PS2_ENABLED_PIN); + const bool o_ps2_kbd = gpio_get(_PS2_SET_KBD_PIN); + const bool o_ps2_mouse = gpio_get(_PS2_SET_MOUSE_PIN); + + const bool o_usb_disabled = gpio_get(_USB_DISABLED_PIN); + const bool o_usb_enabled_w98 = gpio_get(_USB_ENABLE_W98_PIN); + const bool o_usb_mouse_rel = gpio_get(_USB_SET_MOUSE_REL_PIN); + const bool o_usb_mouse_w98 = gpio_get(_USB_SET_MOUSE_W98_PIN); + + int outputs = _read_outputs(); + if (outputs < 0) { + outputs = 0; + + if (o_ps2_enabled && o_ps2_kbd) { + outputs |= PH_PROTO_OUT1_KBD_PS2; + } else if (!o_usb_disabled) { + outputs |= PH_PROTO_OUT1_KBD_USB; + } + + if (o_ps2_enabled && o_ps2_mouse) { + outputs |= PH_PROTO_OUT1_MOUSE_PS2; + } else if (!o_usb_disabled) { + if (o_usb_enabled_w98 && o_usb_mouse_w98) { + outputs |= PH_PROTO_OUT1_MOUSE_USB_W98; + } else if (o_usb_mouse_rel) { + outputs |= PH_PROTO_OUT1_MOUSE_USB_REL; + } else { + outputs |= PH_PROTO_OUT1_MOUSE_USB_ABS; + } + } + + ph_outputs_write(0xFF, outputs, true); + } + + if (!o_usb_disabled) { + ph_g_outputs_avail |= PH_PROTO_OUT2_HAS_USB; + if (o_usb_enabled_w98) { + ph_g_outputs_avail |= PH_PROTO_OUT2_HAS_USB_W98; + } + } + if (o_ps2_enabled) { + ph_g_outputs_avail |= PH_PROTO_OUT2_HAS_PS2; + } + + ph_g_outputs_active = outputs & 0xFF; +} + +void ph_outputs_write(u8 mask, u8 outputs, bool force) { + int old = 0; + if (!force) { + old = _read_outputs(); + if (old < 0) { + old = 0; + } + } + u8 data[4] = {0}; + data[0] = PH_PROTO_MAGIC; + data[1] = (old & ~mask) | outputs; + ph_split16(ph_crc16(data, 2), &data[2], &data[3]); + const u32 s0 = ((u32)data[0] << 24) | ((u32)data[1] << 16) | ((u32)data[2] << 8) | (u32)data[3]; + watchdog_hw->scratch[0] = s0; +} + +static int _read_outputs(void) { + const u32 s0 = watchdog_hw->scratch[0]; + const u8 data[4] = { + (s0 >> 24) & 0xFF, + (s0 >> 16) & 0xFF, + (s0 >> 8) & 0xFF, + s0 & 0xFF, + }; + if (data[0] != PH_PROTO_MAGIC || ph_crc16(data, 2) != ph_merge8_u16(data[2], data[3])) { + return -1; + } + return data[1]; +} diff --git a/hid/pico/src/ph_outputs.h b/hid/pico/src/ph_outputs.h new file mode 100644 index 00000000..1f8e3f40 --- /dev/null +++ b/hid/pico/src/ph_outputs.h @@ -0,0 +1,42 @@ +/* ========================================================================= # +# # +# 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 "ph_types.h" +#include "ph_proto.h" + + +#define PH_O_KBD(x_id) ((ph_g_outputs_active & PH_PROTO_OUT1_KBD_MASK) == PH_PROTO_OUT1_KBD_##x_id) +#define PH_O_MOUSE(x_id) ((ph_g_outputs_active & PH_PROTO_OUT1_MOUSE_MASK) == PH_PROTO_OUT1_MOUSE_##x_id) +#define PH_O_IS_KBD_USB PH_O_KBD(USB) +#define PH_O_IS_MOUSE_USB (PH_O_MOUSE(USB_ABS) || PH_O_MOUSE(USB_REL) || PH_O_MOUSE(USB_W98)) +#define PH_O_IS_MOUSE_USB_ABS (PH_O_MOUSE(USB_ABS) || PH_O_MOUSE(USB_W98)) +#define PH_O_IS_MOUSE_USB_REL PH_O_MOUSE(USB_REL) + + +extern u8 ph_g_outputs_active; +extern u8 ph_g_outputs_avail; + + +void ph_outputs_init(void); +void ph_outputs_write(u8 mask, u8 outputs, bool force); diff --git a/hid/pico/src/ph_proto.h b/hid/pico/src/ph_proto.h new file mode 100644 index 00000000..11989170 --- /dev/null +++ b/hid/pico/src/ph_proto.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 "ph_types.h" + + +#define PH_PROTO_MAGIC ((u8)0x33) +#define PH_PROTO_MAGIC_RESP ((u8)0x34) + +//#define PH_PROTO_RESP_OK ((u8)0x20) // Legacy +#define PH_PROTO_RESP_NONE ((u8)0x24) +#define PH_PROTO_RESP_CRC_ERROR ((u8)0x40) +#define PH_PROTO_RESP_INVALID_ERROR ((u8)0x45) +#define PH_PROTO_RESP_TIMEOUT_ERROR ((u8)0x48) + +// Complex response flags +#define PH_PROTO_PONG_OK ((u8)0b10000000) +#define PH_PROTO_PONG_CAPS ((u8)0b00000001) +#define PH_PROTO_PONG_SCROLL ((u8)0b00000010) +#define PH_PROTO_PONG_NUM ((u8)0b00000100) +#define PH_PROTO_PONG_KBD_OFFLINE ((u8)0b00001000) +#define PH_PROTO_PONG_MOUSE_OFFLINE ((u8)0b00010000) +#define PH_PROTO_PONG_RESET_REQUIRED ((u8)0b01000000) + +// Complex request/response flags +#define PH_PROTO_OUT1_DYNAMIC ((u8)0b10000000) +#define PH_PROTO_OUT1_KBD_MASK ((u8)0b00000111) +#define PH_PROTO_OUT1_KBD_USB ((u8)0b00000001) +#define PH_PROTO_OUT1_KBD_PS2 ((u8)0b00000011) +// + +#define PH_PROTO_OUT1_MOUSE_MASK ((u8)0b00111000) +#define PH_PROTO_OUT1_MOUSE_USB_ABS ((u8)0b00001000) +#define PH_PROTO_OUT1_MOUSE_USB_REL ((u8)0b00010000) +#define PH_PROTO_OUT1_MOUSE_PS2 ((u8)0b00011000) +#define PH_PROTO_OUT1_MOUSE_USB_W98 ((u8)0b00100000) + +// Complex response +#define PH_PROTO_OUT2_CONNECTABLE ((u8)0b10000000) +#define PH_PROTO_OUT2_CONNECTED ((u8)0b01000000) +#define PH_PROTO_OUT2_HAS_USB ((u8)0b00000001) +#define PH_PROTO_OUT2_HAS_PS2 ((u8)0b00000010) +#define PH_PROTO_OUT2_HAS_USB_W98 ((u8)0b00000100) + +#define PH_PROTO_CMD_PING ((u8)0x01) +#define PH_PROTO_CMD_REPEAT ((u8)0x02) +#define PH_PROTO_CMD_SET_KBD ((u8)0x03) +#define PH_PROTO_CMD_SET_MOUSE ((u8)0x04) +#define PH_PROTO_CMD_SET_CONNECTED ((u8)0x05) +#define PH_PROTO_CMD_CLEAR_HID ((u8)0x10) +// + +#define PH_PROTO_CMD_KBD_KEY ((u8)0x11) +// + +#define PH_PROTO_CMD_MOUSE_ABS ((u8)0x12) +#define PH_PROTO_CMD_MOUSE_BUTTON ((u8)0x13) +#define PH_PROTO_CMD_MOUSE_WHEEL ((u8)0x14) +#define PH_PROTO_CMD_MOUSE_REL ((u8)0x15) +// + +#define PH_PROTO_CMD_MOUSE_LEFT_SELECT ((u8)0b10000000) +#define PH_PROTO_CMD_MOUSE_LEFT_STATE ((u8)0b00001000) +// + +#define PH_PROTO_CMD_MOUSE_RIGHT_SELECT ((u8)0b01000000) +#define PH_PROTO_CMD_MOUSE_RIGHT_STATE ((u8)0b00000100) +// + +#define PH_PROTO_CMD_MOUSE_MIDDLE_SELECT ((u8)0b00100000) +#define PH_PROTO_CMD_MOUSE_MIDDLE_STATE ((u8)0b00000010) +// + +#define PH_PROTO_CMD_MOUSE_BACKWARD_SELECT ((u8)0b10000000) // Previous/Up +#define PH_PROTO_CMD_MOUSE_BACKWARD_STATE ((u8)0b00001000) // Previous/Up +// + +#define PH_PROTO_CMD_MOUSE_FORWARD_SELECT ((u8)0b01000000) // Next/Down +#define PH_PROTO_CMD_MOUSE_FORWARD_STATE ((u8)0b00000100) // Next/Down diff --git a/hid/pico/src/ph_spi.c b/hid/pico/src/ph_spi.c new file mode 100644 index 00000000..ef74bd7e --- /dev/null +++ b/hid/pico/src/ph_spi.c @@ -0,0 +1,120 @@ +/***************************************************************************** +# # +# 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 "ph_spi.h" + +#include "hardware/gpio.h" +#include "hardware/irq.h" +#include "hardware/spi.h" +#include "hardware/regs/spi.h" + +#include "ph_types.h" + + +#define _BUS spi0 +#define _IRQ SPI0_IRQ +#define _FREQ (2 * 1000 * 1000) +#define _CS_PIN 21 +#define _RX_PIN 20 +#define _TX_PIN 19 +#define _CLK_PIN 18 + + +static volatile u8 _in_buf[8] = {0}; +static volatile u8 _in_index = 0; + +static volatile u8 _out_buf[8] = {0}; +static volatile u8 _out_index = 0; + +static void (*_data_cb)(const u8 *) = NULL; + + +static void _xfer_isr(void); + + +void ph_spi_init(void (*data_cb)(const u8 *)) { + _data_cb = data_cb; + + spi_init(_BUS, _FREQ); + spi_set_slave(_BUS, true); + spi_set_format(_BUS, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + + gpio_set_function(_CS_PIN, GPIO_FUNC_SPI); + gpio_set_function(_RX_PIN, GPIO_FUNC_SPI); + gpio_set_function(_TX_PIN, GPIO_FUNC_SPI); + gpio_set_function(_CLK_PIN, GPIO_FUNC_SPI); + + // https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2040/hardware_regs/include/hardware/regs/spi.h + irq_set_exclusive_handler(_IRQ, _xfer_isr); + spi_get_hw(_BUS)->imsc = SPI_SSPIMSC_RXIM_BITS | SPI_SSPIMSC_TXIM_BITS; + irq_set_enabled(_IRQ, true); +} + +void ph_spi_task(void) { + if (!_out_buf[0] && _in_index == 8) { + _data_cb((const u8 *)_in_buf); + } +} + +void ph_spi_write(const u8 *data) { + // Меджик в нулевом байте разрешает начать ответ + for (s8 i = 7; i >= 0; --i) { + _out_buf[i] = data[i]; + } +} + +void __isr __not_in_flash_func(_xfer_isr)(void) { +# define SR (spi_get_hw(_BUS)->sr) +# define DR (spi_get_hw(_BUS)->dr) + + while (SR & SPI_SSPSR_TNF_BITS) { + if (_out_buf[0] && _out_index < 8) { + DR = (u32)_out_buf[_out_index]; + ++_out_index; + if (_out_index == 8) { + _out_index = 0; + _in_index = 0; + _out_buf[0] = 0; + } + } else { + DR = (u32)0; + } + } + + while (SR & SPI_SSPSR_RNE_BITS) { + static bool receiving = false; + const u8 in = DR; + if (!receiving && in != 0) { + receiving = true; + } + if (receiving && _in_index < 8) { + _in_buf[_in_index] = in; + ++_in_index; + } + if (_in_index == 8) { + receiving = false; + } + } + +# undef DR +# undef SR +} diff --git a/hid/pico/src/ph_spi.h b/hid/pico/src/ph_spi.h new file mode 100644 index 00000000..ba613bb3 --- /dev/null +++ b/hid/pico/src/ph_spi.h @@ -0,0 +1,30 @@ +/***************************************************************************** +# # +# 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 "ph_types.h" + + +void ph_spi_init(void (*data_cb)(const u8 *)); +void ph_spi_task(void); +void ph_spi_write(const u8 *data); diff --git a/hid/pico/src/ph_tools.h b/hid/pico/src/ph_tools.h new file mode 100644 index 00000000..469be810 --- /dev/null +++ b/hid/pico/src/ph_tools.h @@ -0,0 +1,57 @@ +/***************************************************************************** +# # +# 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 "ph_types.h" + + +inline u16 ph_crc16(const u8 *buf, uz len) { + const u16 polinom = 0xA001; + u16 crc = 0xFFFF; + + for (uz byte_count = 0; byte_count < len; ++byte_count) { + crc = crc ^ buf[byte_count]; + for (uz 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 s16 ph_merge8_s16(u8 a, u8 b) { + return (((int)a << 8) | (int)b); +} + +inline u16 ph_merge8_u16(u8 a, u8 b) { + return (((u16)a << 8) | (u16)b); +} + +inline void ph_split16(u16 from, u8 *to_a, u8 *to_b) { + *to_a = (u8)(from >> 8); + *to_b = (u8)(from & 0xFF); +} diff --git a/hid/pico/src/ph_types.h b/hid/pico/src/ph_types.h new file mode 100644 index 00000000..79ab6c15 --- /dev/null +++ b/hid/pico/src/ph_types.h @@ -0,0 +1,38 @@ +/* ========================================================================= # +# # +# 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 <stdio.h> +#include <stdbool.h> +#include <stdint.h> + + +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; + +typedef size_t uz; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; diff --git a/hid/pico/src/ph_usb.c b/hid/pico/src/ph_usb.c new file mode 100644 index 00000000..b9b3b672 --- /dev/null +++ b/hid/pico/src/ph_usb.c @@ -0,0 +1,394 @@ +/* ========================================================================= # +# # +# 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 "ph_usb.h" + +#include <stdlib.h> +#include <string.h> + +#include "pico/stdlib.h" +#include "pico/unique_id.h" + +#include "tusb.h" +#if TUD_OPT_HIGH_SPEED +# error "High-Speed is not supported" +#endif + +#include "ph_types.h" +#include "ph_outputs.h" +#include "ph_usb_kbd.h" +#include "ph_usb_mouse.h" + + +u8 ph_g_usb_kbd_leds = 0; +bool ph_g_usb_kbd_online = true; +bool ph_g_usb_mouse_online = true; + +static int _kbd_iface = -1; +static int _mouse_iface = -1; + +static u8 _kbd_mods = 0; +static u8 _kbd_keys[6] = {0}; +#define _KBD_CLEAR { _kbd_mods = 0; memset(_kbd_keys, 0, 6); } + +static u8 _mouse_buttons = 0; +static s16 _mouse_abs_x = 0; +static s16 _mouse_abs_y = 0; +#define _MOUSE_CLEAR { _mouse_buttons = 0; _mouse_abs_x = 0; _mouse_abs_y = 0; } + + +static void _kbd_sync_report(bool new); +static void _mouse_abs_send_report(s8 h, s8 v); +static void _mouse_rel_send_report(s8 x, s8 y, s8 h, s8 v); + + +void ph_usb_init(void) { + if (PH_O_IS_KBD_USB || PH_O_IS_MOUSE_USB) { + tud_init(0); + } +} + +void ph_usb_task(void) { + if (PH_O_IS_KBD_USB || PH_O_IS_MOUSE_USB) { + tud_task(); + + static u64 next_ts = 0; + const u64 now_ts = time_us_64(); + if (next_ts == 0 || now_ts >= next_ts) { +# define CHECK_IFACE(x_dev) \ + static u64 offline_ts = 0; \ + static bool prev_online = true; \ + const bool online = (tud_ready() && tud_hid_n_ready(_##x_dev##_iface)); \ + bool force = false; \ + if (online) { \ + if (!ph_g_usb_##x_dev##_online) { \ + force = true; /* Если был переход из долгого оффлайна в онлайн */ \ + } \ + ph_g_usb_##x_dev##_online = true; \ + offline_ts = 0; \ + } else if (prev_online && !online) { \ + offline_ts = now_ts; /* Начинаем отсчет для долгого оффлайна */ \ + } else if (!prev_online && !online && offline_ts + 50000 < now_ts) { \ + ph_g_usb_##x_dev##_online = false; /* Долгий оффлайн найден */ \ + } \ + prev_online = online; + + if (_kbd_iface >= 0) { + CHECK_IFACE(kbd); + _kbd_sync_report(force); + } + + if (_mouse_iface >= 0) { + CHECK_IFACE(mouse); + (void)force; + } + +# undef CHECK_IFACE + next_ts = time_us_64() + 1000; // Every 1 ms + } + } +} + +void ph_usb_kbd_send_key(u8 key, bool state) { + if (_kbd_iface < 0) { + return; // Допускаем планирование нажатия, пока устройство не готово + } + + if (key >= HID_KEY_CONTROL_LEFT && key <= HID_KEY_GUI_RIGHT) { // 0xE0...0xE7 - Modifiers + key = 1 << (key & 0x07); // Номер означает сдвиг + if (state) { + _kbd_mods |= key; + } else { + _kbd_mods &= ~key; + } + + } else { // Regular keys + if (state) { + s8 pos = -1; + for (u8 i = 0; i < 6; ++i) { + if (_kbd_keys[i] == key) { + goto already_pressed; + } else if (_kbd_keys[i] == 0) { + pos = i; + } + } + _kbd_keys[pos >= 0 ? pos : 0] = key; + already_pressed: + } else { + for (u8 i = 0; i < 6; ++i) { + if (_kbd_keys[i] == key) { + _kbd_keys[i] = 0; + break; + } + } + } + } + + _kbd_sync_report(true); +} + +void ph_usb_mouse_send_button(u8 button, bool state) { + if (!PH_O_IS_MOUSE_USB) { + return; + } + if (state) { + _mouse_buttons |= button; + } else { + _mouse_buttons &= ~button; + } + if (PH_O_IS_MOUSE_USB_ABS) { + _mouse_abs_send_report(0, 0); + } else { // PH_O_IS_MOUSE_USB_REL + _mouse_rel_send_report(0, 0, 0, 0); + } +} + +void ph_usb_mouse_send_abs(s16 x, s16 y) { + if (PH_O_IS_MOUSE_USB_ABS) { + _mouse_abs_x = x; + _mouse_abs_y = y; + _mouse_abs_send_report(0, 0); + } +} + +void ph_usb_mouse_send_rel(s8 x, s8 y) { + if (PH_O_IS_MOUSE_USB_REL) { + _mouse_rel_send_report(x, y, 0, 0); + } +} + +void ph_usb_mouse_send_wheel(s8 h, s8 v) { + if (PH_O_IS_MOUSE_USB_ABS) { + _mouse_abs_send_report(h, v); + } else { // PH_O_IS_MOUSE_USB_REL + _mouse_rel_send_report(0, 0, h, v); + } +} + +void ph_usb_send_clear(void) { + if (PH_O_IS_KBD_USB) { + _KBD_CLEAR; + _kbd_sync_report(true); + } + if (PH_O_IS_MOUSE_USB) { + _MOUSE_CLEAR; + if (PH_O_IS_MOUSE_USB_ABS) { + _mouse_abs_send_report(0, 0); + } else { // PH_O_IS_MOUSE_USB_REL + _mouse_rel_send_report(0, 0, 0, 0); + } + } +} + +//-------------------------------------------------------------------- +// RAW report senders +//-------------------------------------------------------------------- + +static void _kbd_sync_report(bool new) { + static bool sent = true; + if (_kbd_iface < 0 || !PH_O_IS_KBD_USB) { + _KBD_CLEAR; + sent = true; + return; + } + if (new) { + sent = false; + } + if (!sent) { + if (tud_suspended()) { + tud_remote_wakeup(); + //_KBD_CLEAR; + //sent = true; + } else { + sent = tud_hid_n_keyboard_report(_kbd_iface, 0, _kbd_mods, _kbd_keys); + } + } +} + +#define _CHECK_MOUSE(x_mode) { \ + if (_mouse_iface < 0 || !PH_O_IS_MOUSE_USB_##x_mode) { _MOUSE_CLEAR; return; } \ + if (tud_suspended()) { tud_remote_wakeup(); _MOUSE_CLEAR; return; } \ + } + + +static void _mouse_abs_send_report(s8 h, s8 v) { + (void)h; // Horizontal scrolling is not supported due BIOS/UEFI compatibility reasons + _CHECK_MOUSE(ABS); + u16 x = ((s32)_mouse_abs_x + 32768) / 2; + u16 y = ((s32)_mouse_abs_y + 32768) / 2; + if (PH_O_MOUSE(USB_W98)) { + x <<= 1; + y <<= 1; + } + struct TU_ATTR_PACKED { + u8 buttons; + u16 x; + u16 y; + s8 v; + } report = {_mouse_buttons, x, y, v}; + tud_hid_n_report(_mouse_iface, 0, &report, sizeof(report)); +} + +static void _mouse_rel_send_report(s8 x, s8 y, s8 h, s8 v) { + (void)h; // Horizontal scrolling is not supported due BIOS/UEFI compatibility reasons + _CHECK_MOUSE(REL); + struct TU_ATTR_PACKED { + u8 buttons; + s8 x; + s8 y; + s8 v; + } report = {_mouse_buttons, x, y, v}; + tud_hid_n_report(_mouse_iface, 0, &report, sizeof(report)); +} + +#undef _CHECK_MOUSE + + +//-------------------------------------------------------------------- +// Device callbacks +//-------------------------------------------------------------------- + +u16 tud_hid_get_report_cb(u8 iface, u8 report_id, hid_report_type_t report_type, u8 *buf, u16 len) { + // Invoked when received GET_REPORT control request, return 0 == STALL + (void)iface; + (void)report_id; + (void)report_type; + (void)buf; + (void)len; + return 0; +} + +void tud_hid_set_report_cb(u8 iface, u8 report_id, hid_report_type_t report_type, const u8 *buf, u16 len) { + // Invoked when received SET_REPORT control request + // or received data on OUT endpoint (ReportID=0, Type=0) + (void)report_id; + if (iface == _kbd_iface && report_type == HID_REPORT_TYPE_OUTPUT && len >= 1) { + ph_g_usb_kbd_leds = buf[0]; + } +} + +const u8 *tud_hid_descriptor_report_cb(u8 iface) { + if ((int)iface == _mouse_iface) { + if (PH_O_IS_MOUSE_USB_ABS) { + return PH_USB_MOUSE_ABS_DESC; + } else { // PH_O_IS_MOUSE_USB_REL + return PH_USB_MOUSE_REL_DESC; + } + } + return PH_USB_KBD_DESC; // _kbd_iface, PH_O_IS_KBD_USB +} + +const u8 *tud_descriptor_configuration_cb(u8 index) { + // Invoked when received GET CONFIGURATION DESCRIPTOR + (void)index; + + static u8 desc[TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN * 2] = {0}; + static bool filled = false; + + if (!filled) { + uz offset = TUD_CONFIG_DESC_LEN; + u8 iface = 0; + u8 ep = 0x81; + +# define APPEND_DESC(x_proto, x_desc, x_iface_to) { \ + const u8 part[] = {TUD_HID_DESCRIPTOR( \ + (x_iface_to = iface), /* Interface number */ \ + 0, x_proto, x_desc##_LEN, /* String index, protocol, report descriptor len */ \ + ep, CFG_TUD_HID_EP_BUFSIZE, 1)}; /* EP In address, size, polling interval */ \ + memcpy(desc + offset, part, TUD_HID_DESC_LEN); \ + offset += TUD_HID_DESC_LEN; ++iface; ++ep; \ + } + + if (PH_O_IS_KBD_USB) { + APPEND_DESC(HID_ITF_PROTOCOL_KEYBOARD, PH_USB_KBD_DESC, _kbd_iface); + } + if (PH_O_IS_MOUSE_USB_ABS) { + APPEND_DESC(HID_ITF_PROTOCOL_NONE, PH_USB_MOUSE_ABS_DESC, _mouse_iface); + } else if (PH_O_IS_MOUSE_USB_REL) { + APPEND_DESC(HID_ITF_PROTOCOL_MOUSE, PH_USB_MOUSE_REL_DESC, _mouse_iface); + } + +# undef APPEND_DESC + + // Config number, interface count, string index, total length, attribute, power in mA + const u8 part[] = {TUD_CONFIG_DESCRIPTOR(1, iface, 0, offset, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100)}; + memcpy(desc, part, TUD_CONFIG_DESC_LEN); + filled = true; + } + return desc; +} + +const u8 *tud_descriptor_device_cb(void) { + // Invoked when received GET DEVICE DESCRIPTOR + + static const tusb_desc_device_t desc = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0x1209, // https://pid.codes/org/Pi-KVM + .idProduct = 0xEDA2, + .bcdDevice = 0x0100, + + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + + .bNumConfigurations = 1, + }; + return (const u8 *)&desc; +} + +const u16 *tud_descriptor_string_cb(u8 index, u16 lang_id) { + // Invoked when received GET STRING DESCRIPTOR request. + (void)lang_id; + + static u16 desc_str[32]; + uz desc_str_len; + + if (index == 0) { + desc_str[1] = 0x0409; // Supported language is English (0x0409) + desc_str_len = 1; + } else { + char str[32]; + switch (index) { + case 1: strcpy(str, "PiKVM"); break; // Manufacturer + case 2: strcpy(str, "PiKVM HID"); break; // Product + case 3: pico_get_unique_board_id_string(str, 32); break; // Serial + default: return NULL; + } + desc_str_len = strlen(str); + for (uz i = 0; i < desc_str_len; ++i) { + desc_str[i + 1] = str[i]; // Convert ASCII string into UTF-16 + } + } + + // First byte is length (including header), second byte is string type + desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * desc_str_len + 2); + return desc_str; +} diff --git a/hid/pico/src/ph_usb.h b/hid/pico/src/ph_usb.h new file mode 100644 index 00000000..5a4146e3 --- /dev/null +++ b/hid/pico/src/ph_usb.h @@ -0,0 +1,43 @@ +/* ========================================================================= # +# # +# 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 "ph_types.h" + + +extern u8 ph_g_usb_kbd_leds; +extern bool ph_g_usb_kbd_online; +extern bool ph_g_usb_mouse_online; + + +void ph_usb_init(void); +void ph_usb_task(void); + +void ph_usb_kbd_send_key(u8 key, bool state); + +void ph_usb_mouse_send_button(u8 button, bool state); +void ph_usb_mouse_send_abs(s16 x, s16 y); +void ph_usb_mouse_send_rel(s8 x, s8 y); +void ph_usb_mouse_send_wheel(s8 h, s8 v); + +void ph_usb_send_clear(void); diff --git a/hid/pico/src/ph_usb_kbd.c b/hid/pico/src/ph_usb_kbd.c new file mode 100644 index 00000000..95835b0f --- /dev/null +++ b/hid/pico/src/ph_usb_kbd.c @@ -0,0 +1,78 @@ +/* ========================================================================= # +# # +# 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 "ph_usb_kbd.h" + +#include "ph_types.h" + + +const u8 PH_USB_KBD_DESC[] = { + // Logitech descriptor. It's very similar to https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt + // Dumped using usbhid-dump; parsed using https://eleccelerator.com/usbdescreqparser + + // Keyboard + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x06, // USAGE (Keyboard) + 0xA1, 0x01, // COLLECTION (Application) + + // Modifiers + 0x05, 0x07, // USAGE_PAGE (Keyboard) + 0x19, 0xE0, // USAGE_MINIMUM (Keyboard LeftControl) + 0x29, 0xE7, // USAGE_MAXIMUM (Keyboard Right GUI) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x75, 0x01, // REPORT_SIZE (1) + 0x95, 0x08, // REPORT_COUNT (8) + 0x81, 0x02, // INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + + // Reserved byte + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x08, // REPORT_SIZE (8) + 0x81, 0x01, // INPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + + // LEDs output + 0x95, 0x05, // REPORT_COUNT (5) + 0x75, 0x01, // REPORT_SIZE (1) + 0x05, 0x08, // USAGE_PAGE (LEDs) + 0x19, 0x01, // USAGE_MINIMUM (Num Lock) + 0x29, 0x05, // USAGE_MAXIMUM (Kana) + 0x91, 0x02, // OUTPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + + // Reserved 3 bits in output + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x03, // REPORT_SIZE (3) + 0x91, 0x01, // OUTPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + + // 6 keys + 0x95, 0x06, // REPORT_COUNT (6) + 0x75, 0x08, // REPORT_SIZE (8) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x26, 0xFF, 0x00, // LOGICAL_MAXIMUM (0xFF) + 0x05, 0x07, // USAGE_PAGE (Keyboard) + 0x19, 0x00, // USAGE_MINIMUM (Reserved) + 0x2A, 0xFF, 0x00, // USAGE_MAXIMUM (0xFF) + 0x81, 0x00, // INPUT (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + + 0xC0, // END_COLLECTION +}; + +const uz PH_USB_KBD_DESC_LEN = sizeof(PH_USB_KBD_DESC); diff --git a/hid/pico/src/ph_usb_kbd.h b/hid/pico/src/ph_usb_kbd.h new file mode 100644 index 00000000..a6658201 --- /dev/null +++ b/hid/pico/src/ph_usb_kbd.h @@ -0,0 +1,29 @@ +/* ========================================================================= # +# # +# 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 "ph_types.h" + + +extern const u8 PH_USB_KBD_DESC[]; +extern const uz PH_USB_KBD_DESC_LEN; diff --git a/hid/pico/src/ph_usb_keymap.h b/hid/pico/src/ph_usb_keymap.h new file mode 100644 index 00000000..e33bbd08 --- /dev/null +++ b/hid/pico/src/ph_usb_keymap.h @@ -0,0 +1,143 @@ +/***************************************************************************** +# # +# 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 "ph_types.h" + + +u8 ph_usb_keymap(u8 key) { + switch (key) { + 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 + } + return 0; +} diff --git a/hid/pico/src/ph_usb_keymap.h.mako b/hid/pico/src/ph_usb_keymap.h.mako new file mode 100644 index 00000000..b90e8970 --- /dev/null +++ b/hid/pico/src/ph_usb_keymap.h.mako @@ -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 "ph_types.h" + +<%! import operator %> +u8 ph_usb_keymap(u8 key) { + switch (key) { +% 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 + } + return 0; +} diff --git a/hid/pico/src/ph_usb_mouse.c b/hid/pico/src/ph_usb_mouse.c new file mode 100644 index 00000000..5004228c --- /dev/null +++ b/hid/pico/src/ph_usb_mouse.c @@ -0,0 +1,120 @@ +/* ========================================================================= # +# # +# 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 "ph_usb_mouse.h" + +#include "ph_types.h" + + +const u8 PH_USB_MOUSE_ABS_DESC[] = { + // https://github.com/NicoHood/HID/blob/0835e6a/src/SingleReport/SingleAbsoluteMouse.cpp + // Репорт взят отсюда ^^^, но изменен диапазон значений координат перемещений. + // Автор предлагает использовать -32768...32767, но семерка почему-то не хочет работать + // с отрицательными значениями координат, как не хочет хавать 65536 и 32768. + // Так что мы ей скармливаем диапазон 0...32767, и передаем рукожопам из микрософта привет, + // потому что линуксы прекрасно работают с любыми двухбайтовыми диапазонами. + + // Absolute mouse + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x02, // USAGE (Mouse) + 0xA1, 0x01, // COLLECTION (Application) + + // Pointer and Physical are required by Apple Recovery + 0x09, 0x01, // USAGE (Pointer) + 0xA1, 0x00, // COLLECTION (Physical) + + // 8 Buttons + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x08, // USAGE_MAXIMUM (Button 8) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x08, // REPORT_COUNT (8) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + // X, Y + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x16, 0x00, 0x00, // LOGICAL_MINIMUM (0) + 0x26, 0xFF, 0x7F, // LOGICAL_MAXIMUM (32767) + 0x75, 0x10, // REPORT_SIZE (16) + 0x95, 0x02, // REPORT_COUNT (2) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + // Wheel + 0x09, 0x38, // USAGE (Wheel) + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7F, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x01, // REPORT_COUNT (1) + 0x81, 0x06, // INPUT (Data,Var,Rel) + + // End + 0xC0, // END_COLLECTION (Physical) + 0xC0, // END_COLLECTION +}; + +const uz PH_USB_MOUSE_ABS_DESC_LEN = sizeof(PH_USB_MOUSE_ABS_DESC); + +const u8 PH_USB_MOUSE_REL_DESC[] = { + // https://github.com/NicoHood/HID/blob/0835e6a/src/SingleReport/BootMouse.cpp + + // Relative mouse + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x02, // USAGE (Mouse) + 0xA1, 0x01, // COLLECTION (Application) + + // Pointer and Physical are required by Apple Recovery + 0x09, 0x01, // USAGE (Pointer) + 0xA1, 0x00, // COLLECTION (Physical) + + // 8 Buttons + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x08, // USAGE_MAXIMUM (Button 8) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x08, // REPORT_COUNT (8) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + // X, Y + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + + // Wheel + 0x09, 0x38, // USAGE (Wheel) + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7F, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x03, // REPORT_COUNT (3) + 0x81, 0x06, // INPUT (Data,Var,Rel) + + // End + 0xC0, // END_COLLECTION (Physical) + 0xC0, // END_COLLECTION +}; + +const uz PH_USB_MOUSE_REL_DESC_LEN = sizeof(PH_USB_MOUSE_REL_DESC); diff --git a/hid/pico/src/ph_usb_mouse.h b/hid/pico/src/ph_usb_mouse.h new file mode 100644 index 00000000..6b6c6c73 --- /dev/null +++ b/hid/pico/src/ph_usb_mouse.h @@ -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/>. # +# # +# ========================================================================= */ + + +#pragma once + +#include "ph_types.h" + + +extern const u8 PH_USB_MOUSE_ABS_DESC[]; +extern const uz PH_USB_MOUSE_ABS_DESC_LEN; + +extern const u8 PH_USB_MOUSE_REL_DESC[]; +extern const uz PH_USB_MOUSE_REL_DESC_LEN; diff --git a/hid/pico/src/tusb_config.h b/hid/pico/src/tusb_config.h new file mode 100644 index 00000000..c29b23ad --- /dev/null +++ b/hid/pico/src/tusb_config.h @@ -0,0 +1,67 @@ +/* ========================================================================= # +# # +# 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 + + +//-------------------------------------------------------------------- +// Common config +//-------------------------------------------------------------------- + +//#define CFG_TUSB_DEBUG 100 + +#define CFG_TUSB_OS OPT_OS_PICO + +// Enable device stack +#define CFG_TUD_ENABLED 1 + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +//#define CFG_TUSB_DEBUG 100 + +// USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. +// Tinyusb use follows macros to declare transferring memory so that they can be put +// into those specific section. +// - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) +// - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) +#ifndef CFG_TUSB_MEM_SECTION +# define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +# define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) +#endif + + +//-------------------------------------------------------------------- +// Device config +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +# define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +#define CFG_TUD_HID 2 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#ifndef CFG_TUD_HID_EP_BUFSIZE +# define CFG_TUD_HID_EP_BUFSIZE 16 +#endif diff --git a/kvmd/plugins/hid/_mcu/__init__.py b/kvmd/plugins/hid/_mcu/__init__.py index 26632553..646e689b 100644 --- a/kvmd/plugins/hid/_mcu/__init__.py +++ b/kvmd/plugins/hid/_mcu/__init__.py @@ -108,6 +108,7 @@ class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many- reset_pin: int, reset_inverted: bool, reset_delay: float, + reset_self: bool, read_retries: int, common_retries: int, @@ -126,6 +127,7 @@ class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many- self.__phy = phy self.__gpio = Gpio(gpio_device_path, reset_pin, reset_inverted, reset_delay) + self.__reset_self = reset_self self.__reset_required_event = multiprocessing.Event() self.__events_queue: "multiprocessing.Queue[BaseEvent]" = multiprocessing.Queue() @@ -146,6 +148,7 @@ class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many- "reset_pin": Option(4, type=valid_gpio_pin_optional), "reset_inverted": Option(False, type=valid_bool), "reset_delay": Option(0.1, type=valid_float_f01), + "reset_self": Option(False, type=valid_bool), "read_retries": Option(5, type=valid_int_f1), "common_retries": Option(5, type=valid_int_f1), @@ -418,4 +421,7 @@ class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many- reset_required = (1 if response[1] & 0b01000000 else 0) self.__state_flags.update(online=1, busy=reset_required, status=status) if reset_required: - self.__reset_required_event.set() + if self.__reset_self: + time.sleep(1) # Pico перезагружается сам вскоре после ответа + else: + self.__reset_required_event.set() diff --git a/kvmd/plugins/hid/spi.py b/kvmd/plugins/hid/spi.py index e3b44e50..f43f344d 100644 --- a/kvmd/plugins/hid/spi.py +++ b/kvmd/plugins/hid/spi.py @@ -100,6 +100,7 @@ class _SpiPhy(BasePhy): # pylint: disable=too-many-instance-attributes chip: int, hw_cs: bool, sw_cs_pin: int, + sw_cs_per_byte: bool, max_freq: int, block_usec: int, read_timeout: float, @@ -110,6 +111,7 @@ class _SpiPhy(BasePhy): # pylint: disable=too-many-instance-attributes self.__chip = chip self.__hw_cs = hw_cs self.__sw_cs_pin = sw_cs_pin + self.__sw_cs_per_byte = sw_cs_per_byte self.__max_freq = max_freq self.__block_usec = block_usec self.__read_timeout = read_timeout @@ -125,7 +127,7 @@ class _SpiPhy(BasePhy): # pylint: disable=too-many-instance-attributes spi.no_cs = (not self.__hw_cs) spi.max_speed_hz = self.__max_freq - def xfer(data: bytes) -> bytes: + def inner_xfer(data: bytes) -> bytes: try: if sw_cs_line is not None: sw_cs_line.set_value(0) @@ -134,6 +136,17 @@ class _SpiPhy(BasePhy): # pylint: disable=too-many-instance-attributes if sw_cs_line is not None: sw_cs_line.set_value(1) + if self.__sw_cs_per_byte: + # Режим для Pico, когда CS должен взводиться для отдельных байтов + def xfer(data: bytes) -> bytes: + got: list[int] = [] + for byte in data: + got.extend(inner_xfer(byte.to_bytes(1, "big"))) + return bytes(got) + else: + # Режим для Arduino, когда CS взводится для целого блока данных + xfer = inner_xfer + yield _SpiPhyConnection( xfer=xfer, read_timeout=self.__read_timeout, @@ -167,11 +180,12 @@ class Plugin(BaseMcuHid): @classmethod def __get_phy_options(cls) -> dict: return { - "bus": Option(-1, type=valid_int_f0), - "chip": Option(-1, type=valid_int_f0), - "hw_cs": Option(False, type=valid_bool), - "sw_cs_pin": Option(-1, type=valid_gpio_pin_optional), - "max_freq": Option(100000, type=valid_int_f1), - "block_usec": Option(1, type=valid_int_f0), - "read_timeout": Option(0.5, type=valid_float_f01), + "bus": Option(-1, type=valid_int_f0), + "chip": Option(-1, type=valid_int_f0), + "hw_cs": Option(False, type=valid_bool), + "sw_cs_pin": Option(-1, type=valid_gpio_pin_optional), + "sw_cs_per_byte": Option(False, type=valid_bool), + "max_freq": Option(100000, type=valid_int_f1), + "block_usec": Option(1, type=valid_int_f0), + "read_timeout": Option(0.5, type=valid_float_f01), } |