RTC的驱动移植和JNI修改

唠叨几句,本文目的是:

1>移植hym8563rtc驱动,硬件板载是S905X,操作系统是Android7.1

2>drivers分析道DateTimeSetting.java的执行过程。

首先把hym8563的驱动加到rtc的位置,我放的位置则是./common/drivers/amlogic/rtc下,修改rtc下的Makefile文件和Kconfig文件,如下:

config HYM8563_RTC

    bool "hym8563 RTC support"

    depends on I2C && OF

    help

      This is the Hym8563  Real Time Clock driver.

obj-$(CONFIG_HYM8563_RTC)+= rtc_hym8563.o

然后在./common/arch/arm64/configs/meson_defconfig下的文件中加入CONFIG_HYM8563_RTC=y,

以上是驱动的加入过程,底下进入驱动的适配和调试环节,首先在dts里加入设备驱动文件,当然作为驱动工作者,从硬件工程师哪里获得相应的硬件信息是必须的,I2C的从机地址,挂载的I2C的核心,细节不在啰嗦,以结果为例。

i2c_ao: i2c@c8100500{ /*I2C-AO*/

                compatible = "amlogic, meson-i2c";

                dev_name = "i2c-AO";

                status = "okay";

                reg = ;

                device_id = ;

                pinctrl-names="default";

                pinctrl-0=;

                #address-cells = ;

                #size-cells = ;

                use_pio = ;

                master_i2c_speed = ;

                clocks = ;

                clock-names = "clk_i2c";

                resets = ;

                hym8563: hym8563@51 {

                                compatible= "haoyu,hym8563";

                                reg = ;

                                interrupts = ;

                                i2c_bus = "i2c_bus_ao";

                                #clock-cells = ;

                                status = "okay";

                                };

}

这里分享个I2C器件地址获得小诀窍,根据ICdatasheet,获取设备的读或者写的从机地址,除以2,就是我们现在的0x51,至于为什么要除以2呢,则是跟设备器件地址的<是有关系的。

加入后编译,发现interruptrequest是报错的,但是由于我们的中断时悬空的,因此直接将驱动里的中断代码屏蔽,以后再做研究。然烧录后发现却没办法调用到外挂RTC,这里的JNI确实只是调用了aml_vrtc.c的驱动,一会给出问题出在哪里。

首先amlogic的代码真的必须被吐槽,编译适配感觉还是有点弱的感觉,举个例子,修改的地方编译老是有种写入文件虽然编译OK,但是编译结果却没有被编译到最后的aml_upgrade的文件里,最惹人烦的就是修改了uboot,但是一直没有打印添加的追踪打印代码,后来检查out下的打包时间,发现时间不一样,删掉uboot才打包OK,在这在编译使用hwclock时也出现,必须吐槽下。

s905xRTC的驱动接口有两个,一个是rtc_dev.c,一个是aml_dev.c这里也许有amlogic的自己做个炉子的目的,但是rtc_dev.c做的确实比aml_dev.c好,这里就是为什么只能调用aml_vrtc,不能调用外部驱动了,在alarmtimer.c里定义了一个rtcdev的实体,

static struct rtc_device*rtcdev

然后按照加载顺序,先是加载hym8564驱动,然后加载aml_vrtc所以,rtcdev的驱动实例是aml_vrtc,故JNI里执行时,获得实例对象是aml_vrtc,这里可以修改下,就可以执行到外部驱动了,我的修改办法则是创照一个结构体,用来放数据。

 static struct rtc_device{

int Val =0;

static struct rtc_device*rtcdev[3];

}rtc_sec;

alarmtimer_rtc_add_device里加入:

rtc_sec.Val++;

rtc_sec.rtcdev[rtc_sec.Val] = rtc;

alarmtimer_get_rtcdev里修改为

ret = rtc_sec.rtcdev[0];

