Skip to main content

dvb_ci_runtime/
linux.rs

1//! Linux `/dev/dvb/adapterN/caM` [`CaDevice`] implementation (the `linux`
2//! feature).
3//!
4//! This is the one place the crate uses `unsafe` — the DVB CA ioctls
5//! (`CA_RESET`, `CA_GET_SLOT_INFO`) via `libc`. The ioctl request numbers are
6//! computed from the standard Linux `_IOC` encoding (Documentation/userspace-api
7//! + `include/uapi/linux/dvb/ca.h`), not hard-coded magic.
8//!
9//! Runtime behaviour requires a real DVB card with a CI slot; it is
10//! compile-checked in CI but exercised only on hardware.
11#![allow(unsafe_code)]
12
13use std::fs::{File, OpenOptions};
14use std::io::{self, Read, Write};
15use std::os::unix::io::AsRawFd;
16use std::time::Duration;
17
18use crate::device::{CaDevice, SlotInfo};
19
20// --- Linux _IOC ioctl encoding (uapi/asm-generic/ioctl.h) ------------------
21const IOC_NRBITS: u32 = 8;
22const IOC_TYPEBITS: u32 = 8;
23const IOC_SIZEBITS: u32 = 14;
24const IOC_NRSHIFT: u32 = 0;
25const IOC_TYPESHIFT: u32 = IOC_NRSHIFT + IOC_NRBITS;
26const IOC_SIZESHIFT: u32 = IOC_TYPESHIFT + IOC_TYPEBITS;
27const IOC_DIRSHIFT: u32 = IOC_SIZESHIFT + IOC_SIZEBITS;
28const IOC_NONE: u32 = 0;
29const IOC_READ: u32 = 2;
30
31const fn ioc(dir: u32, typ: u32, nr: u32, size: u32) -> u64 {
32    ((dir << IOC_DIRSHIFT) | (typ << IOC_TYPESHIFT) | (nr << IOC_NRSHIFT) | (size << IOC_SIZESHIFT))
33        as u64
34}
35
36// DVB CA device (uapi/linux/dvb/ca.h): magic 'o', ca_slot_info, flags bit.
37const DVB_CA_MAGIC: u32 = b'o' as u32;
38const CA_RESET: u64 = ioc(IOC_NONE, DVB_CA_MAGIC, 128, 0);
39const CA_GET_SLOT_INFO: u64 = ioc(
40    IOC_READ,
41    DVB_CA_MAGIC,
42    130,
43    core::mem::size_of::<CaSlotInfo>() as u32,
44);
45/// `CA_CI_MODULE_READY` — the slot has a module that is ready.
46const CA_CI_MODULE_READY: u32 = 1;
47
48#[repr(C)]
49struct CaSlotInfo {
50    num: i32,
51    typ: i32,
52    flags: u32,
53}
54
55/// A [`CaDevice`] backed by a Linux DVB CA character device.
56#[derive(Debug)]
57pub struct LinuxCaDevice {
58    file: File,
59}
60
61impl LinuxCaDevice {
62    /// Open `/dev/dvb/adapter{adapter}/ca{ca}`.
63    pub fn open(adapter: u32, ca: u32) -> io::Result<Self> {
64        let path = format!("/dev/dvb/adapter{adapter}/ca{ca}");
65        let file = OpenOptions::new().read(true).write(true).open(path)?;
66        Ok(Self { file })
67    }
68
69    /// Wrap an already-open CA device file.
70    #[must_use]
71    pub fn from_file(file: File) -> Self {
72        Self { file }
73    }
74}
75
76impl CaDevice for LinuxCaDevice {
77    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
78        // Driver calls this only after `poll` reports readable, so it will not
79        // block. A `WouldBlock` is reported as "no data".
80        match self.file.read(buf) {
81            Ok(n) => Ok(n),
82            Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(0),
83            Err(e) => Err(e),
84        }
85    }
86
87    fn write(&mut self, buf: &[u8]) -> io::Result<()> {
88        self.file.write_all(buf)
89    }
90
91    fn reset(&mut self) -> io::Result<()> {
92        // SAFETY: CA_RESET takes no argument; fd is a valid open CA device.
93        let r = unsafe { libc::ioctl(self.file.as_raw_fd(), CA_RESET as libc::c_ulong) };
94        if r < 0 {
95            Err(io::Error::last_os_error())
96        } else {
97            Ok(())
98        }
99    }
100
101    fn slot_info(&mut self) -> io::Result<SlotInfo> {
102        let mut si = CaSlotInfo {
103            num: 0,
104            typ: 0,
105            flags: 0,
106        };
107        // SAFETY: CA_GET_SLOT_INFO writes a ca_slot_info; `si` is exactly that
108        // struct and outlives the call; fd is a valid open CA device.
109        let r = unsafe {
110            libc::ioctl(
111                self.file.as_raw_fd(),
112                CA_GET_SLOT_INFO as libc::c_ulong,
113                &mut si as *mut CaSlotInfo,
114            )
115        };
116        if r < 0 {
117            return Err(io::Error::last_os_error());
118        }
119        Ok(SlotInfo {
120            num: si.num as u8,
121            module_ready: si.flags & CA_CI_MODULE_READY != 0,
122        })
123    }
124
125    fn poll(&mut self, timeout: Duration) -> io::Result<bool> {
126        let mut pfd = libc::pollfd {
127            fd: self.file.as_raw_fd(),
128            events: libc::POLLIN,
129            revents: 0,
130        };
131        let ms = i32::try_from(timeout.as_millis()).unwrap_or(i32::MAX);
132        // SAFETY: `pfd` points at one valid pollfd for the duration of the call.
133        let r = unsafe { libc::poll(&mut pfd as *mut libc::pollfd, 1, ms) };
134        if r < 0 {
135            Err(io::Error::last_os_error())
136        } else {
137            Ok(pfd.revents & libc::POLLIN != 0)
138        }
139    }
140}