summary refs log tree commit diff
path: root/quantum/split_common
diff options
context:
space:
mode:
Diffstat (limited to 'quantum/split_common')
-rw-r--r--quantum/split_common/matrix.c309
-rw-r--r--quantum/split_common/post_config.h9
-rw-r--r--quantum/split_common/split_util.c57
-rw-r--r--quantum/split_common/split_util.h5
-rw-r--r--quantum/split_common/transaction_id_define.h102
-rw-r--r--quantum/split_common/transactions.c723
-rw-r--r--quantum/split_common/transactions.h54
-rw-r--r--quantum/split_common/transport.c484
-rw-r--r--quantum/split_common/transport.h173
9 files changed, 1188 insertions, 728 deletions
diff --git a/quantum/split_common/matrix.c b/quantum/split_common/matrix.c
deleted file mode 100644
index 039e7d9773..0000000000
--- a/quantum/split_common/matrix.c
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
-Copyright 2012 Jun Wako <wakojun@gmail.com>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 2 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-#include <stdint.h>
-#include <stdbool.h>
-#include "util.h"
-#include "matrix.h"
-#include "debounce.h"
-#include "quantum.h"
-#include "split_util.h"
-#include "config.h"
-#include "transport.h"
-
-#define ERROR_DISCONNECT_COUNT 5
-
-#define ROWS_PER_HAND (MATRIX_ROWS / 2)
-
-#ifdef DIRECT_PINS
-static pin_t direct_pins[MATRIX_ROWS][MATRIX_COLS] = DIRECT_PINS;
-#elif (DIODE_DIRECTION == ROW2COL) || (DIODE_DIRECTION == COL2ROW)
-static pin_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS;
-static pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS;
-#endif
-
-/* matrix state(1:on, 0:off) */
-extern matrix_row_t raw_matrix[MATRIX_ROWS];  // raw values
-extern matrix_row_t matrix[MATRIX_ROWS];      // debounced values
-
-// row offsets for each hand
-uint8_t thisHand, thatHand;
-
-// user-defined overridable functions
-__attribute__((weak)) void matrix_slave_scan_kb(void) { matrix_slave_scan_user(); }
-__attribute__((weak)) void matrix_slave_scan_user(void) {}
-
-static inline void setPinOutput_writeLow(pin_t pin) {
-    ATOMIC_BLOCK_FORCEON {
-        setPinOutput(pin);
-        writePinLow(pin);
-    }
-}
-
-static inline void setPinInputHigh_atomic(pin_t pin) {
-    ATOMIC_BLOCK_FORCEON { setPinInputHigh(pin); }
-}
-
-// matrix code
-
-#ifdef DIRECT_PINS
-
-static void init_pins(void) {
-    for (int row = 0; row < MATRIX_ROWS; row++) {
-        for (int col = 0; col < MATRIX_COLS; col++) {
-            pin_t pin = direct_pins[row][col];
-            if (pin != NO_PIN) {
-                setPinInputHigh(pin);
-            }
-        }
-    }
-}
-
-static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) {
-    // Start with a clear matrix row
-    matrix_row_t current_row_value = 0;
-
-    for (uint8_t col_index = 0; col_index < MATRIX_COLS; col_index++) {
-        pin_t pin = direct_pins[current_row][col_index];
-        if (pin != NO_PIN) {
-            current_row_value |= readPin(pin) ? 0 : (MATRIX_ROW_SHIFTER << col_index);
-        }
-    }
-
-    // If the row has changed, store the row and return the changed flag.
-    if (current_matrix[current_row] != current_row_value) {
-        current_matrix[current_row] = current_row_value;
-        return true;
-    }
-    return false;
-}
-
-#elif defined(DIODE_DIRECTION)
-#    if (DIODE_DIRECTION == COL2ROW)
-
-static void select_row(uint8_t row) { setPinOutput_writeLow(row_pins[row]); }
-
-static void unselect_row(uint8_t row) { setPinInputHigh_atomic(row_pins[row]); }
-
-static void unselect_rows(void) {
-    for (uint8_t x = 0; x < ROWS_PER_HAND; x++) {
-        setPinInputHigh_atomic(row_pins[x]);
-    }
-}
-
-static void init_pins(void) {
-    unselect_rows();
-    for (uint8_t x = 0; x < MATRIX_COLS; x++) {
-        setPinInputHigh_atomic(col_pins[x]);
-    }
-}
-
-static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) {
-    // Start with a clear matrix row
-    matrix_row_t current_row_value = 0;
-
-    // Select row
-    select_row(current_row);
-    matrix_output_select_delay();
-
-    // For each col...
-    for (uint8_t col_index = 0; col_index < MATRIX_COLS; col_index++) {
-        // Select the col pin to read (active low)
-        uint8_t pin_state = readPin(col_pins[col_index]);
-
-        // Populate the matrix row with the state of the col pin
-        current_row_value |= pin_state ? 0 : (MATRIX_ROW_SHIFTER << col_index);
-    }
-
-    // Unselect row
-    unselect_row(current_row);
-    matrix_output_unselect_delay();  // wait for all Col signals to go HIGH
-
-    // If the row has changed, store the row and return the changed flag.
-    if (current_matrix[current_row] != current_row_value) {
-        current_matrix[current_row] = current_row_value;
-        return true;
-    }
-    return false;
-}
-
-#    elif (DIODE_DIRECTION == ROW2COL)
-
-static void select_col(uint8_t col) { setPinOutput_writeLow(col_pins[col]); }
-
-static void unselect_col(uint8_t col) { setPinInputHigh_atomic(col_pins[col]); }
-
-static void unselect_cols(void) {
-    for (uint8_t x = 0; x < MATRIX_COLS; x++) {
-        setPinInputHigh_atomic(col_pins[x]);
-    }
-}
-
-static void init_pins(void) {
-    unselect_cols();
-    for (uint8_t x = 0; x < ROWS_PER_HAND; x++) {
-        setPinInputHigh_atomic(row_pins[x]);
-    }
-}
-
-static bool read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) {
-    bool matrix_changed = false;
-
-    // Select col
-    select_col(current_col);
-    matrix_output_select_delay();
-
-    // For each row...
-    for (uint8_t row_index = 0; row_index < ROWS_PER_HAND; row_index++) {
-        // Store last value of row prior to reading
-        matrix_row_t last_row_value    = current_matrix[row_index];
-        matrix_row_t current_row_value = last_row_value;
-
-        // Check row pin state
-        if (readPin(row_pins[row_index]) == 0) {
-            // Pin LO, set col bit
-            current_row_value |= (MATRIX_ROW_SHIFTER << current_col);
-        } else {
-            // Pin HI, clear col bit
-            current_row_value &= ~(MATRIX_ROW_SHIFTER << current_col);
-        }
-
-        // Determine if the matrix changed state
-        if ((last_row_value != current_row_value)) {
-            matrix_changed |= true;
-            current_matrix[row_index] = current_row_value;
-        }
-    }
-
-    // Unselect col
-    unselect_col(current_col);
-    matrix_output_unselect_delay();  // wait for all Row signals to go HIGH
-
-    return matrix_changed;
-}
-
-#    else
-#        error DIODE_DIRECTION must be one of COL2ROW or ROW2COL!
-#    endif
-#else
-#    error DIODE_DIRECTION is not defined!
-#endif
-
-void matrix_init(void) {
-    split_pre_init();
-
-    // Set pinout for right half if pinout for that half is defined
-    if (!isLeftHand) {
-#ifdef DIRECT_PINS_RIGHT
-        const pin_t direct_pins_right[MATRIX_ROWS][MATRIX_COLS] = DIRECT_PINS_RIGHT;
-        for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
-            for (uint8_t j = 0; j < MATRIX_COLS; j++) {
-                direct_pins[i][j] = direct_pins_right[i][j];
-            }
-        }
-#endif
-#ifdef MATRIX_ROW_PINS_RIGHT
-        const pin_t row_pins_right[MATRIX_ROWS] = MATRIX_ROW_PINS_RIGHT;
-        for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
-            row_pins[i] = row_pins_right[i];
-        }
-#endif
-#ifdef MATRIX_COL_PINS_RIGHT
-        const pin_t col_pins_right[MATRIX_COLS] = MATRIX_COL_PINS_RIGHT;
-        for (uint8_t i = 0; i < MATRIX_COLS; i++) {
-            col_pins[i] = col_pins_right[i];
-        }
-#endif
-    }
-
-    thisHand = isLeftHand ? 0 : (ROWS_PER_HAND);
-    thatHand = ROWS_PER_HAND - thisHand;
-
-    // initialize key pins
-    init_pins();
-
-    // initialize matrix state: all keys off
-    for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
-        raw_matrix[i] = 0;
-        matrix[i]     = 0;
-    }
-
-    debounce_init(ROWS_PER_HAND);
-
-    matrix_init_quantum();
-
-    split_post_init();
-}
-
-bool matrix_post_scan(void) {
-    bool changed = false;
-    if (is_keyboard_master()) {
-        static uint8_t error_count;
-
-        matrix_row_t slave_matrix[ROWS_PER_HAND] = {0};
-        if (!transport_master(matrix + thisHand, slave_matrix)) {
-            error_count++;
-
-            if (error_count > ERROR_DISCONNECT_COUNT) {
-                // reset other half if disconnected
-                for (int i = 0; i < ROWS_PER_HAND; ++i) {
-                    matrix[thatHand + i] = 0;
-                    slave_matrix[i]      = 0;
-                }
-
-                changed = true;
-            }
-        } else {
-            error_count = 0;
-
-            for (int i = 0; i < ROWS_PER_HAND; ++i) {
-                if (matrix[thatHand + i] != slave_matrix[i]) {
-                    matrix[thatHand + i] = slave_matrix[i];
-                    changed              = true;
-                }
-            }
-        }
-
-        matrix_scan_quantum();
-    } else {
-        transport_slave(matrix + thatHand, matrix + thisHand);
-
-        matrix_slave_scan_kb();
-    }
-
-    return changed;
-}
-
-uint8_t matrix_scan(void) {
-    bool local_changed = false;
-
-#if defined(DIRECT_PINS) || (DIODE_DIRECTION == COL2ROW)
-    // Set row, read cols
-    for (uint8_t current_row = 0; current_row < ROWS_PER_HAND; current_row++) {
-        local_changed |= read_cols_on_row(raw_matrix, current_row);
-    }
-#elif (DIODE_DIRECTION == ROW2COL)
-    // Set col, read rows
-    for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++) {
-        local_changed |= read_rows_on_col(raw_matrix, current_col);
-    }
-#endif
-
-    debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, local_changed);
-
-    bool remote_changed = matrix_post_scan();
-    return (uint8_t)(local_changed || remote_changed);
-}
diff --git a/quantum/split_common/post_config.h b/quantum/split_common/post_config.h
index 4ae1d52732..a4c0a1956b 100644
--- a/quantum/split_common/post_config.h
+++ b/quantum/split_common/post_config.h
@@ -7,13 +7,4 @@
 #    ifndef F_SCL
 #        define F_SCL 100000UL  // SCL frequency
 #    endif
