Skip to content

Commit

Permalink
Bootloader ping
Browse files Browse the repository at this point in the history
  • Loading branch information
george-cosma committed Sep 19, 2023
1 parent 0ed13ca commit 474af2e
Show file tree
Hide file tree
Showing 15 changed files with 377 additions and 6 deletions.
12 changes: 12 additions & 0 deletions src/bootloader/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// TODO: What exactly is an attribute?
pub struct Attribute {
pub key: String,
pub value: String,
}

impl Attribute {
/// Intrepret raw bytes, according to the Tock Format (TODO: Source??)
pub fn parse_raw(bytes: Vec<u8>) -> Attribute {
todo!()
}
}
80 changes: 80 additions & 0 deletions src/bootloader/codes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#![allow(unused)]
// "This was chosen as it is infrequent in .bin files" - immesys
pub const ESCAPE_CHAR: u8 = 0xFC;

// Commands from this tool to the bootloader. (tockloader)
// The "X" commands are for external flash. (tockloader)
pub const COMMAND_PING: u8 = 0x01;
pub const COMMAND_INFO: u8 = 0x03;
pub const COMMAND_ID: u8 = 0x04;
pub const COMMAND_RESET: u8 = 0x05;
pub const COMMAND_ERASE_PAGE: u8 = 0x06;
pub const COMMAND_WRITE_PAGE: u8 = 0x07;
pub const COMMAND_XEBLOCK: u8 = 0x08;
pub const COMMAND_XWPAGE: u8 = 0x09;
pub const COMMAND_CRCRX: u8 = 0x10;
pub const COMMAND_READ_RANGE: u8 = 0x11;
pub const COMMAND_XRRANGE: u8 = 0x12;
pub const COMMAND_SET_ATTRIBUTE: u8 = 0x13;
pub const COMMAND_GET_ATTRIBUTE: u8 = 0x14;
pub const COMMAND_CRC_INTERNAL_FLASH: u8 = 0x15;
pub const COMMAND_CRCEF: u8 = 0x16;
pub const COMMAND_XEPAGE: u8 = 0x17;
pub const COMMAND_XFINIT: u8 = 0x18;
pub const COMMAND_CLKOUT: u8 = 0x19;
pub const COMMAND_WUSER: u8 = 0x20;
pub const COMMAND_CHANGE_BAUD_RATE: u8 = 0x21;
pub const COMMAND_EXIT: u8 = 0x22;
pub const COMMAND_SET_START_ADDRESS: u8 = 0x23;

// Responses from the bootloader. (tockloader)
pub const RESPONSE_OVERFLOW: u8 = 0x10;
pub const RESPONSE_PONG: u8 = 0x11;
pub const RESPONSE_BADADDR: u8 = 0x12;
pub const RESPONSE_INTERROR: u8 = 0x13;
pub const RESPONSE_BADARGS: u8 = 0x14;
pub const RESPONSE_OK: u8 = 0x15;
pub const RESPONSE_UNKNOWN: u8 = 0x16;
pub const RESPONSE_XFTIMEOUT: u8 = 0x17;
pub const RESPONSE_XFEPE: u8 = 0x18;
pub const RESPONSE_CRCRX: u8 = 0x19;
pub const RESPONSE_READ_RANGE: u8 = 0x20;
pub const RESPONSE_XRRANGE: u8 = 0x21;
pub const RESPONSE_GET_ATTRIBUTE: u8 = 0x22;
pub const RESPONSE_CRC_INTERNAL_FLASH: u8 = 0x23;
pub const RESPONSE_CRCXF: u8 = 0x24;
pub const RESPONSE_INFO: u8 = 0x25;
pub const RESPONSE_CHANGE_BAUD_FAIL: u8 = 0x26;

pub fn escape(source: Vec<u8>) -> Vec<u8> {
let mut result = Vec::with_capacity(source.len());
for byte in source {
result.push(byte);
// Escape the escape char
if byte == ESCAPE_CHAR {
result.push(ESCAPE_CHAR)
}
}

result
}

