diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..ee0af648b --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.hpu filter=lfs diff=lfs merge=lfs -text diff --git a/backends/tfhe-hpu-backend/Cargo.toml b/backends/tfhe-hpu-backend/Cargo.toml index c69effb30..c6b81dbd9 100644 --- a/backends/tfhe-hpu-backend/Cargo.toml +++ b/backends/tfhe-hpu-backend/Cargo.toml @@ -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]] diff --git a/backends/tfhe-hpu-backend/README.md b/backends/tfhe-hpu-backend/README.md index 2a380d7b2..260523d39 100644 --- a/backends/tfhe-hpu-backend/README.md +++ b/backends/tfhe-hpu-backend/README.md @@ -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: diff --git a/backends/tfhe-hpu-backend/config_store/v80/hpu_config.toml b/backends/tfhe-hpu-backend/config_store/v80/hpu_config.toml index a5fd2df84..8ed704322 100644 --- a/backends/tfhe-hpu-backend/config_store/v80/hpu_config.toml +++ b/backends/tfhe-hpu-backend/config_store/v80/hpu_config.toml @@ -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" diff --git a/backends/tfhe-hpu-backend/config_store/v80_archives/README.md b/backends/tfhe-hpu-backend/config_store/v80_archives/README.md new file mode 100644 index 000000000..e7654d5a8 --- /dev/null +++ b/backends/tfhe-hpu-backend/config_store/v80_archives/README.md @@ -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 +``` diff --git a/backends/tfhe-hpu-backend/config_store/v80_archives/psi16.hpu b/backends/tfhe-hpu-backend/config_store/v80_archives/psi16.hpu new file mode 100644 index 000000000..b2577219e --- /dev/null +++ b/backends/tfhe-hpu-backend/config_store/v80_archives/psi16.hpu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5f578ec0cbcd1525fc88dc57fff1a2384fa742a147f69b6a9c77deafc0601fe +size 33348376 diff --git a/backends/tfhe-hpu-backend/config_store/v80_archives/psi64.hpu b/backends/tfhe-hpu-backend/config_store/v80_archives/psi64.hpu new file mode 100644 index 000000000..c2add7619 --- /dev/null +++ b/backends/tfhe-hpu-backend/config_store/v80_archives/psi64.hpu @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb9ebedd0987130c4f6e1ef09f279d92f083815c1383da4b257198a33ab4881e +size 80293531 diff --git a/backends/tfhe-hpu-backend/scripts/pdi_jtag.tcl b/backends/tfhe-hpu-backend/scripts/pdi_jtag.tcl new file mode 100644 index 000000000..fdd129748 --- /dev/null +++ b/backends/tfhe-hpu-backend/scripts/pdi_jtag.tcl @@ -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] diff --git a/backends/tfhe-hpu-backend/scripts/v80-pcie-perms.sh b/backends/tfhe-hpu-backend/scripts/v80-pcie-perms.sh new file mode 100755 index 000000000..cc0ad9b07 --- /dev/null +++ b/backends/tfhe-hpu-backend/scripts/v80-pcie-perms.sh @@ -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 diff --git a/backends/tfhe-hpu-backend/src/ffi/mod.rs b/backends/tfhe-hpu-backend/src/ffi/mod.rs index 327d23775..52b99906e 100644 --- a/backends/tfhe-hpu-backend/src/ffi/mod.rs +++ b/backends/tfhe-hpu-backend/src/ffi/mod.rs @@ -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; diff --git a/backends/tfhe-hpu-backend/src/ffi/v80/ami.rs b/backends/tfhe-hpu-backend/src/ffi/v80/ami.rs index 528da569c..a5c9f128a 100644 --- a/backends/tfhe-hpu-backend/src/ffi/v80/ami.rs +++ b/backends/tfhe-hpu-backend/src/ffi/v80/ami.rs @@ -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"(?\d{2}:\d{2}\.\d)\s(?\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 (?\d+).(?\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> { + // 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"(?[[:xdigit:]]{2}):(?[[:xdigit:]]{2})\.(?[[:xdigit:]])\s(?\d+)\s(?\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::()?; + let hwmon = caps["hwmon"].parse::()?; + 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::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> { // 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::()?; + let amc_minor = caps["minor"].parse::()?; + 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; diff --git a/backends/tfhe-hpu-backend/src/ffi/v80/mod.rs b/backends/tfhe-hpu-backend/src/ffi/v80/mod.rs index ac1bfc1b6..2fa869f6f 100644 --- a/backends/tfhe-hpu-backend/src/ffi/v80/mod.rs +++ b/backends/tfhe-hpu-backend/src/ffi/v80/mod.rs @@ -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> { + 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::() + ); + 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, } } diff --git a/backends/tfhe-hpu-backend/src/ffi/v80/pdi/metadata.rs b/backends/tfhe-hpu-backend/src/ffi/v80/pdi/metadata.rs new file mode 100644 index 000000000..e3ba84a7f --- /dev/null +++ b/backends/tfhe-hpu-backend/src/ffi/v80/pdi/metadata.rs @@ -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, +} + +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 { + lazy_static! { + static ref VER_ARG_RE: regex::Regex = + regex::Regex::new(r"v(?\d+).(?\d+)(-(?.+))?") + .expect("Invalid regex"); + } + if let Some(caps) = VER_ARG_RE.captures(s) { + let major = caps["major"] + .trim_ascii() + .parse::() + .expect("Invalid major format. Must be an integer"); + let minor = caps["minor"] + .trim_ascii() + .parse::() + .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> { + 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> { + // 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(()) + } +} diff --git a/backends/tfhe-hpu-backend/src/ffi/v80/pdi/mod.rs b/backends/tfhe-hpu-backend/src/ffi/v80/pdi/mod.rs new file mode 100644 index 000000000..0b0acc231 --- /dev/null +++ b/backends/tfhe-hpu-backend/src/ffi/v80/pdi/mod.rs @@ -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, + pub pdi_stg2_bin: Vec, + pub xsa_bin: Vec, + pub elf_bin: Vec, +} + +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, Box> { + 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::, _>>()?; + 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> { + 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> { + 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> { + 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> { + 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> { + // 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(()) + } +} diff --git a/backends/tfhe-hpu-backend/src/ffi/v80/pdi/uuid.rs b/backends/tfhe-hpu-backend/src/ffi/v80/pdi/uuid.rs new file mode 100644 index 000000000..edca51cc8 --- /dev/null +++ b/backends/tfhe-hpu-backend/src/ffi/v80/pdi/uuid.rs @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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, + }) + } + } +} diff --git a/backends/tfhe-hpu-backend/src/ffi/v80/qdma.rs b/backends/tfhe-hpu-backend/src/ffi/v80/qdma.rs index ac16c6d6c..4f82e9d04 100644 --- a/backends/tfhe-hpu-backend/src/ffi/v80/qdma.rs +++ b/backends/tfhe-hpu-backend/src/ffi/v80/qdma.rs @@ -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::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> { 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()) } } diff --git a/backends/tfhe-hpu-backend/src/interface/config.rs b/backends/tfhe-hpu-backend/src/interface/config.rs index 6c3d5975d..822befb92 100644 --- a/backends/tfhe-hpu-backend/src/interface/config.rs +++ b/backends/tfhe-hpu-backend/src/interface/config.rs @@ -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, }, diff --git a/backends/tfhe-hpu-backend/src/utils/pdi_mgmt.rs b/backends/tfhe-hpu-backend/src/utils/pdi_mgmt.rs new file mode 100644 index 000000000..d8acc4df9 --- /dev/null +++ b/backends/tfhe-hpu-backend/src/utils/pdi_mgmt.rs @@ -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> { + 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(()) +} diff --git a/setup_hpu.sh b/setup_hpu.sh index bf15e3032..a6e85e54c 100644 --- a/setup_hpu.sh +++ b/setup_hpu.sh @@ -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