1#[allow(
4 non_camel_case_types,
5 non_snake_case,
6 non_upper_case_globals,
7 dead_code,
8 missing_docs,
9 unsafe_op_in_unsafe_fn,
10 clippy::all
11)]
12mod ffi;
13use crate::{Error, Result, Target};
14use ffi::*;
15use std::{ffi::CStr, path::Path, ptr, sync::Arc};
16
17fn check(operation: &'static str, status: amdf_status_t) -> Result<()> {
18 if status == 0 {
19 return Ok(());
20 }
21 let domain = (status >> 32) as u32;
22 let code = status as u32;
23 Err(Error::Backend {
24 backend: "libamdf",
25 operation,
26 code: code as i32,
27 message: format!("native status domain {domain}, code {code}").into_boxed_str(),
28 })
29}
30fn missing(name: &str) -> Error {
31 Error::Unsupported(format!("libamdf ABI is missing {name}"))
32}
33macro_rules! entry {
34 ($table:expr, $name:ident) => {
35 $table.$name.ok_or_else(|| missing(stringify!($name)))?
36 };
37}
38
39#[cfg(target_os = "linux")]
40mod storage;
41#[cfg(target_os = "linux")]
42pub use storage::{StorageExecution, StorageLayout, StorageOptions, StorageProgress, StorageRing};
43mod resident;
44pub use resident::{ResidentGpu, ResidentSession, ResidentStartup};
45mod xdna;
46pub use xdna::{XdnaBinding, XdnaCompletion, XdnaProgram};
47pub(crate) mod profile;
48pub use profile::{DeviceInterval, DeviceProfile, ProfiledGpu};
49mod queue;
50pub(crate) use queue::GraphArena;
51pub use queue::{Completion, PreparedGpu, Queue};
52mod sanitizer;
53pub use sanitizer::{
54 SanitizerAccess, SanitizerAddress, SanitizerCheck, SanitizerRace, SanitizerReport,
55 SanitizerReports, SanitizerRuntimeOptions, SanitizerSite,
56};
57mod executable;
58pub use executable::{Argument, Kernel};
59mod aql;
60pub use aql::{AqlCompletion, AqlQueue, PreparedAql};
61mod capabilities;
62pub use capabilities::{QueueCapabilities, QueueCommand};
63mod visibility;
64pub use visibility::{
65 AtomicScope, CacheExecutor, CacheOperation, CacheTransition, MemorySite, MemoryVisibility,
66 PreparedHostVisibility, TransitionKind, VisibilityFacts,
67};
68mod memory;
69pub use memory::{AllocationProfile, Buffer, ProspectiveVisibility};
70
71struct Api {
72 core: &'static amdf_api_t,
73 gpu: &'static amdf_gpu_api_t,
74 xdna: &'static amdf_xdna_api_t,
75 _library: Amdf,
77 directory: std::path::PathBuf,
78 bridge: executable::BridgeSlot,
79}
80impl Api {
81 unsafe fn load(path: &Path) -> Result<Self> {
82 let library = unsafe { Amdf::new(path)? };
83 let mut core = ptr::null();
84 check("query_api", unsafe {
85 library.amdf_query_api(HRX_AMDF_ABI_VERSION, HRX_AMDF_ABI_VERSION, &mut core)
86 })?;
87 if core.is_null() {
88 return Err(missing("core table"));
89 }
90 unsafe {
91 validate_table(core.cast(), size_of::<amdf_api_t>(), HRX_AMDF_ABI_VERSION)?;
92 }
93 let core = unsafe { &*core };
94 let mut gpu = ptr::null();
95 let mut xdna = ptr::null();
96 check("query_extension(GPU)", unsafe {
97 entry!(core, query_extension)(
98 AMDF_EXTENSION_GPU,
99 AMDF_GPU_EXTENSION_VERSION_LATEST,
100 AMDF_GPU_EXTENSION_VERSION_LATEST,
101 &mut gpu,
102 )
103 })?;
104 check("query_extension(XDNA)", unsafe {
105 entry!(core, query_extension)(
106 AMDF_EXTENSION_XDNA,
107 AMDF_XDNA_EXTENSION_VERSION_LATEST,
108 AMDF_XDNA_EXTENSION_VERSION_LATEST,
109 &mut xdna,
110 )
111 })?;
112 if gpu.is_null() || xdna.is_null() {
113 return Err(missing("device extension table"));
114 }
115 unsafe {
116 validate_table(
117 gpu,
118 size_of::<amdf_gpu_api_t>(),
119 AMDF_GPU_EXTENSION_VERSION_LATEST,
120 )?;
121 validate_table(
122 xdna,
123 size_of::<amdf_xdna_api_t>(),
124 AMDF_XDNA_EXTENSION_VERSION_LATEST,
125 )?;
126 }
127 let gpu = unsafe { &*gpu.cast::<amdf_gpu_api_t>() };
128 let xdna = unsafe { &*xdna.cast::<amdf_xdna_api_t>() };
129 validate_bridge_api(core, xdna)?;
130 Ok(Self {
131 core,
132 gpu,
133 xdna,
134 _library: library,
135 directory: path
136 .parent()
137 .unwrap_or_else(|| Path::new("."))
138 .to_path_buf(),
139 bridge: Default::default(),
140 })
141 }
142}
143
144unsafe fn validate_table(
146 pointer: *const std::ffi::c_void,
147 bytes: usize,
148 version: u32,
149) -> Result<()> {
150 if pointer.is_null() {
151 return Err(missing("table header"));
152 }
153 let header = unsafe { pointer.cast::<[u32; 2]>().read() };
154 if (header[0] as usize) < bytes || header[1] != version {
155 return Err(missing("complete table with the requested version"));
156 }
157 Ok(())
158}
159fn validate_bridge_api(core: &amdf_api_t, xdna: &amdf_xdna_api_t) -> Result<()> {
162 let _ = entry!(core, endpoint_query_info);
163 let _ = entry!(core, endpoint_query_queue_family_info);
164 let _ = entry!(core, host_mapping_cache_control);
165 let _ = entry!(core, host_mapping_destroy);
166 let _ = entry!(core, host_mapping_query_info);
167 let _ = entry!(core, instance_enumerate_memory_scopes);
168 let _ = entry!(core, kernel_queue_destroy);
169 let _ = entry!(core, kernel_queue_wait);
170 let _ = entry!(core, memory_create);
171 let _ = entry!(core, memory_destroy);
172 let _ = entry!(core, memory_map);
173 let _ = entry!(core, memory_query_address);
174 let _ = entry!(core, memory_scope_query_device_profile);
175 let _ = entry!(core, memory_scope_query_info);
176 let _ = entry!(xdna, context_create);
177 let _ = entry!(xdna, context_destroy);
178 let _ = entry!(xdna, context_enumerate_memory_scopes);
179 let _ = entry!(xdna, device_query_info);
180 let _ = entry!(xdna, endpoint_query_info);
181 let _ = entry!(xdna, kernel_queue_create);
182 let _ = entry!(xdna, kernel_queue_submit);
183 Ok(())
184}
185
186#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
188pub enum NativeLifetime {
189 #[default]
191 Instance,
192 Process,
195}
196#[derive(Clone)]
198pub struct Fabric(Arc<Instance>);
199struct Instance {
200 api: Arc<Api>,
201 raw: *mut amdf_instance_t,
202}
203unsafe impl Send for Instance {}
206unsafe impl Sync for Instance {}
207impl Drop for Instance {
208 fn drop(&mut self) {
209 let status = unsafe { self.api.core.instance_destroy.unwrap()(self.raw) };
210 if status != 0 {
211 std::mem::forget(self.api.clone());
212 }
213 }
214}
215
216#[derive(Clone, Copy, Debug, PartialEq, Eq)]
218pub enum Engine {
219 Gpu,
221 Xdna,
223}
224
225#[derive(Clone)]
227pub struct Endpoint(Arc<EndpointInner>);
228struct EndpointInner {
229 instance: Arc<Instance>,
230 raw: *mut amdf_endpoint_t,
231 name: String,
232 engine: Engine,
233 target: Target,
234}
235unsafe impl Send for EndpointInner {}
236unsafe impl Sync for EndpointInner {}
237impl Drop for EndpointInner {
238 fn drop(&mut self) {
239 let status = unsafe { self.instance.api.core.endpoint_close.unwrap()(self.raw) };
240 if status != 0 {
241 std::mem::forget(self.instance.clone());
242 }
243 }
244}
245impl Endpoint {
246 pub fn name(&self) -> &str {
248 &self.0.name
249 }
250 pub fn target(&self) -> &Target {
252 &self.0.target
253 }
254 pub fn engine(&self) -> Engine {
256 self.0.engine
257 }
258 pub fn open(&self) -> Result<Device> {
260 if self.target().as_str() != "gfx1151" && !self.target().is_xdna() {
261 return Err(Error::Unsupported(format!(
262 "unqualified device {}",
263 self.target().as_str()
264 )));
265 }
266 let api = &self.0.instance.api;
267 let _ = entry!(api.core, device_destroy);
269 let mut raw = ptr::null_mut();
270 unsafe {
271 match self.engine() {
272 Engine::Gpu => {
273 let options = amdf_gpu_device_create_info_t {
274 type_: HRX_AMDF_STRUCTURE_TYPE_GPU_DEVICE_CREATE_INFO,
275 structure_size: size_of::<amdf_gpu_device_create_info_t>() as u32,
276 ..Default::default()
277 };
278 check(
279 "gpu.device_create",
280 entry!(api.gpu, device_create)(self.0.raw, &options, &mut raw),
281 )?;
282 }
283 Engine::Xdna => {
284 let options = amdf_xdna_device_create_info_t {
285 type_: HRX_AMDF_STRUCTURE_TYPE_XDNA_DEVICE_CREATE_INFO,
286 structure_size: size_of::<amdf_xdna_device_create_info_t>() as u32,
287 ..Default::default()
288 };
289 check(
290 "xdna.device_create",
291 entry!(api.xdna, device_create)(self.0.raw, &options, &mut raw),
292 )?;
293 }
294 }
295 }
296 if raw.is_null() {
297 return Err(missing("created device"));
298 }
299 Ok(Device(Arc::new(DeviceInner {
300 endpoint: self.clone(),
301 raw,
302 stream_queues: Default::default(),
303 })))
304 }
305}
306
307#[derive(Clone)]
309pub struct Device(Arc<DeviceInner>);
310struct DeviceInner {
311 endpoint: Endpoint,
312 raw: *mut amdf_device_t,
313 stream_queues: std::sync::Mutex<queue::StreamQueues>,
314}
315unsafe impl Send for DeviceInner {}
316unsafe impl Sync for DeviceInner {}
317impl Drop for DeviceInner {
318 fn drop(&mut self) {
319 let api = &self.endpoint.0.instance.api;
320 let status = unsafe { api.core.device_destroy.unwrap()(self.raw) };
321 if status != 0 {
322 std::mem::forget(self.endpoint.clone());
323 }
324 }
325}
326impl Device {
327 pub fn endpoint(&self) -> &Endpoint {
329 &self.0.endpoint
330 }
331 pub fn target(&self) -> &Target {
333 self.endpoint().target()
334 }
335}
336
337impl Fabric {
338 pub fn load(path: &Path) -> Result<Self> {
340 Self::load_with_lifetime(path, NativeLifetime::Instance)
341 }
342 pub fn load_with_lifetime(path: &Path, lifetime: NativeLifetime) -> Result<Self> {
347 let api = Arc::new(unsafe { Api::load(path)? });
348 let _ = entry!(api.core, instance_destroy);
349 let options = amdf_instance_create_info_t {
350 type_: AMDF_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
351 structure_size: size_of::<amdf_instance_create_info_t>() as u32,
352 native_lifetime: match lifetime {
353 NativeLifetime::Instance => AMDF_NATIVE_LIFETIME_INSTANCE,
354 NativeLifetime::Process => AMDF_NATIVE_LIFETIME_PROCESS,
355 },
356 ..Default::default()
357 };
358 let mut raw = ptr::null_mut();
359 check("instance_create", unsafe {
360 entry!(api.core, instance_create)(&options, &mut raw)
361 })?;
362 if raw.is_null() {
363 return Err(missing("created instance"));
364 }
365 Ok(Self(Arc::new(Instance { api, raw })))
366 }
367
368 pub fn endpoints(&self) -> Result<Vec<Endpoint>> {
370 let api = &self.0.api;
371 let _ = entry!(api.core, endpoint_close);
372 let mut count = 0;
373 check("endpoint_enumerate", unsafe {
374 entry!(api.core, endpoint_enumerate)(self.0.raw, 0, ptr::null_mut(), &mut count)
375 })?;
376 let mut summaries = Vec::new();
379 for attempt in 0..4 {
380 summaries.resize(count as usize, amdf_endpoint_summary_t::default());
381 let status = unsafe {
382 entry!(api.core, endpoint_enumerate)(
383 self.0.raw,
384 summaries.len() as u32,
385 summaries.as_mut_ptr(),
386 &mut count,
387 )
388 };
389 if status as u32 == AMDF_STATUS_CODE_BUFFER_TOO_SMALL && attempt < 3 {
390 continue;
391 }
392 check("endpoint_enumerate", status)?;
393 summaries.truncate(count as usize);
394 break;
395 }
396 let mut endpoints = Vec::new();
397 for summary in summaries {
398 let mut raw = ptr::null_mut();
399 check("endpoint_open", unsafe {
400 entry!(api.core, endpoint_open)(self.0.raw, &summary.id, &mut raw)
401 })?;
402 if raw.is_null() {
403 return Err(missing("opened endpoint"));
404 }
405 let metadata = (|| -> Result<_> {
406 unsafe {
407 let mut info = amdf_endpoint_info_t {
408 type_: AMDF_STRUCTURE_TYPE_ENDPOINT_INFO,
409 structure_size: size_of::<amdf_endpoint_info_t>() as u32,
410 ..Default::default()
411 };
412 check(
413 "endpoint_query_info",
414 entry!(api.core, endpoint_query_info)(raw, &mut info),
415 )?;
416 let name = CStr::from_ptr(info.name.as_ptr())
417 .to_string_lossy()
418 .into_owned();
419 let (engine, target) = match info.engine_kind {
420 AMDF_ENGINE_KIND_GPU => {
421 let mut gpu = amdf_gpu_endpoint_info_t {
422 type_: HRX_AMDF_STRUCTURE_TYPE_GPU_ENDPOINT_INFO,
423 structure_size: size_of::<amdf_gpu_endpoint_info_t>() as u32,
424 ..Default::default()
425 };
426 check(
427 "gpu.endpoint_query_info",
428 entry!(api.gpu, endpoint_query_info)(raw, &mut gpu),
429 )?;
430 (
431 Engine::Gpu,
432 Target::new(&format!(
433 "gfx{}{}{:x}",
434 gpu.gfx_ip.major, gpu.gfx_ip.minor, gpu.gfx_ip.stepping
435 ))?,
436 )
437 }
438 AMDF_ENGINE_KIND_XDNA => {
439 if info.pci.device_id != 0x17f0 || info.pci.revision_id != 0x11 {
441 return Err(Error::Unsupported(
442 "unqualified XDNA deployment".into(),
443 ));
444 }
445 (Engine::Xdna, Target::xdna())
446 }
447 _ => return Err(Error::Unsupported("unknown AMD engine".into())),
448 };
449 Ok((name, engine, target))
450 }
451 })();
452 match metadata {
453 Ok((name, engine, target)) => endpoints.push(Endpoint(Arc::new(EndpointInner {
454 instance: self.0.clone(),
455 raw,
456 name,
457 engine,
458 target,
459 }))),
460 Err(error) => {
461 let status = unsafe { api.core.endpoint_close.unwrap()(raw) };
462 if status != 0 {
463 std::mem::forget(self.0.clone());
464 }
465 return Err(error);
466 }
467 }
468 }
469 Ok(endpoints)
470 }
471}
472
473impl Fabric {
474 pub fn resolve() -> Result<Self> {
476 Self::resolve_with_lifetime(NativeLifetime::Instance)
477 }
478 pub fn resolve_with_lifetime(lifetime: NativeLifetime) -> Result<Self> {
483 struct Instances {
484 instance: std::sync::Weak<Instance>,
485 process: Option<Arc<Instance>>,
486 }
487 static INSTANCES: std::sync::Mutex<Instances> = std::sync::Mutex::new(Instances {
488 instance: std::sync::Weak::new(),
489 process: None,
490 });
491 let mut slots = INSTANCES
492 .lock()
493 .map_err(|_| Error::Message("fabric registry poisoned".into()))?;
494 let existing = match lifetime {
495 NativeLifetime::Instance => slots.instance.upgrade(),
496 NativeLifetime::Process => slots.process.clone(),
497 };
498 if let Some(instance) = existing {
499 return Ok(Self(instance));
500 }
501 let path = match std::env::var_os("HRX_AMDF_LIBRARY") {
502 Some(path) => std::path::PathBuf::from(path),
503 None => crate::bundle::resolve()?.join("libamdf.so"),
504 };
505 let fabric = Self::load_with_lifetime(&path, lifetime)?;
506 match lifetime {
507 NativeLifetime::Instance => slots.instance = Arc::downgrade(&fabric.0),
508 NativeLifetime::Process => slots.process = Some(fabric.0.clone()),
509 }
510 Ok(fabric)
511 }
512}
513impl Device {
514 pub fn open(engine: Engine, index: usize) -> Result<Self> {
517 Self::open_with_lifetime(engine, index, NativeLifetime::Instance)
518 }
519 pub fn open_with_lifetime(
521 engine: Engine,
522 index: usize,
523 lifetime: NativeLifetime,
524 ) -> Result<Self> {
525 type Devices = Vec<(Engine, usize, NativeLifetime, std::sync::Weak<DeviceInner>)>;
526 static DEVICES: std::sync::Mutex<Devices> = std::sync::Mutex::new(Vec::new());
527 let mut devices = DEVICES
528 .lock()
529 .map_err(|_| Error::Message("device registry poisoned".into()))?;
530 if let Some(device) = devices.iter().find_map(|(kind, ordinal, policy, device)| {
531 (*kind == engine && *ordinal == index && *policy == lifetime)
532 .then(|| device.upgrade())
533 .flatten()
534 }) {
535 return Ok(Self(device));
536 }
537 devices.retain(|(_, _, _, device)| device.strong_count() != 0);
538 let device = Fabric::resolve_with_lifetime(lifetime)?
539 .endpoints()?
540 .into_iter()
541 .filter(|endpoint| endpoint.engine() == engine)
542 .nth(index)
543 .ok_or_else(|| Error::Unsupported(format!("{engine:?} device {index} is unavailable")))?
544 .open()?;
545 devices.push((engine, index, lifetime, Arc::downgrade(&device.0)));
546 Ok(device)
547 }
548 pub fn id(&self) -> usize {
550 Arc::as_ptr(&self.0) as usize
551 }
552 pub fn fabric(&self) -> Fabric {
554 Fabric(self.0.endpoint.0.instance.clone())
555 }
556}
557
558#[cfg(test)]
559mod abi_tests {
560 use super::*;
561 #[test]
562 fn reject_truncated_and_mismatched_tables_before_full_reference() {
563 for header in [[8u32, 5], [size_of::<amdf_api_t>() as u32, 4]] {
564 assert!(
565 unsafe { validate_table(header.as_ptr().cast(), size_of::<amdf_api_t>(), 5) }
566 .is_err()
567 );
568 }
569 assert!(validate_bridge_api(&amdf_api_t::default(), &amdf_xdna_api_t::default()).is_err());
570 }
571}