summaryrefslogtreecommitdiff
path: root/hid/pico/src/ph_usb.c
diff options
context:
space:
mode:
Diffstat (limited to 'hid/pico/src/ph_usb.c')
-rw-r--r--hid/pico/src/ph_usb.c394
1 files changed, 394 insertions, 0 deletions
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;
+}