feat(hpu): Add support for tandem pdi

Add support for hpu archive and Fpga reloading.
Rely on Tandem implementation for hot-reloading of FPGA.
Add reload procedure inside ffi/v80 backend.

Now when starting application on HpuV80, a first check of version is done.
If version mismatch, a pdi reload is triggered
This commit is contained in:
Baptiste Roux
2025-06-12 12:10:11 +02:00
committed by B. Roux
parent dcd1af72d4
commit 16c997d686
19 changed files with 1215 additions and 70 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.hpu filter=lfs diff=lfs merge=lfs -text

View File

@@ -12,10 +12,10 @@ keywords = ["encryption", "fhe", "cryptography", "hardware", "fpga"]
[features]
hw-xrt = []
hw-v80 = []
hw-v80 = ["bincode"]
io-dump = ["num-traits"]
rtl_graph = ["dot2"]
utils = ["clap", "clap-num", "bitvec", "serde_json"]
utils = ["clap", "clap-num", "bitvec", "serde_json", "bincode", "serde_derive"]
[build-dependencies]
cxx-build = "1.0"
@@ -52,7 +52,7 @@ ipc-channel = "0.18.3"
num-traits = { version = "0.2", optional = true }
clap = { version = "4.4.4", features = ["derive"], optional = true }
clap-num = { version = "1.1.1", optional = true }
nix = { version = "0.29.0", features = ["ioctl", "uio"] }
nix = { version = "0.29.0", features = ["ioctl", "uio", "fs"] }
# Dependencies used for rtl_graph features
dot2 = { version = "1.0", optional = true }
@@ -60,6 +60,10 @@ dot2 = { version = "1.0", optional = true }
bitvec = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
# Dependencies used for v80 pdi handling
bincode ={ version = "1.3", optional = true}
serde_derive ={ version = "1.0", optional = true}
# Binary for manual debugging
# Enable to access Hpu register and drive some custom sequence by hand
[[bin]]
@@ -67,6 +71,11 @@ name = "hputil"
path = "src/utils/hputil.rs"
required-features = ["utils"]
[[bin]]
name = "pdi_mgmt"
path = "src/utils/pdi_mgmt.rs"
required-features = ["utils", "hw-v80"]
# Binary for asm manipulation
# Enable to convert back and forth between asm/hex format
[[bin]]

View File

