diff --git a/Cargo.lock b/Cargo.lock index dc4ea56..c0b866f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 1493db8..00eb8f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/vendor/libhackrf/Cargo.toml b/vendor/libhackrf/Cargo.toml new file mode 100644 index 0000000..95776e5 --- /dev/null +++ b/vendor/libhackrf/Cargo.toml @@ -0,0 +1,16 @@ +[package] +edition = "2021" +name = "libhackrf" +version = "0.1.1" +authors = ["Connor Slade "] +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" diff --git a/vendor/libhackrf/src/enums.rs b/vendor/libhackrf/src/enums.rs new file mode 100644 index 0000000..18e542b --- /dev/null +++ b/vendor/libhackrf/src/enums.rs @@ -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, + } + } +} diff --git a/vendor/libhackrf/src/error.rs b/vendor/libhackrf/src/error.rs new file mode 100644 index 0000000..1b726f0 --- /dev/null +++ b/vendor/libhackrf/src/error.rs @@ -0,0 +1,47 @@ +use std::{ + error::Error, + fmt::{self, Display}, +}; + +pub type Result = std::result::Result; + +#[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 {} diff --git a/vendor/libhackrf/src/ffi.rs b/vendor/libhackrf/src/ffi.rs new file mode 100644 index 0000000..835c5b5 --- /dev/null +++ b/vendor/libhackrf/src/ffi.rs @@ -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; +} diff --git a/vendor/libhackrf/src/lib.rs b/vendor/libhackrf/src/lib.rs new file mode 100644 index 0000000..cfcb469 --- /dev/null +++ b/vendor/libhackrf/src/lib.rs @@ -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, +} + +struct HackRfInner { + device: *mut ffi::HackrfDevice, + user_data: AtomicPtr, +} + +impl HackRf { + /// Connects to a HackRF device. + pub fn open() -> Result { + 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 { + 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 { + 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()) }; + } + } +} diff --git a/vendor/libhackrf/src/transfer.rs b/vendor/libhackrf/src/transfer.rs new file mode 100644 index 0000000..ba4e4b5 --- /dev/null +++ b/vendor/libhackrf/src/transfer.rs @@ -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], user: &dyn Any); +pub type ReceiveCallback = fn(hack_rf: &HackRf, samples: &[Complex], user: &dyn Any); + +pub struct TransferContext { + callback: Callback, + hackrf: HackRf, + user_data: Box, +} + +impl TransferContext { + pub(super) fn new(callback: Callback, hackrf: HackRf, user_data: Box) -> 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); + + let buffer = slice::from_raw_parts_mut( + transfer.buffer as *mut Complex, + 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); + + let buffer = slice::from_raw_parts( + transfer.buffer as *const Complex, + transfer.valid_length as usize / 2, + ); + (context.callback)(&context.hackrf, buffer, &*context.user_data); + } + + 0 +} diff --git a/vendor/libhackrf/src/util.rs b/vendor/libhackrf/src/util.rs new file mode 100644 index 0000000..4691aa4 --- /dev/null +++ b/vendor/libhackrf/src/util.rs @@ -0,0 +1,21 @@ +use num_complex::Complex; + +pub trait ToComplexI8 { + fn to_i8(self) -> Complex; +} + +pub trait ToComplexF32 { + fn to_f32(self) -> Complex; +} + +impl ToComplexI8 for Complex { + fn to_i8(self) -> Complex { + Complex::new((self.re * 127.0) as i8, (self.im * 127.0) as i8) + } +} + +impl ToComplexF32 for Complex { + fn to_f32(self) -> Complex { + Complex::new(self.re as f32 / 127.0, self.im as f32 / 127.0) + } +}