Skip to main content

vlfd_ffi/
lib.rs

1//! C FFI for vlfd-rs (minimal API)
2use std::cell::RefCell;
3use std::ffi::{CStr, CString};
4use std::os::raw::{c_char, c_int, c_uint, c_void};
5
6use vlfd_rs::{
7    Device,
8    HotplugEvent,
9    HotplugEventKind,
10    HotplugOptions,
11    HotplugRegistration,
12    IoSettings,
13    Programmer,
14};
15
16#[repr(C)]
17pub struct VlfdDevice {
18    inner: *mut c_void,
19}
20
21#[repr(C)]
22#[derive(Debug, Clone, Copy, Default)]
23pub struct VlfdHotplugOptions {
24    pub filter_vendor_id: bool,
25    pub vendor_id: u16,
26    pub filter_product_id: bool,
27    pub product_id: u16,
28    pub filter_class_code: bool,
29    pub class_code: u8,
30    pub enumerate_existing: bool,
31}
32
33#[repr(C)]
34#[derive(Debug, Clone, Copy, Default)]
35pub struct VlfdOptionalU16 {
36    pub has_value: bool,
37    pub value: u16,
38}
39
40#[repr(C)]
41#[derive(Debug, Clone, Copy, Default)]
42pub struct VlfdOptionalU8 {
43    pub has_value: bool,
44    pub value: u8,
45}
46
47#[repr(C)]
48#[derive(Debug, Clone, Copy, Default)]
49pub struct VlfdSliceU8 {
50    pub data: *const u8,
51    pub len: usize,
52}
53
54#[repr(C)]
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum VlfdHotplugEventKind {
57    Arrived = 0,
58    Left = 1,
59}
60
61impl From<HotplugEventKind> for VlfdHotplugEventKind {
62    fn from(value: HotplugEventKind) -> Self {
63        match value {
64            HotplugEventKind::Arrived => VlfdHotplugEventKind::Arrived,
65            HotplugEventKind::Left => VlfdHotplugEventKind::Left,
66        }
67    }
68}
69
70#[repr(C)]
71#[derive(Debug, Clone, Copy, Default)]
72pub struct VlfdHotplugDeviceInfo {
73    pub bus_number: u8,
74    pub address: u8,
75    pub port_numbers: VlfdSliceU8,
76    pub vendor_id: VlfdOptionalU16,
77    pub product_id: VlfdOptionalU16,
78    pub class_code: VlfdOptionalU8,
79    pub sub_class_code: VlfdOptionalU8,
80    pub protocol_code: VlfdOptionalU8,
81}
82
83#[repr(C)]
84#[derive(Debug, Clone, Copy)]
85pub struct VlfdHotplugEvent {
86    pub kind: VlfdHotplugEventKind,
87    pub device: VlfdHotplugDeviceInfo,
88}
89
90#[repr(C)]
91pub struct VlfdHotplugRegistration {
92    inner: *mut c_void,
93}
94
95pub type VlfdHotplugCallback =
96    Option<unsafe extern "C" fn(user_data: *mut c_void, event: *const VlfdHotplugEvent)>;
97
98thread_local! {
99    static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
100}
101
102fn set_last_error(message: &str) {
103    let cstr =
104        CString::new(message).unwrap_or_else(|_| CString::new("<invalid utf8 in error>").unwrap());
105    LAST_ERROR.with(|slot| {
106        *slot.borrow_mut() = Some(cstr);
107    });
108}
109
110fn opt_u16(value: Option<u16>) -> VlfdOptionalU16 {
111    match value {
112        Some(v) => VlfdOptionalU16 {
113            has_value: true,
114            value: v,
115        },
116        None => VlfdOptionalU16 {
117            has_value: false,
118            value: 0,
119        },
120    }
121}
122
123fn opt_u8(value: Option<u8>) -> VlfdOptionalU8 {
124    match value {
125        Some(v) => VlfdOptionalU8 {
126            has_value: true,
127            value: v,
128        },
129        None => VlfdOptionalU8 {
130            has_value: false,
131            value: 0,
132        },
133    }
134}
135
136fn hotplug_options_from_ffi(options: Option<&VlfdHotplugOptions>) -> HotplugOptions {
137    let mut result = HotplugOptions::default();
138    if let Some(opts) = options {
139        if opts.filter_vendor_id {
140            result.vendor_id = Some(opts.vendor_id);
141        }
142        if opts.filter_product_id {
143            result.product_id = Some(opts.product_id);
144        }
145        if opts.filter_class_code {
146            result.class_code = Some(opts.class_code);
147        }
148        result.enumerate = opts.enumerate_existing;
149    }
150    result
151}
152
153fn hotplug_event_to_ffi(event: HotplugEvent) -> (VlfdHotplugEvent, Vec<u8>) {
154    let kind = event.kind;
155    let device = event.device;
156
157    let bus_number = device.bus_number;
158    let address = device.address;
159    let vendor_id = device.vendor_id;
160    let product_id = device.product_id;
161    let class_code = device.class_code;
162    let sub_class_code = device.sub_class_code;
163    let protocol_code = device.protocol_code;
164    let port_numbers = device.port_numbers;
165
166    let info = VlfdHotplugDeviceInfo {
167        bus_number,
168        address,
169        port_numbers: VlfdSliceU8 {
170            data: port_numbers.as_ptr(),
171            len: port_numbers.len(),
172        },
173        vendor_id: opt_u16(vendor_id),
174        product_id: opt_u16(product_id),
175        class_code: opt_u8(class_code),
176        sub_class_code: opt_u8(sub_class_code),
177        protocol_code: opt_u8(protocol_code),
178    };
179
180    (
181        VlfdHotplugEvent {
182            kind: kind.into(),
183            device: info,
184        },
185        port_numbers,
186    )
187}
188
189#[unsafe(no_mangle)]
190pub extern "C" fn vlfd_get_last_error_message() -> *const c_char {
191    static EMPTY: &[u8] = b"\0";
192    LAST_ERROR.with(|slot| match &*slot.borrow() {
193        Some(s) => s.as_ptr(),
194        None => EMPTY.as_ptr() as *const c_char,
195    })
196}
197
198#[unsafe(no_mangle)]
199pub extern "C" fn vlfd_hotplug_options_default() -> VlfdHotplugOptions {
200    VlfdHotplugOptions::default()
201}
202
203#[unsafe(no_mangle)]
204pub extern "C" fn vlfd_hotplug_register(
205    options: *const VlfdHotplugOptions,
206    callback: VlfdHotplugCallback,
207    user_data: *mut c_void,
208) -> *mut VlfdHotplugRegistration {
209    let callback = match callback {
210        Some(cb) => cb,
211        None => {
212            set_last_error("null callback passed to vlfd_hotplug_register");
213            return std::ptr::null_mut();
214        }
215    };
216
217    let options_ref = unsafe { options.as_ref() };
218    let rust_options = hotplug_options_from_ffi(options_ref);
219    let user_data_value = user_data as usize;
220
221    let device = match Device::new() {
222        Ok(dev) => dev,
223        Err(err) => {
224            set_last_error(&format!("Device::new failed: {}", err));
225            return std::ptr::null_mut();
226        }
227    };
228
229    let registration = match device.usb().register_hotplug_callback(rust_options, move |event| {
230        let (ffi_event, ports) = hotplug_event_to_ffi(event);
231        unsafe {
232            let userdata_ptr = user_data_value as *mut c_void;
233            callback(userdata_ptr, &ffi_event as *const VlfdHotplugEvent);
234        }
235        drop(ports);
236    }) {
237        Ok(reg) => reg,
238        Err(err) => {
239            set_last_error(&format!("register_hotplug_callback failed: {}", err));
240            return std::ptr::null_mut();
241        }
242    };
243
244    let boxed_registration = Box::new(registration);
245    let handle = Box::new(VlfdHotplugRegistration {
246        inner: Box::into_raw(boxed_registration) as *mut c_void,
247    });
248
249    Box::into_raw(handle)
250}
251
252#[unsafe(no_mangle)]
253pub extern "C" fn vlfd_hotplug_unregister(registration: *mut VlfdHotplugRegistration) -> c_int {
254    if registration.is_null() {
255        set_last_error("null registration in vlfd_hotplug_unregister");
256        return -1;
257    }
258
259    unsafe {
260        let wrapper = Box::from_raw(registration);
261        if wrapper.inner.is_null() {
262            set_last_error("invalid registration handle");
263            return -1;
264        }
265        let inner = Box::from_raw(wrapper.inner as *mut HotplugRegistration);
266        drop(inner);
267    }
268
269    0
270}
271
272#[unsafe(no_mangle)]
273pub extern "C" fn vlfd_io_open() -> *mut VlfdDevice {
274    let mut dev = match Device::connect() {
275        Ok(d) => d,
276        Err(e) => {
277            set_last_error(&format!("connect failed: {}", e));
278            return std::ptr::null_mut();
279        }
280    };
281
282    if let Err(e) = dev.enter_io_mode(&IoSettings::default()) {
283        set_last_error(&format!("enter_io_mode failed: {}", e));
284        return std::ptr::null_mut();
285    }
286
287    Box::into_raw(Box::new(VlfdDevice {
288        inner: Box::into_raw(Box::new(dev)) as *mut c_void,
289    }))
290}
291
292#[unsafe(no_mangle)]
293pub extern "C" fn vlfd_io_write_read(
294    device: *mut VlfdDevice,
295    write_buffer: *mut u16,
296    read_buffer: *mut u16,
297    word_len: c_uint,
298) -> c_int {
299    if device.is_null() || write_buffer.is_null() || read_buffer.is_null() {
300        set_last_error("null pointer passed to vlfd_io_write_read");
301        return -1;
302    }
303
304    let dev = unsafe { &mut *((*device).inner as *mut Device) };
305    let len = word_len as usize;
306    let tx = unsafe { std::slice::from_raw_parts_mut(write_buffer, len) };
307    let rx = unsafe { std::slice::from_raw_parts_mut(read_buffer, len) };
308
309    match dev.transfer_io(tx, rx) {
310        Ok(_) => 0,
311        Err(e) => {
312            set_last_error(&format!("transfer_io failed: {}", e));
313            -1
314        }
315    }
316}
317
318#[unsafe(no_mangle)]
319pub extern "C" fn vlfd_io_close(device: *mut VlfdDevice) -> c_int {
320    if device.is_null() {
321        set_last_error("null device in vlfd_io_close");
322        return -1;
323    }
324
325    unsafe {
326        let wrapper = Box::from_raw(device);
327        let dev_ptr = wrapper.inner as *mut Device;
328        if dev_ptr.is_null() {
329            set_last_error("invalid device handle");
330            return -1;
331        }
332        let mut dev = Box::from_raw(dev_ptr);
333
334        if let Err(e) = dev.exit_io_mode() {
335            set_last_error(&format!("exit_io_mode failed: {}", e));
336        }
337        if let Err(e) = dev.close() {
338            set_last_error(&format!("close failed: {}", e));
339        }
340        // Boxes dropped here free both wrapper and device
341    }
342
343    0
344}
345
346#[unsafe(no_mangle)]
347pub extern "C" fn vlfd_program_fpga(bitfile_path: *const c_char) -> c_int {
348    if bitfile_path.is_null() {
349        set_last_error("null bitfile_path");
350        return -1;
351    }
352
353    let path_cstr = unsafe { CStr::from_ptr(bitfile_path) };
354    let path = match path_cstr.to_str() {
355        Ok(s) => std::path::Path::new(s),
356        Err(_) => {
357            set_last_error("bitfile_path is not valid UTF-8");
358            return -1;
359        }
360    };
361
362    let mut prog = match Programmer::connect() {
363        Ok(p) => p,
364        Err(e) => {
365            set_last_error(&format!("programmer connect failed: {}", e));
366            return -1;
367        }
368    };
369
370    if let Err(e) = prog.program(path) {
371        let _ = prog.close();
372        set_last_error(&format!("program failed: {}", e));
373        return -1;
374    }
375
376    match prog.close() {
377        Ok(_) => 0,
378        Err(e) => {
379            set_last_error(&format!("programmer close failed: {}", e));
380            -1
381        }
382    }
383}