-
-#else  // use serial
-// When using serial, the user must define RGBLIGHT_SPLIT explicitly
-//  in config.h as needed.
-//      see quantum/rgblight_post_config.h
-#    if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
-// When using serial and RGBLIGHT_SPLIT need separate transaction
-#        define SERIAL_USE_MULTI_TRANSACTION
-#    endif
 #endif
diff --git a/quantum/split_common/split_util.c b/quantum/split_common/split_util.c
index 9e75e19ce0..35f0a9d181 100644
--- a/quantum/split_common/split_util.c
+++ b/quantum/split_common/split_util.c
@@ -39,6 +39,21 @@
 #    define SPLIT_USB_TIMEOUT_POLL 10
 #endif
 
+// Max number of consecutive failed communications (one per scan cycle) before the communication is seen as disconnected.
+// Set to 0 to disable the disconnection check altogether.
+#ifndef SPLIT_MAX_CONNECTION_ERRORS
+#    define SPLIT_MAX_CONNECTION_ERRORS 10
+#endif  // SPLIT_MAX_CONNECTION_ERRORS
+
+// How long (in milliseconds) to block all connection attempts after the communication has been flagged as disconnected.
+// One communication attempt will be allowed everytime this amount of time has passed since the last attempt. If that attempt succeeds, the communication is seen as working again.
+// Set to 0 to disable communication throttling while disconnected
+#ifndef SPLIT_CONNECTION_CHECK_TIMEOUT
+#    define SPLIT_CONNECTION_CHECK_TIMEOUT 500
+#endif  // SPLIT_CONNECTION_CHECK_TIMEOUT
+
+static uint8_t connection_errors = 0;
+
 volatile bool isLeftHand = true;
 
 #if defined(SPLIT_USB_DETECT)
@@ -77,7 +92,11 @@ __attribute__((weak)) bool is_keyboard_left(void) {
 #if defined(SPLIT_HAND_PIN)
     // Test pin SPLIT_HAND_PIN for High/Low, if low it's right hand
     setPinInput(SPLIT_HAND_PIN);
+#    ifdef SPLIT_HAND_PIN_LOW_IS_LEFT
+    return !readPin(SPLIT_HAND_PIN);
+#    else
     return readPin(SPLIT_HAND_PIN);
+#    endif
 #elif defined(SPLIT_HAND_MATRIX_GRID)
 #    ifdef SPLIT_HAND_MATRIX_GRID_LOW_IS_RIGHT
     return peek_matrix_intersection(SPLIT_HAND_MATRIX_GRID);
@@ -102,7 +121,7 @@ __attribute__((weak)) bool is_keyboard_master(void) {
 
         // Avoid NO_USB_STARTUP_CHECK - Disable USB as the previous checks seem to enable it somehow
         if (usbstate == SLAVE) {
-            usb_disable();
+            usb_disconnect();
         }
     }
 
@@ -138,3 +157,39 @@ void split_post_init(void) {
         transport_slave_init();
     }
 }
+
+bool is_transport_connected(void) { return connection_errors < SPLIT_MAX_CONNECTION_ERRORS; }
+
+bool transport_master_if_connected(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+#if SPLIT_MAX_CONNECTION_ERRORS > 0 && SPLIT_CONNECTION_CHECK_TIMEOUT > 0
+    // Throttle transaction attempts if target doesn't seem to be connected
+    // Without this, a solo half becomes unusable due to constant read timeouts
+    static uint16_t connection_check_timer = 0;
+    const bool      is_disconnected        = !is_transport_connected();
+    if (is_disconnected && timer_elapsed(connection_check_timer) < SPLIT_CONNECTION_CHECK_TIMEOUT) {
+        return false;
+    }
+#endif  // SPLIT_MAX_CONNECTION_ERRORS > 0 && SPLIT_CONNECTION_CHECK_TIMEOUT > 0
+
+    __attribute__((unused)) bool okay = transport_master(master_matrix, slave_matrix);
+#if SPLIT_MAX_CONNECTION_ERRORS > 0
+    if (!okay) {
+        if (connection_errors < UINT8_MAX) {
+            connection_errors++;
+        }
+#    if SPLIT_CONNECTION_CHECK_TIMEOUT > 0
+        bool connected = is_transport_connected();
+        if (!connected) {
+            connection_check_timer = timer_read();
+            dprintln("Target disconnected, throttling connection attempts");
+        }
+        return connected;
+    } else if (is_disconnected) {
+        dprintln("Target connected");
+#    endif  // SPLIT_CONNECTION_CHECK_TIMEOUT > 0
+    }
+
+    connection_errors = 0;
+#endif  // SPLIT_MAX_CONNECTION_ERRORS > 0
+    return true;
+}
diff --git a/quantum/split_common/split_util.h b/quantum/split_common/split_util.h
index a4c12519e0..ef72043bb7 100644
--- a/quantum/split_common/split_util.h
+++ b/quantum/split_common/split_util.h
@@ -5,8 +5,13 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#include "matrix.h"
+
 extern volatile bool isLeftHand;
 
 void matrix_master_OLED_init(void);
 void split_pre_init(void);
 void split_post_init(void);
