diff --git a/src/dfu/dfu_manager.c b/src/dfu/dfu_manager.c new file mode 100644 index 0000000..305bf05 --- /dev/null +++ b/src/dfu/dfu_manager.c @@ -0,0 +1,238 @@ +/******************************************************************************* + * @file dfu_manager.c + * @brief DFU state, watchdog, and MCUboot image control + ******************************************************************************/ +#include +#include +#include +#include + +#include "main.h" +#include "dfu_manager.h" +#include "debug_print.h" +#include "led_control.h" +#include "ble_service.h" + +#define DFU_STALL_TIMEOUT_SEC 30 // DFU upload stall disconnect threshold +#define IMG_MGMT_ID_STATE 0U +#define IMG_MGMT_ID_UPLOAD 1U + +static bool dfu_led_active; // DFU upload green LED state +static struct k_work_delayable dfu_watchdog_work; // DFU upload stall watchdog + +static void dfu_watchdog_handler(struct k_work *work); + +static enum mgmt_cb_return dfu_status_cb(uint32_t event, + enum mgmt_cb_return prev_status, + int32_t *rc, + uint16_t *group, + bool *abort_more, + void *data, + size_t data_size); + +static enum mgmt_cb_return smp_cmd_status_cb(uint32_t event, + enum mgmt_cb_return prev_status, + int32_t *rc, + uint16_t *group, + bool *abort_more, + void *data, + size_t data_size); + +static struct mgmt_callback dfu_status_callback = { + .callback = dfu_status_cb, + .event_id = MGMT_EVT_OP_IMG_MGMT_DFU_STARTED | + MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK | + MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK_WRITE_COMPLETE | + MGMT_EVT_OP_IMG_MGMT_DFU_PENDING | + MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED, +}; + +static struct mgmt_callback smp_cmd_status_callback = { + .callback = smp_cmd_status_cb, + .event_id = MGMT_EVT_OP_CMD_DONE, +}; + +/* DFU watchdog와 mcumgr 콜백 초기화 */ +void dfu_manager_init(void) +{ + k_work_init_delayable(&dfu_watchdog_work, dfu_watchdog_handler); + mgmt_callback_register(&dfu_status_callback); + mgmt_callback_register(&smp_cmd_status_callback); +} + +/* 현재 이미지가 confirm 대기 상태인지 확인 */ +bool dfu_is_confirm_pending_boot(void) +{ +#if defined(CONFIG_BOOTLOADER_MCUBOOT) + return !boot_is_img_confirmed(); +#else + return false; +#endif +} + +/* 실행 중 이미지 confirm 처리 */ +void dfu_confirm_running_image(void) +{ +#if defined(CONFIG_BOOTLOADER_MCUBOOT) + if (!boot_is_img_confirmed()) + { + int err = boot_write_img_confirmed(); + if (err == 0) + { + DBG_CORE("[DFU] Running image confirmed\r\n"); + } + else + { + DBG_ERR("[DFU] Image confirm failed (err %d)\r\n", err); + } + } + else + { + DBG_CORE("[DFU] Running image already confirmed\r\n"); + } +#endif +} + +/* MCUboot swap 상태 문자열 변환 */ +static const char *swap_type_to_str(int swap_type) +{ + switch (swap_type) + { + case BOOT_SWAP_TYPE_NONE: + return "none"; + case BOOT_SWAP_TYPE_TEST: + return "test"; + case BOOT_SWAP_TYPE_PERM: + return "perm"; + case BOOT_SWAP_TYPE_REVERT: + return "revert"; + case BOOT_SWAP_TYPE_FAIL: + return "fail"; + default: + return "unknown"; + } +} + +/* MCUboot 이미지 상태 로그 출력 */ +void dfu_log_mcuboot_state(void) +{ +#if defined(CONFIG_BOOTLOADER_MCUBOOT) + struct mcuboot_img_header header; + uint8_t active_slot = boot_fetch_active_slot(); + int swap_type = mcuboot_swap_type(); + int err = boot_read_bank_header(active_slot, &header, sizeof(header)); + + DBG_CORE("[DFU] active_slot=%u swap_type=%s(%d) confirmed=%d\r\n", + active_slot, + swap_type_to_str(swap_type), + swap_type, + boot_is_img_confirmed()); + + if (err == 0) + { + DBG_CORE("[DFU] image version %u.%u.%u+%u size=0x%x\r\n", + header.h.v1.sem_ver.major, + header.h.v1.sem_ver.minor, + header.h.v1.sem_ver.revision, + header.h.v1.sem_ver.build_num, + header.h.v1.image_size); + } + else + { + DBG_ERR("[DFU] image header read failed (slot %u, err %d)\r\n", + active_slot, + err); + } +#endif +} + +/* DFU 업로드 무진행 시 BLE 연결 강제 해제 */ +static void dfu_watchdog_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + if (ble_is_connected()) + { + DBG_CORE("[DFU] upload stalled (%ds), forcing disconnect\r\n", DFU_STALL_TIMEOUT_SEC); + (void)ble_disconnect_active(); + } +} + +/* DFU 업로드 상태에 따라 LED와 watchdog 제어 */ +static enum mgmt_cb_return dfu_status_cb(uint32_t event, enum mgmt_cb_return prev_status, int32_t *rc, uint16_t *group, bool *abort_more, void *data, size_t data_size) +{ + ARG_UNUSED(prev_status); + ARG_UNUSED(rc); + ARG_UNUSED(group); + ARG_UNUSED(abort_more); + ARG_UNUSED(data); + ARG_UNUSED(data_size); + + switch (event) + { + case MGMT_EVT_OP_IMG_MGMT_DFU_STARTED: + case MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK: + case MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK_WRITE_COMPLETE: + dfu_led_active = true; + led_ble_solid(); + k_work_reschedule(&dfu_watchdog_work, K_SECONDS(DFU_STALL_TIMEOUT_SEC)); + break; + + case MGMT_EVT_OP_IMG_MGMT_DFU_PENDING: + dfu_led_active = true; + led_ble_solid(); + k_work_cancel_delayable(&dfu_watchdog_work); + +#if NRF_POWER_HAS_GPREGRET + nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, DFU_RESUME_MAGIC); +#endif + break; + + case MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED: + k_work_cancel_delayable(&dfu_watchdog_work); + if (dfu_led_active) + { + dfu_led_active = false; + led_set_state(LED_STATE_ADVERTISING); + } + break; + + default: + break; + } + + return MGMT_CB_OK; +} + +/* DFU 명령 실패 시 LED와 resume 플래그 복구 */ +static enum mgmt_cb_return smp_cmd_status_cb(uint32_t event, enum mgmt_cb_return prev_status, int32_t *rc, uint16_t *group, bool *abort_more, void *data, size_t data_size) +{ + ARG_UNUSED(event); + ARG_UNUSED(prev_status); + ARG_UNUSED(rc); + ARG_UNUSED(group); + ARG_UNUSED(abort_more); + + if (data == NULL || data_size != sizeof(struct mgmt_evt_op_cmd_arg)) + { + return MGMT_CB_OK; + } + + const struct mgmt_evt_op_cmd_arg *cmd = data; + + if (cmd->group == MGMT_GROUP_ID_IMAGE && + (cmd->id == IMG_MGMT_ID_UPLOAD || cmd->id == IMG_MGMT_ID_STATE) && + cmd->err != MGMT_ERR_EOK) + { + DBG_ERR("[DFU] command failed id=%u err=%d\r\n", cmd->id, cmd->err); + + dfu_led_active = false; + led_set_state(LED_STATE_ADVERTISING); + +#if NRF_POWER_HAS_GPREGRET + nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, 0U); +#endif + } + + return MGMT_CB_OK; +} \ No newline at end of file diff --git a/src/dfu/dfu_manager.h b/src/dfu/dfu_manager.h new file mode 100644 index 0000000..c54ad4d --- /dev/null +++ b/src/dfu/dfu_manager.h @@ -0,0 +1,18 @@ +/******************************************************************************* + * @file dfu_manager.h + * @brief DFU state, watchdog, and MCUboot image control + ******************************************************************************/ +#ifndef DFU_MANAGER_H__ +#define DFU_MANAGER_H__ + +#include + +#define DFU_RESUME_MAGIC 0xD5U +#define DFU_RESUME_GPREGRET_REG 1U + +void dfu_manager_init(void); +bool dfu_is_confirm_pending_boot(void); +void dfu_confirm_running_image(void); +void dfu_log_mcuboot_state(void); + +#endif /* DFU_MANAGER_H__ */ \ No newline at end of file diff --git a/src/main.c b/src/main.c index 157844c..48423fb 100644 --- a/src/main.c +++ b/src/main.c @@ -5,13 +5,12 @@ #include #include -#include -#include #include #include "main.h" #include "debug_print.h" #include "power_control.h" +#include "dfu_manager.h" #include "led_control.h" #include "ble_service.h" #include "battery_adc.h" @@ -24,11 +23,6 @@ LOG_MODULE_REGISTER(vesiscan, LOG_LEVEL_INF); #define BLE_CMD_MAX_LEN 256 #define BLE_CMD_QUEUE_DEPTH 8 // RX 명령 링버퍼 깊이(버스트 흡수) #define BLE_CMD_WORKQ_STACK_SZ 8192 -#define DFU_RESUME_MAGIC 0xD5U -#define DFU_RESUME_GPREGRET_REG 1U -#define DFU_STALL_TIMEOUT_SEC 30 // DFU 업로드 무진행 시 강제 disconnect 임계 -#define IMG_MGMT_ID_STATE 0U -#define IMG_MGMT_ID_UPLOAD 1U /* * BLE 명령 처리 스택 * - piezo sweep @@ -52,45 +46,6 @@ static bool resume_without_power_button; // 리셋 후 버튼 없이 복귀 static bool dfu_confirm_pending_boot; // DFU test 이미지 확인 전 자동 복귀 static bool dfu_reset_resume_request; // DFU 완료 리셋 후 자동 복귀 static uint32_t boot_reset_reason; // RESETREAS 원본 값 -static bool dfu_led_active; // DFU 업로드 중 초록 LED ON -static struct k_work_delayable dfu_watchdog_work; // DFU 업로드 무진행 감시 work -static void dfu_watchdog_handler(struct k_work *work); - -static enum mgmt_cb_return dfu_status_cb(uint32_t event, - enum mgmt_cb_return prev_status, - int32_t *rc, - uint16_t *group, - bool *abort_more, - void *data, - size_t data_size); - -static enum mgmt_cb_return smp_cmd_status_cb(uint32_t event, - enum mgmt_cb_return prev_status, - int32_t *rc, - uint16_t *group, - bool *abort_more, - void *data, - size_t data_size); - -static struct mgmt_callback dfu_status_callback = { - .callback = dfu_status_cb, - .event_id = MGMT_EVT_OP_IMG_MGMT_DFU_STARTED | - MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK | - MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK_WRITE_COMPLETE | - MGMT_EVT_OP_IMG_MGMT_DFU_PENDING | - MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED, -}; - -static struct mgmt_callback smp_cmd_status_callback = { - .callback = smp_cmd_status_cb, - .event_id = MGMT_EVT_OP_CMD_DONE, -}; - -/* - * BLE RX 콜백 안에서 무거운 일을 바로 처리하지 않기 위해 별도 work queue를 둠 - * - 연결 직후 초기화 버스트 등 명령이 몰릴 때 대비해 링버퍼(FIFO)로 흡수 - * - 큐가 가득 차면(BLE_CMD_QUEUE_DEPTH) 그 때만 drop - */ static struct k_work_q ble_cmd_work_q; K_THREAD_STACK_DEFINE(ble_cmd_workq_stack, BLE_CMD_WORKQ_STACK_SZ); static struct k_work ble_cmd_work; // BLE 명령 처리 work @@ -246,188 +201,11 @@ static void load_default_config(void) static void timers_init(void) { - // DFU 업로드 무진행 감시 work - k_work_init_delayable(&dfu_watchdog_work, dfu_watchdog_handler); - // BLE 명령 처리 work queue k_work_init(&ble_cmd_work, ble_cmd_work_handler); k_work_queue_start(&ble_cmd_work_q, ble_cmd_workq_stack, K_THREAD_STACK_SIZEOF(ble_cmd_workq_stack), BLE_CMD_WORKQ_PRIORITY, NULL); - // DFU 업로드 상태를 LED에 반영 - mgmt_callback_register(&dfu_status_callback); - - // DFU 명령 실패 시 LED 상태 복구 - mgmt_callback_register(&smp_cmd_status_callback); -} - - -static void dfu_watchdog_handler(struct k_work *work) -{ - ARG_UNUSED(work); - - if (ble_is_connected()) - { - DBG_CORE("[DFU] upload stalled (%ds), forcing disconnect\r\n", DFU_STALL_TIMEOUT_SEC); - (void)ble_disconnect_active(); - } -} - -static enum mgmt_cb_return dfu_status_cb(uint32_t event, enum mgmt_cb_return prev_status, int32_t *rc, uint16_t *group, bool *abort_more, void *data, size_t data_size) -{ - ARG_UNUSED(prev_status); - ARG_UNUSED(rc); - ARG_UNUSED(group); - ARG_UNUSED(abort_more); - ARG_UNUSED(data); - ARG_UNUSED(data_size); - - switch (event) - { - case MGMT_EVT_OP_IMG_MGMT_DFU_STARTED: - case MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK: - case MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK_WRITE_COMPLETE: - dfu_led_active = true; - led_ble_solid(); // DFU 업로드 중에는 초록 LED ON - // 청크 수신/flash write 완료 때마다 무진행 감시 타이머 리셋 - k_work_reschedule(&dfu_watchdog_work, K_SECONDS(DFU_STALL_TIMEOUT_SEC)); - break; - - case MGMT_EVT_OP_IMG_MGMT_DFU_PENDING: - dfu_led_active = true; - led_ble_solid(); // 업로드 완료 후 리셋 전까지 초록 LED ON - k_work_cancel_delayable(&dfu_watchdog_work); // 업로드 완료: 감시 종료 - -#if NRF_POWER_HAS_GPREGRET - nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, DFU_RESUME_MAGIC); // DFU 리셋 후 버튼 없이 전원 ON -#endif - break; - - case MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED: - k_work_cancel_delayable(&dfu_watchdog_work); // 중단/실패: 감시 종료 - if (dfu_led_active) - { - dfu_led_active = false; - led_set_state(LED_STATE_ADVERTISING); // 실패나 중단 시 일반 광고 LED로 복귀 - } - break; - - default: - break; - } - - return MGMT_CB_OK; -} - -static enum mgmt_cb_return smp_cmd_status_cb(uint32_t event, enum mgmt_cb_return prev_status, int32_t *rc, uint16_t *group, bool *abort_more, void *data, size_t data_size) -{ - ARG_UNUSED(event); - ARG_UNUSED(prev_status); - ARG_UNUSED(rc); - ARG_UNUSED(group); - ARG_UNUSED(abort_more); - - if (data == NULL || data_size != sizeof(struct mgmt_evt_op_cmd_arg)) - { - return MGMT_CB_OK; - } - - const struct mgmt_evt_op_cmd_arg *cmd = data; - - // 이미지 업로드/test 명령 실패 시 DFU LED 고정 해제 - if (cmd->group == MGMT_GROUP_ID_IMAGE && - (cmd->id == IMG_MGMT_ID_UPLOAD || cmd->id == IMG_MGMT_ID_STATE) && - cmd->err != MGMT_ERR_EOK) - { - DBG_ERR("[DFU] command failed id=%u err=%d\r\n", cmd->id, cmd->err); - - dfu_led_active = false; - led_set_state(LED_STATE_ADVERTISING); - - // 실패 후 리셋 복구 표식이 남지 않게 정리 -#if NRF_POWER_HAS_GPREGRET - nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, 0U); -#endif - } - - return MGMT_CB_OK; -} - -static void confirm_running_image(void) -{ -#if defined(CONFIG_BOOTLOADER_MCUBOOT) - // 기본 초기화와 BLE 준비가 끝난 뒤 MCUboot revert 방지 - if (!boot_is_img_confirmed()) - { - int err = boot_write_img_confirmed(); - if (err == 0) - { - DBG_CORE("[DFU] Running image confirmed\r\n"); - } - else - { - DBG_ERR("[DFU] Image confirm failed (err %d)\r\n", err); - } - } - else - { - DBG_CORE("[DFU] Running image already confirmed\r\n"); - } -#endif -} - -static const char *swap_type_to_str(int swap_type) -{ - // MCUboot swap 상태 문자열 - switch (swap_type) - { - case BOOT_SWAP_TYPE_NONE: - return "none"; - case BOOT_SWAP_TYPE_TEST: - return "test"; - case BOOT_SWAP_TYPE_PERM: - return "perm"; - case BOOT_SWAP_TYPE_REVERT: - return "revert"; - case BOOT_SWAP_TYPE_FAIL: - return "fail"; - default: - return "unknown"; - } -} - -static void log_mcuboot_state(void) -{ -#if defined(CONFIG_BOOTLOADER_MCUBOOT) - // 현재 부팅 슬롯/교체 상태 - struct mcuboot_img_header header; - uint8_t active_slot = boot_fetch_active_slot(); - int swap_type = mcuboot_swap_type(); - int err = boot_read_bank_header(active_slot, &header, sizeof(header)); - - DBG_CORE("[DFU] active_slot=%u swap_type=%s(%d) confirmed=%d\r\n", - active_slot, - swap_type_to_str(swap_type), - swap_type, - boot_is_img_confirmed()); - - if (err == 0) - { - /* 실행 이미지 버전 */ - DBG_CORE("[DFU] image version %u.%u.%u+%u size=0x%x\r\n", - header.h.v1.sem_ver.major, - header.h.v1.sem_ver.minor, - header.h.v1.sem_ver.revision, - header.h.v1.sem_ver.build_num, - header.h.v1.image_size); - } - else - { - DBG_ERR("[DFU] image header read failed (slot %u, err %d)\r\n", - active_slot, - err); - } -#endif } /* 메인 함수 */ @@ -438,13 +216,13 @@ int main(void) // 하드웨어 기본 초기화 #if defined(CONFIG_BOOTLOADER_MCUBOOT) - dfu_confirm_pending_boot = !boot_is_img_confirmed(); + dfu_confirm_pending_boot = dfu_is_confirm_pending_boot(); #else dfu_confirm_pending_boot = false; #endif power_control_latch_init(resume_without_power_button || dfu_confirm_pending_boot || dfu_reset_resume_request); power_control_reset_state(); - confirm_running_image(); + dfu_confirm_running_image(); DBG_CORE("\r\n========================================\r\n"); DBG_CORE(" TEST BUILD %s (Zephyr)\r\n", FIRMWARE_VERSION); @@ -455,7 +233,8 @@ int main(void) // 기본 하드웨어/센서 초기화 power_control_init(); timers_init(); - log_mcuboot_state(); + dfu_manager_init(); + dfu_log_mcuboot_state(); load_default_config(); led_init(); battery_adc_init(); @@ -475,7 +254,7 @@ int main(void) power_control_resume_after_reset(boot_reset_reason); } // BLE 준비까지 확인한 뒤 DFU test 이미지를 확정 - confirm_running_image(); + dfu_confirm_running_image(); } else {