【qcom msm8953 android712】rtc 调试分析


文章目录

  • 1.RTC概述
  • 2.平台对应的rtc接口
  • 3.android settime实现流程
    • 3.1 android 层更新时间的起始
    • 3.2 setCurrentTimeMillis调用远程接口aidl
    • 3.3 AlarmManagerService 实现aidl 实现settime接口方法
    • 3.4 settime jni native层实现
  • 4. rtc readtime流程(qcom)
  • 5. rtc settime流程(qcom)
  • 4.总结
  • 5.参考

1.RTC概述 RTC(Real Time Clock),用于关机时继续计算系统日期和时间。是基于硬件的功能。也可以RTC做Alarm来设置power on/off。
2.平台对应的rtc接口 Linux 提供了三种用户空间调用接口。对于笔者所用的平台,在其中对应的路径为:
SYSFS接口:/sys/class/rtc/rtc0/ PROCFS接口: /proc/driver/rtc IOCTL接口: /dev/rtc0

3.android settime实现流程 3.1 android 层更新时间的起始 凡事皆有因果,本次分析从因开始,由上至下,由因至果。
android层对时间的设置在settings系统应用中实现。
packages/apps/Settings/src/com/android/settings/DateTimeSettings.java

Android提供了两种方式对Android系统时间进行设置更新
1.网络校时
2.手动校时
这里先不对这两种校时方式的实现作分析,本文目的旨在rtc,那就顺着系统默认的设置,通过网络校时来分析。
网络校时android层最终是通过NetworkTimeUpdateService.java这个类来实现,咱直奔中心。
幕后操作则是onPollNetworkTimeUnderWakeLock方法。(先不管这个方法是在哪里被谁调用,如何调用的,记得本文目的是rtc)
//frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java private void onPollNetworkTimeUnderWakeLock(int event) { ...... SystemClock.setCurrentTimeMillis(ntp); ...... }

这里为了更快的进入主题,笔者直接将该方法的其他有用没用的都省略了~~~。真是刺激呀。
可以看到android层设置更新时间的起始就是通过 SystemClock.setCurrentTimeMillis 开始的。
这样我们跟去看看它的实现。
//frameworks/base/core/java/android/os/SystemClock.java /** * Sets the current wall time, in milliseconds.Requires the calling * process to have appropriate permissions. * * @return if the clock was successfully set to the specified time. */ public static boolean setCurrentTimeMillis(long millis) { IBinder b = ServiceManager.getService(Context.ALARM_SERVICE); IAlarmManager mgr = IAlarmManager.Stub.asInterface(b); if (mgr == null) { return false; }try { return mgr.setTime(millis); } catch (RemoteException e) { Slog.e(TAG, "Unable to set RTC", e); } catch (SecurityException e) { Slog.e(TAG, "Unable to set RTC", e); }return false; }

这里注释里提到一个wall time,翻译为墙上时间。这里解释下墙上时间。
系统启动时,内核通过读取RTC来初始化内核时钟,又叫墙上时间,该时间放在struct timespec xtime变量中。
回到上面,setCurrentTimeMillis仅仅作了一件事。那就是mgr.setTime(millis);
这个mgr是什么,往上看。
1.首先取得ALARM_SERVICE的binder对象。
2.通过binder对象得到IAlarmManager的接口方法。
3.2 setCurrentTimeMillis调用远程接口aidl 【【qcom msm8953 android712】rtc 调试分析】那么,这就走到了IAlarmManager.setTime里去了。
不过这里IAlarmManager很明显是个接口类。
//frameworks/base/core/java/android/app/IAlarmManager.aidl/** * System private API for talking with the alarm manager service. * * {@hide} */ interface IAlarmManager { /** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */ void set(String callingPackage, int type, long triggerAtTime, long windowLength, long interval, int flags, in PendingIntent operation, in IAlarmListener listener, String listenerTag, in WorkSource workSource, in AlarmManager.AlarmClockInfo alarmClock); boolean setTime(long millis); void setTimeZone(String zone); void remove(in PendingIntent operation, in IAlarmListener listener); long getNextWakeFromIdleTime(); AlarmManager.AlarmClockInfo getNextAlarmClock(int userId); // update the uids being synchronized by network socket request manager void updateBlockedUids(int uid, boolean isBlocked); }

