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:
Generated
-2
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+16
@@ -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"
|
||||
Vendored
+27
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+47
@@ -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 {}
|
||||
Vendored
+94
@@ -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;
|
||||
}
|
||||
Vendored
+210
@@ -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()) };
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+54
@@ -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
|
||||
}
|
||||
Vendored
+21
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user