From: Tilman Sauerbeck Date: Fri, 28 Jun 2019 15:53:20 +0000 (+0200) Subject: bootloader: Add the bootloader module. X-Git-Url: http://git.code-monkey.de/?a=commitdiff_plain;h=d34da2e3193c092d8e999e257fdd7c2734ebc75c;p=gps-watch.git bootloader: Add the bootloader module. This implements a simple serial protocol that offers access to the capabilities of the flash module, i.e. erase, program and verify flash contents. --- diff --git a/SConscript.target b/SConscript.target index 39f0fad..dc60c2e 100644 --- a/SConscript.target +++ b/SConscript.target @@ -47,6 +47,7 @@ SConscript('SConscript.libcommon', exports='env', duplicate=0) libbootloader_source_files = [ 'src/bootloader/main.rs', # Must be listed first (see below). + 'src/bootloader/bootloader.rs', 'src/bootloader/flash.rs', ] diff --git a/src/bootloader/bootloader.rs b/src/bootloader/bootloader.rs new file mode 100644 index 0000000..95f4e97 --- /dev/null +++ b/src/bootloader/bootloader.rs @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2019 Tilman Sauerbeck (tilman at code-monkey de) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +use common::buffer::Buffer; +use common::crc32::Crc32; +use flash; + +const COMMAND_ERASE : u32 = 0x49e89a20; +const COMMAND_PROGRAM : u32 = 0x37f7dc8d; +const COMMAND_VERIFY : u32 = 0x4a213efb; +const COMMAND_LOADCHUNK : u32 = 0x1b329768; + +pub struct Bootloader { + command: u32, + command_bytes: u32, + sector_data: [u8; flash::SECTOR_SIZE], + + // The number of bytes in sector_data. + // This is always 32 bit aligned and never greater than flash::SECTOR_SIZE. + sector_data_length: usize, +} + +#[derive(Copy,Clone)] +enum Error { + UnknownCommand = 1, + InvalidArgument, + ChecksumMismatch, +} + +extern { + fn usb_serial_read(c: *mut u8) -> bool; + + static mut cdc_tx_buf: Buffer; +} + +fn try_read_u8() -> Option { + unsafe { + let mut c = b'\0'; + + if usb_serial_read(&mut c) { + Some(c) + } else { + None + } + } +} + +fn read_u8() -> u8 { + let mut b = b'\0'; + + unsafe { + while !usb_serial_read(&mut b) { + } + } + + b +} + +fn read_u32_le() -> u32 { + let mut b = [0u8; 4]; + + for i in 0..4 { + b[i] = read_u8(); + } + + u32::from_le_bytes(b) +} + +fn write_u8(b: u8) { + unsafe { + cdc_tx_buf.write(&[b]); + cdc_tx_buf.flush(); + } +} + +fn write_u32_le(u: u32) { + let b = u.to_le_bytes(); + + unsafe { + cdc_tx_buf.write(&b); + cdc_tx_buf.flush(); + } +} + +fn write_ack() { + write_u8(0u8); +} + +fn write_nak(e: Error) { + write_u8(e as u8); +} + +impl Bootloader { + pub fn new() -> Bootloader { + Bootloader { + command: 0, + command_bytes: 0, + sector_data: [0u8; flash::SECTOR_SIZE], + sector_data_length: 0, + } + } + + pub fn run(&mut self) { + if self.command_bytes < 4 { + if let Some(b) = try_read_u8() { + self.command >>= 8; + self.command |= (b as u32) << 24; + + self.command_bytes += 1; + } + } + + if self.command_bytes == 4 { + self.command_bytes = 0; + + self.process_command(); + } + } + + fn process_command(&mut self) { + match self.command { + COMMAND_ERASE => { + if let Err(e) = self.exec_erase() { + write_nak(e); + } else { + write_ack(); + } + }, + COMMAND_LOADCHUNK => { + if let Err(e) = self.exec_load_sector_data() { + write_nak(e); + } else { + write_ack(); + } + }, + COMMAND_VERIFY => { + let result = self.exec_verify(); + + if let Err(e) = result { + write_nak(e); + } else if let Ok((a, b)) = result { + write_ack(); + + write_u32_le(a); + write_u32_le(b); + } + }, + COMMAND_PROGRAM => { + if let Err(e) = self.exec_program() { + write_nak(e); + } else { + write_ack(); + } + }, + _ => { + write_nak(Error::UnknownCommand); + } + }; + } + + fn exec_erase(&mut self) -> Result<(), Error> { + let sector = read_u32_le(); + + if sector > 0xff { + Err(Error::InvalidArgument) + } else { + flash::erase(sector); + + Ok(()) + } + } + + fn exec_load_sector_data(&mut self) -> Result<(), Error> { + let num_bytes = read_u32_le() as usize; + + if num_bytes < 1 { + Err(Error::InvalidArgument) + } else if num_bytes > flash::SECTOR_SIZE { + Err(Error::InvalidArgument) + } else if (num_bytes & 3) != 0 { + Err(Error::InvalidArgument) + } else { + write_ack(); + + self.sector_data_length = num_bytes; + + for i in 0..num_bytes { + self.sector_data[i] = read_u8(); + } + + let expected_crc32 = read_u32_le(); + + let chunk = &self.sector_data[0..self.sector_data_length]; + + let mut actual_crc32 = Crc32::new(); + actual_crc32.update(chunk); + + if actual_crc32.finish() != expected_crc32 { + Err(Error::ChecksumMismatch) + } else { + Ok(()) + } + } + } + + fn exec_verify(&mut self) -> Result<(u32, u32), Error> { + let sector = read_u32_le(); + + if sector > 0xff { + Err(Error::InvalidArgument) + } else if self.sector_data_length == 0 { + Err(Error::InvalidArgument) + } else { + let chunk = &self.sector_data[0..self.sector_data_length]; + + Ok(flash::verify(sector, chunk)) + } + } + + fn exec_program(&mut self) -> Result<(), Error> { + let sector = read_u32_le(); + + if sector > 0xff { + Err(Error::InvalidArgument) + } else if self.sector_data_length == 0 { + Err(Error::InvalidArgument) + } else { + let chunk = &self.sector_data[0..self.sector_data_length]; + + Ok(flash::program(sector, chunk)) + } + } +}