+
+bool transport_master_if_connected(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]);
+bool is_transport_connected(void);
diff --git a/quantum/split_common/transaction_id_define.h b/quantum/split_common/transaction_id_define.h
new file mode 100644
index 0000000000..535bc21aea
--- /dev/null
+++ b/quantum/split_common/transaction_id_define.h
@@ -0,0 +1,102 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+enum serial_transaction_id {
+#ifdef USE_I2C
+    I2C_EXECUTE_CALLBACK,
+#endif  // USE_I2C
+
+    GET_SLAVE_MATRIX_CHECKSUM,
+    GET_SLAVE_MATRIX_DATA,
+
+#ifdef SPLIT_TRANSPORT_MIRROR
+    PUT_MASTER_MATRIX,
+#endif  // SPLIT_TRANSPORT_MIRROR
+
+#ifdef ENCODER_ENABLE
+    GET_ENCODERS_CHECKSUM,
+    GET_ENCODERS_DATA,
+#endif  // ENCODER_ENABLE
+
+#ifndef DISABLE_SYNC_TIMER
+    PUT_SYNC_TIMER,
+#endif  // DISABLE_SYNC_TIMER
+
+#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
+    PUT_LAYER_STATE,
+    PUT_DEFAULT_LAYER_STATE,
+#endif  // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
+
+#ifdef SPLIT_LED_STATE_ENABLE
+    PUT_LED_STATE,
+#endif  // SPLIT_LED_STATE_ENABLE
+
+#ifdef SPLIT_MODS_ENABLE
+    PUT_MODS,
+#endif  // SPLIT_MODS_ENABLE
+
+#ifdef BACKLIGHT_ENABLE
+    PUT_BACKLIGHT,
+#endif  // BACKLIGHT_ENABLE
+
+#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
+    PUT_RGBLIGHT,
+#endif  // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
+
+#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
+    PUT_LED_MATRIX,
+#endif  // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
+
+#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
+    PUT_RGB_MATRIX,
+#endif  // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
+
+#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
+    PUT_WPM,
+#endif  // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
+
+#if defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
+    PUT_OLED,
+#endif  // defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
+
+#if defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
+    PUT_ST7565,
+#endif  // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
+
+#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
+    PUT_RPC_INFO,
+    PUT_RPC_REQ_DATA,
+    EXECUTE_RPC,
+    GET_RPC_RESP_DATA,
+#endif  // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
+
+// keyboard-specific
+#ifdef SPLIT_TRANSACTION_IDS_KB
+    SPLIT_TRANSACTION_IDS_KB,
+#endif  // SPLIT_TRANSACTION_IDS_KB
+
+// user/keymap-specific
+#ifdef SPLIT_TRANSACTION_IDS_USER
+    SPLIT_TRANSACTION_IDS_USER,
+#endif  // SPLIT_TRANSACTION_IDS_USER
+
+    NUM_TOTAL_TRANSACTIONS
+};
+
+// Ensure we only use 5 bits for transaction
+_Static_assert(NUM_TOTAL_TRANSACTIONS <= (1 << 5), "Max number of usable transactions exceeded");
diff --git a/quantum/split_common/transactions.c b/quantum/split_common/transactions.c
new file mode 100644
index 0000000000..fd676f0729
--- /dev/null
+++ b/quantum/split_common/transactions.c
@@ -0,0 +1,723 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stddef.h>
+
+#include "crc.h"
+#include "debug.h"
+#include "matrix.h"
+#include "quantum.h"
+#include "transactions.h"
+#include "transport.h"
+#include "split_util.h"
+#include "transaction_id_define.h"
+
+#define SYNC_TIMER_OFFSET 2
+
+#ifndef FORCED_SYNC_THROTTLE_MS
+#    define FORCED_SYNC_THROTTLE_MS 100
+#endif  // FORCED_SYNC_THROTTLE_MS
+
+#define sizeof_member(type, member) sizeof(((type *)NULL)->member)
+
+#define trans_initiator2target_initializer_cb(member, cb) \
+    { &dummy, sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), 0, 0, cb }
+#define trans_initiator2target_initializer(member) trans_initiator2target_initializer_cb(member, NULL)
+
+#define trans_target2initiator_initializer_cb(member, cb) \
+    { &dummy, 0, 0, sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), cb }
+#define trans_target2initiator_initializer(member) trans_target2initiator_initializer_cb(member, NULL)
+
+#define transport_write(id, data, length)          transport_execute_transaction(id, data, length, NULL, 0)
+#define transport_read(id, data, length)           transport_execute_transaction(id, NULL, 0, data, length)
+
+#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
+// Forward-declare the RPC callback handlers
+void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
+void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
+#endif  // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
+
+////////////////////////////////////////////////////
+// Helpers
+
+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[])) {
+    int num_retries = is_transport_connected() ? 10 : 1;
+    for (int iter = 1; iter <= num_retries; ++iter) {
+        if (iter > 1) {
+            for (int i = 0; i < iter * iter; ++i) {
+                wait_us(10);
+            }
+        }
+        bool this_okay = true;
+        ATOMIC_BLOCK_FORCEON { this_okay = handler(master_matrix, slave_matrix); };
+        if (this_okay) return true;
+    }
+    dprintf("Failed to execute %s\n", prefix);
+    return false;
+}
+
+#define TRANSACTION_HANDLER_MASTER(prefix)                                                                              \
+    do {                                                                                                                \
+        if (!transaction_handler_master(master_matrix, slave_matrix, #prefix, &prefix##_handlers_master)) return false; \
+    } while (0)
+
+#define TRANSACTION_HANDLER_SLAVE(prefix)                                               \
+    do {                                                                                \
+        ATOMIC_BLOCK_FORCEON { prefix##_handlers_slave(master_matrix, slave_matrix); }; \
+    } while (0)
+
+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) {
+    uint8_t curr_checksum;
+    bool    okay = transport_read(trans_id_checksum, &curr_checksum, sizeof(curr_checksum));
+    if (okay && (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || curr_checksum != crc8(equiv_shmem, length))) {
+        okay &= transport_read(trans_id_retrieve, destination, length);
+        okay &= curr_checksum == crc8(equiv_shmem, length);
+        if (okay) {
+            *last_update = timer_read32();
+        }
+    } else {
+        memcpy(destination, equiv_shmem, length);
+    }
+    return okay;
+}
+
+inline static bool send_if_condition(int8_t trans_id, uint32_t *last_update, bool condition, void *source, size_t length) {
+    bool okay = true;
+    if (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || condition) {
+        okay &= transport_write(trans_id, source, length);
+        if (okay) {
+            *last_update = timer_read32();
+        }
+    }
+    return okay;
+}
+
+inline static bool send_if_data_mismatch(int8_t trans_id, uint32_t *last_update, void *source, const void *equiv_shmem, size_t length) {
+    // Just run a memcmp to compare the source and equivalent shmem location
+    return send_if_condition(trans_id, last_update, (memcmp(source, equiv_shmem, length) != 0), source, length);
+}
+
+////////////////////////////////////////////////////
+// Slave matrix
+
+static bool slave_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t     last_update                    = 0;
+    static matrix_row_t last_matrix[(MATRIX_ROWS) / 2] = {0};  // last successfully-read matrix, so we can replicate if there are checksum errors
+    matrix_row_t        temp_matrix[(MATRIX_ROWS) / 2];        // holding area while we test whether or not checksum is correct
+
+    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));
+    if (okay) {
+        // Checksum matches the received data, save as the last matrix state
+        memcpy(last_matrix, temp_matrix, sizeof(temp_matrix));
+    }
+    // Copy out the last-known-good matrix state to the slave matrix
+    memcpy(slave_matrix, last_matrix, sizeof(last_matrix));
+    return okay;
+}
+
+static void slave_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    memcpy(split_shmem->smatrix.matrix, slave_matrix, sizeof(split_shmem->smatrix.matrix));
+    split_shmem->smatrix.checksum = crc8(split_shmem->smatrix.matrix, sizeof(split_shmem->smatrix.matrix));
+}
+
+// clang-format off
+#define TRANSACTIONS_SLAVE_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(slave_matrix)
+#define TRANSACTIONS_SLAVE_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(slave_matrix)
+#define TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS \
+    [GET_SLAVE_MATRIX_CHECKSUM] = trans_target2initiator_initializer(smatrix.checksum), \
+    [GET_SLAVE_MATRIX_DATA]     = trans_target2initiator_initializer(smatrix.matrix),
+// clang-format on
+
+////////////////////////////////////////////////////
+// Master matrix
+
+#ifdef SPLIT_TRANSPORT_MIRROR
+
+static bool master_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t last_update = 0;
+    return send_if_data_mismatch(PUT_MASTER_MATRIX, &last_update, master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix));
+}
+
+static void master_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    // Always copy to the master matrix
+    memcpy(master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix));
+}
+
+#    define TRANSACTIONS_MASTER_MATRIX_MASTER()      TRANSACTION_HANDLER_MASTER(master_matrix)
+#    define TRANSACTIONS_MASTER_MATRIX_SLAVE()       TRANSACTION_HANDLER_SLAVE(master_matrix)
+#    define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS [PUT_MASTER_MATRIX] = trans_initiator2target_initializer(mmatrix.matrix),
+
+#else  // SPLIT_TRANSPORT_MIRROR
+
+#    define TRANSACTIONS_MASTER_MATRIX_MASTER()
+#    define TRANSACTIONS_MASTER_MATRIX_SLAVE()
+#    define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS
+
+#endif  // SPLIT_TRANSPORT_MIRROR
+
+////////////////////////////////////////////////////
+// Encoders
+
+#ifdef ENCODER_ENABLE
+
+static bool encoder_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t last_update = 0;
+    uint8_t         temp_state[NUMBER_OF_ENCODERS];
+
+    bool okay = read_if_checksum_mismatch(GET_ENCODERS_CHECKSUM, GET_ENCODERS_DATA, &last_update, temp_state, split_shmem->encoders.state, sizeof(temp_state));
+    if (okay) encoder_update_raw(temp_state);
+    return okay;
+}
+
+static void encoder_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    uint8_t encoder_state[NUMBER_OF_ENCODERS];
+    encoder_state_raw(encoder_state);
+    // Always prepare the encoder state for read.
+    memcpy(split_shmem->encoders.state, encoder_state, sizeof(encoder_state));
+    // Now update the checksum given that the encoders has been written to
+    split_shmem->encoders.checksum = crc8(encoder_state, sizeof(encoder_state));
+}
+
+// clang-format off
+#    define TRANSACTIONS_ENCODERS_MASTER() TRANSACTION_HANDLER_MASTER(encoder)
+#    define TRANSACTIONS_ENCODERS_SLAVE() TRANSACTION_HANDLER_SLAVE(encoder)
+#    define TRANSACTIONS_ENCODERS_REGISTRATIONS \
+    [GET_ENCODERS_CHECKSUM] = trans_target2initiator_initializer(encoders.checksum), \
+    [GET_ENCODERS_DATA]     = trans_target2initiator_initializer(encoders.state),
+// clang-format on
+
+#else  // ENCODER_ENABLE
+
+#    define TRANSACTIONS_ENCODERS_MASTER()
+#    define TRANSACTIONS_ENCODERS_SLAVE()
+#    define TRANSACTIONS_ENCODERS_REGISTRATIONS
+
+#endif  // ENCODER_ENABLE
+
+////////////////////////////////////////////////////
+// Sync timer
+
+#ifndef DISABLE_SYNC_TIMER
+
+static bool sync_timer_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t last_update = 0;
+
+    bool okay = true;
+    if (timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS) {
+        uint32_t sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET;
+        okay &= transport_write(PUT_SYNC_TIMER, &sync_timer, sizeof(sync_timer));
+        if (okay) {
+            last_update = timer_read32();
+        }
+    }
+    return okay;
+}
+
+static void sync_timer_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t last_sync_timer = 0;
+    if (last_sync_timer != split_shmem->sync_timer) {
+        last_sync_timer = split_shmem->sync_timer;
+        sync_timer_update(last_sync_timer);
+    }
+}
+
+#    define TRANSACTIONS_SYNC_TIMER_MASTER()      TRANSACTION_HANDLER_MASTER(sync_timer)
+#    define TRANSACTIONS_SYNC_TIMER_SLAVE()       TRANSACTION_HANDLER_SLAVE(sync_timer)
+#    define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS [PUT_SYNC_TIMER] = trans_initiator2target_initializer(sync_timer),
+
+#else  // DISABLE_SYNC_TIMER
+
+#    define TRANSACTIONS_SYNC_TIMER_MASTER()
+#    define TRANSACTIONS_SYNC_TIMER_SLAVE()
+#    define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS
+
+#endif  // DISABLE_SYNC_TIMER
+
+////////////////////////////////////////////////////
+// Layer state
+
+#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
+
+static bool layer_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t last_layer_state_update         = 0;
+    static uint32_t last_default_layer_state_update = 0;
+
+    bool okay = send_if_condition(PUT_LAYER_STATE, &last_layer_state_update, (layer_state != split_shmem->layers.layer_state), &layer_state, sizeof(layer_state));
+    if (okay) {
+        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));
+    }
+    return okay;
+}
+
+static void layer_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    layer_state         = split_shmem->layers.layer_state;
+    default_layer_state = split_shmem->layers.default_layer_state;
+}
+
+// clang-format off
+#    define TRANSACTIONS_LAYER_STATE_MASTER() TRANSACTION_HANDLER_MASTER(layer_state)
+#    define TRANSACTIONS_LAYER_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(layer_state)
+#    define TRANSACTIONS_LAYER_STATE_REGISTRATIONS \
+    [PUT_LAYER_STATE]         = trans_initiator2target_initializer(layers.layer_state), \
+    [PUT_DEFAULT_LAYER_STATE] = trans_initiator2target_initializer(layers.default_layer_state),
+// clang-format on
+
+#else  // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
+
+#    define TRANSACTIONS_LAYER_STATE_MASTER()
+#    define TRANSACTIONS_LAYER_STATE_SLAVE()
+#    define TRANSACTIONS_LAYER_STATE_REGISTRATIONS
+
+#endif  // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
+
+////////////////////////////////////////////////////
+// LED state
+
+#ifdef SPLIT_LED_STATE_ENABLE
+
+static bool led_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t last_update = 0;
+    uint8_t         led_state   = host_keyboard_leds();
+    return send_if_data_mismatch(PUT_LED_STATE, &last_update, &led_state, &split_shmem->led_state, sizeof(led_state));
+}
+
+static void led_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    void set_split_host_keyboard_leds(uint8_t led_state);
+    set_split_host_keyboard_leds(split_shmem->led_state);
+}
+
+#    define TRANSACTIONS_LED_STATE_MASTER()      TRANSACTION_HANDLER_MASTER(led_state)
+#    define TRANSACTIONS_LED_STATE_SLAVE()       TRANSACTION_HANDLER_SLAVE(led_state)
+#    define TRANSACTIONS_LED_STATE_REGISTRATIONS [PUT_LED_STATE] = trans_initiator2target_initializer(led_state),
+
+#else  // SPLIT_LED_STATE_ENABLE
+
+#    define TRANSACTIONS_LED_STATE_MASTER()
+#    define TRANSACTIONS_LED_STATE_SLAVE()
+#    define TRANSACTIONS_LED_STATE_REGISTRATIONS
+
+#endif  // SPLIT_LED_STATE_ENABLE
+
+////////////////////////////////////////////////////
+// Mods
+
+#ifdef SPLIT_MODS_ENABLE
+
+static bool mods_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t   last_update    = 0;
+    bool              mods_need_sync = timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS;
+    split_mods_sync_t new_mods;
+    new_mods.real_mods = get_mods();
+    if (!mods_need_sync && new_mods.real_mods != split_shmem->mods.real_mods) {
+        mods_need_sync = true;
+    }
+
+    new_mods.weak_mods = get_weak_mods();
+    if (!mods_need_sync && new_mods.weak_mods != split_shmem->mods.weak_mods) {
+        mods_need_sync = true;
+    }
+
+#    ifndef NO_ACTION_ONESHOT
+    new_mods.oneshot_mods = get_oneshot_mods();
+    if (!mods_need_sync && new_mods.oneshot_mods != split_shmem->mods.oneshot_mods) {
+        mods_need_sync = true;
+    }
+#    endif  // NO_ACTION_ONESHOT
+
+    bool okay = true;
+    if (mods_need_sync) {
+        okay &= transport_write(PUT_MODS, &new_mods, sizeof(new_mods));
+        if (okay) {
+            last_update = timer_read32();
+        }
+    }
+
+    return okay;
+}
+
+static void mods_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    set_mods(split_shmem->mods.real_mods);
+    set_weak_mods(split_shmem->mods.weak_mods);
+#    ifndef NO_ACTION_ONESHOT
+    set_oneshot_mods(split_shmem->mods.oneshot_mods);
+#    endif
+}
+
+#    define TRANSACTIONS_MODS_MASTER()      TRANSACTION_HANDLER_MASTER(mods)
+#    define TRANSACTIONS_MODS_SLAVE()       TRANSACTION_HANDLER_SLAVE(mods)
+#    define TRANSACTIONS_MODS_REGISTRATIONS [PUT_MODS] = trans_initiator2target_initializer(mods),
+
+#else  // SPLIT_MODS_ENABLE
+
+#    define TRANSACTIONS_MODS_MASTER()
+#    define TRANSACTIONS_MODS_SLAVE()
+#    define TRANSACTIONS_MODS_REGISTRATIONS
+
+#endif  // SPLIT_MODS_ENABLE
+
+////////////////////////////////////////////////////
+// Backlight
+
+#ifdef BACKLIGHT_ENABLE
+
+static bool backlight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t last_update = 0;
+    uint8_t         level       = is_backlight_enabled() ? get_backlight_level() : 0;
+    return send_if_condition(PUT_BACKLIGHT, &last_update, (level != split_shmem->backlight_level), &level, sizeof(level));
+}
+
+static void backlight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { backlight_set(split_shmem->backlight_level); }
+
+#    define TRANSACTIONS_BACKLIGHT_MASTER()      TRANSACTION_HANDLER_MASTER(backlight)
+#    define TRANSACTIONS_BACKLIGHT_SLAVE()       TRANSACTION_HANDLER_SLAVE(backlight)
+#    define TRANSACTIONS_BACKLIGHT_REGISTRATIONS [PUT_BACKLIGHT] = trans_initiator2target_initializer(backlight_level),
+
+#else  // BACKLIGHT_ENABLE
+
+#    define TRANSACTIONS_BACKLIGHT_MASTER()
+#    define TRANSACTIONS_BACKLIGHT_SLAVE()
+#    define TRANSACTIONS_BACKLIGHT_REGISTRATIONS
+
+#endif  // BACKLIGHT_ENABLE
+
+////////////////////////////////////////////////////
+// RGBLIGHT
+
+#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
+
+static bool rgblight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t     last_update = 0;
+    rgblight_syncinfo_t rgblight_sync;
+    rgblight_get_syncinfo(&rgblight_sync);
+    if (send_if_condition(PUT_RGBLIGHT, &last_update, (rgblight_sync.status.change_flags != 0), &rgblight_sync, sizeof(rgblight_sync))) {
+        rgblight_clear_change_flags();
+    } else {
+        return false;
+    }
+    return true;
+}
+
+static void rgblight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    // Update the RGB with the new data
+    if (split_shmem->rgblight_sync.status.change_flags != 0) {
+        rgblight_update_sync(&split_shmem->rgblight_sync, false);
+        split_shmem->rgblight_sync.status.change_flags = 0;
+    }
+}
+
+#    define TRANSACTIONS_RGBLIGHT_MASTER()      TRANSACTION_HANDLER_MASTER(rgblight)
+#    define TRANSACTIONS_RGBLIGHT_SLAVE()       TRANSACTION_HANDLER_SLAVE(rgblight)
+#    define TRANSACTIONS_RGBLIGHT_REGISTRATIONS [PUT_RGBLIGHT] = trans_initiator2target_initializer(rgblight_sync),
+
+#else  // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
+
+#    define TRANSACTIONS_RGBLIGHT_MASTER()
+#    define TRANSACTIONS_RGBLIGHT_SLAVE()
+#    define TRANSACTIONS_RGBLIGHT_REGISTRATIONS
+
+#endif  // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
+
+////////////////////////////////////////////////////
+// LED Matrix
+
+#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
+
+static bool led_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t   last_update = 0;
+    led_matrix_sync_t led_matrix_sync;
+    memcpy(&led_matrix_sync.led_matrix, &led_matrix_eeconfig, sizeof(led_eeconfig_t));
+    led_matrix_sync.led_suspend_state = led_matrix_get_suspend_state();
+    return send_if_data_mismatch(PUT_LED_MATRIX, &last_update, &led_matrix_sync, &split_shmem->led_matrix_sync, sizeof(led_matrix_sync));
+}
+
+static void led_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    memcpy(&led_matrix_eeconfig, &split_shmem->led_matrix_sync.led_matrix, sizeof(led_eeconfig_t));
+    led_matrix_set_suspend_state(split_shmem->led_matrix_sync.led_suspend_state);
+}
+
+#    define TRANSACTIONS_LED_MATRIX_MASTER()      TRANSACTION_HANDLER_MASTER(led_matrix)
+#    define TRANSACTIONS_LED_MATRIX_SLAVE()       TRANSACTION_HANDLER_SLAVE(led_matrix)
+#    define TRANSACTIONS_LED_MATRIX_REGISTRATIONS [PUT_LED_MATRIX] = trans_initiator2target_initializer(led_matrix_sync),
+
+#else  // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
+
+#    define TRANSACTIONS_LED_MATRIX_MASTER()
+#    define TRANSACTIONS_LED_MATRIX_SLAVE()
+#    define TRANSACTIONS_LED_MATRIX_REGISTRATIONS
+
+#endif  // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
+
+////////////////////////////////////////////////////
+// RGB Matrix
+
+#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
+
+static bool rgb_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t   last_update = 0;
+    rgb_matrix_sync_t rgb_matrix_sync;
+    memcpy(&rgb_matrix_sync.rgb_matrix, &rgb_matrix_config, sizeof(rgb_config_t));
+    rgb_matrix_sync.rgb_suspend_state = rgb_matrix_get_suspend_state();
+    return send_if_data_mismatch(PUT_RGB_MATRIX, &last_update, &rgb_matrix_sync, &split_shmem->rgb_matrix_sync, sizeof(rgb_matrix_sync));
+}
+
+static void rgb_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    memcpy(&rgb_matrix_config, &split_shmem->rgb_matrix_sync.rgb_matrix, sizeof(rgb_config_t));
+    rgb_matrix_set_suspend_state(split_shmem->rgb_matrix_sync.rgb_suspend_state);
+}
+
+#    define TRANSACTIONS_RGB_MATRIX_MASTER()      TRANSACTION_HANDLER_MASTER(rgb_matrix)
+#    define TRANSACTIONS_RGB_MATRIX_SLAVE()       TRANSACTION_HANDLER_SLAVE(rgb_matrix)
+#    define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS [PUT_RGB_MATRIX] = trans_initiator2target_initializer(rgb_matrix_sync),
+
+#else  // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
+
+#    define TRANSACTIONS_RGB_MATRIX_MASTER()
+#    define TRANSACTIONS_RGB_MATRIX_SLAVE()
+#    define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS
+
+#endif  // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
+
+////////////////////////////////////////////////////
+// WPM
+
+#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
+
+static bool wpm_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t last_update = 0;
+    uint8_t         current_wpm = get_current_wpm();
+    return send_if_condition(PUT_WPM, &last_update, (current_wpm != split_shmem->current_wpm), &current_wpm, sizeof(current_wpm));
+}
+
+static void wpm_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { set_current_wpm(split_shmem->current_wpm); }
+
+#    define TRANSACTIONS_WPM_MASTER()      TRANSACTION_HANDLER_MASTER(wpm)
+#    define TRANSACTIONS_WPM_SLAVE()       TRANSACTION_HANDLER_SLAVE(wpm)
+#    define TRANSACTIONS_WPM_REGISTRATIONS [PUT_WPM] = trans_initiator2target_initializer(current_wpm),
+
+#else  // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
+
+#    define TRANSACTIONS_WPM_MASTER()
+#    define TRANSACTIONS_WPM_SLAVE()
+#    define TRANSACTIONS_WPM_REGISTRATIONS
+
+#endif  // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
+
+////////////////////////////////////////////////////
+// OLED
+
+#if defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
+
+static bool oled_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t last_update        = 0;
+    bool            current_oled_state = is_oled_on();
+    return send_if_condition(PUT_OLED, &last_update, (current_oled_state != split_shmem->current_oled_state), &current_oled_state, sizeof(current_oled_state));
+}
+
+static void oled_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    if (split_shmem->current_oled_state) {
+        oled_on();
+    } else {
+        oled_off();
+    }
+}
+
+#    define TRANSACTIONS_OLED_MASTER()      TRANSACTION_HANDLER_MASTER(oled)
+#    define TRANSACTIONS_OLED_SLAVE()       TRANSACTION_HANDLER_SLAVE(oled)
+#    define TRANSACTIONS_OLED_REGISTRATIONS [PUT_OLED] = trans_initiator2target_initializer(current_oled_state),
+
+#else  // defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
+
+#    define TRANSACTIONS_OLED_MASTER()
+#    define TRANSACTIONS_OLED_SLAVE()
+#    define TRANSACTIONS_OLED_REGISTRATIONS
+
+#endif  // defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
+
+////////////////////////////////////////////////////
+// ST7565
+
+#if defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
+
+static bool st7565_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    static uint32_t last_update          = 0;
+    bool            current_st7565_state = st7565_is_on();
+    return send_if_condition(PUT_ST7565, &last_update, (current_st7565_state != split_shmem->current_st7565_state), &current_st7565_state, sizeof(current_st7565_state));
+}
+
+static void st7565_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    if (split_shmem->current_st7565_state) {
+        st7565_on();
+    } else {
+        st7565_off();
+    }
+}
+
+#    define TRANSACTIONS_ST7565_MASTER()      TRANSACTION_HANDLER_MASTER(st7565)
+#    define TRANSACTIONS_ST7565_SLAVE()       TRANSACTION_HANDLER_SLAVE(st7565)
+#    define TRANSACTIONS_ST7565_REGISTRATIONS [PUT_ST7565] = trans_initiator2target_initializer(current_st7565_state),
+
+#else  // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
+
+#    define TRANSACTIONS_ST7565_MASTER()
+#    define TRANSACTIONS_ST7565_SLAVE()
+#    define TRANSACTIONS_ST7565_REGISTRATIONS
+
+#endif  // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
+
+////////////////////////////////////////////////////
+
+uint8_t                  dummy;
+split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS] = {
+    // Set defaults
+    [0 ...(NUM_TOTAL_TRANSACTIONS - 1)] = {NULL, 0, 0, 0, 0, 0},
+
+#ifdef USE_I2C
+    [I2C_EXECUTE_CALLBACK] = trans_initiator2target_initializer(transaction_id),
+#endif  // USE_I2C
+
+    // clang-format off
+    TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS
+    TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS
+    TRANSACTIONS_ENCODERS_REGISTRATIONS
+    TRANSACTIONS_SYNC_TIMER_REGISTRATIONS
+    TRANSACTIONS_LAYER_STATE_REGISTRATIONS
+    TRANSACTIONS_LED_STATE_REGISTRATIONS
+    TRANSACTIONS_MODS_REGISTRATIONS
+    TRANSACTIONS_BACKLIGHT_REGISTRATIONS
+    TRANSACTIONS_RGBLIGHT_REGISTRATIONS
+    TRANSACTIONS_LED_MATRIX_REGISTRATIONS
+    TRANSACTIONS_RGB_MATRIX_REGISTRATIONS
+    TRANSACTIONS_WPM_REGISTRATIONS
+    TRANSACTIONS_OLED_REGISTRATIONS
+    TRANSACTIONS_ST7565_REGISTRATIONS
+// clang-format on
+
+#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
+        [PUT_RPC_INFO]  = trans_initiator2target_initializer_cb(rpc_info, slave_rpc_info_callback),
+    [PUT_RPC_REQ_DATA]  = trans_initiator2target_initializer(rpc_m2s_buffer),
+    [EXECUTE_RPC]       = trans_initiator2target_initializer_cb(rpc_info.transaction_id, slave_rpc_exec_callback),
+    [GET_RPC_RESP_DATA] = trans_target2initiator_initializer(rpc_s2m_buffer),
+#endif  // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
+};
+
+bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    TRANSACTIONS_SLAVE_MATRIX_MASTER();
+    TRANSACTIONS_MASTER_MATRIX_MASTER();
+    TRANSACTIONS_ENCODERS_MASTER();
+    TRANSACTIONS_SYNC_TIMER_MASTER();
+    TRANSACTIONS_LAYER_STATE_MASTER();
+    TRANSACTIONS_LED_STATE_MASTER();
+    TRANSACTIONS_MODS_MASTER();
+    TRANSACTIONS_BACKLIGHT_MASTER();
+    TRANSACTIONS_RGBLIGHT_MASTER();
+    TRANSACTIONS_LED_MATRIX_MASTER();
+    TRANSACTIONS_RGB_MATRIX_MASTER();
+    TRANSACTIONS_WPM_MASTER();
+    TRANSACTIONS_OLED_MASTER();
+    TRANSACTIONS_ST7565_MASTER();
+    return true;
+}
+
+void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+    TRANSACTIONS_SLAVE_MATRIX_SLAVE();
+    TRANSACTIONS_MASTER_MATRIX_SLAVE();
+    TRANSACTIONS_ENCODERS_SLAVE();
+    TRANSACTIONS_SYNC_TIMER_SLAVE();
+    TRANSACTIONS_LAYER_STATE_SLAVE();
+    TRANSACTIONS_LED_STATE_SLAVE();
+    TRANSACTIONS_MODS_SLAVE();
+    TRANSACTIONS_BACKLIGHT_SLAVE();
+    TRANSACTIONS_RGBLIGHT_SLAVE();
+    TRANSACTIONS_LED_MATRIX_SLAVE();
+    TRANSACTIONS_RGB_MATRIX_SLAVE();
+    TRANSACTIONS_WPM_SLAVE();
+    TRANSACTIONS_OLED_SLAVE();
+    TRANSACTIONS_ST7565_SLAVE();
+}
+
+#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
+
+void transaction_register_rpc(int8_t transaction_id, slave_callback_t callback) {
+    // Prevent invoking RPC on QMK core sync data
+    if (transaction_id <= GET_RPC_RESP_DATA) return;
+
+    // Set the callback
+    split_transaction_table[transaction_id].slave_callback          = callback;
+    split_transaction_table[transaction_id].initiator2target_offset = offsetof(split_shared_memory_t, rpc_m2s_buffer);
+    split_transaction_table[transaction_id].target2initiator_offset = offsetof(split_shared_memory_t, rpc_s2m_buffer);
+}
+
+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) {
+    // Prevent transaction attempts while transport is disconnected
+    if (!is_transport_connected()) {
+        return false;
+    }
+    // Prevent invoking RPC on QMK core sync data
+    if (transaction_id <= GET_RPC_RESP_DATA) return false;
+    // Prevent sizing issues
+    if (initiator2target_buffer_size > RPC_M2S_BUFFER_SIZE) return false;
+    if (target2initiator_buffer_size > RPC_S2M_BUFFER_SIZE) return false;
+
+    // Prepare the metadata block
+    rpc_sync_info_t info = {.transaction_id = transaction_id, .m2s_length = initiator2target_buffer_size, .s2m_length = target2initiator_buffer_size};
+
+    // Make sure the local side knows that we're not sending the full block of data
+    split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size  = initiator2target_buffer_size;
+    split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = target2initiator_buffer_size;
+
+    // Run through the sequence:
+    // * set the transaction ID and lengths
+    // * send the request data
+    // * execute RPC callback
+    // * retrieve the response data
+    if (!transport_write(PUT_RPC_INFO, &info, sizeof(info))) {
+        return false;
+    }
+    if (!transport_write(PUT_RPC_REQ_DATA, initiator2target_buffer, initiator2target_buffer_size)) {
+        return false;
+    }
+    if (!transport_write(EXECUTE_RPC, &transaction_id, sizeof(transaction_id))) {
+        return false;
+    }
+    if (!transport_read(GET_RPC_RESP_DATA, target2initiator_buffer, target2initiator_buffer_size)) {
+        return false;
+    }
+    return true;
+}
+
+void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
+    // The RPC info block contains the intended transaction ID, as well as the sizes for both inbound and outbound data.
+    // Ignore the args -- the `split_shmem` already has the info, we just need to act upon it.
+    // We must keep the `split_transaction_table` non-const, so that it is able to be modified at runtime.
+
+    split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size  = split_shmem->rpc_info.m2s_length;
+    split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = split_shmem->rpc_info.s2m_length;
+}
+
+void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
+    // We can assume that the buffer lengths are correctly set, now, given that sequentially the rpc_info callback was already executed.
+    // Go through the rpc_info and execute _that_ transaction's callback, with the scratch buffers as inputs.
+    int8_t transaction_id = split_shmem->rpc_info.transaction_id;
+    if (transaction_id < NUM_TOTAL_TRANSACTIONS) {
+        split_transaction_desc_t *trans = &split_transaction_table[transaction_id];
+        if (trans->slave_callback) {
+            trans->slave_callback(split_shmem->rpc_info.m2s_length, split_shmem->rpc_m2s_buffer, split_shmem->rpc_info.s2m_length, split_shmem->rpc_s2m_buffer);
+        }
+    }
+}
+
+#endif  // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
diff --git a/quantum/split_common/transactions.h b/quantum/split_common/transactions.h
new file mode 100644
index 0000000000..53610d6f8e
--- /dev/null
+++ b/quantum/split_common/transactions.h
@@ -0,0 +1,54 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "stdint.h"
+#include "stdbool.h"
+
+#include "matrix.h"
+#include "transaction_id_define.h"
+#include "transport.h"
+
+typedef void (*slave_callback_t)(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
+
+// Split transaction Descriptor
+typedef struct _split_transaction_desc_t {
+    uint8_t *        status;
+    uint8_t          initiator2target_buffer_size;
+    uint16_t         initiator2target_offset;
+    uint8_t          target2initiator_buffer_size;
+    uint16_t         target2initiator_offset;
+    slave_callback_t slave_callback;
+} split_transaction_desc_t;
+
+// Forward declaration for the split transactions
+extern split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS];
+
+#define split_shmem_offset_ptr(offset) (((uint8_t *)split_shmem) + (offset))
+#define split_trans_initiator2target_buffer(trans) (split_shmem_offset_ptr((trans)->initiator2target_offset))
+#define split_trans_target2initiator_buffer(trans) (split_shmem_offset_ptr((trans)->target2initiator_offset))
+
+// returns false if valid data not received from slave
+bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]);
+void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]);
+
+void transaction_register_rpc(int8_t transaction_id, slave_callback_t callback);
+
+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);
+
+#define transaction_rpc_send(transaction_id, initiator2target_buffer_size, initiator2target_buffer) transaction_rpc_exec(transaction_id, initiator2target_buffer_size, initiator2target_buffer, 0, NULL)
+#define transaction_rpc_recv(transaction_id, target2initiator_buffer_size, target2initiator_buffer) transaction_rpc_exec(transaction_id, 0, NULL, target2initiator_buffer_size, target2initiator_buffer)
diff --git a/quantum/split_common/transport.c b/quantum/split_common/transport.c
index 9ed0f7591b..bcc0261417 100644
--- a/quantum/split_common/transport.c
+++ b/quantum/split_common/transport.c
@@ -1,452 +1,118 @@
-#include <string.h>
-#include <stddef.h>
-
-#include "config.h"
-#include "matrix.h"
-#include "quantum.h"
-
-#define ROWS_PER_HAND (MATRIX_ROWS / 2)
-#define SYNC_TIMER_OFFSET 2
-
-#ifdef RGBLIGHT_ENABLE
-#    include "rgblight.h"
-#endif
-
-#ifdef BACKLIGHT_ENABLE
-#    include "backlight.h"
-#endif
-
-#ifdef ENCODER_ENABLE
-#    include "encoder.h"
-static pin_t encoders_pad[] = ENCODERS_PAD_A;
-#    define NUMBER_OF_ENCODERS (sizeof(encoders_pad) / sizeof(pin_t))
-#endif
-
-#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
-#    include "led_matrix.h"
-#endif
-#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
-#    include "rgb_matrix.h"
-#endif
-
-#if defined(USE_I2C)
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
 
