探索RK3588 framework层到Hal层CEC关机流程实现

近一段时间遇到一个CEC功能失效的bug,即TV关机,DVD没有关机。因为CEC功能涉及 app–> framework --> hal --> kernel,任何一个环节出问题,都可能造成DVD关机失败,所以需要查看android源码来分析原因。第一次从framework 层到kernel一层一层看。感觉还是比较有意思的,在这里分享一下过程。

背景

CEC功能现在用的不多,大家听着玩一下就可以了。CEC概念 CEC是HDMI模块里面的一个功能。CEC标准功能里面有一条:TV设备关机的时候,会顺便把DVD关闭。在Android TV的实现原理是,HdmiControlService里面监听android的关机广播,然后发送CEC指令给到DVD设备,dvd设备关机。下面我们就直接开始从源码一层一层分析,看一下整个CEC关机的流程

framework层

首先我们来到HdmiControlService监听广播的地方

     private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
300          @ServiceThreadOnly
301          @Override
302          public void onReceive(Context context, Intent intent) {
...					...
322                  case Intent.ACTION_SHUTDOWN:
323                      if (isPowerOnOrTransient() && !isReboot) {
324                          onStandby(STANDBY_SHUTDOWN);
325                      }
326                      break;
327              }
328          }
329  
330      }

如上代码,监听到case Intent.ACTION_SHUTDOWN的广播,通过onStandby发送关机指令,然后我们看下onStandby的实现。
(不一定是在这里调用关机,详细分析可以查看这里

    protected void onStandby(final int standbyAction) {
...			 ...
3332  
3333          final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
3334  
3335          if (!isStandbyMessageReceived() && !canGoToStandby()) {
3336              mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
3337              for (HdmiCecLocalDevice device : devices) {
3338                  device.onStandby(mStandbyMessageReceived, standbyAction);
3339              }
3340              return;
3341          }
3342  		...
3356      }

接着看HdmiCecLocalDevice中的onStandby函数实现

    protected void onStandby(boolean initiatedByCec, int standbyAction) {}

啊哈,是个空实现,不要激动,我们拉到最上面看一下,HdmiCecLocalDevice的实现就明白了

abstract class HdmiCecLocalDevice {
...
}
final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
...
}

他是一个抽象类,因为我们是TV设备,所以我们需要在HdmiCecLocalDeviceTv.java中找对应的实现

64      protected void onStandby(boolean initiatedByCec, int standbyAction) {
...				...
1374          if (!initiatedByCec && sendStandbyOnSleep) {
1375              mService.sendCecCommand(
1376                      HdmiCecMessageBuilder.buildStandby(
1377                              getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST));
1378          }
1379      }

这里我们就可以看到,是要发送cec指令了,看样子是回到了HdmiControlService类了,接着往下面走

1238      void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
1239          assertRunOnServiceThread();
1240          if (command.getValidationResult() == HdmiCecMessageValidator.OK
1241                  && verifyPhysicalAddresses(command)) {
1242              mCecController.sendCommand(command, callback);
1243          } else {
1244              HdmiLogger.error("Invalid message type:" + command);
1245              if (callback != null) {
1246                  callback.onSendCompleted(SendMessageResult.FAIL);
1247              }
1248          }
1249      }

我们接着去HdmiCecController 中查看sendCommand的实现

