diff options
Diffstat (limited to 'firmware/octoclock/bootloader/main.c')
-rw-r--r-- | firmware/octoclock/bootloader/main.c | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/firmware/octoclock/bootloader/main.c b/firmware/octoclock/bootloader/main.c new file mode 100644 index 000000000..b2f6730ac --- /dev/null +++ b/firmware/octoclock/bootloader/main.c @@ -0,0 +1,233 @@ +/* + * Copyright 2014 Ettus Research LLC + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 <stdbool.h> +#include <stdint.h> +#include <string.h> + +#include <avr/boot.h> +#include <avr/eeprom.h> +#include <avr/io.h> +#include <avr/pgmspace.h> + +#include <octoclock.h> +#include <debug.h> +#include <network.h> + +#include <net/enc28j60.h> + +#include "octoclock/common.h" + +#define wait() for(uint16_t u=4000; u; u--) asm("nop"); +#define TIME_PASSED (TCNT1 > FIVE_SECONDS || (TIFR & _BV(TOV1))) + +//States +static bool received_cmd = false; +static bool done_burning = false; + +typedef struct { + uint16_t fw_len; + uint16_t fw_crc; +} crc_info_t; + +static crc_info_t crc_info; + +static void boot_program_page(uint8_t *buf, uint16_t page){ + uint16_t i; + + eeprom_busy_wait(); + + boot_page_erase(page); + boot_spm_busy_wait(); // Wait until the memory is erased. + + for(i = 0; i < SPM_PAGESIZE; i += 2){ + // Set up little-endian word. + uint16_t w = *buf++; + w += ((*buf++) << 8); + + boot_page_fill(page + i, w); + } + + boot_page_write(page); // Store buffer in flash page. + boot_spm_busy_wait(); // Wait until the memory is written. + + // Reenable RWW-section again. We need this if we want to jump back + // to the application after bootloading. + boot_rww_enable(); +} + +static void read_firmware(uint16_t addr, octoclock_packet_t *pkt_out){ + for(size_t i = 0; i < SPM_PAGESIZE; i++){ + pkt_out->data[i] = pgm_read_byte(addr+i); + } +} + +void handle_udp_query_packet( + struct socket_address src, struct socket_address dst, + unsigned char *payload, int payload_len +){ + const octoclock_packet_t *pkt_in = (octoclock_packet_t*)payload; + + //Respond to query packets + if(pkt_in->proto_ver == OCTOCLOCK_FW_COMPAT_NUM && + pkt_in->code == OCTOCLOCK_QUERY_CMD){ + octoclock_packet_t pkt_out; + pkt_out.proto_ver = OCTOCLOCK_BOOTLOADER_PROTO_VER; + pkt_out.sequence = pkt_in->sequence; + pkt_out.code = OCTOCLOCK_QUERY_ACK; + pkt_out.len = 0; + send_udp_pkt(OCTOCLOCK_UDP_CTRL_PORT, src, (void*)&pkt_out, sizeof(octoclock_packet_t)); + } +} + +static void calculate_crc(uint16_t *crc, uint16_t len){ + *crc = 0xFFFF; + + for(size_t i = 0; i < len; i++){ + *crc ^= pgm_read_byte(i); + for(uint8_t j = 0; j < 8; ++j){ + if(*crc & 1) *crc = (*crc >> 1) ^ 0xA001; + else *crc = (*crc >> 1); + } + } +} + +static bool valid_app(){ + crc_info_t crc_eeprom_info; + eeprom_read_block(&crc_eeprom_info, (void*)OCTOCLOCK_EEPROM_APP_LEN, 4); + + calculate_crc(&(crc_info.fw_crc), crc_eeprom_info.fw_len); + return (crc_info.fw_crc == crc_eeprom_info.fw_crc); +} + +void handle_udp_fw_packet( + struct socket_address src, struct socket_address dst, + unsigned char *payload, int payload_len +){ + octoclock_packet_t *pkt_in = (octoclock_packet_t*)payload; + octoclock_packet_t pkt_out; + pkt_out.proto_ver = OCTOCLOCK_BOOTLOADER_PROTO_VER; + pkt_out.sequence = pkt_in->sequence; + pkt_out.len = 0; + + switch(pkt_in->code){ + case PREPARE_FW_BURN_CMD: + received_cmd = true; + done_burning = false; + crc_info.fw_len = pkt_in->len; + pkt_out.code = FW_BURN_READY_ACK; + break; + + case FILE_TRANSFER_CMD: + boot_program_page(pkt_in->data, pkt_in->addr); + wait(); //Necessary for some reason + pkt_out.code = FILE_TRANSFER_ACK; + break; + + case READ_FW_CMD: + pkt_out.code = READ_FW_ACK; + read_firmware(pkt_in->addr, &pkt_out); + break; + + case FINALIZE_BURNING_CMD: + //With stuff verified, burn values into EEPROM + done_burning = true; + calculate_crc(&(crc_info.fw_crc), crc_info.fw_len); + eeprom_write_block(&crc_info, (void*)OCTOCLOCK_EEPROM_APP_LEN, 4); + pkt_out.code = FINALIZE_BURNING_ACK; + break; + + default: + break; + } + send_udp_pkt(OCTOCLOCK_UDP_FW_PORT, src, (void*)&pkt_out, sizeof(octoclock_packet_t)); +} + +void handle_udp_eeprom_packet( + struct socket_address src, struct socket_address dst, + unsigned char *payload, int payload_len +){ + octoclock_packet_t *pkt_in = (octoclock_packet_t*)payload; + octoclock_packet_t pkt_out; + pkt_out.proto_ver = OCTOCLOCK_BOOTLOADER_PROTO_VER; + pkt_out.sequence = pkt_in->sequence; + pkt_out.len = 0; + + if(pkt_in->proto_ver == OCTOCLOCK_FW_COMPAT_NUM){ + switch(pkt_in->code){ + case CLEAR_EEPROM_CMD: + received_cmd = true; + uint8_t blank_eeprom[103]; + memset(blank_eeprom, 0xFF, 103); + eeprom_write_block(blank_eeprom, 0, 103); + pkt_out.code = CLEAR_EEPROM_ACK; + send_udp_pkt(OCTOCLOCK_UDP_EEPROM_PORT, src, (void*)&pkt_out, sizeof(octoclock_packet_t)); + break; + + default: + break; + } + } +} + +int main(void){ + + asm("cli"); + + //Initialization + setup_atmel_io_ports(); + network_init(); + init_udp_listeners(); + register_udp_listener(OCTOCLOCK_UDP_CTRL_PORT, handle_udp_query_packet); + register_udp_listener(OCTOCLOCK_UDP_FW_PORT, handle_udp_fw_packet); + register_udp_listener(OCTOCLOCK_UDP_EEPROM_PORT, handle_udp_eeprom_packet); + + /* + * Initialize AVR timer. This will be used to detect how much time has gone + * by with no contact from the octoclock_burn_firmware utility. If 5 seconds go by + * with no PREPARE_FW_BURN_CMD packet, the bootloader will jump into the + * existing application. + * + * This timer is polled by the main loop with each iteration instead of using + * an interrupt. + */ + TCCR1B = (1 << CS12) | (1 << CS10); + TCNT1 = 0; + bool app_checked = false; + + while(true){ + if(done_burning){ + if(valid_app()) break; + else done_burning = false; //Burning somehow failed and wasn't caught + } + if(!app_checked && !received_cmd && TIME_PASSED){ + app_checked = true; + if(valid_app()) break; + } + + size_t recv_len = enc28j60PacketReceive(512, buf_in); + if(recv_len > 0) handle_eth_packet(recv_len); + } + + /* + * Whether the bootloader reaches here through five seconds of inactivity + * or after a firmware burn just finished, it can be assumed that the application + * is valid. + */ + asm("jmp 0000"); + return 0; //Will never get here, but AVR-GCC needs it +} |