/* ========================================================================= # # # # KVMD - The main PiKVM daemon. # # # # Copyright (C) 2018-2023 Maxim Devaev # # # # 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 . # # # # ========================================================================= */ #include "ph_usb.h" #include #include #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; } } } } already_pressed: // Old GCC doesn't like ^ that label in the end of block _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; }