| use core::{str, task}; |
| use std::collections::HashSet; |
| |
| use anyhow::{anyhow, Result}; |
| use clap::{Parser, Subcommand, ValueEnum}; |
| use env_logger::Target; |
| use futures_lite::future::block_on; |
| use log::{debug, error, info}; |
| use nusb::{ |
| transfer::{ControlIn, ControlOut, ControlType, Recipient}, |
| Interface, |
| }; |
| use serde::{Deserialize, Serialize}; |
| |
| #[derive(Parser)] |
| #[command(version, about, long_about = None)] |
| struct Cli { |
| #[command(subcommand)] |
| command: Commands, |
| } |
| |
| #[derive(Clone, Copy, Debug, ValueEnum)] |
| enum SwdTarget { |
| Dut, |
| Buddy, |
| } |
| |
| #[derive(Clone, Subcommand)] |
| enum Commands { |
| /// Query test target for info. |
| Info, |
| |
| /// Sets the Test Status RGB LED color. |
| SetLed { red: u8, green: u8, blue: u8 }, |
| /// Selects which target is connected to the probe. |
| Target { target: SwdTarget }, |
| } |
| |
| #[derive(Debug, Deserialize, Serialize)] |
| #[serde(rename_all = "snake_case")] |
| enum TestTargetCapability { |
| Uart, |
| Spi, |
| I2c, |
| DigitalIo, |
| Analog, |
| } |
| |
| #[derive(Debug, Deserialize, Serialize)] |
| struct TestTargetInfo { |
| /// Name of the test target. |
| name: String, |
| |
| /// MCU of the device under test. |
| mcu: String, |
| |
| /// Capabilities of the test target board. |
| capabilities: Vec<TestTargetCapability>, |
| } |
| |
| const RPI_VENDOR_ID: u16 = 0x2e8a; |
| const DEBUG_PROBE_PRODUCT_ID: u16 = 0x000c; |
| |
| struct PigweedTestTarget { |
| interface: Interface, |
| } |
| |
| #[derive(Debug)] |
| #[repr(u16)] |
| enum PigweedVendorCommand { |
| Hello = 0, |
| SetTarget = 1, |
| GetTargetInfo = 2, |
| SetLed = 3, |
| } |
| |
| impl PigweedTestTarget { |
| pub fn new() -> Result<Self> { |
| let device_info = nusb::list_devices()? |
| .find(|d| d.vendor_id() == RPI_VENDOR_ID && d.product_id() == DEBUG_PROBE_PRODUCT_ID) |
| .ok_or_else(|| anyhow!("Could not find debug probe"))?; |
| |
| let device = device_info.open()?; |
| let interface = device.claim_interface(0)?; |
| |
| debug!("{:?}", PigweedVendorCommand::Hello as u16); |
| let result = block_on(interface.control_in(ControlIn { |
| control_type: ControlType::Vendor, |
| recipient: Recipient::Device, |
| request: 0x50, |
| value: 0x0, |
| index: PigweedVendorCommand::Hello as u16, |
| length: 256, |
| })); |
| if let Err(e) = result.status { |
| return Err(anyhow!("Could not query Pigweed vendor interface: {e}")); |
| } |
| if &result.data != b"PIGWEED" { |
| info!("{result:?}"); |
| |
| return Err(anyhow!( |
| "Pigweed vendor interface did not respond correctly" |
| )); |
| } |
| |
| Ok(Self { interface }) |
| } |
| |
| pub fn set_target(&mut self, target: SwdTarget) -> Result<()> { |
| let value = match target { |
| SwdTarget::Dut => 0, |
| SwdTarget::Buddy => 1, |
| }; |
| let result = block_on(self.interface.control_out(ControlOut { |
| control_type: ControlType::Vendor, |
| recipient: Recipient::Device, |
| request: 0x50, |
| value, |
| index: PigweedVendorCommand::SetTarget as u16, |
| data: &[], |
| })); |
| if let Err(e) = result.status { |
| return Err(anyhow!("Failed to set target to {target:?}: {e}")); |
| } |
| |
| Ok(()) |
| } |
| |
| pub fn get_info(&mut self) -> Result<TestTargetInfo> { |
| let result = block_on(self.interface.control_in(ControlIn { |
| control_type: ControlType::Vendor, |
| recipient: Recipient::Device, |
| request: 0x50, |
| value: 0x0, |
| index: PigweedVendorCommand::GetTargetInfo as u16, |
| length: 256, |
| })); |
| if let Err(e) = result.status { |
| return Err(anyhow!("Could not query Pigweed target info: {e}")); |
| } |
| let json = str::from_utf8(&result.data)?; |
| debug!("json info string: {json}"); |
| let info = serde_json::from_str(json)?; |
| |
| Ok(info) |
| } |
| |
| pub fn set_led(&mut self, red: u8, green: u8, blue: u8) -> Result<()> { |
| let result = block_on(self.interface.control_out(ControlOut { |
| control_type: ControlType::Vendor, |
| recipient: Recipient::Device, |
| request: 0x50, |
| value: 0x0, |
| index: PigweedVendorCommand::SetLed as u16, |
| data: &[red, green, blue], |
| })); |
| |
| if let Err(e) = result.status { |
| return Err(anyhow!("Failed to set target to test status LED: {e}")); |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| fn info_command() -> Result<()> { |
| let mut target = PigweedTestTarget::new()?; |
| let info = target.get_info()?; |
| println!("Board Name: {}", info.name); |
| println!("Microcontroller: {}", info.mcu); |
| println!("Capabilities:"); |
| for cap in &info.capabilities { |
| println!(" - {cap:?}"); |
| } |
| |
| Ok(()) |
| } |
| |
| fn set_led_command(red: u8, green: u8, blue: u8) -> Result<()> { |
| let mut target = PigweedTestTarget::new()?; |
| target.set_led(red, green, blue)?; |
| println!("Target status LED set to #{red:02x}{green:02x}{blue:02x}"); |
| Ok(()) |
| } |
| |
| fn target_command(swd_target: SwdTarget) -> Result<()> { |
| let mut target = PigweedTestTarget::new()?; |
| target.set_target(swd_target)?; |
| println!("SWD Target set to {swd_target:?}"); |
| Ok(()) |
| } |
| |
| fn main() { |
| env_logger::init(); |
| let cli = Cli::parse(); |
| let result = match cli.command { |
| Commands::Info => info_command(), |
| Commands::SetLed { red, green, blue } => set_led_command(red, green, blue), |
| Commands::Target { target } => target_command(target), |
| }; |
| if let Err(e) = result { |
| println!("command failed: {e}"); |
| std::process::exit(1); |
| } |
| } |