6. CEC 核心支援

CEC 框架為 HDMI CEC 硬體的使用提供了一個統一的核心介面。它旨在處理多種型別的硬體(接收器、發射器、USB 加密狗)。該框架還允許選擇在核心驅動程式中執行哪些操作以及應由使用者空間應用程式處理哪些操作。此外,它將遙控器直通功能整合到核心的遙控器框架中。

6.1. CEC 協議

CEC 協議使消費電子裝置可以透過 HDMI 連線相互通訊。該協議在通訊中使用邏輯地址。邏輯地址與裝置提供的功能嚴格相關。充當通訊樞紐的電視始終被分配地址 0。物理地址由裝置之間的物理連線決定。

此處描述的 CEC 框架與 CEC 2.0 規範保持同步。它在 HDMI 1.4 規範中進行了文件化,新的 2.0 位在 HDMI 2.0 規範中進行了文件化。但對於大多數功能,免費提供的 HDMI 1.3a 規範就足夠了

https://www.hdmi.org/spec/index

6.2. CEC 介面卡介面

struct cec_adapter 表示 CEC 介面卡硬體。它透過呼叫 cec_allocate_adapter() 建立,並透過呼叫 cec_delete_adapter() 刪除

struct cec_adapter *cec_allocate_adapter(const struct cec_adap_ops *ops, void *priv, const char *name, u32 caps, u8 available_las);
void cec_delete_adapter(struct cec_adapter *adap);

要建立介面卡,您需要傳遞以下資訊

ops

介面卡操作,由 CEC 框架呼叫,您必須實現。

priv

將儲存在 adap->priv 中,可由介面卡操作使用。使用 cec_get_drvdata(adap) 獲取 priv 指標。

name

CEC 介面卡的名稱。注意:此名稱將被複制。

caps

CEC 介面卡的功能。這些功能決定了硬體的功能以及哪些部分由使用者空間處理,哪些部分由核心空間處理。這些功能由 CEC_ADAP_G_CAPS 返回。

available_las

此介面卡可以處理的同步邏輯地址數。必須為 1 <= available_las <= CEC_MAX_LOG_ADDRS。

要獲取 priv 指標,請使用此輔助函式

void *cec_get_drvdata(const struct cec_adapter *adap);

要註冊 /dev/cecX 裝置節點和遙控器裝置(如果設定了 CEC_CAP_RC),請呼叫

int cec_register_adapter(struct cec_adapter *adap, struct device *parent);

其中 parent 是父裝置。

要登出裝置,請呼叫

void cec_unregister_adapter(struct cec_adapter *adap);

注意:如果 cec_register_adapter() 失敗,則呼叫 cec_delete_adapter() 進行清理。但是如果 cec_register_adapter() 成功,則僅呼叫 cec_unregister_adapter() 進行清理,永遠不要呼叫 cec_delete_adapter()。一旦 /dev/cecX 裝置的最後一個使用者關閉其檔案控制代碼,登出函式將自動刪除該介面卡。

6.3. 實現底層 CEC 介面卡

以下底層介面卡操作必須在您的驅動程式中實現

struct cec_adap_ops
struct cec_adap_ops
{
        /* Low-level callbacks */
        int (*adap_enable)(struct cec_adapter *adap, bool enable);
        int (*adap_monitor_all_enable)(struct cec_adapter *adap, bool enable);
        int (*adap_monitor_pin_enable)(struct cec_adapter *adap, bool enable);
        int (*adap_log_addr)(struct cec_adapter *adap, u8 logical_addr);
        void (*adap_unconfigured)(struct cec_adapter *adap);
        int (*adap_transmit)(struct cec_adapter *adap, u8 attempts,
                              u32 signal_free_time, struct cec_msg *msg);
        void (*adap_nb_transmit_canceled)(struct cec_adapter *adap,
                                          const struct cec_msg *msg);
        void (*adap_status)(struct cec_adapter *adap, struct seq_file *file);
        void (*adap_free)(struct cec_adapter *adap);

        /* Error injection callbacks */
        ...

        /* High-level callback */
        ...
};

