qmk_firmware

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

transactions.c (44751B)


      1 /* Copyright 2021 QMK
      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 <stdint.h>
     18 #include <string.h>
     19 #include <stddef.h>
     20 
     21 #include "crc.h"
     22 #include "debug.h"
     23 #include "matrix.h"
     24 #include "host.h"
     25 #include "action_util.h"
     26 #include "sync_timer.h"
     27 #include "wait.h"
     28 #include "transactions.h"
     29 #include "transport.h"
     30 #include "transaction_id_define.h"
     31 #include "split_util.h"
     32 #include "synchronization_util.h"
     33 
     34 #ifdef BACKLIGHT_ENABLE
     35 #    include "backlight.h"
     36 #endif
     37 #ifdef RGBLIGHT_ENABLE
     38 #    include "rgblight.h"
     39 #endif
     40 #ifdef LED_MATRIX_ENABLE
     41 #    include "led_matrix.h"
     42 #endif
     43 #ifdef RGB_MATRIX_ENABLE
     44 #    include "rgb_matrix.h"
     45 #endif
     46 #ifdef OLED_ENABLE
     47 #    include "oled_driver.h"
     48 #endif
     49 #ifdef ST7565_ENABLE
     50 #    include "st7565.h"
     51 #endif
     52 #ifdef ENCODER_ENABLE
     53 #    include "encoder.h"
     54 #endif
     55 #ifdef HAPTIC_ENABLE
     56 #    include "haptic.h"
     57 #endif
     58 #ifdef POINTING_DEVICE_ENABLE
     59 #    include "pointing_device.h"
     60 #endif
     61 #ifdef OS_DETECTION_ENABLE
     62 #    include "os_detection.h"
     63 #endif
     64 #ifdef WPM_ENABLE
     65 #    include "wpm.h"
     66 #endif
     67 
     68 #define SYNC_TIMER_OFFSET 2
     69 
     70 #ifndef FORCED_SYNC_THROTTLE_MS
     71 #    define FORCED_SYNC_THROTTLE_MS 100
     72 #endif // FORCED_SYNC_THROTTLE_MS
     73 
     74 #define sizeof_member(type, member) sizeof(((type *)NULL)->member)
     75 
     76 #define trans_initiator2target_initializer_cb(member, cb) {sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), 0, 0, cb}
     77 #define trans_initiator2target_initializer(member) trans_initiator2target_initializer_cb(member, NULL)
     78 
     79 #define trans_target2initiator_initializer_cb(member, cb) {0, 0, sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), cb}
     80 #define trans_target2initiator_initializer(member) trans_target2initiator_initializer_cb(member, NULL)
     81 
     82 #define trans_initiator2target_cb(cb) {0, 0, 0, 0, cb}
     83 
     84 #define transport_write(id, data, length) transport_execute_transaction(id, data, length, NULL, 0)
     85 #define transport_read(id, data, length) transport_execute_transaction(id, NULL, 0, data, length)
     86 #define transport_exec(id) transport_execute_transaction(id, NULL, 0, NULL, 0)
     87 
     88 #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
     89 // Forward-declare the RPC callback handlers
     90 void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
     91 void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
     92 #endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
     93 
     94 ////////////////////////////////////////////////////
     95 // Helpers
     96 
     97 static bool transaction_handler_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[], const char *prefix, bool (*handler)(matrix_row_t master_matrix[], matrix_row_t slave_matrix[])) {
     98     int num_retries = is_transport_connected() ? 10 : 1;
     99     for (int iter = 1; iter <= num_retries; ++iter) {
    100         if (iter > 1) {
    101             for (int i = 0; i < iter * iter; ++i) {
    102                 wait_us(10);
    103             }
    104         }
    105         bool this_okay = true;
    106         this_okay      = handler(master_matrix, slave_matrix);
    107         if (this_okay) return true;
    108     }
    109     dprintf("Failed to execute %s\n", prefix);
    110     return false;
    111 }
    112 
    113 #define TRANSACTION_HANDLER_MASTER(prefix)                                                                              \
    114     do {                                                                                                                \
    115         if (!transaction_handler_master(master_matrix, slave_matrix, #prefix, &prefix##_handlers_master)) return false; \
    116     } while (0)
    117 
    118 /**
    119  * @brief Constructs a transaction handler that doesn't acquire a lock to the
    120  * split shared memory. Therefore the locking and unlocking has to be done
    121  * manually inside the handler. Use this macro only if the handler is
    122  * non-deterministic in runtime and thus needs a manual lock unlock
    123  * implementation to hold the lock for the shortest possible time.
    124  */
    125 #define TRANSACTION_HANDLER_SLAVE(prefix)                     \
    126     do {                                                      \
    127         prefix##_handlers_slave(master_matrix, slave_matrix); \
    128     } while (0)
    129 
    130 /**
    131  * @brief Constructs a transaction handler that automatically acquires a lock to
    132  * safely access the split shared memory and releases the lock again after
    133  * processing the handler. Use this macro if the handler is fast and
    134  * deterministic in runtime and thus holds the lock only for a very short time.
    135  * If not fallback to manually locking and unlocking inside the handler.
    136  */
    137 #define TRANSACTION_HANDLER_SLAVE_AUTOLOCK(prefix)            \
    138     do {                                                      \
    139         split_shared_memory_lock();                           \
    140         prefix##_handlers_slave(master_matrix, slave_matrix); \
    141         split_shared_memory_unlock();                         \
    142     } while (0)
    143 
    144 inline static bool read_if_checksum_mismatch(int8_t trans_id_checksum, int8_t trans_id_retrieve, uint32_t *last_update, void *destination, const void *equiv_shmem, size_t length) {
    145     uint8_t curr_checksum;
    146     bool    okay = transport_read(trans_id_checksum, &curr_checksum, sizeof(curr_checksum));
    147     if (okay && (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || curr_checksum != crc8(equiv_shmem, length))) {
    148         okay &= transport_read(trans_id_retrieve, destination, length);
    149         okay &= curr_checksum == crc8(equiv_shmem, length);
    150         if (okay) {
    151             *last_update = timer_read32();
    152         }
    153     } else {
    154         memcpy(destination, equiv_shmem, length);
    155     }
    156     return okay;
    157 }
    158 
    159 inline static bool send_if_condition(int8_t trans_id, uint32_t *last_update, bool condition, void *source, size_t length) {
    160     bool okay = true;
    161     if (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || condition) {
    162         okay &= transport_write(trans_id, source, length);
    163         if (okay) {
    164             *last_update = timer_read32();
    165         }
    166     }
    167     return okay;
    168 }
    169 
    170 inline static bool send_if_data_mismatch(int8_t trans_id, uint32_t *last_update, void *source, const void *equiv_shmem, size_t length) {
    171     // Just run a memcmp to compare the source and equivalent shmem location
    172     return send_if_condition(trans_id, last_update, (memcmp(source, equiv_shmem, length) != 0), source, length);
    173 }
    174 
    175 ////////////////////////////////////////////////////
    176 // Slave matrix
    177 
    178 static bool slave_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    179     static uint32_t     last_update                    = 0;
    180     static matrix_row_t last_matrix[(MATRIX_ROWS) / 2] = {0}; // last successfully-read matrix, so we can replicate if there are checksum errors
    181     matrix_row_t        temp_matrix[(MATRIX_ROWS) / 2];       // holding area while we test whether or not checksum is correct
    182 
    183     bool okay = read_if_checksum_mismatch(GET_SLAVE_MATRIX_CHECKSUM, GET_SLAVE_MATRIX_DATA, &last_update, temp_matrix, split_shmem->smatrix.matrix, sizeof(split_shmem->smatrix.matrix));
    184     if (okay) {
    185         // Checksum matches the received data, save as the last matrix state
    186         memcpy(last_matrix, temp_matrix, sizeof(temp_matrix));
    187     }
    188     // Copy out the last-known-good matrix state to the slave matrix
    189     memcpy(slave_matrix, last_matrix, sizeof(last_matrix));
    190     return okay;
    191 }
    192 
    193 static void slave_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    194     memcpy(split_shmem->smatrix.matrix, slave_matrix, sizeof(split_shmem->smatrix.matrix));
    195     split_shmem->smatrix.checksum = crc8(split_shmem->smatrix.matrix, sizeof(split_shmem->smatrix.matrix));
    196 }
    197 
    198 // clang-format off
    199 #define TRANSACTIONS_SLAVE_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(slave_matrix)
    200 #define TRANSACTIONS_SLAVE_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE_AUTOLOCK(slave_matrix)
    201 #define TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS \
    202     [GET_SLAVE_MATRIX_CHECKSUM] = trans_target2initiator_initializer(smatrix.checksum), \
    203     [GET_SLAVE_MATRIX_DATA]     = trans_target2initiator_initializer(smatrix.matrix),
    204 // clang-format on
    205 
    206 ////////////////////////////////////////////////////
    207 // Master matrix
    208 
    209 #ifdef SPLIT_TRANSPORT_MIRROR
    210 
    211 static bool master_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    212     static uint32_t last_update = 0;
    213     return send_if_data_mismatch(PUT_MASTER_MATRIX, &last_update, master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix));
    214 }
    215 
    216 static void master_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    217     // Always copy to the master matrix
    218     memcpy(master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix));
    219 }
    220 
    221 #    define TRANSACTIONS_MASTER_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(master_matrix)
    222 #    define TRANSACTIONS_MASTER_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE_AUTOLOCK(master_matrix)
    223 #    define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS [PUT_MASTER_MATRIX] = trans_initiator2target_initializer(mmatrix.matrix),
    224 
    225 #else // SPLIT_TRANSPORT_MIRROR
    226 
    227 #    define TRANSACTIONS_MASTER_MATRIX_MASTER()
    228 #    define TRANSACTIONS_MASTER_MATRIX_SLAVE()
    229 #    define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS
    230 
    231 #endif // SPLIT_TRANSPORT_MIRROR
    232 
    233 ////////////////////////////////////////////////////
    234 // Encoders
    235 
    236 #ifdef ENCODER_ENABLE
    237 
    238 static bool encoder_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    239     static uint32_t  last_update   = 0;
    240     static uint8_t   last_checksum = 0;
    241     encoder_events_t temp_events;
    242 
    243     bool okay = read_if_checksum_mismatch(GET_ENCODERS_CHECKSUM, GET_ENCODERS_DATA, &last_update, &temp_events, &split_shmem->encoders.events, sizeof(temp_events));
    244     if (okay) {
    245         if (last_checksum != split_shmem->encoders.checksum) {
    246             bool    actioned = false;
    247             uint8_t index;
    248             bool    clockwise;
    249             while (okay && encoder_dequeue_event_advanced(&split_shmem->encoders.events, &index, &clockwise)) {
    250                 okay &= encoder_queue_event(index, clockwise);
    251                 actioned = true;
    252             }
    253 
    254             if (actioned) {
    255                 okay &= transport_exec(CMD_ENCODER_DRAIN);
    256             }
    257             last_checksum = split_shmem->encoders.checksum;
    258         }
    259     }
    260     return okay;
    261 }
    262 
    263 static void encoder_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    264     // Always prepare the encoder state for read.
    265     encoder_retrieve_events(&split_shmem->encoders.events);
    266     // Now update the checksum given that the encoders has been written to
    267     split_shmem->encoders.checksum = crc8(&split_shmem->encoders.events, sizeof(split_shmem->encoders.events));
    268 }
    269 
    270 static void encoder_handlers_slave_drain(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
    271     encoder_signal_queue_drain();
    272 }
    273 
    274 // clang-format off
    275 #    define TRANSACTIONS_ENCODERS_MASTER() TRANSACTION_HANDLER_MASTER(encoder)
    276 #    define TRANSACTIONS_ENCODERS_SLAVE() TRANSACTION_HANDLER_SLAVE_AUTOLOCK(encoder)
    277 #    define TRANSACTIONS_ENCODERS_REGISTRATIONS \
    278     [GET_ENCODERS_CHECKSUM] = trans_target2initiator_initializer(encoders.checksum), \
    279     [GET_ENCODERS_DATA]     = trans_target2initiator_initializer(encoders.events), \
    280     [CMD_ENCODER_DRAIN]     = trans_initiator2target_cb(encoder_handlers_slave_drain),
    281 // clang-format on
    282 
    283 #else // ENCODER_ENABLE
    284 
    285 #    define TRANSACTIONS_ENCODERS_MASTER()
    286 #    define TRANSACTIONS_ENCODERS_SLAVE()
    287 #    define TRANSACTIONS_ENCODERS_REGISTRATIONS
    288 
    289 #endif // ENCODER_ENABLE
    290 
    291 ////////////////////////////////////////////////////
    292 // Sync timer
    293 
    294 #ifndef DISABLE_SYNC_TIMER
    295 
    296 static bool sync_timer_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    297     static uint32_t last_update = 0;
    298 
    299     bool okay = true;
    300     if (timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS) {
    301         uint32_t sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET;
    302         okay &= transport_write(PUT_SYNC_TIMER, &sync_timer, sizeof(sync_timer));
    303         if (okay) {
    304             last_update = timer_read32();
    305         }
    306     }
    307     return okay;
    308 }
    309 
    310 static void sync_timer_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    311     static uint32_t last_sync_timer = 0;
    312     if (last_sync_timer != split_shmem->sync_timer) {
    313         last_sync_timer = split_shmem->sync_timer;
    314         sync_timer_update(last_sync_timer);
    315     }
    316 }
    317 
    318 #    define TRANSACTIONS_SYNC_TIMER_MASTER() TRANSACTION_HANDLER_MASTER(sync_timer)
    319 #    define TRANSACTIONS_SYNC_TIMER_SLAVE() TRANSACTION_HANDLER_SLAVE_AUTOLOCK(sync_timer)
    320 #    define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS [PUT_SYNC_TIMER] = trans_initiator2target_initializer(sync_timer),
    321 
    322 #else // DISABLE_SYNC_TIMER
    323 
    324 #    define TRANSACTIONS_SYNC_TIMER_MASTER()
    325 #    define TRANSACTIONS_SYNC_TIMER_SLAVE()
    326 #    define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS
    327 
    328 #endif // DISABLE_SYNC_TIMER
    329 
    330 ////////////////////////////////////////////////////
    331 // Layer state
    332 
    333 #if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
    334 
    335 static bool layer_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    336     static uint32_t last_layer_state_update         = 0;
    337     static uint32_t last_default_layer_state_update = 0;
    338 
    339     bool okay = send_if_condition(PUT_LAYER_STATE, &last_layer_state_update, (layer_state != split_shmem->layers.layer_state), &layer_state, sizeof(layer_state));
    340     if (okay) {
    341         okay &= send_if_condition(PUT_DEFAULT_LAYER_STATE, &last_default_layer_state_update, (default_layer_state != split_shmem->layers.default_layer_state), &default_layer_state, sizeof(default_layer_state));
    342     }
    343     return okay;
    344 }
    345 
    346 static void layer_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    347     layer_state         = split_shmem->layers.layer_state;
    348     default_layer_state = split_shmem->layers.default_layer_state;
    349 }
    350 
    351 // clang-format off
    352 #    define TRANSACTIONS_LAYER_STATE_MASTER() TRANSACTION_HANDLER_MASTER(layer_state)
    353 #    define TRANSACTIONS_LAYER_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE_AUTOLOCK(layer_state)
    354 #    define TRANSACTIONS_LAYER_STATE_REGISTRATIONS \
    355     [PUT_LAYER_STATE]         = trans_initiator2target_initializer(layers.layer_state), \
    356     [PUT_DEFAULT_LAYER_STATE] = trans_initiator2target_initializer(layers.default_layer_state),
    357 // clang-format on
    358 
    359 #else // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
    360 
    361 #    define TRANSACTIONS_LAYER_STATE_MASTER()
    362 #    define TRANSACTIONS_LAYER_STATE_SLAVE()
    363 #    define TRANSACTIONS_LAYER_STATE_REGISTRATIONS
    364 
    365 #endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
    366 
    367 ////////////////////////////////////////////////////
    368 // LED state
    369 
    370 #ifdef SPLIT_LED_STATE_ENABLE
    371 
    372 static bool led_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    373     static uint32_t last_update = 0;
    374     uint8_t         led_state   = host_keyboard_leds();
    375     return send_if_data_mismatch(PUT_LED_STATE, &last_update, &led_state, &split_shmem->led_state, sizeof(led_state));
    376 }
    377 
    378 static void led_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    379     void set_split_host_keyboard_leds(uint8_t led_state);
    380     set_split_host_keyboard_leds(split_shmem->led_state);
    381 }
    382 
    383 #    define TRANSACTIONS_LED_STATE_MASTER() TRANSACTION_HANDLER_MASTER(led_state)
    384 #    define TRANSACTIONS_LED_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE_AUTOLOCK(led_state)
    385 #    define TRANSACTIONS_LED_STATE_REGISTRATIONS [PUT_LED_STATE] = trans_initiator2target_initializer(led_state),
    386 
    387 #else // SPLIT_LED_STATE_ENABLE
    388 
    389 #    define TRANSACTIONS_LED_STATE_MASTER()
    390 #    define TRANSACTIONS_LED_STATE_SLAVE()
    391 #    define TRANSACTIONS_LED_STATE_REGISTRATIONS
    392 
    393 #endif // SPLIT_LED_STATE_ENABLE
    394 
    395 ////////////////////////////////////////////////////
    396 // Mods
    397 
    398 #ifdef SPLIT_MODS_ENABLE
    399 
    400 static bool mods_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    401     static uint32_t   last_update    = 0;
    402     bool              mods_need_sync = timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS;
    403     split_mods_sync_t new_mods;
    404     new_mods.real_mods = get_mods();
    405     if (!mods_need_sync && new_mods.real_mods != split_shmem->mods.real_mods) {
    406         mods_need_sync = true;
    407     }
    408 
    409     new_mods.weak_mods = get_weak_mods();
    410     if (!mods_need_sync && new_mods.weak_mods != split_shmem->mods.weak_mods) {
    411         mods_need_sync = true;
    412     }
    413 
    414 #    ifndef NO_ACTION_ONESHOT
    415     new_mods.oneshot_mods = get_oneshot_mods();
    416     if (!mods_need_sync && new_mods.oneshot_mods != split_shmem->mods.oneshot_mods) {
    417         mods_need_sync = true;
    418     }
    419     new_mods.oneshot_locked_mods = get_oneshot_locked_mods();
    420     if (!mods_need_sync && new_mods.oneshot_locked_mods != split_shmem->mods.oneshot_locked_mods) {
    421         mods_need_sync = true;
    422     }
    423 #    endif // NO_ACTION_ONESHOT
    424 
    425     bool okay = true;
    426     if (mods_need_sync) {
    427         okay &= transport_write(PUT_MODS, &new_mods, sizeof(new_mods));
    428         if (okay) {
    429             last_update = timer_read32();
    430         }
    431     }
    432 
    433     return okay;
    434 }
    435 
    436 static void mods_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    437     split_shared_memory_lock();
    438     split_mods_sync_t mods;
    439     memcpy(&mods, &split_shmem->mods, sizeof(split_mods_sync_t));
    440     split_shared_memory_unlock();
    441 
    442     set_mods(mods.real_mods);
    443     set_weak_mods(mods.weak_mods);
    444 #    ifndef NO_ACTION_ONESHOT
    445     set_oneshot_mods(mods.oneshot_mods);
    446     set_oneshot_locked_mods(mods.oneshot_locked_mods);
    447 #    endif
    448 }
    449 
    450 #    define TRANSACTIONS_MODS_MASTER() TRANSACTION_HANDLER_MASTER(mods)
    451 #    define TRANSACTIONS_MODS_SLAVE() TRANSACTION_HANDLER_SLAVE(mods)
    452 #    define TRANSACTIONS_MODS_REGISTRATIONS [PUT_MODS] = trans_initiator2target_initializer(mods),
    453 
    454 #else // SPLIT_MODS_ENABLE
    455 
    456 #    define TRANSACTIONS_MODS_MASTER()
    457 #    define TRANSACTIONS_MODS_SLAVE()
    458 #    define TRANSACTIONS_MODS_REGISTRATIONS
    459 
    460 #endif // SPLIT_MODS_ENABLE
    461 
    462 ////////////////////////////////////////////////////
    463 // Backlight
    464 
    465 #ifdef BACKLIGHT_ENABLE
    466 
    467 static bool backlight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    468     static uint32_t last_update = 0;
    469     uint8_t         level       = is_backlight_enabled() ? get_backlight_level() : 0;
    470     return send_if_condition(PUT_BACKLIGHT, &last_update, (level != split_shmem->backlight_level), &level, sizeof(level));
    471 }
    472 
    473 static void backlight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    474     split_shared_memory_lock();
    475     uint8_t backlight_level = split_shmem->backlight_level;
    476     split_shared_memory_unlock();
    477 
    478     backlight_level_noeeprom(backlight_level);
    479 }
    480 
    481 #    define TRANSACTIONS_BACKLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(backlight)
    482 #    define TRANSACTIONS_BACKLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(backlight)
    483 #    define TRANSACTIONS_BACKLIGHT_REGISTRATIONS [PUT_BACKLIGHT] = trans_initiator2target_initializer(backlight_level),
    484 
    485 #else // BACKLIGHT_ENABLE
    486 
    487 #    define TRANSACTIONS_BACKLIGHT_MASTER()
    488 #    define TRANSACTIONS_BACKLIGHT_SLAVE()
    489 #    define TRANSACTIONS_BACKLIGHT_REGISTRATIONS
    490 
    491 #endif // BACKLIGHT_ENABLE
    492 
    493 ////////////////////////////////////////////////////
    494 // RGBLIGHT
    495 
    496 #if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
    497 
    498 static bool rgblight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    499     static uint32_t     last_update = 0;
    500     rgblight_syncinfo_t rgblight_sync;
    501     rgblight_get_syncinfo(&rgblight_sync);
    502     if (send_if_condition(PUT_RGBLIGHT, &last_update, (rgblight_sync.status.change_flags != 0), &rgblight_sync, sizeof(rgblight_sync))) {
    503         rgblight_clear_change_flags();
    504     } else {
    505         return false;
    506     }
    507     return true;
    508 }
    509 
    510 static void rgblight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    511     split_shared_memory_lock();
    512     // Update the RGB with the new data
    513     rgblight_syncinfo_t rgblight_sync;
    514     memcpy(&rgblight_sync, &split_shmem->rgblight_sync, sizeof(rgblight_syncinfo_t));
    515     split_shmem->rgblight_sync.status.change_flags = 0;
    516     split_shared_memory_unlock();
    517 
    518     if (rgblight_sync.status.change_flags != 0) {
    519         rgblight_update_sync(&rgblight_sync, false);
    520     }
    521 }
    522 
    523 #    define TRANSACTIONS_RGBLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(rgblight)
    524 #    define TRANSACTIONS_RGBLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(rgblight)
    525 #    define TRANSACTIONS_RGBLIGHT_REGISTRATIONS [PUT_RGBLIGHT] = trans_initiator2target_initializer(rgblight_sync),
    526 
    527 #else // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
    528 
    529 #    define TRANSACTIONS_RGBLIGHT_MASTER()
    530 #    define TRANSACTIONS_RGBLIGHT_SLAVE()
    531 #    define TRANSACTIONS_RGBLIGHT_REGISTRATIONS
    532 
    533 #endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
    534 
    535 ////////////////////////////////////////////////////
    536 // LED Matrix
    537 
    538 #if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
    539 
    540 static bool led_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    541     static uint32_t   last_update = 0;
    542     led_matrix_sync_t led_matrix_sync;
    543     memcpy(&led_matrix_sync.led_matrix, &led_matrix_eeconfig, sizeof(led_eeconfig_t));
    544     led_matrix_sync.led_suspend_state = led_matrix_get_suspend_state();
    545     return send_if_data_mismatch(PUT_LED_MATRIX, &last_update, &led_matrix_sync, &split_shmem->led_matrix_sync, sizeof(led_matrix_sync));
    546 }
    547 
    548 static void led_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    549     split_shared_memory_lock();
    550     memcpy(&led_matrix_eeconfig, &split_shmem->led_matrix_sync.led_matrix, sizeof(led_eeconfig_t));
    551     bool led_suspend_state = split_shmem->led_matrix_sync.led_suspend_state;
    552     split_shared_memory_unlock();
    553 
    554     led_matrix_set_suspend_state(led_suspend_state);
    555 }
    556 
    557 #    define TRANSACTIONS_LED_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(led_matrix)
    558 #    define TRANSACTIONS_LED_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(led_matrix)
    559 #    define TRANSACTIONS_LED_MATRIX_REGISTRATIONS [PUT_LED_MATRIX] = trans_initiator2target_initializer(led_matrix_sync),
    560 
    561 #else // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
    562 
    563 #    define TRANSACTIONS_LED_MATRIX_MASTER()
    564 #    define TRANSACTIONS_LED_MATRIX_SLAVE()
    565 #    define TRANSACTIONS_LED_MATRIX_REGISTRATIONS
    566 
    567 #endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
    568 
    569 ////////////////////////////////////////////////////
    570 // RGB Matrix
    571 
    572 #if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
    573 
    574 static bool rgb_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    575     static uint32_t   last_update = 0;
    576     rgb_matrix_sync_t rgb_matrix_sync;
    577     memcpy(&rgb_matrix_sync.rgb_matrix, &rgb_matrix_config, sizeof(rgb_config_t));
    578     rgb_matrix_sync.rgb_suspend_state = rgb_matrix_get_suspend_state();
    579     return send_if_data_mismatch(PUT_RGB_MATRIX, &last_update, &rgb_matrix_sync, &split_shmem->rgb_matrix_sync, sizeof(rgb_matrix_sync));
    580 }
    581 
    582 static void rgb_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    583     split_shared_memory_lock();
    584     memcpy(&rgb_matrix_config, &split_shmem->rgb_matrix_sync.rgb_matrix, sizeof(rgb_config_t));
    585     bool rgb_suspend_state = split_shmem->rgb_matrix_sync.rgb_suspend_state;
    586     split_shared_memory_unlock();
    587 
    588     rgb_matrix_set_suspend_state(rgb_suspend_state);
    589 }
    590 
    591 #    define TRANSACTIONS_RGB_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(rgb_matrix)
    592 #    define TRANSACTIONS_RGB_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(rgb_matrix)
    593 #    define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS [PUT_RGB_MATRIX] = trans_initiator2target_initializer(rgb_matrix_sync),
    594 
    595 #else // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
    596 
    597 #    define TRANSACTIONS_RGB_MATRIX_MASTER()
    598 #    define TRANSACTIONS_RGB_MATRIX_SLAVE()
    599 #    define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS
    600 
    601 #endif // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
    602 
    603 ////////////////////////////////////////////////////
    604 // WPM
    605 
    606 #if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
    607 
    608 static bool wpm_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    609     static uint32_t last_update = 0;
    610     uint8_t         current_wpm = get_current_wpm();
    611     return send_if_condition(PUT_WPM, &last_update, (current_wpm != split_shmem->current_wpm), &current_wpm, sizeof(current_wpm));
    612 }
    613 
    614 static void wpm_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    615     set_current_wpm(split_shmem->current_wpm);
    616 }
    617 
    618 #    define TRANSACTIONS_WPM_MASTER() TRANSACTION_HANDLER_MASTER(wpm)
    619 #    define TRANSACTIONS_WPM_SLAVE() TRANSACTION_HANDLER_SLAVE_AUTOLOCK(wpm)
    620 #    define TRANSACTIONS_WPM_REGISTRATIONS [PUT_WPM] = trans_initiator2target_initializer(current_wpm),
    621 
    622 #else // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
    623 
    624 #    define TRANSACTIONS_WPM_MASTER()
    625 #    define TRANSACTIONS_WPM_SLAVE()
    626 #    define TRANSACTIONS_WPM_REGISTRATIONS
    627 
    628 #endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
    629 
    630 ////////////////////////////////////////////////////
    631 // OLED
    632 
    633 #if defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
    634 
    635 static bool oled_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    636     static uint32_t last_update        = 0;
    637     bool            current_oled_state = is_oled_on();
    638     return send_if_condition(PUT_OLED, &last_update, (current_oled_state != split_shmem->current_oled_state), &current_oled_state, sizeof(current_oled_state));
    639 }
    640 
    641 static void oled_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    642     split_shared_memory_lock();
    643     uint8_t current_oled_state = split_shmem->current_oled_state;
    644     split_shared_memory_unlock();
    645 
    646     if (current_oled_state) {
    647         oled_on();
    648     } else {
    649         oled_off();
    650     }
    651 }
    652 
    653 #    define TRANSACTIONS_OLED_MASTER() TRANSACTION_HANDLER_MASTER(oled)
    654 #    define TRANSACTIONS_OLED_SLAVE() TRANSACTION_HANDLER_SLAVE(oled)
    655 #    define TRANSACTIONS_OLED_REGISTRATIONS [PUT_OLED] = trans_initiator2target_initializer(current_oled_state),
    656 
    657 #else // defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
    658 
    659 #    define TRANSACTIONS_OLED_MASTER()
    660 #    define TRANSACTIONS_OLED_SLAVE()
    661 #    define TRANSACTIONS_OLED_REGISTRATIONS
    662 
    663 #endif // defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
    664 
    665 ////////////////////////////////////////////////////
    666 // ST7565
    667 
    668 #if defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
    669 
    670 static bool st7565_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    671     static uint32_t last_update          = 0;
    672     bool            current_st7565_state = st7565_is_on();
    673     return send_if_condition(PUT_ST7565, &last_update, (current_st7565_state != split_shmem->current_st7565_state), &current_st7565_state, sizeof(current_st7565_state));
    674 }
    675 
    676 static void st7565_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    677     split_shared_memory_lock();
    678     uint8_t current_st7565_state = split_shmem->current_st7565_state;
    679     split_shared_memory_unlock();
    680 
    681     if (current_st7565_state) {
    682         st7565_on();
    683     } else {
    684         st7565_off();
    685     }
    686 }
    687 
    688 #    define TRANSACTIONS_ST7565_MASTER() TRANSACTION_HANDLER_MASTER(st7565)
    689 #    define TRANSACTIONS_ST7565_SLAVE() TRANSACTION_HANDLER_SLAVE(st7565)
    690 #    define TRANSACTIONS_ST7565_REGISTRATIONS [PUT_ST7565] = trans_initiator2target_initializer(current_st7565_state),
    691 
    692 #else // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
    693 
    694 #    define TRANSACTIONS_ST7565_MASTER()
    695 #    define TRANSACTIONS_ST7565_SLAVE()
    696 #    define TRANSACTIONS_ST7565_REGISTRATIONS
    697 
    698 #endif // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
    699 
    700 ////////////////////////////////////////////////////
    701 // POINTING
    702 
    703 #if defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
    704 
    705 static bool pointing_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    706 #    if defined(POINTING_DEVICE_LEFT)
    707     if (is_keyboard_left()) {
    708         return true;
    709     }
    710 #    elif defined(POINTING_DEVICE_RIGHT)
    711     if (!is_keyboard_left()) {
    712         return true;
    713     }
    714 #    endif
    715     static uint32_t last_update     = 0;
    716     static uint32_t last_cpi_update = 0;
    717     static uint16_t last_cpi        = 0;
    718     report_mouse_t  temp_state;
    719     uint16_t        temp_cpi;
    720     bool            okay = read_if_checksum_mismatch(GET_POINTING_CHECKSUM, GET_POINTING_DATA, &last_update, &temp_state, &split_shmem->pointing.report, sizeof(temp_state));
    721     if (okay) pointing_device_set_shared_report(temp_state);
    722     temp_cpi = pointing_device_get_shared_cpi();
    723     if (temp_cpi) {
    724         split_shmem->pointing.cpi = temp_cpi;
    725         okay                      = send_if_condition(PUT_POINTING_CPI, &last_cpi_update, last_cpi != temp_cpi, &split_shmem->pointing.cpi, sizeof(split_shmem->pointing.cpi));
    726         if (okay) {
    727             last_cpi = temp_cpi;
    728         }
    729     }
    730     return okay;
    731 }
    732 
    733 extern const pointing_device_driver_t *pointing_device_driver;
    734 
    735 static void pointing_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    736 #    if defined(POINTING_DEVICE_LEFT)
    737     if (!is_keyboard_left()) {
    738         return;
    739     }
    740 #    elif defined(POINTING_DEVICE_RIGHT)
    741     if (is_keyboard_left()) {
    742         return;
    743     }
    744 #    endif
    745 #    if (POINTING_DEVICE_TASK_THROTTLE_MS > 0)
    746     static uint32_t last_exec = 0;
    747     if (timer_elapsed32(last_exec) < POINTING_DEVICE_TASK_THROTTLE_MS) {
    748         return;
    749     }
    750     last_exec = timer_read32();
    751 #    endif
    752 
    753     uint16_t temp_cpi = !pointing_device_driver->get_cpi ? 0 : pointing_device_driver->get_cpi(); // check for NULL
    754 
    755     split_shared_memory_lock();
    756     split_slave_pointing_sync_t pointing;
    757     memcpy(&pointing, &split_shmem->pointing, sizeof(split_slave_pointing_sync_t));
    758     split_shared_memory_unlock();
    759 
    760     if (pointing.cpi && pointing.cpi != temp_cpi && pointing_device_driver->set_cpi) {
    761         pointing_device_driver->set_cpi(pointing.cpi);
    762     }
    763 
    764     pointing.report = pointing_device_driver->get_report((report_mouse_t){0});
    765     // Now update the checksum given that the pointing has been written to
    766     pointing.checksum = crc8(&pointing.report, sizeof(report_mouse_t));
    767 
    768     split_shared_memory_lock();
    769     memcpy(&split_shmem->pointing, &pointing, sizeof(split_slave_pointing_sync_t));
    770     split_shared_memory_unlock();
    771 }
    772 
    773 #    define TRANSACTIONS_POINTING_MASTER() TRANSACTION_HANDLER_MASTER(pointing)
    774 #    define TRANSACTIONS_POINTING_SLAVE() TRANSACTION_HANDLER_SLAVE(pointing)
    775 #    define TRANSACTIONS_POINTING_REGISTRATIONS [GET_POINTING_CHECKSUM] = trans_target2initiator_initializer(pointing.checksum), [GET_POINTING_DATA] = trans_target2initiator_initializer(pointing.report), [PUT_POINTING_CPI] = trans_initiator2target_initializer(pointing.cpi),
    776 
    777 #else // defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
    778 
    779 #    define TRANSACTIONS_POINTING_MASTER()
    780 #    define TRANSACTIONS_POINTING_SLAVE()
    781 #    define TRANSACTIONS_POINTING_REGISTRATIONS
    782 
    783 #endif // defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
    784 
    785 ////////////////////////////////////////////////////
    786 // WATCHDOG
    787 
    788 #if defined(SPLIT_WATCHDOG_ENABLE)
    789 
    790 static bool watchdog_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    791     bool okay = true;
    792     if (!split_watchdog_check()) {
    793         okay = transport_write(PUT_WATCHDOG, &okay, sizeof(okay));
    794         split_watchdog_update(okay);
    795     }
    796     return okay;
    797 }
    798 
    799 static void watchdog_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    800     split_watchdog_update(split_shmem->watchdog_pinged);
    801 }
    802 
    803 #    define TRANSACTIONS_WATCHDOG_MASTER() TRANSACTION_HANDLER_MASTER(watchdog)
    804 #    define TRANSACTIONS_WATCHDOG_SLAVE() TRANSACTION_HANDLER_SLAVE_AUTOLOCK(watchdog)
    805 #    define TRANSACTIONS_WATCHDOG_REGISTRATIONS [PUT_WATCHDOG] = trans_initiator2target_initializer(watchdog_pinged),
    806 
    807 #else // defined(SPLIT_WATCHDOG_ENABLE)
    808 
    809 #    define TRANSACTIONS_WATCHDOG_MASTER()
    810 #    define TRANSACTIONS_WATCHDOG_SLAVE()
    811 #    define TRANSACTIONS_WATCHDOG_REGISTRATIONS
    812 
    813 #endif // defined(SPLIT_WATCHDOG_ENABLE)
    814 
    815 #if defined(HAPTIC_ENABLE) && defined(SPLIT_HAPTIC_ENABLE)
    816 
    817 uint8_t                split_haptic_play = 0xFF;
    818 extern haptic_config_t haptic_config;
    819 
    820 static bool haptic_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    821     static uint32_t           last_update = 0;
    822     split_slave_haptic_sync_t haptic_sync;
    823 
    824     memcpy(&haptic_sync.haptic_config, &haptic_config, sizeof(haptic_config_t));
    825     haptic_sync.haptic_play = split_haptic_play;
    826 
    827     bool okay = send_if_data_mismatch(PUT_HAPTIC, &last_update, &haptic_sync, &split_shmem->haptic_sync, sizeof(haptic_sync));
    828 
    829     split_haptic_play = 0xFF;
    830 
    831     return okay;
    832 }
    833 
    834 static void haptic_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    835     memcpy(&haptic_config, &split_shmem->haptic_sync.haptic_config, sizeof(haptic_config_t));
    836 
    837     if (split_shmem->haptic_sync.haptic_play != 0xFF) {
    838         haptic_set_mode(split_shmem->haptic_sync.haptic_play);
    839         haptic_play();
    840     }
    841 }
    842 
    843 // clang-format off
    844 #    define TRANSACTIONS_HAPTIC_MASTER() TRANSACTION_HANDLER_MASTER(haptic)
    845 #    define TRANSACTIONS_HAPTIC_SLAVE() TRANSACTION_HANDLER_SLAVE(haptic)
    846 #    define TRANSACTIONS_HAPTIC_REGISTRATIONS [PUT_HAPTIC] = trans_initiator2target_initializer(haptic_sync),
    847 // clang-format on
    848 
    849 #else // defined(HAPTIC_ENABLE) && defined(SPLIT_HAPTIC_ENABLE)
    850 
    851 #    define TRANSACTIONS_HAPTIC_MASTER()
    852 #    define TRANSACTIONS_HAPTIC_SLAVE()
    853 #    define TRANSACTIONS_HAPTIC_REGISTRATIONS
    854 
    855 #endif // defined(HAPTIC_ENABLE) && defined(SPLIT_HAPTIC_ENABLE)
    856 
    857 #if defined(SPLIT_ACTIVITY_ENABLE)
    858 
    859 static bool activity_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    860     static uint32_t             last_update = 0;
    861     split_slave_activity_sync_t activity_sync;
    862     activity_sync.matrix_timestamp          = last_matrix_activity_time();
    863     activity_sync.encoder_timestamp         = last_encoder_activity_time();
    864     activity_sync.pointing_device_timestamp = last_pointing_device_activity_time();
    865     return send_if_data_mismatch(PUT_ACTIVITY, &last_update, &activity_sync, &split_shmem->activity_sync, sizeof(activity_sync));
    866 }
    867 
    868 static void activity_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    869     set_activity_timestamps(split_shmem->activity_sync.matrix_timestamp, split_shmem->activity_sync.encoder_timestamp, split_shmem->activity_sync.pointing_device_timestamp);
    870 }
    871 
    872 // clang-format off
    873 #    define TRANSACTIONS_ACTIVITY_MASTER() TRANSACTION_HANDLER_MASTER(activity)
    874 #    define TRANSACTIONS_ACTIVITY_SLAVE() TRANSACTION_HANDLER_SLAVE_AUTOLOCK(activity)
    875 #    define TRANSACTIONS_ACTIVITY_REGISTRATIONS [PUT_ACTIVITY] = trans_initiator2target_initializer(activity_sync),
    876 // clang-format on
    877 
    878 #else // defined(SPLIT_ACTIVITY_ENABLE)
    879 
    880 #    define TRANSACTIONS_ACTIVITY_MASTER()
    881 #    define TRANSACTIONS_ACTIVITY_SLAVE()
    882 #    define TRANSACTIONS_ACTIVITY_REGISTRATIONS
    883 
    884 #endif // defined(SPLIT_ACTIVITY_ENABLE)
    885 
    886 ////////////////////////////////////////////////////
    887 // Detected OS
    888 
    889 #if defined(OS_DETECTION_ENABLE) && defined(SPLIT_DETECTED_OS_ENABLE)
    890 
    891 static bool detected_os_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    892     static uint32_t last_detected_os_update = 0;
    893     os_variant_t    detected_os             = detected_host_os();
    894     bool            okay                    = send_if_condition(PUT_DETECTED_OS, &last_detected_os_update, (detected_os != split_shmem->detected_os), &detected_os, sizeof(os_variant_t));
    895     return okay;
    896 }
    897 
    898 static void detected_os_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    899     slave_update_detected_host_os(split_shmem->detected_os);
    900 }
    901 
    902 #    define TRANSACTIONS_DETECTED_OS_MASTER() TRANSACTION_HANDLER_MASTER(detected_os)
    903 #    define TRANSACTIONS_DETECTED_OS_SLAVE() TRANSACTION_HANDLER_SLAVE_AUTOLOCK(detected_os)
    904 #    define TRANSACTIONS_DETECTED_OS_REGISTRATIONS [PUT_DETECTED_OS] = trans_initiator2target_initializer(detected_os),
    905 
    906 #else // defined(OS_DETECTION_ENABLE) && defined(SPLIT_DETECTED_OS_ENABLE)
    907 
    908 #    define TRANSACTIONS_DETECTED_OS_MASTER()
    909 #    define TRANSACTIONS_DETECTED_OS_SLAVE()
    910 #    define TRANSACTIONS_DETECTED_OS_REGISTRATIONS
    911 
    912 #endif // defined(OS_DETECTION_ENABLE) && defined(SPLIT_DETECTED_OS_ENABLE)
    913 
    914 ////////////////////////////////////////////////////
    915 
    916 split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS] = {
    917     // Set defaults
    918     [0 ...(NUM_TOTAL_TRANSACTIONS - 1)] = {0, 0, 0, 0, 0},
    919 
    920 #ifdef USE_I2C
    921     [I2C_EXECUTE_CALLBACK] = trans_initiator2target_initializer(transaction_id),
    922 #endif // USE_I2C
    923 
    924     // clang-format off
    925     TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS
    926     TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS
    927     TRANSACTIONS_ENCODERS_REGISTRATIONS
    928     TRANSACTIONS_SYNC_TIMER_REGISTRATIONS
    929     TRANSACTIONS_LAYER_STATE_REGISTRATIONS
    930     TRANSACTIONS_LED_STATE_REGISTRATIONS
    931     TRANSACTIONS_MODS_REGISTRATIONS
    932     TRANSACTIONS_BACKLIGHT_REGISTRATIONS
    933     TRANSACTIONS_RGBLIGHT_REGISTRATIONS
    934     TRANSACTIONS_LED_MATRIX_REGISTRATIONS
    935     TRANSACTIONS_RGB_MATRIX_REGISTRATIONS
    936     TRANSACTIONS_WPM_REGISTRATIONS
    937     TRANSACTIONS_OLED_REGISTRATIONS
    938     TRANSACTIONS_ST7565_REGISTRATIONS
    939     TRANSACTIONS_POINTING_REGISTRATIONS
    940     TRANSACTIONS_WATCHDOG_REGISTRATIONS
    941     TRANSACTIONS_HAPTIC_REGISTRATIONS
    942     TRANSACTIONS_ACTIVITY_REGISTRATIONS
    943     TRANSACTIONS_DETECTED_OS_REGISTRATIONS
    944 // clang-format on
    945 
    946 #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
    947         [PUT_RPC_INFO]  = trans_initiator2target_initializer_cb(rpc_info, slave_rpc_info_callback),
    948     [PUT_RPC_REQ_DATA]  = trans_initiator2target_initializer(rpc_m2s_buffer),
    949     [EXECUTE_RPC]       = trans_initiator2target_initializer_cb(rpc_info.payload.transaction_id, slave_rpc_exec_callback),
    950     [GET_RPC_RESP_DATA] = trans_target2initiator_initializer(rpc_s2m_buffer),
    951 #endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
    952 };
    953 
    954 bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    955     TRANSACTIONS_SLAVE_MATRIX_MASTER();
    956     TRANSACTIONS_MASTER_MATRIX_MASTER();
    957     TRANSACTIONS_ENCODERS_MASTER();
    958     TRANSACTIONS_SYNC_TIMER_MASTER();
    959     TRANSACTIONS_LAYER_STATE_MASTER();
    960     TRANSACTIONS_LED_STATE_MASTER();
    961     TRANSACTIONS_MODS_MASTER();
    962     TRANSACTIONS_BACKLIGHT_MASTER();
    963     TRANSACTIONS_RGBLIGHT_MASTER();
    964     TRANSACTIONS_LED_MATRIX_MASTER();
    965     TRANSACTIONS_RGB_MATRIX_MASTER();
    966     TRANSACTIONS_WPM_MASTER();
    967     TRANSACTIONS_OLED_MASTER();
    968     TRANSACTIONS_ST7565_MASTER();
    969     TRANSACTIONS_POINTING_MASTER();
    970     TRANSACTIONS_WATCHDOG_MASTER();
    971     TRANSACTIONS_HAPTIC_MASTER();
    972     TRANSACTIONS_ACTIVITY_MASTER();
    973     TRANSACTIONS_DETECTED_OS_MASTER();
    974     return true;
    975 }
    976 
    977 void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    978     TRANSACTIONS_SLAVE_MATRIX_SLAVE();
    979     TRANSACTIONS_MASTER_MATRIX_SLAVE();
    980     TRANSACTIONS_ENCODERS_SLAVE();
    981     TRANSACTIONS_SYNC_TIMER_SLAVE();
    982     TRANSACTIONS_LAYER_STATE_SLAVE();
    983     TRANSACTIONS_LED_STATE_SLAVE();
    984     TRANSACTIONS_MODS_SLAVE();
    985     TRANSACTIONS_BACKLIGHT_SLAVE();
    986     TRANSACTIONS_RGBLIGHT_SLAVE();
    987     TRANSACTIONS_LED_MATRIX_SLAVE();
    988     TRANSACTIONS_RGB_MATRIX_SLAVE();
    989     TRANSACTIONS_WPM_SLAVE();
    990     TRANSACTIONS_OLED_SLAVE();
    991     TRANSACTIONS_ST7565_SLAVE();
    992     TRANSACTIONS_POINTING_SLAVE();
    993     TRANSACTIONS_WATCHDOG_SLAVE();
    994     TRANSACTIONS_HAPTIC_SLAVE();
    995     TRANSACTIONS_ACTIVITY_SLAVE();
    996     TRANSACTIONS_DETECTED_OS_SLAVE();
    997 }
    998 
    999 #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
   1000 
   1001 void transaction_register_rpc(int8_t transaction_id, slave_callback_t callback) {
   1002     // Prevent invoking RPC on QMK core sync data
   1003     if (transaction_id <= GET_RPC_RESP_DATA) return;
   1004 
   1005     // Set the callback
   1006     split_transaction_table[transaction_id].slave_callback          = callback;
   1007     split_transaction_table[transaction_id].initiator2target_offset = offsetof(split_shared_memory_t, rpc_m2s_buffer);
   1008     split_transaction_table[transaction_id].target2initiator_offset = offsetof(split_shared_memory_t, rpc_s2m_buffer);
   1009 }
   1010 
   1011 bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
   1012     // Prevent transaction attempts while transport is disconnected
   1013     if (!is_transport_connected()) {
   1014         return false;
   1015     }
   1016     // Prevent invoking RPC on QMK core sync data
   1017     if (transaction_id <= GET_RPC_RESP_DATA) return false;
   1018     // Prevent sizing issues
   1019     if (initiator2target_buffer_size > RPC_M2S_BUFFER_SIZE) return false;
   1020     if (target2initiator_buffer_size > RPC_S2M_BUFFER_SIZE) return false;
   1021 
   1022     // Prepare the metadata block
   1023     rpc_sync_info_t info = {.payload = {.transaction_id = transaction_id, .m2s_length = initiator2target_buffer_size, .s2m_length = target2initiator_buffer_size}};
   1024     info.checksum        = crc8(&info.payload, sizeof(info.payload));
   1025 
   1026     // Make sure the local side knows that we're not sending the full block of data
   1027     split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size  = initiator2target_buffer_size;
   1028     split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = target2initiator_buffer_size;
   1029 
   1030     // Run through the sequence:
   1031     // * set the transaction ID and lengths
   1032     // * send the request data
   1033     // * execute RPC callback
   1034     // * retrieve the response data
   1035     if (!transport_write(PUT_RPC_INFO, &info, sizeof(info))) {
   1036         return false;
   1037     }
   1038     if (!transport_write(PUT_RPC_REQ_DATA, initiator2target_buffer, initiator2target_buffer_size)) {
   1039         return false;
   1040     }
   1041     if (!transport_write(EXECUTE_RPC, &transaction_id, sizeof(transaction_id))) {
   1042         return false;
   1043     }
   1044     if (!transport_read(GET_RPC_RESP_DATA, target2initiator_buffer, target2initiator_buffer_size)) {
   1045         return false;
   1046     }
   1047     return true;
   1048 }
   1049 
   1050 void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
   1051     // The RPC info block contains the intended transaction ID, as well as the sizes for both inbound and outbound data.
   1052     // Ignore the args -- the `split_shmem` already has the info, we just need to act upon it.
   1053     // We must keep the `split_transaction_table` non-const, so that it is able to be modified at runtime.
   1054 
   1055     split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size  = split_shmem->rpc_info.payload.m2s_length;
   1056     split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = split_shmem->rpc_info.payload.s2m_length;
   1057 }
   1058 
   1059 void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
   1060     // We can assume that the buffer lengths are correctly set, now, given that sequentially the rpc_info callback was already executed.
   1061     // Go through the rpc_info and execute _that_ transaction's callback, with the scratch buffers as inputs.
   1062     // As a safety precaution we check that the received payload matches its checksum first.
   1063     if (crc8(&split_shmem->rpc_info.payload, sizeof(split_shmem->rpc_info.payload)) != split_shmem->rpc_info.checksum) {
   1064         return;
   1065     }
   1066 
   1067     int8_t transaction_id = split_shmem->rpc_info.payload.transaction_id;
   1068     if (transaction_id < NUM_TOTAL_TRANSACTIONS) {
   1069         split_transaction_desc_t *trans = &split_transaction_table[transaction_id];
   1070         if (trans->slave_callback) {
   1071             trans->slave_callback(split_shmem->rpc_info.payload.m2s_length, split_shmem->rpc_m2s_buffer, split_shmem->rpc_info.payload.s2m_length, split_shmem->rpc_s2m_buffer);
   1072         }
   1073     }
   1074 }
   1075 
   1076 #endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)