再探RK3588 CEC关机流程

目录

一、前言

我们在探索RK3588 framework层到Hal层CEC关机流程实现大概梳理了Framework层到kernel层CEC的关机流程。但是前段时间又遇到TV关机,CEC设备没有关机问题,在排查过程中,发现前面对于Framework层的认识有点错误, 在这里更正一下。

二、问题现象

我司产品有三路HDMI IN端子,在HDMI1端子整机关机,DVD不会关机。但是HDMI2、HDMI3会正常关机。首先我们用逻辑分析仪,量了整机关机,HDMI CEC引脚的波形,发现没有CEC的波形。到这里,我首先怀疑是硬件问题, 因为我们的三路HDMI是通过HDMI swith芯片在SOC上面拓展出来的,硬件可能漏接了一路HDMI1。但是发现在HDMI1通道,CEC的其他功能是正常的,排出了硬件问题的可能,只能从软件出发,好好分析代码了。

三、解决思路

1.是否可能是硬件问题

HDMI1异常,HDMI2、HDMI3正常,说明硬件是正常的。DVD也是正常的

2.确认DVD是否收到关机信号

使用逻辑分析仪,量了整机关机,HDMI CEC引脚的波形,发现没有CEC的波形。说明DVD未收到关机指令

3.确认整机是否发送关机信号

综合前两个实验,我们已经确认是软件层面的问题了。只能通过阅读Android系统源码来确认问题

四、代码流程分析

1.关机流程回顾

我们这里讲了,CEC的关机第一步是HdmiControlService监听到关机广播后,进行关机, 如下

    private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
        @ServiceThreadOnly
        @Override
        public void onReceive(Context context, Intent intent) {
            assertRunOnServiceThread();
            boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1");
            switch (intent.getAction()) {
                ···
                case Intent.ACTION_SHUTDOWN:
                    if (isPowerOnOrTransient() && !isReboot) {
                        onStandby(STANDBY_SHUTDOWN);
                    }
                    break;
            }
        }
    }
    @ServiceThreadOnly
    @VisibleForTesting
    protected void onStandby(final int standbyAction) {
        mWakeUpMessageReceived = false;
        assertRunOnServiceThread();
        mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY,
                false);
        invokeVendorCommandListenersOnControlStateChanged(false,
                HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);

        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();

        if (!isStandbyMessageReceived() && !canGoToStandby()) {
            mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
            for (HdmiCecLocalDevice device : devices) {
                device.onStandby(mStandbyMessageReceived, standbyAction);
            }
            return;
        }

        disableDevices(new PendingActionClearedCallback() {
            @Override
            public void onCleared(HdmiCecLocalDevice device) {
                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
                devices.remove(device);
                if (devices.isEmpty()) {
                    onPendingActionsCleared(standbyAction);
                    // We will not clear local devices here, since some OEM/SOC will keep passing
                    // the received packets until the application processor enters to the sleep
                    // actually.
                }
            }
        });
    }

前面一直想当然的以为调用关机的是在

            for (HdmiCecLocalDevice device : devices) {
                device.onStandby(mStandbyMessageReceived, standbyAction);
            }

但是实际调试过程中发现,if (!isStandbyMessageReceived() && !canGoToStandby())是未满足的, 最终的调用入口为

        disableDevices(new PendingActionClearedCallback() {
            @Override
            public void onCleared(HdmiCecLocalDevice device) {
                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
                devices.remove(device);
                if (devices.isEmpty()) {
                    onPendingActionsCleared(standbyAction);
                    // We will not clear local devices here, since some OEM/SOC will keep passing
                    // the received packets until the application processor enters to the sleep
                    // actually.
                }
            }
        });

