Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
**/*.dSYM/
72 changes: 34 additions & 38 deletions src/cd321x.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
* Copyright The Asahi Linux Contributors
*/

use crate::transport::Transport;
use crate::{Error, Result};
use i2cdev::{core::I2CDevice, linux::LinuxI2CDevice};
use log::{error, info};
use std::{
io,
str::FromStr,
thread,
time::{Duration, Instant},
Expand Down Expand Up @@ -43,6 +44,7 @@ enum TpsMode {

impl FromStr for TpsMode {
type Err = ();

fn from_str(input: &str) -> std::result::Result<TpsMode, ()> {
match input {
"APP " => Ok(TpsMode::App),
Expand All @@ -60,41 +62,24 @@ fn is_invalid_cmd(val: u32) -> bool {
val == 0x444d4321
}

pub(crate) struct Device {
i2c: LinuxI2CDevice,
pub(crate) struct Device<'a> {
transport: &'a mut dyn Transport,
key: Vec<u8>,
}

/// Try to open the given I2C bus and slave address.
/// Returns a configured LinuxI2CDevice on success.
fn verify_i2c_device(bus: &str, slave_address: u16) -> Result<LinuxI2CDevice> {
match LinuxI2CDevice::new(bus, slave_address) {
Ok(dev) => {
return Ok(dev);
}
Err(_) => {} // Fall through to attempt forced open
}
impl<'a> Device<'a> {
pub(crate) fn new(transport: &'a mut dyn Transport, code: &str) -> Result<Self> {
let mut key = code.as_bytes().to_vec();
key.reverse();

info!("Safely opening failed ==> Forcefully opening device...");
let forced = unsafe { LinuxI2CDevice::force_new(bus, slave_address) };
match forced {
Ok(dev) => Ok(dev),
Err(_) => Err(Error::I2C),
}
}
let mut device = Self { transport, key };

impl Device {
pub(crate) fn new(bus: &str, address: u16, code: String) -> Result<Self> {
let mut device = Self {
i2c: verify_i2c_device(bus, address)?,
key: code.into_bytes().into_iter().rev().collect::<Vec<u8>>(),
};
if device.get_mode()? != TpsMode::App {
return Err(Error::TypecController);
}

device.lock(device.key.clone().as_slice())?;
device.dbma(true)?;

Ok(device)
}

Expand All @@ -109,7 +94,6 @@ impl Device {
cmd_timeout: Duration,
res_delay: Duration,
) -> Result<()> {
// First: Check CMD1 Register busy
{
let mut status_buf = [0u8; 4];
self.read_block(TPS_REG_CMD1, &mut status_buf)?;
Expand All @@ -120,20 +104,18 @@ impl Device {
}
}

// Write input Data to DATA1
if !in_data.is_empty() {
self.write_block(TPS_REG_DATA1, in_data)?;
}

// Write 4-byte command tag
self.write_block(TPS_REG_CMD1, cmd_tag)?;

// Poll until CMD1 becomes zero or timeout
let start = Instant::now();
loop {
let mut status_buf = [0u8; 4];
self.read_block(TPS_REG_CMD1, &mut status_buf)?;
let val = u32::from_le_bytes(status_buf);

if is_invalid_cmd(val) {
info!("Invalid Command");
return Err(Error::TypecController);
Expand All @@ -145,6 +127,7 @@ impl Device {
return Err(Error::ControllerTimeout);
}
}

thread::sleep(res_delay);
Ok(())
}
Expand All @@ -155,23 +138,32 @@ impl Device {
buf.push(reg);
buf.push(size);
buf.extend_from_slice(data);
self.i2c.write(&buf).map_err(|_| Error::I2C)?;

self.transport.write(&buf).map_err(Error::Io)?;
Ok(())
}

fn read_block(&mut self, reg: u8, buf: &mut [u8]) -> Result<()> {
self.i2c.write(&[reg]).map_err(|_| Error::I2C)?;
let mut internal_buf = vec![0u8; buf.len() + 1];
self.i2c.read(&mut internal_buf).map_err(|_| Error::I2C)?;
buf.copy_from_slice(&internal_buf[1..=buf.len()]);
fn read_block(&mut self, reg: u8, out: &mut [u8]) -> Result<()> {
self.transport.write(&[reg]).map_err(Error::Io)?;

let need = out.len() + 1;
let internal = self.transport.read(need).map_err(Error::Io)?;
if internal.len() != need {
return Err(Error::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("read_block: expected {need} bytes, got {}", internal.len()),
)));
}

out.copy_from_slice(&internal[1..=out.len()]);
Ok(())
}

fn get_mode(&mut self) -> Result<TpsMode> {
let mut buf = [0u8; 4];
self.read_block(TPS_REG_MODE, &mut buf)?;
let s = std::str::from_utf8(&buf).unwrap();

let s = std::str::from_utf8(&buf).map_err(Error::Utf8)?;
let m = TpsMode::from_str(s).map_err(|_| Error::TypecController)?;
Ok(m)
}
Expand All @@ -196,11 +188,13 @@ impl Device {
if self.get_mode()? != TpsMode::Dbma {
return Err(Error::TypecController);
}

let data = [
vec![((sop as u8) << 4) | vdos.len() as u8],
vdos.iter().flat_map(|val| val.to_le_bytes()).collect(),
]
.concat();

self.exec_cmd_with_timing(
b"VDMs",
&data,
Expand Down Expand Up @@ -250,6 +244,7 @@ impl Device {
return Err(Error::ReconnectTimeout);
}
}

info!(" Connected");
thread::sleep(RECONNECT_WAIT);
self.serial()
Expand All @@ -260,6 +255,7 @@ impl Device {
info!("Putting target into serial mode...");
self.vdms(VdmSopType::SopStar, &vdos)?;
info!("Putting local end into serial mode... ");

if self.get_mode()? != TpsMode::Dbma {
return Err(Error::TypecController);
}
Expand All @@ -273,7 +269,7 @@ impl Device {
}
}

impl Drop for Device {
impl Drop for Device<'_> {
fn drop(&mut self) {
let lock: [u8; 4] = [0, 0, 0, 0];
let _ = self.dbma(false);
Expand Down
16 changes: 14 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@
* Copyright The Asahi Linux Contributors
*/


#![cfg_attr(not(any(target_os = "linux", target_os = "android")), allow(dead_code, unused_imports))]
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn main() {
eprintln!("tuxvdmtool currently supports Linux only.");
}

pub mod cd321x;
pub mod transport;

use env_logger::Env;
use log::error;
use std::{fs, process::ExitCode};
use transport::i2c::I2cTransport;

#[derive(Debug)]
#[allow(dead_code)]
Expand Down Expand Up @@ -45,7 +54,6 @@ fn vdmtool() -> Result<()> {
clap::Command::new("serial").about("reboot the target and enter serial mode"),
),
)
// dummy command to display help for "reboot serial"
.subcommand(
clap::Command::new("reboot serial").about("reboot the target and enter serial mode"),
)
Expand Down Expand Up @@ -73,7 +81,10 @@ fn vdmtool() -> Result<()> {
}

let code = device.to_uppercase();
let mut device = cd321x::Device::new(matches.get_one::<String>("bus").unwrap(), addr, code)?;

let bus = matches.get_one::<String>("bus").unwrap();
let mut transport = I2cTransport::new(bus.as_str(), addr)?;
let mut device = cd321x::Device::new(&mut transport, &code)?;

match matches.subcommand() {
Some(("dfu", _)) => {
Expand All @@ -100,6 +111,7 @@ fn vdmtool() -> Result<()> {
Ok(())
}

#[cfg(any(target_os = "linux", target_os = "android"))]
fn main() -> ExitCode {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();

Expand Down
71 changes: 71 additions & 0 deletions src/transport/i2c.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright The Asahi Linux Contributors
*/

use crate::transport::Transport;
use crate::{Error, Result};
use std::io;

#[cfg(any(target_os = "linux", target_os = "android"))]
use i2cdev::{core::I2CDevice, linux::LinuxI2CDevice};

#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) struct I2cTransport {
dev: LinuxI2CDevice,
}

#[cfg(any(target_os = "linux", target_os = "android"))]
fn open_i2c(bus: &str, addr: u16) -> Result<LinuxI2CDevice> {
if let Ok(dev) = LinuxI2CDevice::new(bus, addr) {
return Ok(dev);
}
log::info!("Safely opening failed ==> Forcefully opening device...");
unsafe { LinuxI2CDevice::force_new(bus, addr) }.map_err(|_| Error::I2C)
}

#[cfg(any(target_os = "linux", target_os = "android"))]
impl I2cTransport {
pub(crate) fn new(bus: &str, addr: u16) -> Result<Self> {
Ok(Self { dev: open_i2c(bus, addr)? })
}
}

#[cfg(any(target_os = "linux", target_os = "android"))]
impl Transport for I2cTransport {
fn write(&mut self, data: &[u8]) -> io::Result<()> {
self.dev
.write(data)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}

fn read(&mut self, len: usize) -> io::Result<Vec<u8>> {
let mut buf = vec![0u8; len];
self.dev
.read(&mut buf)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(buf)
}
}

#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub(crate) struct I2cTransport;

#[cfg(not(any(target_os = "linux", target_os = "android")))]
impl I2cTransport {
pub(crate) fn new(_bus: &str, _addr: u16) -> Result<Self> {
Err(Error::FeatureMissing)
}
}

#[cfg(not(any(target_os = "linux", target_os = "android")))]
impl Transport for I2cTransport {
fn write(&mut self, _data: &[u8]) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Unsupported, "i2c transport is linux-only"))
}

fn read(&mut self, _len: usize) -> io::Result<Vec<u8>> {
Err(io::Error::new(io::ErrorKind::Unsupported, "i2c transport is linux-only"))
}
}
6 changes: 6 additions & 0 deletions src/transport/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub mod i2c;

pub trait Transport {
fn write(&mut self, data: &[u8]) -> std::io::Result<()>;
fn read(&mut self, len: usize) -> std::io::Result<Vec<u8>>;
}