【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

那么,这就走到了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.java

    private 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.cpp

static 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.cpp

class 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.c

static 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.java

public 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 调试分析_第1张图片

5.参考

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

你可能感兴趣的:(android)