@@ -200,7 +200,7 @@ There are some example applications already available in `tfhe/examples/hpu`:
In order to run those applications on hardware, user must build from the project root (i.e `tfhe-rs-internal`) with `hpu-v80` features:
> NB: Running examples required to have correctly pulled the `.pdi` files. Those files, due to their size, are backed by git-lfs and disabled by default.
> NB: Running examples required to have correctly pulled the `.hpu` files. Those files, due to their size, are backed by git-lfs and disabled by default.
> In order to retrieve them, use the following command:
> ```bash
> git lfs pull --include="*" --exclude=""
@@ -209,11 +209,18 @@ In order to run those applications on hardware, user must build from the project
``` bash
cargo build --release --features="hpu-v80" --example hpu_hlapi --example hpu_bench
# Correctly setup environment with setup_hpu.sh script
source setup_hpu.sh --config v80 --init-qdma
source setup_hpu.sh --config v80
./target/release/examples/hpu_bench --integer-w 64 --integer-w 32 --iop MUL --iter 10
./target/release/examples/hpu_hlapi
```
> NB: Error that occurred when ".hpu" files weren't correctly fetch could be a bit enigmatic: `memory allocation of ... bytes failed`
> If you encountered this issue, you should run the following command:
> ```bash
> git lfs pull --include="*" --exclude=""
> ```
## Test framework
There is also a set of tests backed in tfhe-rs. Tests are gather in testbundle over various integer width.
Those tests have 5 sub-kind:

View File

@@ -6,7 +6,9 @@
"${HPU_BACKEND_DIR}/config_store/${HPU_CONFIG}/hpu_regif_core_prc_3in3.toml"]
polling_us=10
[fpga.ffi.V80]
ami_id=1 # First ami device in the list
id= 0
hpu_path="${HPU_BACKEND_DIR}/config_store/v80_archives/psi64.hpu"
ami_path="${AMI_PATH}/ami.ko"
qdma_h2c="/dev/qdma${V80_PCIE_DEV}001-MM-1"
qdma_c2h="/dev/qdma${V80_PCIE_DEV}001-MM-2"

View File

@@ -0,0 +1,28 @@
# Hpu archives
This folder contains a set of custom archive used to reload the FPGA.
This archives contains pdi files and other metadata.
A tool `pdi_mgmt` is provided to create/expand archives.
## How to build `pdi_mgmt`
Simply go in tfhe-hpu-backend folder and use cargo:
```
cargo build --release --features hw-v80,utils
```
## How to unpack an archive for inspection
For this purpose use `pdi_mgmt`:
```
./target/devo/pdi_mgmt unpack backends/tfhe-hpu-backend/config_store/v80_pdi/psi64.hpu backends/tfhe-hpu-backend/config_store/v80_pdi/psi64
```
## How to pack an archive after update
For example, if you have previously unpack the psi64.hpu, you can use the following command to pack it back:
```
./target/devo/pdi_mgmt pack backends/tfhe-hpu-backend/config_store/v80_pdi/psi64 backends/tfhe-hpu-backend/config_store/v80_pdi/psi64.hpu
```

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d5f578ec0cbcd1525fc88dc57fff1a2384fa742a147f69b6a9c77deafc0601fe
size 33348376

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb9ebedd0987130c4f6e1ef09f279d92f083815c1383da4b257198a33ab4881e
size 80293531

View File

@@ -0,0 +1,16 @@
set TOP_NAME [lindex $::argv 0]
open_hw_manager
connect_hw_server -allow_non_jtag
open_hw_target
current_hw_device [get_hw_devices xcv80_1]
refresh_hw_device -update_hw_probes false [lindex [get_hw_devices xcv80_1] 0]
set_property PROBES.FILE {} [get_hw_devices xcv80_1]
set_property FULL_PROBES.FILE {} [get_hw_devices xcv80_1]
# stage 1 programming
set_property PROGRAM.FILE $TOP_NAME [get_hw_devices xcv80_1]
program_hw_devices [get_hw_devices xcv80_1]
refresh_hw_device [lindex [get_hw_devices xcv80_1] 0]

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
GROUP=hw;
# Enable Pcie rescan
if [ -e /sys/bus/pci/rescan ]; then
chgrp $GROUP /sys/bus/pci/rescan && chmod g+w /sys/bus/pci/rescan
fi
# Search V80 pcie boards PF0
for dev in $(lspci -nn -d 10ee:50b4 | awk '{print $1}'); do
chgrp -R $GROUP /sys/bus/pci/devices/0000\:$dev/
chmod -R g=u /sys/bus/pci/devices/0000\:$dev/
done
# Search V80 pcie boards PF1
for dev in $(lspci -nn -d 10ee:50b5 | awk '{print $1}'); do
chgrp -R $GROUP /sys/bus/pci/devices/0000\:$dev/
chmod -R g=u /sys/bus/pci/devices/0000\:$dev/
done

View File

@@ -168,11 +168,15 @@ impl HpuHw {
{
match mode {
FFIMode::V80 {
ami_id,
id,
hpu_path,
ami_path,
qdma_h2c,
qdma_c2h,
} => Self(v80::HpuHw::new_hpu_hw(
*ami_id,
*id,
&hpu_path.expand(),
&ami_path.expand(),
retry_rate,
&qdma_h2c.expand(),
&qdma_c2h.expand(),
@@ -321,6 +325,9 @@ impl MemZone {
#[cfg(feature = "hw-v80")]
mod v80;
#[cfg(all(feature = "hw-v80", feature = "utils"))]
pub use v80::HpuV80Pdi;
#[cfg(feature = "hw-xrt")]
mod xrt;

View File

@@ -3,8 +3,9 @@
//! AMI driver is used to issue gcq command to the RPU
//! Those command are used for configuration and register R/W
use lazy_static::lazy_static;
use std::error::Error;
use std::fs::{File, OpenOptions};
use std::io::Read;
use std::io::{BufRead, BufReader, Read};
use std::os::fd::AsRawFd;
use std::time::Duration;
@@ -15,7 +16,64 @@ const AMI_ID_FILE: &str = "/sys/bus/pci/drivers/ami/devices";
const AMI_ID_PATTERN: &str = r"(?<pci>\d{2}:\d{2}\.\d)\s(?<dev_id>\d+)\s\d+";
const HIS_VERSION_FILE: &str = "/sys/bus/pci/devices/0000:${V80_PCIE_DEV}:00.0/amc_version";
const HIS_VERSION_PATTERN: &str = r".*- zama ucore 2.0";
const HIS_VERSION_PATTERN: &str = r".*- zama ucore (?<major>\d+).(?<minor>\d+)";
use crate::ffi::v80::pdi::metadata::Version;
use crate::ffi::v80::pdi::uuid::AMI_UUID_WORDS;
const AMI_UUID_BAR_OFFSET: u64 = 0x1001000;
const AMI_DEVICES_MAP: &'static str = "/sys/bus/pci/drivers/ami/devices";
// NB: Some field available in the driver file were never used
#[allow(dead_code)]
pub struct AmiInfo {
bus_id: usize,
dev_id: usize,
func_id: usize,
devn: usize,
hwmon: usize,
}
/// Set of discovery function
/// Enable to probe the device IDs and status
impl AmiInfo {
pub fn new() -> Result<Self, Box<dyn Error>> {
// First read content of AMI_DEVICES_MAP
let devices_file = OpenOptions::new()
.read(true)
.create(false)
.open(AMI_DEVICES_MAP)?;
let devices_rd = BufReader::new(devices_file);
// TODO handle multi-devices cases
let line = devices_rd.lines().nth(1).ok_or("No device found")??;
// Second extract formatted information from string
lazy_static! {
static ref AMI_DEVICES_RE: regex::Regex = regex::Regex::new(
r"(?<bus>[[:xdigit:]]{2}):(?<dev>[[:xdigit:]]{2})\.(?<func>[[:xdigit:]])\s(?<devn>\d+)\s(?<hwmon>\d+)"
)
.expect("Invalid regex");
}
let caps = AMI_DEVICES_RE
.captures(&line)
.ok_or("Invalid AMI_DEVICES_MAP format")?;
let bus_id = usize::from_str_radix(&caps["bus"], 16)?;
let dev_id = usize::from_str_radix(&caps["dev"], 16)?;
let func_id = usize::from_str_radix(&caps["func"], 16)?;
let devn = caps["devn"].parse::<usize>()?;
let hwmon = caps["hwmon"].parse::<usize>()?;
Ok(Self {
bus_id,
dev_id,
func_id,
devn,
hwmon,
})
}
}
pub struct AmiDriver {
ami_dev: File,
@@ -23,8 +81,14 @@ pub struct AmiDriver {
}
impl AmiDriver {
pub fn new(ami_id: usize, retry_rate: Duration) -> Self {
Self::check_version();
pub fn new(
ami_id: u32,
amc_ver: &Version,
retry_rate: Duration,
) -> Result<Self, Box<dyn Error>> {
Self::check_version(amc_ver)?;
// Read Ami info
let ami_info = AmiInfo::new()?;
// Read ami_id_file to get ami device
let ami_path = {
@@ -38,7 +102,7 @@ impl AmiDriver {
let ami_id_f = std::fs::read_to_string(AMI_ID_FILE).expect("Invalid ami_id filepath");
let id_line = ami_id_f
.lines()
.nth(ami_id)
.nth(ami_info.devn)
.unwrap_or_else(|| panic!("Invalid ami id {ami_id}."));
let id_str = AMI_ID_RE
@@ -51,24 +115,60 @@ impl AmiDriver {
format!("/dev/ami{dev_id}")
};
// Open ami device file
let ami_dev = OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(&ami_path)
.unwrap();
Self {
.open(ami_path)?;
Ok(Self {
ami_dev,
retry_rate,
})
}
/// Read currently loaded UUID in BAR
pub fn uuid(&self) -> String {
let ami_fd = self.ami_dev.as_raw_fd();
// Allocate heap memory for read value
let uuid = Box::new([0_u32; AMI_UUID_WORDS]);
let data_ptr = Box::into_raw(uuid);
// Populate payload
let mut payload = AmiBarPayload {
num: AMI_UUID_WORDS as u32,
data_ptr: data_ptr as *mut u32,
bar_idx: 0,
offset: AMI_UUID_BAR_OFFSET,
cap_override: true,
};
tracing::trace!("AMI: Read request with following payload {payload:x?}");
loop {
let ret = unsafe { ami_bar_read(ami_fd, &mut payload) };
match ret {
Err(err) => {
tracing::debug!("AMI: Read failed -> {err:?}");
std::thread::sleep(self.retry_rate);
}
Ok(val) => {
tracing::trace!("AMI: Read ack received {payload:x?} -> {val:?}");
break;
}
}
}
let uuid = unsafe { *Box::from_raw(data_ptr) };
uuid.iter()
.rev()
.fold(String::new(), |acc, w| acc + &format!("{w:0>8x}"))
}
/// Check if current ami version is compliant
///
/// For this purpose we use a regex.
/// it's easy to expressed and understand breaking rules with it
pub fn check_version() {
pub fn check_version(amc_ver: &Version) -> Result<(), Box<dyn Error>> {
// Check AMI version
lazy_static! {
static ref AMI_VERSION_RE: regex::Regex =
@@ -81,7 +181,7 @@ impl AmiDriver {
.write(false)
.create(false)
.open(AMI_VERSION_FILE)
.unwrap();
.map_err(|err| format!("Opening file {AMI_VERSION_FILE} failed: {err:?}"))?;
let ami_version = {
let mut ver = String::new();
@@ -93,10 +193,11 @@ impl AmiDriver {
};
if !AMI_VERSION_RE.is_match(&ami_version) {
panic!(
return Err(format!(
"Invalid ami version. Get {} expect something matching pattern {}",
ami_version, AMI_VERSION_PATTERN
)
.into());
}
// Check HIS version
@@ -114,23 +215,38 @@ impl AmiDriver {
.write(false)
.create(false)
.open(his_version_file.expand())
.unwrap();
.unwrap_or_else(|err| {
panic!("Opening file {} failed: {err:?}", his_version_file.expand())
});
let his_version = {
let mut ver = String::new();
his_ver_f
.read_to_string(&mut ver)
.expect("Invalid HIS_VERSION string format");
.map_err(|e| format!("Invalid HIS_VERSION string format: {e:?}"))?;
ver
};
if !HIS_VERSION_RE.is_match(&his_version) {
panic!(
"Invalid his version. Get {} expect something matching pattern {}",
his_version, HIS_VERSION_PATTERN
let caps = HIS_VERSION_RE
.captures(&his_version)
.ok_or("Invalid his version format")?;
let amc_major = caps["major"].parse::<u32>()?;
let amc_minor = caps["minor"].parse::<u32>()?;
if amc_major != amc_ver.major {
return Err(format!(
"Invalid his major version. Get {} expect {}",
amc_major, amc_ver.major
)
.into());
}
if amc_minor != amc_ver.minor {
return Err(format!(
"Invalid his minor version. Get {} expect {}",
amc_minor, amc_ver.minor
)
.into());
}
Ok(())
}
/// Issue read register request through AMI driver
@@ -292,6 +408,40 @@ impl AmiDriver {
// Define driver IOCTL command and associated payload -------------------------
const AMI_IOC_MAGIC: u8 = b'a';
// Bar Read/Write command used for PCIe device probing ------------------------
const AMI_BAR_READ_CMD: u8 = 1;
// const AMI_BAR_WRITE_CMD: u8 = 2;
/// Payload used for PCI BAR registers read/write
/// Args:
/// * num: Number of BAR registers (to read or write).
/// * data_ptr: Userspace address of data payload (read or write).
/// * bar_idx: Bar number.
/// * offset: Offset within BAR.
/// * cap_override: Bypass permission checks. This may not apply to all IOCTL's.
///
/// Note:
/// For reading a BAR, `addr` is the userspace address of a u32 buffer to be
/// populated with data read from the BAR and `num` is the number of values to read.
/// To write to a BAR, `addr` is the userspace address of the u32 buffer to
/// write and `num` is the number of values to write.
#[derive(Debug)]
#[repr(C)]
struct AmiBarPayload {
num: u32,
data_ptr: *mut u32,
bar_idx: u8,
offset: u64,
cap_override: bool,
}
nix::ioctl_readwrite!(ami_bar_read, AMI_IOC_MAGIC, AMI_BAR_READ_CMD, AmiBarPayload);
// nix::ioctl_write_ptr!(
// ami_bar_write,
// AMI_IOC_MAGIC,
// AMI_BAR_WRITE_CMD,
// AmiBarPayload
// );
// Peak/Poke command used for Read/Write in registers -------------------------
const AMI_PEAK_CMD: u8 = 15;
const AMI_POKE_CMD: u8 = 16;

View File

@@ -5,17 +5,28 @@
//! * Data xfer -> QDMA
use crate::ffi;
use crate::prelude::ShellString;
use std::error::Error;
use std::fs::OpenOptions;
use std::io::Write;
use std::process::Command;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
mod ami;
use ami::AmiDriver;
mod pdi;
pub use pdi::HpuV80Pdi;
mod mem_alloc;
use mem_alloc::{MemAlloc, MemChunk};
mod qdma;
use qdma::QdmaDriver;
use rand::Rng;
const DMA_XFER_ALIGN: usize = 4096_usize;
pub struct HpuHw {
pub(super) ami: AmiDriver,
@@ -25,16 +36,312 @@ pub struct HpuHw {
impl HpuHw {
/// Handle ffi instantiation
/// Instantiate current HW and check uuid. If it match with targeted one continue,
/// otherwise reload Pdi
#[inline(always)]
pub fn new_hpu_hw(
ami_id: usize,
id: u32,
hpu_path: &str,
ami_path: &str,
ami_retry: std::time::Duration,
h2c_path: &str,
c2h_path: &str,
) -> HpuHw {
// Load Pdi archive
let hpu_pdi = HpuV80Pdi::from_bincode(hpu_path)
.unwrap_or_else(|err| panic!("Invalid \'.hpu\' {hpu_path:?}: {err}"));
// Try current hw and fallback to a fresh reload
Self::try_current_hw(id, &hpu_pdi, ami_retry, h2c_path, c2h_path).unwrap_or_else(|err| {
tracing::warn!("Loading current HW failed with {err:?}. Will do a fresh reload");
Self::reload_hw(id, &hpu_pdi, ami_path, ami_retry, h2c_path, c2h_path)
})
}
/// Load a Pdi in the FPGA
/// NB: This procedure required unload of Qdma/Ami driver and thus couldn't be directly
/// implemented in the AMI
fn try_current_hw(
id: u32,
pdi: &HpuV80Pdi,
ami_retry: std::time::Duration,
h2c_path: &str,
c2h_path: &str,
) -> Result<Self, Box<dyn Error>> {
let ami = AmiDriver::new(id, &pdi.metadata.amc.his_version, ami_retry)?;
let qdma = QdmaDriver::new(h2c_path, c2h_path)?;
let hw = Self {
ami,
qdma: Arc::new(Mutex::new(qdma)),
allocator: None,
};
let current_uuid = hw.ami.uuid();
if pdi.metadata.bitstream.uuid == current_uuid {
let uuid = pdi::V80Uuid::from_str(&pdi.metadata.bitstream.uuid)
.expect("Invalid UUID format in pdi");
tracing::info!("Current pdi -> [\n{uuid}]");
Ok(hw)
} else {
Err(format!(
"UUID mismatch loaded {:?} expected {:?}",
current_uuid, pdi.metadata.bitstream.uuid
)
.into())
}
}
/// Load a Pdi in the FPGA
/// NB: This procedure required unload of Qdma/Ami driver and thus couldn't be directly
/// implemented in the AMI
fn reload_hw(
id: u32,
pdi: &HpuV80Pdi,
ami_path: &str,
ami_retry: std::time::Duration,
h2c_path: &str,
c2h_path: &str,
) -> Self {
tracing::warn!("FPGA reload procedure. Following step require sudo rights to handle modules loading and pcie subsystem configuration.");
let uuid = pdi::V80Uuid::from_str(&pdi.metadata.bitstream.uuid)
.expect("Invalid UUID format in pdi");
tracing::info!("Load pdi -> [\n{uuid}]");
tracing::debug!("Unload drivers ami/qdma_pf");
//Prerequist. Enforce that ami/qdma driver are unloaded
// NB: Separate the call to match sudoers rules
let _ = Command::new("sudo")
.arg("/usr/sbin/rmmod")
.arg("ami")
.status();
let _ = Command::new("sudo")
.arg("/usr/sbin/rmmod")
.arg("qdma_pf")
.status();
// 1. Load PDI stage one through JTAG ----------------------------------
// -> Use Xilinx hw_manager. Currently used through vivado for ease setup.
// hw manager expect stage 1 in a file, thus start by expanding the stg1_pdi in a tmp file
tracing::debug!("Load stage1 through JTAG");
let pdi_stg1_tmp = format!(
"hpu_stg1_{}.pdi",
rand::thread_rng()
.sample_iter(rand::distributions::Alphanumeric)
.take(5)
.map(char::from)
.collect::<String>()
);
let tmp_dir = std::env::temp_dir();
let tmp_dir_str = tmp_dir.to_str().expect("TEMP_DIR is not a valid UTF-8");
// Write the binary data
HpuV80Pdi::write_to_path(tmp_dir_str, &pdi_stg1_tmp, &pdi.pdi_stg1_bin)
.expect("Error while expanding stg1 pdi on filesystem");
let hw_monitor =
Command::new(ShellString::new("${XILINX_VIVADO}/bin/vivado".to_string()).expand())
.arg("-mode")
.arg("batch")
.arg("-source")
.arg(
ShellString::new("${HPU_BACKEND_DIR}/scripts/pdi_jtag.tcl".to_string())
.expand(),
)
.arg("-tclargs")
.arg(format!("{}/{}", tmp_dir_str, &pdi_stg1_tmp))
.output();
tracing::debug!("Stage1 loaded: {hw_monitor:?}");
// Update right on V80 pcie subsystem
Command::new("sudo")
.arg(
ShellString::new("${HPU_BACKEND_DIR}/scripts/v80-pcie-perms.sh".to_string())
.expand(),
)
.status()
.expect("Unable to update v80 pcie sysfs right");
tracing::debug!(" Updating Pcie physical functions status");
let rm_pf0 = OpenOptions::new()
.write(true)
.open(
ShellString::new(
"/sys/bus/pci/devices/0000:${V80_PCIE_DEV}:00.0/remove".to_string(),
)
.expand(),
)
.ok();
if let Some(mut pf0) = rm_pf0 {
pf0.write_all(b"1\n")
.expect("Unable to triggered a remove of pci pf0");
} else {
tracing::debug!("Pcie PF0 not present.");
}
OpenOptions::new()
.write(true)
.open(
ShellString::new(
"/sys/bus/pci/devices/0000:${V80_PCIE_DEV}:00.1/remove".to_string(),
)
.expand(),
)
.expect("Unable to open pci remove cmd file")
.write_all(b"1\n")
.expect("Unable to triggered a remove of pci pf1");
OpenOptions::new()
.write(true)
.open("/sys/bus/pci/rescan")
.expect("Unable to open pci rescan cmd file")
.write_all(b"1\n")
.expect("Unable to triggered a pci rescan");
// Update right on V80 pcie subsystem
// NB: sysfs is recreated upon rescan
Command::new("sudo")
.arg(
ShellString::new("${HPU_BACKEND_DIR}/scripts/v80-pcie-perms.sh".to_string())
.expand(),
)
.status()
.expect("Unable to update v80 pcie sysfs right");
// 2. Load PDI stage two through QDMA ----------------------------------
tracing::debug!("Load stage2 through Qdma");
// Create h2c queue at idx 0
tracing::debug!("Initializing queues");
OpenOptions::new()
.write(true)
.open(
ShellString::new(
"/sys/bus/pci/devices/0000:${V80_PCIE_DEV}:00.1/qdma/qmax".to_string(),
)
.expand(),
)
.expect("Unable to open qdma qmax cmd file")
.write_all(b"100\n")
.expect("Unable to configure Qdma max queues");
Command::new("dma-ctl")
.arg("qdma01001")
.arg("q")
.arg("add")
.arg("idx")
.arg("0")
.arg("dir")
.arg("h2c")
.status()
.expect("Unable to create Qdma queue0");
Command::new("dma-ctl")
.arg("qdma01001")
.arg("q")
.arg("start")
.arg("idx")
.arg("0")
.arg("dir")
.arg("h2c")
.arg("aperture_sz")
.arg(DMA_XFER_ALIGN.to_string())
.status()
.expect("Unable to start Qdma queue0");
tracing::debug!("Uploading stage2: [{} bytes]", pdi.pdi_stg2_bin.len());
// NB: Dma required same alignment as aperture.
let stg2_aligned = {
let len = pdi.pdi_stg2_bin.len();
let layout = std::alloc::Layout::from_size_align(len, DMA_XFER_ALIGN)
.expect("Invalid layout definition for stg2 aligned buffer");
let raw_ptr = unsafe { std::alloc::alloc(layout) };
let data = unsafe { std::slice::from_raw_parts_mut(raw_ptr, len) };
data.copy_from_slice(pdi.pdi_stg2_bin.as_slice());
data
};
let qdma_h2c = OpenOptions::new()
.read(false)
.write(true)
.create(false)
.open("/dev/qdma01001-MM-0")
.expect("Unable to open Qdma queue 0");
let ret = nix::sys::uio::pwrite(&qdma_h2c, stg2_aligned, 0x000102100000_i64)
.expect("Unable to write stage2 pdi");
tracing::debug!("QDMA written {ret} bytes to device");
// Properly release custom allocated memory
unsafe { nix::libc::free(stg2_aligned.as_mut_ptr() as *mut _) };
tracing::debug!(" Updating Pcie physical functions 0 status");
OpenOptions::new()
.write(true)
.open("/sys/bus/pci/devices/0000:01:00.0/remove")
.expect("Unable to open pci remove cmd file")
.write_all(b"1\n")
.expect("Unable to triggered a remove of pci pf0");
OpenOptions::new()
.write(true)
.open("/sys/bus/pci/rescan")
.expect("Unable to open pci rescan cmd file")
.write_all(b"1\n")
.expect("Unable to triggered a pci rescan");
// 3. Create user queues ----------------------------------------------
Command::new("dma-ctl")
.arg("qdma01001")
.arg("q")
.arg("add")
.arg("idx")
.arg("1")
.arg("dir")
.arg("h2c")
.status()
.expect("Unable to create Qdma queue1");
Command::new("dma-ctl")
.arg("qdma01001")
.arg("q")
.arg("start")
.arg("idx")
.arg("1")
.arg("dir")
.arg("h2c")
.status()
.expect("Unable to start Qdma queue1");
Command::new("dma-ctl")
.arg("qdma01001")
.arg("q")
.arg("add")
.arg("idx")
.arg("2")
.arg("dir")
.arg("c2h")
.status()
.expect("Unable to create Qdma queue2");
Command::new("dma-ctl")
.arg("qdma01001")
.arg("q")
.arg("start")
.arg("idx")
.arg("2")
.arg("dir")
.arg("c2h")
.status()
.expect("Unable to start Qdma queue2");
// 4. load ami kernel module ------------------------------------------
// Ami is to tight to amc version and thus bundle in .hpu_bin archive
tracing::debug!("Load ami kernel module");
Command::new("sudo")
.arg("insmod")
.arg(ami_path)
.status()
.expect("Unable to load ami.ko");
Self {
ami: AmiDriver::new(ami_id, ami_retry),
qdma: Arc::new(Mutex::new(QdmaDriver::new(h2c_path, c2h_path))),
ami: AmiDriver::new(id, &pdi.metadata.amc.his_version, ami_retry)
.expect("Unable to create AmiDriver after a fresh pdi load on device {id}"),
qdma: Arc::new(Mutex::new(
QdmaDriver::new(h2c_path, c2h_path)
.expect("Unable to create QdmaDriver after a fresh pdi load"),
)),
allocator: None,
}
}

View File

@@ -0,0 +1,132 @@
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fs::OpenOptions;
use std::io::{BufWriter, Write};
use std::path::Path;
use lazy_static::lazy_static;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct Version {
pub major: u32,
pub minor: u32,
pub flavor: Option<String>,
}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "v{}.{}", self.major, self.minor)?;
if let Some(flavor) = &self.flavor {
write!(f, "-{}", flavor)?;
}
Ok(())
}
}
impl std::str::FromStr for Version {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
lazy_static! {
static ref VER_ARG_RE: regex::Regex =
regex::Regex::new(r"v(?<major>\d+).(?<minor>\d+)(-(?<flavor>.+))?")
.expect("Invalid regex");
}
if let Some(caps) = VER_ARG_RE.captures(s) {
let major = caps["major"]
.trim_ascii()
.parse::<u32>()
.expect("Invalid major format. Must be an integer");
let minor = caps["minor"]
.trim_ascii()
.parse::<u32>()
.expect("Invalid minor format. Must be an integer");
let flavor = caps
.name("flavor")
.map(|flavor| flavor.as_str().to_string());
Ok(Self {
major,
minor,
flavor,
})
} else {
Err(
"Invalid version format, expect v{major}.{minor}[-{flavor}]. Where major/minor are integer and flavor String.".to_string(),
)
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BitstreamMetadata {
pub uuid: String,
pub wns_ps: f64,
pub tns_ps: f64,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct AmcMetadata {
pub his_version: Version,
}
pub const HPU_METADATA_VERSION: Version = Version {
major: 1,
minor: 0,
flavor: None,
};
#[derive(Serialize, Deserialize, Debug)]
pub struct Metadata {
pub version: Version,
pub pdi_stg1_file: String,
pub pdi_stg2_file: String,
pub xsa_file: String,
pub elf_file: String,
pub amc: AmcMetadata,
pub bitstream: BitstreamMetadata,
}
impl Metadata {
#[allow(unused)]
/// Provide Serde mechanisms from TOML file
pub fn from_toml(file: &str) -> Result<Self, Box<dyn Error>> {
let file_str = std::fs::read_to_string(file).inspect_err(|_e| {
eprintln!("Read error with file {file}");
})?;
let res = toml::from_str(&file_str)?;
Ok(res)
}
#[allow(unused)]
/// Provide Serde mechanisms to TOML file
pub fn to_toml(&self, file: &str) -> Result<(), Box<dyn Error>> {
// Open file
// Create path
let path = Path::new(&file);
if let Some(dir_p) = path.parent() {
std::fs::create_dir_all(dir_p).unwrap();
}
// Open file
let mut wr_f = BufWriter::new(
OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
.inspect_err(|_e| {
eprintln!("Open error with {path:?}");
})?,
);
// Convert in toml str and write into file
let toml_str = toml::to_string_pretty(&self).inspect_err(|_e| {
eprintln!("Serialize error with {self:?}");
})?;
wr_f.write_all(toml_str.as_bytes()).inspect_err(|_e| {
eprintln!("Write error with {wr_f:?}");
})?;
Ok(())
}
}

View File

@@ -0,0 +1,156 @@
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fs::OpenOptions;
use std::io::{BufReader, BufWriter, Read, Seek, Write};
use std::path::Path;
pub mod metadata;
use metadata::{Metadata, HPU_METADATA_VERSION};
pub mod uuid;
pub use uuid::V80Uuid;
#[derive(Serialize, Deserialize, Debug)]
pub struct HpuV80Pdi {
pub metadata: Metadata,
pub pdi_stg1_bin: Vec<u8>,
pub pdi_stg2_bin: Vec<u8>,
pub xsa_bin: Vec<u8>,
pub elf_bin: Vec<u8>,
}
impl HpuV80Pdi {
#[allow(unused)]
/// Utility function to read stream of data from file with proper error display
pub(crate) fn read_from_path(path: &str, file: &str) -> Result<Vec<u8>, Box<dyn Error>> {
let file_p = Path::new(path).join(file);
let rd_f = BufReader::new(OpenOptions::new().read(true).open(&file_p).inspect_err(
|_e| {
eprintln!("OpenOptions error with {file_p:?}");
},
)?);
let data = rd_f.bytes().collect::<Result<Vec<_>, _>>()?;
Ok(data)
}
#[allow(unused)]
/// Utility function to write stream of data into file with proper error display
pub(crate) fn write_to_path(path: &str, file: &str, data: &[u8]) -> Result<(), Box<dyn Error>> {
let file_p = Path::new(path).join(file);
// Enforce that parent folder exist and open file
if let Some(dir_p) = Path::new(&file_p).parent() {
std::fs::create_dir_all(dir_p).unwrap();
}
let mut wr_f = BufWriter::new(
OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&file_p)
.inspect_err(|_e| {
eprintln!("OpenOptions error with {file_p:?}");
})?,
);
wr_f.write_all(data)?;
Ok(())
}
}
impl HpuV80Pdi {
#[allow(unused)]
/// Construct HpuV80Pdi from a folder with discrete files
pub fn from_folder(folder_path: &str) -> Result<Self, Box<dyn Error>> {
let metadata_path = Path::new(folder_path).join("metadata.toml");
let metadata = Metadata::from_toml(
metadata_path
.to_str()
.expect("Invalid unicode path {metadata_path:?}"),
)?;
// Read the binary files
let pdi_stg1_bin = Self::read_from_path(folder_path, &metadata.pdi_stg1_file)?;
let pdi_stg2_bin = Self::read_from_path(folder_path, &metadata.pdi_stg2_file)?;
let xsa_bin = Self::read_from_path(folder_path, &metadata.xsa_file)?;
let elf_bin = Self::read_from_path(folder_path, &metadata.elf_file)?;
Ok(HpuV80Pdi {
metadata,
pdi_stg1_bin,
pdi_stg2_bin,
xsa_bin,
elf_bin,
})
}
#[allow(unused)]
/// Deconstruct HpuV80Pdi into a folder with discret files
pub fn to_folder(&self, folder_path: &str) -> Result<(), Box<dyn Error>> {
let metadata_path = Path::new(folder_path).join("metadata.toml");
self.metadata.to_toml(
metadata_path
.to_str()
.expect("Invalid unicode in path {metadata_path:?}"),
)?;
// Write the binary data
Self::write_to_path(
folder_path,
&self.metadata.pdi_stg1_file,
&self.pdi_stg1_bin,
)?;
Self::write_to_path(
folder_path,
&self.metadata.pdi_stg2_file,
&self.pdi_stg2_bin,
)?;
Self::write_to_path(folder_path, &self.metadata.xsa_file, &self.xsa_bin)?;
Self::write_to_path(folder_path, &self.metadata.elf_file, &self.elf_bin)?;
Ok(())
}
#[allow(unused)]
/// Deserialize HpuV80Pdi from a bin file
pub fn from_bincode(file_path: &str) -> Result<Self, Box<dyn Error>> {
let mut rd_f = OpenOptions::new()
.read(true)
.open(file_path)
.inspect_err(|_e| {
eprintln!("OpenOptions error with {file_path}");
})?;
let meta_version: metadata::Version = bincode::deserialize_from(&rd_f)?;
if meta_version != HPU_METADATA_VERSION {
return Err(format!(
"Archive use version \"{meta_version}\", Sw expect version \"{HPU_METADATA_VERSION}\""
)
.into());
}
// Start from beginning and use bufReader for performance
rd_f.seek(std::io::SeekFrom::Start(0));
let rd_bfr = BufReader::new(rd_f);
let hpu_pdi = bincode::deserialize_from(rd_bfr)?;
Ok(hpu_pdi)
}
#[allow(unused)]
/// Serialize HpuV80Pdi into a bin file
pub fn to_bincode(&self, file_path: &str) -> Result<(), Box<dyn Error>> {
// Enforce that parent folder exist and open file
if let Some(dir_p) = Path::new(&file_path).parent() {
std::fs::create_dir_all(dir_p).unwrap();
}
let wr_f = BufWriter::new(
OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(file_path)
.inspect_err(|_e| {
eprintln!("OpenOptions error with {file_path}");
})?,
);
bincode::serialize_into(wr_f, self)?;
Ok(())
}
}

View File

@@ -0,0 +1,287 @@
use std::str::FromStr;
pub const UNUSED_FIELD_LEN: usize = 5;
pub const HASH_FIELD_LEN: usize = 7;
pub const FREQ_FIELD_LEN: usize = 4;
pub const VERSION_FIELD_LEN: usize = 2;
pub const NTT_ARCH_FIELD_LEN: usize = 1;
pub const PSI_FIELD_LEN: usize = 1;
pub const HOST_FIELD_LEN: usize = 1;
pub const USER_FIELD_LEN: usize = 1;
pub const DATE_FIELD_LEN: usize = 10;
pub const UUID_LEN: usize = UNUSED_FIELD_LEN
+ HASH_FIELD_LEN
+ FREQ_FIELD_LEN
+ VERSION_FIELD_LEN
+ NTT_ARCH_FIELD_LEN
+ PSI_FIELD_LEN
+ HOST_FIELD_LEN
+ USER_FIELD_LEN
+ DATE_FIELD_LEN;
pub(crate) const AMI_UUID_WORDS: usize = 4;
fn from_readable_hex(s: &str) -> usize {
s.chars()
.flat_map(|c| c.to_digit(10))
.map(|v| v as usize)
.fold(0, |acc, v| 10 * acc + v)
}
struct Hash(usize);
impl std::fmt::Display for Hash {
// Hash is on 7hex digits
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:7>0x}", self.0)
}
}
impl FromStr for Hash {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != HASH_FIELD_LEN {
Err("Hash sub-field: Invalid length".to_string())
} else {
Ok(Self(
usize::from_str_radix(s, 16).map_err(|err| format!("Hash sub-field: {err:?}"))?,
))
}
}
}
pub struct Freq(usize);
impl std::fmt::Display for Freq {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Freq_MHz: {}", self.0)
}
}
// NB: Freq is displayed on 4 hex digits in human readable form
impl FromStr for Freq {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != FREQ_FIELD_LEN {
Err("Freq sub-field: Invalid length".to_string())
} else {
Ok(Self(from_readable_hex(s)))
}
}
}
pub struct Version {
major: usize,
minor: usize,
}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "v{}.{}", self.major, self.minor)
}
}
// NB: Freq is displayed on 4 hex digits in human readable form
impl FromStr for Version {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != VERSION_FIELD_LEN {
Err("Version sub-field: Invalid length".to_string())
} else {
let minor = from_readable_hex(&s[0..=0]);
let major = from_readable_hex(&s[1..=1]);
Ok(Self { major, minor })
}
}
}
#[derive(Debug)]
pub enum NttArch {
Unfold = 4,
GF64 = 5,
}
impl FromStr for NttArch {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != NTT_ARCH_FIELD_LEN {
Err("NttArch sub-field: Invalid length".to_string())
} else {
let val = from_readable_hex(s);
match val {
0x4 => Ok(NttArch::Unfold),
0x5 => Ok(NttArch::GF64),
_ => Err(format!("NttArch sub-field: Invalid value {val}")),
}
}
}
}
#[derive(Debug)]
pub enum Host {
SrvZama = 1,
Unknown,
}
impl FromStr for Host {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != PSI_FIELD_LEN {
Err("Host sub-field: Invalid length".to_string())
} else {
let val = from_readable_hex(s);
match val {
0x1 => Ok(Host::SrvZama),
_ => Ok(Host::Unknown),
}
}
}
}
#[derive(Debug)]
pub enum Psi {
Psi16 = 0,
Psi32 = 1,
Psi64 = 2,
}
impl FromStr for Psi {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != PSI_FIELD_LEN {
Err("Psi sub-field: Invalid length".to_string())
} else {
let val = from_readable_hex(s);
match val {
0x0 => Ok(Psi::Psi16),
0x1 => Ok(Psi::Psi32),
0x2 => Ok(Psi::Psi64),
_ => Err(format!("Psi sub-field: Invalid value {val}")),
}
}
}
}
#[derive(Debug)]
pub struct User(usize);
impl FromStr for User {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != USER_FIELD_LEN {
Err("User sub-field: Invalid length".to_string())
} else {
Ok(Self(from_readable_hex(s)))
}
}
}
impl std::fmt::Display for User {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "0x{:x}", self.0)
}
}
pub struct Date {
year: usize,
month: usize,
day: usize,
hour: usize,
min: usize,
}
impl FromStr for Date {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != DATE_FIELD_LEN {
Err("Date sub-field: Invalid length".to_string())
} else {
Ok(Self {
year: from_readable_hex(&s[0..2]),
month: from_readable_hex(&s[2..4]),
day: from_readable_hex(&s[4..6]),
hour: from_readable_hex(&s[6..8]),
min: from_readable_hex(&s[8..10]),
})
}
}
}
impl std::fmt::Display for Date {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:0>2}/{:0>2}/{:0>2}::{:0>2}h{:0>2}",
self.year, self.month, self.day, self.hour, self.min
)
}
}
pub struct V80Uuid {
hash: Hash,
freq: Freq,
version: Version,
arch: NttArch,
psi: Psi,
host: Host,
user: User,
date: Date,
}
impl std::fmt::Display for V80Uuid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "hash: {}", self.hash)?;
writeln!(f, "freq: {}", self.freq)?;
writeln!(f, "version: {}", self.version)?;
writeln!(f, "arch: {:?}", self.arch)?;
writeln!(f, "psi: {:?}", self.psi)?;
writeln!(f, "host: {:?}", self.host)?;
writeln!(f, "user: {}", self.user)?;
writeln!(f, "date: {}", self.date)?;
Ok(())
}
}
impl FromStr for V80Uuid {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != UUID_LEN {
Err("UUID: Invalid length".to_string())
} else {
let mut idx = UNUSED_FIELD_LEN;
let hash = Hash::from_str(&s[idx..idx + HASH_FIELD_LEN])?;
idx += HASH_FIELD_LEN;
let freq = Freq::from_str(&s[idx..idx + FREQ_FIELD_LEN])?;
idx += FREQ_FIELD_LEN;
let version = Version::from_str(&s[idx..idx + VERSION_FIELD_LEN])?;
idx += VERSION_FIELD_LEN;
let arch = NttArch::from_str(&s[idx..idx + NTT_ARCH_FIELD_LEN])?;
idx += NTT_ARCH_FIELD_LEN;
let psi = Psi::from_str(&s[idx..idx + PSI_FIELD_LEN])?;
idx += PSI_FIELD_LEN;
let host = Host::from_str(&s[idx..idx + HOST_FIELD_LEN])?;
idx += HOST_FIELD_LEN;
let user = User::from_str(&s[idx..idx + USER_FIELD_LEN])?;
idx += USER_FIELD_LEN;
let date = Date::from_str(&s[idx..idx + DATE_FIELD_LEN])?;
Ok(Self {
hash,
freq,
version,
arch,
psi,
host,
user,
date,
})
}
}
}

View File

@@ -23,6 +23,7 @@
//! ```
use lazy_static::lazy_static;
use std::error::Error;
use std::fs::{File, OpenOptions};
use std::io::Read;
@@ -35,16 +36,15 @@ pub(crate) struct QdmaDriver {
}
impl QdmaDriver {
pub fn new(h2c_path: &str, c2h_path: &str) -> Self {
Self::check_version().unwrap();
pub fn new(h2c_path: &str, c2h_path: &str) -> Result<Self, Box<dyn Error>> {
Self::check_version()?;
// Open HostToCard xfer file
let qdma_h2c = OpenOptions::new()
.read(false)
.write(true)
.create(false)
.open(h2c_path)
.unwrap_or_else(|e| panic!("Invalid qdma_h2c path: {h2c_path} -> {e}. Check queue initialization and configuration."));
.map_err(|err| format!("Opening file {h2c_path} failed: {err:?}"))?;
// Open CardToHost xfer file
let qdma_c2h = OpenOptions::new()
@@ -52,16 +52,16 @@ impl QdmaDriver {
.write(false)
.create(false)
.open(c2h_path)
.unwrap_or_else(|e| panic!("Invalid qdma_c2h path: {c2h_path} -> {e}. Check queue initialization and configuration."));
.map_err(|err| format!("Opening file {c2h_path} failed: {err:?}"))?;
Self { qdma_h2c, qdma_c2h }
Ok(Self { qdma_h2c, qdma_c2h })
}
/// Check if current qdma version is compliant
///
/// For this purpose we use a regex.
/// it's easy to expressed and understand breaking rules with it
pub fn check_version() -> Result<(), String> {
pub fn check_version() -> Result<(), Box<dyn Error>> {
lazy_static! {
static ref QDMA_VERSION_RE: regex::Regex =
regex::Regex::new(QDMA_VERSION_PATTERN).expect("Invalid regex");
@@ -73,7 +73,7 @@ impl QdmaDriver {
.write(false)
.create(false)
.open(QDMA_VERSION_FILE)
.unwrap();
.map_err(|err| format!("Opening file {QDMA_VERSION_FILE} failed: {err:?}"))?;
let qdma_version = {
let mut ver = String::new();
@@ -90,7 +90,8 @@ impl QdmaDriver {
Err(format!(
"Invalid qdma version. Get {} expect something matching pattern {}",
qdma_version, QDMA_VERSION_PATTERN
))
)
.into())
}
}

View File

@@ -42,7 +42,9 @@ impl std::str::FromStr for ShellString {
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub enum FFIMode {
V80 {
ami_id: usize,
id: u32,
hpu_path: ShellString,
ami_path: ShellString,
qdma_h2c: ShellString,
qdma_c2h: ShellString,
},

View File

@@ -0,0 +1,36 @@
use clap::{Parser, Subcommand};
use std::error::Error;
use tfhe_hpu_backend::ffi::HpuV80Pdi;
#[derive(Parser)]
#[command(name = "Pdi Management. Enable Packing/Unpacking of Hpu Pdi")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Pack { from_path: String, to_file: String },
Unpack { from_file: String, to_path: String },
}
fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse();
match cli.command {
Commands::Pack { from_path, to_file } => {
let hpu_pdi = HpuV80Pdi::from_folder(&from_path)?;
hpu_pdi.to_bincode(&to_file)?;
println!("Successfully packed folder {from_path} into {to_file}.");
}
Commands::Unpack { from_file, to_path } => {
let hpu_pdi = HpuV80Pdi::from_bincode(&from_file)?;
hpu_pdi.to_folder(&to_path)?;
println!("Successfully unpacked file {from_file} into {to_path} folder.");
}
}
Ok(())
}

View File

@@ -24,12 +24,15 @@ else
V80_PCIE_DEV="${DEVICE[0]%%:*}"
fi
# Default Qdma init
V80_QDMA_INIT=false
# V80 bitstream refresh rely on XilinxVivado tools
XILINX_VIVADO=${XILINX_VIVADO:-"/opt/xilinx/Vivado/2024.2"}
# V80 bitstream refresh require insmod of ami.ko module
AMI_PATH=${AMI_PATH:-"/opt/ami_drv/7423535"}
# Parse user CLI ##############################################################
opt_short="hc:l:p:i"
opt_long="help,config:,rust-log:pcie-dev:init-qdma"
opt_short="hc:l:p:"
opt_long="help,config:,rust-log:pcie-dev"
OPTS=$(getopt -o "$opt_short" -l "$opt_long" -- "$@")
while true
@@ -40,7 +43,6 @@ do
echo " * --config: target configuration [sim, u55c_gf64, v80]"
echo " * --rust-log: Specify rust verbosity [Cf. tracing]"
echo " * --pcie-dev: target pcie device [Warn: v80 only]"
echo " * --init-qdma: init the qdma driver [Warn: v80 only]"
return 0
;;
-c|--config)
@@ -72,10 +74,6 @@ do
fi
shift 2
;;
-i|--init-qdma)
V80_QDMA_INIT=true
shift
;;
"") # End of input reading
break ;;
*)
@@ -95,7 +93,8 @@ if [[ "$HPU_CONFIG" == sim* ]]; then
echo "# * Mockup directory: ${HPU_MOCKUP_DIR}"
elif [[ "$HPU_CONFIG" == v80* ]]; then
echo "# * PCIe id: ${V80_PCIE_DEV} [V80 only]"
echo "# * Init Qdma: ${V80_QDMA_INIT} [V80 only]"
echo "# * XilinxVivado: ${XILINX_VIVADO} [V80 only]"
echo "# * AmiPath: ${AMI_PATH} [V80 only]"
fi
echo "# * Rust verbosity: ${RUST_LOG}"
echo "###############################################################################"
@@ -123,24 +122,6 @@ fi
# V80 specific init ###########################################################
if [[ "$HPU_CONFIG" == v80* ]]; then
export V80_PCIE_DEV
if [[ "$V80_QDMA_INIT" == true ]]; then
while true; do
read -p "QDMA_PF init requested by user. This required sudo right, Are you sure to process [Y/n]" user_input
if [[ "$user_input" == [Yy] ]]; then
echo "Continuing... You could be prompt for sudo password"
sudo modprobe -r qdma-pf && sudo modprobe qdma-pf
sudo bash -c "echo 100 > /sys/bus/pci/devices/0000\:${V80_PCIE_DEV}\:00.1/qdma/qmax"
sudo dma-ctl qdma${V80_PCIE_DEV}001 q add idx 1 mode mm dir h2c
sudo dma-ctl qdma${V80_PCIE_DEV}001 q add idx 2 mode mm dir c2h
sudo dma-ctl qdma${V80_PCIE_DEV}001 q start idx 1 dir h2c
sudo dma-ctl qdma${V80_PCIE_DEV}001 q start idx 2 dir c2h
break
elif [[ "$user_input" == [Nn] ]]; then
echo "Skipped QDMA_PF init"
break
else
echo "Invalid input. Please enter 'Y' or 'n'."
fi
done
fi
export XILINX_VIVADO
export AMI_PATH
fi