RTC的驱动移植和JNI修改
唠叨几句,本文目的是:
1>移植hym8563的rtc驱动,硬件板载是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器件地址获得小诀窍,根据IC的datasheet,获取设备的读或者写的从机地址,除以2,就是我们现在的0x51,至于为什么要除以2呢,则是跟设备器件地址的<是有关系的。
加入后编译,发现interrupt的request是报错的,但是由于我们的中断时悬空的,因此直接将驱动里的中断代码屏蔽,以后再做研究。然烧录后发现却没办法调用到外挂RTC,这里的JNI确实只是调用了aml_vrtc.c的驱动,一会给出问题出在哪里。
首先amlogic的代码真的必须被吐槽,编译适配感觉还是有点弱的感觉,举个例子,修改的地方编译老是有种写入文件虽然编译OK,但是编译结果却没有被编译到最后的aml_upgrade的文件里,最惹人烦的就是修改了uboot,但是一直没有打印添加的追踪打印代码,后来检查out下的打包时间,发现时间不一样,删掉uboot才打包OK,在这在编译使用hwclock时也出现,必须吐槽下。
s905x的RTC的驱动接口有两个,一个是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.c和rtc-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这里是rtc的native层。
具体函数如下:
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的细节。
我们这里看到是setKernelTime和setKernelTImeZone等函数,这里是被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的解决方法。时间问题,代码写的些微粗糙,请见谅:
在AlarmImplTimerFd的public里加入
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;
}
这里copy的setTime的代码,做了些微修改。
然后在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一样,会在开机的时候,会被servicemanager的addservice函数加入,
这里加入过程其实就是一个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;
}
}
这里的mNativeData是init的返回值
我们发现当寻找setTime调用入口时发现迷茫了,因为代码里只是找到了alarmmanager的调用,但还是调用的IalarmManager的调用,这里就是就是Android的著名的binder机制,这是一个让人很头疼也让人很佩服的机制,研究后真心佩服有这么优秀的设计思想。
我们可以找到Ialarmmnager.java的代码来自IAlarmManager.aidl。这里是Google为了降低开发者的难度,而做的一个模板,这里其实就是JAVA设计里的代理思想。会有两个重要的类,proxy和stub类,通过stub类获得proxy实例,再通过proxy的transmit函数执行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就是执行AlarmManagerService的setTime函数,奇妙的世界,这里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 的addservice,SystemServiceRegistry.java的registerService里的注册alarmmanager实例。
这里的serviceManager的add过程不是我们常见的systemserver里的addservice.如下流程:
AlarmManagerService.java的onstart函数:
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的服务的add和get的细节,具体可以分析servicemanager的技术细节,真心感到Android的博大精深,这里只是RTC的分析过程,因此很多技术细节没有设计,毕竟这些技术细节一本书也未必写完,这里仅是自己的一个随笔。后期会慢慢分享其他的技术细节。