再探RK3588 CEC关机流程

目录

一、前言

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

二、问题现象

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

三、代码流程分析

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未被清除。

三、问题解决

1.临时措施

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

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

2.根本原因分析

虽然前面的临时措施可以解决问题,但是这个一定不是最终解决措施,有如下疑问:
(1)RoutingControlAction是做什么的?
(2)RoutingControlAction什么情况下会被添加?什么情况下会被移除?
(3)为什么在HDMI2\HDMI3通道下正常,唯独HDMI1会未被清楚?
(待更新…)

  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

我是李校长

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

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

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

打赏作者

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

抵扣说明:

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

余额充值