244 lines
7.3 KiB
C
244 lines
7.3 KiB
C
/*
|
||
* This file is part of DeskHop (https://github.com/hrvach/deskhop).
|
||
* Copyright (c) 2024 Hrvoje Cavrak
|
||
*
|
||
* This program is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation, version 3.
|
||
*
|
||
* This program is distributed in the hope that it will be useful, but
|
||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
* General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include "main.h"
|
||
|
||
/**================================================== *
|
||
* ============== Checksum Functions ============== *
|
||
* ================================================== */
|
||
|
||
uint8_t calc_checksum(const uint8_t *data, int length) {
|
||
uint8_t checksum = 0;
|
||
|
||
for (int i = 0; i < length; i++) {
|
||
checksum ^= data[i];
|
||
}
|
||
|
||
return checksum;
|
||
}
|
||
|
||
bool verify_checksum(const uart_packet_t *packet) {
|
||
uint8_t checksum = calc_checksum(packet->data, PACKET_DATA_LENGTH);
|
||
return checksum == packet->checksum;
|
||
}
|
||
|
||
uint32_t crc32_iter(uint32_t crc, const uint8_t byte) {
|
||
return crc32_lookup_table[(byte ^ crc) & 0xff] ^ (crc >> 8);
|
||
}
|
||
|
||
/* TODO - use DMA sniffer's built-in CRC32 */
|
||
uint32_t calc_crc32(const uint8_t *s, size_t n) {
|
||
uint32_t crc = 0xffffffff;
|
||
|
||
for(size_t i=0; i < n; i++) {
|
||
crc = crc32_iter(crc, s[i]);
|
||
}
|
||
|
||
return ~crc;
|
||
}
|
||
|
||
uint32_t calculate_firmware_crc32(void) {
|
||
return calc_crc32(ADDR_FW_RUNNING, STAGING_IMAGE_SIZE - FLASH_SECTOR_SIZE);
|
||
}
|
||
|
||
/* ================================================== *
|
||
* Flash and config functions
|
||
* ================================================== */
|
||
|
||
void wipe_config(void) {
|
||
uint32_t ints = save_and_disable_interrupts();
|
||
flash_range_erase((uint32_t)ADDR_CONFIG - XIP_BASE, FLASH_SECTOR_SIZE);
|
||
restore_interrupts(ints);
|
||
}
|
||
|
||
void write_flash_page(uint32_t target_addr, uint8_t *buffer) {
|
||
/* Start of sector == first 256-byte page in a 4096 byte block */
|
||
bool is_sector_start = (target_addr & 0xf00) == 0;
|
||
|
||
uint32_t ints = save_and_disable_interrupts();
|
||
if (is_sector_start)
|
||
flash_range_erase(target_addr, FLASH_SECTOR_SIZE);
|
||
|
||
flash_range_program(target_addr, buffer, FLASH_PAGE_SIZE);
|
||
restore_interrupts(ints);
|
||
}
|
||
|
||
void load_config(device_t *state) {
|
||
const config_t *config = ADDR_CONFIG;
|
||
config_t *running_config = &state->config;
|
||
|
||
/* Load the flash config first, including the checksum */
|
||
memcpy(running_config, config, sizeof(config_t));
|
||
|
||
/* Calculate and update checksum, size without checksum */
|
||
uint8_t checksum = calc_crc32((uint8_t *)running_config, sizeof(config_t) - sizeof(uint32_t));
|
||
|
||
/* We expect a certain byte to start the config header */
|
||
bool magic_header_fail = (running_config->magic_header != 0xB00B1E5);
|
||
|
||
/* We expect the checksum to match */
|
||
bool checksum_fail = (running_config->checksum != checksum);
|
||
|
||
/* We expect the config version to match exactly, to avoid erroneous values */
|
||
bool version_fail = (running_config->version != CURRENT_CONFIG_VERSION);
|
||
|
||
/* On any condition failing, we fall back to default config */
|
||
if (magic_header_fail || checksum_fail || version_fail)
|
||
memcpy(running_config, &default_config, sizeof(config_t));
|
||
}
|
||
|
||
void save_config(device_t *state) {
|
||
uint8_t *raw_config = (uint8_t *)&state->config;
|
||
|
||
/* Calculate and update checksum, size without checksum */
|
||
uint8_t checksum = calc_crc32(raw_config, sizeof(config_t) - sizeof(uint32_t));
|
||
state->config.checksum = checksum;
|
||
|
||
/* Copy the config to buffer and pad the rest with zeros */
|
||
memcpy(state->page_buffer, raw_config, sizeof(config_t));
|
||
memset(state->page_buffer + sizeof(config_t), 0, FLASH_PAGE_SIZE - sizeof(config_t));
|
||
|
||
/* Write the new config to flash */
|
||
write_flash_page((uint32_t)ADDR_CONFIG - XIP_BASE, state->page_buffer);
|
||
}
|
||
|
||
void reset_config_timer(device_t *state) {
|
||
/* Once this is reached, we leave the config mode */
|
||
state->config_mode_timer = time_us_64() + CONFIG_MODE_TIMEOUT;
|
||
}
|
||
|
||
void _configure_flash_cs(enum gpio_override gpo, uint pin_index) {
|
||
hw_write_masked(&ioqspi_hw->io[pin_index].ctrl,
|
||
gpo << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB,
|
||
IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS);
|
||
}
|
||
|
||
bool is_bootsel_pressed(void) {
|
||
const uint CS_PIN_INDEX = 1;
|
||
uint32_t flags = save_and_disable_interrupts();
|
||
|
||
/* Set chip select to high impedance */
|
||
_configure_flash_cs(GPIO_OVERRIDE_LOW, CS_PIN_INDEX);
|
||
sleep_us(20);
|
||
|
||
/* Button pressed pulls pin DOWN, so invert */
|
||
bool button_pressed = !(sio_hw->gpio_hi_in & (1u << CS_PIN_INDEX));
|
||
|
||
/* Restore chip select state */
|
||
_configure_flash_cs(GPIO_OVERRIDE_NORMAL, CS_PIN_INDEX);
|
||
restore_interrupts(flags);
|
||
|
||
return button_pressed;
|
||
}
|
||
|
||
void request_byte(device_t *state, uint32_t address) {
|
||
uart_packet_t packet = {
|
||
.data32[0] = address,
|
||
.type = REQUEST_BYTE_MSG,
|
||
};
|
||
state->fw.byte_done = false;
|
||
|
||
queue_try_add(&global_state.uart_tx_queue, &packet);
|
||
}
|
||
|
||
void reboot(void) {
|
||
*((volatile uint32_t*)(PPB_BASE + 0x0ED0C)) = 0x5FA0004;
|
||
}
|
||
|
||
bool is_start_of_packet(device_t *state) {
|
||
return (uart_rxbuf[state->dma_ptr] == START1 && uart_rxbuf[NEXT_RING_IDX(state->dma_ptr)] == START2);
|
||
}
|
||
|
||
uint32_t get_ptr_delta(uint32_t current_pointer, device_t *state) {
|
||
uint32_t delta;
|
||
|
||
if (current_pointer >= state->dma_ptr)
|
||
delta = current_pointer - state->dma_ptr;
|
||
else
|
||
delta = DMA_RX_BUFFER_SIZE - state->dma_ptr + current_pointer;
|
||
|
||
/* Clamp to 12 bits since it can never be bigger */
|
||
delta = delta & 0x3FF;
|
||
|
||
return delta;
|
||
}
|
||
|
||
void fetch_packet(device_t *state) {
|
||
uint8_t *dst = (uint8_t *)&state->in_packet;
|
||
|
||
for (int i = 0; i < RAW_PACKET_LENGTH; i++) {
|
||
/* Skip the header preamble */
|
||
if (i >= START_LENGTH)
|
||
dst[i - START_LENGTH] = uart_rxbuf[state->dma_ptr];
|
||
|
||
state->dma_ptr = NEXT_RING_IDX(state->dma_ptr);
|
||
}
|
||
}
|
||
|
||
/* Validating any input is mandatory. Only packets of these type are allowed
|
||
to be sent to the device over configuration endpoint. */
|
||
bool validate_packet(uart_packet_t *packet) {
|
||
const enum packet_type_e ALLOWED_PACKETS[] = {
|
||
FLASH_LED_MSG,
|
||
GET_VAL_MSG,
|
||
GET_ALL_VALS_MSG,
|
||
SET_VAL_MSG,
|
||
WIPE_CONFIG_MSG,
|
||
SAVE_CONFIG_MSG,
|
||
REBOOT_MSG,
|
||
PROXY_PACKET_MSG,
|
||
};
|
||
uint8_t packet_type = packet->type;
|
||
|
||
/* Proxied packets are encapsulated in the data field, but same rules apply */
|
||
if (packet->type == PROXY_PACKET_MSG)
|
||
packet_type = packet->data[0];
|
||
|
||
for (int i = 0; i < ARRAY_SIZE(ALLOWED_PACKETS); i++) {
|
||
if (ALLOWED_PACKETS[i] == packet_type)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
|
||
/* ================================================== *
|
||
* Debug functions
|
||
* ================================================== */
|
||
#ifdef DH_DEBUG
|
||
|
||
int dh_debug_printf(const char *format, ...) {
|
||
va_list args;
|
||
va_start(args, format);
|
||
char buffer[512];
|
||
|
||
int string_len = vsnprintf(buffer, 512, format, args);
|
||
|
||
tud_cdc_n_write(0, buffer, string_len);
|
||
tud_cdc_write_flush();
|
||
|
||
va_end(args);
|
||
return string_len;
|
||
}
|
||
#else
|
||
|
||
int dh_debug_printf(const char *format, ...) {
|
||
return 0;
|
||
}
|
||
|
||
#endif
|