這些底層操作處理控制 CEC 介面卡硬體的各個方面。它們都在持有互斥鎖 adap->lock 的情況下被呼叫。

啟用/停用硬體

int (*adap_enable)(struct cec_adapter *adap, bool enable);

此回撥啟用或停用 CEC 硬體。啟用 CEC 硬體意味著在沒有宣告任何邏輯地址的狀態下啟動它。如果設定了 CEC_CAP_NEEDS_HPD,則物理地址將始終有效。如果未設定該功能,則物理地址可能會在啟用 CEC 硬體時更改。CEC 驅動程式不應設定 CEC_CAP_NEEDS_HPD,除非硬體設計需要這樣做,因為這將無法喚醒在待機模式下將 HPD 拉低的顯示器。呼叫 cec_allocate_adapter() 後,CEC 介面卡的初始狀態為停用。

請注意,如果 enable 為 false,則 adap_enable 必須返回 0。

啟用/停用“監視所有”模式

int (*adap_monitor_all_enable)(struct cec_adapter *adap, bool enable);

如果啟用,則應將介面卡置於也監視非發給我們的訊息的模式。並非所有硬體都支援此功能,並且僅當設定了 CEC_CAP_MONITOR_ALL 功能時才呼叫此函式。此回撥是可選的(某些硬體可能始終處於“監視所有”模式)。

請注意,如果 enable 為 false,則 adap_monitor_all_enable 必須返回 0。

啟用/停用“監視引腳”模式

int (*adap_monitor_pin_enable)(struct cec_adapter *adap, bool enable);

如果啟用,則應將介面卡置於也監視 CEC 引腳更改的模式。並非所有硬體都支援此功能,並且僅當設定了 CEC_CAP_MONITOR_PIN 功能時才呼叫此函式。此回撥是可選的(某些硬體可能始終處於“監視引腳”模式)。

請注意,如果 enable 為 false,則 adap_monitor_pin_enable 必須返回 0。

程式設計新的邏輯地址

int (*adap_log_addr)(struct cec_adapter *adap, u8 logical_addr);

如果 logical_addr == CEC_LOG_ADDR_INVALID,則應擦除所有程式設計的邏輯地址。否則,應程式設計給定的邏輯地址。如果超過了最大可用邏輯地址數,則應返回 -ENXIO。一旦程式設計了邏輯地址,CEC 硬體就可以接收發給該地址的定向訊息。

請注意,如果 logical_addr 為 CEC_LOG_ADDR_INVALID,則 adap_log_addr 必須返回 0。

介面卡未配置時呼叫

void (*adap_unconfigured)(struct cec_adapter *adap);

介面卡未配置。如果驅動程式在未配置後必須執行特定操作,則可以透過此可選回撥來完成。

傳送新訊息

int (*adap_transmit)(struct cec_adapter *adap, u8 attempts,
                     u32 signal_free_time, struct cec_msg *msg);

這會發送一條新訊息。attempts 引數是建議的傳送嘗試次數。

signal_free_time 是介面卡在嘗試傳送訊息之前,線路空閒時應等待的資料位週期數。此值取決於此傳送是重試、來自新發起者的訊息還是同一發起者的新訊息。大多數硬體會自動處理此問題,但在某些情況下需要此資訊。

CEC_FREE_TIME_TO_USEC 宏可用於將 signal_free_time 轉換為微秒(一個數據位週期為 2.4 毫秒)。

傳遞已取消的非阻塞傳送的結果

void (*adap_nb_transmit_canceled)(struct cec_adapter *adap,
                                  const struct cec_msg *msg);

此可選回撥可用於獲取序列號為 msg->sequence 的已取消的非阻塞傳送的結果。如果傳送已中止、傳送超時(即硬體從未發出傳送完成的訊號)或傳送成功,但等待預期回覆已中止或超時,則會呼叫此回撥。

記錄當前 CEC 硬體狀態

void (*adap_status)(struct cec_adapter *adap, struct seq_file *file);

此可選回撥可用於顯示 CEC 硬體的狀態。該狀態可透過 debugfs 獲得:cat /sys/kernel/debug/cec/cecX/status