既然是接口,那么得有人去实现它。那就是–看注释,哈哈,告诉读者一个有意思的,在你没有相关知识,或者经验的时候,比如不知道谁调用实现了这个接口类,那么开源代码中的注释是非常有用的信息。这里就说到了它和 alarm manager service(后称AMS,注意区分ActivityManagerService) 进行talk 。
不过这里要注意了,这里的注释是talk。并不是实现。不过有点可以肯定。最终setTime在AMS中肯定也有,你也可以直接去看,不过这里笔者还是按照正常流程进行分析。
既然AMS和用来和它talk的,那么AMS很明显结合命名看它就是服务端,那么作为binder通讯,实现IAlarmManager肯定是客户端。还记得上面讲到setCurrentTimeMillis方法里的实现吗?没错第二步就是得到IAlarmManager的客户端。那现在就可以直接到AMS里去看setTime的实现了。在过去的时候,这里提一句。可能有些人在看到IAlarmManager会想到AlarmManager,这里可以抽点时间过去看看。AlarmManager.java中也会有setTime的实现。
//frameworks/base/core/java/android/app/AlarmManager.java/** * Set the system wall clock time. * Requires the permission android.permission.SET_TIME. * * @param millis time in milliseconds since the Epoch */ public void setTime(long millis) { try { mService.setTime(millis); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } }

可以看到AlarmManager.setTime 最终是调用了mService.setTime。注意了,这里的mService就是IAlarmManager对象。这样看。它也算是个binder的客户端了。这里其实是为了给上层提供接口通过AlarmManager.java调用到AMS中setTime方法。这里暂且说到这,夹杂了些许binder的相关知识。不理解的可自行百度。
3.3 AlarmManagerService 实现aidl 实现settime接口方法 继续回到AMS中的setTime。
//frameworks/base/services/core/java/com/android/server/AlarmManagerService.javaprivate final IBinder mService = new IAlarmManager.Stub() { @Override public void set(String callingPackage, int type, long triggerAtTime, long windowLength, long interval, int flags, PendingIntent operation, IAlarmListener directReceiver, String listenerTag, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) { ....... } }@Override public boolean setTime(long millis) { ...... return setKernelTime(mNativeData, millis) == 0; }@Override public void setTimeZone(String tz) { ....... }@Override public void remove(PendingIntent operation, IAlarmListener listener) { ...... }@Override public long getNextWakeFromIdleTime() { ....... }@Override public AlarmManager.AlarmClockInfo getNextAlarmClock(int userId) { ....... }@Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { ....... }@Override /* updates the blocked uids, so if a wake lock is acquired to only fire * alarm for it, it can be released. */ public void updateBlockedUids(int uid, boolean isBlocked) { ....... } };

这里可以看到在AMS内部实现了IAlarmManager 的 IBinder对象 mService。
那么它就实现了IAlarmManager接口所定义的方法。找到setTime。在setTime中最终调用了setKernelTime()。
3.4 settime jni native层实现 在AMS中可以看到,这个setKernelTime方法是个native方式,看来最终到了jni层。
//frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cppstatic jint android_server_AlarmManagerService_setKernelTime(JNIEnv*, jobject, jlong nativeData, jlong millis) { AlarmImpl *impl = reinterpret_cast(nativeData); struct timeval tv; int ret; if (millis <= 0 || millis / 1000LL >= INT_MAX) { return -1; }tv.tv_sec = (time_t) (millis / 1000LL); tv.tv_usec = (suseconds_t) ((millis % 1000LL) * 1000LL); ALOGD("Setting time of day to sec=%d\n", (int) tv.tv_sec); ret = impl->setTime(&tv); if(ret < 0) { ALOGW("Unable to set rtc to %ld: %s\n", tv.tv_sec, strerror(errno)); ret = -1; } return ret; }