在HDMI1通道下PendingActionClearedCallback的回调,是未被触发的, 其他通道正常触发。接着看触发回调之后的逻辑

    /**
     * Normally called after all devices have cleared their pending actions, to execute the final
     * phase of the standby flow.
     *
     * This can also be called during wakeup, when pending actions are cleared after failing to be
     * cleared during standby. In this case, it does not execute the standby flow.
     */
    @ServiceThreadOnly
    private void onPendingActionsCleared(int standbyAction) {
        assertRunOnServiceThread();
        Slog.v(TAG, "onPendingActionsCleared");

        if (mPowerStatusController.isPowerStatusTransientToStandby()) {
            mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
            for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
            	//最终在这进行关机
                device.onStandby(mStandbyMessageReceived, standbyAction);
            }
            if (!isAudioSystemDevice()) {
                mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
                mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
            }
        }

        // Always reset this flag to set up for the next standby
        mStandbyMessageReceived = false;
    }

最终在这里device.onStandby(mStandbyMessageReceived, standbyAction);进行关机流程。因此我们目标便是是要正常触发PendingActionClearedCallback回调函数, 即可正常关机。我们先不纠结为什么条件未满足,我们先来看下,disableDevices这个函数需要如何触发回调函数

2.详细流程分析

首先我们来看disableDevices的实现, 他接受一个回调的参数PendingActionClearedCallback

    private void disableDevices(PendingActionClearedCallback callback)

PendingActionClearedCallback的实现如下,看注释是所有action清除的时候调用onCleared

    /**
     * A callback interface to get notified when all pending action is cleared. It can be called
     * when timeout happened.
     */
    interface PendingActionClearedCallback {
        void onCleared(HdmiCecLocalDevice device);
    }

接下来继续看disableDevices的实现,

    private void disableDevices(PendingActionClearedCallback callback) {
        if (mCecController != null) {
            for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
                device.disableDevice(mStandbyMessageReceived, callback);
            }
        }
        mMhlController.clearAllLocalDevices();
    }

遍历HdmiCecNetwork中的所有device,然后调用disableDevice

    /**
     * Disable device. {@code callback} is used to get notified when all pending actions are
     * completed or timeout is issued.
     *
     * @param initiatedByCec true if this sequence is initiated by the reception the CEC messages
     *     like &lt;Standby&gt;
     * @param originalCallback callback interface to get notified when all pending actions are
     *     cleared
     */
    protected void disableDevice(
            boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
        removeAction(AbsoluteVolumeAudioStatusAction.class);
        removeAction(SetAudioVolumeLevelDiscoveryAction.class);

        mPendingActionClearedCallback =
                new PendingActionClearedCallback() {
                    @Override
                    public void onCleared(HdmiCecLocalDevice device) {
                        mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
                        originalCallback.onCleared(device);
                    }
                };
        mHandler.sendMessageDelayed(
                Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT), DEVICE_CLEANUP_TIMEOUT);
    }

从这里可以看到,如果想要触发disableDevices(PendingActionClearedCallback callback)中的回调函数,首先要出发mPendingActionClearedCallback, 然后调用 originalCallback.onCleared(device)才会触发最开始的回调函数。那么mPendingActionClearedCallback需要如何触发呢?

disableDevice函数体中removeAction的实现如下:

    @ServiceThreadOnly
    void removeAction(final HdmiCecFeatureAction action) {
        assertRunOnServiceThread();
        action.finish(false);
        mActions.remove(action);
        checkIfPendingActionsCleared();
    }
    protected void checkIfPendingActionsCleared() {
        if (mActions.isEmpty() && mPendingActionClearedCallback != null) {
            PendingActionClearedCallback callback = mPendingActionClearedCallback;
            // To prevent from calling the callback again during handling the callback itself.
            mPendingActionClearedCallback = null;
            callback.onCleared(this);
        }
    }

可以发现,在checkIfPendingActionsCleared中,只要if (mActions.isEmpty() && mPendingActionClearedCallback != null)条件满足,就会触发mPendingActionClearedCallback回调,

        mPendingActionClearedCallback =
                new PendingActionClearedCallback() {
                    @Override
                    public void onCleared(HdmiCecLocalDevice device) {
                        mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
                        originalCallback.onCleared(device);
                    }
                };

