/* * Copyright 2014-2015 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "octoclock/common.h" #define TIME_PASSED (TCNT1 > TIMER1_ONE_SECOND) /* * States */ static bool received_cmd = false; // Received "PREPARE_FW_BURN_CMD" signal static bool done_burning = false; // Received "FINALIZE_BURNING_CMD" signal static bool app_checked = false; // Ran validation check on firmware /* * After new firmware is burned onto the device, the bootloader calculates its * CRC and burns it into the EEPROM. When the device boots, this CRC is used * to validate the firmware before loading it. This struct represents how the * information is stored in the EEPROM. */ typedef struct { uint16_t fw_len; uint16_t fw_crc; } crc_info_t; static crc_info_t crc_info; /* * What actually burns the firmware onto the device. * * Source: http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__boot.html */ static void boot_program_page(uint8_t *buf, uint16_t page){ // Disable interrupts uint8_t sreg = SREG; cli(); eeprom_busy_wait(); boot_page_erase(page); boot_spm_busy_wait(); // Wait until the memory is erased. for(uint16_t 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(); // Restore interrupt state SREG = sreg; sei(); } /* * Load firmware at given address into packet to send to host. */ 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); } } /* * Calculate the CRC of the current firmware. * * Adapted from _crc16_update in . */ 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); } } } /* * Calculate the CRC of the current firmware. If it matches the * CRC burned into the EEPROM, the firmware is considered valid, * and the bootloader can load it. */ 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); } /* * UDP handlers */ 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 uhd::device::find(), identify as bootloader if(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)); } } 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_crc = pkt_in->crc; crc_info.fw_len = pkt_in->len; pkt_out.code = FW_BURN_READY_ACK; break; // Burn firmware sent from the host case FILE_TRANSFER_CMD: boot_program_page(pkt_in->data, pkt_in->addr); pkt_out.code = FILE_TRANSFER_ACK; pkt_out.addr = pkt_in->addr; break; // Send firmware back to the host for verification case READ_FW_CMD: pkt_out.code = READ_FW_ACK; read_firmware(pkt_in->addr, &pkt_out); break; // Calculate the CRC of the new firmware and finish case FINALIZE_BURNING_CMD: done_burning = true; 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; // Restore OctoClock's EEPROM to factory state 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]; // 103 is offset of CRC info 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){ // Disable watchdog timer wdt_disable(); // Give interrupts to bootloader MCUCR = (1<