这样也有个问题就是如果rtc驱动超过三个或者第一个rtc驱动不能实现功能怎么办,这个确实想到了,但是编程量太大,项目时间紧迫,就没有深入继续写。但是如果按照amlogic的加载顺序,只要是外部rtc首选加载,加载驱动不超过三个,就不会有问题。当然这只是一个解决办法,随着分析的继续,在JNI里也会给出另外一种解决办法。

简单分析下aml-dev.crtc-dev.c

首先分析aml-dev.c这里的代码时amlogic官方的配置墙时间和闹钟的入口,当然也是一file_ops的作为入口分析,对驱动有一定的研究的都知道驱动API,这里不做细节分析,只是简单讲下这个文件怎么执行到hym8563的。

static const struct file_operations alarm_fops = {

.owner = THIS_MODULE,

.unlocked_ioctl = alarm_ioctl,

.open = alarm_open,

.release = alarm_release,

#ifdef CONFIG_COMPAT

.compat_ioctl = alarm_compat_ioctl,

#endif

};

这个是驱动API,分析ioctl函数:这里只分析set时间和get时间,闹钟过程,其他的copy_to_user等代码不在分析。

逻辑进入到alarm_do_ioctl这个函数。点入到这个函数发现:如下

switch (ANDROID_ALARM_BASE_CMD(cmd)) {

case ANDROID_ALARM_CLEAR(0):

alarm_clear(alarm_type);

break;

case ANDROID_ALARM_SET(0):

alarm_set(alarm_type, ts);

break;

case ANDROID_ALARM_SET_AND_WAIT(0):

alarm_set(alarm_type, ts);

/* fall though */

case ANDROID_ALARM_WAIT:

rv = alarm_wait();

break;

case ANDROID_ALARM_SET_RTC:

rv = alarm_set_rtc(ts);

break;

case ANDROID_ALARM_GET_TIME(0):

rv = alarm_get_time(alarm_type, ts);

break;

 

default:

rv = -EINVAL;

}

这里就是我们的rtc时间设置,获得,闹钟的设置。这里只分析alarm_set_rtc。其他都一样。

进入到alarm_set_rtc,这里有两个函数指的关注:

rtc_dev = alarmtimer_get_rtcdev();这里就是执行不到外部驱动RTC的罪恶之地。此时在alarmtimer.c

{

ret =rtcdev;

}

rtcdev则是在驱动挂载时adddevice的。因为aml_vrtc是最后一个加载,而这里的只是定义了一个rtcdev,因此后来者把前者覆盖,故执行不到hym8563里。希望amlogic的官方可以看到。

rv = rtc_set_time(rtc_dev, &new_rtc_tm);这里进入到interface.c的代码里。

rtc_set_time

-> err = rtc->ops->set_time(rtc->dev.parent, tm);这里就执行到hym8563的驱动了。

rtc_dev.c的案例与这里类似,都是走到interface.c中,只是如果你选择open /dev/alarm则是这条路线,如果你选择open /dev/rtc0,则是走的是rtc-dev.c的这个执行逻辑。

 

下面分析JNI的执行逻辑:

frameworks\base\services\core\jni\com_android_server_AlarmManagerService.cpp这里是rtcnative层。

具体函数如下:

static const JNINativeMethod sMethods[] = {

     /* name, signature, funcPtr */

    {"init", "()J", (void*)android_server_AlarmManagerService_init},

    {"close", "(J)V", (void*)android_server_AlarmManagerService_close},

    {"set", "(JIJJ)V", (void*)android_server_AlarmManagerService_set},

    {"waitForAlarm", "(J)I", (void*)android_server_AlarmManagerService_waitForAlarm},

    {"setKernelTime", "(JJ)I", (void*)android_server_AlarmManagerService_setKernelTime},

    {"setKernelTimezone", "(JI)I", (void*)android_server_AlarmManagerService_setKernelTimezone},

};

