GCC Code Coverage Report


Directory: main/
File: adapter/config.c
Date: 2025-10-04 14:03:00
Exec Total Coverage
Lines: 56 181 30.9%
Functions: 4 12 33.3%
Branches: 11 76 14.5%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2019-2025, Jacques Gagnon
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6 #include <stdio.h>
7 #include <string.h>
8 #include <sys/stat.h>
9 #include <errno.h>
10 #include "nvs.h"
11 #include "zephyr/types.h"
12 #include "tools/util.h"
13 #include "adapter.h"
14 #include "config.h"
15 #include "system/fs.h"
16 #include "adapter/gameid.h"
17 #include "bluetooth/mon.h"
18 #include "system/manager.h"
19
20 struct config config;
21 struct hw_config hw_config = {
22 .external_adapter = 0,
23 .hotplug = 0,
24 .hw1_ports_led_pins = {2, 4, 12, 15},
25 .led_flash_duty_cycle = 0x80000,
26 .led_flash_hz = {2, 4, 8},
27 .led_flash_off_duty_cycle = 0,
28 .led_flash_on_duty_cycle = 0xFFFFF,
29 .led_pulse_duty_max = 2000,
30 .led_pulse_duty_min = 50,
31 .led_pulse_fade_cycle_delay_ms = 500,
32 .led_pulse_fade_time_ms = 500,
33 .led_pulse_hz = 5000,
34 .led_pulse_off_duty_cycle = 0,
35 .led_pulse_on_duty_cycle = 0x1FFF,
36 .port_cnt = 2,
37 .ports_sense_input_polarity = 0,
38 .ports_sense_output_ms = 1000,
39 .ports_sense_output_od = 0,
40 .ports_sense_output_polarity = 0,
41 .ports_sense_p3_p4_as_output = 0,
42 .power_pin_is_hold = 0,
43 .power_pin_od = 0,
44 .power_pin_polarity = 0,
45 .power_pin_pulse_ms = 20,
46 .reset_pin_od = 1,
47 .reset_pin_polarity = 0,
48 .reset_pin_pulse_ms = 500,
49 .sw_io0_hold_thres_ms = {1000, 3000, 6000},
50 .ps_ctrl_colors = {
51 0xFF0000, /* Blue */
52 0x0000FF, /* Red */
53 0x00FF00, /* Green */
54 0xFF00FF, /* Pink */
55 0xFFFF00, /* Cyan */
56 0x0080FF, /* Orange */
57 0x00FFFF, /* Yellow */
58 0xFF0080, /* Purple */
59 },
60 };
61
62 static char *hw_config_name_idx[] = {
63 "ext_adapter",
64 "hotplug",
65 "hw1_led_pins_0",
66 "hw1_led_pins_1",
67 "hw1_led_pins_2",
68 "hw1_led_pins_3",
69 "led_flash_duty",
70 "led_flash_hz_0",
71 "led_flash_hz_1",
72 "led_flash_hz_2",
73 "led_f_off_duty",
74 "led_f_on_duty",
75 "led_p_duty_max",
76 "led_p_duty_min",
77 "led_p_fade_c_ms",
78 "led_p_fade_t_ms",
79 "led_pulse_hz",
80 "led_p_off_duty",
81 "led_p_on_duty",
82 "port_cnt",
83 "ports_s_in_pol",
84 "ports_s_out_ms",
85 "ports_s_out_od",
86 "ports_s_out_pol",
87 "ports_s_out_en",
88 "pwr_pin_is_hold",
89 "pwr_pin_od",
90 "pwr_pin_pol",
91 "pwr_pin_p_ms",
92 "reset_pin_od",
93 "reset_pin_pol",
94 "reset_pin_p_ms",
95 "sw_thres_ms_0",
96 "sw_thres_ms_1",
97 "sw_thres_ms_2",
98 "ps_ctrl_color_0",
99 "ps_ctrl_color_1",
100 "ps_ctrl_color_2",
101 "ps_ctrl_color_3",
102 "ps_ctrl_color_4",
103 "ps_ctrl_color_5",
104 "ps_ctrl_color_6",
105 "ps_ctrl_color_7",
106 };
107 static uint32_t config_src = DEFAULT_CFG;
108 static uint32_t config_version_magic[] = {
109 CONFIG_MAGIC_V0,
110 CONFIG_MAGIC_V1,
111 CONFIG_MAGIC_V2,
112 CONFIG_MAGIC_V3,
113 };
114 static uint8_t config_default_combo[BR_COMBO_CNT] = {
115 PAD_LM, PAD_RM, PAD_MM, PAD_RB_UP, PAD_RB_LEFT, PAD_RB_RIGHT, PAD_RB_DOWN, PAD_LD_UP, PAD_LD_DOWN, PAD_MS
116 };
117 static bool config_rst_bare_core = false;
118
119 static void config_init_struct(struct config *data);
120 static void config_init_nvs_patch(struct config *data);
121 static int32_t config_load_from_file(struct config *data, char *filename);
122 static int32_t config_store_on_file(struct config *data, char *filename);
123 static int32_t config_v0_update(struct config *data, char *filename);
124 static int32_t config_v1_update(struct config *data, char *filename);
125 static int32_t config_v2_update(struct config *data, char *filename);
126
127 static int32_t (*config_ver_update[])(struct config *data, char *filename) = {
128 config_v0_update,
129 config_v1_update,
130 config_v2_update,
131 NULL,
132 };
133
134 static int32_t config_v0_update(struct config *data, char *filename) {
135 memmove((uint8_t *)data + 7, (uint8_t *)data + 6, sizeof(*data) - 7);
136
137 data->magic = CONFIG_MAGIC;
138 data->global_cfg.inquiry_mode = INQ_AUTO;
139
140 return config_store_on_file(data, filename);
141 }
142
143 static int32_t config_v1_update(struct config *data, char *filename) {
144 memmove((uint8_t *)data + 8, (uint8_t *)data + 7, 31 - 8);
145
146 data->magic = CONFIG_MAGIC;
147 data->global_cfg.banksel = 0;
148
149 FILE *file = fopen(filename, "rb");
150 if (file == NULL) {
151 printf("%s: failed to open file for reading\n", __FUNCTION__);
152 goto fail;
153 }
154 else {
155 uint32_t count = 0;
156 for (uint32_t i = 0; i < WIRED_MAX_DEV; i++) {
157 fseek(file, 31 + (3 + 255 * 8) * i, SEEK_SET);
158 count += fread((uint8_t *)&data->in_cfg[i], sizeof(struct in_cfg), 1, file);
159 if (data->in_cfg[i].map_size > ADAPTER_MAPPING_MAX) {
160 data->in_cfg[i].map_size = ADAPTER_MAPPING_MAX;
161 }
162 }
163 fclose(file);
164
165 if (count != WIRED_MAX_DEV) {
166 goto fail;
167 }
168 }
169 return config_store_on_file(data, filename);
170
171 fail:
172 printf("%s: Update failed, reset config (Sorry!)\n", __FUNCTION__);
173 config_init_struct(data);
174 config_init_nvs_patch(data);
175 return config_store_on_file(data, filename);
176 }
177
178 static int32_t config_get_version(uint32_t magic) {
179 for (uint32_t i = 0; i < ARRAY_SIZE(config_version_magic); i++) {
180 if (magic == config_version_magic[i]) {
181 return i;
182 }
183 }
184 return -1;
185 }
186
187 static int32_t config_v2_update(struct config *data, char *filename) {
188 data->magic = CONFIG_MAGIC;
189
190 for (uint32_t i = 0; i < WIRED_MAX_DEV; i++) {
191 uint32_t j = data->in_cfg[i].map_size;
192 data->in_cfg[i].map_size += BR_COMBO_CNT;
193 for (uint32_t k = 0; k < BR_COMBO_CNT; j++, k++) {
194 data->in_cfg[i].map_cfg[j].src_btn = config_default_combo[k];
195 data->in_cfg[i].map_cfg[j].dst_btn = k + BR_COMBO_BASE_1;
196 data->in_cfg[i].map_cfg[j].dst_id = i;
197 data->in_cfg[i].map_cfg[j].perc_max = 100;
198 data->in_cfg[i].map_cfg[j].perc_threshold = 50;
199 data->in_cfg[i].map_cfg[j].perc_deadzone = 135;
200 data->in_cfg[i].map_cfg[j].turbo = 0;
201 data->in_cfg[i].map_cfg[j].algo = 0;
202 }
203 }
204
205 return config_store_on_file(data, filename);
206 }
207
208 static int32_t hw_config_lookup_key_name(const char* key) {
209 for (uint32_t i = 0; i < sizeof(hw_config_name_idx)/sizeof(*hw_config_name_idx); i++) {
210 if (strstr(key, hw_config_name_idx[i]) != NULL) {
211 return i;
212 }
213 }
214 return -1;
215 }
216
217 1 static void config_init_struct(struct config *data) {
218 1 data->magic = CONFIG_MAGIC;
219 1 data->global_cfg.system_cfg = WIRED_AUTO;
220 1 data->global_cfg.multitap_cfg = MT_NONE;
221 1 data->global_cfg.inquiry_mode = INQ_AUTO;
222 1 data->global_cfg.banksel = 0;
223
224
2/2
✓ Branch 0 (10→3) taken 8 times.
✓ Branch 1 (10→11) taken 1 times.
9 for (uint32_t i = 0; i < WIRED_MAX_DEV; i++) {
225 8 data->out_cfg[i].dev_mode = DEV_PAD;
226 8 data->out_cfg[i].acc_mode = ACC_NONE;
227 8 data->in_cfg[i].bt_dev_id = 0x00; /* Not used placeholder */
228 8 data->in_cfg[i].bt_subdev_id = 0x00; /* Not used placeholder */
229 8 data->in_cfg[i].map_size = KBM_MAX + BR_COMBO_CNT;
230 8 uint32_t j = 0;
231
2/2
✓ Branch 0 (5→4) taken 920 times.
✓ Branch 1 (5→6) taken 8 times.
928 for (; j < KBM_MAX; j++) {
232 920 data->in_cfg[i].map_cfg[j].src_btn = j;
233 920 data->in_cfg[i].map_cfg[j].dst_btn = j;
234 920 data->in_cfg[i].map_cfg[j].dst_id = i;
235 920 data->in_cfg[i].map_cfg[j].perc_max = 100;
236 920 data->in_cfg[i].map_cfg[j].perc_threshold = 50;
237 920 data->in_cfg[i].map_cfg[j].perc_deadzone = 135;
238 920 data->in_cfg[i].map_cfg[j].turbo = 0;
239 920 data->in_cfg[i].map_cfg[j].algo = 0;
240 }
241
2/2
✓ Branch 0 (8→7) taken 80 times.
✓ Branch 1 (8→9) taken 8 times.
88 for (uint32_t k = 0; k < BR_COMBO_CNT; j++, k++) {
242 80 data->in_cfg[i].map_cfg[j].src_btn = config_default_combo[k];
243 80 data->in_cfg[i].map_cfg[j].dst_btn = k + BR_COMBO_BASE_1;
244 80 data->in_cfg[i].map_cfg[j].dst_id = i;
245 80 data->in_cfg[i].map_cfg[j].perc_max = 100;
246 80 data->in_cfg[i].map_cfg[j].perc_threshold = 50;
247 80 data->in_cfg[i].map_cfg[j].perc_deadzone = 135;
248 80 data->in_cfg[i].map_cfg[j].turbo = 0;
249 80 data->in_cfg[i].map_cfg[j].algo = 0;
250 }
251 }
252 1 }
253
254 1 static void config_init_nvs_patch(struct config *data) {
255 1 esp_err_t err;
256 1 nvs_handle_t nvs;
257 1 uint8_t value;
258
259 1 err = nvs_open("global", NVS_READONLY, &nvs);
260
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→17) taken 1 times.
1 if (err == ESP_OK) {
261 err = nvs_get_u8(nvs, "system", &value);
262 if (err == ESP_OK) {
263 data->global_cfg.system_cfg = value;
264 }
265 err = nvs_get_u8(nvs, "multitap", &value);
266 if (err == ESP_OK) {
267 data->global_cfg.multitap_cfg = value;
268 }
269 err = nvs_get_u8(nvs, "inquiry", &value);
270 if (err == ESP_OK) {
271 data->global_cfg.inquiry_mode = value;
272 }
273 err = nvs_get_u8(nvs, "bank", &value);
274 if (err == ESP_OK) {
275 data->global_cfg.banksel = value;
276 }
277 nvs_close(nvs);
278 }
279
280 1 err = nvs_open("output", NVS_READONLY, &nvs);
281
1/2
✗ Branch 0 (18→19) not taken.
✓ Branch 1 (18→30) taken 1 times.
1 if (err == ESP_OK) {
282 err = nvs_get_u8(nvs, "mode", &value);
283 if (err == ESP_OK) {
284 for (uint32_t i = 0; i < WIRED_MAX_DEV; i++) {
285 data->out_cfg[i].dev_mode = value;
286 }
287 }
288 err = nvs_get_u8(nvs, "accessories", &value);
289 if (err == ESP_OK) {
290 for (uint32_t i = 0; i < WIRED_MAX_DEV; i++) {
291 data->out_cfg[i].acc_mode = value;
292 }
293 }
294 nvs_close(nvs);
295 }
296
297 1 err = nvs_open("mapping", NVS_READONLY, &nvs);
298
1/2
✗ Branch 0 (31→32) not taken.
✓ Branch 1 (31→52) taken 1 times.
1 if (err == ESP_OK) {
299 nvs_iterator_t it = NULL;
300 err = nvs_entry_find_in_handle(nvs, NVS_TYPE_ANY, &it);
301 while (err == ESP_OK) {
302 nvs_entry_info_t info;
303 nvs_entry_info(it, &info);
304 errno = 0;
305 uint32_t index = strtol(info.key, NULL, 10);
306 if (!errno) {
307 struct map_cfg mapping = {0};
308 size_t size = sizeof(struct map_cfg);
309 err = nvs_get_blob(nvs, info.key, &mapping, &size);
310 if (err == ESP_OK) {
311 for (uint32_t i = 0; i < WIRED_MAX_DEV; i++) {
312 memcpy(&data->in_cfg[i].map_cfg[index], &mapping, sizeof(struct map_cfg));
313 data->in_cfg[i].map_cfg[index].dst_id = i;
314 }
315 }
316 }
317 err = nvs_entry_next(&it);
318 }
319 nvs_release_iterator(it);
320 nvs_close(nvs);
321 }
322 1 }
323
324 1 static int32_t config_load_from_file(struct config *data, char *filename) {
325 #ifdef CONFIG_BLUERETRO_QEMU
326 1 config_init_struct(data);
327 1 config_init_nvs_patch(data);
328 1 return 0;
329 #else
330 struct stat st;
331 int32_t ret = -1;
332
333 if (stat(filename, &st) != 0) {
334 printf("%s: No config on FS. Creating...\n", __FUNCTION__);
335 config_init_struct(data);
336 config_init_nvs_patch(data);
337 ret = config_store_on_file(data, filename);
338 }
339 else {
340 FILE *file = fopen(filename, "rb");
341 if (file == NULL) {
342 printf("%s: failed to open file for reading\n", __FUNCTION__);
343 }
344 else {
345 uint32_t count = fread((void *)data, sizeof(*data), 1, file);
346 fclose(file);
347 if (count == 1) {
348 ret = 0;
349 }
350 }
351 }
352
353 if (data->magic != CONFIG_MAGIC) {
354 int32_t file_ver = config_get_version(data->magic);
355 if (file_ver == -1) {
356 printf("%s: Bad magic, reset config\n", __FUNCTION__);
357 config_init_struct(data);
358 ret = config_store_on_file(data, filename);
359 }
360 else {
361 printf("%s: Upgrading cfg v%ld to v%d\n", __FUNCTION__, file_ver, CONFIG_VERSION);
362 for (uint32_t i = file_ver; i < CONFIG_VERSION; i++) {
363 if (config_ver_update[i]) {
364 ret = config_ver_update[i](data, filename);
365 }
366 }
367 }
368 }
369
370 return ret;
371 #endif /* CONFIG_BLUERETRO_QEMU */
372 }
373
374 static int32_t config_store_on_file(struct config *data, char *filename) {
375 int32_t ret = -1;
376
377 FILE *file = fopen(filename, "wb");
378 if (file == NULL) {
379 printf("%s: failed to open file for writing\n", __FUNCTION__);
380 }
381 else {
382 fwrite((void *)data, sizeof(*data), 1, file);
383 fclose(file);
384 ret = 0;
385 }
386 return ret;
387 }
388
389 static bool config_is_rst_required(void) {
390 static uint32_t magic = 0;
391 static uint8_t multitap_cfg = 0;
392 static uint8_t dev_mode[WIRED_MAX_DEV] = {0};
393 bool ret = false;
394
395 if (multitap_cfg != config.global_cfg.multitap_cfg) {
396 ret = true;
397 }
398 multitap_cfg = config.global_cfg.multitap_cfg;
399
400 for (uint32_t i = 0; i < WIRED_MAX_DEV; i++) {
401 if (dev_mode[i] != config.out_cfg[i].dev_mode) {
402 ret = true;
403 }
404 dev_mode[i] = config.out_cfg[i].dev_mode;
405 }
406
407 if (magic != config.magic) {
408 ret = false;
409 }
410 magic = config.magic;
411
412 return ret;
413 }
414
415 void IRAM_ATTR config_set_rst_bare_core(bool value) {
416 config_rst_bare_core = value;
417 }
418
419 void hw_config_patch(void) {
420 esp_err_t err;
421 nvs_handle_t nvs;
422
423 err = nvs_open("hw", NVS_READONLY, &nvs);
424 if (err == ESP_OK) {
425 nvs_iterator_t it = NULL;
426 err = nvs_entry_find_in_handle(nvs, NVS_TYPE_ANY, &it);
427 while (err == ESP_OK) {
428 nvs_entry_info_t info;
429 nvs_entry_info(it, &info);
430 int32_t index = hw_config_lookup_key_name(info.key);
431 if (index > -1) {
432 uint32_t value;
433 err = nvs_get_u32(nvs, info.key, &value);
434 if (err == ESP_OK) {
435 hw_config.data32[index] = value;
436 }
437 }
438 err = nvs_entry_next(&it);
439 }
440 nvs_release_iterator(it);
441 nvs_close(nvs);
442 }
443 }
444
445 1 void config_init(uint32_t src) {
446 1 char tmp_str[32] = "/fs/";
447 1 char *filename = CONFIG_FILE;
448 1 char *gameid = gid_get();
449 1 config_src = DEFAULT_CFG;
450
451
1/4
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→9) taken 1 times.
✗ Branch 2 (4→5) not taken.
✗ Branch 3 (4→9) not taken.
1 if (src == GAMEID_CFG && strlen(gameid)) {
452 struct stat st;
453
454 strcat(tmp_str, gameid);
455 if (stat(tmp_str, &st) == 0) {
456 filename = tmp_str;
457 config_src = GAMEID_CFG;
458 }
459 }
460
461 1 config_load_from_file(&config, filename);
462
1/4
✗ Branch 0 (10→11) not taken.
✓ Branch 1 (10→15) taken 1 times.
✗ Branch 2 (12→13) not taken.
✗ Branch 3 (12→15) not taken.
1 if (config_rst_bare_core && config_is_rst_required()) {
463 sys_mgr_cmd(SYS_MGR_CMD_WIRED_RST);
464 printf("# %s: Reloaded wired core cfg: %s\n", __FUNCTION__, filename);
465 }
466 1 }
467
468 void config_update(uint32_t dst) {
469 char tmp_str[32] = "/fs/";
470 char *filename = CONFIG_FILE;
471 char *gameid = gid_get();
472 config_src = DEFAULT_CFG;
473
474 if (dst == GAMEID_CFG && strlen(gameid)) {
475 strcat(tmp_str, gameid);
476 filename = tmp_str;
477 config_src = GAMEID_CFG;
478 }
479
480 config_store_on_file(&config, filename);
481 if (config_rst_bare_core && config_is_rst_required()) {
482 sys_mgr_cmd(SYS_MGR_CMD_WIRED_RST);
483 printf("# %s: Reloaded wired core cfg: %s\n", __FUNCTION__, filename);
484 }
485 }
486
487 uint32_t config_get_src(void) {
488 return config_src;
489 }
490
491 void config_debug_log(void) {
492 bt_mon_log(true,
493 "Global config: system: 0x%02X multitap: 0x%02X inquiry: 0x%02X banksel: 0x%02X",
494 config.global_cfg.system_cfg, config.global_cfg.multitap_cfg,
495 config.global_cfg.inquiry_mode, config.global_cfg.banksel);
496 bt_mon_log(true, "Output config #0: device_mode: 0x%02X acc_mode: 0x%02X",
497 config.out_cfg[0].dev_mode, config.out_cfg[0].acc_mode);
498 bt_mon_log(true, "Mapping config #0");
499 bt_mon_tx(BT_MON_SYS_NOTE, (uint8_t *)config.in_cfg[0].map_cfg,
500 config.in_cfg[0].map_size * sizeof(config.in_cfg[0].map_cfg[0]));
501 }
502