Files
templates/c/protocan-boot/src/pcan_boot.c
T

461 lines
22 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ProtoCAN update state machine; platform owns storage and reset. */
#include "../include/pcan_boot.h"
#include <string.h>
#define PCAN_BOOT_ID_MASK 0x1FFFFFFFUL
static uint16_t get_u16(const pcan_boot_byte_t *p)
{
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}
static uint32_t get_u32(const pcan_boot_byte_t *p)
{
return (uint32_t)p[0]
| ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16)
| ((uint32_t)p[3] << 24);
}
static void put_u16(pcan_boot_byte_t *p, uint16_t value)
{
p[0] = (pcan_boot_byte_t)(value & 0xFFU);
p[1] = (pcan_boot_byte_t)((value >> 8) & 0xFFU);
}
static void put_u32(pcan_boot_byte_t *p, uint32_t value)
{
p[0] = (pcan_boot_byte_t)(value & 0xFFU);
p[1] = (pcan_boot_byte_t)((value >> 8) & 0xFFU);
p[2] = (pcan_boot_byte_t)((value >> 16) & 0xFFU);
p[3] = (pcan_boot_byte_t)(value >> 24);
}
static uint32_t crc32_update(uint32_t crc, const pcan_boot_byte_t *data, size_t length)
{
size_t i;
crc = ~crc;
for (i = 0U; i < length; ++i) {
pcan_boot_byte_t bit;
crc ^= data[i];
for (bit = 0U; bit < 8U; ++bit) {
crc = (crc >> 1) ^ ((crc & 1U) != 0U ? 0xEDB88320UL : 0U);
}
}
return ~crc;
}
uint32_t pcan_boot_make_id(pcan_boot_byte_t priority, pcan_boot_byte_t route,
pcan_boot_byte_t device_type, pcan_boot_byte_t device_id,
pcan_boot_byte_t msg_type, uint16_t msg_body)
{
return ((((uint32_t)priority & 1U) << 28)
| (((uint32_t)route & 1U) << 27)
| (((uint32_t)device_type & 7U) << 24)
| (((uint32_t)device_id & 15U) << 20)
| (((uint32_t)msg_type & 15U) << 16)
| msg_body) & PCAN_BOOT_ID_MASK;
}
pcan_boot_byte_t pcan_boot_id_route(uint32_t can_id) { return (pcan_boot_byte_t)((can_id >> 27) & 1U); }
pcan_boot_byte_t pcan_boot_id_device_type(uint32_t can_id) { return (pcan_boot_byte_t)((can_id >> 24) & 7U); }
pcan_boot_byte_t pcan_boot_id_device_id(uint32_t can_id) { return (pcan_boot_byte_t)((can_id >> 20) & 15U); }
pcan_boot_byte_t pcan_boot_id_msg_type(uint32_t can_id) { return (pcan_boot_byte_t)((can_id >> 16) & 15U); }
uint16_t pcan_boot_id_msg_body(uint32_t can_id) { return (uint16_t)can_id; }
static bool send_frame(pcan_boot_t *boot, pcan_boot_byte_t msg_type, uint16_t body,
const pcan_boot_byte_t *data, pcan_boot_byte_t dlc)
{
uint32_t id = pcan_boot_make_id(1U, PCAN_BOOT_ROUTE_FROM_DEVICE,
boot->config.device_type,
boot->config.device_id,
msg_type, body);
return boot->port.send(boot->port_user, id, data, dlc);
}
/* STATUS всегда содержит состояние передачи: status, target_slot, next_block, CRC32.
* CRC здесь относится к уже принятому потоку, а не заменяет проверку EEPROM.
* Номер следующего блока помогает клиенту продолжить обмен после потери ACK. */
static bool send_status(pcan_boot_t *boot, pcan_boot_byte_t command, pcan_boot_byte_t status)
{
pcan_boot_byte_t data[8];
uint16_t body = (uint16_t)(((uint16_t)boot->session_id << 8) | command);
data[0] = status;
data[1] = boot->target_slot;
put_u16(&data[2], boot->next_block);
put_u32(&data[4], boot->running_crc32);
boot->last_status = status;
return send_frame(boot, PCAN_BOOT_MSG_STATUS, body, data, 8U);
}
/* IDENTIFY имеет отдельный тип сообщения DISCOVERY и Body=1. Он доступен
* без открытия сессии, поэтому обнаружение устройства не должно запускать обновление. */
static bool send_identity(pcan_boot_t *boot)
{
pcan_boot_byte_t data[8];
put_u16(&data[0], boot->config.product_type);
data[2] = boot->config.hardware_revision;
data[3] = PCAN_BOOT_VERSION;
put_u32(&data[4], boot->config.firmware_version);
return send_frame(boot, PCAN_BOOT_MSG_DISCOVERY, 1U, data, 8U);
}
/* Отменяется только состояние протокола в RAM: manifest, счётчики и session.
* Функция не стирает EEPROM и не снимает ранее опубликованный pending.
* Ответственность за постоянное состояние слотов остаётся у платформы. */
void pcan_boot_abort(pcan_boot_t *boot)
{
if (boot == NULL) {
return;
}
(void)memset(&boot->manifest, 0, sizeof(boot->manifest));
boot->state = PCAN_BOOT_STATE_IDLE;
boot->running_crc32 = 0U;
boot->bytes_received = 0U;
boot->next_block = 0U;
boot->session_id = 0U;
boot->target_slot = PCAN_BOOT_SLOT_NONE;
boot->last_status = PCAN_BOOT_STATUS_OK;
boot->have_image = false;
boot->have_compat = false;
}
/* Конфигурация копируется в экземпляр; указатели callbacks остаются привязаны
* к port_user. Значения slot_count=0 и max_image_size=0 сохраняют исторические
* настройки по умолчанию. Для пустого A/B-хранилища допустим active_slot=NONE. */
bool pcan_boot_init(pcan_boot_t *boot, const pcan_boot_config_t *config,
const pcan_boot_port_t *port, void *port_user)
{
pcan_boot_byte_t slot_count;
if ((boot == NULL) || (config == NULL) || (port == NULL)
|| (config->device_type > 7U) || (config->device_id > 15U)
|| ((config->active_slot > 1U) && (config->active_slot != PCAN_BOOT_SLOT_NONE))
|| (port->send == NULL) || (port->erase_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) &&
(config->active_slot != PCAN_BOOT_SLOT_NONE || slot_count != 2U))
|| config->max_image_size > PCAN_BOOT_SLOT_SIZE
|| ((slot_count == 2U) && (port->set_pending_slot == NULL))) {
return false;
}
(void)memset(boot, 0, sizeof(*boot));
boot->config = *config;
boot->port = *port;
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) {
boot->config.ack_window = 1U;
}
pcan_boot_abort(boot);
return true;
}
/* BEGIN_IMAGE и BEGIN_COMPAT приходят отдельными кадрами. До получения обоих
* не разрешаем ERASE. Проверяем размер, Product, HW и необязательную политику
* authorize. В A/B выбираем противоположный active слот; для первого образа — A. */
static bool metadata_complete(pcan_boot_t *boot, pcan_boot_byte_t command)
{
if (!boot->have_image || !boot->have_compat) {
boot->state = PCAN_BOOT_STATE_METADATA;
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
}
if ((boot->manifest.image_size == 0U)
|| (boot->manifest.image_size > boot->config.max_image_size)) {
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_SIZE);
}
if (boot->manifest.product_type != boot->config.product_type) {
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_WRONG_DEVICE);
}
if ((boot->config.hardware_revision < boot->manifest.hardware_revision_min)
|| (boot->config.hardware_revision > boot->manifest.hardware_revision_max)) {
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_WRONG_HARDWARE);
}
if ((boot->port.authorize != NULL)
&& !boot->port.authorize(boot->port_user, &boot->manifest)) {
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_SIGNATURE_ERROR);
}
boot->target_slot = boot->config.active_slot == PCAN_BOOT_SLOT_NONE ? 0U :
boot->config.slot_count == 1U
? boot->config.active_slot
: (pcan_boot_byte_t)(boot->config.active_slot ^ 1U);
boot->state = PCAN_BOOT_STATE_READY_TO_ERASE;
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
}
/* Body состоит из session в старшем октете и command в младшем.
* Сначала проверяем DLC, чтобы обработчики не читали отсутствующие данные.
* Идентификация и паспорт не открывают сессию; CONFIRM обрабатывается отдельно,
* поскольку приложение не должно входить в загрузчик ради подтверждения себя.
* Остальные команды изменяют RAM-автомат только в допустимой сессии/состоянии. */
static bool process_control(pcan_boot_t *boot, uint16_t body,
const pcan_boot_byte_t *data, pcan_boot_byte_t dlc)
{
pcan_boot_byte_t session = (pcan_boot_byte_t)(body >> 8);
pcan_boot_byte_t command = (pcan_boot_byte_t)(body & 0xFFU);
if (dlc != ((command == PCAN_BOOT_CMD_BEGIN_IMAGE || command == PCAN_BOOT_CMD_BEGIN_COMPAT) ? 8U :
(command == PCAN_BOOT_CMD_DEVICE_NAME || command == PCAN_BOOT_CMD_MEMORY_INFO) ? 1U : 0U)) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_SIZE);
}
if (command == PCAN_BOOT_CMD_PING) {
pcan_boot_byte_t reply[8] = {0};
reply[0] = PCAN_BOOT_VERSION; reply[1] = boot->state;
reply[2] = boot->config.active_slot; reply[3] = boot->target_slot;
reply[4] = boot->session_id; reply[5] = boot->config.slot_count;
reply[6] = boot->last_status;
return send_frame(boot, PCAN_BOOT_MSG_DISCOVERY, command, reply, 8);
}
if (command >= PCAN_BOOT_CMD_CAPABILITIES && command <= PCAN_BOOT_CMD_MEMORY_INFO) {
pcan_boot_byte_t reply[8] = {0};
pcan_boot_byte_t page = dlc ? data[0] : 0;
if (!boot->port.service_info || !boot->port.service_info(boot->port_user,command,page,reply))
return send_status(boot,command,PCAN_BOOT_STATUS_INVALID_COMMAND);
return send_frame(boot,PCAN_BOOT_MSG_DISCOVERY,(uint16_t)((page<<8)|command),reply,8);
}
if (command == PCAN_BOOT_CMD_CONFIRM) {
pcan_boot_byte_t saved=boot->session_id; bool sent;
boot->session_id=session;
sent=send_status(boot,command,session && boot->port.confirm_running_slot &&
boot->port.confirm_running_slot(boot->port_user) ? PCAN_BOOT_STATUS_OK : PCAN_BOOT_STATUS_INVALID_STATE);
boot->session_id=saved; return sent;
}
if (command == PCAN_BOOT_CMD_IDENTIFY) {
return send_identity(boot);
}
if (command == PCAN_BOOT_CMD_ENTER_BOOT) {
if (session == 0U) {
return send_status(boot, command, PCAN_BOOT_STATUS_SESSION_ERROR);
}
if (boot->state != PCAN_BOOT_STATE_IDLE && boot->session_id != session)
return send_status(boot,command,PCAN_BOOT_STATUS_BUSY);
if (boot->port.enter_boot) {
unsigned int result=boot->port.enter_boot(boot->port_user,session);
if(result==1) return true;
if(result!=0) return send_status(boot,command,PCAN_BOOT_STATUS_INVALID_STATE);
}
if (boot->session_id==session && boot->state!=PCAN_BOOT_STATE_IDLE)
return send_status(boot,command,PCAN_BOOT_STATUS_OK);
pcan_boot_abort(boot);
boot->session_id = session;
boot->last_activity_ms=boot->now_ms;
boot->state = PCAN_BOOT_STATE_METADATA;
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
}
if ((session == 0U) || (session != boot->session_id)) {
return send_status(boot, command, PCAN_BOOT_STATUS_SESSION_ERROR);
}
boot->last_activity_ms=boot->now_ms;
switch (command) {
case PCAN_BOOT_CMD_BEGIN_IMAGE:
if(boot->state==PCAN_BOOT_STATE_READY_TO_ERASE && boot->manifest.image_size==get_u32(data) &&
boot->manifest.image_crc32==get_u32(data+4)) return send_status(boot,command,PCAN_BOOT_STATUS_OK);
if ((dlc != 8U) || (boot->state != PCAN_BOOT_STATE_METADATA)) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
}
boot->manifest.image_size = get_u32(&data[0]);
boot->manifest.image_crc32 = get_u32(&data[4]);
boot->have_image = true;
return metadata_complete(boot, command);
case PCAN_BOOT_CMD_BEGIN_COMPAT:
if(boot->state==PCAN_BOOT_STATE_READY_TO_ERASE && boot->manifest.product_type==get_u16(data) &&
boot->manifest.hardware_revision_min==data[2] && boot->manifest.hardware_revision_max==data[3] &&
boot->manifest.firmware_version==get_u32(data+4)) return send_status(boot,command,PCAN_BOOT_STATUS_OK);
if ((dlc != 8U) || (boot->state != PCAN_BOOT_STATE_METADATA)) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
}
boot->manifest.product_type = get_u16(&data[0]);
boot->manifest.hardware_revision_min = data[2];
boot->manifest.hardware_revision_max = data[3];
boot->manifest.firmware_version = get_u32(&data[4]);
boot->have_compat = true;
return metadata_complete(boot, command);
case PCAN_BOOT_CMD_ERASE:
if(boot->state==PCAN_BOOT_STATE_RECEIVING && boot->bytes_received==0)
return send_status(boot,command,PCAN_BOOT_STATUS_OK);
if (boot->state != PCAN_BOOT_STATE_READY_TO_ERASE) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
}
if (!boot->port.erase_slot(boot->port_user, boot->target_slot)) {
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_FLASH_ERROR);
}
boot->running_crc32 = 0U;
boot->bytes_received = 0U;
boot->next_block = 0U;
boot->state = PCAN_BOOT_STATE_RECEIVING;
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
case PCAN_BOOT_CMD_VERIFY:
if(boot->state==PCAN_BOOT_STATE_VERIFIED || boot->state==PCAN_BOOT_STATE_COMMITTED)
return send_status(boot,command,PCAN_BOOT_STATUS_OK);
if ((boot->state != PCAN_BOOT_STATE_RECEIVING)
|| (boot->bytes_received != boot->manifest.image_size)) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
}
if (boot->running_crc32 != boot->manifest.image_crc32) {
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_CRC_ERROR);
}
if (boot->port.verify_status) {
pcan_boot_status_t status=boot->port.verify_status(boot->port_user,boot->target_slot,&boot->manifest);
if(status!=PCAN_BOOT_STATUS_OK) {
boot->state=PCAN_BOOT_STATE_FAILED; return send_status(boot,command,status);
}
}
if (!boot->port.verify_status && (boot->port.verify_image != NULL)
&& !boot->port.verify_image(boot->port_user, boot->target_slot,
&boot->manifest)) {
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_SIGNATURE_ERROR);
}
boot->state = PCAN_BOOT_STATE_VERIFIED;
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
case PCAN_BOOT_CMD_COMMIT:
if(boot->state==PCAN_BOOT_STATE_COMMITTED) return send_status(boot,command,PCAN_BOOT_STATUS_OK);
if (boot->state != PCAN_BOOT_STATE_VERIFIED) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
}
if ((boot->port.set_pending_slot != NULL)
&& !boot->port.set_pending_slot(boot->port_user, boot->target_slot,
&boot->manifest)) {
return send_status(boot, command, PCAN_BOOT_STATUS_FLASH_ERROR);
}
boot->state=PCAN_BOOT_STATE_COMMITTED;
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
case PCAN_BOOT_CMD_QUERY_PROGRESS:
return send_status(boot, command, boot->last_status);
case PCAN_BOOT_CMD_ABORT:
pcan_boot_abort(boot);
boot->session_id = session;
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
case PCAN_BOOT_CMD_REBOOT:
if(!boot->port.reboot || (boot->port.can_reboot && !boot->port.can_reboot(boot->port_user)))
return send_status(boot,command,PCAN_BOOT_STATUS_INVALID_STATE);
if(!send_status(boot,command,PCAN_BOOT_STATUS_OK)) return false;
if (boot->port.reboot != NULL) {
boot->port.reboot(boot->port_user);
}
return true;
default:
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_COMMAND);
}
}
/* DATA не содержит session в Body: все 16 бит заняты номером блока. Поэтому
* кадры данных принимаются только в состоянии RECEIVING и для выбранного A/B.
* При неверном номере ничего не записываем и возвращаем ожидаемый next_block.
* Последний кадр дополняется клиентом до 8 октетов, но запись и CRC учитывают
* только оставшиеся октеты image_size. ACK выдаётся на границе окна или в конце. */
static bool process_data(pcan_boot_t *boot, pcan_boot_byte_t msg_type, uint16_t block,
const pcan_boot_byte_t *data, pcan_boot_byte_t dlc)
{
pcan_boot_byte_t expected_type;
pcan_boot_byte_t write_length;
uint32_t remaining;
if (boot->state != PCAN_BOOT_STATE_RECEIVING) {
return send_status(boot, 0U, PCAN_BOOT_STATUS_INVALID_STATE);
}
expected_type = boot->target_slot == 0U ? PCAN_BOOT_MSG_DATA_A
: PCAN_BOOT_MSG_DATA_B;
if (msg_type != expected_type) {
return send_status(boot, 0U, PCAN_BOOT_STATUS_INVALID_STATE);
}
if ((block != boot->next_block) || (dlc != PCAN_BOOT_BLOCK_SIZE)) {
return send_status(boot, 0U, PCAN_BOOT_STATUS_SEQUENCE_ERROR);
}
boot->last_activity_ms=boot->now_ms;
remaining = boot->manifest.image_size - boot->bytes_received;
write_length = remaining < PCAN_BOOT_BLOCK_SIZE ? (pcan_boot_byte_t)remaining
: PCAN_BOOT_BLOCK_SIZE;
if ((write_length == 0U)
|| !boot->port.write_slot(boot->port_user, boot->target_slot,
boot->bytes_received, data, write_length)) {
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, 0U, PCAN_BOOT_STATUS_FLASH_ERROR);
}
boot->running_crc32 = crc32_update(boot->running_crc32, data, write_length);
boot->bytes_received += write_length;
boot->next_block++;
if ((boot->bytes_received == boot->manifest.image_size)
|| ((boot->next_block % boot->config.ack_window) == 0U)) {
return send_status(boot, 0U, PCAN_BOOT_STATUS_OK);
}
return true;
}
/* Общая точка входа для одного кадра после аппаратной фильтрации.
* Проверки адреса повторяются программно, чтобы широкая маска CAN не разрешила
* обновление чужого устройства. Broadcast допускается только для DISCOVERY
* с Body=0/DLC=0; ответ откладывается до poll для разведения устройств по времени. */
bool pcan_boot_process(pcan_boot_t *boot, uint32_t can_id,
const pcan_boot_byte_t *data, pcan_boot_byte_t dlc)
{
pcan_boot_byte_t msg_type;
if ((boot == NULL) || (dlc > 8U) || ((dlc != 0U) && (data == NULL))) {
return false;
}
msg_type = pcan_boot_id_msg_type(can_id);
if ((msg_type < PCAN_BOOT_MSG_CONTROL) || (msg_type > PCAN_BOOT_MSG_DISCOVERY)) {
return false;
}
if(msg_type==PCAN_BOOT_MSG_DISCOVERY && pcan_boot_id_route(can_id)==0 &&
pcan_boot_id_msg_body(can_id)==0 && dlc==0 &&
((pcan_boot_id_device_type(can_id)==7 && pcan_boot_id_device_id(can_id)==15) ||
(pcan_boot_id_device_type(can_id)==boot->config.device_type &&
pcan_boot_id_device_id(can_id)==boot->config.device_id))) {
if(!boot->discovery_pending) {
boot->discovery_at=boot->now_ms+2U*(1U+16U*boot->config.device_type+boot->config.device_id);
boot->discovery_pending=true;
}
return true;
}
if ((pcan_boot_id_route(can_id) != PCAN_BOOT_ROUTE_FROM_PM)
|| (pcan_boot_id_device_type(can_id) != boot->config.device_type)
|| (pcan_boot_id_device_id(can_id) != boot->config.device_id)) {
return false;
}
if (msg_type == PCAN_BOOT_MSG_CONTROL) {
return process_control(boot, pcan_boot_id_msg_body(can_id), data, dlc);
}
if ((msg_type == PCAN_BOOT_MSG_DATA_A) || (msg_type == PCAN_BOOT_MSG_DATA_B)) {
return process_data(boot, msg_type, pcan_boot_id_msg_body(can_id), data, dlc);
}
return false;
}
/* Обслуживание времени не требует входящего кадра. Регулярно вызывать из main
* с монотонным счётчиком миллисекунд: иначе не истечёт сессия и не выйдет discovery.
* При занятом TX discovery остаётся pending до успешного send_identity.
* Вычитание unsigned учитывает переход счётчика через 0xFFFFFFFF. */
void pcan_boot_poll(pcan_boot_t *boot,uint32_t now_ms)
{
if(!boot) return;
boot->now_ms=now_ms;
if(boot->config.session_timeout_ms && boot->state!=PCAN_BOOT_STATE_IDLE &&
(uint32_t)(now_ms-boot->last_activity_ms)>=boot->config.session_timeout_ms)
pcan_boot_abort(boot);
if(boot->discovery_pending && (uint32_t)(now_ms-boot->discovery_at)<0x80000000UL && send_identity(boot))
boot->discovery_pending=false;
}