461 lines
22 KiB
C
461 lines
22 KiB
C
/* 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;
|
||
}
|