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规范就足够了。
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),请调用
其中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,然后再启用新的物理地址。
一个辅助函数,它从 edid 结构中提取物理地址,并使用该地址调用 cec_s_phys_addr()
,如果 EDID 未包含物理地址或 edid 是 NULL 指针,则调用 CEC_PHYS_ADDR_INVALID。
-
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 的通知器已存在,则增加 refcount 并返回该通知器。
如果不存在,则分配一个新的通知器结构并返回指向该新结构的指针。
如果无法分配内存,则返回 NULL。
-
void cec_notifier_conn_unregister(struct cec_notifier *n)¶
减少 refcount,当 refcount 达到 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 的通知器已存在,则增加 refcount 并返回该通知器。
如果不存在,则分配一个新的通知器结构并返回指向该新结构的指针。
如果无法分配内存,则返回 NULL。
-
void cec_notifier_cec_adap_unregister(struct cec_notifier *n, struct cec_adapter *adap)¶
减少 refcount,当 refcount 达到 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
edid 结构体指针
描述
解析 EDID 以获取新的 CEC 物理地址并进行设置。如果 n == NULL,则不执行任何操作。
参数
struct device *dev
具有“hdmi-phandle”设备树属性的设备
描述
返回由“hdmi-phandle”属性引用的设备指针。 请注意,返回的设备的引用计数不会递增。此设备指针仅用作通知器列表中的键值,但 CEC 驱动程序永远不会访问它。
-
void cec_notifier_phys_addr_invalidate(struct cec_notifier *n)¶
将物理地址设置为无效。
参数
struct cec_notifier *n
CEC 通知器
描述
这是一个简单的辅助函数,用于使物理地址无效。如果 n == NULL,则不执行任何操作。