1use hrx::npu::raw as dvxrt;
15
16use std::ffi::c_void;
17
18const ARG_A: i32 = 3;
19
20type Status = u32;
21const HSA_STATUS_SUCCESS: Status = 0;
22const HSA_DEVICE_TYPE_GPU: u32 = 1;
24const HSA_AGENT_INFO_DEVICE: u32 = 17;
26const POOL_INFO_SEGMENT: u32 = 0;
28const POOL_INFO_SIZE: u32 = 2;
29const POOL_INFO_ALLOC_ALLOWED: u32 = 5;
30const SEGMENT_GLOBAL: u32 = 0;
32
33struct Hsa {
34 library: libloading::Library,
35}
36
37impl Hsa {
38 fn open() -> Result<Self, String> {
39 let cache = std::env::var_os("XDG_CACHE_HOME")
40 .map(std::path::PathBuf::from)
41 .filter(|path| path.is_absolute())
42 .or_else(|| {
43 std::env::var_os("HOME").map(|home| std::path::PathBuf::from(home).join(".cache"))
44 })
45 .ok_or("no cache directory")?;
46 let root = cache.join("hrx/runtime");
47 let entry = std::fs::read_dir(&root)
48 .map_err(|e| format!("{}: {e}", root.display()))?
49 .flatten()
50 .map(|entry| entry.path().join("libhsa-runtime64.so.1"))
51 .find(|path| path.is_file())
52 .ok_or_else(|| format!("no libhsa-runtime64.so.1 under {}", root.display()))?;
53 let library =
54 unsafe { libloading::Library::new(&entry) }.map_err(|e| format!("{entry:?}: {e}"))?;
55 Ok(Hsa { library })
56 }
57
58 unsafe fn symbol<T>(&self, name: &[u8]) -> Result<libloading::Symbol<'_, T>, String> {
59 unsafe { self.library.get(name) }
60 .map_err(|e| format!("{}: {e}", String::from_utf8_lossy(name)))
61 }
62}
63
64#[derive(Default)]
66struct Found {
67 agent: u64,
68 pool: u64,
69 pool_bytes: usize,
70}
71
72fn main() -> Result<(), String> {
73 let args: Vec<String> = std::env::args().skip(1).collect();
74 let (xclbin, mib) = match args.as_slice() {
75 [xclbin] => (xclbin.clone(), 4usize),
76 [xclbin, flag, value] if flag == "--mib" => (
77 xclbin.clone(),
78 value.parse().map_err(|_| "--mib expects an integer")?,
79 ),
80 _ => return Err("usage: dmabuf_probe <xclbin> [--mib N]".into()),
81 };
82 let bytes = mib * 1024 * 1024;
83 let hsa = Hsa::open()?;
84
85 unsafe {
86 let init: libloading::Symbol<unsafe extern "C" fn() -> Status> =
87 hsa.symbol(b"hsa_init\0")?;
88 let status = init();
89 if status != HSA_STATUS_SUCCESS {
90 return Err(format!("hsa_init returned {status}"));
91 }
92
93 let iterate_agents: libloading::Symbol<
95 unsafe extern "C" fn(
96 unsafe extern "C" fn(u64, *mut c_void) -> Status,
97 *mut c_void,
98 ) -> Status,
99 > = hsa.symbol(b"hsa_iterate_agents\0")?;
100 let agent_info: libloading::Symbol<unsafe extern "C" fn(u64, u32, *mut c_void) -> Status> =
101 hsa.symbol(b"hsa_agent_get_info\0")?;
102
103 struct AgentScan<'a> {
105 info: &'a dyn Fn(u64, u32, *mut c_void) -> Status,
106 found: Found,
107 }
108 unsafe extern "C" fn on_agent(agent: u64, data: *mut c_void) -> Status {
109 let scan = unsafe { &mut *data.cast::<AgentScan>() };
110 let mut kind: u32 = 0;
111 if (scan.info)(agent, HSA_AGENT_INFO_DEVICE, (&raw mut kind).cast())
112 == HSA_STATUS_SUCCESS
113 && kind == HSA_DEVICE_TYPE_GPU
114 && scan.found.agent == 0
115 {
116 scan.found.agent = agent;
117 }
118 HSA_STATUS_SUCCESS
119 }
120 let info_fn = |agent, key, out| agent_info(agent, key, out);
121 let mut scan = AgentScan {
122 info: &info_fn,
123 found: Found::default(),
124 };
125 iterate_agents(on_agent, (&raw mut scan).cast());
126 if scan.found.agent == 0 {
127 return Err("no GPU agent found".into());
128 }
129 println!("gpu agent: 0x{:x}", scan.found.agent);
130
131 let iterate_pools: libloading::Symbol<
133 unsafe extern "C" fn(
134 u64,
135 unsafe extern "C" fn(u64, *mut c_void) -> Status,
136 *mut c_void,
137 ) -> Status,
138 > = hsa.symbol(b"hsa_amd_agent_iterate_memory_pools\0")?;
139 let pool_info: libloading::Symbol<unsafe extern "C" fn(u64, u32, *mut c_void) -> Status> =
140 hsa.symbol(b"hsa_amd_memory_pool_get_info\0")?;
141
142 struct PoolScan<'a> {
143 info: &'a dyn Fn(u64, u32, *mut c_void) -> Status,
144 found: Found,
145 }
146 unsafe extern "C" fn on_pool(pool: u64, data: *mut c_void) -> Status {
147 let scan = unsafe { &mut *data.cast::<PoolScan>() };
148 if scan.found.pool != 0 {
149 return HSA_STATUS_SUCCESS;
150 }
151 let mut segment: u32 = u32::MAX;
152 let mut allowed: bool = false;
153 let mut size: usize = 0;
154 (scan.info)(pool, POOL_INFO_SEGMENT, (&raw mut segment).cast());
155 (scan.info)(pool, POOL_INFO_ALLOC_ALLOWED, (&raw mut allowed).cast());
156 (scan.info)(pool, POOL_INFO_SIZE, (&raw mut size).cast());
157 if segment == SEGMENT_GLOBAL && allowed && size > 0 {
158 scan.found.pool = pool;
159 scan.found.pool_bytes = size;
160 }
161 HSA_STATUS_SUCCESS
162 }
163 let pool_fn = |pool, key, out| pool_info(pool, key, out);
164 let mut pools = PoolScan {
165 info: &pool_fn,
166 found: Found::default(),
167 };
168 iterate_pools(scan.found.agent, on_pool, (&raw mut pools).cast());
169 if pools.found.pool == 0 {
170 return Err("no allocatable global memory pool on the GPU agent".into());
171 }
172 println!(
173 "gpu memory pool: 0x{:x} ({} MiB)",
174 pools.found.pool,
175 pools.found.pool_bytes / (1024 * 1024)
176 );
177
178 let allocate: libloading::Symbol<
180 unsafe extern "C" fn(u64, usize, u32, *mut *mut c_void) -> Status,
181 > = hsa.symbol(b"hsa_amd_memory_pool_allocate\0")?;
182 let mut pointer: *mut c_void = std::ptr::null_mut();
183 let status = allocate(pools.found.pool, bytes, 0, &raw mut pointer);
184 if status != HSA_STATUS_SUCCESS {
185 return Err(format!("hsa_amd_memory_pool_allocate returned {status}"));
186 }
187 println!("pool allocation: {mib} MiB at {pointer:p}");
188
189 match hrx::Stream::open() {
192 Ok(mut stream) => match stream.allocate_shared(bytes) {
193 Ok(buffer) => {
194 stream.fill(buffer.binding(), 0x11).ok();
195 stream.synchronize().ok();
196 match buffer.device_ptr() {
197 Ok(hrx_pointer) => {
198 println!("hrx shared allocation: ok, device ptr {hrx_pointer:p}");
199 let mut fd: i32 = -1;
200 let mut offset: u64 = 0;
201 let export: libloading::Symbol<
202 unsafe extern "C" fn(
203 *const c_void,
204 usize,
205 *mut i32,
206 *mut u64,
207 ) -> Status,
208 > = hsa.symbol(b"hsa_amd_portable_export_dmabuf\0")?;
209 let status = export(hrx_pointer, bytes, &raw mut fd, &raw mut offset);
210 if status != HSA_STATUS_SUCCESS {
211 println!(" dma-buf export of an hrx buffer: NO (status {status})");
212 } else {
213 println!(
214 " dma-buf export of an hrx buffer: YES (fd {fd}, offset {offset})"
215 );
216 match dvxrt::Context::new(0, &xclbin) {
217 Ok(context) => match context.import_dmabuf(fd, bytes) {
218 Ok(bo) => {
219 let mut seen = vec![0u8; 4096];
220 bo.read(&mut seen).ok();
221 let wrong = seen.iter().filter(|&&b| b != 0x11).count();
222 println!(
223 " npu reads the hrx buffer: {}",
224 if wrong == 0 {
225 "YES -- one buffer, both engines".into()
226 } else {
227 format!("NO ({wrong}/4096 differ)")
228 }
229 );
230 }
231 Err(error) => println!(" npu import: NO ({error})"),
232 },
233 Err(error) => println!(" npu context: {error}"),
234 }
235 }
236 }
237 Err(error) => println!("hrx shared allocation device ptr: NO ({error})"),
238 }
239 }
240 Err(error) => println!("hrx shared allocation: NO ({error})"),
241 },
242 Err(error) => println!("hrx stream unavailable: {error}"),
243 }
244
245 let export: libloading::Symbol<
246 unsafe extern "C" fn(*const c_void, usize, *mut i32, *mut u64) -> Status,
247 > = hsa.symbol(b"hsa_amd_portable_export_dmabuf\0")?;
248 let mut fd: i32 = -1;
249 let mut offset: u64 = 0;
250 let status = export(pointer, bytes, &raw mut fd, &raw mut offset);
251 if status != HSA_STATUS_SUCCESS {
252 println!("hsa dma-buf export of pool memory: NO (status {status})");
253 return Ok(());
254 }
255 println!("hsa dma-buf export of pool memory: YES (fd {fd}, offset {offset})");
256
257 match dvxrt::Context::new(0, &xclbin) {
259 Ok(context) => {
260 let _group = context.group_id(ARG_A).map_err(|e| e.to_string())?;
261 match context.import_dmabuf(fd, bytes) {
262 Ok(imported) => {
263 println!("npu import of the GPU dma-buf: YES");
264 let fill: libloading::Symbol<
267 unsafe extern "C" fn(*mut c_void, u32, usize) -> Status,
268 > = hsa.symbol(b"hsa_amd_memory_fill\0")?;
269 let pattern = 0xa5a5_a5a5u32;
270 let status = fill(pointer, pattern, bytes / 4);
271 if status != HSA_STATUS_SUCCESS {
272 println!(" gpu fill of pool memory failed (status {status})");
273 return Ok(());
274 }
275 let mut seen = vec![0u8; 4096];
276 match imported.read(&mut seen) {
277 Ok(()) => {
278 let wrong = seen.iter().filter(|&&b| b != 0xa5).count();
279 if wrong == 0 {
280 println!(
281 " gpu write -> npu read over dma-buf: YES -- zero-copy confirmed"
282 );
283 } else {
284 println!(
285 " gpu write -> npu read over dma-buf: NO ({wrong}/4096 bytes differ)"
286 );
287 }
288 }
289 Err(error) => {
290 println!(" read through the imported BO failed: {error}")
291 }
292 }
293 }
294 Err(error) => println!("npu import of the GPU dma-buf: NO ({error})"),
295 }
296 }
297 Err(error) => println!("npu context: UNAVAILABLE ({error})"),
298 }
299 }
300 Ok(())
301}