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_PRESENT: u32 = 1;
66const CA_CI_MODULE_READY: u32 = 2;
71
72#[repr(C)]
73struct CaSlotInfo {
74 num: i32,
75 typ: i32,
76 flags: u32,
77}
78
79const RESET_SETTLE: Duration = Duration::from_millis(3000);
85
86#[derive(Debug)]
93pub struct LinuxCaDevice {
94 file: File,
95 slot: u8,
96}
97
98impl LinuxCaDevice {
99 pub fn open(adapter: u32, ca: u32) -> io::Result<Self> {
101 let path = format!("/dev/dvb/adapter{adapter}/ca{ca}");
102 let file = OpenOptions::new().read(true).write(true).open(path)?;
103 Ok(Self { file, slot: 0 })
104 }
105
106 #[must_use]
108 pub fn from_file(file: File, slot: u8) -> Self {
109 Self { file, slot }
110 }
111
112 fn connection_id(tpdu: &[u8]) -> u8 {
115 dvb_ci::length::decode(tpdu.get(1..).unwrap_or(&[]))
116 .ok()
117 .and_then(|(_, hdr)| tpdu.get(1 + hdr).copied())
118 .unwrap_or(1)
119 }
120}
121
122impl CaDevice for LinuxCaDevice {
123 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
124 let mut frame = [0u8; 4096];
128 let n = match self.file.read(&mut frame) {
129 Ok(n) => n,
130 Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(0),
131 Err(e) => return Err(e),
132 };
133 let tpdu = frame.get(2..n).unwrap_or(&[]);
135 let copy = tpdu.len().min(buf.len());
136 buf[..copy].copy_from_slice(&tpdu[..copy]);
137 Ok(copy)
138 }
139
140 fn write(&mut self, buf: &[u8]) -> io::Result<()> {
141 let mut frame = Vec::with_capacity(buf.len() + 2);
143 frame.push(self.slot);
144 frame.push(Self::connection_id(buf));
145 frame.extend_from_slice(buf);
146 self.file.write_all(&frame)
147 }
148
149 fn reset(&mut self) -> io::Result<()> {
150 let r = unsafe { libc::ioctl(self.file.as_raw_fd(), CA_RESET as libc::c_ulong) };
152 if r < 0 {
153 return Err(io::Error::last_os_error());
154 }
155 std::thread::sleep(RESET_SETTLE);
157 Ok(())
158 }
159
160 fn slot_info(&mut self) -> io::Result<SlotInfo> {
161 let mut si = CaSlotInfo {
162 num: i32::from(self.slot),
163 typ: 0,
164 flags: 0,
165 };
166 let r = unsafe {
169 libc::ioctl(
170 self.file.as_raw_fd(),
171 CA_GET_SLOT_INFO as libc::c_ulong,
172 &mut si as *mut CaSlotInfo,
173 )
174 };
175 if r < 0 {
176 return Ok(SlotInfo {
179 num: self.slot,
180 module_ready: true,
181 module_present: true,
182 });
183 }
184 Ok(SlotInfo {
185 num: si.num as u8,
186 module_ready: si.flags & CA_CI_MODULE_READY != 0,
187 module_present: si.flags & CA_CI_MODULE_PRESENT != 0,
188 })
189 }
190
191 fn poll(&mut self, timeout: Duration) -> io::Result<bool> {
192 poll_readable(self.file.as_raw_fd(), timeout)
193 }
194}
195
196#[derive(Debug)]
200pub struct LinuxCiDataDevice {
201 file: File,
202}
203
204impl LinuxCiDataDevice {
205 pub fn open(adapter: u32, ci: u32) -> io::Result<Self> {
207 let path = format!("/dev/dvb/adapter{adapter}/ci{ci}");
208 let file = OpenOptions::new().read(true).write(true).open(path)?;
209 Ok(Self { file })
210 }
211
212 #[must_use]
214 pub fn from_file(file: File) -> Self {
215 Self { file }
216 }
217}
218
219impl CiDataDevice for LinuxCiDataDevice {
220 fn write(&mut self, ts: &[u8]) -> io::Result<()> {
221 if !ts.len().is_multiple_of(TS_PACKET_LEN) {
222 return Err(io::Error::new(
223 io::ErrorKind::InvalidInput,
224 "write not a multiple of 188 bytes",
225 ));
226 }
227 self.file.write_all(ts)
228 }
229
230 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
231 if !buf.len().is_multiple_of(TS_PACKET_LEN) {
232 return Err(io::Error::new(
233 io::ErrorKind::InvalidInput,
234 "read buffer not a multiple of 188 bytes",
235 ));
236 }
237 match self.file.read(buf) {
238 Ok(n) => Ok(n),
239 Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(0),
240 Err(e) => Err(e),
241 }
242 }
243
244 fn poll(&mut self, timeout: Duration) -> io::Result<bool> {
245 poll_readable(self.file.as_raw_fd(), timeout)
246 }
247}