From d0fac7975c9570845736c6e0f1ff4c2dfccd063f Mon Sep 17 00:00:00 2001 From: Jonathan Challinger Date: Fri, 14 Feb 2020 20:52:29 -0800 Subject: [PATCH] wip --- bootloader/Makefile | 9 +- .../equipment/gnss/20210.BodyPosition.uavcan | 1 + .../equipment/gnss/20211.MovingBaseFix.uavcan | 10 + .../dsdl/com/hex/equipment/gnss/gnss.zip | Bin 0 -> 474 bytes .../dsdl/com/hex/file/242.Modify.uavcan | 10 + .../com/hex/file/243.FileStreamStart.uavcan | 4 + .../com/hex/file/244.StartDownload.uavcan | 6 + .../com/hex/file/42444.FileStreamChunk.uavcan | 3 + bootloader/openocd.cfg | 10 +- bootloader/out.bin | Bin 0 -> 43276 bytes bootloader/src/bootloader.c | 174 ++++++++++++++---- include/chconf.h | 4 + mk/build.mk | 2 +- modules/can/can.c | 6 +- modules/can/can_driver.h | 2 +- modules/can_driver_stm32/can_driver_stm32h7.c | 38 ++-- modules/platform_stm32h743/module.mk | 2 +- modules/uavcan_allocatee/uavcan_allocatee.c | 2 +- modules/uavcan_broadcast_file_update/write.c | 79 ++++++++ platforms/ARMCMx/ld/stm32h743/memory.ld | 4 +- tools/uavcan_upload.py | 4 +- 21 files changed, 299 insertions(+), 71 deletions(-) create mode 100644 bootloader/dsdl/com/hex/equipment/gnss/20210.BodyPosition.uavcan create mode 100644 bootloader/dsdl/com/hex/equipment/gnss/20211.MovingBaseFix.uavcan create mode 100644 bootloader/dsdl/com/hex/equipment/gnss/gnss.zip create mode 100644 bootloader/dsdl/com/hex/file/242.Modify.uavcan create mode 100644 bootloader/dsdl/com/hex/file/243.FileStreamStart.uavcan create mode 100644 bootloader/dsdl/com/hex/file/244.StartDownload.uavcan create mode 100644 bootloader/dsdl/com/hex/file/42444.FileStreamChunk.uavcan create mode 100644 bootloader/out.bin create mode 100644 modules/uavcan_broadcast_file_update/write.c diff --git a/bootloader/Makefile b/bootloader/Makefile index 0d27d07..b29c757 100644 --- a/bootloader/Makefile +++ b/bootloader/Makefile @@ -24,14 +24,19 @@ uavcan_nodestatus_publisher \ uavcan_allocatee \ spi_device \ driver_profiLED \ -uavcan_timesync +uavcan_timesync \ +uavcan_debug MESSAGES_ENABLED = \ uavcan.protocol.GetNodeInfo \ uavcan.protocol.file.BeginFirmwareUpdate \ uavcan.protocol.file.Read \ uavcan.protocol.RestartNode \ -uavcan.equipment.indication.LightsCommand +uavcan.equipment.indication.LightsCommand \ +com.hex.file.FileStreamStart \ +com.hex.file.FileStreamChunk + +DSDL_NAMESPACE_DIRS += dsdl/com LOAD_REGION = bl diff --git a/bootloader/dsdl/com/hex/equipment/gnss/20210.BodyPosition.uavcan b/bootloader/dsdl/com/hex/equipment/gnss/20210.BodyPosition.uavcan new file mode 100644 index 0000000..cd16d40 --- /dev/null +++ b/bootloader/dsdl/com/hex/equipment/gnss/20210.BodyPosition.uavcan @@ -0,0 +1 @@ +int18[3] body_pos_mm diff --git a/bootloader/dsdl/com/hex/equipment/gnss/20211.MovingBaseFix.uavcan b/bootloader/dsdl/com/hex/equipment/gnss/20211.MovingBaseFix.uavcan new file mode 100644 index 0000000..cfdfcfe --- /dev/null +++ b/bootloader/dsdl/com/hex/equipment/gnss/20211.MovingBaseFix.uavcan @@ -0,0 +1,10 @@ +uavcan.Timestamp timestamp +uint8[16] base_in_use_hwid +void6 +uint2 CARRIER_SOLUTION_TYPE_NONE = 0 +uint2 CARRIER_SOLUTION_TYPE_FLOAT = 1 +uint2 CARRIER_SOLUTION_TYPE_FIXED = 2 +uint2 carrier_solution_type +float32[<=3] pos_rel_body +float32[3] pos_rel_ecef +float16[<=6] pos_rel_ecef_covariance diff --git a/bootloader/dsdl/com/hex/equipment/gnss/gnss.zip b/bootloader/dsdl/com/hex/equipment/gnss/gnss.zip new file mode 100644 index 0000000000000000000000000000000000000000..b2ace1a501179de0ddc222a6342b792c0deb5c6a GIT binary patch literal 474 zcmWIWW@h1HW&ncr3`2j*O=rVJfou?#1Y#otBSQl{r~H)4fc)ajlFa-(z0$<8|K6>(<^j>-Ycgl?xu{5Vo@$_67EtLT#~j_{BHZV zKIZI)d*9ux47I{6-xpli^1Ab1&9xJ)_e8dZPwL|1h&hmP?(Mnk>`8mR%{!pDTjBH~ zgXUnV`JXj>752+Hdt6dcv5VASL9d=$ literal 0 HcmV?d00001 diff --git a/bootloader/dsdl/com/hex/file/242.Modify.uavcan b/bootloader/dsdl/com/hex/file/242.Modify.uavcan new file mode 100644 index 0000000..e29b046 --- /dev/null +++ b/bootloader/dsdl/com/hex/file/242.Modify.uavcan @@ -0,0 +1,10 @@ +bool preserve_source +bool overwrite_destination +void6 + +uavcan.protocol.file.Path source +uavcan.protocol.file.Path destination + +--- + +uavcan.protocol.file.Error error diff --git a/bootloader/dsdl/com/hex/file/243.FileStreamStart.uavcan b/bootloader/dsdl/com/hex/file/243.FileStreamStart.uavcan new file mode 100644 index 0000000..4b50ca3 --- /dev/null +++ b/bootloader/dsdl/com/hex/file/243.FileStreamStart.uavcan @@ -0,0 +1,4 @@ +uavcan.protocol.file.Path path +uint40 offset +--- +uavcan.protocol.file.Error error diff --git a/bootloader/dsdl/com/hex/file/244.StartDownload.uavcan b/bootloader/dsdl/com/hex/file/244.StartDownload.uavcan new file mode 100644 index 0000000..b717210 --- /dev/null +++ b/bootloader/dsdl/com/hex/file/244.StartDownload.uavcan @@ -0,0 +1,6 @@ +uint8 source_node_id +uavcan.protocol.file.Path source_path +uavcan.protocol.file.Path dest_path +--- +void7 +bool ack diff --git a/bootloader/dsdl/com/hex/file/42444.FileStreamChunk.uavcan b/bootloader/dsdl/com/hex/file/42444.FileStreamChunk.uavcan new file mode 100644 index 0000000..ab0e4e7 --- /dev/null +++ b/bootloader/dsdl/com/hex/file/42444.FileStreamChunk.uavcan @@ -0,0 +1,3 @@ +uavcan.protocol.file.Path path +uint40 offset +uint8[<=256] data diff --git a/bootloader/openocd.cfg b/bootloader/openocd.cfg index d0d6d76..c0d7bf7 100644 --- a/bootloader/openocd.cfg +++ b/bootloader/openocd.cfg @@ -1,12 +1,4 @@ source [find interface/stlink.cfg] source [find target/stm32h7x.cfg] -reset_config srst_only separate connect_assert_srst -$_TARGETNAME configure -rtos ChibiOS -$_TARGETNAME configure -event gdb-attach { - halt -} -$_TARGETNAME configure -event gdb-attach { - reset init -} +$_CHIPNAME.cpu0 configure -rtos ChibiOS init -reset halt diff --git a/bootloader/out.bin b/bootloader/out.bin new file mode 100644 index 0000000000000000000000000000000000000000..964bef0fdd08c364255d2d293c0856f155a82916 GIT binary patch literal 43276 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 literal 0 HcmV?d00001 diff --git a/bootloader/src/bootloader.c b/bootloader/src/bootloader.c index 6e602fd..0aa4d9e 100644 --- a/bootloader/src/bootloader.c +++ b/bootloader/src/bootloader.c @@ -15,6 +15,18 @@ #include #include +#ifdef MODULE_UAVCAN_DEBUG_ENABLED +#include +#define BL_DEBUG(...) uavcan_send_debug_msg(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_DEBUG, "BL", __VA_ARGS__) +#else +#define BL_DEBUG(...) {} +#endif + +#ifdef BOOTLOADER_SUPPORT_BROADCAST_UPDATE +#include +#include +#endif // BOOTLOADER_SUPPORT_BROADCAST_UPDATE + #if __GNUC__ != 6 || __GNUC_MINOR__ != 3 || __GNUC_PATCHLEVEL__ != 1 #error Please use arm-none-eabi-gcc 6.3.1. #endif @@ -40,6 +52,7 @@ static const struct shared_hw_info_s _hw_info = BOARD_CONFIG_HW_INFO_STRUCTURE; static struct { bool in_progress; uint32_t ofs; + uint32_t read_req_ofs; uint32_t app_start_ofs; uint8_t uavcan_idx; uint8_t read_transfer_id; @@ -47,6 +60,9 @@ static struct { uint8_t source_node_id; int32_t last_erased_page; char path[201]; +#ifdef BOOTLOADER_SUPPORT_BROADCAST_UPDATE + bool using_stream_mode; +#endif // BOOTLOADER_SUPPORT_BROADCAST_UPDATE } flash_state; static struct { @@ -64,11 +80,18 @@ static struct worker_thread_listener_task_s restart_req_listener_task; static struct worker_thread_timer_task_s delayed_restart_task; static struct worker_thread_listener_task_s getnodeinfo_req_listener_task; +#ifdef BOOTLOADER_SUPPORT_BROADCAST_UPDATE +static struct worker_thread_listener_task_s filestreamchunk_listener_task; +static void filestreamchunk_handler(size_t msg_size, const void* buf, void* ctx); + +// static struct worker_thread_listener_task_s filestreamstart_res_listener_task; +// static void filestreamstart_res_handler(size_t msg_size, const void* buf, void* ctx); +#endif // BOOTLOADER_SUPPORT_BROADCAST_UPDATE + static void file_beginfirmwareupdate_request_handler(size_t msg_size, const void* buf, void* ctx); static void begin_flash_from_path(uint8_t uavcan_idx, uint8_t source_node_id, const char* path); static void file_read_response_handler(size_t msg_size, const void* buf, void* ctx); -static void do_resend_read_request(void); -static void do_send_read_request(void); +static void do_send_read_request(bool retry); static uint32_t get_app_sec_size(void); static void start_boot(struct worker_thread_timer_task_s* task); static void update_app_info(void); @@ -110,6 +133,14 @@ RUN_AFTER(UAVCAN_INIT) { struct pubsub_topic_s* getnodeinfo_req_topic = uavcan_get_message_topic(0, &uavcan_protocol_GetNodeInfo_req_descriptor); worker_thread_add_listener_task(&WT, &getnodeinfo_req_listener_task, getnodeinfo_req_topic, getnodeinfo_req_handler, NULL); + +#ifdef BOOTLOADER_SUPPORT_BROADCAST_UPDATE + struct pubsub_topic_s* filestreamchunk_topic = uavcan_get_message_topic(0, &com_hex_file_FileStreamChunk_descriptor); + worker_thread_add_listener_task(&WT, &filestreamchunk_listener_task, filestreamchunk_topic, filestreamchunk_handler, NULL); + +// struct pubsub_topic_s* filestreamstart_res_topic = uavcan_get_message_topic(0, &com_hex_file_FileStreamStart_res_descriptor); +// worker_thread_add_listener_task(&WT, &filestreamstart_res_listener_task, filestreamstart_res_topic, filestreamstart_res_handler, NULL); +#endif // BOOTLOADER_SUPPORT_BROADCAST_UPDATE } static void getnodeinfo_req_handler(size_t msg_size, const void* buf, void* ctx) { @@ -168,71 +199,141 @@ static void begin_flash_from_path(uint8_t uavcan_idx, uint8_t source_node_id, co cancel_boot_timer(); memset(&flash_state, 0, sizeof(flash_state)); flash_state.in_progress = true; - flash_state.ofs = 0; + flash_state.ofs = 0; + flash_state.read_transfer_id = 255; flash_state.source_node_id = source_node_id; flash_state.uavcan_idx = uavcan_idx; strncpy(flash_state.path, path, 200); - worker_thread_add_timer_task(&WT, &read_timeout_task, read_request_response_timeout, NULL, chTimeMS2I(500), false); - do_send_read_request(); + worker_thread_add_timer_task(&WT, &read_timeout_task, read_request_response_timeout, NULL, chTimeMS2I(2000), false); + do_send_read_request(false); corrupt_app(); flash_state.last_erased_page = -1; } -static void file_read_response_handler(size_t msg_size, const void* buf, void* ctx) { +static void process_chunk(size_t chunk_size, const void* chunk) { + if (flash_state.ofs + chunk_size > get_app_sec_size()) { + do_fail_update(); + BL_DEBUG("fail: file too large"); + return; + } + + if (chunk_size == 0) { + on_update_complete(); + return; + } + + int32_t curr_page = get_app_page_from_ofs(flash_state.ofs + chunk_size); + if (curr_page > flash_state.last_erased_page) { + for (int32_t i=flash_state.last_erased_page+1; i<=curr_page; i++) { + erase_app_page(i); + } + } + + if (chunk_size < 256) { + struct flash_write_buf_s buf = {((chunk_size/FLASH_WORD_SIZE) + 1) * FLASH_WORD_SIZE, chunk}; + flash_write((void*)get_app_address_from_ofs(flash_state.ofs), 1, &buf); + on_update_complete(); + } else { + struct flash_write_buf_s buf = {chunk_size, chunk}; + flash_write((void*)get_app_address_from_ofs(flash_state.ofs), 1, &buf); + flash_state.ofs += chunk_size; + do_send_read_request(false); + } +} + +// TODO factor common code out of read_response_handler and filestreamchunk_handler +#ifdef BOOTLOADER_SUPPORT_BROADCAST_UPDATE +static void filestreamchunk_handler(size_t msg_size, const void* buf, void* ctx) { (void)msg_size; (void)ctx; + if (flash_state.in_progress) { const struct uavcan_deserialized_message_s* msg_wrapper = buf; - const struct uavcan_protocol_file_Read_res_s *res = (const struct uavcan_protocol_file_Read_res_s*)msg_wrapper->msg; + const struct com_hex_file_FileStreamChunk_s *msg = (const struct com_hex_file_FileStreamChunk_s*)msg_wrapper->msg; - if (msg_wrapper->transfer_id != flash_state.read_transfer_id) { + if (msg->path.path_len != strnlen(flash_state.path, 200) || memcmp(flash_state.path, msg->path.path, msg->path.path_len) != 0) { + // Not our stream return; } - if (res->error.value != 0 || flash_state.ofs + res->data_len > get_app_sec_size()) { - do_fail_update(); + flash_state.using_stream_mode = true; + worker_thread_timer_task_reschedule(&WT, &read_timeout_task, chTimeMS2I(2000)); + + if (msg->offset > flash_state.ofs) { + // We need to ask for the stream to go back to our offset + struct com_hex_file_FileStreamStart_req_s req; + req.path.path_len = strnlen(flash_state.path, 200); + req.offset = flash_state.ofs; + memcpy(req.path.path, flash_state.path, req.path.path_len); + uavcan_request(flash_state.uavcan_idx, &com_hex_file_FileStreamStart_req_descriptor, CANARD_TRANSFER_PRIORITY_MEDIUM+1, flash_state.source_node_id, &req); return; } - if (res->data_len == 0) { - on_update_complete(); + if (msg->offset != flash_state.ofs) { return; } - int32_t curr_page = get_app_page_from_ofs(flash_state.ofs + res->data_len); - if (curr_page > flash_state.last_erased_page) { - for (int32_t i=flash_state.last_erased_page+1; i<=curr_page; i++) { - erase_app_page(i); - } + process_chunk(msg->data_len, msg->data); + } +} +#endif // BOOTLOADER_SUPPORT_BROADCAST_UPDATE + +static void file_read_response_handler(size_t msg_size, const void* buf, void* ctx) { + (void)msg_size; + (void)ctx; + if (flash_state.in_progress) { + const struct uavcan_deserialized_message_s* msg_wrapper = buf; + const struct uavcan_protocol_file_Read_res_s *res = (const struct uavcan_protocol_file_Read_res_s*)msg_wrapper->msg; + + if (msg_wrapper->transfer_id != flash_state.read_transfer_id || flash_state.ofs != flash_state.read_req_ofs) { + return; } - if (res->data_len < 256) { - struct flash_write_buf_s buf = {((res->data_len/FLASH_WORD_SIZE) + 1) * FLASH_WORD_SIZE, (void*)res->data}; - flash_write((void*)get_app_address_from_ofs(flash_state.ofs), 1, &buf); - on_update_complete(); - } else { - struct flash_write_buf_s buf = {res->data_len, (void*)res->data}; - flash_write((void*)get_app_address_from_ofs(flash_state.ofs), 1, &buf); - flash_state.ofs += res->data_len; - do_send_read_request(); + if (res->error.value != 0) { + do_fail_update(); + BL_DEBUG("fail: file read error"); + return; } + + process_chunk(res->data_len, res->data); } } -static void do_resend_read_request(void) { +static void do_send_read_request(bool retry) { + +#ifdef BOOTLOADER_SUPPORT_BROADCAST_UPDATE + if (!flash_state.using_stream_mode) { + struct uavcan_protocol_file_Read_req_s read_req; + flash_state.read_req_ofs = read_req.offset = flash_state.ofs; + strncpy((char*)read_req.path.path,flash_state.path,sizeof(read_req.path)); + read_req.path.path_len = strnlen(flash_state.path,sizeof(read_req.path)); + flash_state.read_transfer_id = uavcan_request(flash_state.uavcan_idx, &uavcan_protocol_file_Read_req_descriptor, CANARD_TRANSFER_PRIORITY_MEDIUM+1, flash_state.source_node_id, &read_req); + } + + if ((retry && flash_state.using_stream_mode) || (flash_state.ofs < 2048 && !flash_state.using_stream_mode)) { + // attempt to start stream mode with the first few requests + struct com_hex_file_FileStreamStart_req_s req; + req.offset = flash_state.ofs; + req.path.path_len = strnlen(flash_state.path, 200); + memcpy(req.path.path, flash_state.path, req.path.path_len); + uavcan_request(flash_state.uavcan_idx, &com_hex_file_FileStreamStart_req_descriptor, CANARD_TRANSFER_PRIORITY_MEDIUM+1, flash_state.source_node_id, &req); + } +#else struct uavcan_protocol_file_Read_req_s read_req; read_req.offset = flash_state.ofs; strncpy((char*)read_req.path.path,flash_state.path,sizeof(read_req.path)); read_req.path.path_len = strnlen(flash_state.path,sizeof(read_req.path)); - flash_state.read_transfer_id = uavcan_request(flash_state.uavcan_idx, &uavcan_protocol_file_Read_req_descriptor, CANARD_TRANSFER_PRIORITY_HIGH, flash_state.source_node_id, &read_req); - worker_thread_timer_task_reschedule(&WT, &read_timeout_task, chTimeMS2I(500)); - flash_state.retries++; -} + flash_state.read_transfer_id = uavcan_request(flash_state.uavcan_idx, &uavcan_protocol_file_Read_req_descriptor, CANARD_TRANSFER_PRIORITY_MEDIUM+1, flash_state.source_node_id, &read_req); +#endif -static void do_send_read_request(void) { - do_resend_read_request(); - flash_state.retries = 0; + worker_thread_timer_task_reschedule(&WT, &read_timeout_task, chTimeMS2I(1000)); + + if (retry) { + flash_state.retries++; + } else { + flash_state.retries = 0; + } } static uint32_t get_app_sec_size(void) { @@ -414,9 +515,10 @@ static void delayed_restart_func(struct worker_thread_timer_task_s* task) { static void read_request_response_timeout(struct worker_thread_timer_task_s* task) { (void)task; if (flash_state.in_progress) { - do_resend_read_request(); - if (flash_state.retries > 10) { // retry for 5 seconds + do_send_read_request(true); + if (flash_state.retries > 100) { // retry for 5 seconds do_fail_update(); + BL_DEBUG("fail: out of retries"); } } } diff --git a/include/chconf.h b/include/chconf.h index 86dff84..0ca0e09 100644 --- a/include/chconf.h +++ b/include/chconf.h @@ -672,16 +672,20 @@ /** * @brief ISR enter hook. */ +#ifndef CH_CFG_IRQ_PROLOGUE_HOOK #define CH_CFG_IRQ_PROLOGUE_HOOK() { \ /* IRQ prologue code here.*/ \ } +#endif /** * @brief ISR exit hook. */ +#ifndef CH_CFG_IRQ_EPILOGUE_HOOK #define CH_CFG_IRQ_EPILOGUE_HOOK() { \ /* IRQ epilogue code here.*/ \ } +#endif /** * @brief Idle thread enter hook. diff --git a/mk/build.mk b/mk/build.mk index c51ef23..eb85adc 100644 --- a/mk/build.mk +++ b/mk/build.mk @@ -209,7 +209,7 @@ CWARN = -Wall -Wextra -Wundef -Wstrict-prototypes CPPWARN = -Wall -Wextra -Wundef # List all user C define here, like -D_DEBUG=1 -UDEFS += -DGIT_HASH=0x$(shell git rev-parse --short=8 HEAD) $(MODULES_ENABLED_DEFS) -DCORTEX_ENABLE_WFI_IDLE=TRUE +UDEFS += -DGIT_HASH=0x$(shell git rev-parse --short=8 HEAD) $(MODULES_ENABLED_DEFS) -DCORTEX_ENABLE_WFI_IDLE=FALSE # Define ASM defines here UADEFS = diff --git a/modules/can/can.c b/modules/can/can.c index afa98e1..d1b63e8 100644 --- a/modules/can/can.c +++ b/modules/can/can.c @@ -567,9 +567,9 @@ static void can_expire_handler(struct worker_thread_timer_task_s* task) { for (uint8_t i=0; i < instance->num_tx_mailboxes; i++) { chSysLock(); if (instance->tx_mailbox[i].state == CAN_TX_MAILBOX_PENDING && can_tx_frame_expired_X(instance->tx_mailbox[i].frame)) { - if (instance->driver_iface->abort_tx_mailbox_I(instance->driver_ctx, i)) { - instance->tx_mailbox[i].state = CAN_TX_MAILBOX_ABORTING; - } + // NOTE: order matters below - abort_tx_mailbox_I may call functions that read the mailbox state + instance->tx_mailbox[i].state = CAN_TX_MAILBOX_ABORTING; + instance->driver_iface->abort_tx_mailbox_I(instance->driver_ctx, i); } chSysUnlock(); } diff --git a/modules/can/can_driver.h b/modules/can/can_driver.h index 5ebbed5..ec82890 100644 --- a/modules/can/can_driver.h +++ b/modules/can/can_driver.h @@ -4,7 +4,7 @@ typedef void (*driver_start_t)(void* ctx, bool silent, bool auto_retransmit, uint32_t baudrate); typedef void (*driver_stop_t)(void* ctx); -typedef bool (*driver_mailbox_abort_t)(void* ctx, uint8_t mb_idx); +typedef void (*driver_mailbox_abort_t)(void* ctx, uint8_t mb_idx); typedef bool (*driver_load_tx_mailbox_t)(void* ctx, uint8_t mb_idx, struct can_frame_s* frame); typedef bool (*driver_pop_rx_frame_t)(void* ctx, uint8_t mb_idx, struct can_frame_s* frame); typedef bool (*driver_rx_frame_available_t)(void* ctx, uint8_t mb_idx); diff --git a/modules/can_driver_stm32/can_driver_stm32h7.c b/modules/can_driver_stm32/can_driver_stm32h7.c index f3fd847..b64ca35 100644 --- a/modules/can_driver_stm32/can_driver_stm32h7.c +++ b/modules/can_driver_stm32/can_driver_stm32h7.c @@ -25,7 +25,7 @@ static void can_driver_stm32_start(void* ctx, bool silent, bool auto_retransmit, uint32_t baudrate); static void can_driver_stm32_stop(void* ctx); -bool can_driver_stm32_abort_tx_I(void* ctx, uint8_t mb_idx); +void can_driver_stm32_abort_tx_I(void* ctx, uint8_t mb_idx); bool can_driver_stm32_load_tx_I(void* ctx, uint8_t mb_idx, struct can_frame_s* frame); static const struct can_driver_iface_s can_driver_stm32_iface = { @@ -56,7 +56,7 @@ struct can_driver_stm32_instance_s { struct can_instance_s* frontend; struct message_ram_s message_ram; uint32_t fdcan_ram_offset; // Offset of next allocatable block in CAN SRAM, in 32-bit words - uint32_t tx_bits_processed; + uint32_t tx_cb_called_mask; FDCAN_GlobalTypeDef* can; }; @@ -287,7 +287,7 @@ static void can_driver_stm32_start(void* ctx, bool silent, bool auto_retransmit, instance->can->TXBTIE = (1 << NUM_TX_MAILBOXES) - 1; instance->can->ILE = 0x3; // Enable both interrupt handlers - instance->tx_bits_processed = 0; + instance->tx_cb_called_mask = 0; //Leave Init instance->can->CCCR &= ~FDCAN_CCCR_INIT; // Leave init mode @@ -303,17 +303,23 @@ static void can_driver_stm32_stop(void* ctx) { RCC->APB1HENR &= ~RCC_APB1HENR_FDCANEN; } -bool can_driver_stm32_abort_tx_I(void* ctx, uint8_t mb_idx) { +void can_driver_stm32_abort_tx_I(void* ctx, uint8_t mb_idx) { struct can_driver_stm32_instance_s* instance = ctx; chDbgCheckClassI(); + chDbgCheck(mb_idx < NUM_TX_MAILBOXES); - if (mb_idx < NUM_TX_MAILBOXES && ((1 << mb_idx) & instance->can->TXBRP)) { - instance->can->TXBCR = 1 << mb_idx; - return true; + stm32_can_tx_handler_I(instance); + + if (((1 << mb_idx) & instance->tx_cb_called_mask)) { + // We've already called the completion callback, therefore we fail to abort this mailbox + return; } - return false; + if ((1 << mb_idx) & instance->can->TXBRP) { + // Transmission is pending or in progress, attempt abort + instance->can->TXBCR = 1 << mb_idx; + } } #define FDCAN_IDE (0x40000000U) // Identifier Extension @@ -354,7 +360,7 @@ bool can_driver_stm32_load_tx_I(void* ctx, uint8_t mb_idx, struct can_frame_s* f //Set Add Request instance->can->TXBAR = (1 << mb_idx); - instance->tx_bits_processed &= ~(1UL << mb_idx); + instance->tx_cb_called_mask &= ~(1UL << mb_idx); return true; } @@ -425,21 +431,27 @@ static void stm32_can_tx_handler_I(struct can_driver_stm32_instance_s* instance) systime_t t_now = chVTGetSystemTimeX(); for (uint8_t i = 0; i < NUM_TX_MAILBOXES; i++) { - if (instance->tx_bits_processed & (1UL << i)) { + if (instance->tx_cb_called_mask & (1UL << i)) { + // Already called callback for this mailbox + continue; + } + + if (instance->can->TXBRP & (1UL << i)) { + // Transmission is still in progress continue; } if (instance->can->TXBTO & (1UL << i)) { // Transmit successful - instance->tx_bits_processed |= (1UL << i); + instance->tx_cb_called_mask |= (1UL << i); can_driver_tx_request_complete_I(instance->frontend, i, true, t_now); } else if ((instance->can->CCCR & FDCAN_CCCR_DAR) && (instance->can->TXBCF & (1UL << i)) && can_driver_get_mailbox_transmit_pending(instance->frontend, i) && ((instance->can->ECR & 0xff) == 0)) { // Auto-retransmit disabled and transmit cancellation finished and frontend says transmit still desired and no transmit errors // Ideally we would use an error flag pertaining to the current frame, rather than the error counter. instance->can->TXBAR |= (1UL << i); - instance->tx_bits_processed &= ~(1UL << i); + instance->tx_cb_called_mask &= ~(1UL << i); } else if (instance->can->TXBCF & (1UL << i)) { - instance->tx_bits_processed |= (1UL << i); + instance->tx_cb_called_mask |= (1UL << i); can_driver_tx_request_complete_I(instance->frontend, i, false, t_now); } } diff --git a/modules/platform_stm32h743/module.mk b/modules/platform_stm32h743/module.mk index cb2441c..9af2de7 100644 --- a/modules/platform_stm32h743/module.mk +++ b/modules/platform_stm32h743/module.mk @@ -1,2 +1,2 @@ TGT_MCU = stm32h743 -APP_OFFSET=0 +APP_OFFSET=131072 diff --git a/modules/uavcan_allocatee/uavcan_allocatee.c b/modules/uavcan_allocatee/uavcan_allocatee.c index 1646dd7..185fb4b 100644 --- a/modules/uavcan_allocatee/uavcan_allocatee.c +++ b/modules/uavcan_allocatee/uavcan_allocatee.c @@ -79,7 +79,7 @@ static void allocation_timer_expired(struct worker_thread_timer_task_s* task) { msg.first_part_of_unique_id = (instance->unique_id_offset == 0); msg.unique_id_len = MIN(16-instance->unique_id_offset, UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_MAX_LENGTH_OF_UNIQUE_ID_IN_REQUEST); memcpy(&msg.unique_id, &my_unique_id[instance->unique_id_offset], msg.unique_id_len); - uavcan_broadcast(instance->uavcan_idx, &uavcan_protocol_dynamic_node_id_Allocation_descriptor, CANARD_TRANSFER_PRIORITY_LOW, &msg); + uavcan_broadcast(instance->uavcan_idx, &uavcan_protocol_dynamic_node_id_Allocation_descriptor, CANARD_TRANSFER_PRIORITY_HIGHEST, &msg); instance->unique_id_offset = 0; } diff --git a/modules/uavcan_broadcast_file_update/write.c b/modules/uavcan_broadcast_file_update/write.c new file mode 100644 index 0000000..ce98638 --- /dev/null +++ b/modules/uavcan_broadcast_file_update/write.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include + +#ifndef UAVCAN_FILE_SERVER_WORKER_THREAD +#error Please define UAVCAN_FILE_SERVER_WORKER_THREAD in framework_conf.h. +#endif + +#define WT UAVCAN_FILE_SERVER_WORKER_THREAD +WORKER_THREAD_DECLARE_EXTERN(WT) + + + +static struct worker_thread_listener_task_s filestreamchunk_listener_task; +static void filestreamchunk_listener_task_func(size_t msg_size, const void* buf, void* ctx); + +RUN_AFTER(UAVCAN_INIT) { + struct pubsub_topic_s* filestreamchunk_topic = uavcan_get_message_topic(0, &com_hex_file_FileStreamChunk_descriptor); + worker_thread_add_listener_task(&WT, &filestreamchunk_listener_task, filestreamchunk_topic, filestreamchunk_listener_task_func, NULL); +} + +static void filestreamchunk_listener_task_func(size_t msg_size, const void* buf, void* ctx) { + (void)msg_size; + (void)ctx; + + const struct uavcan_deserialized_message_s* msg_wrapper = buf; + const struct uavcan_protocol_file_Write_req_s* req = (struct uavcan_protocol_file_Write_req_s*)msg_wrapper->msg; + + struct uavcan_protocol_file_Write_res_s res; + memset(&res, 0, sizeof(res)); + + FATFS* fs = uSD_get_filesystem(); + + if (!fs) { + res.error.value = UAVCAN_PROTOCOL_FILE_ERROR_UNKNOWN_ERROR; + uavcan_respond(msg_wrapper->uavcan_idx, msg_wrapper, &res); + return; + } + + FIL f; + char path[sizeof(req->path.path)]; + + memcpy(path, req->path.path, req->path.path_len); + + path[req->path.path_len] = '\0'; + + if (f_open(&f, path, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK) { + res.error.value = UAVCAN_PROTOCOL_FILE_ERROR_UNKNOWN_ERROR; + uavcan_respond(msg_wrapper->uavcan_idx, msg_wrapper, &res); + return; + } + + if(f_lseek(&f, req->offset) != FR_OK) { + f_close(&f); + res.error.value = UAVCAN_PROTOCOL_FILE_ERROR_UNKNOWN_ERROR; + uavcan_respond(msg_wrapper->uavcan_idx, msg_wrapper, &res); + return; + } + + if (req->data_len == 0) { + if (f_truncate(&f) != FR_OK) { + res.error.value = UAVCAN_PROTOCOL_FILE_ERROR_UNKNOWN_ERROR; + } + } else { + UINT bw; + if (f_write(&f, req->data, req->data_len, &bw) != FR_OK) { + res.error.value = UAVCAN_PROTOCOL_FILE_ERROR_UNKNOWN_ERROR; + } + + if (bw != req->data_len) { // this means the volume is full + res.error.value = UAVCAN_PROTOCOL_FILE_ERROR_UNKNOWN_ERROR; + } + } + + f_close(&f); + + uavcan_respond(msg_wrapper->uavcan_idx, msg_wrapper, &res); +} diff --git a/platforms/ARMCMx/ld/stm32h743/memory.ld b/platforms/ARMCMx/ld/stm32h743/memory.ld index eb3d4c8..c95b54f 100644 --- a/platforms/ARMCMx/ld/stm32h743/memory.ld +++ b/platforms/ARMCMx/ld/stm32h743/memory.ld @@ -6,7 +6,7 @@ MEMORY app (rx) : ORIGIN = 0x08000000+384K, LENGTH = 2M-384K - ram0 : org = 0x24000000, len = 512k /* AXI SRAM */ + ram0 : org = 0x24000000, len = 512k-256 /* AXI SRAM */ ram1 : org = 0x30000000, len = 256k /* AHB SRAM1+SRAM2 */ ram2 : org = 0x30000000, len = 288k /* AHB SRAM1+SRAM2+SRAM3 */ ram3 : org = 0x30040000, len = 32k /* AHB SRAM3 */ @@ -14,5 +14,5 @@ MEMORY ram5 : org = 0x20000000, len = 128k /* DTCM-RAM */ ram6 : org = 0x00000000, len = 64k /* ITCM-RAM */ ram7 : org = 0x38800000, len = 4k /* BCKP SRAM */ - app_bl_shared (rwx) : ORIGIN = 0x30000000+(256k-256), LENGTH = 256 + app_bl_shared (rwx) : ORIGIN = 0x24000000+(512k-256), LENGTH = 256 } diff --git a/tools/uavcan_upload.py b/tools/uavcan_upload.py index 98a1ace..fffc786 100644 --- a/tools/uavcan_upload.py +++ b/tools/uavcan_upload.py @@ -28,7 +28,7 @@ def get_firmware_crc_provided(data): parser.add_argument('node_name', nargs=1) parser.add_argument('firmware_name', nargs=1) parser.add_argument('bus', nargs=1) -parser.add_argument('--discovery_time', nargs=1, default=5) +parser.add_argument('--discovery_time', nargs=1, default=[5]) args = parser.parse_args() with open(args.firmware_name[0], 'rb') as f: @@ -43,7 +43,7 @@ def monitor_update_handler(e): print(e.entry) if e.entry.info.name == args.node_name[0]: if e.entry.info.software_version.image_crc != firmware_crc: - if e.entry.status.mode != e.entry.status.MODE_SOFTWARE_UPDATE: + if e.entry.status.mode != e.entry.status.MODE_SOFTWARE_UPDATE or e.entry.status.health == e.entry.status.HEALTH_CRITICAL: print('updating %u' % (e.entry.node_id,)) req_msg = uavcan.protocol.file.BeginFirmwareUpdate.Request(source_node_id=node.node_id, image_file_remote_path=uavcan.protocol.file.Path(path=args.firmware_name[0])) node.request(req_msg, e.entry.node_id, update_response_handler)