Skip to content

Commit a256c62

Browse files
bjarne-dietrichjannau
authored andcommitted
Changed implementation to i2cdev instead of sysfs calls.
Signed-off-by: bjarne-dietrich <[email protected]>
1 parent 0ea08c2 commit a256c62

3 files changed

Lines changed: 196 additions & 56 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tuxvdmtool"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2024"
55
license = "Apache-2.0"
66
description = "A tool to interact with USB Type C controller chips to interact with connected Apple devices"
@@ -10,3 +10,4 @@ repository = "https://github.com/AsahiLinux/tuxvdmtool"
1010
clap = { version = "3", features = ["cargo"] }
1111
log = "0.4.28"
1212
env_logger = { version = "0.11.8", default-features = false }
13+
i2cdev = "0.6"

src/cd321x.rs

Lines changed: 176 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,23 @@
55
*/
66

77
use crate::{Error, Result};
8+
use i2cdev::{core::I2CDevice, linux::LinuxI2CDevice};
89
use log::{error, info};
910
use std::{
10-
fs::{self, OpenOptions},
11-
io::Read,
12-
path, thread,
11+
str::FromStr,
12+
thread,
1313
time::{Duration, Instant},
1414
};
1515

1616
const RECONNECT_TIMEOUT: Duration = Duration::from_secs(3);
1717
const POLL_WAIT: Duration = Duration::from_millis(100);
1818
const RECONNECT_WAIT: Duration = Duration::from_secs(1);
1919

