qmk_firmware

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

serial_protocol.c (5764B)


      1 // Copyright 2022 Stefan Kerkmann
      2 // SPDX-License-Identifier: GPL-2.0-or-later
      3 
      4 #include <ch.h>
      5 
      6 #include "serial.h"
      7 #include "serial_protocol.h"
      8 #include "synchronization_util.h"
      9 
     10 static inline bool initiate_transaction(uint8_t transaction_id);
     11 static inline bool react_to_transaction(void);
     12 
     13 /**
     14  * @brief This thread runs on the slave and responds to transactions initiated
     15  * by the master.
     16  */
     17 static THD_WORKING_AREA(waSlaveThread, 1024);
     18 static THD_FUNCTION(SlaveThread, arg) {
     19     (void)arg;
     20     chRegSetThreadName("split_protocol_tx_rx");
     21 
     22     while (true) {
     23         if (unlikely(!react_to_transaction())) {
     24             /* Clear the receive queue, to start with a clean slate.
     25              * Parts of failed transactions or spurious bytes could still be in it. */
     26             serial_transport_driver_clear();
     27         }
     28     }
     29 }
     30 
     31 /**
     32  * @brief Slave specific initializations.
     33  */
     34 void soft_serial_target_init(void) {
     35     serial_transport_driver_slave_init();
     36 
     37     /* Start transport thread. */
     38     chThdCreateStatic(waSlaveThread, sizeof(waSlaveThread), HIGHPRIO, SlaveThread, NULL);
     39 }
     40 
     41 /**
     42  * @brief Master specific initializations.
     43  */
     44 void soft_serial_initiator_init(void) {
     45     serial_transport_driver_master_init();
     46 }
     47 
     48 /**
     49  * @brief React to transactions started by the master.
     50  */
     51 static inline bool react_to_transaction(void) {
     52     uint8_t transaction_id = 0;
     53     /* Wait until there is a transaction for us. */
     54     if (unlikely(!serial_transport_receive_blocking(&transaction_id, sizeof(transaction_id)))) {
     55         return false;
     56     }
     57 
     58     /* Sanity check that we are actually responding to a valid transaction. */
     59     if (unlikely(transaction_id >= NUM_TOTAL_TRANSACTIONS)) {
     60         return false;
     61     }
     62 
     63     split_shared_memory_lock_autounlock();
     64 
     65     split_transaction_desc_t* transaction = &split_transaction_table[transaction_id];
     66 
     67     /* Send back the handshake which is XORed as a simple checksum,
     68      to signal that the slave is ready to receive possible transaction buffers  */
     69     transaction_id ^= NUM_TOTAL_TRANSACTIONS;
     70     if (unlikely(!serial_transport_send(&transaction_id, sizeof(transaction_id)))) {
     71         return false;
     72     }
     73 
     74     /* Receive transaction buffer from the master. If this transaction requires it.*/
     75     if (transaction->initiator2target_buffer_size) {
     76         if (unlikely(!serial_transport_receive(split_trans_initiator2target_buffer(transaction), transaction->initiator2target_buffer_size))) {
     77             return false;
     78         }
     79     }
     80 
     81     /* Allow any slave processing to occur. */
     82     if (transaction->slave_callback) {
     83         transaction->slave_callback(transaction->initiator2target_buffer_size, split_trans_initiator2target_buffer(transaction), transaction->initiator2target_buffer_size, split_trans_target2initiator_buffer(transaction));
     84     }
     85 
     86     /* Send transaction buffer to the master. If this transaction requires it. */
     87     if (transaction->target2initiator_buffer_size) {
     88         if (unlikely(!serial_transport_send(split_trans_target2initiator_buffer(transaction), transaction->target2initiator_buffer_size))) {
     89             return false;
     90         }
     91     }
     92 
     93     return true;
     94 }
     95 
     96 /**
     97  * @brief Start transaction from the master half to the slave half.
     98  *
     99  * @param index Transaction Table index of the transaction to start.
    100  * @return bool Indicates success of transaction.
    101  */
    102 bool soft_serial_transaction(int index) {
    103     /* Clear the receive queue, to start with a clean slate.
    104      * Parts of failed transactions or spurious bytes could still be in it. */
    105     serial_transport_driver_clear();
    106 
    107     return initiate_transaction((uint8_t)index);
    108 }
    109 
    110 /**
    111  * @brief Initiate transaction to slave half.
    112  */
    113 static inline bool initiate_transaction(uint8_t transaction_id) {
    114     /* Sanity check that we are actually starting a valid transaction. */
    115     if (unlikely(transaction_id >= NUM_TOTAL_TRANSACTIONS)) {
    116         serial_dprintf("SPLIT: illegal transaction id\n");
    117         return false;
    118     }
    119 
    120     split_shared_memory_lock_autounlock();
    121 
    122     split_transaction_desc_t* transaction = &split_transaction_table[transaction_id];
    123 
    124     /* Send transaction table index to the slave, which doubles as basic handshake token. */
    125     if (unlikely(!serial_transport_send(&transaction_id, sizeof(transaction_id)))) {
    126         serial_dprintf("SPLIT: sending handshake failed\n");
    127         return false;
    128     }
    129 
    130     uint8_t transaction_id_shake = 0xFF;
    131 
    132     /* Which we always read back first so that we can error out correctly.
    133      *   - due to the half duplex limitations on return codes, we always have to read *something*.
    134      *   - without the read, write only transactions *always* succeed, even during the boot process where the slave is not ready.
    135      */
    136     if (unlikely(!serial_transport_receive(&transaction_id_shake, sizeof(transaction_id_shake)) || (transaction_id_shake != (transaction_id ^ NUM_TOTAL_TRANSACTIONS)))) {
    137         serial_dprintf("SPLIT: receiving handshake failed\n");
    138         return false;
    139     }
    140 
    141     /* Send transaction buffer to the slave. If this transaction requires it. */
    142     if (transaction->initiator2target_buffer_size) {
    143         if (unlikely(!serial_transport_send(split_trans_initiator2target_buffer(transaction), transaction->initiator2target_buffer_size))) {
    144             serial_dprintf("SPLIT: sending buffer failed\n");
    145             return false;
    146         }
    147     }
    148 
    149     /* Receive transaction buffer from the slave. If this transaction requires it. */
    150     if (transaction->target2initiator_buffer_size) {
    151         if (unlikely(!serial_transport_receive(split_trans_target2initiator_buffer(transaction), transaction->target2initiator_buffer_size))) {
    152             serial_dprintf("SPLIT: receiving buffer failed\n");
    153             return false;
    154         }
    155     }
    156 
    157     return true;
    158 }