Skip to main content

edgefirst_tflite_sys/
hal_ffi.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 Au-Zone Technologies. All Rights Reserved.
3
4//! FFI bindings for the HAL Delegate DMA-BUF API.
5//!
6//! These types mirror the stable C ABI defined in `edgefirst-hal-capi`
7//! (`hal.h`). The HAL owns the type definitions; function implementations
8//! live in delegate shared libraries (e.g., Neutron NPU delegate).
9//!
10//! Function pointers are loaded at runtime from the delegate `.so` using
11//! `libloading`, following the same pattern as [`super::vx_ffi`].
12
13use std::ffi::{c_char, c_int, c_void};
14
15// ---------------------------------------------------------------------------
16// HalDtype
17// ---------------------------------------------------------------------------
18
19/// Element data type for HAL tensors.
20///
21/// Mirrors `hal_dtype` from `edgefirst-hal-capi` (`hal.h`).
22#[repr(u32)]
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum HalDtype {
25    /// Unsigned 8-bit integer.
26    U8 = 0,
27    /// Signed 8-bit integer.
28    I8 = 1,
29    /// Unsigned 16-bit integer.
30    U16 = 2,
31    /// Signed 16-bit integer.
32    I16 = 3,
33    /// Unsigned 32-bit integer.
34    U32 = 4,
35    /// Signed 32-bit integer.
36    I32 = 5,
37    /// Unsigned 64-bit integer.
38    U64 = 6,
39    /// Signed 64-bit integer.
40    I64 = 7,
41    /// 16-bit floating point (half).
42    F16 = 8,
43    /// 32-bit floating point (float).
44    F32 = 9,
45    /// 64-bit floating point (double).
46    F64 = 10,
47}
48
49// ---------------------------------------------------------------------------
50// HalDmabufTensorInfo
51// ---------------------------------------------------------------------------
52
53/// Maximum number of dimensions in a delegate tensor shape.
54pub const HAL_DMABUF_MAX_NDIM: usize = 8;
55
56/// DMA-BUF tensor information returned by a delegate.
57///
58/// Describes a single tensor's DMA-BUF allocation, including the file
59/// descriptor, buffer geometry, and element type. The `fd` is borrowed
60/// from the delegate and must **not** be closed by the caller.
61///
62/// Fields are ordered to eliminate padding on LP64: all `usize` fields
63/// first (8-byte aligned), then smaller `c_int` and enum fields (4 bytes
64/// each). Total size: 96 bytes on LP64.
65///
66/// Mirrors `hal_dmabuf_tensor_info` from `edgefirst-hal-capi` (`hal.h`).
67#[repr(C)]
68#[derive(Debug, Clone, Copy)]
69pub struct HalDmabufTensorInfo {
70    /// Buffer size in bytes.
71    pub size: usize,
72    /// Byte offset within the DMA-BUF.
73    pub offset: usize,
74    /// Tensor dimensions (up to [`HAL_DMABUF_MAX_NDIM`]).
75    pub shape: [usize; 8],
76    /// Number of valid entries in `shape`.
77    pub ndim: usize,
78    /// DMA-BUF file descriptor (borrowed — do **not** close).
79    pub fd: c_int,
80    /// Element data type.
81    pub dtype: HalDtype,
82}
83
84impl Default for HalDmabufTensorInfo {
85    fn default() -> Self {
86        // SAFETY: HalDmabufTensorInfo is a #[repr(C)] plain-old-data struct.
87        // The HAL ABI contract requires zero-initialization, so an all-zero
88        // bit-pattern is a valid "empty" state.
89        unsafe { std::mem::zeroed() }
90    }
91}
92
93// Ensure the shape array length matches HAL_DMABUF_MAX_NDIM.
94const _: () = assert!(
95    std::mem::size_of::<[usize; 8]>() == std::mem::size_of::<[usize; HAL_DMABUF_MAX_NDIM]>()
96);
97
98// Compile-time layout assertion: 11 × usize + 1 × c_int + 1 × HalDtype
99// = 11×8 + 4 + 4 = 96 bytes on LP64 with no internal padding.
100#[cfg(target_pointer_width = "64")]
101const _: () = assert!(std::mem::size_of::<HalDmabufTensorInfo>() == 96);
102
103// ---------------------------------------------------------------------------
104// HalDmaBufFunctions
105// ---------------------------------------------------------------------------
106
107/// Function pointers for the HAL Delegate DMA-BUF API.
108///
109/// Loaded at runtime from the delegate shared library. Use
110/// [`HalDmaBufFunctions::try_load`] to attempt loading.
111///
112/// The `hal_delegate_t` parameter (`*mut c_void`) is the `TfLiteDelegate*`
113/// cast to an opaque handle.
114#[derive(Debug)]
115pub struct HalDmaBufFunctions {
116    /// `hal_dmabuf_get_instance` — returns the delegate handle.
117    pub get_instance: unsafe extern "C" fn() -> *mut c_void,
118    /// `hal_dmabuf_is_supported` — returns 1 if supported, 0 otherwise.
119    pub is_supported: unsafe extern "C" fn(*mut c_void) -> c_int,
120    /// `hal_dmabuf_get_tensor_info` — fills `info` for the given tensor index.
121    pub get_tensor_info:
122        unsafe extern "C" fn(*mut c_void, c_int, *mut HalDmabufTensorInfo, usize) -> c_int,
123    /// `hal_dmabuf_sync_for_device` — flush CPU caches for device access.
124    pub sync_for_device: unsafe extern "C" fn(*mut c_void, c_int) -> c_int,
125    /// `hal_dmabuf_sync_for_cpu` — invalidate caches for CPU access.
126    pub sync_for_cpu: unsafe extern "C" fn(*mut c_void, c_int) -> c_int,
127}
128
129impl HalDmaBufFunctions {
130    /// Attempt to load all HAL DMA-BUF function pointers from a delegate library.
131    ///
132    /// Returns `None` if any required symbol is missing.
133    ///
134    /// # Safety
135    ///
136    /// The library must remain loaded for the lifetime of this struct.
137    #[must_use]
138    pub unsafe fn try_load(lib: &libloading::Library) -> Option<Self> {
139        unsafe {
140            Some(Self {
141                get_instance: *lib.get(b"hal_dmabuf_get_instance\0").ok()?,
142                is_supported: *lib.get(b"hal_dmabuf_is_supported\0").ok()?,
143                get_tensor_info: *lib.get(b"hal_dmabuf_get_tensor_info\0").ok()?,
144                sync_for_device: *lib.get(b"hal_dmabuf_sync_for_device\0").ok()?,
145                sync_for_cpu: *lib.get(b"hal_dmabuf_sync_for_cpu\0").ok()?,
146            })
147        }
148    }
149}
150
151// ---------------------------------------------------------------------------
152// HalCameraAdaptorFormatInfo
153// ---------------------------------------------------------------------------
154
155/// Camera adaptor format information returned by a delegate.
156///
157/// Describes a camera format's channel layout and V4L2 `FourCC` code.
158///
159/// Mirrors `hal_camera_adaptor_format_info` from `edgefirst-hal-capi`
160/// (`hal.h`).
161#[repr(C)]
162#[derive(Debug, Clone, Copy)]
163pub struct HalCameraAdaptorFormatInfo {
164    /// Number of input channels for this format.
165    pub input_channels: c_int,
166    /// Number of output channels for this format.
167    pub output_channels: c_int,
168    /// V4L2 `FourCC` code (NUL-padded, up to 8 bytes).
169    pub fourcc: [u8; 8],
170}
171
172impl Default for HalCameraAdaptorFormatInfo {
173    fn default() -> Self {
174        // SAFETY: HalCameraAdaptorFormatInfo is a #[repr(C)] plain-old-data
175        // struct. The HAL ABI contract requires zero-initialization, so an
176        // all-zero bit-pattern is a valid "empty" state.
177        unsafe { std::mem::zeroed() }
178    }
179}
180
181// ---------------------------------------------------------------------------
182// HalCameraAdaptorFunctions
183// ---------------------------------------------------------------------------
184
185/// Function pointers for the HAL Delegate Camera Adaptor API.
186///
187/// Loaded at runtime from the delegate shared library. Use
188/// [`HalCameraAdaptorFunctions::try_load`] to attempt loading.
189///
190/// The `hal_delegate_t` parameter (`*mut c_void`) is the `TfLiteDelegate*`
191/// cast to an opaque handle.
192#[derive(Debug)]
193pub struct HalCameraAdaptorFunctions {
194    /// `hal_camera_adaptor_is_supported` — returns 1 if format is supported.
195    pub is_supported: unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int,
196    /// `hal_camera_adaptor_get_format_info` — fills `info` for the given format.
197    pub get_format_info: unsafe extern "C" fn(
198        *mut c_void,
199        *const c_char,
200        *mut HalCameraAdaptorFormatInfo,
201        usize,
202    ) -> c_int,
203}
204
205impl HalCameraAdaptorFunctions {
206    /// Attempt to load all HAL Camera Adaptor function pointers from a delegate library.
207    ///
208    /// Returns `None` if any required symbol is missing.
209    ///
210    /// # Safety
211    ///
212    /// The library must remain loaded for the lifetime of this struct.
213    #[must_use]
214    pub unsafe fn try_load(lib: &libloading::Library) -> Option<Self> {
215        unsafe {
216            Some(Self {
217                is_supported: *lib.get(b"hal_camera_adaptor_is_supported\0").ok()?,
218                get_format_info: *lib.get(b"hal_camera_adaptor_get_format_info\0").ok()?,
219            })
220        }
221    }
222}
223
224// ---------------------------------------------------------------------------
225// Tests
226// ---------------------------------------------------------------------------
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn hal_dtype_discriminants() {
234        assert_eq!(HalDtype::U8 as u32, 0);
235        assert_eq!(HalDtype::I8 as u32, 1);
236        assert_eq!(HalDtype::U16 as u32, 2);
237        assert_eq!(HalDtype::I16 as u32, 3);
238        assert_eq!(HalDtype::U32 as u32, 4);
239        assert_eq!(HalDtype::I32 as u32, 5);
240        assert_eq!(HalDtype::U64 as u32, 6);
241        assert_eq!(HalDtype::I64 as u32, 7);
242        assert_eq!(HalDtype::F16 as u32, 8);
243        assert_eq!(HalDtype::F32 as u32, 9);
244        assert_eq!(HalDtype::F64 as u32, 10);
245    }
246
247    #[test]
248    fn tensor_info_default_is_zeroed() {
249        let info = HalDmabufTensorInfo::default();
250        assert_eq!(info.size, 0);
251        assert_eq!(info.offset, 0);
252        assert_eq!(info.ndim, 0);
253        assert_eq!(info.fd, 0);
254        assert!(info.shape.iter().all(|&s| s == 0));
255    }
256
257    #[test]
258    fn hal_dmabuf_max_ndim() {
259        assert_eq!(HAL_DMABUF_MAX_NDIM, 8);
260    }
261
262    #[test]
263    fn camera_adaptor_format_info_default_is_zeroed() {
264        let info = HalCameraAdaptorFormatInfo::default();
265        assert_eq!(info.input_channels, 0);
266        assert_eq!(info.output_channels, 0);
267        assert!(info.fourcc.iter().all(|&b| b == 0));
268    }
269
270    #[test]
271    fn camera_adaptor_format_info_clone_copy() {
272        let mut info = HalCameraAdaptorFormatInfo {
273            input_channels: 4,
274            output_channels: 3,
275            ..Default::default()
276        };
277        info.fourcc[..4].copy_from_slice(b"RGBA");
278
279        let copied = info;
280        assert_eq!(copied.input_channels, 4);
281        assert_eq!(copied.output_channels, 3);
282        assert_eq!(&copied.fourcc[..4], b"RGBA");
283    }
284
285    #[test]
286    fn camera_adaptor_format_info_debug() {
287        let info = HalCameraAdaptorFormatInfo::default();
288        let debug = format!("{info:?}");
289        assert!(debug.contains("HalCameraAdaptorFormatInfo"));
290        assert!(debug.contains("input_channels"));
291    }
292}