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<Boolean> {
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 < 0)
return index;
/* Check whether the external connector's state is changed. */
spin_lock_irqsave(&edev->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 < 0)
return ret;
return extcon_sync(edev, id); //hdmi接入拔出状态最终在这里改变 即sys/class/extcon/hdmi/state HDMI=1或者HDMI=0
}
//上面系统检测sys/class/extcon/hdmi/state的分析已经结束了,下面是hdmi状态变化通知系统做出改变设备物理地址的分析
\hardware\rockchip\hdmicec\hdmicec_event.cpp
static void *uevent_loop(void *param)
{
hdmi_cec_context_t * ctx = reinterpret_cast<hdmi_cec_context_t *>(param);
char thread_name[64] = HDMI_CEC_UEVENT_THREAD_NAME;
hdmi_event_t cec_event;
struct pollfd pfd[2];
int fd[2];
int ret, i;
prctl(PR_SET_NAME, (unsigned long) &thread_name, 0, 0, 0);
setpriority(PRIO_PROCESS, 0, HAL_PRIORITY_URGENT_DISPLAY);
fd[0] = ctx->fd;
if (fd[0] < 0) {
ALOGE ("%s:not able to open cec state node", __func__);
return NULL;
}
pfd[0].fd = fd[0];
if (pfd[0].fd >= 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 < cec_event.cec.length; ret++)
cec_event.cec.body [ret + 1] = cecframe.msg[ret + 2];
for (i = 0; i < cecframe.len; i++)
ALOGD("poll receive msg[%d]:%02x\n", i, cecframe.msg[i]);
if (ctx->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<void> HdmiCec::setCallback(const sp<IHdmiCecCallback>& 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) << "hdmi_cec_open " << hal << " failed: " << ret;
}
} else {
LOG(ERROR) << "hw_get_module " << hal << " failed: " << ret;
}
if (ret == 0) {
return new HdmiCec(hdmi_cec_device);
} else {
LOG(ERROR) << "Passthrough failed to load legacy HAL.";
return nullptr;
}
}
hardware/interfaces/tv/cec/1.0/IHdmiCecCallback.hal
interface IHdmiCecCallback {
/**
* The callback function that must be called by HAL implementation to notify
* the system of new CEC message arrival.
*/
oneway onCecMessage(CecMessage message);
/**
* The callback function that must be called by HAL implementation to notify
* the system of new hotplug event.
*/
oneway onHotplugEvent(HotplugEvent event);
};
hardware/interfaces/tv/cec/1.0/default/HdmiCec.h
static void eventCallback(const hdmi_event_t* event, void* /* arg */) {
if (mCallback != nullptr && event != nullptr) {
if (event->type == HDMI_EVENT_CEC_MESSAGE) {
size_t length = std::min(event->cec.length,
static_cast<size_t>(MaxLength::MESSAGE_BODY));
CecMessage cecMessage {
.initiator = static_cast<CecLogicalAddress>(event->cec.initiator),
.destination = static_cast<CecLogicalAddress>(event->cec.destination),
};
cecMessage.body.resize(length);
for (size_t i = 0; i < length; ++i) {
cecMessage.body[i] = static_cast<uint8_t>(event->cec.body[i]);
}
mCallback->onCecMessage(cecMessage); //实现在HdmiCecController.java 看下面
} else if (event->type == HDMI_EVENT_HOT_PLUG) {
HotplugEvent hotplugEvent {
.connected = event->hotplug.connected > 0,
.portId = static_cast<uint32_t>(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 < message.body.size(); i++) {
body[i] = message.body.get(i);
}
runOnServiceThread(
() -> 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<HdmiCecLocalDevice> 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<HdmiPortInfo> 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;
}
}
}
}