-#    include "i2c_master.h"
-#    include "i2c_slave.h"
-
-typedef struct _I2C_slave_buffer_t {
-#    ifndef DISABLE_SYNC_TIMER
-    uint32_t sync_timer;
-#    endif
-#    ifdef SPLIT_TRANSPORT_MIRROR
-    matrix_row_t mmatrix[ROWS_PER_HAND];
-#    endif
-    matrix_row_t smatrix[ROWS_PER_HAND];
-#    ifdef SPLIT_MODS_ENABLE
-    uint8_t real_mods;
-    uint8_t weak_mods;
-#        ifndef NO_ACTION_ONESHOT
-    uint8_t oneshot_mods;
-#        endif
-#    endif
-#    ifdef BACKLIGHT_ENABLE
-    uint8_t backlight_level;
-#    endif
-#    if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
-    rgblight_syncinfo_t rgblight_sync;
-#    endif
-#    ifdef ENCODER_ENABLE
-    uint8_t encoder_state[NUMBER_OF_ENCODERS];
-#    endif
-#    ifdef WPM_ENABLE
-    uint8_t current_wpm;
-#    endif
-#    if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
-    led_eeconfig_t led_matrix;
-    bool           led_suspend_state;
-#    endif
-#    if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
-    rgb_config_t rgb_matrix;
-    bool         rgb_suspend_state;
-#    endif
-} I2C_slave_buffer_t;
+#include <string.h>
+#include <debug.h>
 
