mod status;
pub mod sys;
use std::{
ffi::CStr,
fmt,
fs::{
File,
OpenOptions,
},
ops::Range,
os::{
fd::{
AsFd,
BorrowedFd,
},
unix::{
fs::{
FileTypeExt,
OpenOptionsExt,
},
io::{
AsRawFd,
RawFd,
},
},
},
};
pub use status::FeStatus;
use self::sys::*;
use crate::error::{
Error,
Result,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DtvProperty {
Frequency(u32),
Modulation(Modulation),
BandwidthHz(u32),
Inversion(Inversion),
SymbolRate(u32),
InnerFec(Fec),
Voltage(SecVoltage),
Tone(SecTone),
Pilot(Pilot),
Rolloff(Rolloff),
DeliverySystem(DeliverySystem),
CodeRateHp(Fec),
CodeRateLp(Fec),
GuardInterval(GuardInterval),
TransmissionMode(TransmitMode),
Hierarchy(Hierarchy),
StreamId(u32),
Tune,
Clear,
}
impl DtvProperty {
pub fn to_raw(&self) -> DtvPropertyRaw {
match *self {
DtvProperty::Frequency(v) => DtvPropertyRaw::new(DTV_FREQUENCY, v),
DtvProperty::Modulation(v) => DtvPropertyRaw::new(DTV_MODULATION, v as u32),
DtvProperty::BandwidthHz(v) => DtvPropertyRaw::new(DTV_BANDWIDTH_HZ, v),
DtvProperty::Inversion(v) => DtvPropertyRaw::new(DTV_INVERSION, v as u32),
DtvProperty::SymbolRate(v) => DtvPropertyRaw::new(DTV_SYMBOL_RATE, v),
DtvProperty::InnerFec(v) => DtvPropertyRaw::new(DTV_INNER_FEC, v as u32),
DtvProperty::Voltage(v) => DtvPropertyRaw::new(DTV_VOLTAGE, v as u32),
DtvProperty::Tone(v) => DtvPropertyRaw::new(DTV_TONE, v as u32),
DtvProperty::Pilot(v) => DtvPropertyRaw::new(DTV_PILOT, v as u32),
DtvProperty::Rolloff(v) => DtvPropertyRaw::new(DTV_ROLLOFF, v as u32),
DtvProperty::DeliverySystem(v) => DtvPropertyRaw::new(DTV_DELIVERY_SYSTEM, v as u32),
DtvProperty::CodeRateHp(v) => DtvPropertyRaw::new(DTV_CODE_RATE_HP, v as u32),
DtvProperty::CodeRateLp(v) => DtvPropertyRaw::new(DTV_CODE_RATE_LP, v as u32),
DtvProperty::GuardInterval(v) => DtvPropertyRaw::new(DTV_GUARD_INTERVAL, v as u32),
DtvProperty::TransmissionMode(v) => {
DtvPropertyRaw::new(DTV_TRANSMISSION_MODE, v as u32)
}
DtvProperty::Hierarchy(v) => DtvPropertyRaw::new(DTV_HIERARCHY, v as u32),
DtvProperty::StreamId(v) => DtvPropertyRaw::new(DTV_STREAM_ID, v),
DtvProperty::Tune => DtvPropertyRaw::new(DTV_TUNE, 0),
DtvProperty::Clear => DtvPropertyRaw::new(DTV_CLEAR, 0),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ApiVersion {
pub major: u8,
pub minor: u8,
}
impl fmt::Display for ApiVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.{}", self.major, self.minor)
}
}
#[repr(C)]
struct DtvProperties {
num: u32,
props: *mut DtvPropertyRaw,
}
#[derive(Debug)]
pub struct FeDevice {
file: File,
api_version: ApiVersion,
name: String,
delivery_system_list: Vec<DeliverySystem>,
frequency_range: Range<u32>,
symbolrate_range: Range<u32>,
caps: FeCaps,
}
impl AsRawFd for FeDevice {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl AsFd for FeDevice {
fn as_fd(&self) -> BorrowedFd<'_> {
self.file.as_fd()
}
}
impl FeDevice {
pub fn clear(&self) -> Result<()> {
let cmdseq = [
DtvProperty::Voltage(SecVoltage::Off),
DtvProperty::Tone(SecTone::Off),
DtvProperty::Clear,
];
self.set_properties(&cmdseq)?;
let mut event = FeEvent::default();
for _ in 0 .. FE_MAX_EVENT {
if self.get_event(&mut event).is_err() {
break;
}
}
Ok(())
}
fn get_info(&mut self) -> Result<()> {
let mut feinfo = FeInfo::default();
nix::ioctl_read!(
#[inline]
ioctl_call,
b'o',
61,
FeInfo
);
unsafe { ioctl_call(self.as_raw_fd(), &mut feinfo as *mut _) }?;
if let Ok(name) = CStr::from_bytes_until_nul(&feinfo.name)
&& let Ok(name) = name.to_str()
{
self.name = name.to_owned();
}
self.frequency_range = feinfo.frequency_min .. feinfo.frequency_max;
self.symbolrate_range = feinfo.symbol_rate_min .. feinfo.symbol_rate_max;
self.caps = FeCaps::from_bits_retain(feinfo.caps);
let mut cmdseq = [
DtvPropertyRaw::new(DTV_API_VERSION, 0),
DtvPropertyRaw::new(DTV_ENUM_DELSYS, 0),
];
self.get_properties(&mut cmdseq)?;
let v = cmdseq[0].data() as u16;
self.api_version = ApiVersion {
major: (v >> 8) as u8,
minor: (v & 0xFF) as u8,
};
let u_buffer = unsafe { cmdseq[1].u.buffer };
let u_buffer_len = ::std::cmp::min(u_buffer.len as usize, u_buffer.data.len());
for &v in &u_buffer.data[.. u_buffer_len] {
if let Ok(ds) = DeliverySystem::try_from(v as u32) {
self.delivery_system_list.push(ds);
}
}
Ok(())
}
fn open(adapter: u32, device: u32, is_write: bool) -> Result<FeDevice> {
let path = format!("/dev/dvb/adapter{}/frontend{}", adapter, device);
let file = OpenOptions::new()
.read(true)
.write(is_write)
.custom_flags(::nix::libc::O_NONBLOCK)
.open(&path)?;
if !file.metadata()?.file_type().is_char_device() {
return Err(Error::InvalidProperty(format!(
"{}: not a character device",
path
)));
}
let mut fe = FeDevice {
file,
api_version: ApiVersion { major: 0, minor: 0 },
name: String::default(),
delivery_system_list: Vec::default(),
frequency_range: 0 .. 0,
symbolrate_range: 0 .. 0,
caps: FeCaps::empty(),
};
fe.get_info()?;
Ok(fe)
}
pub fn open_ro(adapter: u32, device: u32) -> Result<FeDevice> {
Self::open(adapter, device, false)
}
pub fn open_rw(adapter: u32, device: u32) -> Result<FeDevice> {
Self::open(adapter, device, true)
}
fn check_properties(&self, cmdseq: &[DtvProperty]) -> Result<()> {
for p in cmdseq {
match *p {
DtvProperty::Frequency(v) => {
if !self.frequency_range.contains(&v) {
return Err(Error::InvalidProperty("frequency out of range".to_owned()));
}
}
DtvProperty::SymbolRate(v) => {
if !self.symbolrate_range.contains(&v) {
return Err(Error::InvalidProperty("symbolrate out of range".to_owned()));
}
}
DtvProperty::Inversion(v) => {
if v == Inversion::Auto && !self.caps.contains(FeCaps::CAN_INVERSION_AUTO) {
return Err(Error::InvalidProperty(
"frontend does not support auto inversion".to_owned(),
));
}
}
DtvProperty::TransmissionMode(v) => {
if v == TransmitMode::Auto
&& !self.caps.contains(FeCaps::CAN_TRANSMISSION_MODE_AUTO)
{
return Err(Error::InvalidProperty(
"frontend does not support auto transmission mode".to_owned(),
));
}
}
DtvProperty::GuardInterval(v) => {
if v == GuardInterval::Auto
&& !self.caps.contains(FeCaps::CAN_GUARD_INTERVAL_AUTO)
{
return Err(Error::InvalidProperty(
"frontend does not support auto guard interval".to_owned(),
));
}
}
DtvProperty::Hierarchy(v) => {
if v == Hierarchy::Auto && !self.caps.contains(FeCaps::CAN_HIERARCHY_AUTO) {
return Err(Error::InvalidProperty(
"frontend does not support auto hierarchy".to_owned(),
));
}
}
DtvProperty::StreamId(_) => {
if !self.caps.contains(FeCaps::CAN_MULTISTREAM) {
return Err(Error::InvalidProperty(
"frontend does not support multistream".to_owned(),
));
}
}
_ => {}
}
}
Ok(())
}
pub fn set_properties(&self, cmdseq: &[DtvProperty]) -> Result<()> {
self.check_properties(cmdseq)?;
let raw: Vec<DtvPropertyRaw> = cmdseq.iter().map(DtvProperty::to_raw).collect();
let cmd = DtvProperties {
num: raw.len() as u32,
props: raw.as_ptr() as *mut _,
};
nix::ioctl_write_ptr!(
#[inline]
ioctl_call,
b'o',
82,
DtvProperties
);
unsafe { ioctl_call(self.as_raw_fd(), &cmd as *const _) }?;
Ok(())
}
pub(crate) fn get_properties(&self, cmdseq: &mut [DtvPropertyRaw]) -> Result<()> {
let mut cmd = DtvProperties {
num: cmdseq.len() as u32,
props: cmdseq.as_mut_ptr(),
};
nix::ioctl_read!(
#[inline]
ioctl_call,
b'o',
83,
DtvProperties
);
unsafe { ioctl_call(self.as_raw_fd(), &mut cmd as *mut _) }?;
Ok(())
}
pub fn get_event(&self, event: &mut FeEvent) -> Result<()> {
nix::ioctl_read!(
#[inline]
ioctl_call,
b'o',
78,
FeEvent
);
unsafe { ioctl_call(self.as_raw_fd(), event as *mut _) }?;
Ok(())
}
pub fn read_status(&self) -> Result<FeStatusFlags> {
let mut result: u32 = 0;
nix::ioctl_read!(
#[inline]
ioctl_call,
b'o',
69,
u32
);
unsafe { ioctl_call(self.as_raw_fd(), &mut result as *mut _) }?;
Ok(FeStatusFlags::from_bits_retain(result))
}
pub fn read_signal_strength(&self) -> Result<u16> {
let mut result: u16 = 0;
nix::ioctl_read!(
#[inline]
ioctl_call,
b'o',
71,
u16
);
unsafe { ioctl_call(self.as_raw_fd(), &mut result as *mut _) }?;
Ok(result)
}
pub fn read_snr(&self) -> Result<u16> {
let mut result: u16 = 0;
nix::ioctl_read!(
#[inline]
ioctl_call,
b'o',
72,
u16
);
unsafe { ioctl_call(self.as_raw_fd(), &mut result as *mut _) }?;
Ok(result)
}
pub fn read_ber(&self) -> Result<u32> {
let mut result: u32 = 0;
nix::ioctl_read!(
#[inline]
ioctl_call,
b'o',
70,
u32
);
unsafe { ioctl_call(self.as_raw_fd(), &mut result as *mut _) }?;
Ok(result)
}
pub fn read_unc(&self) -> Result<u32> {
let mut result: u32 = 0;
nix::ioctl_read!(
#[inline]
ioctl_call,
b'o',
73,
u32
);
unsafe { ioctl_call(self.as_raw_fd(), &mut result as *mut _) }?;
Ok(result)
}
pub fn set_tone(&self, value: SecTone) -> Result<()> {
nix::ioctl_write_int_bad!(
#[inline]
ioctl_call,
nix::request_code_none!(b'o', 66)
);
unsafe { ioctl_call(self.as_raw_fd(), (value as u32) as _) }?;
Ok(())
}
pub fn set_voltage(&self, value: SecVoltage) -> Result<()> {
nix::ioctl_write_int_bad!(
#[inline]
ioctl_call,
nix::request_code_none!(b'o', 67)
);
unsafe { ioctl_call(self.as_raw_fd(), (value as u32) as _) }?;
Ok(())
}
pub fn diseqc_send_burst(&self, cmd: SecMiniCmd) -> Result<()> {
nix::ioctl_write_int_bad!(
#[inline]
ioctl_call,
nix::request_code_none!(b'o', 65)
);
unsafe { ioctl_call(self.as_raw_fd(), (cmd as u32) as _) }?;
Ok(())
}
pub fn diseqc_master_cmd(&self, msg: &[u8]) -> Result<()> {
let mut cmd = DiseqcMasterCmd::default();
debug_assert!(msg.len() <= cmd.msg.len());
cmd.msg[0 .. msg.len()].copy_from_slice(msg);
cmd.len = msg.len() as u8;
nix::ioctl_write_ptr!(ioctl_call, b'o', 63, DiseqcMasterCmd);
unsafe { ioctl_call(self.as_raw_fd(), &cmd as *const _) }?;
Ok(())
}
pub fn api_version(&self) -> ApiVersion {
self.api_version
}
pub fn name(&self) -> &str {
&self.name
}
pub fn delivery_systems(&self) -> &[DeliverySystem] {
&self.delivery_system_list
}
pub fn frequency_range(&self) -> Range<u32> {
self.frequency_range.clone()
}
pub fn symbolrate_range(&self) -> Range<u32> {
self.symbolrate_range.clone()
}
pub fn caps(&self) -> FeCaps {
self.caps
}
}