刪除介面卡時釋放任何資源

void (*adap_free)(struct cec_adapter *adap);

此可選回撥可用於釋放驅動程式可能已分配的任何資源。它從 cec_delete_adapter 呼叫。

您的介面卡驅動程式還必須透過在以下情況下呼叫框架來響應事件(通常是中斷驅動的)

傳送完成(成功或其他)時

void cec_transmit_done(struct cec_adapter *adap, u8 status,
                       u8 arb_lost_cnt,  u8 nack_cnt, u8 low_drive_cnt,
                       u8 error_cnt);

void cec_transmit_attempt_done(struct cec_adapter *adap, u8 status);

狀態可以是以下之一

CEC_TX_STATUS_OK

傳送成功。

CEC_TX_STATUS_ARB_LOST

仲裁丟失:另一個 CEC 發起者控制了 CEC 線路,您失去了仲裁。

CEC_TX_STATUS_NACK

訊息被否定確認(對於定向訊息)或確認(對於廣播訊息)。需要重新傳輸。

CEC_TX_STATUS_LOW_DRIVE

在 CEC 總線上檢測到低驅動。這表示跟隨者檢測到總線上有錯誤並請求重新傳輸。

CEC_TX_STATUS_ERROR

發生了一些未指定的錯誤:如果硬體無法區分或完全是其他錯誤,則可能是 ARB_LOST 或 LOW_DRIVE。一些硬體僅支援 OK 和 FAIL 作為傳送的結果,即無法區分不同的可能錯誤。在這種情況下,將 FAIL 對映到 CEC_TX_STATUS_NACK 而不是 CEC_TX_STATUS_ERROR。

CEC_TX_STATUS_MAX_RETRIES

多次嘗試後無法傳送訊息。只有當驅動程式具有硬體支援來重試訊息時,才應由驅動程式設定。如果設定,則框架假定它不必再次嘗試傳送訊息,因為硬體已經這樣做了。

硬體必須能夠區分 OK、NACK 和“其他”。

*_cnt 引數是看到的錯誤條件數。如果沒有可用資訊,則可能為 0。不支援硬體重試的驅動程式可以將對應於傳送錯誤的計數器設定為 1,如果硬體支援重試,則如果硬體不提供哪些錯誤發生以及多少次的反饋,則將這些計數器設定為 0,或者填寫硬體報告的正確值。

請注意,如果在佇列中有一個等待發送的訊息,則呼叫這些函式可能會立即開始新的傳送。因此,請確保硬體處於可以啟動新發送的狀態之後再呼叫這些函式。

cec_transmit_attempt_done() 函式是硬體從不重試,因此傳送始終只進行一次嘗試的情況下的輔助函式。它將依次呼叫 cec_transmit_done(),為對應於狀態的 count 引數填寫 1。或者如果狀態為 OK,則全部為 0。

收到 CEC 訊息時

void cec_received_msg(struct cec_adapter *adap, struct cec_msg *msg);

不言而喻。

6.4. 實現中斷處理程式

通常,CEC 硬體提供中斷,以指示傳送何時完成以及是否成功,並且在收到 CEC 訊息時提供中斷。

CEC 驅動程式應始終先處理傳送中斷,然後再處理接收中斷。框架希望在 cec_received_msg 呼叫之前看到 cec_transmit_done 呼叫,否則如果收到的訊息是對傳送的訊息的回覆,則可能會感到困惑。

6.5. 可選:實現錯誤注入支援

如果 CEC 介面卡支援錯誤注入功能,則可以透過錯誤注入回撥公開該功能

struct cec_adap_ops {
        /* Low-level callbacks */
        ...

        /* Error injection callbacks */
        int (*error_inj_show)(struct cec_adapter *adap, struct seq_file *sf);
        bool (*error_inj_parse_line)(struct cec_adapter *adap, char *line);

        /* High-level CEC message callback */
        ...
};

如果同時設定了這兩個回撥,則 error-inj 檔案將出現在 debugfs 中。基本語法如下

將忽略前導空格/製表符。如果下一個字元是 # 或到達行尾,則將忽略整行。否則,將需要一個命令。