-static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_reg;
+#include "transactions.h"
+#include "transport.h"
+#include "transaction_id_define.h"
+#include "atomic_util.h"
 
-#    define I2C_SYNC_TIME_START offsetof(I2C_slave_buffer_t, sync_timer)
-#    define I2C_KEYMAP_MASTER_START offsetof(I2C_slave_buffer_t, mmatrix)
-#    define I2C_KEYMAP_SLAVE_START offsetof(I2C_slave_buffer_t, smatrix)
-#    define I2C_REAL_MODS_START offsetof(I2C_slave_buffer_t, real_mods)
-#    define I2C_WEAK_MODS_START offsetof(I2C_slave_buffer_t, weak_mods)
-#    define I2C_ONESHOT_MODS_START offsetof(I2C_slave_buffer_t, oneshot_mods)
-#    define I2C_BACKLIGHT_START offsetof(I2C_slave_buffer_t, backlight_level)
-#    define I2C_RGB_START offsetof(I2C_slave_buffer_t, rgblight_sync)
-#    define I2C_ENCODER_START offsetof(I2C_slave_buffer_t, encoder_state)
-#    define I2C_WPM_START offsetof(I2C_slave_buffer_t, current_wpm)
-#    define I2C_LED_MATRIX_START offsetof(I2C_slave_buffer_t, led_matrix)
-#    define I2C_LED_SUSPEND_START offsetof(I2C_slave_buffer_t, led_suspend_state)
-#    define I2C_RGB_MATRIX_START offsetof(I2C_slave_buffer_t, rgb_matrix)
-#    define I2C_RGB_SUSPEND_START offsetof(I2C_slave_buffer_t, rgb_suspend_state)
+#ifdef USE_I2C
 
