欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【qcom msm8953 android712】rtc 调试分析

程序员文章站 2022-04-01 16:21:02
...

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<AlarmImpl *>(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 调试分析

5.参考

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

相关标签: rtc android