然后在mPendingActionClearedCallback中, originalCallback.onCleared(device)中,触发HdmiControlService.javadisableDevices(new PendingActionClearedCallback() {处的回调,实现关机.

到这里,我们基本可以明确,因为mPendingActionClearedCallback在这个流程中一定不为null, 所有需要mActions.isEmpty为空是TV关机,DVD关机流程的关键.

然后在checkIfPendingActionsCleared中加了打印,果然在HDMI1通道下,mActions.isEmpty不为空,还存在一个Action
在这里插入图片描述
后面加打印确认在HDMI1通道下,关机的时候,还存在一个RoutingControlAction未被清除。

五、解决措施

既然确认是RoutingControlAction未被清除, 我们在中添加这一行removeAction(RoutingControlAction.class);代码,最终问题解决

    protected void disableDevice(
            boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
        removeAction(AbsoluteVolumeAudioStatusAction.class);
        removeAction(SetAudioVolumeLevelDiscoveryAction.class);
       	//在检测之前,移除RoutingControlAction,
        removeAction(RoutingControlAction.class);
        ···
    }

六、反思

1.RoutingControlAction是什么?

RoutingControl表示在CEC链路上,不是CEC设备直通TV设备, 中间有其他设备的时候,会启动RoutingControlAction来转发控制命令

2.为什么仅仅HDMI1通道异常,其他通道正常?

硬件设计的原因,使用的主芯片仅一路HDMI IN, 我司使用了HDMI switch芯片来拓展,在HDMI1链路中间多加了一个芯片

3.在disableDevice中移除RoutingControlAction,是否会影响CEC功能?

不会,disableDevice的调用时机仅为关机前。同步也找芯片厂商确认,谷歌在hdmi多次拓展的场景下兼容不太好,可以修改此处。

### RK3588 HDMI 输入配置和支持 对于RK3588芯片组中的HDMI输入功能,其配置流程涉及多个方面,包括但不限于音频设置、DDC(显示数据通道)、视频输入参数调整以及可能涉及到的高级特性如MPLL(主像素锁相环),CEC(消费者电子控制)和HDCP(高带宽数字内容保护)。具体来说: #### 音频配置 为了使能并优化通过HDMI端口传输的声音信号,在系统初始化阶段需指定所采用的具体编解码器标准。这通常是在设备树文件或是特定的应用层框架内完成定义[^1]。 #### DDC配置 DDC用于自动读取连接显示器的能力描述符,从而简化了分辨率及其他显示属性的选择过程。正确设定这一部分可以确保最佳视觉效果的同时也提高了用户体验的一致性和便捷度。 #### 视频输入配置 此环节主要关注如何处理来自外部源的数据流,比如确定支持的最大帧率、色彩空间转换机制等。这些选项同样依赖于底层硬件特性的暴露程度及其对应的软件接口实现方式。 #### MPLL配置 作为影响图像质量的关键因素之一,合理调节MPLL能够有效改善画面清晰度和平滑过渡表现。这项工作往往需要深入理解内部时钟生成原理并与实际应用场景相结合来做出最合适的决策。 #### CEC配置 (可选) 如果目标应用环境中有必要启用此项,则可以通过相应的APIs或命令行工具来进行简单的开关操作或者是更复杂的场景联动编程。 #### HDCP配置 针对版权保护需求较高的场合下,激活HDCP协议有助于防止未授权的内容复制行为发生。该步骤一般会紧随其他基础项之后执行,并且可能会牵涉到密钥交换等一系列安全措施。 ```bash # 示例:开启PWM遥控调试模式以便观察红外接收情况 echo 1 > /sys/module/rockchip_pwm_remotectl/parameters/code_print ``` 值得注意的是,上述各项配置并非孤立存在而是相互关联构成完整的解决方案链路;另外由于不同版本的操作系统可能存在差异因此建议参照官方文档获取最新指导说明[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是李校长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值