-#    define TIMEOUT 100
+#    ifndef SLAVE_I2C_TIMEOUT
+#        define SLAVE_I2C_TIMEOUT 100
+#    endif  // SLAVE_I2C_TIMEOUT
 
 #    ifndef SLAVE_I2C_ADDRESS
 #        define SLAVE_I2C_ADDRESS 0x32
 #    endif
 
-// Get rows from other half over i2c
-bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
-    i2c_readReg(SLAVE_I2C_ADDRESS, I2C_KEYMAP_SLAVE_START, (void *)slave_matrix, sizeof(i2c_buffer->smatrix), TIMEOUT);
-#    ifdef SPLIT_TRANSPORT_MIRROR
-    i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_KEYMAP_MASTER_START, (void *)master_matrix, sizeof(i2c_buffer->mmatrix), TIMEOUT);
-#    endif
+#    include "i2c_master.h"
+#    include "i2c_slave.h"
 
-    // write backlight info
-#    ifdef BACKLIGHT_ENABLE
-    uint8_t level = is_backlight_enabled() ? get_backlight_level() : 0;
-    if (level != i2c_buffer->backlight_level) {
-        if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_BACKLIGHT_START, (void *)&level, sizeof(level), TIMEOUT) >= 0) {
-            i2c_buffer->backlight_level = level;
-        }
-    }
-#    endif
+// Ensure the I2C buffer has enough space
+_Static_assert(sizeof(split_shared_memory_t) <= I2C_SLAVE_REG_COUNT, "split_shared_memory_t too large for I2C_SLAVE_REG_COUNT");
 
-#    if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
-    if (rgblight_get_change_flags()) {
-        rgblight_syncinfo_t rgblight_sync;
-        rgblight_get_syncinfo(&rgblight_sync);
-        if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_START, (void *)&rgblight_sync, sizeof(rgblight_sync), TIMEOUT) >= 0) {
-            rgblight_clear_change_flags();
-        }
-    }
-#    endif
+split_shared_memory_t *const split_shmem = (split_shared_memory_t *)i2c_slave_reg;
 
-#    ifdef ENCODER_ENABLE
-    i2c_readReg(SLAVE_I2C_ADDRESS, I2C_ENCODER_START, (void *)i2c_buffer->encoder_state, sizeof(i2c_buffer->encoder_state), TIMEOUT);
-    encoder_update_raw(i2c_buffer->encoder_state);
-#    endif
+void transport_master_init(void) { i2c_init(); }
+void transport_slave_init(void) { i2c_slave_init(SLAVE_I2C_ADDRESS); }
 
-#    ifdef WPM_ENABLE
-    uint8_t current_wpm = get_current_wpm();
-    if (current_wpm != i2c_buffer->current_wpm) {
-        if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WPM_START, (void *)&current_wpm, sizeof(current_wpm), TIMEOUT) >= 0) {
-            i2c_buffer->current_wpm = current_wpm;
-        }
+i2c_status_t transport_trigger_callback(int8_t id) {
+    // If there's no callback, indicate that we were successful
+    if (!split_transaction_table[id].slave_callback) {
+        return I2C_STATUS_SUCCESS;
     }
-#    endif
 
-#    ifdef SPLIT_MODS_ENABLE
-    uint8_t real_mods = get_mods();
-    if (real_mods != i2c_buffer->real_mods) {
-        if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_REAL_MODS_START, (void *)&real_mods, sizeof(real_mods), TIMEOUT) >= 0) {
-            i2c_buffer->real_mods = real_mods;
+    // Kick off the "callback executor", now that data has been written to the slave
+    split_shmem->transaction_id     = id;
+    split_transaction_desc_t *trans = &split_transaction_table[I2C_EXECUTE_CALLBACK];
+    return i2c_writeReg(SLAVE_I2C_ADDRESS, trans->initiator2target_offset, split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size, SLAVE_I2C_TIMEOUT);
+}
+
+bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length) {
+    i2c_status_t              status;
+    split_transaction_desc_t *trans = &split_transaction_table[id];
+    if (initiator2target_length > 0) {
+        size_t len = trans->initiator2target_buffer_size < initiator2target_length ? trans->initiator2target_buffer_size : initiator2target_length;
+        memcpy(split_trans_initiator2target_buffer(trans), initiator2target_buf, len);
+        if ((status = i2c_writeReg(SLAVE_I2C_ADDRESS, trans->initiator2target_offset, split_trans_initiator2target_buffer(trans), len, SLAVE_I2C_TIMEOUT)) < 0) {
+            return false;
         }
     }
 
-    uint8_t weak_mods = get_weak_mods();
-    if (weak_mods != i2c_buffer->weak_mods) {
-        if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WEAK_MODS_START, (void *)&weak_mods, sizeof(weak_mods), TIMEOUT) >= 0) {
-            i2c_buffer->weak_mods = weak_mods;
-        }
+    // If we need to execute a callback on the slave, do so
+    if ((status = transport_trigger_callback(id)) < 0) {
+        return false;
     }
 
