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
73#[derive(Debug)]
75pub struct LinuxCaDevice {
76 file: File,
77}
78
79impl LinuxCaDevice {
80 pub fn open(adapter: u32, ca: u32) -> io::Result<Self> {
82 let path = format!("/dev/dvb/adapter{adapter}/ca{ca}");
83 let file = OpenOptions::new().read(true).write(true).open(path)?;
84 Ok(Self { file })
85 }
86
87 #[must_use]
89 pub fn from_file(file: File) -> Self {
90 Self { file }
91 }
92}
93
94impl CaDevice for LinuxCaDevice {
95 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
96 match self.file.read(buf) {
99 Ok(n) => Ok(n),
100 Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(0),
101 Err(e) => Err(e),
102 }
103 }
104
105 fn write(&mut self, buf: &[u8]) -> io::Result<()> {
106 self.file.write_all(buf)
107 }
108
109 fn reset(&mut self) -> io::Result<()> {
110 let r = unsafe { libc::ioctl(self.file.as_raw_fd(), CA_RESET as libc::c_ulong) };
112 if r < 0 {
113 Err(io::Error::last_os_error())
114 } else {
115 Ok(())
116 }
117 }
118
119 fn slot_info(&mut self) -> io::Result<SlotInfo> {
120 let mut si = CaSlotInfo {
121 num: 0,
122 typ: 0,
123 flags: 0,
124 };
125 let r = unsafe {
128 libc::ioctl(
129 self.file.as_raw_fd(),
130 CA_GET_SLOT_INFO as libc::c_ulong,
131 &mut si as *mut CaSlotInfo,
132 )
133 };
134 if r < 0 {
135 return Err(io::Error::last_os_error());
136 }
137 Ok(SlotInfo {
138 num: si.num as u8,
139 module_ready: si.flags & CA_CI_MODULE_READY != 0,
140 })
141 }
142
143 fn poll(&mut self, timeout: Duration) -> io::Result<bool> {
144 poll_readable(self.file.as_raw_fd(), timeout)
145 }
146}
147
148#[derive(Debug)]
152pub struct LinuxCiDataDevice {
153 file: File,
154}
155
156impl LinuxCiDataDevice {
157 pub fn open(adapter: u32, ci: u32) -> io::Result<Self> {
159 let path = format!("/dev/dvb/adapter{adapter}/ci{ci}");
160 let file = OpenOptions::new().read(true).write(true).open(path)?;
161 Ok(Self { file })
162 }
163
164 #[must_use]
166 pub fn from_file(file: File) -> Self {
167 Self { file }
168 }
169}
170
171impl CiDataDevice for LinuxCiDataDevice {
172 fn write(&mut self, ts: &[u8]) -> io::Result<()> {
173 if ts.len() % TS_PACKET_LEN != 0 {
174 return Err(io::Error::new(
175 io::ErrorKind::InvalidInput,
176 "write not a multiple of 188 bytes",
177 ));
178 }
179 self.file.write_all(ts)
180 }
181
182 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
183 if buf.len() % TS_PACKET_LEN != 0 {
184 return Err(io::Error::new(
185 io::ErrorKind::InvalidInput,
186 "read buffer not a multiple of 188 bytes",
187 ));
188 }
189 match self.file.read(buf) {
190 Ok(n) => Ok(n),
191 Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(0),
192 Err(e) => Err(e),
193 }
194 }
195
196 fn poll(&mut self, timeout: Duration) -> io::Result<bool> {
197 poll_readable(self.file.as_raw_fd(), timeout)
198 }
199}