在native setKernelTime 方法中又调用了impl->setTime,这可真是百转千绕啊。
这个impl从代码中可以看到是AlarmImpl对象。它的类声明实现就在本类中com_android_server_AlarmManagerService.cpp
//frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cppclass AlarmImpl { public: AlarmImpl(int *fds, size_t n_fds); virtual ~AlarmImpl(); virtual int set(int type, struct timespec *ts) = 0; virtual int clear(int type, struct timespec *ts) = 0; virtual int setTime(struct timeval *tv) = 0; virtual int waitForAlarm() = 0; protected: int *fds; size_t n_fds; }; class AlarmImplAlarmDriver : public AlarmImpl { public: AlarmImplAlarmDriver(int fd) : AlarmImpl(&fd, 1) { }int set(int type, struct timespec *ts); int clear(int type, struct timespec *ts); int setTime(struct timeval *tv); int waitForAlarm(); }; class AlarmImplTimerFd : public AlarmImpl { public: AlarmImplTimerFd(int fds[N_ANDROID_TIMERFDS], int epollfd, int rtc_id) : AlarmImpl(fds, N_ANDROID_TIMERFDS), epollfd(epollfd), rtc_id(rtc_id) { } ~AlarmImplTimerFd(); int set(int type, struct timespec *ts); int clear(int type, struct timespec *ts); int setTime(struct timeval *tv); int waitForAlarm(); private: int epollfd; int rtc_id; };

可以看到AlarmImpl的声明完全是个抽象类,得必须有子类去实现它,而在现在实现它的有两个子类。AlarmImplAlarmDriver和AlarmImplTimerFd ,都是公有继承。
而这两个子类都实现了setTime。那最终是走了哪里呢?
因为笔者C++不是太懂,这里直接跑系统加日志最终是走了AlarmImplTimerFd::setTime。
这里留个坑,为什么走了了这条路,还请知道的朋友记得在评论区告知。感谢~
//frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp int AlarmImplTimerFd::setTime(struct timeval *tv) { struct rtc_time rtc; struct tm tm, *gmtime_res; int fd; int res; res = settimeofday(tv, NULL); if (res < 0) { ALOGV("settimeofday() failed: %s\n", strerror(errno)); return -1; }if (rtc_id < 0) { ALOGV("Not setting RTC because wall clock RTC was not found"); errno = ENODEV; return -1; }android::String8 rtc_dev = String8::format("/dev/rtc%d", rtc_id); fd = open(rtc_dev.string(), O_RDWR); if (fd < 0) { ALOGV("Unable to open %s: %s\n", rtc_dev.string(), strerror(errno)); return res; }gmtime_res = gmtime_r(&tv->tv_sec, &tm); if (!gmtime_res) { ALOGV("gmtime_r() failed: %s\n", strerror(errno)); res = -1; goto done; }memset(&rtc, 0, sizeof(rtc)); rtc.tm_sec = tm.tm_sec; rtc.tm_min = tm.tm_min; rtc.tm_hour = tm.tm_hour; rtc.tm_mday = tm.tm_mday; rtc.tm_mon = tm.tm_mon; rtc.tm_year = tm.tm_year; rtc.tm_wday = tm.tm_wday; rtc.tm_yday = tm.tm_yday; rtc.tm_isdst = tm.tm_isdst; res = ioctl(fd, RTC_SET_TIME, &rtc); if (res < 0) ALOGV("RTC_SET_TIME ioctl failed: %s\n", strerror(errno)); done: close(fd); return res; }

