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_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 *dev
具有“hdmi-phandle”设备树属性的设备
描述
返回“hdmi-phandle”属性引用的设备指针。 请注意,不会递增返回设备的引用计数。 此设备指针仅用作通知器列表中的键值,但 CEC 驱动程序永远不会访问它。
-
void cec_notifier_phys_addr_invalidate(struct cec_notifier *n)¶
将物理地址设置为 INVALID
参数
struct cec_notifier *n
CEC 通知器
描述
这是一个简单的辅助函数,用于使物理地址无效。 如果 n == NULL,则不执行任何操作。