Moved to seperate project from memflow
commit
7cfbf8dbe6
@ -0,0 +1,5 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
*.swp
|
||||||
|
.vscode
|
||||||
|
Cargo.lock
|
@ -0,0 +1,32 @@
|
|||||||
|
[package]
|
||||||
|
name = "memflow-pcileech"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["ko1N <ko1N1337@gmail.com>"]
|
||||||
|
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"
|
@ -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.
|
@ -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();
|
||||||
|
}
|
@ -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<Self> {
|
||||||
|
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::<u8>(0x0008, FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READONLY)?;
|
||||||
|
println!("version_major = {}", version_major);
|
||||||
|
let version_minor =
|
||||||
|
self.read_config::<u8>(0x0009, FPGA_CONFIG_CORE | FPGA_CONFIG_SPACE_READONLY)?;
|
||||||
|
println!("version_minor = {}", version_minor);
|
||||||
|
let fpga_id =
|
||||||
|
self.read_config::<u8>(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::<u16>(0x0008, FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READONLY)
|
||||||
|
.unwrap_or_default();
|
||||||
|
if device_id == 0 {
|
||||||
|
let magic_pcie = self
|
||||||
|
.read_config::<u16>(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::<u16>(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::<u16>(0x0016, FPGA_CONFIG_PCIE | FPGA_CONFIG_SPACE_READWRITE)?;
|
||||||
|
let rd_raw =
|
||||||
|
self.read_config::<u32>(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<T: Pod>(&mut self, addr: u16, flags: u16) -> Result<T> {
|
||||||
|
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<u8> {
|
||||||
|
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::<u32>(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::<u32>(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::<u32>(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<T: Pod>(&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::<PhyConfigWr>(), 2);
|
||||||
|
assert_eq!(size_of::<PhyConfigRd>(), 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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<GlobalContext>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Self> {
|
||||||
|
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<chip::Config> {
|
||||||
|
let mut buf = vec![0u8; size_of::<chip::Config>()];
|
||||||
|
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::<chip::Config>(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::<chip::Config>() {
|
||||||
|
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<usize> {
|
||||||
|
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",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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::<Config>(), 0x98);
|
||||||
|
assert_eq!(size_of::<ControlRequest>(), 0x14);
|
||||||
|
}
|
||||||
|
}
|
@ -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<Self> {
|
||||||
|
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> {
|
||||||
|
PciLeech::new()
|
||||||
|
}
|
Loading…
Reference in New Issue