20+
const TPS_REG_MODE: u8 = 0x03;
21+
const TPS_REG_CMD1: u8 = 0x08;
22+
const TPS_REG_DATA1: u8 = 0x09;
23+
const TPS_REG_POWER_STATUS: u8 = 0x3f;
24+
2025
#[allow(dead_code)]
2126
enum VdmSopType {
2227
Sop = 0b00,
@@ -25,106 +30,222 @@ enum VdmSopType {
2530
SopStar = 0b11,
2631
}
2732

33+
#[allow(dead_code)]
34+
#[derive(Debug, PartialEq)]
35+
enum TpsMode {
36+
TpsModeApp,
37+
TpsModeBoot,
38+
TpsModeBist,
39+
TpsModeDisc,
40+
TpsModePtch,
41+
TpsModeDbma,
42+
}
43+
44+
impl FromStr for TpsMode {
45+
type Err = ();
46+
fn from_str(input: &str) -> std::result::Result<TpsMode, ()> {
47+
match input {
48+
"APP " => Ok(TpsMode::TpsModeApp),
49+
"BOOT" => Ok(TpsMode::TpsModeBoot),
50+
"BIST" => Ok(TpsMode::TpsModeBist),
51+
"DISC" => Ok(TpsMode::TpsModeDisc),
52+
"PTCH" => Ok(TpsMode::TpsModePtch),
53+
"DBMa" => Ok(TpsMode::TpsModeDbma),
54+
_ => Err(()),
55+
}
56+
}
57+
}
58+
59+
fn is_invalid_cmd(val: u32) -> bool {
60+
val == 0x444d4321
61+
}
62+
2863
pub(crate) struct Device {
29-
path: path::PathBuf,
64+
i2c: LinuxI2CDevice,
3065
key: Vec<u8>,
3166
}
3267

33-
fn verify_device(dev: &str) -> Result<path::PathBuf> {
34-
let mut fname = OpenOptions::new()
35-
.read(true)
36-
.open(path::Path::new(dev).join("name"))
37-
.unwrap();
38-
let mut data = Vec::new();
39-
fname.read_to_end(&mut data).unwrap();
40-
let name = std::str::from_utf8(&data).map_err(Error::Utf8)?.trim();
41-
if name != "cd321x" {
42-
error!("{dev}/name \"{name}\" does not match \"cd321x\"");
43-
return Err(Error::TypecController);
44-
}
45-
46-
let vdm_dir = path::Path::new(dev).join("cd321x_vdm");
47-
if !vdm_dir.exists() {
48-
error!("{} does not exists", vdm_dir.display());
49-
return Err(Error::FeatureMissing);
50-
}
51-
Ok(vdm_dir.to_path_buf())
68+
/// Try to open the given I2C bus and slave address.
69+
/// Returns a configured LinuxI2CDevice on success.
70+
fn verify_i2c_device(bus: &str, slave_address: u16) -> Result<LinuxI2CDevice> {
71+
match LinuxI2CDevice::new(bus, slave_address) {
72+
Ok(dev) => {
73+
return Ok(dev);
74+
}
75+
Err(_) => {} // Fall through to attempt forced open
76+
}
77+
78+
info!("Safely opening failed ==> Forcefully opening device...");
79+
let forced = unsafe { LinuxI2CDevice::force_new(bus, slave_address) };
80+
match forced {
81+
Ok(dev) => Ok(dev),
82+
Err(_) => Err(Error::I2CError),
83+
}
5284
}
5385

5486
impl Device {
55-
pub(crate) fn new(dev: &str, code: String) -> Result<Self> {
56-
let device = Self {
57-
path: verify_device(dev)?,
87+
pub(crate) fn new(bus: &str, address: u16, code: String) -> Result<Self> {
88+
let mut device = Self {
89+
i2c: verify_i2c_device(bus, address)?,
5890
key: code.into_bytes().into_iter().rev().collect::<Vec<u8>>(),
5991
};
60-
device.lock(device.key.as_slice())?;
92+
device.lock(device.key.clone().as_slice())?;
6193
device.dbma(true)?;
6294

6395
Ok(device)
6496
}
6597

66-
fn command(&self, command: &[u8; 4], data: &[u8]) -> Result<()> {
67-
let data: Vec<u8> = [command, data].concat();
68-
fs::write(self.path.as_path().join("command"), &data).map_err(Error::Io)
98+
fn exec_cmd(&mut self, cmd_tag: &[u8; 4], in_data: &[u8]) -> Result<()> {
99+
self.exec_cmd_with_timing(cmd_tag, in_data, Duration::from_secs(1), Duration::ZERO)
69100
}
70101

71-
fn lock(&self, key: &[u8]) -> Result<()> {
72-
self.command(b"LOCK", key)
102+
fn exec_cmd_with_timing(
103+
&mut self,
104+
cmd_tag: &[u8; 4],
105+
in_data: &[u8],
106+
cmd_timeout: Duration,
107+
res_delay: Duration,
108+
) -> Result<()> {
109+
// First: Check CMD1 Register busy
110+
{
111+
let mut status_buf = [0u8; 4];
112+
self.read_block(TPS_REG_CMD1, &mut status_buf)?;
113+
let val = u32::from_le_bytes(status_buf);
114+
if val != 0 && !is_invalid_cmd(val) {
115+
info!("Busy Check Failed with VAL = {:?}", val);
116+
return Err(Error::TypecController);
117+
}
118+
}
119+
120+
// Write input Data to DATA1
121+
if !in_data.is_empty() {
122+
self.write_block(TPS_REG_DATA1, in_data)?;
123+
info!("Wrote Data: {:02X?}", in_data);
124+
}
125+
126+
// Write 4-byte command tag
127+
self.write_block(TPS_REG_CMD1, cmd_tag)?;
128+
129+
// Poll until CMD1 becomes zero or timeout
130+
let start = Instant::now();
131+
loop {
132+
let mut status_buf = [0u8; 4];
133+
self.read_block(TPS_REG_CMD1, &mut status_buf)?;
134+
let val = u32::from_le_bytes(status_buf);
135+
136+
if is_invalid_cmd(val) {
137+
info!("Invalid Command");
138+
return Err(Error::InvalidArgument);
139+
}
140+
141+
if val == 0 {
142+
break;
143+
}
144+
if start.elapsed() > cmd_timeout {
145+
return Err(Error::ControllerTimeout);
146+
}
147+
}
148+
thread::sleep(res_delay);
149+
150+
Ok(())
151+
}
152+
153+
fn write_block(&mut self, reg: u8, data: &[u8]) -> Result<()> {
154+
let mut buf = Vec::with_capacity(1 + 1 + data.len());
155+
let size: u8 = data.len().try_into().unwrap();
156+
buf.push(reg);
157+
buf.push(size);
158+
buf.extend_from_slice(data);
159+
self.i2c.write(&buf).map_err(|_| Error::I2CError)?;
160+
Ok(())
161+
}
162+
163+
fn read_block(&mut self, reg: u8, buf: &mut [u8]) -> Result<()> {
164+
self.i2c.write(&[reg]).map_err(|_| Error::I2CError)?;
165+
let mut internal_buf = vec![0u8; buf.len() + 1];
166+
self.i2c
167+
.read(&mut internal_buf)
168+
.map_err(|_| Error::I2CError)?;
169+
buf.copy_from_slice(&internal_buf[1..=buf.len()]);
170+
171+
Ok(())
73172
}
74173

75-
fn dbma(&self, debug: bool) -> Result<()> {
174+
fn get_mode(&mut self) -> Result<TpsMode> {
175+
let mut buf = [0u8; 4];
176+
self.read_block(TPS_REG_MODE, &mut buf)?;
177+
let s = std::str::from_utf8(&buf).unwrap();
178+
let m = TpsMode::from_str(s).map_err(|_| Error::TypecController)?;
179+
180+
Ok(m)
181+
}
182+
183+
fn lock(&mut self, key: &[u8]) -> Result<()> {
184+
self.exec_cmd(b"LOCK", &key)
185+
}
186+
187+
fn dbma(&mut self, debug: bool) -> Result<()> {
76188
let data: [u8; 1] = if debug { [1] } else { [0] };
77-
self.command(b"DBMa", &data)
189+
self.exec_cmd(b"DBMa", &data)?;
190+
if self.get_mode()? != TpsMode::TpsModeDbma {
191+
return Err(Error::TypecController);
192+
}
193+
Ok(())
78194
}
79195

80-
fn vdms(&self, sop: VdmSopType, vdos: &[u32]) -> Result<()> {
196+
fn vdms(&mut self, sop: VdmSopType, vdos: &[u32]) -> Result<()> {
81197
if vdos.is_empty() || vdos.len() > 7 {
82198
return Err(Error::InvalidArgument);
83199
}
200+
if self.get_mode()? != TpsMode::TpsModeDbma {
201+
return Err(Error::TypecController);
202+
}
84203
let data = [
85204
vec![((sop as u8) << 4) | vdos.len() as u8],
86205
vdos.iter().flat_map(|val| val.to_le_bytes()).collect(),
87206
]
88207
.concat();
89-
self.command(b"VDMs", &data)
208+
self.exec_cmd_with_timing(
209+
b"VDMs",
210+
&data,
211+
Duration::from_millis(200),
212+
Duration::from_millis(200),
213+
)
90214
}
91215

92-
fn dven(&self, vdos: &[u32]) -> Result<()> {
216+
fn dven(&mut self, vdos: &[u32]) -> Result<()> {
93217
let data: Vec<u8> = vdos.iter().flat_map(|val| val.to_le_bytes()).collect();
94-
self.command(b"DEVn", &data)
218+
self.exec_cmd(b"DEVn", &data)
95219
}
96220

97-
fn is_connected(&self) -> Option<bool> {
98-
let data: Vec<u8> = fs::read(self.path.as_path().join("power_status")).ok()?;
99-
let string = std::str::from_utf8(&data).ok()?;
100-
if string.len() < 6 {
101-
return None;
102-
}
103-
let power_status = u16::from_str_radix(&string[2..6], 16).ok()?;
104-
105-
Some((power_status & 1) != 0)
221+
fn check_connected(&mut self) -> Result<bool> {
222+
let mut buf = [0u8; 2];
223+
self.read_block(TPS_REG_POWER_STATUS, &mut buf)?;
224+
let power_status = u16::from_le_bytes(buf);
225+
Ok((power_status & 1) != 0)
106226
}
107227

108-
pub(crate) fn dfu(&self) -> Result<()> {
228+
pub(crate) fn dfu(&mut self) -> Result<()> {
109229
let vdos: [u32; 3] = [0x5ac8012, 0x106, 0x80010000];
110230
info!("Rebooting target into DFU mode...");
111231
self.vdms(VdmSopType::SopStar, &vdos)
112232
}
113-
pub(crate) fn reboot(&self) -> Result<()> {
233+
234+
pub(crate) fn reboot(&mut self) -> Result<()> {
114235
let vdos: [u32; 3] = [0x5ac8012, 0x105, 0x80000000];
115236
info!("Rebooting target into normal mode...");
116237
self.vdms(VdmSopType::SopStar, &vdos)
117238
}
118239

119-
pub(crate) fn reboot_serial(&self) -> Result<()> {
240+
pub(crate) fn reboot_serial(&mut self) -> Result<()> {
120241
self.reboot()?;
121242
info!("Waiting for connection...");
122243

123244
thread::sleep(RECONNECT_WAIT);
124245

125246
let now = Instant::now();
126247
loop {
127-
if self.is_connected().unwrap_or(false) {
248+
if self.check_connected().unwrap_or(false) {
128249
break;
129250
}
130251
thread::sleep(POLL_WAIT);
@@ -138,11 +259,14 @@ impl Device {
138259
self.serial()
139260
}
140261

141-
pub(crate) fn serial(&self) -> Result<()> {
262+
pub(crate) fn serial(&mut self) -> Result<()> {
142263
let vdos: [u32; 2] = [0x5ac8012, 0x1840306];
143264
info!("Putting target into serial mode...");
144265
self.vdms(VdmSopType::SopStar, &vdos)?;
145266
info!("Putting local end into serial mode... ");
267+
if self.get_mode()? != TpsMode::TpsModeDbma {
268+
return Err(Error::TypecController);
269+
}
146270
self.dven(&vdos[1..2])
147271
}
148272
}

src/main.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ enum Error {
1919
TypecController,
2020
InvalidArgument,
2121
ReconnectTimeout,
22+
ControllerTimeout,
23+
I2CError,
2224
Io(std::io::Error),
2325
Utf8(std::str::Utf8Error),
2426
}
@@ -28,8 +30,12 @@ type Result<T> = std::result::Result<T, Error>;
2830
fn vdmtool() -> Result<()> {
2931
let matches = clap::command!()
3032
.arg(
31-
clap::arg!(-d --device [DEVICE] "Path to the USB-C controller device.")
32-
.default_value("/sys/class/i2c-dev/i2c-0/device/0-0038"),
33+
clap::arg!(-b --bus [BUS] "i2c bus of the USB-C controller device.")
34+
.default_value("/dev/i2c-0"),
35+
)
36+
.arg(
37+
clap::arg!(-a --address [ADDRESS] "i2c slave address of the USB-C controller device.")
38+
.default_value("0x38"),
3339
)
3440
.subcommand(
3541
clap::Command::new("reboot")
@@ -55,8 +61,17 @@ fn vdmtool() -> Result<()> {
5561
error!("Host is not an Apple silicon system: \"{compat}\"");
5662
return Err(Error::Compatible);
5763
}
64+
65+
let addr_str = matches.get_one::<String>("address").unwrap();
66+
let addr: u16;
67+
if addr_str.starts_with("0x") {
68+
addr = u16::from_str_radix(&addr_str[2..], 16).unwrap();
69+
} else {
70+
addr = u16::from_str_radix(addr_str, 10).unwrap();
71+
}
72+
5873
let code = device.to_uppercase();
59-
let device = cd321x::Device::new(matches.get_one::<String>("device").unwrap(), code)?;
74+
let mut device = cd321x::Device::new(matches.get_one::<String>("bus").unwrap(), addr, code)?;
6075

6176
match matches.subcommand() {
6277
Some(("dfu", _)) => {

0 commit comments

Comments
 (0)