此基本解析在 CEC 框架中完成。驅動程式可以自由決定要實現的命令。唯一的要求是必須實現沒有任何引數的命令 clear,並且它將刪除所有當前的錯誤注入命令。

這確保您可以始終執行 echo clear >error-inj 以清除任何錯誤注入,而無需知道特定於驅動程式的命令的詳細資訊。

請注意,error-inj 的輸出應作為 error-inj 的輸入有效。因此,這必須有效

$ cat error-inj >einj.txt
$ cat einj.txt >error-inj

讀取此檔案時會呼叫第一個回撥,它應顯示當前的錯誤注入狀態

int (*error_inj_show)(struct cec_adapter *adap, struct seq_file *sf);

建議從帶有基本使用資訊的註釋塊開始。它返回 0 表示成功,否則返回錯誤。

第二個回撥將解析寫入 error-inj 檔案的命令

bool (*error_inj_parse_line)(struct cec_adapter *adap, char *line);

line 引數指向命令的開頭。任何前導空格或製表符都已被跳過。它只是單行(因此沒有嵌入換行符),並且以 0 結尾。回撥可以自由修改緩衝區的內容。僅為包含命令的行呼叫它,因此永遠不會為空行或註釋行呼叫此回撥。

如果命令有效,則返回 true;如果存在語法錯誤,則返回 false。

6.6. 實現高階 CEC 介面卡

底層操作驅動硬體,高階操作由 CEC 協議驅動。在不持有 adap->lock 互斥鎖的情況下呼叫高階回撥。以下高階回撥可用

struct cec_adap_ops {
        /* Low-level callbacks */
        ...

        /* Error injection callbacks */
        ...

        /* High-level CEC message callback */
        void (*configured)(struct cec_adapter *adap);
        int (*received)(struct cec_adapter *adap, struct cec_msg *msg);
};

介面卡已配置時呼叫

void (*configured)(struct cec_adapter *adap);

介面卡已完全配置,即所有邏輯地址都已成功宣告。如果驅動程式在配置後必須執行特定操作,則可以透過此可選回撥來完成。

received() 回撥允許驅動程式選擇性地處理新收到的 CEC 訊息

int (*received)(struct cec_adapter *adap, struct cec_msg *msg);

如果驅動程式想要處理 CEC 訊息,則可以實現此回撥。如果它不想處理此訊息,則應返回 -ENOMSG,否則 CEC 框架會假定它已處理此訊息,並且不會對其執行任何操作。

6.7. CEC 框架函式

CEC 介面卡驅動程式可以呼叫以下 CEC 框架函式

int cec_transmit_msg(struct cec_adapter *adap, struct cec_msg *msg, bool block);

傳送 CEC 訊息。如果 block 為 true,則等待直到訊息已傳送,否則只需將其排隊並返回。

void cec_s_phys_addr(struct cec_adapter *adap, u16 phys_addr, bool block);

更改物理地址。如果已更改,此函式將設定 adap->phys_addr 併發送一個事件。如果已呼叫 cec_s_log_addrs() 且物理地址已變為有效,則 CEC 框架將開始宣告邏輯地址。如果 block 為 true,則此函式將不會返回,直到此過程完成。

當物理地址設定為有效值時,將啟用 CEC 介面卡(請參閱 adap_enable 操作)。當設定為 CEC_PHYS_ADDR_INVALID 時,將停用 CEC 介面卡。如果您將有效物理地址更改為另一個有效物理地址,則此函式將首先將地址設定為 CEC_PHYS_ADDR_INVALID,然後再啟用新的物理地址。

void cec_s_phys_addr_from_edid(struct cec_adapter *adap, const struct edid *edid);

一個輔助函式,用於從 edid 結構中提取物理地址,並使用該地址或 CEC_PHYS_ADDR_INVALID 呼叫 cec_s_phys_addr()(如果 EDID 未包含物理地址或 edid 是 NULL 指標)。

int cec_s_log_addrs(struct cec_adapter *adap, struct cec_log_addrs *log_addrs, bool block);

