Fix ARM compilation: vendor libhackrf with c_char portability fix

The upstream libhackrf v0.1.1 crate fails to compile on ARM because
c_char is u8 on ARM (not i8 like on x86). The transmute at lib.rs:102
explicitly casts &[i8] -> &[u8], but on ARM the source is already &[u8],
causing a type mismatch.

Vendored the crate at vendor/libhackrf/ with the transmute replaced by
slice::from_raw_parts with a pointer cast, which works on both
architectures. Also cleaned up transmute calls in transfer.rs.

Fixes #1
This commit is contained in:
KaraZajac
2026-03-22 11:14:41 -04:00
parent 295e4c4d4a
commit 84b75ee6b5
9 changed files with 473 additions and 2 deletions
Generated
-2
View File
@@ -482,8 +482,6 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libhackrf"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e640122f67f490129cd3ab1517149b4091eeca0a14de3df7510626df258196b9"
dependencies = [
"num-complex",
]
+4
View File
@@ -54,6 +54,10 @@ atty = "0.2"
[build-dependencies]
pkg-config = "0.3"
[patch.crates-io]
# Vendored with ARM fix: c_char is u8 on ARM, so the upstream transmute fails
libhackrf = { path = "vendor/libhackrf" }
[profile.release]
opt-level = 3
lto = true
+16
View File
@@ -0,0 +1,16 @@
[package]
edition = "2021"
name = "libhackrf"
version = "0.1.1"
authors = ["Connor Slade <connor@connorcode.com>"]
description = "A modern libhackrf wrapper that supports receiving and transmitting. (Patched for ARM cross-compilation)"
license = "MIT"
repository = "https://github.com/connorslade/libhackrf-rs"
[lib]
name = "libhackrf"
path = "src/lib.rs"
doctest = false
[dependencies.num-complex]
version = "0.4.6"
+27
View File
@@ -0,0 +1,27 @@
pub enum DeviceType {
Jellybean = 0,
Jawbreaker = 1,
/// HackRF One prior to r9
Hackrf1Og = 2,
Rad1O = 3,
Hackrf1R9 = 4,
/// Tried detection but did not recognize board
Unrecognized = 0xFE,
/// detection not yet attempted
Undetected = 0xFF,
}
impl DeviceType {
pub fn from_id(id: u8) -> Self {
match id {
0 => DeviceType::Jellybean,
1 => DeviceType::Jawbreaker,
2 => DeviceType::Hackrf1Og,
3 => DeviceType::Rad1O,
4 => DeviceType::Hackrf1R9,
0xFE => DeviceType::Unrecognized,
0xFF => DeviceType::Undetected,
_ => DeviceType::Undetected,
}
}
}
+47
View File
@@ -0,0 +1,47 @@
use std::{
error::Error,
fmt::{self, Display},
};
pub type Result<T> = std::result::Result<T, HackrfError>;
#[derive(Debug)]
pub enum HackrfError {
InvalidParam = -2,
NotFound = -5,
Busy = -6,
NoMem = -11,
Libusb = -1000,
Thread = -1001,
StreamingThreadErr = -1002,
StreamingStopped = -1003,
StreamingExitCalled = -1004,
Other = -9999,
}
impl HackrfError {
pub fn from_id(id: i32) -> Result<()> {
Err(match id {
0 | 1 => return Ok(()),
-2 => HackrfError::InvalidParam,
-5 => HackrfError::NotFound,
-6 => HackrfError::Busy,
-11 => HackrfError::NoMem,
-1000 => HackrfError::Libusb,
-1001 => HackrfError::Thread,
-1002 => HackrfError::StreamingThreadErr,
-1003 => HackrfError::StreamingStopped,
-1004 => HackrfError::StreamingExitCalled,
-9999 => HackrfError::Other,
_ => HackrfError::Other,
})
}
}
impl Display for HackrfError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("{self:?}"))
}
}
impl Error for HackrfError {}
+94
View File
@@ -0,0 +1,94 @@
#![allow(improper_ctypes)]
use std::ffi::{c_char, c_double, c_int, c_uchar, c_uint, c_ulonglong, c_void};
#[repr(C)]
pub struct HackrfDevice;
#[repr(C)]
pub struct HackrfTransfer {
pub device: *mut HackrfDevice,
pub buffer: *mut c_uchar,
pub buffer_length: c_int,
pub valid_length: c_int,
pub rx_ctx: *mut c_void,
pub tx_ctx: *mut c_void,
}
#[derive(Default)]
#[repr(C)]
pub struct SerialNumber {
pub part_id: [c_uint; 2],
pub serial_no: [c_uint; 4],
}
#[link(name = "hackrf")]
extern "C" {
pub fn hackrf_init() -> c_int;
pub fn hackrf_exit() -> c_int;
pub fn hackrf_open(device: *mut *mut HackrfDevice) -> c_int;
pub fn hackrf_close(device: *mut HackrfDevice) -> c_int;
pub fn hackrf_start_rx(
device: *mut HackrfDevice,
callback: extern "C" fn(*mut HackrfTransfer) -> c_int,
rx_ctx: *mut c_void,
) -> c_int;
pub fn hackrf_stop_rx(device: *mut HackrfDevice) -> c_int;
pub fn hackrf_start_tx(
device: *mut HackrfDevice,
callback: extern "C" fn(*mut HackrfTransfer) -> c_int,
tx_ctx: *mut c_void,
) -> c_int;
pub fn hackrf_stop_tx(device: *mut HackrfDevice) -> c_int;
pub fn hackrf_is_streaming(device: *mut HackrfDevice) -> c_int;
pub fn hackrf_set_baseband_filter_bandwidth(
device: *mut HackrfDevice,
bandwidth_hz: c_uint,
) -> c_int;
pub fn hackrf_board_id_read(device: *mut HackrfDevice, value: *mut c_uchar) -> c_int;
pub fn hackrf_version_string_read(
device: *mut HackrfDevice,
version: *mut c_char,
length: c_uchar,
) -> c_int;
pub fn hackrf_board_partid_serialno_read(
device: *mut HackrfDevice,
read_partid_serialno: *mut SerialNumber,
) -> c_int;
pub fn hackrf_set_freq(device: *mut HackrfDevice, freq_hz: c_ulonglong) -> c_int;
pub fn hackrf_set_freq_explicit(
device: *mut HackrfDevice,
if_freq_hz: c_ulonglong,
lo_freq_hz: c_ulonglong,
path: c_uint,
) -> c_int;
pub fn hackrf_set_sample_rate_manual(
device: *mut HackrfDevice,
freq_hz: c_uint,
divider: c_uint,
) -> c_int;
pub fn hackrf_set_sample_rate(device: *mut HackrfDevice, freq_hz: c_double) -> c_int;
pub fn hackrf_set_amp_enable(device: *mut HackrfDevice, value: c_uchar) -> c_int;
pub fn hackrf_set_lna_gain(device: *mut HackrfDevice, value: c_uint) -> c_int;
pub fn hackrf_set_vga_gain(device: *mut HackrfDevice, value: c_uint) -> c_int;
pub fn hackrf_set_txvga_gain(device: *mut HackrfDevice, value: c_uint) -> c_int;
pub fn hackrf_set_antenna_enable(device: *mut HackrfDevice, value: c_uchar) -> c_int;
pub fn hackrf_error_name(errcode: c_int) -> *const c_char;
pub fn hackrf_board_id_name(hackrf_board_id: c_uchar) -> *const c_char;
pub fn hackrf_filter_path_name(path: c_uint) -> *const c_char;
pub fn hackrf_compute_baseband_filter_bw_round_down_lt(bandwidth_hz: c_uint) -> c_uint;
pub fn hackrf_compute_baseband_filter_bw(bandwidth_hz: c_uint) -> c_uint;
}
+210
View File
@@ -0,0 +1,210 @@
use std::{
any::Any,
ffi::c_void,
mem, ptr, slice,
sync::{
atomic::{AtomicPtr, AtomicUsize, Ordering},
Arc,
},
};
mod enums;
pub mod error;
pub mod ffi;
pub use enums::DeviceType;
mod transfer;
pub mod util;
use error::{HackrfError, Result};
use ffi::SerialNumber;
use transfer::{rx_callback, tx_callback, ReceiveCallback, TransferContext, TransmitCallback};
pub mod exports {
pub use num_complex;
}
static DEVICE_COUNT: AtomicUsize = AtomicUsize::new(0);
/// A HackRf device.
#[derive(Clone)]
pub struct HackRf {
inner: Arc<HackRfInner>,
}
struct HackRfInner {
device: *mut ffi::HackrfDevice,
user_data: AtomicPtr<c_void>,
}
impl HackRf {
/// Connects to a HackRF device.
pub fn open() -> Result<HackRf> {
if DEVICE_COUNT.fetch_add(1, Ordering::Relaxed) == 0 {
unsafe { HackrfError::from_id(ffi::hackrf_init())? }
}
let mut device = std::ptr::null_mut();
unsafe { HackrfError::from_id(ffi::hackrf_open(&mut device))? }
Ok(Self {
inner: Arc::new(HackRfInner {
device,
user_data: AtomicPtr::new(ptr::null_mut()),
}),
})
}
/// Gets the internial representation of the HackRF device. This can be used
/// with unsafe FFI functions if needed.
#[inline(always)]
pub fn device(&self) -> *mut ffi::HackrfDevice {
self.inner.device
}
/// Gets the device serial number.
pub fn get_serial_number(&self) -> Result<SerialNumber> {
let mut serial_number = SerialNumber::default();
unsafe {
HackrfError::from_id(ffi::hackrf_board_partid_serialno_read(
self.device(),
&mut serial_number,
))?
}
Ok(serial_number)
}
/// Read hackrf_board_id from a device and convert it to a DeviceType.
pub fn get_device_type(&self) -> Result<DeviceType> {
let mut value = 0;
unsafe { HackrfError::from_id(ffi::hackrf_board_id_read(self.device(), &mut value)) }?;
Ok(DeviceType::from_id(value))
}
/// Read HackRF firmware version as a string.
pub fn version(&self) -> String {
let mut version = vec![0; 32];
unsafe {
ffi::hackrf_version_string_read(
self.device(),
version.as_mut_ptr(),
version.len() as u8,
);
}
let end = version
.iter()
.position(|&x| x == 0)
.unwrap_or(version.len());
// Use from_raw_parts to handle both c_char=i8 (x86) and c_char=u8 (ARM)
let version = unsafe { slice::from_raw_parts(version.as_ptr() as *const u8, end) };
String::from_utf8_lossy(version).into_owned()
}
/// Sets the center frequency in Hz.
pub fn set_freq(&self, freq: u64) -> Result<()> {
unsafe { HackrfError::from_id(ffi::hackrf_set_freq(self.device(), freq)) }
}
/// Sets the sample rate in Hz.
pub fn set_sample_rate(&self, sample_rate: u32) -> Result<()> {
unsafe {
HackrfError::from_id(ffi::hackrf_set_sample_rate_manual(
self.device(),
sample_rate,
1,
))
}
}
/// Sets the state of the externial amplifier.
pub fn set_amp_enable(&self, enable: bool) -> Result<()> {
unsafe { HackrfError::from_id(ffi::hackrf_set_amp_enable(self.device(), enable as u8)) }
}
/// Low noise amplifier gain.
/// Between 0d and 40d in steps of 8dB.
pub fn set_lna_gain(&self, gain: u32) -> Result<()> {
unsafe { HackrfError::from_id(ffi::hackrf_set_lna_gain(self.device(), gain)) }
}
/// Variable gain amplifier. Range 0-62 (step 2dB).
pub fn set_rxvga_gain(&self, gain: u32) -> Result<()> {
unsafe { HackrfError::from_id(ffi::hackrf_set_vga_gain(self.device(), gain)) }
}
/// Transmit variable gain amplifier. Range 0-47 (step 1dB).
pub fn set_txvga_gain(&self, gain: u32) -> Result<()> {
unsafe { HackrfError::from_id(ffi::hackrf_set_txvga_gain(self.device(), gain)) }
}
pub fn set_baseband_filter_bandwidth(&self, bandwidth_hz: u32) -> Result<()> {
unsafe {
HackrfError::from_id(ffi::hackrf_set_baseband_filter_bandwidth(
self.device(),
ffi::hackrf_compute_baseband_filter_bw(bandwidth_hz),
))
}
}
/// Starts transmitting samples from the device.
pub fn start_tx(&self, callback: TransmitCallback, user_data: impl Any) -> Result<()> {
let context = TransferContext::new(callback, self.clone(), Box::new(user_data));
let callback = Box::leak(Box::new(context)) as *mut _ as *mut _;
self.inner.user_data.store(callback, Ordering::Relaxed);
unsafe { HackrfError::from_id(ffi::hackrf_start_tx(self.device(), tx_callback, callback)) }
}
/// Stops the current transmit operation.
pub fn stop_tx(&self) -> Result<()> {
let user_data = &self.inner.user_data;
let callback = user_data.swap(ptr::null_mut(), Ordering::Relaxed);
if !callback.is_null() {
let callback = unsafe { Box::from_raw(callback as *mut fn(*mut ffi::HackrfTransfer)) };
drop(callback);
}
unsafe { HackrfError::from_id(ffi::hackrf_stop_tx(self.device())) }
}
/// Starts receiving samples from the device.
pub fn start_rx(&self, callback: ReceiveCallback, user_data: impl Any + Sync) -> Result<()> {
let context = TransferContext::new(callback, self.clone(), Box::new(user_data));
let callback = Box::leak(Box::new(context)) as *mut _ as *mut _;
self.inner.user_data.store(callback, Ordering::Relaxed);
unsafe { HackrfError::from_id(ffi::hackrf_start_rx(self.device(), rx_callback, callback)) }
}
/// Stops the current receive operation.
pub fn stop_rx(&self) -> Result<()> {
let user_data = &self.inner.user_data;
let callback = user_data.swap(ptr::null_mut(), Ordering::Relaxed);
if !callback.is_null() {
let callback = unsafe { Box::from_raw(callback as *mut fn(*mut ffi::HackrfTransfer)) };
drop(callback);
}
unsafe { HackrfError::from_id(ffi::hackrf_stop_rx(self.device())) }
}
/// Returns true if the device is currently streaming samples (transmitting or receiving).
pub fn is_streaming(&self) -> bool {
unsafe { ffi::hackrf_is_streaming(self.device()) != 0 }
}
}
unsafe impl Send for HackRfInner {}
unsafe impl Sync for HackRfInner {}
impl Drop for HackRf {
fn drop(&mut self) {
let _ = unsafe { HackrfError::from_id(ffi::hackrf_close(self.device())) };
if DEVICE_COUNT.fetch_sub(1, Ordering::Relaxed) == 1 {
let _ = unsafe { HackrfError::from_id(ffi::hackrf_exit()) };
}
}
}
+54
View File
@@ -0,0 +1,54 @@
use std::{any::Any, slice};
use num_complex::Complex;
use super::{ffi, HackRf};
pub type TransmitCallback = fn(hack_rf: &HackRf, samples: &mut [Complex<i8>], user: &dyn Any);
pub type ReceiveCallback = fn(hack_rf: &HackRf, samples: &[Complex<i8>], user: &dyn Any);
pub struct TransferContext<Callback> {
callback: Callback,
hackrf: HackRf,
user_data: Box<dyn Any>,
}
impl<Callback> TransferContext<Callback> {
pub(super) fn new(callback: Callback, hackrf: HackRf, user_data: Box<dyn Any>) -> Self {
Self {
callback,
hackrf,
user_data,
}
}
}
pub(super) extern "C" fn tx_callback(transfer: *mut ffi::HackrfTransfer) -> i32 {
unsafe {
let transfer = &mut *transfer;
let context = &*(transfer.tx_ctx as *mut TransferContext<TransmitCallback>);
let buffer = slice::from_raw_parts_mut(
transfer.buffer as *mut Complex<i8>,
transfer.valid_length as usize / 2,
);
(context.callback)(&context.hackrf, buffer, &*context.user_data);
}
0
}
pub(super) extern "C" fn rx_callback(transfer: *mut ffi::HackrfTransfer) -> i32 {
unsafe {
let transfer = &*transfer;
let context = &*(transfer.rx_ctx as *mut TransferContext<ReceiveCallback>);
let buffer = slice::from_raw_parts(
transfer.buffer as *const Complex<i8>,
transfer.valid_length as usize / 2,
);
(context.callback)(&context.hackrf, buffer, &*context.user_data);
}
0
}
+21
View File
@@ -0,0 +1,21 @@
use num_complex::Complex;
pub trait ToComplexI8 {
fn to_i8(self) -> Complex<i8>;
}
pub trait ToComplexF32 {
fn to_f32(self) -> Complex<f32>;
}
impl ToComplexI8 for Complex<f32> {
fn to_i8(self) -> Complex<i8> {
Complex::new((self.re * 127.0) as i8, (self.im * 127.0) as i8)
}
}
impl ToComplexF32 for Complex<i8> {
fn to_f32(self) -> Complex<f32> {
Complex::new(self.re as f32 / 127.0, self.im as f32 / 127.0)
}
}