Skip to content

Commit 5dfd201

Browse files
Bl00d-B0bLN882H Fix
authored andcommitted
[lightning-ln882h] Restore Tuya factory MAC on first boot; fall back to TRNG
Every LN882H ships with the same factory-default MAC address (00:50:C2:5E:10:88) written to KV flash by sysparam_integrity_check_all(). Without a fix every device on the same network shares one MAC, causing ARP conflicts and connection failures. Fix: lt_init_unique_mac() is called immediately after sysparam_integrity_check_all() in lt_init_family(). It resolves the MAC in priority order: 1. sysparam KV (0x1E0000) already holds a non-sentinel MAC: return immediately. This is the fast path on every boot after the first, and also covers the LibreTiny-to-LibreTiny OTA update case since 0x1E0000 is outside the OTA partition and is never erased by OTA. 2. Tuya BLK_V1.0 KV store at 0x1C5000: scan for the last valid 6_sta_mac entry and restore it. Tuya assigns every LN882H a unique MAC at the factory and stores it here. This region lies within the OTA partition (0x133000-0x1DD000), but OTA writes are block-addressed and the current firmware (~530 KB) ends at ~0x1B4700 — well short of 0x1C5000 — so the Tuya KV is preserved under both UART flash (covers 0x000000-0x088000) and OTA (block-addressed, stops at image end). No partition layout changes are needed. 3. Tuya KV absent or erased (e.g. UART flash with full-chip erase option): generate a unique address via ln_generate_random_mac() (hardware TRNG). This mirrors the approach used in OpenBK7231T_App. In all cases the result is persisted to sysparam KV so step 1 handles every subsequent boot. The SoftAP MAC is derived by setting the locally-administered bit on the first octet of the STA MAC. Analysis based on binary examination of 5 unique Tuya LN882H flash dumps (4x DS-101JL wall switches + 1x GU10 bulb). All devices, including those never provisioned via the Tuya app, carry a unique 6_sta_mac entry at a predictable offset within the BLK_V1.0 KV region, confirming that the MAC is factory-assigned at manufacturing time.
1 parent af7e7e8 commit 5dfd201

1 file changed

Lines changed: 114 additions & 1 deletion

File tree

cores/lightning-ln882h/base/api/lt_init.c

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
/* Copyright (c) Etienne Le Cousin 2024-02-24. */
22

3+
#include <hal/hal_flash.h>
34
#include <libretiny.h>
45
#include <sdk_private.h>
56

7+
// Declared in components/utils/ln_misc.h; compiled in ln882h_net.
8+
// Not thread-safe — safe here since lt_init_family() runs before the RTOS scheduler.
9+
extern int ln_generate_random_mac(uint8_t *addr);
10+
611
extern uint8_t uart_print_port;
712
extern Serial_t m_LogSerial;
813

@@ -13,6 +18,111 @@ static void lt_init_log(void) {
1318
serial_init(&m_LogSerial, LT_UART_DEFAULT_PORT, CFG_UART_BAUDRATE_LOG, NULL);
1419
}
1520

