main.rs (4010B)
1 use clap::Parser; 2 use indicatif::ProgressStyle; 3 use tokio::io::{self, AsyncReadExt, AsyncWriteExt, ReadHalf, Stdout}; 4 use tokio_serial::{DataBits, FlowControl, Parity, SerialPortBuilderExt, SerialStream, StopBits}; 5 use tracing::{info, level_filters::LevelFilter, warn}; 6 use tracing_indicatif::{ 7 IndicatifLayer, 8 filter::{IndicatifFilter, hide_indicatif_span_fields}, 9 }; 10 use tracing_subscriber::{ 11 EnvFilter, Layer, fmt::format::DefaultFields, layer::SubscriberExt, util::SubscriberInitExt, 12 }; 13 14 use std::{path::PathBuf, process::exit, time::Duration}; 15 16 mod bootload; 17 mod sylveos; 18 mod tcp; 19 20 #[derive(Parser, Debug)] 21 #[command(about)] 22 struct Args { 23 #[arg(short, long, default_value_t = 921600)] 24 baud: u32, 25 #[arg(short, long, value_hint = clap::ValueHint::FilePath, default_value = "/dev/ttyUSB0")] 26 device: String, 27 #[arg(short, long, default_value_t = 10.0)] 28 timeout: f64, 29 #[arg(short, long, default_value = "0x8000", value_parser = hex_value, value_name = "ADDRESS")] 30 arm_base: u32, 31 #[arg(long, default_value_t = false)] 32 tcp: bool, 33 #[arg(long, default_value_t = false)] 34 sylveos: bool, 35 #[arg(value_hint = clap::ValueHint::DirPath)] 36 file: PathBuf, 37 } 38 39 fn hex_value(s: &str) -> Result<u32, String> { 40 let trimmed = if let Some(trimmed) = s.strip_prefix("0x") { 41 trimmed 42 } else { 43 s 44 }; 45 46 u32::from_str_radix(trimmed, 16).map_err(|e| e.to_string()) 47 } 48 49 async fn serial_to_stdout( 50 serial: &mut ReadHalf<SerialStream>, 51 stdout: &mut Stdout, 52 ) -> io::Result<()> { 53 loop { 54 let byte = serial.read_u8().await?; 55 56 if byte == 0 { 57 info!("pi exited"); 58 exit(0); 59 } 60 61 stdout.write_u8(byte).await?; 62 stdout.flush().await?; 63 } 64 } 65 66 async fn default_action(port: SerialStream) -> anyhow::Result<()> { 67 let (mut port_read, mut port_write) = io::split(port); 68 let mut stdin = io::stdin(); 69 let mut stdout = io::stdout(); 70 71 tokio::select!( 72 _ = io::copy(&mut stdin, &mut port_write) => { 73 warn!("stdin disconnected, exiting"); 74 exit(0); 75 }, 76 _ = serial_to_stdout(&mut port_read, &mut stdout) => { 77 warn!("serial port disconnected, exiting"); 78 exit(0); 79 }, 80 ); 81 } 82 83 #[tokio::main] 84 async fn main() -> anyhow::Result<()> { 85 let indicatif_layer = IndicatifLayer::new() 86 .with_span_field_formatter(hide_indicatif_span_fields(DefaultFields::new())) 87 .with_progress_style(ProgressStyle::with_template( 88 "{span_child_prefix}{span_name}{{{span_fields}}} {wide_msg} {elapsed}", 89 )?) 90 .with_span_child_prefix_symbol("↳ ") 91 .with_span_child_prefix_indent(" "); 92 93 tracing_subscriber::registry() 94 .with( 95 EnvFilter::builder() 96 .with_default_directive(LevelFilter::INFO.into()) 97 .from_env_lossy(), 98 ) 99 .with( 100 tracing_subscriber::fmt::layer() 101 .with_writer(indicatif_layer.get_stderr_writer()) 102 .with_target(false), 103 ) 104 .with(indicatif_layer.with_filter(IndicatifFilter::new(false))) 105 .init(); 106 107 let args = Args::parse(); 108 109 if !PathBuf::from(&args.device).exists() { 110 anyhow::bail!("serial device not found"); 111 } 112 113 if !args.file.exists() { 114 anyhow::bail!("binary not found"); 115 } 116 117 let mut port = tokio_serial::new(&args.device, args.baud) 118 .data_bits(DataBits::Eight) 119 .flow_control(FlowControl::None) 120 .parity(Parity::None) 121 .stop_bits(StopBits::One) 122 .timeout(Duration::from_secs_f64(args.timeout)) 123 .open_native_async()?; 124 125 bootload::boot(&mut port, &args.file, args.arm_base).await?; 126 127 if args.tcp { 128 let (port_read, port_write) = io::split(port); 129 tcp::serve(port_read, port_write).await?; 130 } else if args.sylveos { 131 sylveos::start(port).await?; 132 } else { 133 default_action(port).await?; 134 } 135 136 Ok(()) 137 }