use memmap::{MmapMut, MmapOptions};
use std::fs::OpenOptions;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::time::Duration;
use log::{debug, error};
use crate::{Bridge, BridgeConfig, BridgeError};
#[derive(Clone)]
pub struct PCIeBridge {
path: PathBuf,
}
impl PCIeBridge {
pub fn new<P: AsRef<Path>>(path: P) -> Result<PCIeBridge, BridgeError> {
if !path.as_ref().exists() {
return Err(BridgeError::InvalidAddress);
}
Ok(PCIeBridge {
path: path.as_ref().to_path_buf(),
})
}
pub fn create(&self) -> Result<Bridge, BridgeError> {
Bridge::new(BridgeConfig::PCIeBridge(self.clone()))
}
}
impl From<&str> for PCIeBridge {
fn from(f: &str) -> Self {
PCIeBridge {
path: PathBuf::from(f),
}
}
}
pub struct PCIeBridgeInner {
path: PathBuf,
main_tx: Sender<ConnectThreadRequests>,
main_rx: Arc<(Mutex<Option<ConnectThreadResponses>>, Condvar)>,
mutex: Arc<Mutex<()>>,
poll_thread: Option<thread::JoinHandle<()>>,
}
enum ConnectThreadRequests {
StartPolling(PathBuf ),
Exit,
Poke(u32 , u32 ),
Peek(u32 ),
}
#[derive(Debug)]
enum ConnectThreadResponses {
Exiting,
OpenedDevice,
PeekResult(Result<u32, BridgeError>),
PokeResult(Result<(), BridgeError>),
}
fn mmap_mut_path(path: &Path) -> MmapMut {
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.expect("Couldn't open PCIe BAR");
unsafe {
MmapOptions::new()
.map_mut(&file)
.expect("Couldn't mmap PCIe BAR")
}
}
impl Clone for PCIeBridgeInner {
fn clone(&self) -> Self {
PCIeBridgeInner {
path: self.path.clone(),
main_tx: self.main_tx.clone(),
main_rx: self.main_rx.clone(),
mutex: self.mutex.clone(),
poll_thread: None,
}
}
}
impl PCIeBridgeInner {
pub fn new(cfg: &PCIeBridge) -> Result<Self, BridgeError> {
let (main_tx, thread_rx) = channel();
let cv = Arc::new((Mutex::new(None), Condvar::new()));
let path = cfg.path.clone();
let thr_cv = cv.clone();
let thr_path = path.clone();
let poll_thread = Some(thread::spawn(move || {
Self::pcie_thread(thr_cv, thread_rx, thr_path)
}));
Ok(PCIeBridgeInner {
path,
main_tx,
main_rx: cv,
mutex: Arc::new(Mutex::new(())),
poll_thread,
})
}
fn pcie_thread(
tx: Arc<(Mutex<Option<ConnectThreadResponses>>, Condvar)>,
rx: Receiver<ConnectThreadRequests>,
mut path: PathBuf,
) {
let mut first_run = true;
let &(ref response, ref cvar) = &*tx;
loop {
let mut mem = mmap_mut_path(&path);
if first_run {
*response.lock().unwrap() = Some(ConnectThreadResponses::OpenedDevice);
first_run = false;
cvar.notify_one();
}
let mut keep_going = true;
let mut result_error = "".to_owned();
while keep_going {
let var = rx.recv();
match var {
Err(_) => {
error!("connection closed");
return;
}
Ok(o) => match o {
ConnectThreadRequests::Exit => {
debug!("pcie_thread requested exit");
*response.lock().unwrap() = Some(ConnectThreadResponses::Exiting);
cvar.notify_one();
return;
}
ConnectThreadRequests::StartPolling(b) => {
path = b;
}
ConnectThreadRequests::Peek(addr) => {
let result = Self::do_peek_32(&mut mem, addr);
if let Err(err) = &result {
result_error = format!("peek {:?} @ {:08x}", err, addr);
keep_going = false;
}
*response.lock().unwrap() =
Some(ConnectThreadResponses::PeekResult(result));
cvar.notify_one();
}
ConnectThreadRequests::Poke(addr, val) => {
let result = Self::do_poke_32(&mut mem, addr, val);
if let Err(err) = &result {
result_error = format!("poke {:?} @ {:08x}", err, addr);
keep_going = false;
}
*response.lock().unwrap() =
Some(ConnectThreadResponses::PokeResult(result));
cvar.notify_one();
}
},
}
}
error!("pcie connection was closed: {}", result_error);
thread::park_timeout(Duration::from_millis(500));
loop {
match rx.try_recv() {
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => panic!("main thread disconnected"),
Ok(m) => match m {
ConnectThreadRequests::Exit => {
*response.lock().unwrap() = Some(ConnectThreadResponses::Exiting);
cvar.notify_one();
debug!("main thread requested exit");
return;
}
ConnectThreadRequests::Peek(_addr) => {
*response.lock().unwrap() = Some(ConnectThreadResponses::PeekResult(
Err(BridgeError::NotConnected),
));
cvar.notify_one();
}
ConnectThreadRequests::Poke(_addr, _val) => {
*response.lock().unwrap() = Some(ConnectThreadResponses::PokeResult(
Err(BridgeError::NotConnected),
));
cvar.notify_one();
}
ConnectThreadRequests::StartPolling(p) => {
path = p;
}
},
}
}
}
}
pub fn mutex(&self) -> &Arc<Mutex<()>> {
&self.mutex
}
pub fn connect(&self) -> Result<(), BridgeError> {
self.main_tx
.send(ConnectThreadRequests::StartPolling(self.path.clone()))
.unwrap();
loop {
let &(ref lock, ref cvar) = &*self.main_rx;
let mut _mtx = lock.lock().unwrap();
*_mtx = None;
while _mtx.is_none() {
_mtx = cvar.wait(_mtx).unwrap();
}
if let Some(ConnectThreadResponses::OpenedDevice) = _mtx.take() {
return Ok(());
}
}
}
fn do_poke_32(mem: &mut MmapMut, addr: u32, value: u32) -> Result<(), BridgeError> {
debug!("POKE @ {:08x} -> {:08x}", addr, value);
#[allow(clippy::cast_ptr_alignment)]
let memory_range = mem.as_mut_ptr() as *mut u32;
unsafe { memory_range.add(addr as usize / 4).write_volatile(value) };
Ok(())
}
fn do_peek_32(mem: &mut MmapMut, addr: u32) -> Result<u32, BridgeError> {
#[allow(clippy::cast_ptr_alignment)]
let memory_range = mem.as_mut_ptr() as *mut u32;
let val = unsafe { memory_range.add(addr as usize / 4).read_volatile() };
debug!("PEEK @ {:08x} = {:08x}", addr, val);
Ok(val)
}
pub fn poke(&self, addr: u32, value: u32) -> Result<(), BridgeError> {
let &(ref lock, ref cvar) = &*self.main_rx;
let mut _mtx = lock.lock().unwrap();
self.main_tx
.send(ConnectThreadRequests::Poke(addr, value))
.expect("Unable to send poke to connect thread");
*_mtx = None;
while _mtx.is_none() {
_mtx = cvar.wait(_mtx).unwrap();
}
match _mtx.take() {
Some(ConnectThreadResponses::PokeResult(r)) => Ok(r?),
e => {
error!("unexpected bridge poke response: {:?}", e);
Err(BridgeError::WrongResponse)
}
}
}
pub fn peek(&self, addr: u32) -> Result<u32, BridgeError> {
let &(ref lock, ref cvar) = &*self.main_rx;
let mut _mtx = lock.lock().unwrap();
self.main_tx
.send(ConnectThreadRequests::Peek(addr))
.expect("Unable to send peek to connect thread");
*_mtx = None;
while _mtx.is_none() {
_mtx = cvar.wait(_mtx).unwrap();
}
match _mtx.take() {
Some(ConnectThreadResponses::PeekResult(r)) => Ok(r?),
e => {
error!("unexpected bridge peek response: {:?}", e);
Err(BridgeError::WrongResponse)
}
}
}
}
impl Drop for PCIeBridgeInner {
fn drop(&mut self) {
let sc = Arc::strong_count(&self.mutex);
let wc = Arc::weak_count(&self.mutex);
debug!("strong count: {} weak count: {}", sc, wc);
if (sc + wc) <= 1 {
let &(ref lock, ref cvar) = &*self.main_rx;
let mut mtx = lock.lock().unwrap();
self.main_tx
.send(ConnectThreadRequests::Exit)
.expect("Unable to send Exit request to thread");
*mtx = None;
while mtx.is_none() {
mtx = cvar.wait(mtx).unwrap();
}
match mtx.take() {
Some(ConnectThreadResponses::Exiting) => (),
e => {
error!("unexpected bridge exit response: {:?}", e);
}
}
if let Some(pt) = self.poll_thread.take() {
pt.join().expect("Unable to join polling thread");
}
}
}
}