这里细节不在分析,此处是分析RTC的,具体为什么如此,不清楚请看JNI的细节。

我们这里看到是setKernelTimesetKernelTImeZone等函数,这里是被AlarmManagerService调用的,由于代码量太大,因此分析时也是以setKernelTime为主线,其他一个流程。

进入到android_server_AlarmManagerService_setKernelTime这个函数分析,这里只分析函数进入退出接口,具体数据处理细节不做分析:

ret = impl->setTime(&tv);这里执行的是

int AlarmImplAlarmDriver::setTime(struct timeval *tv)

看到 res = ioctl(fds[0], ANDROID_ALARM_SET_RTC, &ts); 这个对于驱动工作者来说,熟悉而狂喜,终于对接上kernel了。话不多说,继续分析。

init函数里

static jlong android_server_AlarmManagerService_init(JNIEnv*, jobject)

{

    jlong ret = init_alarm_driver();

    if (ret) {

        return ret;

    }

 

    return init_timerfd();

}

init_alarm_driver,这里就是open /dev/alarm

static jlong init_alarm_driver()

{

    int fd = open("/dev/alarm", O_RDWR);

    if (fd < 0) {

        ALOGV("opening alarm driver failed: %s", strerror(errno));

        return 0;

    }

    AlarmImpl *ret = new AlarmImplAlarmDriver(fd);

    return reinterpret_cast(ret);

}

这里/dev/alarm的过程分析到此,下面给出另外一种open /dev/rtc的解决方法。时间问题,代码写的些微粗糙,请见谅:

AlarmImplTimerFdpublic里加入

static int setTime_Zone(struct timeval *tv);

实现过程

int AlarmImplTimerFd::setTime_Zone(struct timeval *tv)

