1use 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 }
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}