diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 262a9ee4..c19645bc 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -github: meshcore-dev +github: IoTThinks diff --git a/docs/cli_command_availability.md b/docs/cli_command_availability.md index 4a84f9de..24d508ac 100644 --- a/docs/cli_command_availability.md +++ b/docs/cli_command_availability.md @@ -79,6 +79,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without | Radio | [`get freq`; `set freq `](cli_commands.md#view-or-change-this-nodes-frequency) | `set` is local serial only | Yes | Yes | Yes | | Radio | [`get/set radio.rxgain`](cli_commands.md#view-or-change-this-nodes-rx-boosted-gain-mode-sx12xx-and-lr1110-v1141) | Supported radio | Feature | Feature | Feature | | Radio | [`get/set radio.fem.rxgain`](cli_commands.md#view-or-change-the-lora-fem-receive-path-gain-state-on-supported-boards) | Controllable LoRa FEM | Feature | Feature | Feature | +| Radio | [`get/set radio.fem.txgain`](cli_commands.md#view-or-change-the-lora-fem-transmit-path-gain-state-on-supported-boards) | Controllable LoRa FEM | Feature | Feature | Feature | | Radio | [`get/set radio.rxps`; `get rxps.wd`](#radio-rxps) | RX power-saving support | Feature | Feature | Feature | | System | [`get/set name`](cli_commands.md#view-or-change-this-nodes-name) | All text CLI roles | Yes | Yes | Yes | | System | [`get/set lat`](cli_commands.md#view-or-change-this-nodes-latitude) | All text CLI roles | Yes | Yes | Yes | @@ -234,6 +235,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without | Radio | [`get freq`; `set freq `](cli_commands.md#view-or-change-this-nodes-frequency) | `set` is local serial only | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Radio | [`get/set radio.rxgain`](cli_commands.md#view-or-change-this-nodes-rx-boosted-gain-mode-sx12xx-and-lr1110-v1141) | Supported radio | Feature | Feature | Feature | Feature | Feature | Feature | Feature | | Radio | [`get/set radio.fem.rxgain`](cli_commands.md#view-or-change-the-lora-fem-receive-path-gain-state-on-supported-boards) | Controllable LoRa FEM | Feature | Feature | Feature | Feature | Feature | Feature | Feature | +| Radio | [`get/set radio.fem.txgain`](cli_commands.md#view-or-change-the-lora-fem-transmit-path-gain-state-on-supported-boards) | Controllable LoRa FEM | Feature | Feature | Feature | Feature | Feature | Feature | Feature | | Radio | [`get/set radio.rxps`; `get rxps.wd`](#radio-rxps) | Full parser and RX power-saving support | Feature | Feature | Feature | No | No | Feature | Feature | | System | [`get/set name`](cli_commands.md#view-or-change-this-nodes-name) | All full-parser text CLI roles | Yes | Yes | Yes | No | No | Yes | Yes | | System | [`get/set lat`](cli_commands.md#view-or-change-this-nodes-latitude) | All full-parser text CLI roles | Yes | Yes | Yes | No | No | Yes | Yes | diff --git a/docs/cli_commands.md b/docs/cli_commands.md index aed3765b..89a42114 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -605,6 +605,22 @@ Station G2/G3 targets default to `off`. --- +#### View or change the LoRa FEM transmit-path gain state on supported boards +**Usage:** +- `get radio.fem.txgain` +- `set radio.fem.txgain ` + +**Parameters:** +- `state`: `on`|`off` + +**Notes:** +- This controls a software-selectable external LoRa FEM transmit gain where the board supports it. +- On Station G3, remove the PA PL1 jumper to allow software control. `on` selects PA PL1 high/short and `off` selects PA PL1 low/open. The PA PL2 hardware jumper determines whether this switches between power levels 1/3 or 2/4. +- Select an operating level and SX1262 transmit power that comply with local RF limits and the Station G3 power-supply requirements. +- The setting is saved immediately, but on Station G3 the level is applied to the hardware at the start of the next transmit, so that the PA supply rail is never re-targeted while the PA is being driven. `get` reports the configured state, which may lead the hardware until the node next transmits. + +--- + ### System #### View or change this node's name diff --git a/docs/companion_radio_full.md b/docs/companion_radio_full.md index 7355978f..87c2f010 100644 --- a/docs/companion_radio_full.md +++ b/docs/companion_radio_full.md @@ -57,9 +57,13 @@ boost** switch. From the USB terminal, use: get radio.fem.rxgain set radio.fem.rxgain off set radio.fem.rxgain on +get radio.fem.txgain +set radio.fem.txgain off +set radio.fem.txgain on ``` -The selected state is applied immediately and retained after reboot. +The selected states are applied immediately and retained after reboot. FEM TX +gain is reported as unsupported on boards without software-selectable PA gain. Artifacts are written to `out/` by default. diff --git a/docs/terminal_chat_cli.md b/docs/terminal_chat_cli.md index 84ab7431..6044212c 100644 --- a/docs/terminal_chat_cli.md +++ b/docs/terminal_chat_cli.md @@ -87,12 +87,14 @@ Sets the transmit air-time-factor. Deprecated - use `set dutycycle` instead. ``` get radio.fem.rxgain set radio.fem.rxgain {on|off} +get radio.fem.txgain +set radio.fem.txgain {on|off} ``` -Shows or changes the external FEM receive-path LNA on supported Heltec boards. -The change is applied immediately, saved across reboots, and recalibrates the -radio noise floor. This is separate from the SX126x `radio.rxgain` setting. -Boards whose FEM receive path cannot be controlled report the setting as -unsupported. +Shows or changes the external FEM receive-path LNA or transmit-path gain on +supported boards. Changes are applied immediately and saved across reboots; +changing RX gain also recalibrates the radio noise floor. These settings are +separate from the SX126x `radio.rxgain` setting. Boards without the respective +control report it as unsupported. ``` diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 8f92fc4a..fbf590d6 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -320,6 +320,9 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no if (file.available() >= (int)sizeof(_prefs.vibe_quiet)) { file.read((uint8_t *)&_prefs.vibe_quiet, sizeof(_prefs.vibe_quiet)); // 124 } + if (file.available() >= (int)sizeof(_prefs.radio_fem_txgain)) { + file.read((uint8_t *)&_prefs.radio_fem_txgain, sizeof(_prefs.radio_fem_txgain)); // 125 + } file.close(); } @@ -369,6 +372,8 @@ bool DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ sizeof(_prefs.radio_fem_rxgain_override)) == sizeof(_prefs.radio_fem_rxgain_override); // 123 success = success && file.write((uint8_t *)&_prefs.vibe_quiet, sizeof(_prefs.vibe_quiet)) == sizeof(_prefs.vibe_quiet); // 124 + success = success && file.write((uint8_t *)&_prefs.radio_fem_txgain, + sizeof(_prefs.radio_fem_txgain)) == sizeof(_prefs.radio_fem_txgain); // 125 #if defined(NRF52_PLATFORM) success = file.commit(success); diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9e381cc1..9db83d28 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1357,6 +1357,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.gps_interval = 0; // No automatic GPS updates by default _prefs.autoadd_config = DEFAULT_AUTOADD_CONFIG; _prefs.path_hash_mode = DEFAULT_PATH_HASH_MODE; + _prefs.radio_fem_txgain = 0; //_prefs.rx_delay_base = 10.0f; enable once new algo fixed _prefs.setRepeatEn(false); #if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110) \ @@ -1436,6 +1437,7 @@ void MyMesh::begin(bool has_display) { _prefs.radio_fem_rxgain = DEFAULT_FEM_RX_GAIN; } _prefs.radio_fem_rxgain = constrain(_prefs.radio_fem_rxgain, 0, 1); + _prefs.radio_fem_txgain = constrain(_prefs.radio_fem_txgain, 0, 1); #ifdef BLE_PIN_CODE // 123456 by default if (_prefs.ble_pin == 0) { @@ -1484,6 +1486,7 @@ void MyMesh::begin(bool has_display) { if (board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain) && fem_gain_changed) { _radio->recalibrateNoiseFloor(); } + board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain); MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s", radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled"); @@ -2047,6 +2050,19 @@ void MyMesh::execCommand(char* cmd, char* reply) { } return; } + if (strcmp(key, "radio.fem.txgain") == 0) { + bool enabled; + if (!wcParseBool(value, enabled)) { + strcpy(reply, "Error: must be on or off"); + } else if (!board.canControlLoRaFemPaGain()) { + strcpy(reply, "Error: unsupported"); + } else if (!applyAndSaveFemTxGain(enabled)) { + strcpy(reply, "Error: failed to apply FEM TX gain"); + } else { + strcpy(reply, "OK"); + } + return; + } #ifdef WITH_MQTT_BRIDGE bool changed = false; @@ -3450,6 +3466,15 @@ bool MyMesh::applyAndSaveFemRxGain(bool enabled) { return true; } +bool MyMesh::applyAndSaveFemTxGain(bool enabled) { + if (!board.canControlLoRaFemPaGain()) return false; + if (!board.setLoRaFemPaGainEnabled(enabled)) return false; + + _prefs.radio_fem_txgain = enabled ? 1 : 0; + savePrefs(); + return true; +} + #ifdef ENABLE_USB_INTERFACE void MyMesh::enterTerminalMode() { _terminal_mode = true; @@ -4236,6 +4261,13 @@ void MyMesh::handleTerminalCommand(char* command) { Serial.printf(" FEM RX gain: %s\r\n", board.isLoRaFemLnaEnabled() ? "on" : "off"); } + } else if (strcmp(command, "get radio.fem.txgain") == 0) { + if (!board.canControlLoRaFemPaGain()) { + Serial.print(" ERROR: FEM TX gain control is unsupported on this board\r\n"); + } else { + Serial.printf(" FEM TX gain: %s\r\n", + board.isLoRaFemPaGainEnabled() ? "on" : "off"); + } } else if (strncmp(command, "set ", 4) == 0) { const char* config = command + 4; if (strncmp(config, "af ", 3) == 0) { @@ -4276,6 +4308,20 @@ void MyMesh::handleTerminalCommand(char* command) { } else { Serial.printf(" OK - FEM RX gain %s\r\n", value); } + } else if (strncmp(config, "radio.fem.txgain", 16) == 0 + && (config[16] == 0 || config[16] == ' ' + || config[16] == '\t')) { + const char* value = config + 16; + while (*value == ' ' || *value == '\t') value++; + if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) { + Serial.print(" ERROR: use set radio.fem.txgain \r\n"); + } else if (!board.canControlLoRaFemPaGain()) { + Serial.print(" ERROR: FEM TX gain control is unsupported on this board\r\n"); + } else if (!applyAndSaveFemTxGain(strcmp(value, "on") == 0)) { + Serial.print(" ERROR: failed to apply FEM TX gain\r\n"); + } else { + Serial.printf(" OK - FEM TX gain %s\r\n", value); + } } else { Serial.printf(" ERROR: unknown setting: %s\r\n", config); } @@ -4287,6 +4333,8 @@ void MyMesh::handleTerminalCommand(char* command) { Serial.print(" set {name|lat|lon|freq|tx|af} {value}\r\n"); Serial.print(" get radio.fem.rxgain\r\n"); Serial.print(" set radio.fem.rxgain \r\n"); + Serial.print(" get radio.fem.txgain\r\n"); + Serial.print(" set radio.fem.txgain \r\n"); Serial.print(" card\r\n"); Serial.print(" import \r\n"); Serial.print(" clock\r\n"); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 1678b08e..45d7a9ee 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -277,6 +277,7 @@ private: void checkCLIRescueCmd(); void checkSerialInterface(); bool applyAndSaveFemRxGain(bool enabled); + bool applyAndSaveFemTxGain(bool enabled); #ifdef ENABLE_USB_INTERFACE ContactInfo* getTerminalRecipient(); void printTerminalPath(const ContactInfo& recipient); diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 9537607e..214852dc 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -37,6 +37,7 @@ struct NodePrefs { // persisted to file uint8_t default_scope_key[16]; uint8_t radio_fem_rxgain_override; // 1 once the user overrides the build default uint8_t vibe_quiet; // haptic quiet mode; appended for prefs compatibility + uint8_t radio_fem_txgain; // LoRa FEM TX gain; appended for prefs compatibility // Keep the upstream repeat API while retaining the existing binary prefs // layout used by this branch. diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 8ed1fcb5..f59a8984 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -3030,6 +3030,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.rx_boosted_gain = mesh::radio::configuredRxBoostedGainDefault(); #endif _prefs.radio_fem_rxgain = 1; // LoRa FEM RX gain on by default (FEM boards) + _prefs.radio_fem_txgain = 0; pending_discover_tag = 0; pending_discover_until = 0; @@ -3200,6 +3201,7 @@ void MyMesh::begin(FILESYSTEM *fs) { if (board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain) && fem_gain_changed) { _radio->recalibrateNoiseFloor(); } + board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain); setRxPowerSaving(_prefs.rx_powersaving_enabled, _prefs.rx_ps_rx_us, _prefs.rx_ps_sleep_us); updateAdvertTimer(); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 08406ba8..8edec37d 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -1164,6 +1164,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.rx_boosted_gain = mesh::radio::configuredRxBoostedGainDefault(); #endif _prefs.radio_fem_rxgain = 1; + _prefs.radio_fem_txgain = 0; // Observer defaults (alert.*, etc.) moved to applyMQTTDefaults() - they live // in /mqtt_prefs now, not NodePrefs. @@ -1241,6 +1242,7 @@ void MyMesh::begin(FILESYSTEM *fs) { if (board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain) && fem_gain_changed) { _radio->recalibrateNoiseFloor(); } + board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain); setRxPowerSaving(_prefs.rx_powersaving_enabled, _prefs.rx_ps_rx_us, _prefs.rx_ps_sleep_us); updateAdvertTimer(); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index bc169275..8f7b3fb1 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -931,6 +931,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.gps_interval = 0; _prefs.advert_loc_policy = ADVERT_LOC_PREFS; _prefs.radio_fem_rxgain = 1; + _prefs.radio_fem_txgain = 0; memset(default_scope.key, 0, sizeof(default_scope.key)); } @@ -974,6 +975,7 @@ void SensorMesh::begin(FILESYSTEM* fs) { if (board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain) && fem_gain_changed) { _radio->recalibrateNoiseFloor(); } + board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain); setRxPowerSaving(_prefs.rx_powersaving_enabled, _prefs.rx_ps_rx_us, _prefs.rx_ps_sleep_us); updateAdvertTimer(); diff --git a/src/Identity.cpp b/src/Identity.cpp index 75a99711..f2e9da03 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -5,8 +5,9 @@ #include #ifdef USE_CC310_HW_CRYPTO -#include +#include "helpers/NRF52Crypto.h" #include "nrf_cc310/include/crys_ec_edw_api.h" +#include "nrf_cc310/include/crys_ec_mont_edw_error.h" #endif namespace mesh { @@ -21,20 +22,29 @@ Identity::Identity(const char* pub_hex) { } bool Identity::verify(const uint8_t* sig, const uint8_t* message, int msg_len) const { + if (sig == NULL || message == NULL || msg_len < 0) return false; + #ifdef USE_CC310_HW_CRYPTO // nRF52840 CryptoCell CC310 hardware Ed25519 verification. The software // implementations need ~3KB of stack (which can overflow the Adafruit core's // 4KB loop task stack from the advert receive path); the hardware path - // needs much less, around 600-700bytes. The CC310 workspace is static, faster, - // should save power at scale as well. - static CRYS_ECEDW_TempBuff_t cc310_tmp; - nRFCrypto.begin(); - CRYSError_t rc = CRYS_ECEDW_Verify((uint8_t*)sig, CRYS_ECEDW_SIGNATURE_BYTES, - (uint8_t*)pub_key, CRYS_ECEDW_MOD_SIZE_IN_BYTES, - (uint8_t*)message, (size_t)msg_len, &cc310_tmp); - nRFCrypto.end(); - return rc == CRYS_OK; -#elif 0 + // needs much less, around 600-700 bytes. Access is serialized because the + // workspace and the CC310 driver are not reentrant. + CC310CryptoSession session; + if (session) { + static CRYS_ECEDW_TempBuff_t cc310_tmp; + const CRYSError_t rc = CRYS_ECEDW_Verify( + const_cast(sig), CRYS_ECEDW_SIGNATURE_BYTES, + const_cast(pub_key), CRYS_ECEDW_MOD_SIZE_IN_BYTES, + const_cast(message), static_cast(msg_len), &cc310_tmp); + if (rc == CRYS_OK) return true; + // A normal signature mismatch is a conclusive hardware result. Other + // CC310 errors fall through to the correctness-preserving software path. + if (rc == CRYS_ECEDW_SIGN_VERIFY_FAILED_ERROR) return false; + } +#endif + +#if 0 // NOTE: memory corruption bug was found in this function!! return ed25519_verify(sig, message, msg_len, pub_key); #else diff --git a/src/MeshCore.h b/src/MeshCore.h index ec390899..b389f8ec 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -93,6 +93,10 @@ public: virtual bool setLoRaFemLnaEnabled(bool enable) { return false; } virtual bool canControlLoRaFemLna() const { return false; } virtual bool isLoRaFemLnaEnabled() const { return false; } + // Software-selectable external FEM transmit gain. This is not a PA power switch. + virtual bool setLoRaFemPaGainEnabled(bool enable) { return false; } + virtual bool canControlLoRaFemPaGain() const { return false; } + virtual bool isLoRaFemPaGainEnabled() const { return false; } #if defined(ENABLE_OTA) // 4-byte build-target discriminator for OTA-over-LoRa (docs/ota_protocol.md Section 9). Default is the // MOTA_TARGET_ID build flag injected by build.sh; 0 when unset (e.g. a bare IDE build). diff --git a/src/Utils.cpp b/src/Utils.cpp index bd97937b..0bd75263 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -3,7 +3,7 @@ #include #ifdef USE_CC310_HW_CRYPTO -#include +#include "helpers/NRF52Crypto.h" #include "nrf_cc310/include/crys_hash.h" #include "nrf_cc310/include/crys_hmac.h" #include "nrf_cc310/include/ssi_aes.h" @@ -13,6 +13,142 @@ #include #endif +namespace { + +void sha256Software(uint8_t* hash, size_t hash_len, const uint8_t* msg, int msg_len) { + SHA256 sha; + sha.update(msg, msg_len); + sha.finalize(hash, hash_len); +} + +void sha256Software(uint8_t* hash, size_t hash_len, + const uint8_t* frag1, int frag1_len, + const uint8_t* frag2, int frag2_len) { + SHA256 sha; + sha.update(frag1, frag1_len); + sha.update(frag2, frag2_len); + sha.finalize(hash, hash_len); +} + +int decryptSoftware(const uint8_t* shared_secret, uint8_t* dest, + const uint8_t* src, int src_len) { + AES128 aes; + uint8_t* dp = dest; + const uint8_t* sp = src; + + aes.setKey(shared_secret, CIPHER_KEY_SIZE); + while (sp - src < src_len) { + aes.decryptBlock(dp, sp); + dp += CIPHER_BLOCK_SIZE; + sp += CIPHER_BLOCK_SIZE; + } + return static_cast(sp - src); +} + +int encryptSoftware(const uint8_t* shared_secret, uint8_t* dest, + const uint8_t* src, int src_len) { + AES128 aes; + uint8_t* dp = dest; + + aes.setKey(shared_secret, CIPHER_KEY_SIZE); + while (src_len >= CIPHER_BLOCK_SIZE) { + aes.encryptBlock(dp, src); + dp += CIPHER_BLOCK_SIZE; + src += CIPHER_BLOCK_SIZE; + src_len -= CIPHER_BLOCK_SIZE; + } + if (src_len > 0) { + uint8_t tmp[CIPHER_BLOCK_SIZE] = {}; + memcpy(tmp, src, src_len); + aes.encryptBlock(dp, tmp); + dp += CIPHER_BLOCK_SIZE; + } + return static_cast(dp - dest); +} + +void hmacSoftware(const uint8_t* shared_secret, uint8_t* dest, + const uint8_t* src, int src_len) { + SHA256 sha; + sha.resetHMAC(shared_secret, PUB_KEY_SIZE); + sha.update(src, src_len); + sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, dest, CIPHER_MAC_SIZE); +} + +#ifdef USE_CC310_HW_CRYPTO + +bool rangesOverlap(const uint8_t* first, size_t first_len, + const uint8_t* second, size_t second_len) { + const uintptr_t first_addr = reinterpret_cast(first); + const uintptr_t second_addr = reinterpret_cast(second); + if (first_addr <= second_addr) return second_addr - first_addr < first_len; + return first_addr - second_addr < second_len; +} + +// Returns -1 when the hardware path is unavailable or any CC310 call fails. +// Callers retain the original input and can recompute the complete result in +// software. +int aesHardware(bool encrypting, const uint8_t* shared_secret, uint8_t* dest, + const uint8_t* src, int src_len) { + mesh::CC310CryptoSession session; + if (!session) return -1; + + static SaSiAesUserContext_t ctx; + SaSiAesUserKeyData_t key_data = { + const_cast(shared_secret), CIPHER_KEY_SIZE + }; + SaSiError_t rc = SaSi_AesInit( + &ctx, encrypting ? SASI_AES_ENCRYPT : SASI_AES_DECRYPT, + SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE); + const bool initialized = rc == SASI_OK; + if (rc == SASI_OK) { + rc = SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &key_data, sizeof(key_data)); + } + + uint8_t* dp = dest; + const uint8_t* sp = src; + int remaining = src_len; + while (rc == SASI_OK && remaining >= CIPHER_BLOCK_SIZE) { + rc = SaSi_AesBlock(&ctx, const_cast(sp), CIPHER_BLOCK_SIZE, dp); + if (rc == SASI_OK) { + dp += CIPHER_BLOCK_SIZE; + sp += CIPHER_BLOCK_SIZE; + remaining -= CIPHER_BLOCK_SIZE; + } + } + if (rc == SASI_OK && encrypting && remaining > 0) { + uint8_t padded[CIPHER_BLOCK_SIZE] = {}; + memcpy(padded, sp, remaining); + rc = SaSi_AesBlock(&ctx, padded, CIPHER_BLOCK_SIZE, dp); + if (rc == SASI_OK) dp += CIPHER_BLOCK_SIZE; + } + + size_t final_size = 0; + if (rc == SASI_OK) { + rc = SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &final_size); + } + const SaSiError_t free_rc = initialized ? SaSi_AesFree(&ctx) : SASI_OK; + if (rc != SASI_OK || free_rc != SASI_OK) return -1; + return static_cast(dp - dest); +} + +bool hmacHardware(const uint8_t* shared_secret, uint8_t* dest, + const uint8_t* src, int src_len) { + mesh::CC310CryptoSession session; + if (!session) return false; + + static CRYS_HASH_Result_t result; + const CRYSError_t rc = CRYS_HMAC( + CRYS_HASH_SHA256_mode, const_cast(shared_secret), PUB_KEY_SIZE, + const_cast(src), static_cast(src_len), result); + if (rc != CRYS_OK) return false; + memcpy(dest, result, CIPHER_MAC_SIZE); + return true; +} + +#endif + +} // namespace + namespace mesh { uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) { @@ -23,35 +159,53 @@ uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) { void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_len) { #ifdef USE_CC310_HW_CRYPTO - static CRYS_HASH_Result_t result; - nRFCrypto.begin(); - CRYS_HASH(CRYS_HASH_SHA256_mode, (uint8_t*)msg, (size_t)msg_len, result); - nRFCrypto.end(); - memcpy(hash, result, hash_len); -#else - SHA256 sha; - sha.update(msg, msg_len); - sha.finalize(hash, hash_len); + if (hash_len <= SHA256::HASH_SIZE && msg_len >= 0) { + CC310CryptoSession session; + if (session) { + static CRYS_HASH_Result_t result; + const CRYSError_t rc = CRYS_HASH( + CRYS_HASH_SHA256_mode, const_cast(msg), + static_cast(msg_len), result); + if (rc == CRYS_OK) { + memcpy(hash, result, hash_len); + return; + } + } + } #endif + sha256Software(hash, hash_len, msg, msg_len); } void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int frag1_len, const uint8_t* frag2, int frag2_len) { #ifdef USE_CC310_HW_CRYPTO - static CRYS_HASHUserContext_t ctx; - static CRYS_HASH_Result_t result; - nRFCrypto.begin(); - CRYS_HASH_Init(&ctx, CRYS_HASH_SHA256_mode); - CRYS_HASH_Update(&ctx, (uint8_t*)frag1, (size_t)frag1_len); - CRYS_HASH_Update(&ctx, (uint8_t*)frag2, (size_t)frag2_len); - CRYS_HASH_Finish(&ctx, result); - nRFCrypto.end(); - memcpy(hash, result, hash_len); -#else - SHA256 sha; - sha.update(frag1, frag1_len); - sha.update(frag2, frag2_len); - sha.finalize(hash, hash_len); + if (hash_len <= SHA256::HASH_SIZE && frag1_len > 0 && frag2_len > 0) { + CC310CryptoSession session; + if (session) { + static CRYS_HASHUserContext_t ctx; + static CRYS_HASH_Result_t result; + CRYSError_t rc = CRYS_HASH_Init(&ctx, CRYS_HASH_SHA256_mode); + const bool initialized = rc == CRYS_OK; + if (rc == CRYS_OK) { + rc = CRYS_HASH_Update(&ctx, const_cast(frag1), + static_cast(frag1_len)); + } + if (rc == CRYS_OK) { + rc = CRYS_HASH_Update(&ctx, const_cast(frag2), + static_cast(frag2_len)); + } + if (rc == CRYS_OK) rc = CRYS_HASH_Finish(&ctx, result); + if (rc == CRYS_OK) { + memcpy(hash, result, hash_len); + return; + } + if (initialized) { + const bool context_freed = CRYS_HASH_Free(&ctx) == CRYS_OK; + (void) context_freed; + } + } + } #endif + sha256Software(hash, hash_len, frag1, frag1_len, frag2, frag2_len); } int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) { @@ -61,100 +215,38 @@ int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s } #ifdef USE_CC310_HW_CRYPTO - static SaSiAesUserContext_t ctx; - SaSiAesUserKeyData_t keyData = { (uint8_t*)shared_secret, CIPHER_KEY_SIZE }; - uint8_t* dp = dest; - const uint8_t* sp = src; - size_t dummy_out = 0; - - nRFCrypto.begin(); - SaSi_AesInit(&ctx, SASI_AES_DECRYPT, SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE); - SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &keyData, sizeof(keyData)); - while (sp - src < src_len) { - SaSi_AesBlock(&ctx, (uint8_t*)sp, 16, dp); - dp += 16; sp += 16; + if (!rangesOverlap(dest, static_cast(src_len), + src, static_cast(src_len))) { + const int hardware_len = aesHardware(false, shared_secret, dest, src, src_len); + if (hardware_len >= 0) return hardware_len; } - SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out); - SaSi_AesFree(&ctx); - nRFCrypto.end(); - return sp - src; -#else - AES128 aes; - uint8_t* dp = dest; - const uint8_t* sp = src; - - aes.setKey(shared_secret, CIPHER_KEY_SIZE); - while (sp - src < src_len) { - aes.decryptBlock(dp, sp); - dp += 16; sp += 16; - } - - return sp - src; // will always be multiple of 16 #endif + return decryptSoftware(shared_secret, dest, src, src_len); } int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) { + if (shared_secret == NULL || dest == NULL || src == NULL || src_len <= 0) return 0; + #ifdef USE_CC310_HW_CRYPTO - static SaSiAesUserContext_t ctx; - SaSiAesUserKeyData_t keyData = { (uint8_t*)shared_secret, CIPHER_KEY_SIZE }; - uint8_t* dp = dest; - size_t dummy_out = 0; - - nRFCrypto.begin(); - SaSi_AesInit(&ctx, SASI_AES_ENCRYPT, SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE); - SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &keyData, sizeof(keyData)); - while (src_len >= 16) { - SaSi_AesBlock(&ctx, (uint8_t*)src, 16, dp); - dp += 16; src += 16; src_len -= 16; + const size_t output_len = static_cast( + (src_len + CIPHER_BLOCK_SIZE - 1) / CIPHER_BLOCK_SIZE * CIPHER_BLOCK_SIZE); + if (!rangesOverlap(dest, output_len, src, static_cast(src_len))) { + const int hardware_len = aesHardware(true, shared_secret, dest, src, src_len); + if (hardware_len >= 0) return hardware_len; } - if (src_len > 0) { // remaining partial block — zero-pad to 16 bytes - uint8_t tmp[16] = {}; - memcpy(tmp, src, src_len); - SaSi_AesBlock(&ctx, tmp, 16, dp); - dp += 16; - } - SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out); - SaSi_AesFree(&ctx); - nRFCrypto.end(); - return dp - dest; -#else - AES128 aes; - uint8_t* dp = dest; - - aes.setKey(shared_secret, CIPHER_KEY_SIZE); - while (src_len >= 16) { - aes.encryptBlock(dp, src); - dp += 16; src += 16; src_len -= 16; - } - if (src_len > 0) { // remaining partial block - uint8_t tmp[16]; - memset(tmp, 0, 16); - memcpy(tmp, src, src_len); - aes.encryptBlock(dp, tmp); - dp += 16; - } - return dp - dest; // will always be multiple of 16 #endif + return encryptSoftware(shared_secret, dest, src, src_len); } int Utils::encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) { int enc_len = encrypt(shared_secret, dest + CIPHER_MAC_SIZE, src, src_len); #ifdef USE_CC310_HW_CRYPTO - static CRYS_HMACUserContext_t hmac_ctx; - static CRYS_HASH_Result_t hmac_result; - nRFCrypto.begin(); - CRYS_HMAC_Init(&hmac_ctx, CRYS_HASH_SHA256_mode, (uint8_t*)shared_secret, PUB_KEY_SIZE); - CRYS_HMAC_Update(&hmac_ctx, dest + CIPHER_MAC_SIZE, enc_len); - CRYS_HMAC_Finish(&hmac_ctx, hmac_result); - nRFCrypto.end(); - memcpy(dest, hmac_result, CIPHER_MAC_SIZE); -#else - SHA256 sha; - sha.resetHMAC(shared_secret, PUB_KEY_SIZE); - sha.update(dest + CIPHER_MAC_SIZE, enc_len); - sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, dest, CIPHER_MAC_SIZE); + if (!hmacHardware(shared_secret, dest, dest + CIPHER_MAC_SIZE, enc_len)) #endif + { + hmacSoftware(shared_secret, dest, dest + CIPHER_MAC_SIZE, enc_len); + } return CIPHER_MAC_SIZE + enc_len; } @@ -167,24 +259,11 @@ int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uin uint8_t hmac[CIPHER_MAC_SIZE]; #ifdef USE_CC310_HW_CRYPTO - { - static CRYS_HMACUserContext_t hmac_ctx; - static CRYS_HASH_Result_t hmac_result; - nRFCrypto.begin(); - CRYS_HMAC_Init(&hmac_ctx, CRYS_HASH_SHA256_mode, (uint8_t*)shared_secret, PUB_KEY_SIZE); - CRYS_HMAC_Update(&hmac_ctx, (uint8_t*)(src + CIPHER_MAC_SIZE), src_len - CIPHER_MAC_SIZE); - CRYS_HMAC_Finish(&hmac_ctx, hmac_result); - nRFCrypto.end(); - memcpy(hmac, hmac_result, CIPHER_MAC_SIZE); - } -#else - { - SHA256 sha; - sha.resetHMAC(shared_secret, PUB_KEY_SIZE); - sha.update(src + CIPHER_MAC_SIZE, enc_len); - sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, hmac, CIPHER_MAC_SIZE); - } + if (!hmacHardware(shared_secret, hmac, src + CIPHER_MAC_SIZE, enc_len)) #endif + { + hmacSoftware(shared_secret, hmac, src + CIPHER_MAC_SIZE, enc_len); + } if (memcmp(hmac, src, CIPHER_MAC_SIZE) == 0) { return decrypt(shared_secret, dest, src + CIPHER_MAC_SIZE, enc_len); } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 806eb9da..6e1f931c 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -909,6 +909,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // Defaults for the trailing fields that older/shorter files may not contain. // Build-profile defaults - overwritten below when the saved field is present. _prefs->radio_fem_rxgain = 1; + _prefs->radio_fem_txgain = 0; _prefs->cad_enabled = DEFAULT_CAD_ENABLED; memset(_prefs->extra_sf, 0, sizeof(_prefs->extra_sf)); _prefs->rx_powersaving_enabled = 0; @@ -1193,6 +1194,10 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { } if (file.available() >= (int)sizeof(_prefs->extra_sf)) { file.read((uint8_t *)_prefs->extra_sf, sizeof(_prefs->extra_sf)); + if (file.available() >= (int)sizeof(_prefs->radio_fem_txgain)) { + file.read((uint8_t *)&_prefs->radio_fem_txgain, + sizeof(_prefs->radio_fem_txgain)); + } } else if (file.available() > 0) { // Never accept a torn append as a partial detector list. _com_prefs_needs_upgrade = true; @@ -1261,6 +1266,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean _prefs->radio_fem_rxgain = constrain(_prefs->radio_fem_rxgain, 0, 1); // boolean + _prefs->radio_fem_txgain = constrain(_prefs->radio_fem_txgain, 0, 1); // boolean _prefs->cad_enabled = constrain(_prefs->cad_enabled, 0, 1); // boolean if (!directRetryPrefsValid(_prefs)) { setDefaultDirectRetryPrefs(_prefs); @@ -1467,6 +1473,7 @@ static bool writeCommonPrefsImage(Writer& writer, NodePrefs* prefs) { WRITE_COMMON_PREFS(&prefs->rx_watchdog_enabled); // 854 WRITE_COMMON_PREFS(&prefs->system_watchdog_enabled); // 855 WRITE_COMMON_PREFS(&prefs->extra_sf); // 856 + WRITE_COMMON_PREFS(&prefs->radio_fem_txgain); // 860 #undef WRITE_COMMON_PREFS_BYTES #undef WRITE_COMMON_PREFS @@ -1617,7 +1624,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs, PrefsSaveRouting::Scope scope) { file.write((uint8_t *)&_prefs->system_watchdog_enabled, sizeof(_prefs->system_watchdog_enabled)); // 855 file.write((uint8_t *)_prefs->extra_sf, sizeof(_prefs->extra_sf)); // 856 - // next: 860 + file.write((uint8_t *)&_prefs->radio_fem_txgain, sizeof(_prefs->radio_fem_txgain)); // 860 + // next: 861 #if defined(NRF52_PLATFORM) if (!file.commit()) { @@ -3208,6 +3216,23 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep strcpy(reply, "Error: failed to apply LoRa FEM RX gain"); } } + } else if (memcmp(config, "radio.fem.txgain ", 17) == 0) { + const char* value = &config[17]; + if (!_board->canControlLoRaFemPaGain()) { + strcpy(reply, "Error: unsupported"); + } else if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) { + strcpy(reply, "Error: state must be on or off"); + } else { + const bool enabled = strcmp(value, "on") == 0; + if (_board->setLoRaFemPaGainEnabled(enabled)) { + _prefs->radio_fem_txgain = enabled ? 1 : 0; + savePrefs(); + strcpy(reply, enabled + ? "OK - LoRa FEM TX gain on" : "OK - LoRa FEM TX gain off"); + } else { + strcpy(reply, "Error: failed to apply LoRa FEM TX gain"); + } + } } else if (memcmp(config, "radio ", 6) == 0) { strcpy(tmp, &config[6]); const char* parts[4]; @@ -3508,6 +3533,28 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep } else { strcpy(reply, "Error: state must be on or off"); } + } else if (memcmp(config, "radio.fem.txgain ", 17) == 0) { + if (!_board->canControlLoRaFemPaGain()) { + strcpy(reply, "Error: unsupported"); + } else if (memcmp(&config[17], "on", 2) == 0) { + if (_board->setLoRaFemPaGainEnabled(true)) { + _prefs->radio_fem_txgain = 1; + savePrefs(); + strcpy(reply, "OK - LoRa FEM TX gain on"); + } else { + strcpy(reply, "Error: failed to apply LoRa FEM TX gain"); + } + } else if (memcmp(&config[17], "off", 3) == 0) { + if (_board->setLoRaFemPaGainEnabled(false)) { + _prefs->radio_fem_txgain = 0; + savePrefs(); + strcpy(reply, "OK - LoRa FEM TX gain off"); + } else { + strcpy(reply, "Error: failed to apply LoRa FEM TX gain"); + } + } else { + strcpy(reply, "Error: state must be on or off"); + } } else if (memcmp(config, "radio.rxps ", 11) == 0) { // RX PowerSaving const char* value = &config[11]; uint8_t enable = _prefs->rx_powersaving_enabled; @@ -4249,6 +4296,13 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off"); } + } else if (strcmp(config, "radio.fem.txgain") == 0) { + if (!_board->canControlLoRaFemPaGain()) { + strcpy(reply, "Error: unsupported"); + } else { + sprintf(reply, "> %s", + _board->isLoRaFemPaGainEnabled() ? "on" : "off"); + } } else if (strcmp(config, "radio") == 0) { char freq[16], bw[16]; strcpy(freq, StrHelper::ftoa(_prefs->freq)); @@ -4359,6 +4413,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep } else { sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off"); } + } else if (memcmp(config, "radio.fem.txgain", 16) == 0) { + if (!_board->canControlLoRaFemPaGain()) { + strcpy(reply, "Error: unsupported"); + } else { + sprintf(reply, "> %s", _board->isLoRaFemPaGainEnabled() ? "on" : "off"); + } } else if (memcmp(config, "tempradioat", 11) == 0 && (config[11] == 0 || config[11] == ' ')) { _callbacks->formatScheduledRadioParams(true, skipSpacesConst(&config[11]), reply); } else if (memcmp(config, "radioat", 7) == 0 && (config[7] == 0 || config[7] == ' ')) { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index a24e6632..69f7b559 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -160,6 +160,7 @@ struct NodePrefs { // persisted to file // LR2021 side-detector SFs are appended at /com_prefs offset 856. Their // in-memory placement here does not shift the established binary layout. uint8_t extra_sf[4] = {}; + uint8_t radio_fem_txgain; // LoRa FEM TX gain; persisted at /com_prefs offset 860 uint8_t retry_preset; uint8_t direct_retry_attempts; uint16_t direct_retry_base_ms; diff --git a/src/helpers/NRF52Crypto.cpp b/src/helpers/NRF52Crypto.cpp new file mode 100644 index 00000000..c9e68943 --- /dev/null +++ b/src/helpers/NRF52Crypto.cpp @@ -0,0 +1,88 @@ +#include "NRF52Crypto.h" + +#if defined(USE_CC310_HW_CRYPTO) + +#include +#include +#include + +namespace { + +enum CC310State : uint8_t { + CC310_UNINITIALIZED, + CC310_AVAILABLE, + CC310_UNAVAILABLE +}; + +StaticSemaphore_t cc310_mutex_storage; +SemaphoreHandle_t cc310_mutex = NULL; +CC310State cc310_state = CC310_UNINITIALIZED; + +SemaphoreHandle_t getCC310Mutex() { + if (cc310_mutex == NULL) { + // nRF52840 is single-core. The critical section makes the lazy static + // mutex construction safe if two tasks make their first crypto call at + // the same time. + taskENTER_CRITICAL(); + if (cc310_mutex == NULL) { + cc310_mutex = xSemaphoreCreateMutexStatic(&cc310_mutex_storage); + } + taskEXIT_CRITICAL(); + } + return cc310_mutex; +} + +} // namespace + +namespace mesh { + +CC310CryptoSession::CC310CryptoSession() + : _locked(false), _available(false) { + // FreeRTOS mutex APIs and the CC310 driver are not ISR-safe. + if (isInISR()) return; + + SemaphoreHandle_t mutex = getCC310Mutex(); + if (mutex == NULL || xSemaphoreTake(mutex, portMAX_DELAY) != pdTRUE) return; + _locked = true; + + if (cc310_state == CC310_UNINITIALIZED) { + // Adafruit_nRFCrypto::begin() also initializes its DRBG. Keep it alive for + // the process lifetime: end()/begin() would repeatedly reset that state. + cc310_state = nRFCrypto.begin() ? CC310_AVAILABLE : CC310_UNAVAILABLE; + } + _available = cc310_state == CC310_AVAILABLE; +} + +CC310CryptoSession::~CC310CryptoSession() { + if (_locked) xSemaphoreGive(cc310_mutex); +} + +bool initializeCC310Crypto() { + CC310CryptoSession session; + return static_cast(session); +} + +void mixCC310Random(uint8_t* dest, size_t size) { + if (dest == NULL || size == 0) return; + + CC310CryptoSession session; + if (!session) return; + + uint8_t hardware_random[32]; + size_t offset = 0; + while (offset < size) { + const size_t remaining = size - offset; + const uint16_t chunk = static_cast( + remaining < sizeof(hardware_random) ? remaining : sizeof(hardware_random)); + if (!nRFCrypto.Random.generate(hardware_random, chunk)) break; + for (uint16_t i = 0; i < chunk; ++i) { + dest[offset + i] ^= hardware_random[i]; + } + offset += chunk; + } + memset(hardware_random, 0, sizeof(hardware_random)); +} + +} // namespace mesh + +#endif diff --git a/src/helpers/NRF52Crypto.h b/src/helpers/NRF52Crypto.h new file mode 100644 index 00000000..eaee6d04 --- /dev/null +++ b/src/helpers/NRF52Crypto.h @@ -0,0 +1,38 @@ +#pragma once + +#if defined(USE_CC310_HW_CRYPTO) + +#include +#include + +namespace mesh { + +// Serializes access to the CC310 and performs its process-lifetime +// initialization. A session is unavailable in interrupt context or when the +// one-time hardware initialization failed; callers must then use software. +class CC310CryptoSession { +public: + CC310CryptoSession(); + ~CC310CryptoSession(); + + explicit operator bool() const { return _available; } + + CC310CryptoSession(const CC310CryptoSession&) = delete; + CC310CryptoSession& operator=(const CC310CryptoSession&) = delete; + +private: + bool _locked; + bool _available; +}; + +// Initialize the CC310 once during normal system startup. Sessions also call +// this lazily so early crypto users remain safe. +bool initializeCC310Crypto(); + +// XOR CC310 random output into bytes already populated by another entropy +// source. A hardware failure leaves those existing bytes usable. +void mixCC310Random(uint8_t* dest, size_t size); + +} // namespace mesh + +#endif diff --git a/src/helpers/radiolib/CustomLLCC68Wrapper.h b/src/helpers/radiolib/CustomLLCC68Wrapper.h index ae3b7398..ffdefcef 100644 --- a/src/helpers/radiolib/CustomLLCC68Wrapper.h +++ b/src/helpers/radiolib/CustomLLCC68Wrapper.h @@ -42,7 +42,7 @@ public: } uint8_t getSpreadingFactor() const override { return ((CustomLLCC68 *)_radio)->spreadingFactor; } - void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); } protected: bool radioDeepInit() override { diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index a4d44fa1..c2d3e12f 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -40,7 +40,6 @@ public: return ((CustomLR1110 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE; } - void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); } bool isReceivingPacket() override { return ((CustomLR1110 *)_radio)->isReceiving(); } @@ -133,4 +132,6 @@ public: bool getRxBoostedGainMode() const override { return ((CustomLR1110 *)_radio)->getRxBoostedGainMode(); } + + void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz(), getRxBoostedGainMode()); } }; diff --git a/src/helpers/radiolib/CustomSTM32WLxWrapper.h b/src/helpers/radiolib/CustomSTM32WLxWrapper.h index 010de972..a3ae64a0 100644 --- a/src/helpers/radiolib/CustomSTM32WLxWrapper.h +++ b/src/helpers/radiolib/CustomSTM32WLxWrapper.h @@ -35,5 +35,5 @@ public: } uint8_t getSpreadingFactor() const override { return ((CustomSTM32WLx *)_radio)->spreadingFactor; } - void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); } }; diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index ec7a2a5a..09920c40 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -99,8 +99,6 @@ protected: } bool supportsRadioDeepInit() const override { return true; } - void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } - protected: bool applyRxBoostedGainMode(bool en) override { return ((CustomSX1262 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE; @@ -109,4 +107,6 @@ public: bool getRxBoostedGainMode() const override { return ((CustomSX1262 *)_radio)->getRxBoostedGainMode(); } + + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); } }; diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index 34b145d1..e24c2740 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -46,8 +46,6 @@ public: } uint8_t getSpreadingFactor() const override { return ((CustomSX1268 *)_radio)->spreadingFactor; } - void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } - protected: bool radioDeepInit() override { return ((CustomSX1268 *)_radio)->std_init(); @@ -62,4 +60,6 @@ public: bool getRxBoostedGainMode() const override { return ((CustomSX1268 *)_radio)->getRxBoostedGainMode(); } + + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); } }; diff --git a/src/helpers/radiolib/LR11x0Reset.h b/src/helpers/radiolib/LR11x0Reset.h index 5c4c62d6..cdfc1f9f 100644 --- a/src/helpers/radiolib/LR11x0Reset.h +++ b/src/helpers/radiolib/LR11x0Reset.h @@ -5,7 +5,7 @@ // Full receiver reset for LR11x0-family chips (LR1110, LR1120, LR1121). // Warm sleep powers down analog, calibrate(0x3F) refreshes all calibration blocks, // then re-applies RX settings that calibration may reset. -inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) { +inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz, bool rx_boost_gain) { radio->sleep(true, 0); radio->standby(RADIOLIB_LR11X0_STANDBY_RC, true); @@ -16,8 +16,6 @@ inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) { radio->calibrateImageRejection(freqMHz - 4.0f, freqMHz + 4.0f); #ifdef RX_BOOSTED_GAIN - // Fallback for callers without a runtime override. RadioLibWrapper::resetAGC() - // reapplies its cached radio.rxgain selection before receive mode resumes. - radio->setRxBoostedGainMode(RX_BOOSTED_GAIN); + radio->setRxBoostedGainMode(rx_boost_gain); #endif } diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 6b936709..e87bb7b4 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -32,6 +32,11 @@ void setFlag(void) { } void RadioLibWrapper::begin() { +#ifdef USE_CC310_HW_CRYPTO + // Initialize CryptoCell once from normal task context. The session helper + // still initializes lazily if an earlier crypto operation runs first. + (void) mesh::initializeCC310Crypto(); +#endif _radio->setPacketReceivedAction(setFlag); // this is also SentComplete interrupt _preamble_sf = getSpreadingFactor(); _radio->setPreambleLength(preambleLengthForSF(_preamble_sf)); // longer preamble for lower SF improves reliability diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 83aa9150..19fb1914 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -13,7 +13,7 @@ #define RX_PS_FALLBACK_SLEEP_US 60000UL #ifdef USE_CC310_HW_CRYPTO -#include +#include "../NRF52Crypto.h" #endif struct PacketMillis { uint32_t preambleMillis; // preamble-detect -> header-valid deadline @@ -217,15 +217,14 @@ public: RadioNoiseListener(PhysicalLayer& radio): _radio(&radio) { } void random(uint8_t* dest, size_t sz) override { -#ifdef USE_CC310_HW_CRYPTO - // CC310 TRNG is higher quality and environment-independent vs radio RSSI noise. - nRFCrypto.begin(); - nRFCrypto.Random.generate(dest, (uint16_t)sz); - nRFCrypto.end(); -#else - for (int i = 0; i < sz; i++) { + // Preserve the existing radio/PRNG entropy on every platform. On nRF52840, + // independently generated CC310 bytes are mixed in without becoming the + // sole source of randomness. + for (size_t i = 0; i < sz; i++) { dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF); } +#ifdef USE_CC310_HW_CRYPTO + mesh::mixCC310Random(dest, sz); #endif } }; diff --git a/src/helpers/radiolib/SX126xReset.h b/src/helpers/radiolib/SX126xReset.h index a0ee46e3..472eb33b 100644 --- a/src/helpers/radiolib/SX126xReset.h +++ b/src/helpers/radiolib/SX126xReset.h @@ -5,7 +5,7 @@ // Full receiver reset for all SX126x-family chips (SX1262, SX1268, LLCC68, STM32WLx). // Warm sleep powers down analog, Calibrate(0x7F) refreshes ADC/PLL/image calibration, // then re-applies RX settings that calibration may reset. -inline void sx126xResetAGC(SX126x* radio) { +inline void sx126xResetAGC(SX126x* radio, bool rx_boost_gain) { radio->sleep(true); radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); @@ -26,9 +26,7 @@ inline void sx126xResetAGC(SX126x* radio) { radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); #endif #ifdef SX126X_RX_BOOSTED_GAIN - // Fallback for callers without a runtime override. RadioLibWrapper::resetAGC() - // reapplies its cached radio.rxgain selection before receive mode resumes. - radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); + radio->setRxBoostedGainMode(rx_boost_gain); #endif #ifdef SX126X_REGISTER_PATCH uint8_t r_data = 0; diff --git a/variants/station_g3_esp32/LoRaFEMControl.cpp b/variants/station_g3_esp32/LoRaFEMControl.cpp new file mode 100644 index 00000000..8d4f90f6 --- /dev/null +++ b/variants/station_g3_esp32/LoRaFEMControl.cpp @@ -0,0 +1,79 @@ +#include "LoRaFEMControl.h" + +#include +#include + +void LoRaFEMControl::init() { +#ifdef P_PA1_EN + rtc_gpio_hold_dis((gpio_num_t)P_PA1_EN); + pinMode(P_PA1_EN, OUTPUT); + applyPAGain(); +#endif + +#ifdef P_PRIMARY_LNA_EN + rtc_gpio_hold_dis((gpio_num_t)P_PRIMARY_LNA_EN); + pinMode(P_PRIMARY_LNA_EN, OUTPUT); + setRxModeEnable(); +#endif +} + +void LoRaFEMControl::setSleepModeEnable() { +#ifdef P_PA1_EN + // PA PL1 low/open selects the lower of the two hardware-jumper-selected levels. + digitalWrite(P_PA1_EN, !P_PA1_EN_ACTIVE); +#endif +#ifdef P_PRIMARY_LNA_EN + // Preserve the existing Station G3 power-off state. + digitalWrite(P_PRIMARY_LNA_EN, P_PRIMARY_LNA_EN_ACTIVE); +#endif +} + +void LoRaFEMControl::setTxModeEnable() { + // Latch the requested PA level here, before the SX1262 starts driving the PA. PA PL1 + // retargets the PA's DC-DC rail, so moving it mid-transmit collapses the supply while + // the PA is still driven at full input power. + applyPAGain(); +#ifdef P_PRIMARY_LNA_EN + digitalWrite(P_PRIMARY_LNA_EN, !P_PRIMARY_LNA_EN_ACTIVE); +#endif +} + +void LoRaFEMControl::setRxModeEnable() { +#ifdef P_PRIMARY_LNA_EN + digitalWrite(P_PRIMARY_LNA_EN, lna_enabled ? P_PRIMARY_LNA_EN_ACTIVE : !P_PRIMARY_LNA_EN_ACTIVE); +#endif +} + +void LoRaFEMControl::setLNAEnable(bool enabled) { + lna_enabled = enabled; + setRxModeEnable(); +} + +void LoRaFEMControl::setPAGainEnable(bool enabled) { + // Recorded only -- the pin is driven from setTxModeEnable(). The PA level only matters + // while transmitting, so deferring costs nothing and keeps the rail change out of an + // in-flight transmit (the CLI runs on every main-loop pass, including mid-TX). + pa_gain_enabled = enabled; +} + +void LoRaFEMControl::applyPAGain() { +#ifdef P_PA1_EN + digitalWrite(P_PA1_EN, pa_gain_enabled ? P_PA1_EN_ACTIVE : !P_PA1_EN_ACTIVE); +#endif +} + +bool LoRaFEMControl::canControlLNA() const { +#ifdef P_PRIMARY_LNA_EN + return true; +#else + return false; +#endif +} + +bool LoRaFEMControl::canControlPAGain() const { +#ifdef P_PA1_EN + return true; +#else + return false; +#endif +} diff --git a/variants/station_g3_esp32/LoRaFEMControl.h b/variants/station_g3_esp32/LoRaFEMControl.h new file mode 100644 index 00000000..f622de91 --- /dev/null +++ b/variants/station_g3_esp32/LoRaFEMControl.h @@ -0,0 +1,22 @@ +#pragma once + +class LoRaFEMControl { +public: + void init(); + void setSleepModeEnable(); + void setTxModeEnable(); + void setRxModeEnable(); + void setLNAEnable(bool enabled); + void setPAGainEnable(bool enabled); + + bool canControlLNA() const; + bool canControlPAGain() const; + bool isLNAEnabled() const { return lna_enabled; } + bool isPAGainEnabled() const { return pa_gain_enabled; } + +private: + void applyPAGain(); + + bool lna_enabled = true; + bool pa_gain_enabled = false; +}; diff --git a/variants/station_g3_esp32/StationG3Board.cpp b/variants/station_g3_esp32/StationG3Board.cpp index 4a498311..dd863aca 100644 --- a/variants/station_g3_esp32/StationG3Board.cpp +++ b/variants/station_g3_esp32/StationG3Board.cpp @@ -1,15 +1,46 @@ #include "StationG3Board.h" void StationG3Board::powerOff() { + loRaFEMControl.setSleepModeEnable(); #ifdef P_PA1_EN - setPAModeHigh(false); rtc_gpio_hold_en((gpio_num_t)P_PA1_EN); #endif #ifdef P_PRIMARY_LNA_EN - setPrimaryLNAControl(true); rtc_gpio_hold_en((gpio_num_t)P_PRIMARY_LNA_EN); #endif ESP32Board::powerOff(); } + +bool StationG3Board::setLoRaFemLnaEnabled(bool enable) { + if (!loRaFEMControl.canControlLNA()) { + return false; + } + loRaFEMControl.setLNAEnable(enable); + return true; +} + +bool StationG3Board::canControlLoRaFemLna() const { + return loRaFEMControl.canControlLNA(); +} + +bool StationG3Board::isLoRaFemLnaEnabled() const { + return loRaFEMControl.isLNAEnabled(); +} + +bool StationG3Board::setLoRaFemPaGainEnabled(bool enable) { + if (!loRaFEMControl.canControlPAGain()) { + return false; + } + loRaFEMControl.setPAGainEnable(enable); + return true; +} + +bool StationG3Board::canControlLoRaFemPaGain() const { + return loRaFEMControl.canControlPAGain(); +} + +bool StationG3Board::isLoRaFemPaGainEnabled() const { + return loRaFEMControl.isPAGainEnabled(); +} diff --git a/variants/station_g3_esp32/StationG3Board.h b/variants/station_g3_esp32/StationG3Board.h index 4b1fb81c..52628eb6 100644 --- a/variants/station_g3_esp32/StationG3Board.h +++ b/variants/station_g3_esp32/StationG3Board.h @@ -3,45 +3,15 @@ #include #include #include - -#ifndef P_PRIMARY_LNA_EN_ACTIVE -#define P_PRIMARY_LNA_EN_ACTIVE LOW -#endif - -#ifndef P_PA1_EN_ACTIVE -#define P_PA1_EN_ACTIVE HIGH -#endif +#include "LoRaFEMControl.h" class StationG3Board : public ESP32Board { - void setPAModeHigh(bool enabled) { -#ifdef P_PA1_EN - // Station G3 PA PL1 mode: LOW/open is PA low, HIGH/short is PA high. - digitalWrite(P_PA1_EN, enabled ? P_PA1_EN_ACTIVE : !P_PA1_EN_ACTIVE); -#endif - } - - void setPrimaryLNAControl(bool enabled) { -#ifdef P_PRIMARY_LNA_EN - // Station G3 primary LNA mode is active-low: LOW/open is LNA on, HIGH/short is LNA off. - digitalWrite(P_PRIMARY_LNA_EN, enabled ? P_PRIMARY_LNA_EN_ACTIVE : !P_PRIMARY_LNA_EN_ACTIVE); -#endif - } - public: + LoRaFEMControl loRaFEMControl; + void begin() { ESP32Board::begin(); - -#ifdef P_PA1_EN - rtc_gpio_hold_dis((gpio_num_t)P_PA1_EN); - pinMode(P_PA1_EN, OUTPUT); - setPAModeHigh(false); -#endif - -#ifdef P_PRIMARY_LNA_EN - rtc_gpio_hold_dis((gpio_num_t)P_PRIMARY_LNA_EN); - pinMode(P_PRIMARY_LNA_EN, OUTPUT); - setPrimaryLNAControl(true); -#endif + loRaFEMControl.init(); esp_reset_reason_t reason = esp_reset_reason(); if (reason == ESP_RST_DEEPSLEEP) { @@ -56,23 +26,30 @@ public: } void setPrimaryLNAEnable(bool enabled) { - setPrimaryLNAControl(enabled); + loRaFEMControl.setLNAEnable(enabled); } void setPrimaryPAHighPower(bool enabled) { - setPAModeHigh(enabled); + loRaFEMControl.setPAGainEnable(enabled); } void onBeforeTransmit() override { ESP32Board::onBeforeTransmit(); - setPrimaryLNAControl(false); + loRaFEMControl.setTxModeEnable(); } void onAfterTransmit() override { ESP32Board::onAfterTransmit(); - setPrimaryLNAControl(true); + loRaFEMControl.setRxModeEnable(); } + bool setLoRaFemLnaEnabled(bool enable) override; + bool canControlLoRaFemLna() const override; + bool isLoRaFemLnaEnabled() const override; + bool setLoRaFemPaGainEnabled(bool enable) override; + bool canControlLoRaFemPaGain() const override; + bool isLoRaFemPaGainEnabled() const override; + void powerOff() override; uint16_t getBattMilliVolts() override { diff --git a/variants/station_g3_esp32/platformio.ini b/variants/station_g3_esp32/platformio.ini index db0f7026..00a05713 100644 --- a/variants/station_g3_esp32/platformio.ini +++ b/variants/station_g3_esp32/platformio.ini @@ -18,11 +18,11 @@ build_flags = -D P_LORA_SCLK=12 -D P_LORA_MISO=14 -D P_LORA_MOSI=13 - -D P_PA1_EN=9 ; PA PL1 Mode: LOW/open is PA low, HIGH/short is PA high. + -D P_PA1_EN=9 ; PA PL1 Mode: LOW/open selects low level, HIGH/short selects high level. -D P_PA1_EN_ACTIVE=HIGH -D P_PRIMARY_LNA_EN=10 ; Primary Slot LNA Mode: LOW/open is LNA on, HIGH/short is LNA off. -D P_PRIMARY_LNA_EN_ACTIVE=LOW - -D LORA_TX_POWER=7 ; configured as 7dbm, because the final output will be ~27dbm (~0.5w) if the PA is enabled. + -D LORA_TX_POWER=7 ; SX1262 input power to the Station G3 PA; final output depends on PA PL1/PL2 level. -D MAX_LORA_TX_POWER=22 ; -D P_LORA_TX_LED=35 -D PIN_BOARD_SDA=5