宣告 CEC 邏輯地址。如果設定了 CEC_CAP_LOG_ADDRS,則永遠不應呼叫。如果 block 為 true,則等待直到已宣告邏輯地址,否則只需將其排隊並返回。要取消配置所有邏輯地址,請呼叫此函式並將 log_addrs 設定為 NULL 或將 log_addrs->num_log_addrs 設定為 0。取消配置時將忽略 block 引數。如果物理地址無效,此函式將只返回。一旦物理地址變為有效,框架將嘗試宣告這些邏輯地址。

6.8. CEC 引腳框架

大多數 CEC 硬體都在完整的 CEC 訊息上執行,軟體提供訊息,硬體處理底層 CEC 協議。但一些硬體只驅動 CEC 引腳,軟體必須處理底層 CEC 協議。建立 CEC 引腳框架是為了處理此類裝置。

請注意,由於接近即時的要求,永遠無法保證 100% 工作。此框架在內部使用高解析度計時器,但如果計時器延遲超過 300 微秒,則可能發生錯誤的結果。實際上,它看起來相當可靠。

這種低階實現的一個優點是它可以用作廉價的 CEC 分析器,尤其是在可以使用中斷檢測 CEC 引腳從低到高或反之的轉換時。

struct cec_pin_ops

低階 CEC 引腳操作

定義:

struct cec_pin_ops {
    int (*read)(struct cec_adapter *adap);
    void (*low)(struct cec_adapter *adap);
    void (*high)(struct cec_adapter *adap);
    bool (*enable_irq)(struct cec_adapter *adap);
    void (*disable_irq)(struct cec_adapter *adap);
    void (*free)(struct cec_adapter *adap);
    void (*status)(struct cec_adapter *adap, struct seq_file *file);
    int (*read_hpd)(struct cec_adapter *adap);
    int (*read_5v)(struct cec_adapter *adap);
    int (*received)(struct cec_adapter *adap, struct cec_msg *msg);
};

成員

read

讀取 CEC 引腳。如果高,則返回 > 0;如果低,則返回 0;如果負數,則返回錯誤。

low

將 CEC 引腳驅動為低電平。

high

停止驅動 CEC 引腳。除非其他人將引腳驅動為低電平,否則上拉電阻會將引腳驅動為高電平。

enable_irq

可選,啟用中斷以檢測引腳電壓變化。

disable_irq

可選,停用中斷。

free

可選。釋放任何已分配的資源。在刪除介面卡時呼叫。

status

可選,記錄狀態資訊。

read_hpd

可選。讀取 HPD 引腳。如果高,則返回 > 0;如果低,則返回 0;如果負數,則返回錯誤。

read_5v

可選。讀取 5V 引腳。如果高,則返回 > 0;如果低,則返回 0;如果負數,則返回錯誤。

received

可選。高階 CEC 訊息回撥。允許驅動程式處理 CEC 訊息。

描述

這些操作(除了 received 操作)由 cec 引腳框架用於操作 CEC 引腳。

void cec_pin_changed(struct cec_adapter *adap, bool value)

從中斷更新引腳狀態

引數

struct cec_adapter *adap

指向 cec 介面卡的指標

bool value

當為 true 時,引腳為高電平,否則為低電平

描述

如果透過中斷檢測到 CEC 電壓的變化,則從具有新值的 中斷呼叫 cec_pin_changed。

struct cec_adapter *cec_pin_allocate_adapter(const struct cec_pin_ops *pin_ops, void *priv, const char *name, u32 caps)

分配一個基於引腳的 CEC 介面卡

引數

const struct cec_pin_ops *pin_ops

底層引腳操作

void *priv

將儲存在 adap->priv 中,可由介面卡操作使用。使用 cec_get_drvdata(adap) 獲取 priv 指標。

const char *name

CEC 介面卡的名稱。注意:此名稱將被複制。

u32 caps

CEC 介面卡的能力。 這將被與 CEC_CAP_MONITOR_ALL 和 CEC_CAP_MONITOR_PIN 進行 OR 運算。

描述

使用 CEC 引腳框架分配一個 CEC 介面卡。

