summaryrefslogtreecommitdiff
path: root/hid/pico
diff options
context:
space:
mode:
authorMaxim Devaev <[email protected]>2023-08-03 05:47:27 +0300
committerMaxim Devaev <[email protected]>2023-08-03 05:47:27 +0300
commitc96057772c8d845c90ad7429e51689eeffb63d71 (patch)
treec376d9715169b9248aa8b7ef70b8a6506297f982 /hid/pico
parent1a8f98a64f9480c1062225e0fc994ceac4ba346d (diff)
rp2040 hid
Diffstat (limited to 'hid/pico')
-rw-r--r--hid/pico/.gitignore6
-rw-r--r--hid/pico/CMakeLists.txt21
-rw-r--r--hid/pico/Makefile36
-rw-r--r--hid/pico/src/CMakeLists.txt25
-rw-r--r--hid/pico/src/main.c122
-rw-r--r--hid/pico/src/ph_cmds.c105
-rw-r--r--hid/pico/src/ph_cmds.h37
-rw-r--r--hid/pico/src/ph_debug.c53
-rw-r--r--hid/pico/src/ph_debug.h27
-rw-r--r--hid/pico/src/ph_outputs.c137
-rw-r--r--hid/pico/src/ph_outputs.h42
-rw-r--r--hid/pico/src/ph_proto.h92
-rw-r--r--hid/pico/src/ph_spi.c120
-rw-r--r--hid/pico/src/ph_spi.h30
-rw-r--r--hid/pico/src/ph_tools.h57
-rw-r--r--hid/pico/src/ph_types.h38
-rw-r--r--hid/pico/src/ph_usb.c394
-rw-r--r--hid/pico/src/ph_usb.h43
-rw-r--r--hid/pico/src/ph_usb_kbd.c78
-rw-r--r--hid/pico/src/ph_usb_kbd.h29
-rw-r--r--hid/pico/src/ph_usb_keymap.h143
-rw-r--r--hid/pico/src/ph_usb_keymap.h.mako39
-rw-r--r--hid/pico/src/ph_usb_mouse.c120
-rw-r--r--hid/pico/src/ph_usb_mouse.h32
-rw-r--r--hid/pico/src/tusb_config.h67
25 files changed, 1893 insertions, 0 deletions
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