温馨提示:这篇文章已超过442天没有更新,请注意相关的内容是否还可用!
摘要:安卓11系统的HDMI插拔检测流程包括以下几个步骤:系统通过硬件接口识别HDMI设备的插入;启动相应的驱动程序以建立与HDMI设备的通信;系统通过驱动程序发送检测信号到HDMI设备,验证设备的连接状态;系统根据接收到的反馈信号判断HDMI设备是否成功连接。整个流程旨在确保安卓系统与外部显示设备之间的顺畅通信。
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.c mDefaultDisplayPolicy.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.c static 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_status dw_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.c int 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.cpp static 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通知应用层,下面查找下callback hardware/rockchip/hdmicec/hdmi_cec.cpp static 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; } } } }

(图片来源网络,侵删)
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。
还没有评论,来说两句吧...