Parent directory

transactions.c

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