From 00c19d4295b243d955da416001cfc376ca50721f Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 2 Dec 2020 10:52:08 -0700 Subject: [PATCH] Bitbang I2C --- usb4/Cargo.lock | 70 +------- usb4/Cargo.toml | 3 +- usb4/src/main.rs | 411 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 362 insertions(+), 122 deletions(-) diff --git a/usb4/Cargo.lock b/usb4/Cargo.lock index d51e07a..6a0949d 100644 --- a/usb4/Cargo.lock +++ b/usb4/Cargo.lock @@ -1,29 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "byteorder" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" - -[[package]] -name = "cc" -version = "1.0.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "coreboot-collector" version = "0.1.0" @@ -47,37 +23,12 @@ dependencies = [ "synstructure", ] -[[package]] -name = "i2cdev" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0eb3d9b6b02dc2508ee23439170004e44344bab9d53a490eb1f64c885b5003" -dependencies = [ - "bitflags", - "byteorder", - "libc", - "nix", -] - [[package]] name = "libc" version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" -[[package]] -name = "nix" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "void", -] - [[package]] name = "numtoa" version = "0.2.3" @@ -169,22 +120,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "usb4" +version = "0.1.0" +dependencies = [ + "coreboot-collector", +] + [[package]] name = "version_check" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - -[[package]] -name = "you-ass-bee-see" -version = "0.1.0" -dependencies = [ - "coreboot-collector", - "i2cdev", -] diff --git a/usb4/Cargo.toml b/usb4/Cargo.toml index 108ca77..1feffc2 100644 --- a/usb4/Cargo.toml +++ b/usb4/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "you-ass-bee-see" +name = "usb4" version = "0.1.0" authors = ["Jeremy Soller "] edition = "2018" @@ -8,4 +8,3 @@ edition = "2018" [dependencies] coreboot-collector = { path = "../tools/coreboot-collector" } -i2cdev = "0.4.4" diff --git a/usb4/src/main.rs b/usb4/src/main.rs index de7fe40..7db5b67 100644 --- a/usb4/src/main.rs +++ b/usb4/src/main.rs @@ -1,7 +1,11 @@ use coreboot_collector::sideband::Sideband; -use i2cdev::core::I2CDevice; -use i2cdev::linux::LinuxI2CDevice; -use std::{fs, thread, time}; +use std::{ + fs, + rc::Rc, + process, + thread, + time +}; const IECS_CMD: u8 = 8; const IECS_DATA: u8 = 9; @@ -13,71 +17,319 @@ const CMD_BLKW: u32 = 0x574b4c42; const CMD_BOPS: u32 = 0x53504f42; const CMD_PCYC: u32 = 0x43594350; -const GPIO_FORCE_POWER: (u8, u8) = (0x6E, 0x82); // GPP_A23 -fn read(dev: &mut I, reg: u8) -> Result { - let bytes = dev.smbus_read_block_data(reg)?; - //TODO: return error on bytes.len() != 4 - Ok( - bytes[0] as u32 | - (bytes[1] as u32) << 8 | - (bytes[2] as u32) << 16 | - (bytes[3] as u32) << 24 - ) +#[repr(u64)] +pub enum GpioPadMode { + Gpio = 0 << 10, + Nf1 = 1 << 10, + Nf2 = 2 << 10, + Nf3 = 3 << 10, + Nf4 = 4 << 10, + Nf5 = 5 << 10, + Nf6 = 6 << 10, + Nf7 = 7 << 10, } -fn write(dev: &mut I, reg: u8, data: u32) -> Result<(), I::Error> { - let bytes = [ - data as u8, - (data >> 8) as u8, - (data >> 16) as u8, - (data >> 24) as u8, - ]; - dev.smbus_write_block_data(reg, &bytes) +pub struct Gpio { + //TODO: this should probably be locked + sideband: Rc, + port: u8, + pad: u8, } +impl Gpio { + const PAD_MODE: u64 = 0b111 << 10; + const RX_DIS: u64 = 1 << 9; + const TX_DIS: u64 = 1 << 8; + const RX: u64 = 1 << 1; + const TX: u64 = 1 << 0; -fn command(dev: &mut I, cmd: u32) -> Result { - write(dev, IECS_CMD, cmd)?; - loop { - let status = read(dev, IECS_CMD)?; - if status != cmd { - //TODO: perform error checking here - return Ok(status); + pub fn new(sideband: Rc, port: u8, pad: u8) -> Self { + Self { sideband, port, pad } + } + + unsafe fn get_config(&self) -> u64 { + self.sideband.gpio(self.port, self.pad) + } + + unsafe fn set_config(&mut self, config: u64) { + self.sideband.set_gpio(self.port, self.pad, config); + } + + unsafe fn get_mask(&self, mask: u64) -> bool { + self.get_config() & mask == mask + } + + unsafe fn set_mask(&mut self, mask: u64, value: bool) { + let mut config = self.get_config(); + if value { + config |= mask; + } else { + config &= !mask; + } + self.set_config(config); + } + + pub unsafe fn set_pad_mode(&mut self, mode: GpioPadMode) { + let mut config = self.get_config(); + config &= !Self::PAD_MODE; + config |= mode as u64; + self.set_config(config); + + } + + pub unsafe fn enable_rx(&mut self, value: bool) { + self.set_mask(Self::RX_DIS, !value); + } + + pub unsafe fn enable_tx(&mut self, value: bool) { + self.set_mask(Self::TX_DIS, !value); + } + + pub unsafe fn get_rx(&self) -> bool { + self.get_mask(Self::RX) + } + + pub unsafe fn get_tx(&self) -> bool { + self.get_mask(Self::TX) + } + + pub unsafe fn set_tx(&mut self, value: bool) { + self.set_mask(Self::TX, value); + } +} + +pub struct I2CBitbang { + scl: Gpio, + scl_config: u64, + sda: Gpio, + sda_config: u64, +} + +impl I2CBitbang { + pub unsafe fn new(mut scl: Gpio, mut sda: Gpio) -> Self { + //TODO: will this transmit something invalid? + + let scl_config = scl.get_config(); + scl.enable_rx(true); + scl.enable_tx(false); + scl.set_tx(false); + scl.set_pad_mode(GpioPadMode::Gpio); + + let sda_config = sda.get_config(); + sda.enable_rx(true); + sda.enable_tx(false); + sda.set_tx(false); + sda.set_pad_mode(GpioPadMode::Gpio); + + Self { scl, scl_config, sda, sda_config, } + } + + // Delay half half of period + fn delay(&self) { + // Hard coded to 5 us, which is half of the period 10 us for a frequency of 100 KHz + thread::sleep(time::Duration::from_micros(5)); + } + + // Pull SCL low + unsafe fn clr_scl(&mut self) { + self.scl.enable_tx(true); + } + + // Release SCL, bus pulls it high + unsafe fn set_scl(&mut self) { + self.scl.enable_tx(false); + } + + // Pull SDA low + unsafe fn clr_sda(&mut self) { + self.sda.enable_tx(true); + } + + // Release SDA, bus pulls it high + unsafe fn set_sda(&mut self) { + self.sda.enable_tx(false); + } + + unsafe fn start(&mut self) { + self.set_scl(); + self.set_sda(); + self.delay(); + self.clr_sda(); + self.delay(); + self.clr_scl(); + self.delay(); + } + + unsafe fn stop(&mut self) { + self.clr_sda(); + self.delay(); + self.set_scl(); + self.delay(); + self.set_sda(); + self.delay(); + } + + unsafe fn write_bit(&mut self, bit: bool) { + if bit { + self.set_sda(); + } else { + self.clr_sda(); + } + self.delay(); + self.set_scl(); + self.delay(); + self.clr_scl(); + } + + unsafe fn read_bit(&mut self) -> bool { + self.set_sda(); + self.delay(); + self.set_scl(); + self.delay(); + let bit = self.sda.get_rx(); + self.clr_scl(); + bit + } + + unsafe fn write_byte(&mut self, byte: u8, start: bool) -> bool { + if start { + self.start(); + } + for i in (0..8).rev() { + self.write_bit(byte & (1 << i) != 0); + } + self.read_bit() + } + + unsafe fn read_byte(&mut self, ack: bool) -> u8 { + let mut byte = 0; + for i in (0..8).rev() { + if self.read_bit() { + byte |= 1 << i; + } + } + self.write_bit(!ack); + byte + } + + pub unsafe fn smbus_block_write(&mut self, address: u8, command: u8, bytes: &[u8]) -> usize { + // Only 32 bytes can be processed at a time + if bytes.len() > 32 { + return 0; + } + + let mut count = 0; + if self.write_byte(address << 1, true) { + if self.write_byte(command, false) { + if self.write_byte(bytes.len() as u8, false) { + for byte in bytes.iter() { + if self.write_byte(*byte, false) { + count += 1; + } else { + break; + } + } + } + } + } + self.stop(); + count + } + + pub unsafe fn smbus_block_read(&mut self, address: u8, command: u8) -> Vec { + //TODO: use static buffer? + let mut bytes = Vec::new(); + if self.write_byte(address << 1, true) { + if self.write_byte(command, false) { + if self.write_byte(address << 1 | 1, true) { + let count = self.read_byte(true); + for _i in 0..count { + bytes.push(self.read_byte(true)); + } + } + } + } + self.stop(); + bytes + } +} + +impl Drop for I2CBitbang { + fn drop(&mut self) { + unsafe { + //TODO: will this transmit something invalid? + self.scl.set_config(self.scl_config); + self.sda.set_config(self.sda_config); } } } -fn main() { - //TODO: check model +pub struct Retimer { + i2c: I2CBitbang, + address: u8, +} - let sideband = unsafe { Sideband::new(0xFD00_0000).unwrap() }; - - // Set FORCE_POWER high - unsafe { - let (port, pad) = GPIO_FORCE_POWER; - let mut value = sideband.gpio(port, pad); - value |= 1; - sideband.set_gpio(port, pad, value); +impl Retimer { + pub fn new(i2c: I2CBitbang, address: u8) -> Self { + Self { i2c, address } } - // Sleep 40ms - thread::sleep(time::Duration::from_millis(40)); - - let mut dev = LinuxI2CDevice::new("/dev/i2c-11", 0x40).unwrap(); - eprintln!("Vendor: {:X}", read(&mut dev, 0).unwrap()); - eprintln!("Device: {:X}", read(&mut dev, 1).unwrap()); - - let image = fs::read("../models/galp5/usb4-retimer.rom").unwrap(); - - for i in 2..=32 { - println!("{}: {:X}", i, read(&mut dev, i).unwrap()); + pub unsafe fn read(&mut self, reg: u8) -> Result { + let bytes = self.i2c.smbus_block_read(self.address, reg); + if bytes.len() == 4 { + Ok( + bytes[0] as u32 | + (bytes[1] as u32) << 8 | + (bytes[2] as u32) << 16 | + (bytes[3] as u32) << 24 + ) + } else { + Err(format!("Retimer::read: read {} bytes instead of 4", bytes.len())) + } } - write(&mut dev, IECS_DATA, 0).unwrap(); - println!("IECS_DATA: {:X}", read(&mut dev, IECS_DATA).unwrap()); + pub unsafe fn write(&mut self, reg: u8, data: u32) -> Result<(), String> { + let bytes = [ + data as u8, + (data >> 8) as u8, + (data >> 16) as u8, + (data >> 24) as u8, + ]; + let count = self.i2c.smbus_block_write(self.address, reg, &bytes); + if count == 4 { + Ok(()) + } else { + Err(format!("Retimer::write: wrote {} bytes instead of 4", count)) + } + } + + + pub unsafe fn command(&mut self, cmd: u32) -> Result<(), String> { + self.write(IECS_CMD, cmd)?; + //TODO: is this the right number of retries? + let retries = 1000; + for _i in 0..retries { + let status = self.read(IECS_CMD)?; + if status != cmd { + if status == 0 { + return Ok(()); + } else { + return Err(format!("Retimer::command: read 0x{:X} instead of 0", status)); + } + } + } + Err(format!("Retimer::command: timed out after {} retries", retries)) + } +} + +unsafe fn flash_retimer(retimer: &mut Retimer) -> Result<(), String> { + eprintln!("Vendor: {:X}", retimer.read(0)?); + eprintln!("Device: {:X}", retimer.read(1)?); /* + let image = fs::read("../models/galp5/usb4-retimer.rom").unwrap(); + eprintln!("Set offset to 0"); write(&mut dev, IECS_DATA, 0).unwrap(); let status = command(&mut dev, CMD_BOPS).unwrap(); @@ -125,11 +377,56 @@ fn main() { eprintln!("Successfully flashed retimer"); */ - // Set FORCE_POWER low - unsafe { - let (port, pad) = GPIO_FORCE_POWER; - let mut value = sideband.gpio(port, pad); - value &= !1; - sideband.set_gpio(port, pad, value); + Ok(()) +} + +unsafe fn retimer_access(i2c: I2CBitbang) -> i32 { + let mut retimer = Retimer::new(i2c, 0x40); + match flash_retimer(&mut retimer) { + Ok(()) => 0, + Err(err) => { + eprintln!("Failed to flash retimer: {}", err); + 1 + } + } +} + +unsafe fn i2c_access(sideband: Rc) -> i32 { + let scl = Gpio::new(sideband.clone(), 0x6A, 0x06); // GPP_C3 + let sda = Gpio::new(sideband.clone(), 0x6A, 0x08); // GPP_C4 + let i2c = I2CBitbang::new(scl, sda); + retimer_access(i2c) +} + +unsafe fn force_power(sideband: Rc) -> i32 { + let mut force_power = Gpio::new(sideband.clone(), 0x6E, 0x82); // GPP_A23 + + println!("Set FORCE_POWER high"); + force_power.set_tx(true); + + println!("Sleep 40 ms"); + thread::sleep(time::Duration::from_millis(40)); + + let exit_status = i2c_access(sideband); + + eprintln!("Set FORCE_POWER low"); + force_power.set_tx(false); + + exit_status +} + +fn main() { + //TODO: check model + + unsafe { + let sideband = match Sideband::new(0xFD00_0000) { + Ok(ok) => Rc::new(ok), + Err(err) => { + eprintln!("Failed to access sideband: {}", err); + process::exit(1); + } + }; + + process::exit(force_power(sideband)); } }