1#![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::dataplane::{CiDataDevice, TS_PACKET_LEN};
19use crate::device::{CaDevice, SlotInfo};
20
21fn poll_readable(fd: libc::c_int, timeout: Duration) -> io::Result<bool> {
23 let mut pfd = libc::pollfd {
24 fd,
25 events: libc::POLLIN,
26 revents: 0,
27 };
28 let ms = i32::try_from(timeout.as_millis()).unwrap_or(i32::MAX);
29 let r = unsafe { libc::poll(&mut pfd as *mut libc::pollfd, 1, ms) };
31 if r < 0 {
32 Err(io::Error::last_os_error())
33 } else {
34 Ok(pfd.revents & libc::POLLIN != 0)
35 }
36}
37
38const IOC_NRBITS: u32 = 8;
40const IOC_TYPEBITS: u32 = 8;
41const IOC_SIZEBITS: u32 = 14;
42const IOC_NRSHIFT: u32 = 0;
43const IOC_TYPESHIFT: u32 = IOC_NRSHIFT + IOC_NRBITS;
44const IOC_SIZESHIFT: u32 = IOC_TYPESHIFT + IOC_TYPEBITS;
45const IOC_DIRSHIFT: u32 = IOC_SIZESHIFT + IOC_SIZEBITS;
46const IOC_NONE: u32 = 0;
47const IOC_READ: u32 = 2;
48
49const fn ioc(dir: u32, typ: u32, nr: u32, size: u32) -> u64 {
50 ((dir << IOC_DIRSHIFT) | (typ << IOC_TYPESHIFT) | (nr << IOC_NRSHIFT) | (size << IOC_SIZESHIFT))
51 as u64
52}
53
54const DVB_CA_MAGIC: u32 = b'o' as u32;
56const CA_RESET: u64 = ioc(IOC_NONE, DVB_CA_MAGIC, 128, 0);
57const CA_GET_SLOT_INFO: u64 = ioc(
58 IOC_READ,
59 DVB_CA_MAGIC,
60 130,
61 core::mem::size_of::<CaSlotInfo>() as u32,
62);
63const CA_CI_MODULE_READY: u32 = 1;
65
66#[repr(C)]
67struct CaSlotInfo {
68 num: i32,
69 typ: i32,
70 flags: u32,
71}
72
73const RESET_SETTLE: Duration = Duration::from_millis(2000);
77
78#[derive(Debug)]
85pub struct LinuxCaDevice {
86 file: File,
87 slot: u8,
88}
89
90impl LinuxCaDevice {
91 pub fn open(adapter: u32, ca: u32) -> io::Result<Self> {
93 let path = format!("/dev/dvb/adapter{adapter}/ca{ca}");
94 let file = OpenOptions::new().read(true).write(true).open(path)?;
95 Ok(Self { file, slot: 0 })
96 }
97
98 #[must_use]
100 pub fn from_file(file: File, slot: u8) -> Self {
101 Self { file, slot }
102 }
103
104 fn connection_id(tpdu: &[u8]) -> u8 {
107 dvb_ci::length::decode(tpdu.get(1..).unwrap_or(&[]))
108 .ok()
109 .and_then(|(_, hdr)| tpdu.get(1 + hdr).copied())
110 .unwrap_or(1)
111 }
112}
113
114impl CaDevice for LinuxCaDevice {
115 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
116 let mut frame = [0u8; 4096];
120 let n = match self.file.read(&mut frame) {
121 Ok(n) => n,
122 Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(0),
123 Err(e) => return Err(e),
124 };
125 let tpdu = frame.get(2..n).unwrap_or(&[]);
127 let copy = tpdu.len().min(buf.len());
128 buf[..copy].copy_from_slice(&tpdu[..copy]);
129 Ok(copy)
130 }
131
132 fn write(&mut self, buf: &[u8]) -> io::Result<()> {
133 let mut frame = Vec::with_capacity(buf.len() + 2);
135 frame.push(self.slot);
136 frame.push(Self::connection_id(buf));
137 frame.extend_from_slice(buf);
138 self.file.write_all(&frame)
139 }
140
141 fn reset(&mut self) -> io::Result<()> {
142 let r = unsafe { libc::ioctl(self.file.as_raw_fd(), CA_RESET as libc::c_ulong) };
144 if r < 0 {
145 return Err(io::Error::last_os_error());
146 }
147 std::thread::sleep(RESET_SETTLE);
149 Ok(())
150 }
151
152 fn slot_info(&mut self) -> io::Result<SlotInfo> {
153 let mut si = CaSlotInfo {
154 num: i32::from(self.slot),
155 typ: 0,
156 flags: 0,
157 };
158 let r = unsafe {
161 libc::ioctl(
162 self.file.as_raw_fd(),
163 CA_GET_SLOT_INFO as libc::c_ulong,
164 &mut si as *mut CaSlotInfo,
165 )
166 };
167 if r < 0 {
168 return Ok(SlotInfo {
171 num: self.slot,
172 module_ready: true,
173 });
174 }
175 Ok(SlotInfo {
176 num: si.num as u8,
177 module_ready: si.flags & CA_CI_MODULE_READY != 0,
178 })
179 }
180
181 fn poll(&mut self, timeout: Duration) -> io::Result<bool> {
182 poll_readable(self.file.as_raw_fd(), timeout)
183 }
184}
185
186#[derive(Debug)]
190pub struct LinuxCiDataDevice {
191 file: File,
192}
193
194impl LinuxCiDataDevice {
195 pub fn open(adapter: u32, ci: u32) -> io::Result<Self> {
197 let path = format!("/dev/dvb/adapter{adapter}/ci{ci}");
198 let file = OpenOptions::new().read(true).write(true).open(path)?;
199 Ok(Self { file })
200 }
201
202 #[must_use]
204 pub fn from_file(file: File) -> Self {
205 Self { file }
206 }
207}
208
209impl CiDataDevice for LinuxCiDataDevice {
210 fn write(&mut self, ts: &[u8]) -> io::Result<()> {
211 if ts.len() % TS_PACKET_LEN != 0 {
212 return Err(io::Error::new(
213 io::ErrorKind::InvalidInput,
214 "write not a multiple of 188 bytes",
215 ));
216 }
217 self.file.write_all(ts)
218 }
219
220 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
221 if buf.len() % TS_PACKET_LEN != 0 {
222 return Err(io::Error::new(
223 io::ErrorKind::InvalidInput,
224 "read buffer not a multiple of 188 bytes",
225 ));
226 }
227 match self.file.read(buf) {
228 Ok(n) => Ok(n),
229 Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(0),
230 Err(e) => Err(e),
231 }
232 }
233
234 fn poll(&mut self, timeout: Duration) -> io::Result<bool> {
235 poll_readable(self.file.as_raw_fd(), timeout)
236 }
237}