返回值

指向 CEC 介面卡的指標或錯誤指標

6.9. CEC 通知器框架

大多數 DRM HDMI 實現都有整合的 CEC 實現,不需要通知器支援。 但有些有獨立的 CEC 實現,它們有自己的驅動程式。 這可能是 SoC 的 IP 塊,也可能是處理 CEC 引腳的完全獨立的晶片。 對於這些情況,DRM 驅動程式可以安裝一個通知器,並使用該通知器通知 CEC 驅動程式物理地址的變化。

struct cec_notifier *cec_notifier_conn_register(struct device *hdmi_dev, const char *port_name, const struct cec_connector_info *conn_info)

為給定的 HDMI 裝置和聯結器元組查詢或建立一個新的 cec_notifier。

引數

struct device *hdmi_dev

傳送事件的 HDMI 裝置。

const char *port_name

事件發生的聯結器名稱。 如果 HDMI 裝置始終只建立一個 HDMI 聯結器,則可能為 NULL。

const struct cec_connector_info *conn_info

事件發生的聯結器資訊(可能為 NULL)

描述

如果裝置 dev 和聯結器 port_name 的通知器已經存在,則增加引用計數並返回該通知器。

如果它不存在,則分配一個新的通知器結構並返回指向該新結構的指標。

如果記憶體無法分配,則返回 NULL。

void cec_notifier_conn_unregister(struct cec_notifier *n)

減少引用計數,並在引用計數達到 0 時刪除。

引數

struct cec_notifier *n

通知器。 如果為 NULL,則此函式不執行任何操作。

struct cec_notifier *cec_notifier_cec_adap_register(struct device *hdmi_dev, const char *port_name, struct cec_adapter *adap)

為給定裝置查詢或建立一個新的 cec_notifier。

引數

struct device *hdmi_dev

傳送事件的 HDMI 裝置。

const char *port_name

事件發生的聯結器名稱。 如果 HDMI 裝置始終只建立一個 HDMI 聯結器,則可能為 NULL。

struct cec_adapter *adap

註冊此通知器的 CEC 介面卡。

描述

如果裝置 dev 和聯結器 port_name 的通知器已經存在,則增加引用計數並返回該通知器。

如果它不存在,則分配一個新的通知器結構並返回指向該新結構的指標。

如果記憶體無法分配,則返回 NULL。

void cec_notifier_cec_adap_unregister(struct cec_notifier *n, struct cec_adapter *adap)

減少引用計數,並在引用計數達到 0 時刪除。

引數

struct cec_notifier *n

通知器。 如果為 NULL,則此函式不執行任何操作。

struct cec_adapter *adap

註冊此通知器的 CEC 介面卡。

void cec_notifier_set_phys_addr(struct cec_notifier *n, u16 pa)

設定新的物理地址。

引數

struct cec_notifier *n

CEC 通知器

u16 pa

CEC 物理地址

描述

設定新的 CEC 物理地址。 如果 n == NULL,則不執行任何操作。

void cec_notifier_set_phys_addr_from_edid(struct cec_notifier *n, const struct edid *edid)

從 EDID 中解析 PA。

引數

struct cec_notifier *n

CEC 通知器

const struct edid *edid

struct edid 指標

描述

解析 EDID 以獲取新的 CEC 物理地址並進行設定。 如果 n == NULL,則不執行任何操作。

struct device *cec_notifier_parse_hdmi_phandle(struct device *dev)

從“hdmi-phandle”中查詢 HDMI 裝置

引數

struct device *dev

具有“hdmi-phandle”裝置樹屬性的裝置

描述

返回“hdmi-phandle”屬性引用的裝置指標。 請注意,不會遞增返回裝置的引用計數。 此裝置指標僅用作通知器列表中的鍵值,但 CEC 驅動程式永遠不會訪問它。

void cec_notifier_phys_addr_invalidate(struct cec_notifier *n)

將物理地址設定為 INVALID

引數

struct cec_notifier *n

CEC 通知器

描述

這是一個簡單的輔助函式,用於使物理地址無效。 如果 n == NULL,則不執行任何操作。