651      void sendCommand(final HdmiCecMessage cecMessage,
652              final HdmiControlService.SendMessageCallback callback) {
....		....
655          runOnIoThread(new Runnable() {
656              @Override
657              public void run() {
658                  HdmiLogger.debug("[S]:" + cecMessage);
659                  byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
660                  int retransmissionCount = 0;
661                  int errorCode = SendMessageResult.SUCCESS;
662                  do {
663                      errorCode = mNativeWrapperImpl.nativeSendCecCommand(
664                          cecMessage.getSource(), cecMessage.getDestination(), body);
665                      if (errorCode == SendMessageResult.SUCCESS) {
666                          break;
667                      }
668                  } while (retransmissionCount++ < HdmiConfig.RETRANSMISSION_COUNT);
669  
...					....
690      }

代码量有点大,但是我们主要关注nativeSendCecCommand函数即可,从函数的名字上面来看,应该是要到下一层了

errorCode = mNativeWrapperImpl.nativeSendCecCommand(cecMessage.getSource(), cecMessage.getDestination(), body);

接着来看nativeSendCecCommand的实现

		private android.hardware.tv.cec.V1_1.IHdmiCec mHdmiCec;

          public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
1056              CecMessage message = new CecMessage();
1057              message.initiator = srcAddress;
1058              message.destination = dstAddress;
1059              message.body = new ArrayList<>(body.length);
1060              for (byte b : body) {
1061                  message.body.add(b);
1062              }
1063              try {
1064                  return mHdmiCec.sendMessage(message);
1065              } catch (RemoteException e) {
1066                  HdmiLogger.error("Failed to send CEC message : ", e);
1067                  return SendMessageResult.FAIL;
1068              }
1069          }

链路好长啊,从这里看,我们应该是要达到hal层了

硬件抽象层【Hal层】

欢迎来到hal层,这里也是我收获最多的地方。我们接着看代码

309  Return<SendMessageResult> HdmiCec::sendMessage(const CecMessage& message) {
310      cec_message_t legacyMessage {
311          .initiator = static_cast<cec_logical_address_t>(message.initiator),
312          .destination = static_cast<cec_logical_address_t>(message.destination),
313          .length = message.body.size(),
314      };
315      for (size_t i = 0; i < message.body.size(); ++i) {
316          legacyMessage.body[i] = static_cast<unsigned char>(message.body[i]);
317      }
318      return static_cast<SendMessageResult>(mDevice->send_message(mDevice, &legacyMessage));
319  }

初看到我是一脸懵逼,还是请教了公司的同事,才大概知道hal层的一些规范。当然了,他也是发我了一些文档,不过文档内容质量很高,分享给大家 Android硬件抽象层(HAL)概要介绍和学习计划我们不着急接着追接口,先来学习一下基础知识。

1.为什么需要hal层

Android硬件抽象层(HAL)概要介绍和学习计划写的很清楚。大概总结一下就是,Android 是基于Linux的,但是Linux是需要开源的。各个厂家的一下硬件模块参数或算法是有一定商业价值,不想公开,因此就有了硬件抽象层的概念。厂家可以将重要的实现放到Hal层来实现,Kernel只需要提供对外的接口能力即可。当然这个东西也相当于一个投机取巧的办法,也是因为这个原因,Android被踢出了Linux内核主线代码树中。

2.Hal层代码流程

(参考:在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序
①首先我们需要三个东西:定义模块ID、硬件模块结构体、硬件接口结构体。我们通过CEC的hal层实现来验证一下我们的结论。
代码量较大,我仅保留重要内容

    #ifndef ANDROID_INCLUDE_HARDWARE_HDMI_CEC_H
18  #define ANDROID_INCLUDE_HARDWARE_HDMI_CEC_H
... ...
32  
33  #define HDMI_CEC_HARDWARE_MODULE_ID "hdmi_cec" //模块ID
34  #define HDMI_CEC_HARDWARE_INTERFACE "hdmi_cec_hw_if"

	//硬件模块结构体
 	 typedef struct hdmi_cec_module {
288      /**
289       * Common methods of the HDMI CEC module.  This *must* be the first member of
290       * hdmi_cec_module as users of this structure will cast a hw_module_t to hdmi_cec_module
291       * pointer in contexts where it's known the hw_module_t references a hdmi_cec_module.
292       */
293      struct hw_module_t common;
294  } hdmi_module_t; 
...
... 	//硬件接口结构体
       /*
297   * HDMI-CEC HAL interface definition.
298   */
299  typedef struct hdmi_cec_device {
300      /**
301       * Common methods of the HDMI CEC device.  This *must* be the first member of
302       * hdmi_cec_device as users of this structure will cast a hw_device_t to hdmi_cec_device
303       * pointer in contexts where it's known the hw_device_t references a hdmi_cec_device.
304       */
305      struct hw_device_t common;
...		....
341      int (*get_physical_address)(const struct hdmi_cec_device* dev, uint16_t* addr);
342  
343      /*
344       * (*send_message)() transmits HDMI-CEC message to other HDMI device.
345       *
346       * The method should be designed to return in a certain amount of time not
347       * hanging forever, which can happen if CEC signal line is pulled low for
348       * some reason. HAL implementation should take the situation into account
349       * so as not to wait forever for the message to get sent out.
350       *
351       * It should try retransmission at least once as specified in the standard.
352       *
353       * Returns error code. See HDMI_RESULT_SUCCESS, HDMI_RESULT_NACK, and
354       * HDMI_RESULT_BUSY.
355       */
356      int (*send_message)(const struct hdmi_cec_device* dev, const cec_message_t*);
...		....
413  } hdmi_cec_device_t;

②实现对应接口
既然我们现在已经在头文件中定义了对应的接口了,那下一步就需要实现对应接口。实现接口的时候,有什么流程呢?
我们根据hdmi_module_t前面的硬件模块结构体来看下

36  hdmi_module_t HAL_MODULE_INFO_SYM = {
37  	common: {
38  		tag: HARDWARE_MODULE_TAG,
39  		version_major: 1,
40  		version_minor: 0,
41  		id: HDMI_CEC_HARDWARE_MODULE_ID,
42  		name: "Rockchip hdmi cec module",
43  		author: "Rockchip",
44  		methods: &hdmi_cec_module_methods,
45  	}
46  };

这里,实例变量名必须为HAL_MODULE_INFO_SYM,tag也必须为HARDWARE_MODULE_TAG,这是Android硬件抽象层规范规定的。

找到了对应实现hdmi_module_t 文件,我们就知道了,接口实现在hdmi_cec_device_open中

32  static struct hw_module_methods_t hdmi_cec_module_methods = {
33  	open: hdmi_cec_device_open
34  };

我们接着看hdmi_cec_device_open函数

static int hdmi_cec_device_open(const struct hw_module_t* module, const char* name,
451  				struct hw_device_t** device)
452  {
453  	if (strcmp(name, HDMI_CEC_HARDWARE_INTERFACE))
454  		return -EINVAL;
455  
456  	struct hdmi_cec_context_t *dev;
457  	dev = (hdmi_cec_context_t*)malloc(sizeof(*dev));
458  
459  	/* initialize our state here */
460  	memset(dev, 0, sizeof(*dev));
461  
462  	dev->enable = true;
463  	dev->system_control = false;
464  	dev->cec_init = false;
465  	/* initialize the procs */
466  	dev->device.common.tag = HARDWARE_DEVICE_TAG;
467  	dev->device.common.version = HDMI_CEC_DEVICE_API_VERSION_1_0;
468  	dev->device.common.module = const_cast<hw_module_t*>(module);
469  	dev->device.common.close = hdmi_cec_device_close;
470  
471  	dev->device.add_logical_address = hdmi_cec_add_logical_address;
472  	dev->device.clear_logical_address = hdmi_cec_clear_logical_address;
473  	dev->device.get_physical_address = hdmi_cec_get_physical_address;
474  	dev->device.send_message = hdmi_cec_send_message;
475  	dev->device.register_event_callback = hdmi_cec_register_event_callback;
476  	dev->device.get_version = hdmi_cec_get_version;
477  	dev->device.get_vendor_id = hdmi_cec_get_vendor_id;
478  	dev->device.get_port_info = hdmi_cec_get_port_info;
479  	dev->device.set_option = hdmi_cec_set_option;
480  	dev->device.set_audio_return_channel = hdmi_cec_set_audio_return_channel;
481  	dev->device.is_connected = hdmi_cec_is_connected;
482  	dev->phy_addr = 0;
483  	dev->fd = open(HDMI_DEV_PATH,O_RDWR,0);
484  	ALOGE(HDMI_DEV_PATH);
485  	ALOGE("\n");
486  	if (dev->fd < 0) {
487  		ALOGE("%s open error!", __func__);
488  		ALOGE("cec %s\n", strerror(errno));
489  	}
490  	ALOGI("%s dev->fd = %d", __func__, dev->fd);
491  	property_set("vendor.sys.hdmicec.version",HDMI_CEC_HAL_VERSION);
492  	*device = &dev->device.common;
493  	init_uevent_thread(dev);
494  
495  	ALOGI("rockchip hdmi cec modules loaded");
496  	return 0;
497  }

看到这里dev->device.send_message = hdmi_cec_send_message;,是不是觉得特别眼熟,没错这个就是我们前面的

在这里插入图片描述
然后后面就是RK3588的具体实现了hdmi_cec_send_message,这个是各个厂家自己实现的内容,我将整个函数贴出来吐槽一下。实在是太丑了。整个函数长达100多行,且if 里面套if ,还接着套for循环,for循环里面又是各种if else。且变量定义的也没有一点标准,追到这里,实在头疼。大家可以不用看这段代码实现内容,仅仅是吐槽一下。

(实际我们出现没有关机的情况,是因为在整个函数里面的某个场景下,直接return了。不太适合我司的业务场景,向RK研发确认,可以删除。删除后确认可以解决问题)

static int hdmi_cec_send_message(const struct hdmi_cec_device* dev, const cec_message_t* message)
256  {
257  	struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
258  	struct cec_msg cecframe;
259  	int i, ret = 0;
260  
261  	if (!ctx->enable) {
262  		ALOGE("%s cec disabled\n", __func__);
263  		return -EPERM;
264  	}
265  
266  	if (ctx->fd < 0) {
267  		ALOGE("%s open error", __func__);
268  		return -ENOENT;
269  	}
270  
271  	memset(&cecframe, 0, sizeof(struct cec_msg));
272  	if (message->initiator == message->destination) {
273  		struct cec_log_addrs log_addr;
274  
275  		ret = ioctl(ctx->fd, CEC_ADAP_G_LOG_ADDRS, &log_addr);
276  		if (ret) {
277  			ALOGE("%s get logic address err ret:%d\n", __func__, ret);
278  			return -EINVAL;
279  		}
280  
281  		ALOGD("kernel logic addr:%02x, preferred logic addr:%02x",
282  		      log_addr.log_addr[0], message->initiator);
283  		if (log_addr.log_addr[0] != CEC_LOG_ADDR_INVALID && log_addr.log_addr[0]) {
284  			ALOGI("kernel logaddr is existing\n");
285  			if (log_addr.log_addr[0] == message->initiator) {
286  				ALOGI("kernel logaddr is preferred logaddr\n");
287  				return HDMI_RESULT_NACK;
288  			} else {
289  				ALOGI("preferred log addr is not kernel log addr\n");
290  				return HDMI_RESULT_SUCCESS;
291  			}
292  		} else {
293  			ALOGI("kernel logaddr is not existing\n");
294  			if(!set_kernel_logical_address(ctx, message->initiator)) {
295  				for (i = 0; i < 5; i++) {
296  					if (!ctx->phy_addr || ctx->phy_addr == 0xffff) {
297  						ALOGE("phy addr not ready\n");
298  						usleep(200000);
299  					} else {
300  						break;
301  					}
302  				}
303  			}
304  			if (i == 5) {
305  				ALOGE("can't make kernel addr done\n");
306  				return HDMI_RESULT_FAIL;
307  			} else {
308  
309  				return HDMI_RESULT_NACK;
310  			}
311  		}
312  	}
313  
314  	cecframe.msg[0] = (message->initiator << 4) | message->destination;
315  	cecframe.len = message->length + 1;
316  	cecframe.msg[1] = message->body[0];
317  	ALOGI("send msg LEN:%d,opcode:%02x,addr:%02x\n",
318  	      cecframe.len ,cecframe.msg[1],cecframe.msg[0]);
319  	if (cecframe.len > 16)
320  		cecframe.len = 0;
321  	for (ret = 0; ret < cecframe.len; ret++)
322  		cecframe.msg[ret + 2] = message->body[ret + 1];
323  	if (cecframe.msg[1] == 0x90)
324  		cecframe.msg[2] = 0;
325  
326  	ret = ioctl(ctx->fd, CEC_TRANSMIT, &cecframe);
327  
328   	if (ret < 0) {
329   		ALOGE("ioctl err:%d\n", ret);
330  		return HDMI_RESULT_FAIL;
331  	}
332  	if (cecframe.tx_status & CEC_TX_STATUS_NACK) {
333  		ALOGE("HDMI_RESULT_NACK\n");
334  		return HDMI_RESULT_NACK;
335  	}
336  	else if (cecframe.tx_status & CEC_TX_STATUS_OK) {
337  		ALOGE("HDMI_RESULT_SUCCESS\n");
338  		return HDMI_RESULT_SUCCESS;
339  	}
340  	else {
341  		ALOGE("HDMI_RESULT_BUSY\n");
342  		return HDMI_RESULT_BUSY;
343  	}
344  	return HDMI_RESULT_FAIL;
345  }

看到整个接口中的ioctl函数我们自然的就想到了,要到达kernel层了。Kernal层的实现各个厂家实现不一致,且没有参考意义,就不很细的看了。基本逻辑都是遵循CEC标准发送指令。

Kernel 层

我们回到hdmi_cec_device_open函数,可以看到
在这里插入图片描述

#define HDMI_DEV_PATH   "dev/cec0"

然后我们全局搜"dev/cec0",即可找到对应的kernel代码。

总结

最终总结时序图如下:
在这里插入图片描述

Android系统中实现一个完整的GPS定位流程,需要理解并操作Framework、JNIHAL这三个关键面。首先,应用程序开发者通常通过Framework提供的API来请求位置服务,比如使用`LocationManager`类的`requestLocationUpdates`方法来请求位置更新。Framework会接收这些请求,并通过服务`LocationManagerService`来管理这些位置更新的请求。 参考资源链接:[深入解析Android GPS架构](https://wenku.csdn.net/doc/649301fa9aecc961cb2b9bed?spm=1055.2569.3001.10343) 接下来,Framework会通过JNI与底的C/C++代码交互。具体来说,`LocationManagerService`会调用JNI提供的接口,例如`android_location_GpsLocationProvider.cpp`中的方法,将请求转发给HAL。JNI在这里的作用是将Java的API调用翻译成对HAL的调用,并处理从HAL返回的数据。 在HAL,GPS的具体硬件交互得以实现HAL定义了一系列的接口函数,这些函数会在`gps.h`中声明,并在相应的硬件实现文件中定义。例如,`gps_init()`用于初始化GPS模块,`gps_start()`和`gps_stop()`用于控制GPS模块的开启和关闭,而`gps_location_callback()`则用于向Framework报告位置更新。 总的来说,从FrameworkHAL流程可以概括为:应用程序通过Framework的API发送位置服务请求,Framework将请求转发到LocationManagerService进行处理,然后通过JNI调用HAL提供的接口与GPS硬件交互,最后HAL处理硬件数据并返回位置信息给Framework,再由Framework传递给请求的应用程序。 对于希望深入了解Android GPS架构的开发者来说,推荐阅读《深入解析Android GPS架构》。这本书详细地解析了Android GPS架构的各个方面,从Framework的API使用,到JNI的Java与C/C++交互,再到HAL的硬件接口实现,帮助开发者全面理解GPS服务的实现机制。 参考资源链接:[深入解析Android GPS架构](https://wenku.csdn.net/doc/649301fa9aecc961cb2b9bed?spm=1055.2569.3001.10343)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

我是李校长

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

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

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

打赏作者

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

抵扣说明:

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

余额充值