protocan-boot: support single-slot targets
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
`protocan-boot` — переносимое C99-ядро адресной прошивки приборов по classic
|
`protocan-boot` — переносимое C99-ядро адресной прошивки приборов по classic
|
||||||
CAN 2.0B и 29-битному ProtoCAN ID. Оно реализует сессию обновления, два
|
CAN 2.0B и 29-битному ProtoCAN ID. Оно реализует сессию обновления, два
|
||||||
логических слота по 512 КиБ, последовательную запись 8-байтовых блоков,
|
логических слота по 512 КиБ либо single-slot обновление, последовательную запись 8-байтовых блоков,
|
||||||
CRC32, проверку совместимости, продолжение по номеру следующего блока и
|
CRC32, проверку совместимости, продолжение по номеру следующего блока и
|
||||||
безопасный выбор неактивного слота.
|
безопасный выбор неактивного слота.
|
||||||
|
|
||||||
@@ -10,6 +10,11 @@ CRC32, проверку совместимости, продолжение по
|
|||||||
linker script, обработчиков прерываний, криптографии и перехода в приложение.
|
linker script, обработчиков прерываний, криптографии и перехода в приложение.
|
||||||
Эти операции предоставляет проект через таблицу callbacks.
|
Эти операции предоставляет проект через таблицу callbacks.
|
||||||
|
|
||||||
|
Для STM32G474VET6 из `candleLight_fw` задаются `slot_count = 1` и
|
||||||
|
`max_image_size = 480 * 1024`. В этом режиме запись идёт в единственную
|
||||||
|
область приложения, callback `set_pending_slot` не требуется, а аппаратный
|
||||||
|
rollback недоступен. Значение `slot_count = 0` сохраняет прежний A/B-режим.
|
||||||
|
|
||||||
## Слои
|
## Слои
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ typedef struct {
|
|||||||
uint8_t hardware_revision;
|
uint8_t hardware_revision;
|
||||||
uint32_t firmware_version;
|
uint32_t firmware_version;
|
||||||
uint8_t active_slot;
|
uint8_t active_slot;
|
||||||
|
/** 1 for an in-place/single-slot target, 2 for A/B. Zero keeps legacy A/B. */
|
||||||
|
uint8_t slot_count;
|
||||||
|
/** Writable image capacity. Zero uses PCAN_BOOT_SLOT_SIZE. */
|
||||||
|
uint32_t max_image_size;
|
||||||
uint8_t ack_window; /**< 1 — ACK каждого блока, 16 — ACK каждых 16 блоков. */
|
uint8_t ack_window; /**< 1 — ACK каждого блока, 16 — ACK каждых 16 блоков. */
|
||||||
} pcan_boot_config_t;
|
} pcan_boot_config_t;
|
||||||
|
|
||||||
|
|||||||
@@ -115,17 +115,27 @@ void pcan_boot_abort(pcan_boot_t *boot)
|
|||||||
bool pcan_boot_init(pcan_boot_t *boot, const pcan_boot_config_t *config,
|
bool pcan_boot_init(pcan_boot_t *boot, const pcan_boot_config_t *config,
|
||||||
const pcan_boot_port_t *port, void *port_user)
|
const pcan_boot_port_t *port, void *port_user)
|
||||||
{
|
{
|
||||||
|
uint8_t slot_count;
|
||||||
if ((boot == NULL) || (config == NULL) || (port == NULL)
|
if ((boot == NULL) || (config == NULL) || (port == NULL)
|
||||||
|| (config->device_type > 7U) || (config->device_id > 15U)
|
|| (config->device_type > 7U) || (config->device_id > 15U)
|
||||||
|| (config->active_slot > 1U)
|
|| (config->active_slot > 1U)
|
||||||
|| (port->send == NULL) || (port->erase_slot == NULL)
|
|| (port->send == NULL) || (port->erase_slot == NULL)
|
||||||
|| (port->write_slot == NULL) || (port->set_pending_slot == NULL)) {
|
|| (port->write_slot == NULL)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
slot_count = config->slot_count == 0U ? 2U : config->slot_count;
|
||||||
|
if ((slot_count > 2U) || (config->active_slot >= slot_count)
|
||||||
|
|| ((slot_count == 2U) && (port->set_pending_slot == NULL))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
(void)memset(boot, 0, sizeof(*boot));
|
(void)memset(boot, 0, sizeof(*boot));
|
||||||
boot->config = *config;
|
boot->config = *config;
|
||||||
boot->port = *port;
|
boot->port = *port;
|
||||||
boot->port_user = port_user;
|
boot->port_user = port_user;
|
||||||
|
boot->config.slot_count = slot_count;
|
||||||
|
if (boot->config.max_image_size == 0U) {
|
||||||
|
boot->config.max_image_size = PCAN_BOOT_SLOT_SIZE;
|
||||||
|
}
|
||||||
if (boot->config.ack_window == 0U) {
|
if (boot->config.ack_window == 0U) {
|
||||||
boot->config.ack_window = 1U;
|
boot->config.ack_window = 1U;
|
||||||
}
|
}
|
||||||
@@ -140,7 +150,7 @@ static bool metadata_complete(pcan_boot_t *boot, uint8_t command)
|
|||||||
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
|
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
|
||||||
}
|
}
|
||||||
if ((boot->manifest.image_size == 0U)
|
if ((boot->manifest.image_size == 0U)
|
||||||
|| (boot->manifest.image_size > PCAN_BOOT_SLOT_SIZE)) {
|
|| (boot->manifest.image_size > boot->config.max_image_size)) {
|
||||||
boot->state = PCAN_BOOT_STATE_FAILED;
|
boot->state = PCAN_BOOT_STATE_FAILED;
|
||||||
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_SIZE);
|
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_SIZE);
|
||||||
}
|
}
|
||||||
@@ -155,7 +165,9 @@ static bool metadata_complete(pcan_boot_t *boot, uint8_t command)
|
|||||||
boot->state = PCAN_BOOT_STATE_FAILED;
|
boot->state = PCAN_BOOT_STATE_FAILED;
|
||||||
return send_status(boot, command, PCAN_BOOT_STATUS_SIGNATURE_ERROR);
|
return send_status(boot, command, PCAN_BOOT_STATUS_SIGNATURE_ERROR);
|
||||||
}
|
}
|
||||||
boot->target_slot = (uint8_t)(boot->config.active_slot ^ 1U);
|
boot->target_slot = boot->config.slot_count == 1U
|
||||||
|
? boot->config.active_slot
|
||||||
|
: (uint8_t)(boot->config.active_slot ^ 1U);
|
||||||
boot->state = PCAN_BOOT_STATE_READY_TO_ERASE;
|
boot->state = PCAN_BOOT_STATE_READY_TO_ERASE;
|
||||||
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
|
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
|
||||||
}
|
}
|
||||||
@@ -239,8 +251,9 @@ static bool process_control(pcan_boot_t *boot, uint16_t body,
|
|||||||
if (boot->state != PCAN_BOOT_STATE_VERIFIED) {
|
if (boot->state != PCAN_BOOT_STATE_VERIFIED) {
|
||||||
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
|
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
|
||||||
}
|
}
|
||||||
if (!boot->port.set_pending_slot(boot->port_user, boot->target_slot,
|
if ((boot->config.slot_count == 2U)
|
||||||
&boot->manifest)) {
|
&& !boot->port.set_pending_slot(boot->port_user, boot->target_slot,
|
||||||
|
&boot->manifest)) {
|
||||||
return send_status(boot, command, PCAN_BOOT_STATUS_FLASH_ERROR);
|
return send_status(boot, command, PCAN_BOOT_STATUS_FLASH_ERROR);
|
||||||
}
|
}
|
||||||
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
|
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
|
||||||
|
|||||||
@@ -255,6 +255,44 @@ static void test_rejects_wrong_session_and_compatibility(void)
|
|||||||
assert(boot.state == PCAN_BOOT_STATE_FAILED);
|
assert(boot.state == PCAN_BOOT_STATE_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_single_slot_update(void)
|
||||||
|
{
|
||||||
|
static const uint8_t image[8] = { 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U };
|
||||||
|
pcan_boot_t boot;
|
||||||
|
fake_t fake;
|
||||||
|
pcan_boot_config_t config;
|
||||||
|
pcan_boot_port_t port;
|
||||||
|
|
||||||
|
(void)memset(&fake, 0, sizeof(fake));
|
||||||
|
(void)memset(&config, 0, sizeof(config));
|
||||||
|
(void)memset(&port, 0, sizeof(port));
|
||||||
|
fake.pending_slot = PCAN_BOOT_SLOT_NONE;
|
||||||
|
fake.authorized = true;
|
||||||
|
fake.verified = true;
|
||||||
|
config.device_type = 3U;
|
||||||
|
config.device_id = 5U;
|
||||||
|
config.product_type = 0x1234U;
|
||||||
|
config.hardware_revision = 2U;
|
||||||
|
config.slot_count = 1U;
|
||||||
|
config.max_image_size = sizeof(fake.flash[0]);
|
||||||
|
config.ack_window = 16U;
|
||||||
|
port.send = fake_send;
|
||||||
|
port.erase_slot = fake_erase;
|
||||||
|
port.write_slot = fake_write;
|
||||||
|
port.authorize = fake_authorize;
|
||||||
|
port.verify_image = fake_verify;
|
||||||
|
port.reboot = fake_reboot;
|
||||||
|
assert(pcan_boot_init(&boot, &config, &port, &fake));
|
||||||
|
begin_update(&boot, image, sizeof(image));
|
||||||
|
assert(boot.target_slot == 0U);
|
||||||
|
assert(command(&boot, 7U, PCAN_BOOT_CMD_ERASE, NULL, 0U));
|
||||||
|
assert(pcan_boot_process(&boot, request_id(PCAN_BOOT_MSG_DATA_A, 0U), image, 8U));
|
||||||
|
assert(command(&boot, 7U, PCAN_BOOT_CMD_VERIFY, NULL, 0U));
|
||||||
|
assert(command(&boot, 7U, PCAN_BOOT_CMD_COMMIT, NULL, 0U));
|
||||||
|
assert(fake.pending_slot == PCAN_BOOT_SLOT_NONE);
|
||||||
|
assert(memcmp(fake.flash[0], image, sizeof(image)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
test_id_layout();
|
test_id_layout();
|
||||||
@@ -262,6 +300,7 @@ int main(void)
|
|||||||
test_complete_update();
|
test_complete_update();
|
||||||
test_rejects_wrong_address_and_sequence();
|
test_rejects_wrong_address_and_sequence();
|
||||||
test_rejects_wrong_session_and_compatibility();
|
test_rejects_wrong_session_and_compatibility();
|
||||||
|
test_single_slot_update();
|
||||||
puts("pcan_boot tests: OK");
|
puts("pcan_boot tests: OK");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user