这里可以看到AlarmImplTimerFd中的setTime里作了这么几件事。
1.调用settimeofday;
2.取得要操作的rtcX设备并打开它,笔者这里是/dev/rtc0;
3.通过ioctl对打开的rtc0设备进行操作。
ps:笔者这里是高通的平台,在调试过程中发现,当走到ioctl这步的时候,笔者发现这里总是失败。报错无效参数。最终发现是ker驱动中。没有打开对rtc的属性set。但是在笔者打开相应的设置后,发现一运行到这步,系统就死机挂掉了。询问上级厂商,给的回复是qcom平台的rtc就是这样的设定。只能Read不能Write。
那么这里问题就来了,既然qcom平台没有使用Android默认的rtc设置方式,那么肯定也有自己的rtc设置方式吧。先留个疑问,在分析rtc read后释疑。
到目前为止,除开ker层从上往下的set流程都分析完毕。jni中通过对设备文件的ioctl将操作传递到ker层。
ker层的rtc分析另开博客分析。
4. rtc readtime流程(qcom) 这里再分析下从rtc read时间的流程。从上述setTime来看。是通过ioctl来实现的。但在整个流程分析下来并没有发现和read相关的实现(笔者的平台是这样的)。那么系统是从何处执行read操作的呢?
要知道ker层提供的read接口就是通过ioctl接口。那么笔者在源代码根目录开始了地毯式搜索。最终在vendor目录下找到了一丝线索。
vendor/qcom/proprietary/time-services/* 【后面再开一篇简单分析下这个私有代码,因为是私有代码,没有开源,所以分析的时候可能上的代码不全,敬请谅解】
vendor目录通常是厂商定制的一些代码。
在该目录下的time_daemon_qmi.c找到了对/dev/rtc0的ioctl操作。需要注意的是,这里有两个地方对/dev/rtc0进行了open和ioctl。这里又留了一个坑。
//vendor/qcom/proprietary/time-services/time_daemon_qmi.cstatic int rtc_get(int64_t *msecs) { ...... fd = open("/dev/rtc0", O_RDONLY); ...... rc = ioctl(fd,RTC_RD_TIME,&rtc_tm); ...... }static int ats_rtc_init(time_genoff_ptr ptime_genoff) { ...... fd = open("/dev/rtc0", O_RDONLY); ...... rc = ioctl(fd,RTC_RD_TIME,&rtc_tm); ...... }

分析完qcom的rtc read,有没有想到一个问题,为啥源生代码中没有看到rtc read 相关,从app到jni 都没看到rtc read 相关的代码,这点有点疑问,知道的大佬还请在评论区留言告知。感谢!!!
5. rtc settime流程(qcom) 前面花了大篇幅跟踪了rtc的设置流程,跟到ams settime ioctl的时候,出现了问题。那么Android系统总的要有settime这个功能,3.2中分析了rtc read,同样的,在本平台中。set也是在该模块中实现。通过了广播触发的方式。下面看代码,因为是高通私有代码这里只做简要分析
//vendor/qcom/proprietary/time-services/src/com/qualcomm/timeservice/TimeServiceBroadcastReceiver.javapublic class TimeServiceBroadcastReceiver extends BroadcastReceiver {private static final String TAG = "TimeService"; public native void setTimeServicesUserTime(long millis); //本地方法 static { System.loadLibrary("TimeService"); } //加载本地库TimeService@Override public void onReceive(Context context, Intent intent) {if ((Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) || (Intent.ACTION_DATE_CHANGED.equals(intent.getAction()))) {//可以看到这里就是触发设置的广播Log.d(TAG, "Received" + intent.getAction() + " intent. " + "Current Time is " + System.currentTimeMillis()); setTimeServicesUserTime(System.currentTimeMillis()); } else { Log.w(TAG, "Received Unexpected Intent " + intent.toString()); } } }

TimeServiceBroadcastReceiver作了这么几件事。
1.声明本地native方法。
2.加载包含刚刚声明的方法的本地库。
3.实现BroadcastReceiver接口。监听Intent.ACTION_TIME_CHANGED和Intent.ACTION_DATE_CHANGED。可以推测当时间和日期改变的时候,将会触发将当前时间进行设置。
给setTimeServicesUserTime传值的时间是通过System.currentTimeMillis()得到的。这里应该还记得上面分析rtc 网络校时的时候,在NetworkTimeUpdateService中onPollNetworkTimeUnderWakeLock方法中调用了SystemClock.setCurrentTimeMillis(ntp); 进行校时后的时间设置。那么在校时后,时间日期改变,这里广播也会监听到,从而这里获取到的当前时间就是当前的正确时间,那么通过setTimeServicesUserTime将正确时间进行设置。qcom rtc settime就完成了。
setTimeServicesUserTime的具体实现不进行分析了。
4.总结 因果分析完毕,因学艺不精,留了一些坑,望知道的大佬评论指教。关于rtc,还有两部分需要单独开博客分析,这里记录下:
1.ker层rtc框架和qcom rtc 驱动分析。
2.time-services模块分析。(这个不会太详细)
另外,前面分析了那么多,字多眼花。附上一张图看的舒心点,我个人就更喜欢看图~~~
【qcom msm8953 android712】rtc 调试分析
文章图片

5.参考 Qcom平台RTC驱动分析
Linux内核中_IO,_IOR,_IOW,_IOWR宏的用法与解析
Android RTC 自下而上分析
Freescale i.MX6平台Android4.4.3之外部硬件RTC自动同步网络时间调试经验
GMT、UTC、DST、CST时区代表的意义

    推荐阅读