mirror of
https://github.com/andreili/katapult.git
synced 2025-08-23 19:34:06 +02:00
289 lines
8.0 KiB
C
289 lines
8.0 KiB
C
// Canboot main event loop
|
|
//
|
|
// Copyright (C) 2021 Eric Callahan <arksine.code@gmail.com>
|
|
//
|
|
// This file may be distributed under the terms of the GNU GPLv3 license.
|
|
|
|
#include <string.h> // memmove
|
|
#include "autoconf.h" // CONFIG_*
|
|
#include "board/misc.h" // delay_ms
|
|
#include "board/canbus.h" // canbus_send
|
|
#include "board/flash.h" // write_page
|
|
#include "canboot_main.h" // canboot_main
|
|
|
|
|
|
#define COMMAND_SIZE 8
|
|
#define CMD_BUF_SIZE 16
|
|
#define CMD_CONNECT 0x11
|
|
#define CMD_RX_BLOCK 0x12
|
|
#define CMD_RX_EOF 0x13
|
|
#define CMD_REQ_BLOCK 0x14
|
|
#define CMD_COMPLETE 0x15
|
|
#define ACK_COMMAND 0xa0
|
|
#define ACK_BLOCK_RX 0xa1
|
|
#define NACK 0xf0
|
|
|
|
// Command Format:
|
|
// <4 byte header> <1 byte cmd> <2 byte arg> <1 byte trailer>
|
|
#define CMD_HEADER 0x6362746C // "cbtl"
|
|
#define CMD_TRAILER 0x03 // ETX
|
|
|
|
#define WAIT_BLINK_TIME 1000000
|
|
#define XFER_BLINK_TIME 10000
|
|
|
|
#define REQUEST_SIG 0x5984E3FA6CA1589B // Random request sig
|
|
|
|
static uint8_t page_buffer[CONFIG_MAX_FLASH_PAGE_SIZE];
|
|
static uint8_t cmd_buf[CMD_BUF_SIZE];
|
|
static uint8_t cmd_pos = 0;
|
|
static uint16_t page_count = 0;
|
|
static uint16_t page_pos = 0;
|
|
static uint32_t last_blink_time = 0;
|
|
static uint16_t cmd_arg = 0;
|
|
enum { CMD_PENDING, RX_BLOCK, RX_DONE, TX_BLOCK, COMPLETE };
|
|
static uint8_t current_state = CMD_PENDING;
|
|
|
|
|
|
static void
|
|
send_ack(uint8_t ack, uint16_t arg)
|
|
{
|
|
uint8_t ack_buf[8];
|
|
ack_buf[0] = (CMD_HEADER >> 24) & 0xFF;
|
|
ack_buf[1] = (CMD_HEADER >> 16) & 0xFF;
|
|
ack_buf[2] = (CMD_HEADER >> 8) & 0xFF;
|
|
ack_buf[3] = CMD_HEADER & 0xFF;
|
|
ack_buf[4] = ack;
|
|
ack_buf[5] = (arg >> 8) & 0xFF;
|
|
ack_buf[6] = arg & 0xFF;
|
|
ack_buf[7] = CMD_TRAILER;
|
|
canboot_sendf(ack_buf, COMMAND_SIZE);
|
|
}
|
|
|
|
static inline void
|
|
process_command(void)
|
|
{
|
|
if (cmd_pos < COMMAND_SIZE)
|
|
return;
|
|
|
|
uint8_t cmd = 0xFF;
|
|
uint8_t remaining = cmd_pos;
|
|
uint8_t *tmpbuf = cmd_buf;
|
|
uint32_t header;
|
|
while (remaining) {
|
|
if (tmpbuf[0] == 0x63) {
|
|
// potential match
|
|
if (remaining >= COMMAND_SIZE) {
|
|
header = (tmpbuf[0] << 24) | (tmpbuf[1] << 16) | (tmpbuf[2] << 8)
|
|
| (tmpbuf[3]);
|
|
if (header == CMD_HEADER && cmd_buf[7] == CMD_TRAILER) {
|
|
cmd = tmpbuf[4];
|
|
cmd_arg = (tmpbuf[5] << 8) | tmpbuf[6];
|
|
remaining -= COMMAND_SIZE;
|
|
break;
|
|
}
|
|
} else
|
|
// A potential command, check it after the next read
|
|
break;
|
|
}
|
|
remaining--;
|
|
tmpbuf++;
|
|
}
|
|
if (remaining) {
|
|
// move the buffer
|
|
uint8_t rpos = cmd_pos - remaining;
|
|
memmove(&cmd_buf[0], &cmd_buf[rpos], remaining);
|
|
}
|
|
cmd_pos = remaining;
|
|
switch (cmd) {
|
|
case CMD_CONNECT:
|
|
send_ack(ACK_COMMAND, CONFIG_BLOCK_SIZE);
|
|
// TODO: reinit page?
|
|
break;
|
|
case CMD_RX_BLOCK:
|
|
send_ack(ACK_COMMAND, cmd_arg);
|
|
current_state = RX_BLOCK;
|
|
break;
|
|
case CMD_RX_EOF:
|
|
// This will be ACK'ed after the final
|
|
// page is written
|
|
current_state = RX_DONE;
|
|
break;
|
|
case CMD_REQ_BLOCK:
|
|
send_ack(ACK_COMMAND, cmd_arg);
|
|
current_state = TX_BLOCK;
|
|
break;
|
|
case CMD_COMPLETE:
|
|
send_ack(ACK_COMMAND, 1);
|
|
current_state = COMPLETE;
|
|
break;
|
|
default:
|
|
// Unknown command or gabage data, NACK it
|
|
send_ack(NACK, 0);
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_page(uint16_t page)
|
|
{
|
|
flash_write_page(page, (uint16_t*)page_buffer);
|
|
memset(page_buffer, 0xFF, sizeof(page_buffer));
|
|
page_pos = 0;
|
|
}
|
|
|
|
static void
|
|
process_page(void) {
|
|
static uint16_t last_page_pos = 0;
|
|
uint8_t need_ack = 0;
|
|
if (page_pos == last_page_pos) {
|
|
return;
|
|
}
|
|
uint32_t flash_page_size = flash_get_page_size();
|
|
if (page_pos == flash_page_size)
|
|
write_page(page_count++);
|
|
if (page_pos % CONFIG_BLOCK_SIZE == 0) {
|
|
current_state = CMD_PENDING;
|
|
need_ack = 1;
|
|
}
|
|
last_page_pos = page_pos;
|
|
if (need_ack)
|
|
send_ack(ACK_BLOCK_RX, cmd_arg);
|
|
}
|
|
|
|
static void
|
|
check_blink_time(uint32_t usec)
|
|
{
|
|
uint32_t curtime = timer_read_time();
|
|
uint32_t endtime = last_blink_time + timer_from_us(usec);
|
|
if (timer_is_before(endtime, curtime)) {
|
|
led_toggle();
|
|
last_blink_time = timer_read_time();
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
process_state(void)
|
|
{
|
|
switch (current_state) {
|
|
case CMD_PENDING:
|
|
check_blink_time(WAIT_BLINK_TIME);
|
|
process_command();
|
|
break;
|
|
case RX_BLOCK:
|
|
check_blink_time(XFER_BLINK_TIME);
|
|
process_page();
|
|
break;
|
|
case RX_DONE:
|
|
if (page_pos)
|
|
write_page(page_count++);
|
|
flash_complete();
|
|
current_state = CMD_PENDING;
|
|
send_ack(ACK_COMMAND, page_count);
|
|
break;
|
|
case TX_BLOCK:
|
|
// TODO: rather than tx the block, we can do the
|
|
// CRC check here? CRC16 or SHA1?
|
|
check_blink_time(XFER_BLINK_TIME);
|
|
flash_read_block(cmd_arg, (uint32_t*)page_buffer);
|
|
canboot_sendf(page_buffer, CONFIG_BLOCK_SIZE);
|
|
current_state = CMD_PENDING;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
canboot_process_rx(uint32_t id, uint32_t len, uint8_t *data)
|
|
{
|
|
switch (current_state) {
|
|
case CMD_PENDING:
|
|
// read into the command buffer
|
|
if (cmd_pos >= CMD_BUF_SIZE)
|
|
return;
|
|
else if (cmd_pos + len > CMD_BUF_SIZE)
|
|
len = CMD_BUF_SIZE - cmd_pos;
|
|
memcpy(&cmd_buf[cmd_pos], data, len);
|
|
cmd_pos += len;
|
|
break;
|
|
case RX_BLOCK:
|
|
// read into into the page buffer
|
|
if (page_pos >= sizeof(page_buffer))
|
|
return;
|
|
else if (page_pos + len > sizeof(page_buffer))
|
|
len = sizeof(page_buffer) - page_pos;
|
|
memcpy(&page_buffer[page_pos], data, len);
|
|
page_pos += len;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
static inline uint8_t
|
|
check_application_code(void)
|
|
{
|
|
// Read the first block of memory, if it
|
|
// is all 0xFF then no application has been flashed
|
|
flash_read_block(0, (uint32_t*)page_buffer);
|
|
for (uint8_t i = 0; i < CONFIG_BLOCK_SIZE; i++) {
|
|
if (page_buffer[i] != 0xFF)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/****************************************************************
|
|
* Startup
|
|
****************************************************************/
|
|
static void
|
|
enter_bootloader(void)
|
|
{
|
|
can_init();
|
|
|
|
// TODO: this is temporary. It lets us know
|
|
// that the bootloader has been entered. We can
|
|
// also toggle this as a means to visualize transfers.
|
|
// We will want to set it up in the menuconfig
|
|
led_init();
|
|
// The short delay is simply to ensure that the Debug Timer is
|
|
// enabled
|
|
udelay(10);
|
|
last_blink_time = timer_read_time();
|
|
|
|
for (;;) {
|
|
canbus_rx_task();
|
|
canbus_tx_task();
|
|
process_state();
|
|
if (current_state == COMPLETE && canbus_tx_clear())
|
|
// wait until we are complete and the ack has returned
|
|
break;
|
|
}
|
|
|
|
// Flash Complete, system reset
|
|
udelay(100000);
|
|
canbus_reboot();
|
|
}
|
|
|
|
// Main loop of program
|
|
void
|
|
canboot_main(void)
|
|
{
|
|
// Enter the bootloader in the following conditions:
|
|
// - The request signature is set in memory (request from app)
|
|
// - No application code is present
|
|
uint64_t bootup_code = get_bootup_code();
|
|
if (bootup_code == REQUEST_SIG || !check_application_code()) {
|
|
set_bootup_code(0);
|
|
enter_bootloader();
|
|
}
|
|
|
|
// set request signature and delay for two seconds. This enters the bootloader if
|
|
// the reset button is double clicked
|
|
set_bootup_code(REQUEST_SIG);
|
|
udelay(2000000);
|
|
set_bootup_code(0);
|
|
// No reset, read the key back out to clear it
|
|
|
|
// jump to app
|
|
jump_to_application();
|
|
}
|