From 7cfbf8dbe6f21673a38321247886932e290b3615 Mon Sep 17 00:00:00 2001 From: ko1N Date: Sun, 26 Jul 2020 15:56:21 +0200 Subject: [PATCH] Moved to seperate project from memflow --- .gitignore | 5 + Cargo.toml | 32 +++ LICENSE | 21 ++ README.md | 0 examples/read_pcileech.rs | 9 + src/fpga.rs | 486 ++++++++++++++++++++++++++++++++++++++ src/ft60x.rs | 200 ++++++++++++++++ src/ft60x/chip.rs | 110 +++++++++ src/lib.rs | 40 ++++ 9 files changed, 903 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 examples/read_pcileech.rs create mode 100644 src/fpga.rs create mode 100644 src/ft60x.rs create mode 100644 src/ft60x/chip.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3582899 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target +**/*.rs.bk +*.swp +.vscode +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..24677dd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "memflow-pcileech" +version = "0.1.0" +authors = ["ko1N "] +edition = "2018" +description = "pcileech fpga connector for the memflow physical memory introspection framework" +homepage = "https://github.com/memflow/memflow-pcileech" +repository = "https://github.com/memflow/memflow-pcileech" +readme = "README.md" + +[lib] +crate-type = ["lib", "cdylib"] + +[dependencies] +memflow-core = { path = "../memflow/memflow-core" } +memflow-derive = { path = "../memflow/memflow-derive" } +log = { version = "0.4.8", default-features = false } +rusb = "0.6.0" +dataview = "0.1" +bitfield = "0.13.2" + +[dev-dependencies] +clap = "2.33.0" +simple_logger = "1.0.1" + +[features] +default = [] +plugin = [] + +[[example]] +name = "read_pcileech" +path = "examples/read_pcileech.rs" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fe7f97c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 ko1N + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/examples/read_pcileech.rs b/examples/read_pcileech.rs new file mode 100644 index 0000000..19c38e3 --- /dev/null +++ b/examples/read_pcileech.rs @@ -0,0 +1,9 @@ +use log::Level; + +use memflow_core::connector::ConnectorArgs; +use memflow_pcileech::create_connector; + +fn main() { + simple_logger::init_with_level(Level::Trace).unwrap(); + create_connector(&ConnectorArgs::new()).unwrap(); +} diff --git a/src/fpga.rs b/src/fpga.rs new file mode 100644 index 0000000..de1303c --- /dev/null +++ b/src/fpga.rs @@ -0,0 +1,486 @@ +use crate::ft60x::*; + +use std::mem::MaybeUninit; +use std::time::Duration; + +use log::{info, trace, warn}; + +use memflow_core::{ + error::{Error, Result}, + size, +}; + +use bitfield::bitfield; +use dataview::Pod; + +pub const FPGA_CONFIG_CORE: u16 = 0x0003; +pub const FPGA_CONFIG_PCIE: u16 = 0x0001; +pub const FPGA_CONFIG_SPACE_READONLY: u16 = 0x0000; +pub const FPGA_CONFIG_SPACE_READWRITE: u16 = 0x8000; + +// TODO: remove unused +#[allow(unused)] +pub struct PhyConfig { + magic: u8, // 8 bit + tp_cfg: u8, // 4 bit + tp: u8, // 4 bit + pub wr: PhyConfigWr, // 16 bits + pub rd: PhyConfigRd, // 32 bits +} + +bitfield! { + pub struct PhyConfigWr(u16); + impl Debug; + pl_directed_link_auton, _: 0; + pl_directed_link_change, _: 2, 1; + pl_directed_link_speed, _: 3; + pl_directed_link_width, _: 5, 4; + pl_upstream_prefer_deemph, _: 6; + pl_transmit_hot_rst, _: 7; + pl_downstream_deemph_source, _: 8; + //_, _: 16, 9; +} + +bitfield! { + pub struct PhyConfigRd(u32); + impl Debug; + pl_ltssm_state, _: 5, 0; + pl_rx_pm_state, _: 7, 6; + pl_tx_pm_state, _: 10, 8; + pl_initial_link_width, _: 13, 11; + pl_lane_reversal_mode, _: 15, 14; + pl_sel_lnk_width, _: 17, 16; + pl_phy_lnk_up, _: 18; + pl_link_gen2_cap, _: 19; + pl_link_partner_gen2_supported, _: 20; + pl_link_upcfg_cap, _: 21; + pl_sel_lnk_rate, _: 22; + pl_directed_change_done, _: 23; + pl_received_hot_rst, _: 24; + //_, _: 31, 25; +} + +pub struct Device { + ft60: FT60x, +} + +impl Device { + pub fn new() -> Result { + let mut ft60 = FT60x::new()?; + ft60.abort_pipe(0x02)?; + ft60.abort_pipe(0x82)?; + + ft60.set_suspend_timeout(Duration::new(0, 0))?; + + // check chip configuration + let mut conf = ft60.config()?; + trace!( + "ft60x config: fifo_mode={} channel_config={} optional_feature={}", + conf.fifo_mode, + conf.channel_config, + conf.optional_feature_support + ); + + if conf.fifo_mode != FifoMode::Mode245 as i8 + || conf.channel_config != ChannelConfig::Config1 as i8 + || conf.optional_feature_support != OptionalFeatureSupport::DisableAll as i16 + { + warn!("bad ft60x config, reconfiguring chip"); + + conf.fifo_mode = FifoMode::Mode245 as i8; + conf.channel_config = ChannelConfig::Config1 as i8; + conf.optional_feature_support = OptionalFeatureSupport::DisableAll as i16; + + ft60.set_config(&conf)?; + } else { + info!("ft60x config is valid"); + } + + Ok(Self { ft60 }) + } + + pub fn get_version(&mut self) -> Result<()> { + // DeviceFPGA_GetDeviceId_FpgaVersion_ClearPipe + self.read_version_clear_pipe()?; + + self.read_version_v4()?; + + Ok(()) + } + + fn read_version_clear_pipe(&mut self) -> Result<()> { + let dummy = [ + // cmd msg: FPGA bitstream version (major.minor) v4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x13, 0x77, + // cmd msg: FPGA bitstream version (major) v3 + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x77, + ]; + + self.ft60.write_pipe(&dummy)?; + + let mut buf = vec![0u8; size::mb(16)]; + let bytes = self.ft60.read_pipe(&mut buf[..0x1000])?; + if bytes >= 0x1000 { + self.ft60.read_pipe(&mut buf)?; + } + + Ok(()) + } + + fn read_version_v4(&mut self) -> Result<()> { + let version_major = + self.read_config::(0x0008, FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READONLY)?; + println!("version_major = {}", version_major); + let version_minor = + self.read_config::(0x0009, FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READONLY)?; + println!("version_minor = {}", version_minor); + let fpga_id = + self.read_config::(0x000a, FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READONLY)?; + println!("fpga_id = {}", fpga_id); + + // this will cause the hardware to reset briefly + /* + let inactivity_timer = 0x000186a0u32; // set inactivity timer to 1ms (0x0186a0 * 100MHz) [only later activated on UDP bitstreams] + self.write_config( + 0x0008, + inactivity_timer, + FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READWRITE, + )?; + */ + + let mut device_id = self + .read_config::(0x0008, FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READONLY) + .unwrap_or_default(); + if device_id == 0 { + let magic_pcie = self + .read_config::(0x0000, FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READWRITE) + .unwrap_or_default(); + println!("magic_pcie = {:?}", magic_pcie); + if magic_pcie == 0x6745 { + println!("failed to get device_id - trying to recover via hot reset"); + self.hot_reset_v4().ok(); + device_id = self + .read_config::(0x0008, FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READONLY) + .unwrap_or_default(); + } + } + println!("device_id = {:?}", device_id); + + let (wr, rd) = self.get_phy_v4()?; + println!("wr: {:?}", wr); + println!("rd: {:?}", rd); + + /* + ctx->wDeviceId = _byteswap_ushort(wbsDeviceId); + ctx->phySupported = DeviceFPGA_GetPHYv4(ctx); + */ + + Ok(()) + } + + fn hot_reset_v4(&mut self) -> Result<()> { + trace!("hot resetting the fpga"); + let (wr, _) = self.get_phy_v4()?; + self.write_config(0x0016, wr.0, FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READWRITE)?; + std::thread::sleep(Duration::from_millis(250)); // TODO: poll pl_ltssm_state + timeout with failure + self.write_config(0x0016, wr.0, FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READWRITE)?; + Ok(()) + } + + fn get_phy_v4(&mut self) -> Result<(PhyConfigWr, PhyConfigRd)> { + let wr_raw = + self.read_config::(0x0016, FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READWRITE)?; + let rd_raw = + self.read_config::(0x000a, FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READONLY)?; + Ok((PhyConfigWr { 0: wr_raw }, PhyConfigRd { 0: rd_raw })) + } + + #[allow(clippy::uninit_assumed_init)] + fn read_config(&mut self, addr: u16, flags: u16) -> Result { + let mut obj: T = unsafe { MaybeUninit::uninit().assume_init() }; + self.read_config_into_raw(addr, obj.as_bytes_mut(), flags)?; + Ok(obj) + } + + fn read_config_build_request(addr: u16, bytes: u16, flags: u16) -> Vec { + let mut res = Vec::new(); + for a in (addr..addr + bytes).step_by(2) { + let mut req = [0u8; 8]; + req[4] = ((a | (flags & 0x8000)) >> 8) as u8; + req[5] = (a & 0xff) as u8; + req[6] = (0x10 | (flags & 0x03)) as u8; + req[7] = 0x77; + res.extend_from_slice(&req); + } + res + } + + fn read_config_parse_response( + addr: u16, + respbuf: &[u8], + outbuf: &mut [u8], + flags: u16, + ) -> Result<()> { + let view = respbuf.as_data_view(); + let mut skip = 0; + for i in (0..respbuf.len()).step_by(32) { + if i + skip >= respbuf.len() { + break; + } + + while view.copy::(i + skip) == 0x55556666 { + //trace!("ftdi workaround detected, skipping 4 bytes"); + skip += 4; + if i + skip + 32 > respbuf.len() { + return Err(Error::Connector("out of range config read")); + } + } + + let mut status = view.copy::(i + skip); + if status & 0xf0000000 != 0xe0000000 { + trace!("invalid status reply, skipping"); + } + + trace!("parsing data buffer"); + for j in 0..7 { + let status_flag = (status & 0x0f) == (flags & 0x03) as u32; + status >>= 4; // move to next status + if !status_flag { + //trace!("status source flag does not match source"); + continue; + } + + let data = view.copy::(i + skip + 4 + j * 4); + let mut a = (data as u16).to_be(); // only enforce a byteswap if we are on le + a -= (flags & 0x8000) + addr; + if a >= outbuf.len() as u16 { + trace!("address data out of range, skipping"); + continue; + } + + if a == outbuf.len() as u16 - 1 { + outbuf[a as usize] = ((data >> 16) & 0xff) as u8; + } else { + let b = (((data >> 16) & 0xffff) as u16).to_le_bytes(); + outbuf[a as usize] = b[0]; + outbuf[a as usize + 1] = b[1]; + } + } + } + Ok(()) + } + + fn read_config_into_raw(&mut self, addr: u16, buf: &mut [u8], flags: u16) -> Result<()> { + if buf.is_empty() || buf.len() > size::kb(4) || addr > size::kb(4) as u16 { + return Err(Error::Connector("invalid config address requested")); + } + + let req = Self::read_config_build_request(addr, buf.len() as u16, flags); + + self.ft60.write_pipe(&req)?; + + let mut readbuf = [0u8; size::kb(128)]; + let bytes = self.ft60.read_pipe(&mut readbuf)?; + + Self::read_config_parse_response(addr, &readbuf[..bytes], buf, flags) + } + + fn write_config(&mut self, addr: u16, obj: T, flags: u16) -> Result<()> { + self.write_config_raw(addr, obj.as_bytes(), flags) + } + + fn write_config_raw(&mut self, addr: u16, buf: &[u8], flags: u16) -> Result<()> { + if buf.is_empty() || buf.len() > 0x200 || addr > size::kb(4) as u16 { + return Err(Error::Connector("invalid config address to write")); + } + + let mut outbuf = [0u8; 0x800]; + let mut ptr = 0; + for i in (0..buf.len()).step_by(2) { + let a = (addr + i as u16) | (flags & 0x8000); + outbuf[ptr] = buf[i as usize]; // byte_value_addr + outbuf[ptr + 1] = if buf.len() == i + 1 { + 0 + } else { + buf[i as usize + 1] + }; // byte_value_addr + 1 + outbuf[ptr + 2] = 0xFF; // byte_mask_addr + outbuf[ptr + 3] = if buf.len() == i + 1 { 0 } else { 0xFF }; // byte_mask_addr + 1 + outbuf[ptr + 4] = (a >> 8) as u8; // addr_high = bit[6:0], write_regbank = bit[7] + outbuf[ptr + 5] = (a & 0xFF) as u8; // addr_low + outbuf[ptr + 6] = (0x20 | (flags & 0x03)) as u8; // target = bit[0:1], read = bit[4], write = bit[5] + outbuf[ptr + 7] = 0x77; // MAGIC 0x77 + ptr += 8; + } + + self.ft60.write_pipe(&buf) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem::size_of; + + #[test] + fn test_struct_sizes() { + assert_eq!(size_of::(), 2); + assert_eq!(size_of::(), 4); + } + + #[test] + fn test_config_read_build_request() { + assert_eq!( + Device::read_config_build_request( + 0x0008, + 1, + FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READONLY + ), + [0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x13, 0x77] + ); + assert_eq!( + Device::read_config_build_request( + 0x0009, + 1, + FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READONLY + ), + [0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x13, 0x77] + ); + + assert_eq!( + Device::read_config_build_request( + 0x0008, + 2, + FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READONLY + ), + [0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x11, 0x77] + ); + assert_eq!( + Device::read_config_build_request( + 0x0000, + 2, + FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READWRITE + ), + [0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x11, 0x77] + ); + + assert_eq!( + Device::read_config_build_request( + 0x0016, + 2, + FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READWRITE + ), + [0x0, 0x0, 0x0, 0x0, 0x80, 0x16, 0x11, 0x77] + ); + assert_eq!( + Device::read_config_build_request( + 0x000a, + 4, + FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READONLY + ), + [0x0, 0x0, 0x0, 0x0, 0x0, 0xA, 0x11, 0x77, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC, 0x11, 0x77] + ); + } + + #[test] + fn test_config_parse_version_major() { + let mut version_major = 0u8; + Device::read_config_parse_response( + 0x0008, + &[ + 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, + 85, 85, 243, 255, 255, 239, 0, 8, 4, 2, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ], + version_major.as_bytes_mut(), + FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READONLY, + ) + .unwrap(); + assert_eq!(version_major, 4); + } + + #[test] + fn test_config_parse_version_minor() { + let mut version_minor = 0u8; + Device::read_config_parse_response( + 0x0009, + &[ + 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, + 85, 85, 243, 255, 255, 239, 0, 9, 2, 1, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ], + version_minor.as_bytes_mut(), + FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READONLY, + ) + .unwrap(); + assert_eq!(version_minor, 2); + } + + #[test] + fn test_config_parse_fpga_id() { + let mut fpga_id = 0u8; + Device::read_config_parse_response( + 0x000a, + &[ + 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, + 85, 85, 243, 255, 255, 239, 0, 10, 1, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ], + fpga_id.as_bytes_mut(), + FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READONLY, + ) + .unwrap(); + assert_eq!(fpga_id, 1); + } + + #[test] + fn test_config_parse_device_id() { + let mut fpga_id = 0u8; + Device::read_config_parse_response( + 0x000a, + &[ + 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, + 85, 85, 243, 255, 255, 239, 0, 10, 1, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ], + fpga_id.as_bytes_mut(), + FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READONLY, + ) + .unwrap(); + assert_eq!(fpga_id, 1); + } + + #[test] + fn test_config_parse_phy_wr() { + let mut wr = 0u16; + Device::read_config_parse_response( + 0x0016, + &[ + 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, + 85, 85, 241, 255, 255, 239, 128, 22, 72, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ], + wr.as_bytes_mut(), + FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READWRITE, + ) + .unwrap(); + assert_eq!(wr, 0x48); + } + + #[test] + fn test_config_parse_phy_rd() { + let mut rd = 0u32; + Device::read_config_parse_response( + 0x000a, + &[ + 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, 85, 85, 102, 102, + 85, 85, 17, 255, 255, 239, 0, 10, 25, 8, 0, 12, 28, 0, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ], + rd.as_bytes_mut(), + FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READONLY, + ) + .unwrap(); + assert_eq!(rd, 0x1C0819); + } +} diff --git a/src/ft60x.rs b/src/ft60x.rs new file mode 100644 index 0000000..c74bd71 --- /dev/null +++ b/src/ft60x.rs @@ -0,0 +1,200 @@ +/// This module provides a wrapper around libusb to communicate with a FT60x chip directly. +/// The main interface will be through the chip configuration and read/write pipe functions. +mod chip; +pub use chip::{ChannelConfig, FifoMode, OptionalFeatureSupport}; + +use rusb::{ + request_type, DeviceHandle, DeviceList, Direction, GlobalContext, Recipient, RequestType, +}; + +use std::mem::size_of; +use std::time::Duration; + +use log::{info, trace}; + +use memflow_core::error::{Error, Result}; + +use dataview::Pod; + +pub const FTDI_VENDOR_ID: u16 = 0x0403; +pub const FTDI_FT60X_PRODUCT_ID: u16 = 0x601f; + +// TODO: enum? +pub const FTDI_COMMUNICATION_INTERFACE: u8 = 0x00; +pub const FTDI_DATA_INTERFACE: u8 = 0x01; + +// TODO: enum? +pub const FTDI_ENDPOINT_SESSION_OUT: u8 = 0x01; +pub const FTDI_ENDPOINT_OUT: u8 = 0x02; +pub const FTDI_ENDPOINT_IN: u8 = 0x82; + +pub struct FT60x { + handle: DeviceHandle, +} + +impl FT60x { + /// Tries to open a usb connection to a FT60x chip connected via libusb. + // TODO: handle multiple devices at the same time + pub fn new() -> Result { + let (dev, desc) = DeviceList::new() + .map_err(|_| Error::Connector("unable to get usb device list"))? + .iter() + .filter_map(|dev| match dev.device_descriptor() { + Ok(desc) => Some((dev, desc)), + Err(_) => None, + }) + .find(|(_dev, desc)| { + desc.vendor_id() == FTDI_VENDOR_ID && desc.product_id() == FTDI_FT60X_PRODUCT_ID + }) + .ok_or_else(|| Error::Connector("unable to find ftdi device"))?; + + info!( + "found FTDI device: {}:{} (bus {}, device {})", + desc.vendor_id(), + desc.product_id(), + dev.bus_number(), + dev.address() + ); + + // open handle and reset chip + let mut handle = dev + .open() + .map_err(|_| Error::Connector("unable to open ftdi usb device"))?; + handle + .reset() + .map_err(|_| Error::Connector("unable to reset ftdi device"))?; + + /* + let manufacturer = handle + .read_string_descriptor_ascii(desc.manufacturer_string_index().unwrap_or_default()) + .map_err(|_| Error::Connector("unable to read ftdi manufacturer name"))?; + let product = handle + .read_string_descriptor_ascii(desc.product_string_index().unwrap_or_default()) + .map_err(|_| Error::Connector("unable to read ftdi product name"))?; + let serial = handle + .read_string_descriptor_ascii(desc.serial_number_string_index().unwrap_or_default()) + .map_err(|_| Error::Connector("unable to read ftdi serial number"))?; + info!( + "device: manufacturer={} product={} serial={}", + manufacturer, product, serial + ); + */ + + // check driver state + if handle + .kernel_driver_active(FTDI_COMMUNICATION_INTERFACE) + .map_err(|_| Error::Connector("ftdi driver check failed"))? + { + return Err(Error::Connector( + "ftdi driver is already active on FTDI_COMMUNICATION_INTERFACE", + )); + } + info!("ftdi driver is not active on FTDI_COMMUNICATION_INTERFACE"); + + if handle + .kernel_driver_active(FTDI_DATA_INTERFACE) + .map_err(|_| Error::Connector("ftdi driver check failed"))? + { + return Err(Error::Connector( + "ftdi driver is already active on FTDI_DATA_INTERFACE", + )); + } + info!("ftdi driver is not active on FTDI_DATA_INTERFACE"); + + // claim interfaces + handle + .claim_interface(FTDI_COMMUNICATION_INTERFACE) + .map_err(|_| Error::Connector("unable to claim FTDI_COMMUNICATION_INTERFACE"))?; + handle + .claim_interface(FTDI_DATA_INTERFACE) + .map_err(|_| Error::Connector("unable to claim FTDI_DATA_INTERFACE"))?; + + Ok(Self { handle }) + } + + pub fn abort_pipe(&mut self, pipe_id: u8) -> Result<()> { + // dummy function, only used for ftdi compat + trace!("abort_pipe: {}", pipe_id); + Ok(()) + } + + pub fn set_suspend_timeout(&mut self, timeout: Duration) -> Result<()> { + // dummy function, only used for ftdi compat + trace!("set_suspend_timeout: {:?}", timeout); + Ok(()) + } + + /// Retrieves the FT60x chip configuration + pub fn config(&mut self) -> Result { + let mut buf = vec![0u8; size_of::()]; + self.handle + .read_control( + request_type(Direction::In, RequestType::Vendor, Recipient::Device), + 0xCF, + 1, + 0, + &mut buf, + Duration::from_millis(1000), + ) + .map_err(|_| Error::Connector("unable to get ft60x config"))?; + + // dataview buf to struct + let view = buf.as_data_view(); + Ok(view.copy::(0)) + } + + /// Writes the FT60x chip configuration + pub fn set_config(&mut self, conf: &chip::Config) -> Result<()> { + let bytes = self + .handle + .write_control( + request_type(Direction::Out, RequestType::Vendor, Recipient::Device), + 0xCF, + 0, + 0, + conf.as_bytes(), + Duration::from_millis(1000), + ) + .map_err(|_| Error::Connector("unable to set ft60x config"))?; + if bytes == size_of::() { + Ok(()) + } else { + Err(Error::Connector("unable to set ft60x config")) + } + } + + /// Write to the ft60x chip + pub fn write_pipe(&mut self, data: &[u8]) -> Result<()> { + self.write_bulk_raw(FTDI_ENDPOINT_OUT, data) + } + + /// Read from the ft60x chip + pub fn read_pipe(&mut self, data: &mut [u8]) -> Result { + self.send_read_request(data.len() as u32)?; + self.handle + .read_bulk(FTDI_ENDPOINT_IN, data, Duration::from_millis(1000)) + .map_err(|_| Error::Connector("unable to read from ft60x")) + } + + /// Sends a ControlRequest to issue a read with a given size + fn send_read_request(&mut self, len: u32) -> Result<()> { + let req = chip::ControlRequest::new(1, FTDI_ENDPOINT_IN, 1, len); + self.write_bulk_raw(FTDI_ENDPOINT_SESSION_OUT, req.as_bytes()) + } + + /// Does a raw bulk write to the chip and validates the sent size + fn write_bulk_raw(&self, endpoint: u8, buf: &[u8]) -> Result<()> { + // TODO: customizable write_bulk timeout + let bytes = self + .handle + .write_bulk(endpoint, buf, Duration::from_millis(1000)) + .map_err(|_| Error::Connector("unable to write to ft60x"))?; + if bytes == buf.len() { + Ok(()) + } else { + Err(Error::Connector( + "unable to write the entire buffer to the ft60x", + )) + } + } +} diff --git a/src/ft60x/chip.rs b/src/ft60x/chip.rs new file mode 100644 index 0000000..a19be43 --- /dev/null +++ b/src/ft60x/chip.rs @@ -0,0 +1,110 @@ +use dataview::Pod; + +/// Chip configuration - FIFO Mode +#[allow(unused)] +pub enum FifoMode { + Mode245 = 0, + Mode600 = 1, + Max = 2, +} + +/// Chip configuration - Channel Configuration +#[allow(unused)] +pub enum ChannelConfig { + Config4 = 0, + Config2 = 1, + Config1 = 2, + Config1OutPipe = 3, + Config1InPipe = 4, + Max = 5, +} + +/// Chip configuration - Optional Feature Support +#[allow(unused)] +pub enum OptionalFeatureSupport { + DisableAll = 0, + EnableBatteryCharging = 1, + DisableCancelSessionUnderrun = 2, + EnableNotificationMessageInch1 = 4, + EnableNotificationMessageInch2 = 8, + EnableNotificationMessageInch3 = 0x10, + EnableNotificationMessageInch4 = 0x20, + EnableNotificationMessageInchAll = 0x3C, + DisableUnderrunInch1 = 0x1 << 6, + DisableUnderrunInch2 = 0x1 << 7, + DisableUnderrunInch3 = 0x1 << 8, + DisableUnderrunInch4 = 0x1 << 9, + DisableUnderrunInchAll = 0xF << 6, +} + +/// Chip configuration - Config structure +#[repr(C)] +#[derive(Clone, Pod)] +pub struct Config { + // Device Descriptor + pub vendor_id: i16, + pub product_id: i16, + + // String Descriptors + pub string_descriptors: [i8; 128], + + // Configuration Descriptor + reserved1: i8, + pub power_attributes: i8, + pub power_consumption: i16, + + // Data Transfer Configuration + reserved2: i8, + pub fifo_clock: i8, + pub fifo_mode: i8, + pub channel_config: i8, + + // Optional Feature Support + pub optional_feature_support: i16, + pub battery_charging_gpio_config: i8, + pub flash_eeprom_detection: i8, // Read-only + + // MSIO and GPIO Configuration + pub msio_control: u32, + pub gpio_control: u32, +} + +#[repr(C, packed)] +#[derive(Copy, Clone, Pod)] +pub struct ControlRequest { + pub idx: u32, + pub pipe: u8, + pub cmd: u8, + unknown1: u8, + unknown2: u8, + pub len: u32, + unknown3: u32, + unknown4: u32, +} + +impl ControlRequest { + pub fn new(idx: u32, pipe: u8, cmd: u8, len: u32) -> Self { + Self { + idx, + pipe, + cmd, + unknown1: 0, + unknown2: 0, + len, + unknown3: 0, + unknown4: 0, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem::size_of; + + #[test] + fn test_struct_sizes() { + assert_eq!(size_of::(), 0x98); + assert_eq!(size_of::(), 0x14); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c2d9ec8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,40 @@ +mod fpga; +mod ft60x; + +use memflow_core::connector::ConnectorArgs; +use memflow_core::*; +use memflow_derive::connector; + +#[allow(unused)] +pub struct PciLeech { + device: fpga::Device, +} + +impl PciLeech { + pub fn new() -> Result { + let mut device = fpga::Device::new()?; + device.get_version()?; + Ok(Self { device }) + } +} + +impl PhysicalMemory for PciLeech { + fn phys_read_raw_list(&mut self, _data: &mut [PhysicalReadData]) -> Result<()> { + Err(Error::Connector( + "memflow_pcileech::phys_read_iter not implemented", + )) + } + + fn phys_write_raw_list(&mut self, _data: &[PhysicalWriteData]) -> Result<()> { + Err(Error::Connector( + "memflow_pcileech::phys_write_iter not implemented", + )) + } +} + +// TODO: handle args properly +/// Creates a new Pcileech Connector instance. +#[connector(name = "pcileech")] +pub fn create_connector(_args: &ConnectorArgs) -> Result { + PciLeech::new() +}