21+
// Scan the Tuya BLK_V1.0 KV store for the last valid "6_sta_mac" entry.
22+
//
23+
// Background: Tuya assigns every LN882H a unique MAC at the factory and writes
24+
// it to a BLK_V1.0 append-log KV store at flash offset 0x1C5000 (4 blocks x 4 KB).
25+
// This region lies within LibreTiny's OTA partition (0x133000–0x1DD000), but OTA
26+
// writes only the firmware image bytes (~530 KB, ending at ~0x1B4700) so 0x1C5000
27+
// is never touched unless the firmware grows beyond 592 KB. UART full-flash also
28+
// only covers 0x000000–0x088000 and therefore likewise preserves the Tuya KV.
29+
//
30+
// BLK_V1.0 entry layout (no null terminator between key and value):
31+
// it_magic[8] | crc16[2] | val_size_u16_le[2] | prev_ptr_u32_le[4] | key[N] | value[val_size]
32+
//
33+
// The factory sentinel "6_sta_mac" #1 is always 00:50:C2:5E:10:88 (from
34+
// sysparam_factory_setting.h). The real unique MAC is written as a second
35+
// "6_sta_mac" entry during Tuya manufacturing. BLK_V1.0 is an append-only log,
36+
// so the last matching entry wins — we return that.
37+
//
38+
// Returns true and fills mac_out[6] if a valid non-sentinel MAC is found.
39+
static bool lt_tuya_kv_read_sta_mac(uint8_t *mac_out) {
40+
static const uint8_t IT_MAGIC[8] = "it_magic";
41+
static const uint8_t STA_MAC_KEY[9] = "6_sta_mac";
42+
static const uint8_t SENTINEL[6] = {0x00, 0x50, 0xC2, 0x5E, 0x10, 0x88};
43+
44+
// Tuya KV: 4 blocks x 4 KB starting at FLASH_OTA_OFFSET + 0x92000
45+
// (= 0x1C5000 on the standard lightning-ln882hki partition layout)
46+
const uint32_t kv_base = FLASH_OTA_OFFSET + 0x92000UL;
47+
const uint32_t kv_size = 0x4000UL;
48+
49+
// Entry buffer: it_magic(8) + header(8) + key(9) + value(6) = 31 bytes
50+
uint8_t entry[31];
51+
bool found = false;
52+
uint32_t off;
53+
54+
for (off = 0; off + sizeof(entry) <= kv_size; off++) {
55+
hal_flash_read(kv_base + off, 8, entry);
56+
if (memcmp(entry, IT_MAGIC, 8) != 0)
57+
continue;
58+
59+
// Read header + key + value
60+
hal_flash_read(kv_base + off + 8, 23, entry + 8);
61+
62+
uint16_t val_size = entry[10] | ((uint16_t)entry[11] << 8);
63+
if (val_size != 6)
64+
goto next;
65+
if (memcmp(entry + 16, STA_MAC_KEY, 9) != 0)
66+
goto next;
67+
68+
// entry[25..30] = 6-byte MAC value
69+
if (memcmp(entry + 25, SENTINEL, 6) == 0)
70+
goto next;
71+
bool all_ff = true;
72+
for (int i = 0; i < 6; i++)
73+
if (entry[25 + i] != 0xFF) { all_ff = false; break; }
74+
if (all_ff)
75+
goto next;
76+
77+
memcpy(mac_out, entry + 25, 6);
78+
found = true; // keep scanning — last entry wins in BLK_V1.0 log
79+
80+
next:
81+
off += 7; // skip past magic header, outer off++ gives total advance of 8
82+
}
83+
return found;
84+
}
85+
86+
// Ensure the stored STA/SoftAP MACs are unique on first boot after flash.
87+
//
88+
// Priority:
89+
// 1. LibreTiny sysparam KV already holds a non-sentinel MAC → nothing to do.
90+
// (fast path on every boot after first; also covers LibreTiny→LibreTiny OTA)
91+
// 2. Tuya BLK_V1.0 KV at 0x1C5000 has a valid unique MAC → restore it.
92+
// (first boot after UART flash or Kickstart/cloudcutter OTA from Tuya stock)
93+
// 3. Tuya KV empty / erased (e.g. UART flash with full-chip erase) → TRNG.
94+
//
95+
// sysparam KV at 0x1E0000 is outside the OTA partition and is never erased by
96+
// OTA, so the resolved MAC is stable across all future LibreTiny OTA updates.
97+
static void lt_init_unique_mac(void) {
98+
static const uint8_t factory_mac[6] = {0x00, 0x50, 0xC2, 0x5E, 0x10, 0x88};
99+
uint8_t mac[6];
100+
101+
if (SYSPARAM_ERR_NONE != sysparam_sta_mac_get(mac))
102+
return;
103+
if (memcmp(mac, factory_mac, 6) != 0)
104+
return; // step 1: already unique, nothing to do
105+
106+
// Step 2: try to recover Tuya-assigned factory MAC from BLK_V1.0 KV
107+
if (lt_tuya_kv_read_sta_mac(mac)) {
108+
LT_I("Restored Tuya factory MAC: %02X:%02X:%02X:%02X:%02X:%02X",
109+
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
110+
sysparam_sta_mac_update(mac);
111+
mac[0] |= 0x02; // locally-administered bit → distinct SoftAP MAC
112+
sysparam_softap_mac_update(mac);
113+
return;
114+
}
115+
116+
// Step 3: Tuya KV unavailable — generate via hardware TRNG
117+
if (ln_generate_random_mac(mac) != 0)
118+
return;
119+
LT_I("Generated random unique MAC: %02X:%02X:%02X:%02X:%02X:%02X",
120+
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
121+
sysparam_sta_mac_update(mac);
122+
mac[0] |= 0x02;
123+
sysparam_softap_mac_update(mac);
124+
}
125+
16126
void lt_init_family() {
17127
// 0. check reboot cause
18128
ln_chip_get_reboot_cause();
@@ -42,11 +152,14 @@ void lt_init_family() {
42152
// init system parameter
43153
sysparam_integrity_check_all();
44154

155+
// randomize MAC if still at factory default (first boot after flash)
156+
lt_init_unique_mac();
157+
45158
ln_pm_sleep_mode_set(ACTIVE);
46159
// ln_pm_always_clk_disable_select(CLK_G_I2S | CLK_G_WS2811 | CLK_G_SDIO);
47160
/*ln_pm_always_clk_disable_select(CLK_G_I2S | CLK_G_WS2811 | CLK_G_SDIO | CLK_G_AES);
48161
ln_pm_lightsleep_clk_disable_select(CLK_G_GPIOA | CLK_G_GPIOB | CLK_G_SPI0 | CLK_G_SPI1 | CLK_G_I2C0 |
49-
CLK_G_UART1 | CLK_G_UART2 | CLK_G_WDT | CLK_G_TIM1 | CLK_G_TIM2 | CLK_G_MAC |
162+
CLK_G_UART1 | CLK_G_UART2 | CLK_G_WDT | CLK_G_TIM1 | CLK_G_TIM2 | CLK_G_MAC |
50163
CLK_G_DMA | CLK_G_RF | CLK_G_ADV_TIMER| CLK_G_TRNG);*/
51164
}
52165

0 commit comments

Comments
 (0)