qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

os_detection.c (8697B)


      1 /* Copyright 2022 Ruslan Sayfutdinov (@KapJI)
      2  *
      3  * This program is free software: you can redistribute it and/or modify
      4  * it under the terms of the GNU General Public License as published by
      5  * the Free Software Foundation, either version 2 of the License, or
      6  * (at your option) any later version.
      7  *
      8  * This program is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11  * GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License
     14  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     15  */
     16 
     17 #include "os_detection.h"
     18 
     19 #include <string.h>
     20 #include "timer.h"
     21 #ifdef OS_DETECTION_KEYBOARD_RESET
     22 #    include "quantum.h"
     23 #endif
     24 
     25 #ifdef OS_DETECTION_DEBUG_ENABLE
     26 #    include "nvm_eeprom_eeconfig_internal.h"
     27 #    include "eeprom.h"
     28 #    include "print.h"
     29 
     30 #    define STORED_USB_SETUPS 50
     31 #    define EEPROM_USER_OFFSET (uint8_t*)EECONFIG_SIZE
     32 
     33 static uint16_t usb_setups[STORED_USB_SETUPS];
     34 #endif
     35 
     36 #ifndef OS_DETECTION_DEBOUNCE
     37 #    define OS_DETECTION_DEBOUNCE 250
     38 #endif
     39 
     40 // 2s should always be more than enough (otherwise, you may have other issues)
     41 #if OS_DETECTION_DEBOUNCE > 2000
     42 #    undef OS_DETECTION_DEBOUNCE
     43 #    define OS_DETECTION_DEBOUNCE 2000
     44 #endif
     45 
     46 struct setups_data_t {
     47     uint8_t  count;
     48     uint8_t  cnt_02;
     49     uint8_t  cnt_04;
     50     uint8_t  cnt_ff;
     51     uint16_t last_wlength;
     52 };
     53 
     54 struct setups_data_t setups_data = {
     55     .count  = 0,
     56     .cnt_02 = 0,
     57     .cnt_04 = 0,
     58     .cnt_ff = 0,
     59 };
     60 
     61 static volatile os_variant_t detected_os = OS_UNSURE;
     62 static volatile os_variant_t reported_os = OS_UNSURE;
     63 
     64 // we need to be able to report OS_UNSURE if that is the stable result of the guesses
     65 static volatile bool first_report = true;
     66 
     67 // to react on USB state changes
     68 static volatile struct usb_device_state current_usb_device_state = {.configure_state = USB_DEVICE_STATE_NO_INIT};
     69 static volatile struct usb_device_state maxprev_usb_device_state = {.configure_state = USB_DEVICE_STATE_NO_INIT};
     70 
     71 // to reset the keyboard on USB state change
     72 #ifdef OS_DETECTION_KEYBOARD_RESET
     73 #    ifndef OS_DETECTION_RESET_DEBOUNCE
     74 #        define OS_DETECTION_RESET_DEBOUNCE OS_DETECTION_DEBOUNCE
     75 #    endif
     76 static volatile fast_timer_t configured_since = 0;
     77 static volatile bool         reset_pending    = false;
     78 #endif
     79 
     80 // the OS detection might be unstable for a while, "debounce" it
     81 static volatile bool         debouncing = false;
     82 static volatile fast_timer_t last_time  = 0;
     83 
     84 bool process_detected_host_os_modules(os_variant_t os);
     85 
     86 void os_detection_task(void) {
     87 #ifdef OS_DETECTION_KEYBOARD_RESET
     88     // resetting the keyboard on the USB device state change callback results in instability, so delegate that to this task
     89     if (reset_pending) {
     90         soft_reset_keyboard();
     91     }
     92     // reset the keyboard if it is stuck in the init state for longer than debounce duration, which can happen with some KVMs
     93     if (current_usb_device_state.configure_state <= USB_DEVICE_STATE_INIT && maxprev_usb_device_state.configure_state >= USB_DEVICE_STATE_CONFIGURED) {
     94         if (debouncing && timer_elapsed_fast(last_time) >= OS_DETECTION_DEBOUNCE) {
     95             soft_reset_keyboard();
     96         }
     97         return;
     98     }
     99 #endif
    100 #ifdef OS_DETECTION_SINGLE_REPORT
    101     if (!first_report) {
    102         return;
    103     }
    104 #endif
    105     if (current_usb_device_state.configure_state == USB_DEVICE_STATE_CONFIGURED) {
    106         // debouncing goes for both the detected OS as well as the USB state
    107         if (debouncing && timer_elapsed_fast(last_time) >= OS_DETECTION_DEBOUNCE) {
    108             debouncing = false;
    109             last_time  = 0;
    110             if (detected_os != reported_os || first_report) {
    111                 first_report = false;
    112                 reported_os  = detected_os;
    113                 process_detected_host_os_modules(detected_os);
    114                 process_detected_host_os_kb(detected_os);
    115             }
    116         }
    117     }
    118 }
    119 
    120 __attribute__((weak)) bool process_detected_host_os_modules(os_variant_t os) {
    121     return true;
    122 }
    123 
    124 __attribute__((weak)) bool process_detected_host_os_kb(os_variant_t detected_os) {
    125     return process_detected_host_os_user(detected_os);
    126 }
    127 
    128 __attribute__((weak)) bool process_detected_host_os_user(os_variant_t detected_os) {
    129     return true;
    130 }
    131 
    132 // Some collected sequences of wLength can be found in tests.
    133 void process_wlength(const uint16_t w_length) {
    134 #ifdef OS_DETECTION_DEBUG_ENABLE
    135     usb_setups[setups_data.count] = w_length;
    136 #endif
    137     setups_data.count++;
    138     setups_data.last_wlength = w_length;
    139     if (w_length == 0x2) {
    140         setups_data.cnt_02++;
    141     } else if (w_length == 0x4) {
    142         setups_data.cnt_04++;
    143     } else if (w_length == 0xFF) {
    144         setups_data.cnt_ff++;
    145     }
    146 
    147     // now try to make a guess
    148     os_variant_t guessed = OS_UNSURE;
    149     if (setups_data.count >= 3) {
    150         if (setups_data.cnt_ff >= 2 && setups_data.cnt_04 >= 1) {
    151             guessed = OS_WINDOWS;
    152         } else if (setups_data.count == setups_data.cnt_ff) {
    153             // Linux has 3 packets with 0xFF.
    154             guessed = OS_LINUX;
    155         } else if (setups_data.count >= 5 && setups_data.last_wlength == 0xFF && setups_data.cnt_ff >= 1 && setups_data.cnt_02 >= 2) {
    156             guessed = OS_MACOS;
    157         } else if (setups_data.count == 4 && setups_data.cnt_ff == 0 && setups_data.cnt_02 == 2) {
    158             // iOS and iPadOS don't have the last 0xFF packet.
    159             guessed = OS_IOS;
    160         } else if (setups_data.cnt_ff == 0 && setups_data.cnt_02 == 3 && setups_data.cnt_04 == 1) {
    161             // This is actually PS5.
    162             guessed = OS_LINUX;
    163         } else if (setups_data.cnt_ff >= 1 && setups_data.cnt_02 == 0 && setups_data.cnt_04 == 0) {
    164             // This is actually Quest 2 or Nintendo Switch.
    165             guessed = OS_LINUX;
    166         }
    167     }
    168 
    169     // only replace the guessed value if not unsure
    170     if (guessed != OS_UNSURE) {
    171         detected_os = guessed;
    172     }
    173 
    174     // whatever the result, debounce
    175     last_time  = timer_read_fast();
    176     debouncing = true;
    177 }
    178 
    179 os_variant_t detected_host_os(void) {
    180     return detected_os;
    181 }
    182 
    183 void erase_wlength_data(void) {
    184     memset(&setups_data, 0, sizeof(setups_data));
    185     detected_os                              = OS_UNSURE;
    186     reported_os                              = OS_UNSURE;
    187     current_usb_device_state.configure_state = USB_DEVICE_STATE_NO_INIT;
    188     maxprev_usb_device_state.configure_state = USB_DEVICE_STATE_NO_INIT;
    189     debouncing                               = false;
    190     last_time                                = 0;
    191     first_report                             = true;
    192 }
    193 
    194 void os_detection_notify_usb_device_state_change(struct usb_device_state usb_device_state) {
    195     // treat this like any other source of instability
    196     if (maxprev_usb_device_state.configure_state < current_usb_device_state.configure_state) {
    197         maxprev_usb_device_state.configure_state = current_usb_device_state.configure_state;
    198     }
    199     current_usb_device_state = usb_device_state;
    200     last_time                = timer_read_fast();
    201     debouncing               = true;
    202 
    203 #ifdef OS_DETECTION_KEYBOARD_RESET
    204     if (configured_since == 0 && current_usb_device_state.configure_state == USB_DEVICE_STATE_CONFIGURED) {
    205         configured_since = timer_read_fast();
    206     } else if (current_usb_device_state.configure_state == USB_DEVICE_STATE_INIT) {
    207         // reset the keyboard only if it's been stable for at least debounce duration, to avoid issues with some KVMs
    208         if (configured_since > 0 && timer_elapsed_fast(configured_since) >= OS_DETECTION_RESET_DEBOUNCE) {
    209             reset_pending = true;
    210         }
    211         configured_since = 0;
    212     }
    213 #endif
    214 }
    215 
    216 #if defined(SPLIT_KEYBOARD) && defined(SPLIT_DETECTED_OS_ENABLE)
    217 void slave_update_detected_host_os(os_variant_t os) {
    218     detected_os = os;
    219     last_time   = timer_read_fast();
    220     debouncing  = true;
    221 }
    222 #endif
    223 
    224 #ifdef OS_DETECTION_DEBUG_ENABLE
    225 void print_stored_setups(void) {
    226 #    ifdef CONSOLE_ENABLE
    227     uint8_t cnt = eeprom_read_byte(EEPROM_USER_OFFSET);
    228     for (uint16_t i = 0; i < cnt; ++i) {
    229         uint16_t* addr = (uint16_t*)EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t);
    230         xprintf("i: %d, wLength: 0x%02X\n", i, eeprom_read_word(addr));
    231     }
    232 #    endif
    233 }
    234 
    235 void store_setups_in_eeprom(void) {
    236     eeprom_update_byte(EEPROM_USER_OFFSET, setups_data.count);
    237     for (uint16_t i = 0; i < setups_data.count; ++i) {
    238         uint16_t* addr = (uint16_t*)EEPROM_USER_OFFSET + i * sizeof(uint16_t) + sizeof(uint8_t);
    239         eeprom_update_word(addr, usb_setups[i]);
    240     }
    241 }
    242 
    243 #endif // OS_DETECTION_DEBUG_ENABLE