Skip to main content

linux_cec_sys/
structs.rs

1/*
2 * Copyright © 2024 Valve Software
3 *
4 * Based in part on linux/cec.h
5 * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
6 * SPDX-License-Identifier: BSD-3-Clause
7 */
8
9#![allow(non_camel_case_types)]
10
11use bitflags::bitflags;
12use core::ffi::{c_char, c_uchar};
13
14use crate::constants::*;
15use crate::{LogicalAddress, PhysicalAddress, Timestamp};
16
17/// CEC message structure.
18#[derive(Debug, Clone)]
19#[repr(C)]
20pub struct cec_msg {
21    /// Timestamp in nanoseconds using `CLOCK_MONOTONIC`. Set by the
22    /// driver when the message transmission has finished.
23    pub tx_ts: Timestamp,
24    /// Timestamp in nanoseconds using `CLOCK_MONOTONIC`. Set by the
25    /// driver when the message was received.
26    pub rx_ts: Timestamp,
27    /// Length in bytes of the message.
28    pub len: u32,
29    /**
30     * The timeout (in ms) that is used to timeout `CEC_RECEIVE`.
31     * Set to 0 if you want to wait forever. This timeout can also be
32     * used with `CEC_TRANSMIT` as the timeout for waiting for a reply.
33     * If 0, then it will use a 1 second timeout instead of waiting
34     * forever as is done with `CEC_RECEIVE`.
35     */
36    pub timeout: u32,
37    /// The framework assigns a sequence number to messages that are
38    /// sent. This can be used to track replies to previously sent messages.
39    pub sequence: u32,
40    /// Set to 0.
41    pub flags: CEC_MSG_FL,
42    /// The message payload.
43    pub msg: [u8; CEC_MAX_MSG_SIZE],
44    /**
45     * This field is ignored with `CEC_RECEIVE` and is only used by
46     * `CEC_TRANSMIT`. If non-zero, then wait for a reply with this
47     * opcode. Set to `CEC_MSG_FEATURE_ABORT` if you want to wait for
48     * a possible `ABORT` reply. If there was an error when sending the
49     * msg or `FeatureAbort` was returned, then reply is set to 0.
50     * If reply is non-zero upon return, then len/msg are set to
51     * the received message.
52     * If reply is zero upon return and status has the
53     * `CEC_TX_STATUS_FEATURE_ABORT` bit set, then len/msg are set to
54     * the received feature abort message.
55     * If reply is zero upon return and status has the
56     * `CEC_TX_STATUS_MAX_RETRIES` bit set, then no reply was seen at
57     * all. If reply is non-zero for `CEC_TRANSMIT` and the message is a
58     * broadcast, then `-EINVAL` is returned.
59     * if reply is non-zero, then timeout is set to 1000 (the required
60     * maximum response time).
61     */
62    pub reply: u8,
63    /// The message receive status bits. Set by the driver.
64    pub rx_status: CEC_RX_STATUS,
65    /// The message transmit status bits. Set by the driver.
66    pub tx_status: CEC_TX_STATUS,
67    /// The number of 'Arbitration Lost' events. Set by the driver.
68    pub tx_arb_lost_cnt: u8,
69    /// The number of 'Not Acknowledged' events. Set by the driver.
70    pub tx_nack_cnt: u8,
71    /// The number of 'Low Drive Detected' events. Set by the driver.
72    pub tx_low_drive_cnt: u8,
73    /// The number of 'Error' events. Set by the driver.
74    pub tx_error_cnt: u8,
75}
76
77bitflags! {
78    /// A bitflag struct for values of the CEC_MSG_FL_* constants
79    #[derive(Debug, Copy, Clone, Default)]
80    pub struct CEC_MSG_FL: u32 {
81        const REPLY_TO_FOLLOWERS = CEC_MSG_FL_REPLY_TO_FOLLOWERS;
82        const RAW = CEC_MSG_FL_RAW;
83    }
84}
85
86bitflags! {
87    /// A bitflag struct for values of the CEC_TX_STATUS_* constants
88    #[derive(Debug, Copy, Clone)]
89    pub struct CEC_TX_STATUS: u8 {
90        const OK = CEC_TX_STATUS_OK;
91        const ARB_LOST = CEC_TX_STATUS_ARB_LOST;
92        const NACK = CEC_TX_STATUS_NACK;
93        const LOW_DRIVE = CEC_TX_STATUS_LOW_DRIVE;
94        const ERROR = CEC_TX_STATUS_ERROR;
95        const MAX_RETRIES = CEC_TX_STATUS_MAX_RETRIES;
96        const ABORTED = CEC_TX_STATUS_ABORTED;
97        const TIMEOUT = CEC_TX_STATUS_TIMEOUT;
98    }
99}
100
101bitflags! {
102    /// A bitflag struct for values of the CEC_RX_STATUS_* constants
103    #[derive(Debug, Copy, Clone)]
104    pub struct CEC_RX_STATUS: u8 {
105        const OK = CEC_RX_STATUS_OK;
106        const TIMEOUT = CEC_RX_STATUS_TIMEOUT;
107        const FEATURE_ABORT = CEC_RX_STATUS_FEATURE_ABORT;
108        const ABORTED = CEC_RX_STATUS_ABORTED;
109    }
110}
111
112impl cec_msg {
113    /// Return the initiator's logical address.
114    #[inline]
115    #[must_use]
116    pub fn initiator(&self) -> u8 {
117        self.msg[0] >> 4
118    }
119
120    /// Return the destination's logical address.
121    #[inline]
122    #[must_use]
123    pub fn destination(&self) -> u8 {
124        self.msg[0] & 0xf
125    }
126
127    /// Return the opcode of the message, None for poll
128    #[inline]
129    #[must_use]
130    pub fn opcode(&self) -> Option<u8> {
131        if self.len > 1 {
132            Some(self.msg[1])
133        } else {
134            None
135        }
136    }
137
138    /// Return true if this is a broadcast message.
139    #[inline]
140    #[must_use]
141    pub fn is_broadcast(&self) -> bool {
142        (self.msg[0] & 0xf) == 0xf
143    }
144
145    /**
146     * Initialize the message structure.
147     * `initiator` is the logical address of the initiator and
148     * `destination` the logical address of the destination (`0xf` for broadcast).
149     *
150     * The whole structure is zeroed, the len field is set to 1 (i.e. a poll
151     * message) and the initiator and destination are filled in.
152     */
153    #[inline]
154    #[must_use]
155    pub fn new(initiator: LogicalAddress, destination: LogicalAddress) -> cec_msg {
156        let mut msg = cec_msg {
157            tx_ts: 0,
158            rx_ts: 0,
159            len: 1,
160            timeout: 0,
161            sequence: 0,
162            flags: CEC_MSG_FL::empty(),
163            msg: [0; 16],
164            reply: 0,
165            rx_status: CEC_RX_STATUS::empty(),
166            tx_status: CEC_TX_STATUS::empty(),
167            tx_arb_lost_cnt: 0,
168            tx_nack_cnt: 0,
169            tx_low_drive_cnt: 0,
170            tx_error_cnt: 0,
171        };
172        msg.msg[0] = (initiator << 4) | destination;
173
174        msg
175    }
176
177    #[inline]
178    #[must_use]
179    pub fn with_timeout(mut self, timeout_ms: u32) -> cec_msg {
180        self.timeout = timeout_ms;
181        self
182    }
183
184    #[inline]
185    #[must_use]
186    pub fn from_timeout(timeout_ms: u32) -> cec_msg {
187        cec_msg {
188            tx_ts: 0,
189            rx_ts: 0,
190            len: 0,
191            timeout: timeout_ms,
192            sequence: 0,
193            flags: CEC_MSG_FL::empty(),
194            msg: [0; 16],
195            reply: 0,
196            rx_status: CEC_RX_STATUS::empty(),
197            tx_status: CEC_TX_STATUS::empty(),
198            tx_arb_lost_cnt: 0,
199            tx_nack_cnt: 0,
200            tx_low_drive_cnt: 0,
201            tx_error_cnt: 0,
202        }
203    }
204
205    /**
206     * Fill in destination/initiator in a reply message.
207     *
208     * Set the msg destination to the orig initiator and the msg initiator to the
209     * orig destination. Note that msg and orig may be the same pointer, in which
210     * case the change is done in place.
211     */
212    #[inline]
213    pub fn set_reply_to(&mut self, orig: &cec_msg) {
214        /* The destination becomes the initiator and vice versa */
215        self.msg[0] = (orig.destination() << 4) | orig.initiator();
216        self.reply = 0;
217        self.timeout = 0;
218    }
219
220    /// Return true if this message contains the result of an earlier non-blocking transmit
221    #[inline]
222    #[must_use]
223    pub fn recv_is_tx_result(&self) -> bool {
224        self.sequence != 0 && !self.tx_status.is_empty() && self.rx_status.is_empty()
225    }
226
227    /// Return true if this message contains the reply of an earlier non-blocking transmit
228    #[inline]
229    #[must_use]
230    pub fn recv_is_rx_result(&self) -> bool {
231        self.sequence != 0 && self.tx_status.is_empty() && !self.rx_status.is_empty()
232    }
233
234    #[inline]
235    #[must_use]
236    pub fn status_is_ok(&self) -> bool {
237        if !self.tx_status.is_empty() && !self.tx_status.contains(CEC_TX_STATUS::OK) {
238            return false;
239        }
240        if !self.rx_status.is_empty() && !self.rx_status.contains(CEC_RX_STATUS::OK) {
241            return false;
242        }
243        if self.tx_status.is_empty() && self.rx_status.is_empty() {
244            return false;
245        }
246        !self.rx_status.contains(CEC_RX_STATUS::FEATURE_ABORT)
247    }
248}
249
250bitflags! {
251    /// A bitflag struct for values of the CEC_LOG_ADDR_MASK_* constants
252    #[derive(Debug, Copy, Clone, Default)]
253    pub struct CEC_LOG_ADDR_MASK: u16 {
254        const TV = CEC_LOG_ADDR_MASK_TV;
255        const MASK_RECORD = CEC_LOG_ADDR_MASK_RECORD;
256        const TUNER = CEC_LOG_ADDR_MASK_TUNER;
257        const PLAYBACK = CEC_LOG_ADDR_MASK_PLAYBACK;
258        const AUDIOSYSTEM = CEC_LOG_ADDR_MASK_AUDIOSYSTEM;
259        const BACKUP = CEC_LOG_ADDR_MASK_BACKUP;
260        const SPECIFIC = CEC_LOG_ADDR_MASK_SPECIFIC;
261        const UNREGISTERED = CEC_LOG_ADDR_MASK_UNREGISTERED;
262    }
263}
264
265bitflags! {
266    /// A bitflag struct for values of the CEC_CAP_* constants
267    #[derive(Debug, Copy, Clone, Default)]
268    pub struct CEC_CAP: u32 {
269        /// Userspace has to configure the physical address
270        const PHYS_ADDR = CEC_CAP_PHYS_ADDR;
271        /// Userspace has to configure the logical addresses
272        const LOG_ADDRS = CEC_CAP_LOG_ADDRS;
273        /// Userspace can transmit messages (and thus become follower as well)
274        const TRANSMIT = CEC_CAP_TRANSMIT;
275        /// Passthrough all messages instead of processing them.
276        const PASSTHROUGH = CEC_CAP_PASSTHROUGH;
277        /// Supports remote control
278        const RC = CEC_CAP_RC;
279        /// Hardware can monitor all messages, not just directed and broadcast.
280        const MONITOR_ALL = CEC_CAP_MONITOR_ALL;
281        /// Hardware can use CEC only if the HDMI HPD pin is high.
282        const NEEDS_HPD = CEC_CAP_NEEDS_HPD;
283        /// Hardware can monitor CEC pin transitions
284        const MONITOR_PIN = CEC_CAP_MONITOR_PIN;
285        /// CEC_ADAP_G_CONNECTOR_INFO is available
286        const CONNECTOR_INFO = CEC_CAP_CONNECTOR_INFO;
287        /// CEC_MSG_FL_REPLY_VENDOR_ID is available
288        const REPLY_VENDOR_ID = CEC_CAP_REPLY_VENDOR_ID;
289    }
290}
291
292/// CEC capabilities structure.
293#[derive(Debug, Clone, Default)]
294#[repr(C)]
295pub struct cec_caps {
296    /// Name of the CEC device driver.
297    pub driver: [c_char; 32],
298    /// Name of the CEC device. `driver` + `name` must be unique.
299    pub name: [c_char; 32],
300    /// Number of available logical addresses.
301    pub available_log_addrs: u32,
302    /// Capabilities of the CEC adapter.
303    pub capabilities: CEC_CAP,
304    /// version of the CEC adapter framework.
305    pub version: u32,
306}
307
308/// CEC logical addresses structure
309#[derive(Debug, Clone, Default)]
310#[repr(C)]
311pub struct cec_log_addrs {
312    /// The claimed logical addresses. Set by the driver.
313    pub log_addr: [u8; CEC_MAX_LOG_ADDRS],
314    /// Current logical address mask. Set by the driver.
315    pub log_addr_mask: CEC_LOG_ADDR_MASK,
316    /// The CEC version that the adapter should implement. Set by the caller.
317    pub cec_version: u8,
318    /// How many logical addresses should be claimed. Set by the caller.
319    pub num_log_addrs: u8,
320    /// The vendor ID of the device. Set by the caller.
321    pub vendor_id: VendorId,
322    /// Flags.
323    pub flags: CEC_LOG_ADDRS_FL,
324    /// The OSD name of the device. Set by the caller.
325    pub osd_name: [c_uchar; 15],
326    /// The primary device type for each logical address. Set by the caller.
327    pub primary_device_type: [u8; CEC_MAX_LOG_ADDRS],
328    /// The logical address types. Set by the caller.
329    pub log_addr_type: [u8; CEC_MAX_LOG_ADDRS],
330
331    /* CEC 2.0 */
332    /// CEC 2.0: all device types represented by the logical address. Set by the caller.
333    pub all_device_types: [u8; CEC_MAX_LOG_ADDRS],
334    /// CEC 2.0: The logical address features. Set by the caller.
335    pub features: [[u8; 12]; CEC_MAX_LOG_ADDRS],
336}
337
338impl cec_log_addrs {
339    /* Helper functions to identify the 'special' CEC devices */
340
341    #[inline]
342    #[must_use]
343    pub fn is_2nd_tv(&self) -> bool {
344        /*
345         * It is a second TV if the logical address is 14 or 15 and the
346         * primary device type is a TV.
347         */
348        self.num_log_addrs != 0
349            && self.log_addr[0] >= CEC_LOG_ADDR_SPECIFIC
350            && self.primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_TV
351    }
352
353    #[inline]
354    #[must_use]
355    pub fn is_processor(&self) -> bool {
356        /*
357         * It is a processor if the logical address is 12-15 and the
358         * primary device type is a Processor.
359         */
360        self.num_log_addrs != 0
361            && self.log_addr[0] >= CEC_LOG_ADDR_BACKUP_1
362            && self.primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_PROCESSOR
363    }
364
365    #[inline]
366    #[must_use]
367    pub fn is_switch(&self) -> bool {
368        /*
369         * It is a switch if the logical address is 15 and the
370         * primary device type is a Switch and the CDC-Only flag is not set.
371         */
372        self.num_log_addrs == 1
373            && self.log_addr[0] == CEC_LOG_ADDR_UNREGISTERED
374            && self.primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_SWITCH
375            && !self.flags.contains(CEC_LOG_ADDRS_FL::CDC_ONLY)
376    }
377
378    #[inline]
379    #[must_use]
380    pub fn is_cdc_only(&self) -> bool {
381        /*
382         * It is a CDC-only device if the logical address is 15 and the
383         * primary device type is a Switch and the CDC-Only flag is set.
384         */
385        self.num_log_addrs == 1
386            && self.log_addr[0] == CEC_LOG_ADDR_UNREGISTERED
387            && self.primary_device_type[0] == CEC_OP_PRIM_DEVTYPE_SWITCH
388            && self.flags.contains(CEC_LOG_ADDRS_FL::CDC_ONLY)
389    }
390}
391
392bitflags! {
393    /// A bitflag struct for values of the CEC_LOG_ADDRS_FL_* constants
394    #[derive(Debug, Copy, Clone, Default)]
395    pub struct CEC_LOG_ADDRS_FL: u32 {
396        /// Allow a fallback to unregistered
397        const ALLOW_UNREG_FALLBACK = CEC_LOG_ADDRS_FL_ALLOW_UNREG_FALLBACK;
398        /// Passthrough RC messages to the input subsystem
399        const ALLOW_RC_PASSTHRU = CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU;
400        /// CDC-Only device: supports only CDC messages
401        const CDC_ONLY = CEC_LOG_ADDRS_FL_CDC_ONLY;
402    }
403}
404
405/// Tells which drm connector is associated with the CEC adapter.
406#[derive(Debug, Copy, Clone, Default)]
407#[repr(C)]
408pub struct cec_drm_connector_info {
409    /// drm card number
410    pub card_no: u32,
411    /// drm connector ID
412    pub connector_id: u32,
413}
414
415#[derive(Copy, Clone)]
416#[repr(C)]
417pub union cec_connector_info_union {
418    /// drm connector info
419    pub drm: cec_drm_connector_info,
420    /// Array to pad the union
421    pub raw: [u32; 16],
422}
423
424/// Tells if and which connector is associated with the CEC adapter.
425#[derive(Clone)]
426#[repr(C)]
427pub struct cec_connector_info {
428    /// Connector type (if any)
429    pub ty: u32,
430    pub data: cec_connector_info_union,
431}
432
433impl Default for cec_connector_info {
434    #[inline]
435    fn default() -> cec_connector_info {
436        cec_connector_info {
437            ty: CEC_CONNECTOR_TYPE_NO_CONNECTOR,
438            data: cec_connector_info_union { raw: [0; 16] },
439        }
440    }
441}
442
443bitflags! {
444    /// A bitflag struct for values of the CEC_EVENT_FL_* constants
445    #[derive(Debug, Copy, Clone, Default)]
446    pub struct CEC_EVENT_FL: u32 {
447        const INITIAL_STATE = CEC_EVENT_FL_INITIAL_STATE;
448        const DROPPED_EVENTS = CEC_EVENT_FL_DROPPED_EVENTS;
449    }
450}
451
452/// Used when the CEC adapter changes state.
453#[derive(Debug, Copy, Clone)]
454#[repr(C)]
455pub struct cec_event_state_change {
456    /// The current physical address
457    pub phys_addr: PhysicalAddress,
458    /// The current logical address mask
459    pub log_addr_mask: CEC_LOG_ADDR_MASK,
460    /** If non-zero, then HDMI connector information is available.
461     *  This field is only valid if `CEC_CAP_CONNECTOR_INFO` is set. If that
462     *  capability is set and `have_conn_info` is zero, then that indicates
463     *  that the HDMI connector device is not instantiated, either because
464     *  the HDMI driver is still configuring the device or because the HDMI
465     *  device was unbound.
466     */
467    pub have_conn_info: u16,
468}
469
470/// Tells you how many messages were lost.
471#[derive(Debug, Copy, Clone)]
472#[repr(C)]
473pub struct cec_event_lost_msgs {
474    /// How many messages were lost.
475    pub lost_msgs: u32,
476}
477
478#[derive(Copy, Clone)]
479#[repr(C)]
480pub union cec_event_union {
481    /// The event payload for `CEC_EVENT_STATE_CHANGE`.
482    pub state_change: cec_event_state_change,
483    /// The event payload for `CEC_EVENT_LOST_MSGS`.
484    pub lost_msgs: cec_event_lost_msgs,
485    /// Array to pad the union.
486    pub raw: [u32; 16],
487}
488
489/// CEC event structure
490#[derive(Clone)]
491#[repr(C)]
492pub struct cec_event {
493    /// The timestamp of when the event was sent.
494    pub ts: Timestamp,
495    /// The event.
496    pub event: u32,
497    /// Event flags.
498    pub flags: CEC_EVENT_FL,
499    pub data: cec_event_union,
500}
501
502impl Default for cec_event {
503    #[inline]
504    fn default() -> cec_event {
505        cec_event {
506            ts: 0,
507            event: 0,
508            flags: CEC_EVENT_FL::default(),
509            data: cec_event_union { raw: [0; 16] },
510        }
511    }
512}
513
514/// Convenience type for the non-zero null vendor ID
515#[derive(Copy, Clone, Debug, PartialEq, Eq)]
516#[repr(transparent)]
517pub struct VendorId(u32);
518
519impl Default for VendorId {
520    #[inline]
521    fn default() -> Self {
522        VendorId(CEC_VENDOR_ID_NONE)
523    }
524}
525
526impl TryFrom<u32> for VendorId {
527    type Error = ();
528
529    #[inline]
530    fn try_from(val: u32) -> Result<VendorId, ()> {
531        if val < 0x1_00_00_00 || val == CEC_VENDOR_ID_NONE {
532            Ok(VendorId(val))
533        } else {
534            Err(())
535        }
536    }
537}
538
539impl From<VendorId> for u32 {
540    #[inline]
541    fn from(val: VendorId) -> u32 {
542        val.0
543    }
544}
545
546impl VendorId {
547    #[inline]
548    #[must_use]
549    pub fn is_none(self) -> bool {
550        self.0 == CEC_VENDOR_ID_NONE
551    }
552
553    #[inline]
554    #[must_use]
555    pub fn is_valid(self) -> bool {
556        self.0 < 0x1_00_00_00
557    }
558}