Skip to main content

linux_cec/
device.rs

1/*
2 * Copyright © 2024 Valve Software
3 * SPDX-License-Identifier: LGPL-2.1-or-later
4 */
5
6//! Hardware devices interfaces
7
8use linux_cec_sys::constants::{
9    CEC_CONNECTOR_TYPE_DRM, CEC_CONNECTOR_TYPE_NO_CONNECTOR, CEC_EVENT_LOST_MSGS,
10    CEC_EVENT_PIN_5V_HIGH, CEC_EVENT_PIN_5V_LOW, CEC_EVENT_PIN_CEC_HIGH, CEC_EVENT_PIN_CEC_LOW,
11    CEC_EVENT_PIN_HPD_HIGH, CEC_EVENT_PIN_HPD_LOW, CEC_EVENT_STATE_CHANGE, CEC_MAX_LOG_ADDRS,
12};
13use linux_cec_sys::ioctls::{
14    adapter_get_capabilities, adapter_get_connector_info, adapter_get_logical_addresses,
15    adapter_get_physical_address, adapter_set_logical_addresses, adapter_set_physical_address,
16    dequeue_event, get_mode, receive_message, set_mode, transmit_message,
17};
18use linux_cec_sys::structs::{
19    cec_caps, cec_connector_info, cec_drm_connector_info, cec_event, cec_log_addrs, cec_msg,
20    CEC_RX_STATUS, CEC_TX_STATUS,
21};
22use linux_cec_sys::{PhysicalAddress as SysPhysicalAddress, Timestamp, VendorId as SysVendorId};
23use nix::fcntl::{fcntl, FcntlArg, OFlag};
24use nix::poll::{poll, PollFd, PollFlags};
25use num_enum::TryFromPrimitive;
26use std::ffi::{c_char, OsStr, OsString};
27use std::fs::{File, OpenOptions};
28use std::os::fd::{AsFd, AsRawFd, OwnedFd};
29use std::os::unix::ffi::OsStrExt;
30use std::path::Path;
31use std::str::FromStr;
32use tinyvec::ArrayVec;
33#[cfg(feature = "tracing")]
34use tracing::{debug, warn};
35
36pub use linux_cec_sys::structs::CEC_CAP as Capabilities;
37pub use nix::poll::PollTimeout;
38
39use crate::ioctls::CecMessageHandlingMode;
40use crate::message::{Message, Opcode};
41use crate::operand::{BufferOperand, UiCommand};
42use crate::{
43    Error, FollowerMode, InitiatorMode, LogicalAddress, LogicalAddressType, PhysicalAddress, Range,
44    Result, RxError, Timeout, VendorId,
45};
46
47#[cfg(feature = "async")]
48pub use crate::async_support::{AsyncDevice, AsyncDevicePoller};
49
50/// An object for interacting with system CEC devices.
51#[derive(Debug)]
52pub struct Device {
53    file: File,
54    tx_logical_address: LogicalAddress,
55    internal_log_addrs: cec_log_addrs,
56}
57
58/// An enum containing the message data, either successfully parsed or invalid.
59#[derive(Debug, Copy, Clone, PartialEq, Hash)]
60pub enum MessageData {
61    /// Valid, parsed data
62    Valid(Message),
63    /// Invalid, unparsed data
64    Invalid(ArrayVec<[u8; 14]>),
65}
66
67impl MessageData {
68    #[must_use]
69    pub fn opcode(&self) -> u8 {
70        match self {
71            MessageData::Valid(message) => message.opcode().into(),
72            MessageData::Invalid(bytes) => bytes[0],
73        }
74    }
75
76    #[must_use]
77    pub fn to_bytes(&self) -> Vec<u8> {
78        match self {
79            MessageData::Valid(message) => message.to_bytes(),
80            MessageData::Invalid(bytes) => bytes.to_vec(),
81        }
82    }
83}
84
85/// A representation of a received [`Message`] and its associated metadata.
86#[derive(Debug, Clone, Hash)]
87pub struct Envelope {
88    /// The received message data.
89    pub message: MessageData,
90    /// The logical address of the CEC device that sent the message.
91    pub initiator: LogicalAddress,
92    /// The logical address to which this message was sent. Unless the [`Device`]
93    /// has the [`FollowerMode`] set to either [`FollowerMode::Monitor`] or
94    /// [`FollowerMode::MonitorAll`], this value will either be the logical address
95    /// of the `Device` itself or [`LogicalAddress::Broadcast`].
96    pub destination: LogicalAddress,
97    /// The time at which this message was received. This may be different
98    /// from the time at which the message was read out of the kernel.
99    pub timestamp: Timestamp,
100    /// A system-tracked sequence number.
101    pub sequence: u32,
102}
103
104impl TryFrom<cec_msg> for Envelope {
105    type Error = Error;
106
107    fn try_from(message: cec_msg) -> Result<Envelope> {
108        if message.rx_status.contains(CEC_RX_STATUS::TIMEOUT) {
109            return Err(Error::Timeout);
110        }
111        if message.rx_status.contains(CEC_RX_STATUS::ABORTED) {
112            return Err(Error::Abort);
113        }
114        if message.rx_status.contains(CEC_RX_STATUS::FEATURE_ABORT) {
115            return Err(RxError::FeatureAbort.into());
116        }
117        if !(2..=15).contains(&message.len) {
118            return Err(Error::InvalidData);
119        }
120        let bytes = &message.msg[1..message.len as usize];
121        let initiator = LogicalAddress::try_from_primitive(message.msg[0] >> 4)?;
122        let destination = LogicalAddress::try_from_primitive(message.msg[0] & 0xF)?;
123        let timestamp = message.rx_ts;
124        let sequence = message.sequence;
125
126        let message = match Message::try_from_bytes(bytes) {
127            Ok(message) => MessageData::Valid(message),
128            Err(e) => {
129                #[cfg(feature = "tracing")]
130                warn!("Failed to parse incoming message {bytes:?}: {e}");
131                let _ = e;
132                MessageData::Invalid(ArrayVec::from_array_len(
133                    message.msg[1..15].try_into().unwrap(),
134                    bytes.len(),
135                ))
136            }
137        };
138
139        let envelope = Envelope {
140            message,
141            initiator,
142            destination,
143            timestamp,
144            sequence,
145        };
146        #[cfg(feature = "tracing")]
147        debug!("Got message {envelope:#?}");
148        Ok(envelope)
149    }
150}
151
152#[cfg(test)]
153mod test_envelope {
154    use super::*;
155    use crate::sys::CEC_MSG_FL;
156
157    #[test]
158    fn decode_simple() {
159        let msg = cec_msg {
160            tx_ts: 0,
161            rx_ts: 911462400,
162            len: 2,
163            timeout: 0,
164            sequence: 1,
165            flags: CEC_MSG_FL::empty(),
166            msg: [
167                0xF,
168                Opcode::Standby as u8,
169                0,
170                0,
171                0,
172                0,
173                0,
174                0,
175                0,
176                0,
177                0,
178                0,
179                0,
180                0,
181                0,
182                0,
183            ],
184            reply: 0,
185            rx_status: CEC_RX_STATUS::OK,
186            tx_status: CEC_TX_STATUS::empty(),
187            tx_arb_lost_cnt: 0,
188            tx_nack_cnt: 0,
189            tx_low_drive_cnt: 0,
190            tx_error_cnt: 0,
191        };
192
193        let envelope = Envelope::try_from(msg).unwrap();
194        assert_eq!(envelope.message.opcode(), Opcode::Standby as u8);
195        assert_eq!(envelope.initiator, LogicalAddress::Tv);
196        assert_eq!(envelope.destination, LogicalAddress::Broadcast);
197        assert_eq!(envelope.timestamp, 911462400);
198        assert_eq!(envelope.sequence, 1);
199        let MessageData::Valid(message) = envelope.message else {
200            panic!();
201        };
202        assert_eq!(message.opcode(), Opcode::Standby);
203    }
204
205    #[test]
206    fn decode_invalid_opcode() {
207        let msg = cec_msg {
208            tx_ts: 0,
209            rx_ts: 0,
210            len: 2,
211            timeout: 0,
212            sequence: 1,
213            flags: CEC_MSG_FL::empty(),
214            msg: [0xF, 0xFE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
215            reply: 0,
216            rx_status: CEC_RX_STATUS::OK,
217            tx_status: CEC_TX_STATUS::empty(),
218            tx_arb_lost_cnt: 0,
219            tx_nack_cnt: 0,
220            tx_low_drive_cnt: 0,
221            tx_error_cnt: 0,
222        };
223
224        let envelope = Envelope::try_from(msg).unwrap();
225        assert_eq!(envelope.message.opcode(), 0xFE);
226        let MessageData::Invalid(message) = envelope.message else {
227            panic!();
228        };
229        assert_eq!(message.as_slice(), &[0xFE]);
230    }
231
232    #[test]
233    fn decode_too_long() {
234        let msg = cec_msg {
235            tx_ts: 0,
236            rx_ts: 0,
237            len: 16,
238            timeout: 0,
239            sequence: 1,
240            flags: CEC_MSG_FL::empty(),
241            msg: [
242                0xF,
243                Opcode::Standby as u8,
244                0,
245                0,
246                0,
247                0,
248                0,
249                0,
250                0,
251                0,
252                0,
253                0,
254                0,
255                0,
256                0,
257                0,
258            ],
259            reply: 0,
260            rx_status: CEC_RX_STATUS::OK,
261            tx_status: CEC_TX_STATUS::empty(),
262            tx_arb_lost_cnt: 0,
263            tx_nack_cnt: 0,
264            tx_low_drive_cnt: 0,
265            tx_error_cnt: 0,
266        };
267
268        let Err(err) = Envelope::try_from(msg) else {
269            panic!();
270        };
271        assert_eq!(err, Error::InvalidData);
272    }
273
274    #[test]
275    fn decode_way_too_short() {
276        let msg = cec_msg {
277            tx_ts: 0,
278            rx_ts: 0,
279            len: 0,
280            timeout: 0,
281            sequence: 1,
282            flags: CEC_MSG_FL::empty(),
283            msg: [0; 16],
284            reply: 0,
285            rx_status: CEC_RX_STATUS::OK,
286            tx_status: CEC_TX_STATUS::empty(),
287            tx_arb_lost_cnt: 0,
288            tx_nack_cnt: 0,
289            tx_low_drive_cnt: 0,
290            tx_error_cnt: 0,
291        };
292
293        let Err(err) = Envelope::try_from(msg) else {
294            panic!();
295        };
296        assert_eq!(err, Error::InvalidData);
297    }
298
299    #[test]
300    fn decode_too_short() {
301        let msg = cec_msg {
302            tx_ts: 0,
303            rx_ts: 0,
304            len: 1,
305            timeout: 0,
306            sequence: 1,
307            flags: CEC_MSG_FL::empty(),
308            msg: [0; 16],
309            reply: 0,
310            rx_status: CEC_RX_STATUS::OK,
311            tx_status: CEC_TX_STATUS::empty(),
312            tx_arb_lost_cnt: 0,
313            tx_nack_cnt: 0,
314            tx_low_drive_cnt: 0,
315            tx_error_cnt: 0,
316        };
317
318        let Err(err) = Envelope::try_from(msg) else {
319            panic!();
320        };
321        assert_eq!(err, Error::InvalidData);
322    }
323}
324
325/// A physical pin that can be monitored via [`PinEvent`]s.
326#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
327#[non_exhaustive]
328pub enum Pin {
329    /// The CEC data pin (13)
330    Cec,
331    /// The hot plug detect (HPD) pin (19)
332    HotPlugDetect,
333    /// The +5 V power pin (18)
334    Power5V,
335}
336
337/// The logic level of a physical pin as reported in a [`PinEvent`].
338#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
339pub enum PinState {
340    Low,
341    High,
342}
343
344/// An event representing a logic level change of a physical pin.
345///
346/// To receive `PinEvent`s from a [`Device`], it must be configured with a
347/// [`FollowerMode`] of either [`FollowerMode::MonitorPin`] or
348/// [`FollowerMode::MonitorAll`].
349#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
350pub struct PinEvent {
351    /// Which physical pin generated the event.
352    pub pin: Pin,
353    /// The logic level of the pin.
354    pub state: PinState,
355}
356
357/// An object used for polling the status of the event and message queues in the
358/// kernel without borrowing the [`Device`], created by [`Device::get_poller`].
359#[derive(Debug)]
360pub struct DevicePoller {
361    fd: OwnedFd,
362}
363
364/// Information from a [`DevicePoller`] about which information is available
365/// from the kernel, to be passed to [`Device::handle_status`].
366///
367/// As this is a representation of what data is available and not used for
368/// requesting data manually, it should not be constructed directly.
369#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
370#[non_exhaustive]
371pub enum PollStatus {
372    Nothing,
373    Destroyed,
374    GotEvent,
375    GotMessage,
376    GotAll,
377}
378
379/// A return result of [`Device::handle_status`], containing
380/// about a single message or event was returned from the kernel.
381#[derive(Debug, Clone, Hash)]
382#[non_exhaustive]
383pub enum PollResult {
384    /// The device received a [`Message`].
385    Message(Envelope),
386    /// A monitored pin changed state.
387    PinEvent(PinEvent),
388    /// The message queue was full and a number of messages were
389    /// received and lost. To avoid this, make sure to poll as
390    /// frequently as possible.
391    LostMessages(u32),
392    /// The device state changed. Usually this means the device was configured, unconfigured,
393    /// or the physical address changed. If the caller has cached any properties like logical
394    /// address or physical address, these values should be refreshed.
395    StateChange,
396}
397
398/// Information about how the CEC device is connected to the system.
399#[derive(Debug, Clone, PartialEq, Hash)]
400#[non_exhaustive]
401pub enum ConnectorInfo {
402    None,
403    /// Tells which drm connector is associated with the CEC adapter.
404    DrmConnector {
405        /// drm card number
406        card_no: u32,
407        /// drm connector ID
408        connector_id: u32,
409    },
410    Unknown {
411        ty: u32,
412        data: [u32; 16],
413    },
414}
415
416impl Device {
417    /// Open a CEC device from a given `path`. Generally, this will be of the
418    /// form `/dev/cecX`.
419    pub fn open(path: impl AsRef<Path>) -> Result<Device> {
420        let file = OpenOptions::new()
421            .read(true)
422            .write(true)
423            .create(false)
424            .open(path)?;
425        Device::try_from(file)
426    }
427
428    /// Get a new [`DevicePoller`] object that can be used to poll the status
429    /// of the kernel queue without having to borrow the `Device` object itself.
430    ///
431    /// While the poller can return a [`PollStatus`] to say which kinds of
432    /// events or messages are available, it must be passed to
433    /// [`handle_status`](Device::handle_status), which will require borrowing
434    /// the `Device`, to get the actual data.
435    pub fn get_poller(&self) -> Result<DevicePoller> {
436        Ok(DevicePoller {
437            fd: self.file.as_fd().try_clone_to_owned()?,
438        })
439    }
440
441    /// Wait up to `timeout` for a message or event and process the results.
442    /// This is a convenience method that combines using a `DevicePoller`
443    /// and calling `handle_status` internally.
444    pub fn poll(&mut self, timeout: PollTimeout) -> Result<Vec<PollResult>> {
445        let poller = self.get_poller()?;
446        let status = poller.poll(timeout)?;
447        self.handle_status(status)
448    }
449
450    /// Set or clear `O_NONBLOCK` on the underlying fd.
451    pub fn set_blocking(&self, blocking: bool) -> Result<()> {
452        let rawfd = self.file.as_fd();
453        let mut flags = OFlag::from_bits_retain(fcntl(rawfd, FcntlArg::F_GETFL)?);
454        flags.set(OFlag::O_NONBLOCK, !blocking);
455        fcntl(rawfd, FcntlArg::F_SETFL(flags))?;
456        Ok(())
457    }
458
459    /// Get the [`InitiatorMode`] of the device.
460    pub fn get_initiator_mode(&self) -> Result<InitiatorMode> {
461        self.get_mode()?.initiator().try_into()
462    }
463
464    /// Set the [`InitiatorMode`] of the device.
465    pub fn set_initiator_mode(&self, mode: InitiatorMode) -> Result<()> {
466        let mode = self.get_mode()?.with_initiator(mode.into());
467        self.set_mode(mode)
468    }
469
470    /// Get the [`FollowerMode`] of the device.
471    pub fn get_follower_mode(&self) -> Result<FollowerMode> {
472        self.get_mode()?.follower().try_into()
473    }
474
475    /// Set the [`FollowerMode`] of the device.
476    pub fn set_follower_mode(&self, mode: FollowerMode) -> Result<()> {
477        let mode = self.get_mode()?.with_follower(mode.into());
478        self.set_mode(mode)
479    }
480
481    /// Get the raw [`cec_caps`] struct for the device from the kernel.
482    pub fn get_raw_capabilities(&self) -> Result<cec_caps> {
483        let mut caps = cec_caps::default();
484        unsafe {
485            adapter_get_capabilities(self.file.as_raw_fd(), &mut caps)?;
486        }
487        Ok(caps)
488    }
489
490    /// Get the [`Capabilities`] of the device, informing the quirks of
491    /// the specific device.
492    pub fn get_capabilities(&self) -> Result<Capabilities> {
493        self.get_raw_capabilities().map(|caps| caps.capabilities)
494    }
495
496    /// Get the name of the driver that is backing this device.
497    pub fn get_driver_name(&self) -> Result<OsString> {
498        self.get_raw_capabilities().map(|caps| {
499            let driver =
500                unsafe { &*std::ptr::from_ref::<[c_char; 32]>(&caps.driver).cast::<[u8; 32]>() };
501            OsStr::from_bytes(driver).to_os_string()
502        })
503    }
504
505    /// Get the name of the adapter that is backing this device.
506    pub fn get_adapter_name(&self) -> Result<OsString> {
507        self.get_raw_capabilities().map(|caps| {
508            let adapter =
509                unsafe { &*std::ptr::from_ref::<[c_char; 32]>(&caps.name).cast::<[u8; 32]>() };
510            OsStr::from_bytes(adapter).to_os_string()
511        })
512    }
513
514    /// Get the currently configured [`PhysicalAddress`] of the device.
515    pub fn get_physical_address(&self) -> Result<PhysicalAddress> {
516        let mut phys_addr: SysPhysicalAddress = 0;
517        unsafe {
518            adapter_get_physical_address(self.file.as_raw_fd(), &mut phys_addr)?;
519        }
520        Ok(PhysicalAddress(phys_addr))
521    }
522
523    /// Set the [`PhysicalAddress`] of the device. This function will only work if the capability
524    /// [`Capabilities::PHYS_ADDR`] is present.
525    pub fn set_physical_address(&self, phys_addr: PhysicalAddress) -> Result<()> {
526        unsafe {
527            adapter_set_physical_address(self.file.as_raw_fd(), &phys_addr.0)?;
528        }
529        Ok(())
530    }
531
532    /// Get the currently configured [`LogicalAddress`]es of the device.
533    pub fn get_logical_addresses(&mut self) -> Result<Vec<LogicalAddress>> {
534        unsafe {
535            adapter_get_logical_addresses(self.file.as_raw_fd(), &mut self.internal_log_addrs)?;
536        }
537
538        let mut vec = Vec::new();
539        for index in 0..self.internal_log_addrs.num_log_addrs {
540            vec.push(self.internal_log_addrs.log_addr[index as usize].try_into()?);
541        }
542        Ok(vec)
543    }
544
545    /// Set the [`LogicalAddress`]es of the device. This function will only work if the capability
546    /// [`Capabilities::LOG_ADDRS`] is present. Note that the device must be configured to act as
547    /// an initiator via [`Device::set_initiator_mode`], otherwise this function will return an error.
548    pub fn set_logical_addresses(&mut self, log_addrs: &[LogicalAddressType]) -> Result<()> {
549        Range::AtMost(CEC_MAX_LOG_ADDRS).check(log_addrs.len(), "logical addresses")?;
550
551        #[cfg(feature = "tracing")]
552        debug!("Attempting to set logical addresses: {log_addrs:?}");
553
554        for (index, log_addr) in log_addrs.iter().enumerate() {
555            self.internal_log_addrs.log_addr_type[index] = (*log_addr).into();
556            if let Some(prim_dev_type) = (*log_addr).primary_device_type() {
557                self.internal_log_addrs.primary_device_type[index] = prim_dev_type.into();
558            } else {
559                self.internal_log_addrs.primary_device_type[index] = 0xFF;
560            }
561        }
562
563        if !log_addrs.is_empty() && self.internal_log_addrs.num_log_addrs > 0 {
564            // Clear old logical addresses first, if present
565            self.clear_logical_addresses()?;
566        }
567
568        self.internal_log_addrs.num_log_addrs = log_addrs.len().try_into().unwrap();
569        unsafe {
570            adapter_set_logical_addresses(self.file.as_raw_fd(), &mut self.internal_log_addrs)?;
571        }
572        if !log_addrs.is_empty() {
573            self.tx_logical_address =
574                LogicalAddress::try_from_primitive(self.internal_log_addrs.log_addr[0])
575                    .unwrap_or(LogicalAddress::Unregistered);
576        }
577        Ok(())
578    }
579
580    /// Set the single [`LogicalAddress`] of the device. This function will only work if the capability
581    /// [`Capabilities::LOG_ADDRS`] is present. Note that the device must be configured to act as
582    /// an initiator via [`Device::set_initiator_mode`], otherwise this function will return an error.
583    pub fn set_logical_address(&mut self, log_addr: LogicalAddressType) -> Result<()> {
584        self.set_logical_addresses(&[log_addr])
585    }
586
587    /// Clear the [`LogicalAddress`]es of the device. This function will only work if the capability
588    /// [`Capabilities::LOG_ADDRS`] is present.
589    pub fn clear_logical_addresses(&mut self) -> Result<()> {
590        self.internal_log_addrs.num_log_addrs = 0;
591        self.tx_logical_address = LogicalAddress::Unregistered;
592        unsafe {
593            adapter_set_logical_addresses(self.file.as_raw_fd(), &mut self.internal_log_addrs)?;
594        }
595        Ok(())
596    }
597
598    /// Get the configured OSD name.
599    pub fn get_osd_name(&mut self) -> Result<OsString> {
600        unsafe {
601            adapter_get_logical_addresses(self.file.as_raw_fd(), &mut self.internal_log_addrs)?;
602        }
603        Ok(OsStr::from_bytes(&self.internal_log_addrs.osd_name).to_os_string())
604    }
605
606    /// Set the advertised OSD name. This is used by TV sets to display the name
607    /// of connected devices. The maximum length allowed is only 14 bytes, and is
608    /// defined by the specification to be only ASCII. However, some TV sets will
609    /// properly display UTF-8 text as well. Note that the encoding *does not*
610    /// affect the allowable length of bytes, so using any multi-byte characters
611    /// will effectively decrease the maximum number of characters.
612    ///
613    /// For the kernel to properly advertise the OSD name on query, you must call
614    /// this function *before* calling [`Device::set_logical_addresses`].
615    ///
616    /// # Errors
617    /// This function will raise an [`Error::OutOfRange`] error if the name passed
618    /// exceeds 14 bytes.
619    pub fn set_osd_name(&mut self, name: &str) -> Result<()> {
620        let name_buffer = BufferOperand::from_str(name)?;
621        #[cfg(feature = "tracing")]
622        debug!("Setting OSD name to {name}");
623        self.internal_log_addrs.osd_name[..14].copy_from_slice(&name_buffer.buffer);
624        if self.tx_logical_address != LogicalAddress::Unregistered {
625            let message = Message::SetOsdName { name: name_buffer };
626            self.tx_message(&message, LogicalAddress::Tv)?;
627        }
628        Ok(())
629    }
630
631    /// Get the [`VendorId`] of this device, if configured.
632    pub fn get_vendor_id(&mut self) -> Result<Option<VendorId>> {
633        unsafe {
634            adapter_get_logical_addresses(self.file.as_raw_fd(), &mut self.internal_log_addrs)?;
635        }
636        VendorId::try_from_sys(self.internal_log_addrs.vendor_id)
637    }
638
639    /// Set the advertised [`VendorId`] of the device. You should generally
640    /// use the OUI assigned to your organization, if applicable. You must call
641    /// this function *before* calling [`Device::set_logical_addresses`].
642    pub fn set_vendor_id(&mut self, vendor_id: Option<VendorId>) -> Result<()> {
643        if let Some(vendor_id) = vendor_id {
644            self.internal_log_addrs.vendor_id = vendor_id.into();
645            #[cfg(feature = "tracing")]
646            debug!(
647                "Setting vendor ID to {:02X}-{:02X}-{:02X}",
648                vendor_id[0], vendor_id[1], vendor_id[2]
649            );
650        } else {
651            #[cfg(feature = "tracing")]
652            debug!("Clearing vendor ID");
653            self.internal_log_addrs.vendor_id = SysVendorId::default();
654        }
655        Ok(())
656    }
657
658    /// Transmit a [`Message`] to a given [`LogicalAddress`]. Use
659    /// [`LogicalAddress::Broadcast`] for broadcasting to all attached devices.
660    /// The sequence number of the submitted message is returned.
661    pub fn tx_message(&self, message: &Message, destination: LogicalAddress) -> Result<u32> {
662        let reply =
663            self.tx_rx_message(message, destination, Opcode::FeatureAbort, Timeout::NONE)?;
664        Ok(reply.sequence)
665    }
666
667    /// Transmit a raw system [`cec_msg`] directly through the `CEC_TRANSMIT` ioctl.
668    pub fn tx_raw_message(&self, message: &mut cec_msg) -> Result<()> {
669        unsafe {
670            transmit_message(self.file.as_raw_fd(), message)?;
671        }
672        Ok(())
673    }
674
675    /// Transmit a [`Message`] to a given [`LogicalAddress`] and wait for a reply of
676    /// a given [`Opcode`]. Use [`LogicalAddress::Broadcast`] for broadcasting to all
677    /// attached devices. Note that the timeout cannot be 0 or more than 1 second,
678    /// otherwise they will be coerced to 1 second.
679    pub fn tx_rx_message(
680        &self,
681        message: &Message,
682        destination: LogicalAddress,
683        reply: Opcode,
684        timeout: Timeout,
685    ) -> Result<Envelope> {
686        let mut raw_message = cec_msg::new(self.tx_logical_address.into(), destination.into());
687        let bytes = message.to_bytes();
688        let len = usize::min(bytes.len(), 15) + 1;
689        raw_message.len = len.try_into().unwrap();
690        raw_message.msg[1..len].copy_from_slice(&bytes[..len - 1]);
691        raw_message.reply = reply.into();
692        raw_message.timeout = timeout.as_ms();
693        #[cfg(feature = "tracing")]
694        debug!(
695            "Sending message {message:#?} to {destination} ({:x})",
696            destination as u8
697        );
698        self.tx_raw_message(&mut raw_message)?;
699        if !raw_message.tx_status.contains(CEC_TX_STATUS::OK) {
700            #[cfg(feature = "tracing")]
701            warn!("Message failed to send: {:?}", raw_message.tx_status);
702            return Err(raw_message.tx_status.into());
703        }
704        raw_message.try_into()
705    }
706
707    /// Receive a message, waiting up to a [`Timeout`] if one is not available
708    /// in the kernel buffer already. The resulting [`Envelope`] will contain the
709    /// message and associated metadata.
710    pub fn rx_message(&self, timeout: Timeout) -> Result<Envelope> {
711        self.rx_raw_message(timeout.as_ms())?.try_into()
712    }
713
714    /// Receive a raw system [`cec_msg`] directly through the `CEC_RECEIVE` ioctl.
715    pub fn rx_raw_message(&self, timeout_ms: u32) -> Result<cec_msg> {
716        let mut message = cec_msg::from_timeout(timeout_ms);
717        unsafe {
718            receive_message(self.file.as_raw_fd(), &mut message)?;
719        }
720        Ok(message)
721    }
722
723    /// Poll the bus for the presence of a given [`LogicalAddress`]. If the
724    /// device is present, this returns `Ok`, otherwise it returns with a
725    /// specific error detailing the manner of failure. Usually this will be
726    /// [`TxError::Nack`](crate::TxError::Nack) if the device isn't present,
727    /// but other manners of failure are theoretically possible.
728    pub fn poll_address(&self, destination: LogicalAddress) -> Result<()> {
729        let mut raw_message = cec_msg::new(self.tx_logical_address.into(), destination.into());
730        #[cfg(feature = "tracing")]
731        debug!("Sending poll to {destination} ({:x})", destination as u8);
732        self.tx_raw_message(&mut raw_message)?;
733        if !raw_message.tx_status.contains(CEC_TX_STATUS::OK) {
734            #[cfg(feature = "tracing")]
735            warn!("Poll failed: {:?}", raw_message.tx_status);
736            return Err(raw_message.tx_status.into());
737        }
738        Ok(())
739    }
740
741    pub(crate) fn dequeue_event(&self) -> Result<cec_event> {
742        let mut event = cec_event::default();
743        unsafe {
744            dequeue_event(self.file.as_raw_fd(), &mut event)?;
745        }
746        Ok(event)
747    }
748
749    pub(crate) fn get_mode(&self) -> Result<CecMessageHandlingMode> {
750        let mut mode = 0u32;
751        unsafe {
752            get_mode(self.file.as_raw_fd(), &mut mode)?;
753        }
754        Ok(mode.into())
755    }
756
757    pub(crate) fn set_mode(&self, mode: CecMessageHandlingMode) -> Result<()> {
758        unsafe {
759            set_mode(self.file.as_raw_fd(), &mode.into())?;
760        }
761        Ok(())
762    }
763
764    /// Handle the [`PollStatus`] returned from [`DevicePoller::poll`]. This
765    /// will dequeue any indicated messages or events, handle any internal
766    /// processing, and return information about the events or [`Envelope`]s
767    /// containing [`Message`]s.
768    pub fn handle_status(&mut self, status: PollStatus) -> Result<Vec<PollResult>> {
769        let mut results = Vec::new();
770        if status.got_event() {
771            let ev = self.dequeue_event()?;
772            match ev.event {
773                CEC_EVENT_STATE_CHANGE => {
774                    unsafe {
775                        adapter_get_logical_addresses(
776                            self.file.as_raw_fd(),
777                            &mut self.internal_log_addrs,
778                        )?;
779                    }
780                    if self.internal_log_addrs.num_log_addrs > 0 {
781                        self.tx_logical_address =
782                            LogicalAddress::try_from_primitive(self.internal_log_addrs.log_addr[0])
783                                .unwrap_or(LogicalAddress::Unregistered);
784                    } else {
785                        self.tx_logical_address = LogicalAddress::Unregistered;
786                    }
787                    results.push(PollResult::StateChange);
788                }
789                CEC_EVENT_LOST_MSGS => results.push(PollResult::LostMessages(unsafe {
790                    ev.data.lost_msgs.lost_msgs
791                })),
792                CEC_EVENT_PIN_CEC_LOW => results.push(PollResult::PinEvent(PinEvent {
793                    pin: Pin::Cec,
794                    state: PinState::Low,
795                })),
796                CEC_EVENT_PIN_CEC_HIGH => results.push(PollResult::PinEvent(PinEvent {
797                    pin: Pin::Cec,
798                    state: PinState::High,
799                })),
800                CEC_EVENT_PIN_HPD_LOW => results.push(PollResult::PinEvent(PinEvent {
801                    pin: Pin::HotPlugDetect,
802                    state: PinState::Low,
803                })),
804                CEC_EVENT_PIN_HPD_HIGH => results.push(PollResult::PinEvent(PinEvent {
805                    pin: Pin::HotPlugDetect,
806                    state: PinState::High,
807                })),
808                CEC_EVENT_PIN_5V_LOW => results.push(PollResult::PinEvent(PinEvent {
809                    pin: Pin::Power5V,
810                    state: PinState::Low,
811                })),
812                CEC_EVENT_PIN_5V_HIGH => results.push(PollResult::PinEvent(PinEvent {
813                    pin: Pin::Power5V,
814                    state: PinState::High,
815                })),
816                _ => return Err(Error::InvalidData),
817            }
818        }
819
820        if status.got_message() {
821            results.push(PollResult::Message(self.rx_message(Timeout::from_ms(1))?));
822        }
823
824        Ok(results)
825    }
826
827    /// Get information about the connector for the device, which is usually a card handled
828    /// by Linux's [DRM](https://en.wikipedia.org/wiki/Direct_Rendering_Manager) subsystem.
829    pub fn get_connector_info(&self) -> Result<ConnectorInfo> {
830        let mut conn_info = cec_connector_info::default();
831        unsafe {
832            adapter_get_connector_info(self.file.as_raw_fd(), &mut conn_info)?;
833        }
834        match conn_info.ty {
835            CEC_CONNECTOR_TYPE_NO_CONNECTOR => Ok(ConnectorInfo::None),
836            CEC_CONNECTOR_TYPE_DRM => {
837                let cec_drm_connector_info {
838                    card_no,
839                    connector_id,
840                } = unsafe { conn_info.data.drm };
841                Ok(ConnectorInfo::DrmConnector {
842                    card_no,
843                    connector_id,
844                })
845            }
846            ty => Ok(ConnectorInfo::Unknown {
847                ty,
848                data: unsafe { conn_info.data.raw },
849            }),
850        }
851    }
852
853    /// Tell the TV to switch to the given phyiscal address for its input. If
854    /// the address is None, it uses the physical address of the device itself.
855    pub fn set_active_source(&self, address: Option<PhysicalAddress>) -> Result<()> {
856        let address = match address {
857            Some(address) => address,
858            None => self.get_physical_address()?,
859        };
860        let active_source = Message::ActiveSource { address };
861        self.tx_message(&active_source, LogicalAddress::Broadcast)?;
862        Ok(())
863    }
864
865    /// Wake the TV and optionally tell the TV to make this device the active source via
866    /// the One Touch Play feature.
867    ///
868    /// HDMI CEC specifies two messages for how to handle device switching: Image View
869    /// and Text View. The Image View is simply the video input, with the possibility of
870    /// menus or infoboxes (the Text View) displayed over it. If `text_view` is set to
871    /// `true`, the device will request both and the TV should dismiss any active menus.
872    pub fn wake(&self, set_active: bool, text_view: bool) -> Result<()> {
873        if text_view {
874            let text_view_on = Message::TextViewOn {};
875            self.tx_message(&text_view_on, LogicalAddress::Tv)?;
876        } else {
877            let image_view_on = Message::ImageViewOn {};
878            self.tx_message(&image_view_on, LogicalAddress::Tv)?;
879        }
880        if set_active {
881            let address = self.get_physical_address()?;
882            let active_source = Message::ActiveSource { address };
883            self.tx_message(&active_source, LogicalAddress::Broadcast)?;
884        }
885        Ok(())
886    }
887
888    /// Tell a device with the given [`LogicalAddress`] to enter standby mode.
889    pub fn standby(&self, target: LogicalAddress) -> Result<()> {
890        let standby = Message::Standby {};
891        self.tx_message(&standby, target)?;
892        Ok(())
893    }
894
895    /// Convenience method for sending a user control command to a given [`LogicalAddress`]
896    /// by generating a [`UserControlPressed`](Message::UserControlPressed) message.
897    /// These generally correspond to the buttons on a remote control that are relayed to other
898    /// devices. This must be matched with a call to [`Device::release_user_control`].
899    pub fn press_user_control(&self, ui_command: UiCommand, target: LogicalAddress) -> Result<()> {
900        let user_control = Message::UserControlPressed { ui_command };
901        self.tx_message(&user_control, target)?;
902        Ok(())
903    }
904
905    /// Convenience method for terminating a user control command, as started with
906    /// [`Device::press_user_control`]. Internally, this just creates and sends a
907    /// [`UserControlReleased`](Message::UserControlReleased) message to the given
908    /// [`LogicalAddress`].
909    pub fn release_user_control(&self, target: LogicalAddress) -> Result<()> {
910        let user_control = Message::UserControlReleased {};
911        self.tx_message(&user_control, target)?;
912        Ok(())
913    }
914}
915
916impl TryFrom<File> for Device {
917    type Error = Error;
918
919    fn try_from(file: File) -> Result<Device> {
920        let mut internal_log_addrs = cec_log_addrs::default();
921        unsafe {
922            adapter_get_logical_addresses(file.as_raw_fd(), &mut internal_log_addrs)?;
923        }
924        let tx_logical_address = if internal_log_addrs.num_log_addrs > 0 {
925            LogicalAddress::try_from_primitive(internal_log_addrs.log_addr[0]).unwrap_or_default()
926        } else {
927            LogicalAddress::Unregistered
928        };
929
930        Ok(Device {
931            file,
932            tx_logical_address,
933            internal_log_addrs,
934        })
935    }
936}
937
938impl DevicePoller {
939    /// Poll the kernel queues for the [`Device`]. The returned [`PollStatus`]
940    /// must be passed to [`Device::handle_status`] to dequeue the events or
941    /// messages that the status may indicate are present.
942    pub fn poll(&self, timeout: PollTimeout) -> Result<PollStatus> {
943        let mut pollfd = [PollFd::new(
944            self.fd.as_fd(),
945            PollFlags::POLLPRI | PollFlags::POLLIN,
946        )];
947        let done = poll(&mut pollfd, timeout)?;
948
949        if done == 0 {
950            return Err(Error::Timeout);
951        }
952
953        match pollfd[0].revents() {
954            None => Ok(PollStatus::Nothing),
955            Some(flags) if flags.contains(PollFlags::POLLHUP) => Ok(PollStatus::Destroyed),
956            Some(flags) if flags.contains(PollFlags::POLLIN | PollFlags::POLLPRI) => {
957                Ok(PollStatus::GotAll)
958            }
959            Some(flags) if flags.contains(PollFlags::POLLIN) => Ok(PollStatus::GotMessage),
960            Some(flags) if flags.contains(PollFlags::POLLPRI) => Ok(PollStatus::GotEvent),
961            Some(_) => Err(Error::UnknownError(String::from(
962                "Polling error encountered",
963            ))),
964        }
965    }
966}
967
968impl PollStatus {
969    #[must_use]
970    pub fn got_message(&self) -> bool {
971        matches!(self, PollStatus::GotMessage | PollStatus::GotAll)
972    }
973
974    #[must_use]
975    pub fn got_event(&self) -> bool {
976        matches!(self, PollStatus::GotEvent | PollStatus::GotAll)
977    }
978}