行业知识
安卓11-HDMI插拔检测流程
2026-11-08 20:47:26
来源:互联网转载
hdmi从插入到拔出经过底层一系列检测到应用层,应用层获取hdmi插入状态后又会做出一系列相应的动作,下面梳理了从应用层到底层一步步追踪到芯片的hpd-pin的检测过程。其大致原理就是framework层通过检测/sys/class/extcon/hdmi/state 来获取hdmi插入与否,具体更新这个状态的地方再kernel层,kernel层通过一个dw_hdmi_connector_detect轮询函数不断的查询hpd状态然后更新sys/class/extcon/hdmi/state,后面分析了hdmi插入拔出状态改变后系统所做的其它动作。
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java +775 private class HdmiVideoExtconUEventObserver extends ExtconStateObserver { private static final String HDMI_EXIST = "HDMI=1"; private static final String NAME = "hdmi"; private final ExtconInfo mHdmi = new ExtconInfo(NAME); //判断/sys/class/extcon/hdmi文件是否存在 private boolean init() { boolean plugged = false; try { plugged = parseStateFromFile(mHdmi); //解析sys/class/extcon/hdmi/state这个文件内容 } catch (FileNotFoundException e) { Slog.w(TAG, mHdmi.getStatePath() + " not found while attempting to determine initial state", e); } catch (IOException e) { Slog.e( TAG, "Error reading " + mHdmi.getStatePath() + " while attempting to determine initial state", e); } startObserving(mHdmi); //开始监控/sys/class/extcon/hdmi return plugged; } @Override public void updateState(ExtconInfo extconInfo, String eventName, Boolean state) {//通过这里更新hdmi plug状态,这个转态来自hal层 //这里的变化来自kernel层dw-hdmi.cmDefaultDisplayPolicy.setHdmiPlugged(state); } @Override public Boolean parseState(ExtconInfo extconIfno, String state) { // extcon event state changes from kernel4.9 // new state will be like STATE=HDMI=1 return state.contains(HDMI_EXIST); //判断这里是否sys/class/extcon/hdmi/state HDMI=1 是的话返回true } } void initializeHdmiStateInternal() { boolean plugged = false; // watch for HDMI plug messages if the hdmi switch exists if (new File("/sys/devices/virtual/switch/hdmi/state").exists()) { mHDMIObserver.startObserving("DEVPATH=/devices/virtual/switch/hdmi"); final String filename = "/sys/class/switch/hdmi/state"; FileReader reader = null; try { reader = new FileReader(filename); char[] buf = new char[15]; int n = reader.read(buf); if (n > 1) { plugged = 0 != Integer.parseInt(new String(buf, 0, n - 1)); } } catch (IOException ex) { Slog.w(TAG, "Couldn't read hdmi state from " + filename + ": " + ex); } catch (NumberFormatException ex) { Slog.w(TAG, "Couldn't read hdmi state from " + filename + ": " + ex); } finally { if (reader != null) { try { reader.close(); } catch (IOException ex) { } } } } else if (ExtconUEventObserver.extconExists() //走这里 判断sys/class/extcon是否存在 && ExtconUEventObserver.namedExtconDirExists(HdmiVideoExtconUEventObserver.NAME)) { Log.i("fan","xtconUEventObserver.extconExists"); HdmiVideoExtconUEventObserver observer = new HdmiVideoExtconUEventObserver();//新建一个hdmi观察者,检测hdmi hpd引脚的变化 plugged = observer.init(); mHDMIObserver = observer; } else if (localLOGV) { Slog.v(TAG, "Not observing HDMI plug state because HDMI was not found."); } // This dance forces the code in setHdmiPlugged to run. // Always do this so the sticky intent is stuck (to false) if there is no hdmi. mDefaultDisplayPolicy.setHdmiPlugged(plugged, true /* force */); } frameworks/base/services/core/java/com/android/server/ExtconStateObserver.java public void onUEvent(ExtconInfo extconInfo, UEvent event) { if (LOG) Slog.d(TAG, extconInfo.getName() + " UEVENT: " + event); String name = event.get("NAME"); S state = parseState(extconInfo, event.get("STATE")); Slog.d("fan","onUEvent get name="+name+"state="+state); if (state != null) { updateState(extconInfo, name, state); } }public abstract S parseState(ExtconInfo extconInfo, String state); //在PhoneWindowManager.java里实现判断sys/class/extcon/hdmi/state 是否与HDMI=1相等frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java String ACTION_HDMI_PLUGGED = "android.intent.action.HDMI_PLUGGED"; public void setHdmiPlugged(boolean plugged) { setHdmiPlugged(plugged, false /* force */); } public void setHdmiPlugged(boolean plugged, boolean force) { if (force || mHdmiPlugged != plugged) { mHdmiPlugged = plugged; mService.updateRotation(true /* alwaysSendConfiguration */, true /* forceRelayout */); final Intent intent = new Intent(ACTION_HDMI_PLUGGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.putExtra(EXTRA_HDMI_PLUGGED_STATE, plugged); mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);//通知系统hdmi插入状态 } } boolean isHdmiPlugged() { return mHdmiPlugged; } frameworks/base/services/core/java/com/android/server/ExtconUEventObserver.java public static boolean extconExists() { File extconDir = new File("/sys/class/extcon"); //检查这个文件是否存在,对应上面的else if (ExtconUEventObserver.extconExists() return extconDir.exists() && extconDir.isDirectory(); }kernel/drivers/gpu/drm/bridge/synopsys/dw-hdmi.cstatic const struct drm_connector_funcs dw_hdmi_connector_funcs = { .fill_modes = drm_helper_probe_single_connector_modes, .detect = dw_hdmi_connector_detect, //通过这里检测hdmi变化 .destroy = drm_connector_cleanup, .force = dw_hdmi_connector_force, .reset = drm_atomic_helper_connector_reset, .set_property = dw_hdmi_connector_set_property, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .atomic_set_property = dw_hdmi_atomic_connector_set_property, .atomic_get_property = dw_hdmi_atomic_connector_get_property,};static enum drm_connector_statusdw_hdmi_connector_detect(struct drm_connector *connector, bool force){ struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); enum drm_connector_status result; if (!hdmi->force_logo) { mutex_lock(&hdmi->mutex); hdmi->force = DRM_FORCE_UNSPECIFIED; dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); } result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); if (result == connector_status_connected) extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, true); else extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, false);.....kernel/drivers/extcon/extcon.cint extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, bool state){ int ret, index; unsigned long flags; index = find_cable_index_by_id(edev, id); if (index lock, flags); ret = is_extcon_changed(edev, index, state); spin_unlock_irqrestore(&edev->lock, flags); if (!ret) return 0; ret = extcon_set_state(edev, id, state); if (ret fd;if (fd[0] = 0)pfd[0].events = POLLIN | POLLRDNORM | POLLPRI;while (true) { usleep(1000);int err = poll(&pfd[0], 1, 20);if (!err) {continue;} else if(err > 0) {if (!ctx->enable || !ctx->system_control)continue;ALOGD("poll revent:%02x\n", pfd[0].revents);memset(&cec_event, 0, sizeof(hdmi_event_t));if (pfd[0].revents & (POLLIN)) {struct cec_msg cecframe;ALOGD("poll receive msg\n");ret = ioctl(pfd[0].fd, CEC_RECEIVE, &cecframe);if (!ret) {cec_event.type = HDMI_EVENT_CEC_MESSAGE;cec_event.dev = &ctx->device;cec_event.cec.initiator = (cec_logical_address_t)(cecframe.msg[0] >> 4);cec_event.cec.destination = (cec_logical_address_t)(cecframe.msg[0] & 0x0f);cec_event.cec.length = cecframe.len - 1;cec_event.cec.body[0] = cecframe.msg[1];if (!validcecmessage(cec_event)) {for (ret = 0; ret event_callback)ctx->event_callback(&cec_event, ctx->cec_arg);} else {ALOGE("%s cec_event length > 15 ", __func__);}} else {ALOGE("%s hdmi cec read error", __FUNCTION__);}}if (pfd[0].revents & (POLLPRI)) {int state = -1;struct cec_event event;ALOGI("poll receive event\n");ret = ioctl(pfd[0].fd, CEC_DQEVENT, &event);//取得一个cec事件,然后判断事件的状态,此部分内容在内核层if (!ret) {ALOGD("event:%d\n", event.event);if (event.event == CEC_EVENT_PIN_HPD_LOW) {//获取底层hpdin管教状态ALOGI("CEC_EVENT_PIN_HPD_LOW\n");ctx->hotplug = false;cec_event.type = HDMI_EVENT_HOT_PLUG;cec_event.dev = &ctx->device;cec_event.hotplug.connected = HDMI_NOT_CONNECTED;cec_event.hotplug.port_id = HDMI_CEC_PORT_ID;if (ctx->event_callback)ctx->event_callback(&cec_event, ctx->cec_arg);} else if (event.event == CEC_EVENT_PIN_HPD_HIGH) {//高为连接ALOGI("CEC_EVENT_PIN_HPD_HIGH\n");ctx->hotplug = true;cec_event.type = HDMI_EVENT_HOT_PLUG;cec_event.dev = &ctx->device;cec_event.hotplug.connected = HDMI_CONNECTED;cec_event.hotplug.port_id = HDMI_CEC_PORT_ID;if (ctx->event_callback)ctx->event_callback(&cec_event, ctx->cec_arg);} else if (event.event == CEC_EVENT_STATE_CHANGE) {ALOGD("adapt state change,phy_addr:%x,flags:%x\n", event.state_change.phys_addr, event.flags);/* * Before cec HAL is initialized, hdmi hpd state may be * changed. So we should confirm the hpd status * after cec is initialized(Kernel will report * CEC_EVENT_FL_INITIAL_STATE to notify HAL that * initialization is done). */if (event.flags & CEC_EVENT_FL_INITIAL_STATE) {ALOGD("cec adapter init complete, get connect state\n");ctx->hotplug = get_hpd_state_from_node(ctx);ctx->cec_init = true;/* * Framework will start la polling when box turn on, * In addition, as soon as framewrok receives hdmi * plug in, it will start la polling immediately. * There is not need to report plug in event if hdmi * is connecting when box turn on. So we should report * hdmi plug out only. */if (!ctx->hotplug)report_hdp_event(ctx, ctx->hotplug);}ctx->phy_addr = event.state_change.phys_addr;}} else {ALOGE("%s cec event get err, ret:%d\n", __func__, ret);}}} else {ALOGE("%s: cec poll failed errno: %s", __FUNCTION__,strerror(errno));continue;}}return NULL;}Kernel/drivers/media/cec/cec-api.c static long cec_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) case CEC_DQEVENT: return cec_dqevent(adap, fh, block, parg); //hal层调用这里获取一个cec事件,既然有获取事件就有把事件放入事件队列的地方 drivers/media/cec/cec-adap.c void cec_queue_pin_hpd_event(struct cec_adapter *adap, bool is_high, ktime_t ts){ struct cec_event ev = { .event = is_high ? CEC_EVENT_PIN_HPD_HIGH : CEC_EVENT_PIN_HPD_LOW, }; struct cec_fh *fh; if (!adap) return; /* hdmi HPD may occur before devnode is registered */ if (!adap->devnode.registered) return; mutex_lock(&adap->devnode.lock); list_for_each_entry(fh, &adap->devnode.fhs, list) cec_queue_event_fh(fh, &ev, ktime_to_ns(ts)); //插入一个cec事件,把这个事件放入到ece事件队列,供hal层获取,hal层获取后传到framework层 mutex_unlock(&adap->devnode.lock);}hardware/rockchip/hdmicec/hdmicec_event.cppstatic void report_hdp_event(hdmi_cec_context_t* ctx, bool hpd){ hdmi_event_t cec_event; cec_event.type = HDMI_EVENT_HOT_PLUG; cec_event.dev = &ctx->device; if (hpd) cec_event.hotplug.connected = HDMI_CONNECTED; else cec_event.hotplug.connected = HDMI_NOT_CONNECTED; cec_event.hotplug.port_id = HDMI_CEC_PORT_ID; if (ctx->event_callback) ctx->event_callback(&cec_event, ctx->cec_arg);}最终通过event_callback通知应用层,下面查找下callbackhardware/rockchip/hdmicec/hdmi_cec.cppstatic void hdmi_cec_register_event_callback(const struct hdmi_cec_device* dev, event_callback_t callback, void* arg){ struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev; ALOGI("%s", __func__); ctx->event_callback = callback; ctx->cec_arg = arg;}static int hdmi_cec_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device){ .... dev->device.register_event_callback = hdmi_cec_register_event_callback; //在HdmiCec.cpp完成注册 ......hardware/interfaces/tv/cec/1.0/default/HdmiCec.cpp Return HdmiCec::setCallback(const sp& callback) { //接口在IHdmiCecCallback.hal中 if (mCallback != nullptr) { mCallback->unlinkToDeath(this); mCallback = nullptr; } if (callback != nullptr) { mCallback = callback; mCallback->linkToDeath(this, 0 /*cookie*/); mDevice->register_event_callback(mDevice, eventCallback, nullptr); //注册回调通知framework层 eventCallback看hardware/interfaces/tv/cec/1.0/default/HdmiCec.h } return Void();}IHdmiCec* HIDL_FETCH_IHdmiCec(const char* hal) { hdmi_cec_device_t* hdmi_cec_device; int ret = 0; const hw_module_t* hw_module = nullptr; ret = hw_get_module (HDMI_CEC_HARDWARE_MODULE_ID, &hw_module); if (ret == 0) { ret = hdmi_cec_open (hw_module, &hdmi_cec_device); if (ret != 0) { LOG(ERROR) 0, .portId = static_cast(event->hotplug.port_id) }; mCallback->onHotplugEvent(hotplugEvent); //实现在HdmiCecController.java 看下面 } } frameworks/base/services/core/java/com/android/server/hdmi/HdmiCecController.java import android.hardware.tv.cec.V1_0.IHdmiCecCallback;private IHdmiCec mHdmiCec; final class HdmiCecCallback extends IHdmiCecCallback.Stub { //接口在hal层IHdmiCecCallback.hal @Override public void onCecMessage(CecMessage message) throws RemoteException { byte[] body = new byte[message.body.size()]; for (int i = 0; i handleIncomingCecCommand(message.initiator, message.destination, body)); } @Override public void onHotplugEvent(HotplugEvent event) throws RemoteException { runOnServiceThread(() -> handleHotplug(event.portId, event.connected)); } } private final HdmiControlService mService; @ServiceThreadOnly private void handleHotplug(int port, boolean connected) { assertRunOnServiceThread(); HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected); addHotplugEventToHistory(port, connected); mService.onHotplug(port, connected); } private void init(NativeWrapper nativeWrapper) { mIoHandler = new Handler(mService.getIoLooper()); mControlHandler = new Handler(mService.getServiceLooper()); nativeWrapper.setCallback(new HdmiCecCallback()); } public void setCallback(HdmiCecCallback callback) { //应用层调用 try { mHdmiCec.setCallback(callback); } catch (RemoteException e) { HdmiLogger.error("Couldn't initialise tv.cec callback : ", e); } }frameworks/base/services/core/java/com/android/server/hdmi/HdmiControlService.java void onHotplug(int portId, boolean connected) { assertRunOnServiceThread(); if (connected && !isTvDevice() && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { if (isSwitchDevice()) { initPortInfo(); HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx."); } ArrayList localDevices = new ArrayList(); for (int type : mLocalDevices) { if (type == HdmiDeviceInfo.DEVICE_PLAYBACK && isHdmiCecNeverClaimPlaybackLogicAddr) { continue; } HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); if (localDevice == null) { localDevice = HdmiCecLocalDevice.create(this, type); localDevice.init(); } localDevices.add(localDevice); } allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG); } for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { device.onHotplug(portId, connected); } announceHotplugEvent(portId, connected); } private void announceHotplugEvent(int portId, boolean connected) { HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); synchronized (mLock) { for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { invokeHotplugEventListenerLocked(record.mListener, event); } } } private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, HdmiHotplugEvent event) { try { listener.onReceived(event); } catch (RemoteException e) { Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); } }frameworks/base/core/java/android/hardware/hdmi/HdmiControlManager.java private IHdmiHotplugEventListener getHotplugEventListenerWrapper( final HotplugEventListener listener) { return new IHdmiHotplugEventListener.Stub() { @Override public void onReceived(HdmiHotplugEvent event) { listener.onReceived(event);; } }; } private final class ClientHotplugEventListener implements HotplugEventListener { @Override public void onReceived(HdmiHotplugEvent event) { List ports = new ArrayList(); try { ports = mService.getPortInfo(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } if (ports.isEmpty()) { Log.e(TAG, "Can't find port info, not updating connected status. " + "Hotplug event:" + event); return; } // If the HDMI OUT port is plugged or unplugged, update the mLocalPhysicalAddress for (HdmiPortInfo port : ports) { if (port.getId() == event.getPort()) { if (port.getType() == HdmiPortInfo.PORT_OUTPUT) { setLocalPhysicalAddress( event.isConnected() ? port.getAddress() : INVALID_PHYSICAL_ADDRESS); } break; } } } }