{

    struct rtc_time rtc;

    struct tm tm, *gmtime_res;

    int fd;

    int res;

    ALOGW("setTime_Zone");

 res = settimeofday(tv, NULL);

    if (res < 0) {

        ALOGV("settimeofday() failed: %s\n", strerror(errno));

        return -1;

    }

 

    fd = open("/dev/rtc0", O_RDWR);

    if (fd < 0) {

        ALOGV("Unable to open %s\n",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;

    ALOGW("sec= %d min = %d,hour =%d,mday =%d,mom =%d,year =%d\n",rtc.tm_sec,rtc.tm_min,rtc.tm_hour,rtc.tm_mday,rtc.tm_mon,rtc.tm_year);

    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;

}

这里copysetTime的代码,做了些微修改。

然后在android_server_AlarmManagerService_setKernelTime里。

 //ret = impl->setTime(&tv);

ret = AlarmImplTimerFd::setTime(&tv,1);/*zwh add by*/

也可以直接来用,但时间关系这种比较简单,匆匆写了测试代码,后期会完善。

这里我们不得不佩服Google优美的软件设计思想,我们研究代码时不要只是关注功能实现,优美的设计思想也是我们要欣赏的,做一行爱一行,不是吗?

请记住init close set setTime setTimeZone这几个接口,即将进入到JAVA世界AlarmManagerService.java

这里与大多数的server一样,会在开机的时候,会被servicemanageraddservice函数加入,

这里加入过程其实就是一个binder的实例,具体binder机制,内容量太大。这里不啰嗦。

    private native long init();

    private native void close(long nativeData);

    private native void set(long nativeData, int type, long seconds, long nanoseconds);

    private native int waitForAlarm(long nativeData);

    private native int setKernelTime(long nativeData, long millis);

private native int setKernelTimezone(long nativeData, int minuteswest);

看到这里又到了我们那熟悉地方,这里是JAVA调用native的函数入口。沿着这里的调用过程封装接口如下:这里依然只是给出setTIme的实例,其他的一样的执行逻辑。

  public boolean setTime(long millis) {

getContext().enforceCallingOrSelfPermission( "android.permission.SET_TIME","setTime");

   if (mNativeData == 0) {

        Slog.w(TAG, "Not setting time since no alarm driver is available.");

        return false;

    }

    synchronized (mLock) {

    return setKernelTime(mNativeData, millis) == 0;

    }

  }

这里的mNativeDatainit的返回值

我们发现当寻找setTime调用入口时发现迷茫了,因为代码里只是找到了alarmmanager的调用,但还是调用的IalarmManager的调用,这里就是就是Android的著名的binder机制,这是一个让人很头疼也让人很佩服的机制,研究后真心佩服有这么优秀的设计思想。

我们可以找到Ialarmmnager.java的代码来自IAlarmManager.aidl。这里是Google为了降低开发者的难度,而做的一个模板,这里其实就是JAVA设计里的代理思想。会有两个重要的类,proxystub类,通过stub类获得proxy实例,再通过proxytransmit函数执行Parcel的数据解析和写入,

_data.writeInterfaceToken(DESCRIPTOR);

_data.writeLong(millis);

mRemote.transact(Stub.TRANSACTION_setTime, _data, _reply, 0);

_reply.readException();

_result = (0!=_reply.readInt());

然后在binder机制下执行到stub里的onTransact

onTransact里的this.setTime就是执行AlarmManagerServicesetTime函数,奇妙的世界,这里binder机制不做详细讲解,水平有限,几页纸也讲不清楚。后续会根据时间来补充博客。

boolean _result = this.setTime(_arg0);

这里似乎完整的解释了怎么执行的。然谁调用的alarmmanager的接口呢?

这里继续分析,我们知道在Framework.jar里,都是系统架构的接口,有著名的activity.java等,这里也是alarmmanager.java就在Framework.jar里,因此在DateTimeSetting.java会调用到此,DateTimeSetting.java在如下路径。

packages\apps\Settings\src\com\android\settings\DateTimeSettings.java

这里调用过程如下:

onDateSet onTimeSet是我们设置时间的控件函数。

分别执行 setTime(activity, hourOfDay, minute);setDate(activity, year, month, day);

这里的函数分别执行

  /* package */ static void setDate(Context context, int year, int month, int day) {

        Calendar c = Calendar.getInstance();

        c.set(Calendar.YEAR, year);

        c.set(Calendar.MONTH, month);

        c.set(Calendar.DAY_OF_MONTH, day);

        long when = Math.max(c.getTimeInMillis(), MIN_DATE);

        if (when / 1000 < Integer.MAX_VALUE) {

   ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when);

        }

    }

wow 又到了开心的时刻,这里找到了alarmmanager的调用入口。

((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when);

注册机制就是刚才提到的在servicemanager addserviceSystemServiceRegistry.javaregisterService里的注册alarmmanager实例。

这里的serviceManageradd过程不是我们常见的systemserver里的addservice.如下流程:

AlarmManagerService.javaonstart函数:

  publishBinderService(Context.ALARM_SERVICE, mService);

  publishLocalService(LocalService.class, new LocalService());

执行到SystemService.java的函数如下。

ServiceManager.addService(name, service, allowIsolated);

 

frameworks\base\core\java\android\app\SystemServiceRegistry.java

    registerService(Context.ALARM_SERVICE, AlarmManager.class,

                new CachedServiceFetcher() {

            @Override

            public AlarmManager createService(ContextImpl ctx) {

                IBinder b = ServiceManager.getService(Context.ALARM_SERVICE);

                IAlarmManager service = IAlarmManager.Stub.asInterface(b);

                return new AlarmManager(service, ctx);

            }});

这里用到的是servicemanager的服务的addget的细节,具体可以分析servicemanager的技术细节,真心感到Android的博大精深,这里只是RTC的分析过程,因此很多技术细节没有设计,毕竟这些技术细节一本书也未必写完,这里仅是自己的一个随笔。后期会慢慢分享其他的技术细节。

12-21 21:21