-#        ifndef NO_ACTION_ONESHOT
-    uint8_t oneshot_mods = get_oneshot_mods();
-    if (oneshot_mods != i2c_buffer->oneshot_mods) {
-        if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_ONESHOT_MODS_START, (void *)&oneshot_mods, sizeof(oneshot_mods), TIMEOUT) >= 0) {
-            i2c_buffer->oneshot_mods = oneshot_mods;
+    if (target2initiator_length > 0) {
+        size_t len = trans->target2initiator_buffer_size < target2initiator_length ? trans->target2initiator_buffer_size : target2initiator_length;
+        if ((status = i2c_readReg(SLAVE_I2C_ADDRESS, trans->target2initiator_offset, split_trans_target2initiator_buffer(trans), len, SLAVE_I2C_TIMEOUT)) < 0) {
+            return false;
         }
+        memcpy(target2initiator_buf, split_trans_target2initiator_buffer(trans), len);
     }
-#        endif
-#    endif
 
-#    if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
-    i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_LED_MATRIX_START, (void *)led_matrix_eeconfig, sizeof(i2c_buffer->led_matrix), TIMEOUT);
-    bool suspend_state = led_matrix_get_suspend_state();
-    i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_LED_SUSPEND_START, (void *)suspend_state, sizeof(i2c_buffer->led_suspend_state), TIMEOUT);
-#    endif
-#    if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
-    i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_MATRIX_START, (void *)rgb_matrix_config, sizeof(i2c_buffer->rgb_matrix), TIMEOUT);
-    bool suspend_state = rgb_matrix_get_suspend_state();
-    i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_SUSPEND_START, (void *)suspend_state, sizeof(i2c_buffer->rgb_suspend_state), TIMEOUT);
-#    endif
-
-#    ifndef DISABLE_SYNC_TIMER
-    i2c_buffer->sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET;
-    i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_SYNC_TIME_START, (void *)&i2c_buffer->sync_timer, sizeof(i2c_buffer->sync_timer), TIMEOUT);
-#    endif
     return true;
 }
 
-void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
-#    ifndef DISABLE_SYNC_TIMER
-    sync_timer_update(i2c_buffer->sync_timer);
-#    endif
-    // Copy matrix to I2C buffer
-    memcpy((void *)i2c_buffer->smatrix, (void *)slave_matrix, sizeof(i2c_buffer->smatrix));
-#    ifdef SPLIT_TRANSPORT_MIRROR
-    memcpy((void *)master_matrix, (void *)i2c_buffer->mmatrix, sizeof(i2c_buffer->mmatrix));
-#    endif
-
-// Read Backlight Info
-#    ifdef BACKLIGHT_ENABLE
-    backlight_set(i2c_buffer->backlight_level);
-#    endif
-
-#    if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
-    // Update the RGB with the new data
-    if (i2c_buffer->rgblight_sync.status.change_flags != 0) {
-        rgblight_update_sync(&i2c_buffer->rgblight_sync, false);
-        i2c_buffer->rgblight_sync.status.change_flags = 0;
-    }
-#    endif
-
-#    ifdef ENCODER_ENABLE
-    encoder_state_raw(i2c_buffer->encoder_state);
-#    endif
-
-#    ifdef WPM_ENABLE
-    set_current_wpm(i2c_buffer->current_wpm);
-#    endif
-
-#    ifdef SPLIT_MODS_ENABLE
-    set_mods(i2c_buffer->real_mods);
-    set_weak_mods(i2c_buffer->weak_mods);
-#        ifndef NO_ACTION_ONESHOT
-    set_oneshot_mods(i2c_buffer->oneshot_mods);
-#        endif
-#    endif
-
-#    if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
-    memcpy((void *)i2c_buffer->led_matrix, (void *)led_matrix_eeconfig, sizeof(i2c_buffer->led_matrix));
-    led_matrix_set_suspend_state(i2c_buffer->led_suspend_state);
-#    endif
-#    if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
-    memcpy((void *)i2c_buffer->rgb_matrix, (void *)rgb_matrix_config, sizeof(i2c_buffer->rgb_matrix));
-    rgb_matrix_set_suspend_state(i2c_buffer->rgb_suspend_state);
-#    endif
-}
-
-void transport_master_init(void) { i2c_init(); }
-
-void transport_slave_init(void) { i2c_slave_init(SLAVE_I2C_ADDRESS); }
-
-#else  // USE_SERIAL
+#else  // USE_I2C
 
 #    include "serial.h"
 
-typedef struct _Serial_s2m_buffer_t {
-    // TODO: if MATRIX_COLS > 8 change to uint8_t packed_matrix[] for pack/unpack
-    matrix_row_t smatrix[ROWS_PER_HAND];
-
-#    ifdef ENCODER_ENABLE
-    uint8_t encoder_state[NUMBER_OF_ENCODERS];
-#    endif
-
-} Serial_s2m_buffer_t;
-
-typedef struct _Serial_m2s_buffer_t {
-#    ifdef SPLIT_MODS_ENABLE
-    uint8_t real_mods;
-    uint8_t weak_mods;
-#        ifndef NO_ACTION_ONESHOT
-    uint8_t oneshot_mods;
-#        endif
-#    endif
-#    ifndef DISABLE_SYNC_TIMER
-    uint32_t sync_timer;
-#    endif
-#    ifdef SPLIT_TRANSPORT_MIRROR
-    matrix_row_t mmatrix[ROWS_PER_HAND];
-#    endif
-#    ifdef BACKLIGHT_ENABLE
-    uint8_t backlight_level;
-#    endif
-#    ifdef WPM_ENABLE
-    uint8_t current_wpm;
-#    endif
-#    if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
-    led_eeconfig_t led_matrix;
-    bool           led_suspend_state;
-#    endif
-#    if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
-    rgb_config_t rgb_matrix;
-    bool         rgb_suspend_state;
-#    endif
-} Serial_m2s_buffer_t;
-
-#    if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
-// When MCUs on both sides drive their respective RGB LED chains,
-// it is necessary to synchronize, so it is necessary to communicate RGB
-// information. In that case, define RGBLIGHT_SPLIT with info on the number
-// of LEDs on each half.
-//
-// Otherwise, if the master side MCU drives both sides RGB LED chains,
-// there is no need to communicate.
-
-typedef struct _Serial_rgblight_t {
-    rgblight_syncinfo_t rgblight_sync;
-} Serial_rgblight_t;
+static split_shared_memory_t shared_memory;
+split_shared_memory_t *const split_shmem = &shared_memory;
 
-volatile Serial_rgblight_t serial_rgblight = {};
-uint8_t volatile status_rgblight           = 0;
-#    endif
-
-volatile Serial_s2m_buffer_t serial_s2m_buffer = {};
-volatile Serial_m2s_buffer_t serial_m2s_buffer = {};
-uint8_t volatile status0                       = 0;
-
-enum serial_transaction_id {
-    GET_SLAVE_MATRIX = 0,
-#    if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
-    PUT_RGBLIGHT,
-#    endif
-};
-
-SSTD_t transactions[] = {
-    [GET_SLAVE_MATRIX] =
-        {
-            (uint8_t *)&status0,
-            sizeof(serial_m2s_buffer),
-            (uint8_t *)&serial_m2s_buffer,
-            sizeof(serial_s2m_buffer),
-            (uint8_t *)&serial_s2m_buffer,
-        },
-#    if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
-    [PUT_RGBLIGHT] =
-        {
-            (uint8_t *)&status_rgblight, sizeof(serial_rgblight), (uint8_t *)&serial_rgblight, 0, NULL  // no slave to master transfer
-        },
-#    endif
-};
-
-void transport_master_init(void) { soft_serial_initiator_init(transactions, TID_LIMIT(transactions)); }
-
-void transport_slave_init(void) { soft_serial_target_init(transactions, TID_LIMIT(transactions)); }
+void transport_master_init(void) { soft_serial_initiator_init(); }
+void transport_slave_init(void) { soft_serial_target_init(); }
 
