Extensible split data sync (#11930)

* Extensible split data sync capability through transactions.

- Split common transport has been split up between the transport layer
  and data layer.
- Split "transactions" model used, with convergence between I2C and
  serial data definitions.
- Slave matrix "generation count" is used to determine if the full slave
  matrix needs to be retrieved.
- Encoders get the same "generation count" treatment.
- All other blocks of data are synchronised when a change is detected.
- All transmissions have a globally-configurable deadline before a
  transmission is forced (`FORCED_SYNC_THROTTLE_MS`, default 100ms).
- Added atomicity for all core-synced data, preventing partial updates
- Added retries to AVR i2c_master's i2c_start, to minimise the number of
  failed transactions when interrupts are disabled on the slave due to
  atomicity checks.
- Some keyboards have had slight modifications made in order to ensure
  that they still build due to firmware size restrictions.

* Fixup LED_MATRIX compile.

* Parameterise ERROR_DISCONNECT_COUNT.
master
Nick Brassel 2021-06-18 09:10:06 +10:00 committed by GitHub
parent ef92c9ee2c
commit 172e6a7030
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1389 additions and 693 deletions

View File

@ -537,7 +537,11 @@ ifeq ($(strip $(SPLIT_KEYBOARD)), yes)
# Determine which (if any) transport files are required
ifneq ($(strip $(SPLIT_TRANSPORT)), custom)
QUANTUM_LIB_SRC += $(QUANTUM_DIR)/split_common/transport.c
QUANTUM_SRC += $(QUANTUM_DIR)/split_common/transport.c \
$(QUANTUM_DIR)/split_common/transactions.c
OPT_DEFS += -DSPLIT_COMMON_TRANSACTIONS
# Functions added via QUANTUM_LIB_SRC are only included in the final binary if they're called.
# Unused functions are pruned away, which is why we can add multiple drivers here without bloat.
ifeq ($(PLATFORM),AVR)

View File

@ -274,7 +274,7 @@ There are a few different ways to set handedness for split keyboards (listed in
### Other Options
* `#define USE_I2C`
* For using I2C instead of Serial (defaults to serial)
* For using I2C instead of Serial (default is serial; serial transport is supported on ARM -- I2C is AVR-only)
* `#define SOFT_SERIAL_PIN D0`
* When using serial, define this. `D0` or `D1`,`D2`,`D3`,`E6`.
@ -303,7 +303,7 @@ There are a few different ways to set handedness for split keyboards (listed in
* `#define SPLIT_USB_DETECT`
* Detect (with timeout) USB connection when delegating master/slave
* Default behavior for ARM
* Required for AVR Teensy
* Required for AVR Teensy (without hardware mods)
* `#define SPLIT_USB_TIMEOUT 2000`
* Maximum timeout when detecting master/slave when using `SPLIT_USB_DETECT`
@ -311,6 +311,28 @@ There are a few different ways to set handedness for split keyboards (listed in
* `#define SPLIT_USB_TIMEOUT_POLL 10`
* Poll frequency when detecting master/slave when using `SPLIT_USB_DETECT`
* `#define FORCED_SYNC_THROTTLE_MS 100`
* Deadline for synchronizing data from master to slave when using the QMK-provided split transport.
* `#define SPLIT_TRANSPORT_MIRROR`
* Mirrors the master-side matrix on the slave when using the QMK-provided split transport.
* `#define SPLIT_LAYER_STATE_ENABLE`
* Ensures the current layer state is available on the slave when using the QMK-provided split transport.
* `#define SPLIT_LED_STATE_ENABLE`
* Ensures the current host indicator state (caps/num/scroll) is available on the slave when using the QMK-provided split transport.
* `#define SPLIT_MODS_ENABLE`
* Ensures the current modifier state (normal, weak, and oneshot) is available on the slave when using the QMK-provided split transport.
* `#define SPLIT_WPM_ENABLE`
* Ensures the current WPM is available on the slave when using the QMK-provided split transport.
* `#define SPLIT_TRANSACTION_IDS_KB .....`
* `#define SPLIT_TRANSACTION_IDS_USER .....`
* Allows for custom data sync with the slave when using the QMK-provided split transport. See [custom data sync between sides](feature_split_keyboard.md#custom-data-sync) for more information.
# The `rules.mk` File
This is a [make](https://www.gnu.org/software/make/manual/make.html) file that is included by the top-level `Makefile`. It is used to set some information about the MCU that we will be compiling for as well as enabling and disabling certain features.

View File

@ -8,8 +8,7 @@ QMK Firmware has a generic implementation that is usable by any board, as well a
For this, we will mostly be talking about the generic implementation used by the Let's Split and other keyboards.
!> ARM is not yet fully supported for Split Keyboards and has many limitations. Progress is being made, but we have not yet reached 100% feature parity.
!> ARM split supports most QMK subsystems when using the 'serial' and 'serial_usart' drivers. I2C slave is currently unsupported.
## Compatibility Overview
@ -169,7 +168,7 @@ Because not every split keyboard is identical, there are a number of additional
#define USE_I2C
```
This enables I<sup>2</sup>C support for split keyboards. This isn't strictly for communication, but can be used for OLED or other I<sup>2</sup>C-based devices.
This configures the use of I<sup>2</sup>C support for split keyboard transport (AVR only).
```c
#define SOFT_SERIAL_PIN D0
@ -193,20 +192,115 @@ If you're having issues with serial communication, you can change this value, as
* **`5`**: about 20kbps
```c
#define SPLIT_MODS_ENABLE
#define FORCED_SYNC_THROTTLE_MS 100
```
This enables transmitting modifier state (normal, weak and oneshot) to the non
primary side of the split keyboard. This adds a few bytes of data to the split
communication protocol and may impact the matrix scan speed when enabled.
The purpose of this feature is to support cosmetic use of modifer state (e.g.
displaying status on an OLED screen).
This sets the maximum number of milliseconds before forcing a synchronization of data from master to slave. Under normal circumstances this sync occurs whenever the data _changes_, for safety a data transfer occurs after this number of milliseconds if no change has been detected since the last sync.
```c
#define SPLIT_TRANSPORT_MIRROR
```
This mirrors the master side matrix to the slave side for features that react or require knowledge of master side key presses on the slave side. This adds a few bytes of data to the split communication protocol and may impact the matrix scan speed when enabled. The purpose of this feature is to support cosmetic use of key events (e.g. RGB reacting to Keypresses).
This mirrors the master side matrix to the slave side for features that react or require knowledge of master side key presses on the slave side. The purpose of this feature is to support cosmetic use of key events (e.g. RGB reacting to keypresses). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled.
```c
#define SPLIT_LAYER_STATE_ENABLE
```
This enables syncing of the layer state between both halves of the split keyboard. The main purpose of this feature is to enable support for use of things like OLED display of the currently active layer. This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled.
```c
#define SPLIT_LED_STATE_ENABLE
```
This enables syncing of the Host LED status (caps lock, num lock, etc) between both halves of the split keyboard. The main purpose of this feature is to enable support for use of things like OLED display of the Host LED status. This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled.
```c
#define SPLIT_MODS_ENABLE
```
This enables transmitting modifier state (normal, weak and oneshot) to the non primary side of the split keyboard. The purpose of this feature is to support cosmetic use of modifer state (e.g. displaying status on an OLED screen). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled.
```c
#define SPLIT_WPM_ENABLE
```
This enables transmitting the current WPM to the slave side of the split keyboard. The purpose of this feature is to support cosmetic use of WPM (e.g. displaying the current value on an OLED screen). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled.
### Custom data sync between sides :id=custom-data-sync
QMK's split transport allows for arbitrary data transactions at both the keyboard and user levels. This is modelled on a remote procedure call, with the master invoking a function on the slave side, with the ability to send data from master to slave, process it slave side, and send data back from slave to master.
To leverage this, a keyboard or user/keymap can define a comma-separated list of _transaction IDs_:
```c
// for keyboard-level data sync:
#define SPLIT_TRANSACTION_IDS_KB KEYBOARD_SYNC_A, KEYBOARD_SYNC_B
// or, for user:
#define SPLIT_TRANSACTION_IDS_USER USER_SYNC_A, USER_SYNC_B, USER_SYNC_C
```
These _transaction IDs_ then need a slave-side handler function to be registered with the split transport, for example:
```c
typedef struct _master_to_slave_t {
int m2s_data;
} master_to_slave_t;
typedef struct _slave_to_master_t {
int s2m_data;
} slave_to_master_t;
void user_sync_a_slave_handler(uint8_t in_buflen, const void* in_data, uint8_t out_buflen, void* out_data) {
const master_to_slave_t *m2s = (const master_to_slave_t*)in_data;
slave_to_master_t *s2m = (slave_to_master_t*)out_data;
s2m->s2m_data = m2s->m2s_data + 5; // whatever comes in, add 5 so it can be sent back
}
void keyboard_post_init_user(void) {
transaction_register_rpc(USER_SYNC_A, user_sync_a_slave_handler);
}
```
The master side can then invoke the slave-side handler - for normal keyboard functionality to be minimally affected, any keyboard- or user-level code attempting to sync data should be throttled:
```c
void housekeeping_task_user(void) {
if (is_keyboard_master()) {
// Interact with slave every 500ms
static uint32_t last_sync = 0;
if (timer_elapsed32(last_sync) > 500) {
master_to_slave_t m2s = {6};
slave_to_master_t s2m = {0};
if(transaction_rpc_exec(USER_SYNC_A, sizeof(m2s), &m2s, sizeof(s2m), &s2m)) {
last_sync = timer_read32();
dprintf("Slave value: %d\n", s2m.s2m_data); // this will now be 11, as the slave adds 5
} else {
dprint("Slave sync failed!\n");
}
}
}
}
```
!> It is recommended that any data sync between halves happens during the master side's _housekeeping task_. This ensures timely retries should failures occur.
If only one-way data transfer is needed, helper methods are provided:
```c
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);
bool transaction_rpc_send(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer);
bool transaction_rpc_recv(int8_t transaction_id, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
```
By default, the inbound and outbound data is limited to a maximum of 32 bytes each. The sizes can be altered if required:
```c
// Master to slave:
#define RPC_M2S_BUFFER_SIZE 48
// Slave to master:
#define RPC_S2M_BUFFER_SIZE 48
```
### Hardware Configuration Options

View File

@ -28,8 +28,14 @@
# define F_SCL 400000UL // SCL frequency
#endif
#ifndef I2C_START_RETRY_COUNT
# define I2C_START_RETRY_COUNT 20
#endif // I2C_START_RETRY_COUNT
#define TWBR_val (((F_CPU / F_SCL) - 16) / 2)
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
void i2c_init(void) {
TWSR = 0; /* no prescaler */
TWBR = (uint8_t)TWBR_val;
@ -47,7 +53,7 @@ void i2c_init(void) {
#endif
}
i2c_status_t i2c_start(uint8_t address, uint16_t timeout) {
static i2c_status_t i2c_start_impl(uint8_t address, uint16_t timeout) {
// reset TWI control register
TWCR = 0;
// transmit START condition
@ -86,6 +92,17 @@ i2c_status_t i2c_start(uint8_t address, uint16_t timeout) {
return I2C_STATUS_SUCCESS;
}
i2c_status_t i2c_start(uint8_t address, uint16_t timeout) {
// Retry i2c_start_impl a bunch times in case the remote side has interrupts disabled.
uint16_t timeout_timer = timer_read();
uint16_t time_slice = MAX(1, (timeout == (I2C_TIMEOUT_INFINITE)) ? 5 : (timeout / (I2C_START_RETRY_COUNT))); // if it's infinite, wait 1ms between attempts, otherwise split up the entire timeout into the number of retries
i2c_status_t status;
do {
status = i2c_start_impl(address, time_slice);
} while ((status < 0) && ((timeout == I2C_TIMEOUT_INFINITE) || (timer_elapsed(timeout_timer) < timeout)));
return status;
}
i2c_status_t i2c_write(uint8_t data, uint16_t timeout) {
// load data into data register
TWDR = data;

View File

@ -17,6 +17,7 @@
* GitHub repository: https://github.com/g4lvanix/I2C-slave-lib
*/
#include <stddef.h>
#include <avr/io.h>
#include <util/twi.h>
#include <avr/interrupt.h>
@ -24,6 +25,12 @@
#include "i2c_slave.h"
#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
# include "transactions.h"
static volatile bool is_callback_executor = false;
#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT];
static volatile uint8_t buffer_address;
@ -48,11 +55,14 @@ ISR(TWI_vect) {
case TW_SR_SLA_ACK:
// The device is now a slave receiver
slave_has_register_set = false;
#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
is_callback_executor = false;
#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
break;
case TW_SR_DATA_ACK:
// This device is a slave receiver and has received data
// First byte is the location then the bytes will be writen in buffer with auto-incriment
// First byte is the location then the bytes will be writen in buffer with auto-increment
if (!slave_has_register_set) {
buffer_address = TWDR;
@ -60,10 +70,25 @@ ISR(TWI_vect) {
ack = 0;
buffer_address = 0;
}
slave_has_register_set = true; // address has been receaved now fill in buffer
slave_has_register_set = true; // address has been received now fill in buffer
#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
// Work out if we're attempting to execute a callback
is_callback_executor = buffer_address == split_transaction_table[I2C_EXECUTE_CALLBACK].initiator2target_offset;
#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
} else {
i2c_slave_reg[buffer_address] = TWDR;
buffer_address++;
#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
// If we're intending to execute a transaction callback, do so, as we've just received the transaction ID
if (is_callback_executor) {
split_transaction_desc_t *trans = &split_transaction_table[split_shmem->transaction_id];
if (trans->slave_callback) {
trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans));
}
}
#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
}
break;

View File

@ -22,7 +22,18 @@
#pragma once
#define I2C_SLAVE_REG_COUNT 30
#ifndef I2C_SLAVE_REG_COUNT
# if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
# include "transport.h"
# define I2C_SLAVE_REG_COUNT sizeof(split_shared_memory_t)
# else // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
# define I2C_SLAVE_REG_COUNT 30
# endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
#endif // I2C_SLAVE_REG_COUNT
_Static_assert(I2C_SLAVE_REG_COUNT < 256, "I2C target registers must be single byte");
extern volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT];

View File

@ -224,15 +224,8 @@
# define SERIAL_DELAY_HALF2 (SERIAL_DELAY - SERIAL_DELAY / 2)
# define SLAVE_INT_WIDTH_US 1
# ifndef SERIAL_USE_MULTI_TRANSACTION
# define SLAVE_INT_RESPONSE_TIME SERIAL_DELAY
# else
# define SLAVE_INT_ACK_WIDTH_UNIT 2
# define SLAVE_INT_ACK_WIDTH 4
# endif
static SSTD_t *Transaction_table = NULL;
static uint8_t Transaction_table_size = 0;
# define SLAVE_INT_ACK_WIDTH_UNIT 2
# define SLAVE_INT_ACK_WIDTH 4
inline static void serial_delay(void) ALWAYS_INLINE;
inline static void serial_delay(void) { _delay_us(SERIAL_DELAY); }
@ -259,16 +252,12 @@ inline static void serial_low(void) { writePinLow(SOFT_SERIAL_PIN); }
inline static void serial_high(void) ALWAYS_INLINE;
inline static void serial_high(void) { writePinHigh(SOFT_SERIAL_PIN); }
void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size) {
Transaction_table = sstd_table;
Transaction_table_size = (uint8_t)sstd_table_size;
void soft_serial_initiator_init(void) {
serial_output();
serial_high();
}
void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size) {
Transaction_table = sstd_table;
Transaction_table_size = (uint8_t)sstd_table_size;
void soft_serial_target_init(void) {
serial_input_with_pullup();
// Enable INT0-INT7
@ -395,19 +384,14 @@ static inline uint8_t nibble_bits_count(uint8_t bits) {
// interrupt handle to be used by the target device
ISR(SERIAL_PIN_INTERRUPT) {
# ifndef SERIAL_USE_MULTI_TRANSACTION
serial_low();
serial_output();
SSTD_t *trans = Transaction_table;
# else
// recive transaction table index
uint8_t tid, bits;
uint8_t pecount = 0;
sync_recv();
bits = serial_read_chunk(&pecount, 7);
bits = serial_read_chunk(&pecount, 8);
tid = bits >> 3;
bits = (bits & 7) != nibble_bits_count(tid);
if (bits || pecount > 0 || tid > Transaction_table_size) {
bits = (bits & 7) != (nibble_bits_count(tid) & 7);
if (bits || pecount > 0 || tid > NUM_TOTAL_TRANSACTIONS) {
return;
}
serial_delay_half1();
@ -415,18 +399,22 @@ ISR(SERIAL_PIN_INTERRUPT) {
serial_high(); // response step1 low->high
serial_output();
_delay_sub_us(SLAVE_INT_ACK_WIDTH_UNIT * SLAVE_INT_ACK_WIDTH);
SSTD_t *trans = &Transaction_table[tid];
split_transaction_desc_t *trans = &split_transaction_table[tid];
serial_low(); // response step2 ack high->low
# endif
// If the transaction has a callback, we can execute it now
if (trans->slave_callback) {
trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans));
}
// target send phase
if (trans->target2initiator_buffer_size > 0) serial_send_packet((uint8_t *)trans->target2initiator_buffer, trans->target2initiator_buffer_size);
if (trans->target2initiator_buffer_size > 0) serial_send_packet((uint8_t *)split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size);
// target switch to input
change_sender2reciver();
// target recive phase
if (trans->initiator2target_buffer_size > 0) {
if (serial_recive_packet((uint8_t *)trans->initiator2target_buffer, trans->initiator2target_buffer_size)) {
if (serial_recive_packet((uint8_t *)split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size)) {
*trans->status = TRANSACTION_ACCEPTED;
} else {
*trans->status = TRANSACTION_DATA_ERROR;
@ -448,14 +436,12 @@ ISR(SERIAL_PIN_INTERRUPT) {
// TRANSACTION_NO_RESPONSE
// TRANSACTION_DATA_ERROR
// this code is very time dependent, so we need to disable interrupts
# ifndef SERIAL_USE_MULTI_TRANSACTION
int soft_serial_transaction(void) {
SSTD_t *trans = Transaction_table;
# else
int soft_serial_transaction(int sstd_index) {
if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR;
SSTD_t *trans = &Transaction_table[sstd_index];
# endif
if (sstd_index > NUM_TOTAL_TRANSACTIONS) return TRANSACTION_TYPE_ERROR;
split_transaction_desc_t *trans = &split_transaction_table[sstd_index];
if (!trans->status) return TRANSACTION_TYPE_ERROR; // not registered
cli();
// signal to the target that we want to start a transaction
@ -463,27 +449,11 @@ int soft_serial_transaction(int sstd_index) {
serial_low();
_delay_us(SLAVE_INT_WIDTH_US);
# ifndef SERIAL_USE_MULTI_TRANSACTION
// wait for the target response
serial_input_with_pullup();
_delay_us(SLAVE_INT_RESPONSE_TIME);
// check if the target is present
if (serial_read_pin()) {
// target failed to pull the line low, assume not present
serial_output();
serial_high();
*trans->status = TRANSACTION_NO_RESPONSE;
sei();
return TRANSACTION_NO_RESPONSE;
}
# else
// send transaction table index
int tid = (sstd_index << 3) | (7 & nibble_bits_count(sstd_index));
sync_send();
_delay_sub_us(TID_SEND_ADJUST);
serial_write_chunk(tid, 7);
serial_write_chunk(tid, 8);
serial_delay_half1();
// wait for the target response (step1 low->high)
@ -504,12 +474,11 @@ int soft_serial_transaction(int sstd_index) {
}
_delay_sub_us(SLAVE_INT_ACK_WIDTH_UNIT);
}
# endif
// initiator recive phase
// if the target is present syncronize with it
if (trans->target2initiator_buffer_size > 0) {
if (!serial_recive_packet((uint8_t *)trans->target2initiator_buffer, trans->target2initiator_buffer_size)) {
if (!serial_recive_packet((uint8_t *)split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size)) {
serial_output();
serial_high();
*trans->status = TRANSACTION_DATA_ERROR;
@ -523,7 +492,7 @@ int soft_serial_transaction(int sstd_index) {
// initiator send phase
if (trans->initiator2target_buffer_size > 0) {
serial_send_packet((uint8_t *)trans->initiator2target_buffer, trans->initiator2target_buffer_size);
serial_send_packet((uint8_t *)split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size);
}
// always, release the line when not in use
@ -534,9 +503,8 @@ int soft_serial_transaction(int sstd_index) {
return TRANSACTION_END;
}
# ifdef SERIAL_USE_MULTI_TRANSACTION
int soft_serial_get_and_clean_status(int sstd_index) {
SSTD_t *trans = &Transaction_table[sstd_index];
split_transaction_desc_t *trans = &split_transaction_table[sstd_index];
cli();
int retval = *trans->status;
*trans->status = 0;
@ -544,8 +512,6 @@ int soft_serial_get_and_clean_status(int sstd_index) {
sei();
return retval;
}
# endif
#endif
// Helix serial.c history

View File

@ -1,62 +0,0 @@
#pragma once
#include <stdbool.h>
// /////////////////////////////////////////////////////////////////
// Need Soft Serial defines in config.h
// /////////////////////////////////////////////////////////////////
// ex.
// #define SOFT_SERIAL_PIN ?? // ?? = D0,D1,D2,D3,E6
// OPTIONAL: #define SELECT_SOFT_SERIAL_SPEED ? // ? = 1,2,3,4,5
// // 1: about 137kbps (default)
// // 2: about 75kbps
// // 3: about 39kbps
// // 4: about 26kbps
// // 5: about 20kbps
//
// //// USE simple API (using signle-type transaction function)
// /* nothing */
// //// USE flexible API (using multi-type transaction function)
// #define SERIAL_USE_MULTI_TRANSACTION
//
// /////////////////////////////////////////////////////////////////
// Soft Serial Transaction Descriptor
typedef struct _SSTD_t {
uint8_t *status;
uint8_t initiator2target_buffer_size;
uint8_t *initiator2target_buffer;
uint8_t target2initiator_buffer_size;
uint8_t *target2initiator_buffer;
} SSTD_t;
#define TID_LIMIT(table) (sizeof(table) / sizeof(SSTD_t))
// initiator is transaction start side
void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size);
// target is interrupt accept side
void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size);
// initiator resullt
#define TRANSACTION_END 0
#define TRANSACTION_NO_RESPONSE 0x1
#define TRANSACTION_DATA_ERROR 0x2
#define TRANSACTION_TYPE_ERROR 0x4
#ifndef SERIAL_USE_MULTI_TRANSACTION
int soft_serial_transaction(void);
#else
int soft_serial_transaction(int sstd_index);
#endif
// target status
// *SSTD_t.status has
// initiator:
// TRANSACTION_END
// or TRANSACTION_NO_RESPONSE
// or TRANSACTION_DATA_ERROR
// target:
// TRANSACTION_DATA_ERROR
// or TRANSACTION_ACCEPTED
#define TRANSACTION_ACCEPTED 0x8
#ifdef SERIAL_USE_MULTI_TRANSACTION
int soft_serial_get_and_clean_status(int sstd_index);
#endif

View File

@ -74,21 +74,12 @@ static THD_FUNCTION(Thread1, arg) {
}
}
static SSTD_t *Transaction_table = NULL;
static uint8_t Transaction_table_size = 0;
void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size) {
Transaction_table = sstd_table;
Transaction_table_size = (uint8_t)sstd_table_size;
void soft_serial_initiator_init(void) {
serial_output();
serial_high();
}
void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size) {
Transaction_table = sstd_table;
Transaction_table_size = (uint8_t)sstd_table_size;
void soft_serial_target_init(void) {
serial_input();
palEnablePadEvent(PAL_PORT(SOFT_SERIAL_PIN), PAL_PAD(SOFT_SERIAL_PIN), PAL_EVENT_MODE_FALLING_EDGE);
@ -154,16 +145,14 @@ void interrupt_handler(void *arg) {
uint8_t checksum_computed = 0;
int sstd_index = 0;
#ifdef SERIAL_USE_MULTI_TRANSACTION
sstd_index = serial_read_byte();
sync_send();
#endif
SSTD_t *trans = &Transaction_table[sstd_index];
split_transaction_desc_t *trans = &split_transaction_table[sstd_index];
for (int i = 0; i < trans->initiator2target_buffer_size; ++i) {
trans->initiator2target_buffer[i] = serial_read_byte();
split_trans_initiator2target_buffer(trans)[i] = serial_read_byte();
sync_send();
checksum_computed += trans->initiator2target_buffer[i];
checksum_computed += split_trans_initiator2target_buffer(trans)[i];
}
checksum_computed ^= 7;
uint8_t checksum_received = serial_read_byte();
@ -172,12 +161,17 @@ void interrupt_handler(void *arg) {
// wait for the sync to finish sending
serial_delay();
// Allow any slave processing to occur
if (trans->slave_callback) {
trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans));
}
uint8_t checksum = 0;
for (int i = 0; i < trans->target2initiator_buffer_size; ++i) {
serial_write_byte(trans->target2initiator_buffer[i]);
serial_write_byte(split_trans_target2initiator_buffer(trans)[i]);
sync_send();
serial_delay_half();
checksum += trans->target2initiator_buffer[i];
checksum += split_trans_target2initiator_buffer(trans)[i];
}
serial_write_byte(checksum ^ 7);
sync_send();
@ -206,15 +200,10 @@ void interrupt_handler(void *arg) {
// TRANSACTION_NO_RESPONSE
// TRANSACTION_DATA_ERROR
// this code is very time dependent, so we need to disable interrupts
#ifndef SERIAL_USE_MULTI_TRANSACTION
int soft_serial_transaction(void) {
int sstd_index = 0;
#else
int soft_serial_transaction(int sstd_index) {
#endif
if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR;
SSTD_t *trans = &Transaction_table[sstd_index];
if (sstd_index > NUM_TOTAL_TRANSACTIONS) return TRANSACTION_TYPE_ERROR;
split_transaction_desc_t *trans = &split_transaction_table[sstd_index];
if (!trans->status) return TRANSACTION_TYPE_ERROR; // not registered
// TODO: remove extra delay between transactions
serial_delay();
@ -244,14 +233,13 @@ int soft_serial_transaction(int sstd_index) {
uint8_t checksum = 0;
// send data to the slave
#ifdef SERIAL_USE_MULTI_TRANSACTION
serial_write_byte(sstd_index); // first chunk is transaction id
sync_recv();
#endif
for (int i = 0; i < trans->initiator2target_buffer_size; ++i) {
serial_write_byte(trans->initiator2target_buffer[i]);
serial_write_byte(split_trans_initiator2target_buffer(trans)[i]);
sync_recv();
checksum += trans->initiator2target_buffer[i];
checksum += split_trans_initiator2target_buffer(trans)[i];
}
serial_write_byte(checksum ^ 7);
sync_recv();
@ -262,9 +250,9 @@ int soft_serial_transaction(int sstd_index) {
// receive data from the slave
uint8_t checksum_computed = 0;
for (int i = 0; i < trans->target2initiator_buffer_size; ++i) {
trans->target2initiator_buffer[i] = serial_read_byte();
split_trans_target2initiator_buffer(trans)[i] = serial_read_byte();
sync_recv();
checksum_computed += trans->target2initiator_buffer[i];
checksum_computed += split_trans_target2initiator_buffer(trans)[i];
}
checksum_computed ^= 7;
uint8_t checksum_received = serial_read_byte();

View File

@ -1,62 +0,0 @@
#pragma once
#include <stdbool.h>
// /////////////////////////////////////////////////////////////////
// Need Soft Serial defines in config.h
// /////////////////////////////////////////////////////////////////
// ex.
// #define SOFT_SERIAL_PIN ?? // ?? = D0,D1,D2,D3,E6
// OPTIONAL: #define SELECT_SOFT_SERIAL_SPEED ? // ? = 1,2,3,4,5
// // 1: about 137kbps (default)
// // 2: about 75kbps
// // 3: about 39kbps
// // 4: about 26kbps
// // 5: about 20kbps
//
// //// USE simple API (using signle-type transaction function)
// /* nothing */
// //// USE flexible API (using multi-type transaction function)
// #define SERIAL_USE_MULTI_TRANSACTION
//
// /////////////////////////////////////////////////////////////////
// Soft Serial Transaction Descriptor
typedef struct _SSTD_t {
uint8_t *status;
uint8_t initiator2target_buffer_size;
uint8_t *initiator2target_buffer;
uint8_t target2initiator_buffer_size;
uint8_t *target2initiator_buffer;
} SSTD_t;
#define TID_LIMIT(table) (sizeof(table) / sizeof(SSTD_t))
// initiator is transaction start side
void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size);
// target is interrupt accept side
void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size);
// initiator result
#define TRANSACTION_END 0
#define TRANSACTION_NO_RESPONSE 0x1
#define TRANSACTION_DATA_ERROR 0x2
#define TRANSACTION_TYPE_ERROR 0x4
#ifndef SERIAL_USE_MULTI_TRANSACTION
int soft_serial_transaction(void);
#else
int soft_serial_transaction(int sstd_index);
#endif
// target status
// *SSTD_t.status has
// initiator:
// TRANSACTION_END
// or TRANSACTION_NO_RESPONSE
// or TRANSACTION_DATA_ERROR
// target:
// TRANSACTION_DATA_ERROR
// or TRANSACTION_ACCEPTED
#define TRANSACTION_ACCEPTED 0x8
#ifdef SERIAL_USE_MULTI_TRANSACTION
int soft_serial_get_and_clean_status(int sstd_index);
#endif

View File

@ -113,37 +113,29 @@ void usart_slave_init(void) {
chThdCreateStatic(waSlaveThread, sizeof(waSlaveThread), HIGHPRIO, SlaveThread, NULL);
}
static SSTD_t* Transaction_table = NULL;
static uint8_t Transaction_table_size = 0;
void soft_serial_initiator_init(void) { usart_master_init(); }
void soft_serial_initiator_init(SSTD_t* sstd_table, int sstd_table_size) {
Transaction_table = sstd_table;
Transaction_table_size = (uint8_t)sstd_table_size;
usart_master_init();
}
void soft_serial_target_init(SSTD_t* sstd_table, int sstd_table_size) {
Transaction_table = sstd_table;
Transaction_table_size = (uint8_t)sstd_table_size;
usart_slave_init();
}
void soft_serial_target_init(void) { usart_slave_init(); }
void handle_soft_serial_slave(void) {
uint8_t sstd_index = sdGet(&SERIAL_USART_DRIVER); // first chunk is always transaction id
SSTD_t* trans = &Transaction_table[sstd_index];
uint8_t sstd_index = sdGet(&SERIAL_USART_DRIVER); // first chunk is always transaction id
split_transaction_desc_t* trans = &split_transaction_table[sstd_index];
// Always write back the sstd_index as part of a basic handshake
sstd_index ^= HANDSHAKE_MAGIC;
sdWrite(&SERIAL_USART_DRIVER, &sstd_index, sizeof(sstd_index));
if (trans->initiator2target_buffer_size) {
sdRead(&SERIAL_USART_DRIVER, trans->initiator2target_buffer, trans->initiator2target_buffer_size);
sdRead(&SERIAL_USART_DRIVER, split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size);
}
// Allow any slave processing to occur
if (trans->slave_callback) {
trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans));
}
if (trans->target2initiator_buffer_size) {
sdWrite(&SERIAL_USART_DRIVER, trans->target2initiator_buffer, trans->target2initiator_buffer_size);
sdWrite(&SERIAL_USART_DRIVER, split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size);
}
if (trans->status) {
@ -160,17 +152,14 @@ void handle_soft_serial_slave(void) {
// TRANSACTION_END
// TRANSACTION_NO_RESPONSE
// TRANSACTION_DATA_ERROR
#ifndef SERIAL_USE_MULTI_TRANSACTION
int soft_serial_transaction(void) {
uint8_t sstd_index = 0;
#else
int soft_serial_transaction(int index) {
uint8_t sstd_index = index;
#endif
if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR;
SSTD_t* trans = &Transaction_table[sstd_index];
msg_t res = 0;
if (sstd_index > NUM_TOTAL_TRANSACTIONS) return TRANSACTION_TYPE_ERROR;
split_transaction_desc_t* trans = &split_transaction_table[sstd_index];
msg_t res = 0;
if (!trans->status) return TRANSACTION_TYPE_ERROR; // not registered
sdClear(&SERIAL_USART_DRIVER);
@ -189,7 +178,7 @@ int soft_serial_transaction(int index) {
}
if (trans->initiator2target_buffer_size) {
res = sdWriteTimeout(&SERIAL_USART_DRIVER, trans->initiator2target_buffer, trans->initiator2target_buffer_size, TIME_MS2I(SERIAL_USART_TIMEOUT));
res = sdWriteTimeout(&SERIAL_USART_DRIVER, split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size, TIME_MS2I(SERIAL_USART_TIMEOUT));
if (res < 0) {
dprintf("serial::usart_transmit NO_RESPONSE\n");
return TRANSACTION_NO_RESPONSE;
@ -197,7 +186,7 @@ int soft_serial_transaction(int index) {
}
if (trans->target2initiator_buffer_size) {
res = sdReadTimeout(&SERIAL_USART_DRIVER, trans->target2initiator_buffer, trans->target2initiator_buffer_size, TIME_MS2I(SERIAL_USART_TIMEOUT));
res = sdReadTimeout(&SERIAL_USART_DRIVER, split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size, TIME_MS2I(SERIAL_USART_TIMEOUT));
if (res < 0) {
dprintf("serial::usart_receive NO_RESPONSE\n");
return TRANSACTION_NO_RESPONSE;

46
drivers/serial.h 100644
View File

@ -0,0 +1,46 @@
/* 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 <transactions.h>
// initiator is transaction start side
void soft_serial_initiator_init(void);
// target is interrupt accept side
void soft_serial_target_init(void);
// initiator result
#define TRANSACTION_END 0
#define TRANSACTION_NO_RESPONSE 0x1
#define TRANSACTION_DATA_ERROR 0x2
#define TRANSACTION_TYPE_ERROR 0x4
int soft_serial_transaction(int sstd_index);
// target status
// *SSTD_t.status has
// initiator:
// TRANSACTION_END
// or TRANSACTION_NO_RESPONSE
// or TRANSACTION_DATA_ERROR
// target:
// TRANSACTION_DATA_ERROR
// or TRANSACTION_ACCEPTED
#define TRANSACTION_ACCEPTED 0x8
int soft_serial_get_and_clean_status(int sstd_index);

View File

@ -65,3 +65,5 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define UNUSED_PINS
#define EE_HANDS
#define LAYER_STATE_8BIT

View File

@ -11,7 +11,7 @@ BOOTMAGIC_ENABLE = no # Virtual DIP switch configuration
MOUSEKEY_ENABLE = yes # Mouse keys
EXTRAKEY_ENABLE = yes # Audio control and System control
CONSOLE_ENABLE = no # Console for debug
COMMAND_ENABLE = yes # Commands for debug and configuration
COMMAND_ENABLE = no # Commands for debug and configuration
# Do not enable SLEEP_LED_ENABLE. it uses the same timer as BACKLIGHT_ENABLE
SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend
# if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work
@ -21,4 +21,4 @@ RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow
BLUETOOTH_ENABLE = no # Enable Bluetooth
AUDIO_ENABLE = no # Audio output
VELOCIKEY_ENABLE = yes
SPLIT_KEYBOARD = yes
SPLIT_KEYBOARD = yes

View File

@ -5,7 +5,7 @@
# See TOP/keyboards/helix/rules.mk for a list of options that can be set.
# See TOP/docs/config_options.md for more information.
#
LTO_ENABLE = no # if firmware size over limit, try this option
LTO_ENABLE = yes # if firmware size over limit, try this option
# Helix Spacific Build Options
# you can uncomment and edit follows 7 Variables

View File

@ -32,3 +32,5 @@ RGBLIGHT_ENABLE = yes # Enable WS2812 RGB underlight.
SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend
SPLIT_KEYBOARD = yes
LTO_ENABLE = yes

View File

@ -33,3 +33,4 @@ SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend
SPLIT_KEYBOARD = yes
ENCODER_ENABLE = yes
LTO_ENABLE = yes

View File

@ -33,3 +33,5 @@ SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend
SPLIT_KEYBOARD = yes
ENCODER_ENABLE = yes
LTO_ENABLE = yes

View File

@ -19,3 +19,4 @@ SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend
SPLIT_KEYBOARD = yes
DEFAULT_FOLDER = keebio/quefrency/rev1
LTO_ENABLE = yes

View File

@ -1,3 +1,5 @@
BACKLIGHT_ENABLE = yes
LAYOUTS = ortho_5x14
LTO_ENABLE = yes

View File

@ -19,16 +19,16 @@ BOOTMAGIC_ENABLE = no # Virtual DIP switch configuration
MOUSEKEY_ENABLE = yes # Mouse keys
EXTRAKEY_ENABLE = yes # Audio control and System control
CONSOLE_ENABLE = no # Console for debug
COMMAND_ENABLE = yes # Commands for debug and configuration
COMMAND_ENABLE = no # Commands for debug and configuration
NKRO_ENABLE = no # Nkey Rollover - if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work
BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality
BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality
MIDI_ENABLE = no # MIDI controls
AUDIO_ENABLE = no # Audio output on port C6
UNICODE_ENABLE = no # Unicode
BLUETOOTH_ENABLE = no # Enable Bluetooth with the Adafruit EZ-Key HID
RGBLIGHT_ENABLE = no # Enable WS2812 RGB underlight.
RGBLIGHT_ENABLE = no # Enable WS2812 RGB underlight.
# Do not enable SLEEP_LED_ENABLE. it uses the same timer as BACKLIGHT_ENABLE
SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend
SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend
SPLIT_KEYBOARD = yes

View File

@ -23,9 +23,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "quantum.h"
#include "split_util.h"
#include "config.h"
#include "transport.h"
#include "transactions.h"
#define ERROR_DISCONNECT_COUNT 5
#ifndef ERROR_DISCONNECT_COUNT
# define ERROR_DISCONNECT_COUNT 5
#endif // ERROR_DISCONNECT_COUNT
#define ROWS_PER_HAND (MATRIX_ROWS / 2)

View File

@ -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

View File

@ -0,0 +1,94 @@
/* 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(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");

View File

@ -0,0 +1,670 @@
/* 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 "debug.h"
#include "matrix.h"
#include "quantum.h"
#include "transactions.h"
#include "transport.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)
static uint8_t crc8(const void *data, size_t len) {
const uint8_t *p = (const uint8_t *)data;
uint8_t crc = 0xff;
size_t i, j;
for (i = 0; i < len; i++) {
crc ^= p[i];
for (j = 0; j < 8; j++) {
if ((crc & 0x80) != 0)
crc = (uint8_t)((crc << 1) ^ 0x31);
else
crc <<= 1;
}
}
return crc;
}
#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
bool transaction_handler_master(bool okay, 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[])) {
if (okay) {
bool this_okay = true;
for (int iter = 1; iter <= 10; ++iter) {
if (!this_okay) {
for (int i = 0; i < iter * iter; ++i) {
wait_us(10);
}
}
ATOMIC_BLOCK_FORCEON { this_okay = handler(master_matrix, slave_matrix); };
if (this_okay) break;
}
okay &= this_okay;
if (!okay) {
dprintf("Failed to execute %s\n", prefix);
}
}
return okay;
}
#define TRANSACTION_HANDLER_MASTER(prefix) \
do { \
okay &= transaction_handler_master(okay, master_matrix, slave_matrix, #prefix, &prefix##_master); \
} while (0)
#define TRANSACTION_HANDLER_SLAVE(prefix) \
do { \
ATOMIC_BLOCK_FORCEON { prefix##_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_handlers)
#define TRANSACTIONS_SLAVE_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(slave_matrix_handlers)
#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_handlers)
# define TRANSACTIONS_MASTER_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(master_matrix_handlers)
# 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_handlers)
# define TRANSACTIONS_ENCODERS_SLAVE() TRANSACTION_HANDLER_SLAVE(encoder_handlers)
# 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_handlers)
# define TRANSACTIONS_SYNC_TIMER_SLAVE() TRANSACTION_HANDLER_SLAVE(sync_timer_handlers)
# 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_handlers)
# define TRANSACTIONS_LAYER_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(layer_state_handlers)
# 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_handlers)
# define TRANSACTIONS_LED_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(led_state_handlers)
# 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_handlers)
# define TRANSACTIONS_MODS_SLAVE() TRANSACTION_HANDLER_SLAVE(mods_handlers)
# 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_handlers)
# define TRANSACTIONS_BACKLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(backlight_handlers)
# 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_handlers)
# define TRANSACTIONS_RGBLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(rgblight_handlers)
# 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_handlers)
# define TRANSACTIONS_LED_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(led_matrix_handlers)
# 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_handlers)
# define TRANSACTIONS_RGB_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(rgb_matrix_handlers)
# 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_handlers)
# define TRANSACTIONS_WPM_SLAVE() TRANSACTION_HANDLER_SLAVE(wpm_handlers)
# 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)
////////////////////////////////////////////////////
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
// 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[]) {
bool okay = true;
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();
return okay;
}
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();
}
#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 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)

View File

@ -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) ((void *)(((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)

View File

@ -1,452 +1,118 @@
/* 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 <debug.h>
#include "config.h"
#include "matrix.h"
#include "quantum.h"
#include "transactions.h"
#include "transport.h"
#include "transaction_id_define.h"
#include "atomic_util.h"
#define ROWS_PER_HAND (MATRIX_ROWS / 2)
#define SYNC_TIMER_OFFSET 2
#ifdef USE_I2C
#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)
# 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;
static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_reg;
# 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)
# 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;
}
// 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");
split_shared_memory_t *const split_shmem = (split_shared_memory_t *)i2c_slave_reg;
void transport_master_init(void) { i2c_init(); }
void transport_slave_init(void) { i2c_slave_init(SLAVE_I2C_ADDRESS); }
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
# 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
// 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);
}
# 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
# 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;
}
}
# 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;
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];
static split_shared_memory_t shared_memory;
split_shared_memory_t *const split_shmem = &shared_memory;
# ifdef ENCODER_ENABLE
uint8_t encoder_state[NUMBER_OF_ENCODERS];
# endif
void transport_master_init(void) { soft_serial_initiator_init(); }
void transport_slave_init(void) { soft_serial_target_init(); }
} 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;
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)); }
# 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();
}
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);
}
}
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;
}
}
# 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) {
if (soft_serial_transaction(id) != TRANSACTION_END) {
return false;
}
# else
transport_rgblight_master();
if (soft_serial_transaction(GET_SLAVE_MATRIX) != 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
#endif // USE_I2C
// 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
bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { return transactions_master(master_matrix, slave_matrix); }
# ifdef ENCODER_ENABLE
encoder_state_raw((uint8_t *)serial_s2m_buffer.encoder_state);
# endif
# 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
}
#endif
void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { transactions_slave(master_matrix, slave_matrix); }

View File

@ -1,10 +1,175 @@
/* 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(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;

View File

@ -17,6 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <stdint.h>
//#include <avr/interrupt.h>
#include "keyboard.h"
#include "keycode.h"
#include "host.h"
#include "util.h"
@ -35,15 +36,20 @@ void host_set_driver(host_driver_t *d) { driver = d; }
host_driver_t *host_get_driver(void) { return driver; }
#ifdef SPLIT_KEYBOARD
uint8_t split_led_state = 0;
void set_split_host_keyboard_leds(uint8_t led_state) { split_led_state = led_state; }
#endif
uint8_t host_keyboard_leds(void) {
#ifdef SPLIT_KEYBOARD
if (!is_keyboard_master()) return split_led_state;
#endif
if (!driver) return 0;
return (*driver->keyboard_leds)();
}
led_t host_keyboard_led_state(void) {
if (!driver) return (led_t){0};
return (led_t)((*driver->keyboard_leds)());
}
led_t host_keyboard_led_state(void) { return (led_t)host_keyboard_leds(); }
/* send report */
void host_keyboard_send(report_keyboard_t *report) {