pub fn deescape(source: Vec<u8>) -> Vec<u8> {
let mut result = Vec::with_capacity(source.len());

if !source.is_empty() {
result.push(source[0]);
}

for i in 1..source.len() {
if source[i] == ESCAPE_CHAR && source[i - 1] == ESCAPE_CHAR {
// The previous char was already pushed, so we can skip
// pushing this one
continue;
} else {
result.push(source[i]);
}
}

result
}
2 changes: 2 additions & 0 deletions src/bootloader/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod attribute;
pub mod codes;
16 changes: 11 additions & 5 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@ pub fn make_cli() -> Command {

/// Generate all of the [subcommands](clap::Command) used by the program.
fn get_subcommands() -> Vec<Command> {
vec![Command::new("listen")
.about("Open a terminal to receive UART data")
.args(get_app_args())
.args(get_channel_args())
.arg_required_else_help(false)]
vec![
Command::new("listen")
.about("Open a terminal to receive UART data")
.args(get_app_args())
.args(get_channel_args())
.arg_required_else_help(false),
Command::new("info")
.about("TODO")
.args(get_channel_args())
.arg_required_else_help(false),
]
}

/// Generate all of the [arguments](clap::Arg) that are required by subcommands which work with apps.
Expand Down
4 changes: 4 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub enum TockloaderError {
CLIError(CLIError),
IOError(std::io::Error),
JoinError(JoinError),
StreamClosed,
}

#[derive(Debug)]
Expand All @@ -34,6 +35,9 @@ impl fmt::Display for TockloaderError {
TockloaderError::JoinError(inner) => {
inner.fmt(f)
},
TockloaderError::StreamClosed => {
f.write_str("The serial stream unexpectedly closed.")
},
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use self::{jlink::JLinkInterface, openocd::OpenOCDInterface, serial::SerialInterface, traits::*};
use crate::bootloader::attribute::Attribute;
use crate::errors::{CLIError, TockloaderError};
use clap::ArgMatches;
use enum_dispatch::enum_dispatch;
Expand All @@ -10,6 +11,7 @@ pub mod traits;

#[enum_dispatch(BoardInterface)]
#[enum_dispatch(VirtualTerminal)]
#[enum_dispatch(BootloaderInterface)]
pub enum Interface {
Serial(SerialInterface),
OpenOCD(OpenOCDInterface),
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/jlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use clap::ArgMatches;
use crate::errors::TockloaderError;

pub mod board_interface;
pub mod bootloader_interface;
pub mod virtual_terminal;

pub struct JLinkInterface {}
Expand Down
25 changes: 25 additions & 0 deletions src/interfaces/jlink/bootloader_interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use async_trait::async_trait;

use crate::{
bootloader::attribute::Attribute, errors::TockloaderError,
interfaces::traits::BootloaderInterface, interfaces::JLinkInterface,
};

#[async_trait]
impl BootloaderInterface for JLinkInterface {
async fn enter_bootloader(&mut self) -> Result<bool, TockloaderError> {
todo!()
}

async fn ping(&mut self) -> Result<bool, TockloaderError> {
todo!()
}

async fn sync(&mut self) -> Result<(), TockloaderError> {
todo!()
}

async fn get_attribute(&mut self) -> Result<Attribute, TockloaderError> {
todo!()
}
}
1 change: 1 addition & 0 deletions src/interfaces/openocd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use clap::ArgMatches;
use crate::errors::TockloaderError;

pub mod board_interface;
pub mod bootloader_interface;
pub mod virtual_terminal;

pub struct OpenOCDInterface {}
Expand Down
25 changes: 25 additions & 0 deletions src/interfaces/openocd/bootloader_interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use async_trait::async_trait;

use crate::{
bootloader::attribute::Attribute, errors::TockloaderError,
interfaces::traits::BootloaderInterface, interfaces::OpenOCDInterface,
};

#[async_trait]
impl BootloaderInterface for OpenOCDInterface {
async fn enter_bootloader(&mut self) -> Result<bool, TockloaderError> {
todo!()
}

async fn ping(&mut self) -> Result<bool, TockloaderError> {
todo!()
}

async fn sync(&mut self) -> Result<(), TockloaderError> {
todo!()
}

async fn get_attribute(&mut self) -> Result<Attribute, TockloaderError> {
todo!()
}
}
2 changes: 2 additions & 0 deletions src/interfaces/serial.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
mod binary_codec;
pub mod board_interface;
pub mod bootloader_interface;
pub mod virtual_terminal;

use clap::ArgMatches;
Expand Down
45 changes: 45 additions & 0 deletions src/interfaces/serial/binary_codec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use crate::errors::TockloaderError;
use bytes::{BufMut, BytesMut};
use tokio_util::codec::{Decoder, Encoder};

pub struct BinaryCodec;

impl Decoder for BinaryCodec {
type Item = Vec<u8>;
type Error = TockloaderError;

fn decode(&mut self, source: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if source.is_empty() {
return Ok(None);
}

Ok(Some(Vec::from(&source[..])))
}
}

impl Encoder<Vec<u8>> for BinaryCodec {
type Error = TockloaderError;

fn encode(&mut self, item: Vec<u8>, dst: &mut BytesMut) -> Result<(), Self::Error> {
dst.put(&item[..]);
Ok(())
}
}

impl Encoder<&[u8]> for BinaryCodec {
type Error = TockloaderError;

fn encode(&mut self, item: &[u8], dst: &mut BytesMut) -> Result<(), Self::Error> {
dst.put(item);
Ok(())
}
}

impl<const N: usize> Encoder<[u8; N]> for BinaryCodec {
type Error = TockloaderError;

fn encode(&mut self, item: [u8; N], dst: &mut BytesMut) -> Result<(), Self::Error> {
dst.put(&item[..]);
Ok(())
}
}
126 changes: 126 additions & 0 deletions src/interfaces/serial/bootloader_interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use super::binary_codec::BinaryCodec;
use crate::{
bootloader::{
attribute::Attribute,
codes::{ESCAPE_CHAR, RESPONSE_PONG},
},
errors::TockloaderError,
interfaces::traits::BootloaderInterface,
interfaces::SerialInterface,
};
use async_trait::async_trait;
use futures::{SinkExt, StreamExt};
use std::time::Duration;
use tokio_serial::SerialPort;
use tokio_util::codec::Decoder;

#[async_trait]
impl BootloaderInterface for SerialInterface {
async fn enter_bootloader(&mut self) -> Result<bool, TockloaderError> {
// These methods are taken from the python version of tockloader
// bootlaoder_serial.py:518 [_toggle_bootloader_entry_DTR_RTS()]

if self.stream.is_none() {
return Err(TockloaderError::StreamClosed);
}

// Method 0: We are already in bootloader mode
if self.bootloader_open().await {
return Ok(true);
}

// Method 1: Change baud rate to 1200 and change back.
// TODO: When does this work?
self.stream
.as_mut()
.unwrap()
.set_baud_rate(1200)
.map_err(TockloaderError::TokioSeriallError)?;
tokio::time::sleep(Duration::from_millis(1000)).await;

if self.bootloader_open().await {
return Ok(true);
}

self.stream
.as_mut()
.unwrap()
.set_baud_rate(self.baud_rate)
.map_err(TockloaderError::TokioSeriallError)?;
tokio::time::sleep(Duration::from_millis(1000)).await;

// Method 2: DTR & RTS
// > Use the DTR and RTS lines on UART to reset the chip and assert the
// > bootloader select pin to enter bootloader mode so that the chip will
// > start in bootloader mode.
// - tocklaoder

// > Reset the SAM4L
self.stream
.as_mut()
.unwrap()
.write_data_terminal_ready(true)
.map_err(TockloaderError::TokioSeriallError)?;

// > Set RTS to make the SAM4L go into bootloader mode
self.stream
.as_mut()
.unwrap()
.write_request_to_send(true)
.map_err(TockloaderError::TokioSeriallError)?;

tokio::time::sleep(Duration::from_millis(100)).await;

// > Let the SAM4L startup
self.stream
.as_mut()
.unwrap()
.write_data_terminal_ready(false)
.map_err(TockloaderError::TokioSeriallError)?;

// > make sure the bootloader enters bootloader mode
tokio::time::sleep(Duration::from_millis(500)).await;

self.stream
.as_mut()
.unwrap()
.write_request_to_send(false)
.map_err(TockloaderError::TokioSeriallError)?;

if self.bootloader_open().await {
return Ok(true);
}

Ok(false)
}

async fn ping(&mut self) -> Result<bool, TockloaderError> {
let mut channel = BinaryCodec.framed(self.stream.as_mut().unwrap());

channel.send([ESCAPE_CHAR, 0x1]).await?;

if let Ok(response) =
tokio::time::timeout(Duration::from_millis(1000), channel.next()).await
{
if let Some(decoder_result) = response {
let response = decoder_result?;
if response == [ESCAPE_CHAR, RESPONSE_PONG] {
return Ok(true);
}
}

Ok(false)
} else {
// Timeout
Ok(false)
}
}

async fn sync(&mut self) -> Result<(), TockloaderError> {
todo!()
}

async fn get_attribute(&mut self) -> Result<Attribute, TockloaderError> {
todo!()
}
}
Loading

0 comments on commit 474af2e

Please sign in to comment.