-#    if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
-
-// rgblight synchronization information communication.
-
-void transport_rgblight_master(void) {
-    if (rgblight_get_change_flags()) {
-        rgblight_get_syncinfo((rgblight_syncinfo_t *)&serial_rgblight.rgblight_sync);
-        if (soft_serial_transaction(PUT_RGBLIGHT) == TRANSACTION_END) {
-            rgblight_clear_change_flags();
-        }
-    }
-}
-
-void transport_rgblight_slave(void) {
-    if (status_rgblight == TRANSACTION_ACCEPTED) {
-        rgblight_update_sync((rgblight_syncinfo_t *)&serial_rgblight.rgblight_sync, false);
-        status_rgblight = TRANSACTION_END;
+bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length) {
+    split_transaction_desc_t *trans = &split_transaction_table[id];
+    if (initiator2target_length > 0) {
+        size_t len = trans->initiator2target_buffer_size < initiator2target_length ? trans->initiator2target_buffer_size : initiator2target_length;
+        memcpy(split_trans_initiator2target_buffer(trans), initiator2target_buf, len);
     }
-}
 
-#    else
-#        define transport_rgblight_master()
-#        define transport_rgblight_slave()
-#    endif
-
-bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
-#    ifndef SERIAL_USE_MULTI_TRANSACTION
-    if (soft_serial_transaction() != TRANSACTION_END) {
-        return false;
-    }
-#    else
-    transport_rgblight_master();
-    if (soft_serial_transaction(GET_SLAVE_MATRIX) != TRANSACTION_END) {
+    if (soft_serial_transaction(id) != TRANSACTION_END) {
         return false;
     }
-#    endif
 
-    // TODO:  if MATRIX_COLS > 8 change to unpack()
-    for (int i = 0; i < ROWS_PER_HAND; ++i) {
-        slave_matrix[i] = serial_s2m_buffer.smatrix[i];
-#    ifdef SPLIT_TRANSPORT_MIRROR
-        serial_m2s_buffer.mmatrix[i] = master_matrix[i];
-#    endif
+    if (target2initiator_length > 0) {
+        size_t len = trans->target2initiator_buffer_size < target2initiator_length ? trans->target2initiator_buffer_size : target2initiator_length;
+        memcpy(target2initiator_buf, split_trans_target2initiator_buffer(trans), len);
     }
 
-#    ifdef BACKLIGHT_ENABLE
-    // Write backlight level for slave to read
-    serial_m2s_buffer.backlight_level = is_backlight_enabled() ? get_backlight_level() : 0;
-#    endif
-
-#    ifdef ENCODER_ENABLE
-    encoder_update_raw((uint8_t *)serial_s2m_buffer.encoder_state);
-#    endif
-
-#    ifdef WPM_ENABLE
-    // Write wpm to slave
-    serial_m2s_buffer.current_wpm = get_current_wpm();
-#    endif
-
-#    ifdef SPLIT_MODS_ENABLE
-    serial_m2s_buffer.real_mods = get_mods();
-    serial_m2s_buffer.weak_mods = get_weak_mods();
-#        ifndef NO_ACTION_ONESHOT
-    serial_m2s_buffer.oneshot_mods = get_oneshot_mods();
-#        endif
-#    endif
-
-#    if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
-    serial_m2s_buffer.led_matrix        = led_matrix_eeconfig;
-    serial_m2s_buffer.led_suspend_state = led_matrix_get_suspend_state();
-#    endif
-#    if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
-    serial_m2s_buffer.rgb_matrix        = rgb_matrix_config;
-    serial_m2s_buffer.rgb_suspend_state = rgb_matrix_get_suspend_state();
-#    endif
-
-#    ifndef DISABLE_SYNC_TIMER
-    serial_m2s_buffer.sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET;
-#    endif
     return true;
 }
 
-void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
-    transport_rgblight_slave();
-#    ifndef DISABLE_SYNC_TIMER
-    sync_timer_update(serial_m2s_buffer.sync_timer);
-#    endif
-
-    // TODO: if MATRIX_COLS > 8 change to pack()
-    for (int i = 0; i < ROWS_PER_HAND; ++i) {
-        serial_s2m_buffer.smatrix[i] = slave_matrix[i];
-#    ifdef SPLIT_TRANSPORT_MIRROR
-        master_matrix[i] = serial_m2s_buffer.mmatrix[i];
-#    endif
-    }
-#    ifdef BACKLIGHT_ENABLE
-    backlight_set(serial_m2s_buffer.backlight_level);
-#    endif
-
-#    ifdef ENCODER_ENABLE
-    encoder_state_raw((uint8_t *)serial_s2m_buffer.encoder_state);
-#    endif
+#endif  // USE_I2C
 
-#    ifdef WPM_ENABLE
-    set_current_wpm(serial_m2s_buffer.current_wpm);
-#    endif
-
-#    ifdef SPLIT_MODS_ENABLE
-    set_mods(serial_m2s_buffer.real_mods);
-    set_weak_mods(serial_m2s_buffer.weak_mods);
-#        ifndef NO_ACTION_ONESHOT
-    set_oneshot_mods(serial_m2s_buffer.oneshot_mods);
-#        endif
-#    endif
-
-#    if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
-    led_matrix_eeconfig = serial_m2s_buffer.led_matrix;
-    led_matrix_set_suspend_state(serial_m2s_buffer.led_suspend_state);
-#    endif
-#    if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
-    rgb_matrix_config = serial_m2s_buffer.rgb_matrix;
-    rgb_matrix_set_suspend_state(serial_m2s_buffer.rgb_suspend_state);
-#    endif
-}
+bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { return transactions_master(master_matrix, slave_matrix); }
 
-#endif
+void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { transactions_slave(master_matrix, slave_matrix); }
diff --git a/quantum/split_common/transport.h b/quantum/split_common/transport.h
index a9f66301bf..1d4f6ed0cd 100644
--- a/quantum/split_common/transport.h
+++ b/quantum/split_common/transport.h
@@ -1,10 +1,183 @@
+/* Copyright 2021 QMK
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
 #pragma once
 
+#include "stdint.h"
+#include "stdbool.h"
+
+#include "progmem.h"
+#include "action_layer.h"
 #include "matrix.h"
 
+#ifndef RPC_M2S_BUFFER_SIZE
+#    define RPC_M2S_BUFFER_SIZE 32
+#endif  // RPC_M2S_BUFFER_SIZE
+
+#ifndef RPC_S2M_BUFFER_SIZE
+#    define RPC_S2M_BUFFER_SIZE 32
+#endif  // RPC_S2M_BUFFER_SIZE
+
 void transport_master_init(void);
 void transport_slave_init(void);
 
 // returns false if valid data not received from slave
 bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]);
 void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]);
+
+bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length);
+
+#ifdef ENCODER_ENABLE
+#    include "encoder.h"
+#    define NUMBER_OF_ENCODERS (sizeof((pin_t[])ENCODERS_PAD_A) / sizeof(pin_t))
+#endif  // ENCODER_ENABLE
+
+#ifdef BACKLIGHT_ENABLE
+#    include "backlight.h"
+#endif  // BACKLIGHT_ENABLE
+
+#ifdef RGBLIGHT_ENABLE
+#    include "rgblight.h"
+#endif  // RGBLIGHT_ENABLE
+
+typedef struct _split_slave_matrix_sync_t {
+    uint8_t      checksum;
+    matrix_row_t matrix[(MATRIX_ROWS) / 2];
+} split_slave_matrix_sync_t;
+
+#ifdef SPLIT_TRANSPORT_MIRROR
+typedef struct _split_master_matrix_sync_t {
+    matrix_row_t matrix[(MATRIX_ROWS) / 2];
+} split_master_matrix_sync_t;
+#endif  // SPLIT_TRANSPORT_MIRROR
+
+#ifdef ENCODER_ENABLE
+typedef struct _split_slave_encoder_sync_t {
+    uint8_t checksum;
+    uint8_t state[NUMBER_OF_ENCODERS];
+} split_slave_encoder_sync_t;
+#endif  // ENCODER_ENABLE
+
+#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
+typedef struct _split_layers_sync_t {
+    layer_state_t layer_state;
+    layer_state_t default_layer_state;
+} split_layers_sync_t;
+#endif  // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
+
+#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
+#    include "led_matrix.h"
+
+typedef struct _led_matrix_sync_t {
+    led_eeconfig_t led_matrix;
+    bool           led_suspend_state;
+} led_matrix_sync_t;
+#endif  // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
+
+#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
+#    include "rgb_matrix.h"
+
+typedef struct _rgb_matrix_sync_t {
+    rgb_config_t rgb_matrix;
+    bool         rgb_suspend_state;
+} rgb_matrix_sync_t;
+#endif  // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
+
+#ifdef SPLIT_MODS_ENABLE
+typedef struct _split_mods_sync_t {
+    uint8_t real_mods;
+    uint8_t weak_mods;
+#    ifndef NO_ACTION_ONESHOT
+    uint8_t oneshot_mods;
+#    endif  // NO_ACTION_ONESHOT
+} split_mods_sync_t;
+#endif  // SPLIT_MODS_ENABLE
+
+#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
+typedef struct _rpc_sync_info_t {
+    int8_t  transaction_id;
+    uint8_t m2s_length;
+    uint8_t s2m_length;
+} rpc_sync_info_t;
+#endif  // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
+
+typedef struct _split_shared_memory_t {
+#ifdef USE_I2C
+    int8_t transaction_id;
+#endif  // USE_I2C
+
+    split_slave_matrix_sync_t smatrix;
+
+#ifdef SPLIT_TRANSPORT_MIRROR
+    split_master_matrix_sync_t mmatrix;
+#endif  // SPLIT_TRANSPORT_MIRROR
+
+#ifdef ENCODER_ENABLE
+    split_slave_encoder_sync_t encoders;
+#endif  // ENCODER_ENABLE
+
+#ifndef DISABLE_SYNC_TIMER
+    uint32_t sync_timer;
+#endif  // DISABLE_SYNC_TIMER
+
+#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
+    split_layers_sync_t layers;
+#endif  // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
+
+#ifdef SPLIT_LED_STATE_ENABLE
+    uint8_t led_state;
+#endif  // SPLIT_LED_STATE_ENABLE
+
+#ifdef SPLIT_MODS_ENABLE
+    split_mods_sync_t mods;
+#endif  // SPLIT_MODS_ENABLE
+
+#ifdef BACKLIGHT_ENABLE
+    uint8_t backlight_level;
+#endif  // BACKLIGHT_ENABLE
+
+#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
+    rgblight_syncinfo_t rgblight_sync;
+#endif  // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
+
+#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
+    led_matrix_sync_t led_matrix_sync;
+#endif  // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
+
+#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
+    rgb_matrix_sync_t rgb_matrix_sync;
+#endif  // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
+
+#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
+    uint8_t current_wpm;
+#endif  // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
+
+#if defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
+    uint8_t current_oled_state;
+#endif  // defined(OLED_ENABLE) && defined(SPLIT_OLED_ENABLE)
+
+#if defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
+    uint8_t current_st7565_state;
+#endif  // ST7565_ENABLE(OLED_ENABLE) && defined(SPLIT_ST7565_ENABLE)
+
+#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
+    rpc_sync_info_t rpc_info;
+    uint8_t         rpc_m2s_buffer[RPC_M2S_BUFFER_SIZE];
+    uint8_t         rpc_s2m_buffer[RPC_S2M_BUFFER_SIZE];
+#endif  // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
+} split_shared_memory_t;
+
+extern split_shared_memory_t *const split_shmem;