libdvb_rs/fe/
mod.rs

1#![allow(dead_code)]
2mod status;
3pub mod sys;
4
5use {
6    anyhow::{Context, Result},
7    nix::{ioctl_read, ioctl_write_int_bad, ioctl_write_ptr, request_code_none},
8    std::{
9        ffi::CStr,
10        fmt,
11        fs::{File, OpenOptions},
12        ops::Range,
13        os::unix::{
14            fs::{FileTypeExt, OpenOptionsExt},
15            io::{AsRawFd, RawFd},
16        },
17    },
18    sys::*,
19};
20
21pub use status::FeStatus;
22
23/// A reference to the frontend device and device information
24#[derive(Debug)]
25pub struct FeDevice {
26    adapter: u32,
27    device: u32,
28
29    file: File,
30
31    api_version: u16,
32
33    name: String,
34    delivery_system_list: Vec<fe_delivery_system>,
35    frequency_range: Range<u32>,
36    symbolrate_range: Range<u32>,
37    caps: fe_caps,
38}
39
40impl fmt::Display for FeDevice {
41    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42        writeln!(
43            f,
44            "DVB API: {}.{}",
45            self.api_version >> 8,
46            self.api_version & 0xFF
47        )?;
48        writeln!(f, "Frontend: {}", self.name)?;
49
50        write!(f, "Delivery system:")?;
51        for v in &self.delivery_system_list {
52            write!(f, " {}", v)?;
53        }
54        writeln!(f, "")?;
55
56        writeln!(
57            f,
58            "Frequency range: {} .. {}",
59            self.frequency_range.start / 1000,
60            self.frequency_range.end / 1000
61        )?;
62
63        writeln!(
64            f,
65            "Symbolrate range: {} .. {}",
66            self.symbolrate_range.start / 1000,
67            self.symbolrate_range.end / 1000
68        )?;
69
70        write!(f, "Frontend capabilities: 0x{:08x}", self.caps)?;
71
72        Ok(())
73    }
74}
75
76impl AsRawFd for FeDevice {
77    #[inline]
78    fn as_raw_fd(&self) -> RawFd {
79        self.file.as_raw_fd()
80    }
81}
82
83#[macro_export]
84macro_rules! get_dtv_properties {
85    ( $device:expr, $( $property:ident ),+ ) => { (|| -> ::anyhow::Result<_> {
86        let mut input = [ $( $property($crate::fe::sys::DtvPropertyRequest::default()), )* ];
87        ::anyhow::Context::context($device.get_properties(&mut input), "Error fetching properties")?;
88        let mut iterator = input.iter();
89        Ok((
90            $(
91                ::anyhow::Context::with_context(match iterator.next() {
92                    Some($property(d)) => d.get(),
93                    _ => ::anyhow::Result::Err(::anyhow::anyhow!("Missing value")),
94                }, || format!("Error unpacking {}", stringify!($property)))?,
95            )*
96        ))
97    })()}
98}
99
100#[macro_export]
101macro_rules! set_dtv_properties {
102    ( $device:expr, $( $property:ident($data:expr) ),+ ) => {
103        $device.set_properties(&[
104            $( $property($crate::fe::sys::DtvPropertyRequest::new($data)), )*
105        ])
106    };
107}
108
109impl FeDevice {
110    /// Clears frontend settings and event queue
111    pub fn clear(&self) -> Result<()> {
112        set_dtv_properties!(
113            self,
114            DTV_VOLTAGE(SEC_VOLTAGE_OFF),
115            DTV_TONE(SEC_TONE_OFF),
116            DTV_CLEAR(())
117        )
118        .context("FE: clear")?;
119
120        let mut event = FeEvent::default();
121
122        for _ in 0..FE_MAX_EVENT {
123            if self.get_event(&mut event).is_err() {
124                break;
125            }
126        }
127
128        Ok(())
129    }
130
131    fn get_info(&mut self) -> Result<()> {
132        let mut feinfo = FeInfo::default();
133
134        // FE_GET_INFO
135        ioctl_read!(
136            #[inline]
137            ioctl_call,
138            b'o',
139            61,
140            FeInfo
141        );
142        unsafe { ioctl_call(self.as_raw_fd(), &mut feinfo as *mut _) }.context("FE: get info")?;
143
144        if let Some(len) = feinfo.name.iter().position(|&b| b == 0) {
145            let name = unsafe { CStr::from_ptr(feinfo.name[..len + 1].as_ptr()) };
146            if let Ok(name) = name.to_str() {
147                self.name = name.to_owned();
148            }
149        }
150
151        self.frequency_range = feinfo.frequency_min..feinfo.frequency_max;
152        self.symbolrate_range = feinfo.symbol_rate_min..feinfo.symbol_rate_max;
153
154        self.caps = feinfo.caps;
155
156        // DVB v5 properties
157        let (api_version, enum_delsys) =
158            get_dtv_properties!(self, DTV_API_VERSION, DTV_ENUM_DELSYS)
159                .context("FE: get api version (deprecated driver)")?;
160
161        // DVB API Version
162        self.api_version = api_version as u16;
163
164        // Suppoerted delivery systems
165        self.delivery_system_list = enum_delsys;
166
167        // dev-file metadata
168
169        let metadata = self.file.metadata().context("FE: get device metadata")?;
170
171        ensure!(
172            metadata.file_type().is_char_device(),
173            "FE: path is not to char device"
174        );
175
176        Ok(())
177    }
178
179    fn open(adapter: u32, device: u32, is_write: bool) -> Result<FeDevice> {
180        let path = format!("/dev/dvb/adapter{}/frontend{}", adapter, device);
181        let file = OpenOptions::new()
182            .read(true)
183            .write(is_write)
184            .custom_flags(::nix::libc::O_NONBLOCK)
185            .open(&path)
186            .with_context(|| format!("FE: failed to open device {}", &path))?;
187
188        let mut fe = FeDevice {
189            adapter,
190            device,
191
192            file,
193
194            api_version: 0,
195
196            name: String::default(),
197            delivery_system_list: Vec::default(),
198            frequency_range: 0..0,
199            symbolrate_range: 0..0,
200            caps: fe_caps::FE_IS_STUPID,
201        };
202
203        fe.get_info()?;
204
205        Ok(fe)
206    }
207
208    /// Attempts to open frontend device in read-only mode
209    #[inline]
210    pub fn open_ro(adapter: u32, device: u32) -> Result<FeDevice> {
211        Self::open(adapter, device, false)
212    }
213
214    /// Attempts to open frontend device in read-write mode
215    #[inline]
216    pub fn open_rw(adapter: u32, device: u32) -> Result<FeDevice> {
217        Self::open(adapter, device, true)
218    }
219
220    fn check_properties(&self, cmdseq: &[DtvProperty]) -> Result<()> {
221        for p in cmdseq {
222            match p {
223                DTV_FREQUENCY(d) => {
224                    ensure!(
225                        self.frequency_range.contains(&d.get()?),
226                        "FE: frequency out of range"
227                    );
228                }
229                DTV_SYMBOL_RATE(d) => {
230                    ensure!(
231                        self.symbolrate_range.contains(&d.get()?),
232                        "FE: symbolrate out of range"
233                    );
234                }
235                DTV_INVERSION(d) => {
236                    if d.get()? == INVERSION_AUTO {
237                        ensure!(
238                            self.caps.contains(fe_caps::FE_CAN_INVERSION_AUTO),
239                            "FE: auto inversion is not available"
240                        );
241                    }
242                }
243                DTV_TRANSMISSION_MODE(d) => {
244                    if d.get()? == TRANSMISSION_MODE_AUTO {
245                        ensure!(
246                            self.caps.contains(fe_caps::FE_CAN_TRANSMISSION_MODE_AUTO),
247                            "FE: no auto transmission mode"
248                        );
249                    }
250                }
251                DTV_GUARD_INTERVAL(d) => {
252                    if d.get()? == GUARD_INTERVAL_AUTO {
253                        ensure!(
254                            self.caps.contains(fe_caps::FE_CAN_GUARD_INTERVAL_AUTO),
255                            "FE: no auto guard interval"
256                        );
257                    }
258                }
259                DTV_HIERARCHY(d) => {
260                    if d.get()? == HIERARCHY_AUTO {
261                        ensure!(
262                            self.caps.contains(fe_caps::FE_CAN_HIERARCHY_AUTO),
263                            "FE: no auto hierarchy"
264                        );
265                    }
266                }
267                DTV_STREAM_ID(..) => {
268                    ensure!(
269                        self.caps.contains(fe_caps::FE_CAN_MULTISTREAM),
270                        "FE: no multistream"
271                    );
272                }
273                _ => {}
274            }
275        }
276
277        Ok(())
278    }
279
280    /// Sets properties on frontend device
281    pub fn set_properties(&self, cmdseq: &[DtvProperty]) -> Result<()> {
282        self.check_properties(cmdseq)?;
283
284        #[repr(C)]
285        pub struct DtvProperties {
286            num: u32,
287            props: *const DtvProperty,
288        }
289
290        let cmd = DtvProperties {
291            num: cmdseq.len() as u32,
292            props: cmdseq.as_ptr(),
293        };
294
295        // FE_SET_PROPERTY
296        ioctl_write_ptr!(
297            #[inline]
298            ioctl_call,
299            b'o',
300            82,
301            DtvProperties
302        );
303        unsafe { ioctl_call(self.as_raw_fd(), &cmd as *const _) }.context("FE: set properties")?;
304
305        Ok(())
306    }
307
308    /// Gets properties from frontend device
309    pub fn get_properties(&self, cmdseq: &mut [DtvProperty]) -> Result<()> {
310        #[repr(C)]
311        pub struct DtvProperties {
312            num: u32,
313            props: *mut DtvProperty,
314        }
315
316        let mut cmd = DtvProperties {
317            num: cmdseq.len() as u32,
318            props: cmdseq.as_mut_ptr(),
319        };
320
321        // FE_GET_PROPERTY
322        ioctl_read!(
323            #[inline]
324            ioctl_call,
325            b'o',
326            83,
327            DtvProperties
328        );
329        unsafe { ioctl_call(self.as_raw_fd(), &mut cmd as *mut _) }
330            .context("FE: get properties")?;
331
332        Ok(())
333    }
334
335    /// Returns a frontend events if available
336    pub fn get_event(&self, event: &mut FeEvent) -> Result<()> {
337        // FE_GET_EVENT
338        ioctl_read!(
339            #[inline]
340            ioctl_call,
341            b'o',
342            78,
343            FeEvent
344        );
345        unsafe { ioctl_call(self.as_raw_fd(), event as *mut _) }.context("FE: get event")?;
346
347        Ok(())
348    }
349
350    /// Returns frontend status
351    /// - [`FE_NONE`]
352    /// - [`FE_HAS_SIGNAL`]
353    /// - [`FE_HAS_CARRIER`]
354    /// - [`FE_HAS_VITERBI`]
355    /// - [`FE_HAS_SYNC`]
356    /// - [`FE_HAS_LOCK`]
357    /// - [`FE_TIMEDOUT`]
358    /// - [`FE_REINIT`]
359    pub fn read_status(&self) -> Result<fe_status> {
360        let mut result: u32 = 0;
361
362        // FE_READ_STATUS
363        ioctl_read!(
364            #[inline]
365            ioctl_call,
366            b'o',
367            69,
368            u32
369        );
370        unsafe { ioctl_call(self.as_raw_fd(), &mut result as *mut _) }
371            .context("FE: read status")?;
372
373        Ok(fe_status::from_bits(result).context("Invalid status")?)
374    }
375
376    /// Reads and returns a signal strength relative value (DVBv3 API)
377    pub fn read_signal_strength(&self) -> Result<u16> {
378        let mut result: u16 = 0;
379
380        // FE_READ_SIGNAL_STRENGTH
381        ioctl_read!(
382            #[inline]
383            ioctl_call,
384            b'o',
385            71,
386            u16
387        );
388        unsafe { ioctl_call(self.as_raw_fd(), &mut result as *mut _) }
389            .context("FE: read signal strength")?;
390
391        Ok(result)
392    }
393
394    /// Reads and returns a signal-to-noise ratio, relative value (DVBv3 API)
395    pub fn read_snr(&self) -> Result<u16> {
396        let mut result: u16 = 0;
397
398        // FE_READ_SNR
399        ioctl_read!(
400            #[inline]
401            ioctl_call,
402            b'o',
403            72,
404            u16
405        );
406        unsafe { ioctl_call(self.as_raw_fd(), &mut result as *mut _) }.context("FE: read snr")?;
407
408        Ok(result)
409    }
410
411    /// Reads and returns a bit error counter (DVBv3 API)
412    pub fn read_ber(&self) -> Result<u64> {
413        let mut result: u32 = 0;
414
415        // FE_READ_BER
416        ioctl_read!(
417            #[inline]
418            ioctl_call,
419            b'o',
420            70,
421            u32
422        );
423        unsafe { ioctl_call(self.as_raw_fd(), &mut result as *mut _) }.context("FE: read ber")?;
424
425        Ok(result as u64)
426    }
427
428    /// Reads and returns an uncorrected blocks counter (DVBv3 API)
429    pub fn read_unc(&self) -> Result<u64> {
430        let mut result: u32 = 0;
431
432        // FE_READ_UNCORRECTED_BLOCKS
433        ioctl_read!(
434            #[inline]
435            ioctl_call,
436            b'o',
437            73,
438            u32
439        );
440        unsafe { ioctl_call(self.as_raw_fd(), &mut result as *mut _) }
441            .context("FE: read uncorrected blocks")?;
442
443        Ok(result as u64)
444    }
445
446    /// Turns on/off generation of the continuous 22kHz tone
447    ///
448    /// allowed `value`'s:
449    ///
450    /// - SEC_TONE_ON - turn 22kHz on
451    /// - SEC_TONE_OFF - turn 22kHz off
452    pub fn set_tone(&self, value: u32) -> Result<()> {
453        // FE_SET_TONE
454        ioctl_write_int_bad!(
455            #[inline]
456            ioctl_call,
457            request_code_none!(b'o', 66)
458        );
459
460        unsafe { ioctl_call(self.as_raw_fd(), value as _) }.context("FE: set tone")?;
461
462        Ok(())
463    }
464
465    /// Sets the DC voltage level for LNB
466    ///
467    /// allowed `value`'s:
468    ///
469    /// - SEC_VOLTAGE_13 for 13V
470    /// - SEC_VOLTAGE_18 for 18V
471    /// - SEC_VOLTAGE_OFF turns LNB power supply off
472    ///
473    /// Different power levels used to select internal antennas for different polarizations:
474    ///
475    /// - 13V:
476    ///     - Vertical in linear LNB
477    ///     - Right in circular LNB
478    /// - 18V:
479    ///     - Horizontal in linear LNB
480    ///     - Left in circular LNB
481    /// - OFF is needed with external power supply, for example
482    ///   to use same LNB with several receivers.
483    pub fn set_voltage(&self, value: u32) -> Result<()> {
484        // FE_SET_VOLTAGE
485        ioctl_write_int_bad!(
486            #[inline]
487            ioctl_call,
488            request_code_none!(b'o', 67)
489        );
490
491        unsafe { ioctl_call(self.as_raw_fd(), value as _) }.context("FE: set voltage")?;
492
493        Ok(())
494    }
495
496    /// Sets DiSEqC master command
497    ///
498    /// `msg` is a message no more 6 bytes length
499    ///
500    /// Example DiSEqC commited command:
501    ///
502    /// ```text
503    /// [0xE0, 0x10, 0x38, 0xF0 | value]
504    /// ```
505    ///
506    /// - byte 1 is a framing (master command without response)
507    /// - byte 2 is an address (any LNB)
508    /// - byte 3 is a command (commited)
509    /// - last 4 bits of byte 4 is:
510    ///     - xx00 - switch input
511    ///     - 00x0 - bit is set on SEC_VOLTAGE_18
512    ///     - 000x - bit is set on SEC_TONE_ON
513    ///
514    pub fn diseqc_master_cmd(&self, msg: &[u8]) -> Result<()> {
515        let mut cmd = DiseqcMasterCmd::default();
516        debug_assert!(msg.len() <= cmd.msg.len());
517
518        cmd.msg[0..msg.len()].copy_from_slice(msg);
519        cmd.len = msg.len() as u8;
520
521        // FE_DISEQC_SEND_MASTER_CMD
522        ioctl_write_ptr!(ioctl_call, b'o', 63, DiseqcMasterCmd);
523        unsafe { ioctl_call(self.as_raw_fd(), &cmd as *const _) }
524            .context("FE: diseqc master cmd")?;
525
526        Ok(())
527    }
528
529    /// Returns the current API version
530    /// major - first byte
531    /// minor - second byte
532    #[inline]
533    pub fn get_api_version(&self) -> u16 {
534        self.api_version
535    }
536}