diff options
author | Miodrag Dinic <miodrag.dinic@imgtec.com> | 2015-01-19 11:41:33 +0100 |
---|---|---|
committer | Miodrag Dinic <miodrag.dinic@imgtec.com> | 2015-02-05 11:22:21 +0100 |
commit | 8a63981d5afe44bfb5c8c82ba39c055c28818a29 (patch) | |
tree | 5684b996a22f9d8d285b5edfc31131f18e5a5951 | |
parent | be044481849d9a3373563b5ab15d50bd6b1c86ca (diff) | |
download | qemu-android-8a63981d5afe44bfb5c8c82ba39c055c28818a29.tar.gz |
Add Goldfish Timer & RTC devices
The following was taken from AOSP emulator documentation :
<AOSP>/external/qemu/GOLDFISH-VIRTUAL-HARDWARE.TXT
$QEMU=qemu-android : new code base QEMU with ranchu machine
$KERNEL=https://android.googlesource.com/kernel/goldfish.git
Godlfish timer:
==================================
Relevant files:
$QEMU/hw/timer/goldfish_timer.c
$KERNEL/arch/mips/goldfish/goldfish-time.c
Device properties:
Name: goldfish_timer
Id: -1
IrqCount: 1
32-bit I/O registers
0x00 R TIME_LOW : Get current time, return low-order 32-bits.
0x04 R TIME_HIGH : Return high 32-bits from TIME_LOW read.
0x08 W ALARM_LOW : Set low 32-bit value of alarm, and arm it.
0x0c W ALARM_HIGH : Set high 32-bit value of alarm.
0x10 W CLEAR_INTERRUPT : Lower device's irq level.
0x14 W CLEAR_ALARM
This device is used to return the current host time to the kernel,
as a high-precision signed 64-bit nanoseconds value, starting from
a liberal point in time.
Goldfish real-time clock (RTC):
==================================
Relevant files:
$QEMU/hw/timer/goldfish_timer.c
$KERNEL/drivers/rtc/rtc-goldfish.c
This device is _very_ similar to the Goldfish timer one,
with the following important differences:
- Values reported are still 64-bit nanoseconds, but they
have a granularity of 1 second, and represent
host-specific values (really 'time() * 1e9')
- The alarm is non-functioning, i.e. writing to
ALARM_LOW / ALARM_HIGH will work, but will never
arm any alarm.
Change-Id: Ie548ae7e39d02fa6766b9ea89def26aa42ba1366
-rw-r--r-- | hw/timer/Makefile.objs | 1 | ||||
-rw-r--r-- | hw/timer/goldfish_timer.c | 310 |
2 files changed, 311 insertions, 0 deletions
diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs index 2c86c3d412..4828b96970 100644 --- a/hw/timer/Makefile.objs +++ b/hw/timer/Makefile.objs @@ -27,6 +27,7 @@ obj-$(CONFIG_PXA2XX) += pxa2xx_timer.o obj-$(CONFIG_SH4) += sh_timer.o obj-$(CONFIG_TUSB6010) += tusb6010.o obj-$(CONFIG_DIGIC) += digic-timer.o +obj-$(CONFIG_GOLDFISH) += goldfish_timer.o obj-$(CONFIG_MC146818RTC) += mc146818rtc.o diff --git a/hw/timer/goldfish_timer.c b/hw/timer/goldfish_timer.c new file mode 100644 index 0000000000..0b2f89541d --- /dev/null +++ b/hw/timer/goldfish_timer.c @@ -0,0 +1,310 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu-common.h" +#include "qemu/timer.h" +#include "cpu.h" +#include "hw/hw.h" +#include "hw/sysbus.h" + +enum { + TIMER_TIME_LOW = 0x00, // get low bits of current time and update TIMER_TIME_HIGH + TIMER_TIME_HIGH = 0x04, // get high bits of time at last TIMER_TIME_LOW read + TIMER_ALARM_LOW = 0x08, // set low bits of alarm and activate it + TIMER_ALARM_HIGH = 0x0c, // set high bits of next alarm + TIMER_CLEAR_INTERRUPT = 0x10, + TIMER_CLEAR_ALARM = 0x14 +}; + +struct timer_state { + SysBusDevice parent; + + MemoryRegion iomem; + qemu_irq irq; + + uint32_t alarm_low_ns; + int32_t alarm_high_ns; + int64_t now_ns; + char armed; + QEMUTimer *timer; +}; + +#define GOLDFISH_TIMER_SAVE_VERSION 1 + +#define TYPE_GOLDFISH_TIMER "goldfish_timer" +#define GOLDFISH_TIMER(obj) OBJECT_CHECK(struct timer_state, (obj), TYPE_GOLDFISH_TIMER) + +static void goldfish_timer_save(QEMUFile* f, void* opaque) +{ + struct timer_state* s = opaque; + + qemu_put_be64(f, s->now_ns); /* in case the kernel is in the middle of a timer read */ + qemu_put_byte(f, s->armed); + if (s->armed) { + int64_t now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + int64_t alarm_ns = (s->alarm_low_ns | (int64_t)s->alarm_high_ns << 32); + qemu_put_be64(f, alarm_ns - now_ns); + } +} + +static int goldfish_timer_load(QEMUFile* f, void* opaque, int version_id) +{ + struct timer_state* s = opaque; + + if (version_id != GOLDFISH_TIMER_SAVE_VERSION) + return -1; + + s->now_ns = qemu_get_be64(f); + s->armed = qemu_get_byte(f); + if (s->armed) { + int64_t now_tks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + int64_t diff_tks = qemu_get_be64(f); + int64_t alarm_tks = now_tks + diff_tks; + + if (alarm_tks <= now_tks) { + qemu_set_irq(s->irq, 1); + s->armed = 0; + } else { + qemu_set_irq(s->irq, 0); + timer_mod(s->timer, alarm_tks); + } + } + return 0; +} + +static uint64_t goldfish_timer_read(void *opaque, hwaddr offset, unsigned size) +{ + struct timer_state *s = (struct timer_state *)opaque; + switch(offset) { + case TIMER_TIME_LOW: + s->now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + return s->now_ns; + case TIMER_TIME_HIGH: + return s->now_ns >> 32; + default: + cpu_abort(current_cpu, + "goldfish_timer_read: Bad offset %" HWADDR_PRIx "\n", + offset); + return 0; + } +} + +static void goldfish_timer_write(void *opaque, hwaddr offset, uint64_t value_ns, unsigned size) +{ + struct timer_state *s = (struct timer_state *)opaque; + int64_t alarm_ns, now_ns; + switch(offset) { + case TIMER_ALARM_LOW: + s->alarm_low_ns = value_ns; + alarm_ns = (s->alarm_low_ns | (int64_t)s->alarm_high_ns << 32); + now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + if (alarm_ns <= now_ns) { + qemu_set_irq(s->irq, 1); + } else { + timer_mod(s->timer, alarm_ns); + s->armed = 1; + } + break; + case TIMER_ALARM_HIGH: + s->alarm_high_ns = value_ns; + break; + case TIMER_CLEAR_ALARM: + timer_del(s->timer); + s->armed = 0; + /* fall through */ + case TIMER_CLEAR_INTERRUPT: + qemu_set_irq(s->irq, 0); + break; + default: + cpu_abort(current_cpu, + "goldfish_timer_write: Bad offset %" HWADDR_PRIx "\n", + offset); + } +} + +static void goldfish_timer_tick(void *opaque) +{ + struct timer_state *s = (struct timer_state *)opaque; + s->armed = 0; + qemu_set_irq(s->irq, 1); +} + +static const MemoryRegionOps mips_qemu_timer_ops = { + .read = goldfish_timer_read, + .write = goldfish_timer_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void goldfish_timer_realize(DeviceState *dev, Error **errp) +{ + SysBusDevice *sbdev = SYS_BUS_DEVICE(dev); + struct timer_state *s = GOLDFISH_TIMER(dev); + + s->timer = timer_new(QEMU_CLOCK_VIRTUAL, SCALE_NS, goldfish_timer_tick, s); + + memory_region_init_io(&s->iomem, OBJECT(s), &mips_qemu_timer_ops, s, + "goldfish_timer", 0x1000); + sysbus_init_mmio(sbdev, &s->iomem); + sysbus_init_irq(sbdev, &s->irq); + register_savevm(NULL, + "goldfish_timer", + 0, + GOLDFISH_TIMER_SAVE_VERSION, + goldfish_timer_save, + goldfish_timer_load, + s); +} + +static void goldfish_timer_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = goldfish_timer_realize; + dc->desc = "goldfish timer"; +} + +static const TypeInfo goldfish_timer_info = { + .name = TYPE_GOLDFISH_TIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(struct timer_state), + .class_init = goldfish_timer_class_init, +}; + +static void goldfish_timer_register(void) +{ + type_register_static(&goldfish_timer_info); +} + +type_init(goldfish_timer_register); + +/***************************************************************************** + * Goldfish Real Time Clock device implementation + *****************************************************************************/ + +struct rtc_state { + SysBusDevice parent; + + MemoryRegion iomem; + qemu_irq irq; + + uint32_t alarm_low; + int32_t alarm_high; + int64_t now; +}; + +/* we save the RTC for the case where the kernel is in the middle of a rtc_read + * (i.e. it has read the low 32-bit of s->now, but not the high 32-bits yet */ +#define GOLDFISH_RTC_SAVE_VERSION 1 + +#define TYPE_GOLDFISH_RTC "goldfish_rtc" +#define GOLDFISH_RTC(obj) OBJECT_CHECK(struct rtc_state, (obj), TYPE_GOLDFISH_RTC) + +static void goldfish_rtc_save(QEMUFile* f, void* opaque) +{ + struct rtc_state* s = opaque; + + qemu_put_be64(f, s->now); +} + +static int goldfish_rtc_load(QEMUFile* f, void* opaque, int version_id) +{ + struct rtc_state* s = opaque; + + if (version_id != GOLDFISH_RTC_SAVE_VERSION) + return -1; + + /* this is an old value that is not correct. but that's ok anyway */ + s->now = qemu_get_be64(f); + return 0; +} + +static uint64_t goldfish_rtc_read(void *opaque, hwaddr offset, unsigned size) +{ + struct rtc_state *s = (struct rtc_state *)opaque; + switch(offset) { + case 0x0: + s->now = (int64_t)time(NULL) * 1000000000; + return s->now; + case 0x4: + return s->now >> 32; + default: + cpu_abort(current_cpu, + "goldfish_rtc_read: Bad offset %" HWADDR_PRIx "\n", + offset); + return 0; + } +} + +static void goldfish_rtc_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) +{ + struct rtc_state *s = (struct rtc_state *)opaque; + switch(offset) { + case 0x8: + s->alarm_low = value; + break; + case 0xc: + s->alarm_high = value; + break; + case 0x10: + qemu_set_irq(s->irq, 0); + break; + default: + cpu_abort(current_cpu, + "goldfish_rtc_write: Bad offset %" HWADDR_PRIx "\n", + offset); + } +} + +static const MemoryRegionOps mips_qemu_rtc_ops = { + .read = goldfish_rtc_read, + .write = goldfish_rtc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void goldfish_rtc_realize(DeviceState *dev, Error **errp) +{ + SysBusDevice *sbdev = SYS_BUS_DEVICE(dev); + struct rtc_state *s = GOLDFISH_RTC(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &mips_qemu_rtc_ops, s, + "goldfish_rtc", 0x1000); + sysbus_init_mmio(sbdev, &s->iomem); + sysbus_init_irq(sbdev, &s->irq); + register_savevm(NULL, + "goldfish_rtc", + 0, + GOLDFISH_RTC_SAVE_VERSION, + goldfish_rtc_save, + goldfish_rtc_load, + s); +} + +static void goldfish_rtc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = goldfish_rtc_realize; + dc->desc = "goldfish battery"; +} + +static const TypeInfo goldfish_rtc_info = { + .name = TYPE_GOLDFISH_RTC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(struct rtc_state), + .class_init = goldfish_rtc_class_init, +}; + +static void goldfish_rtc_register(void) +{ + type_register_static(&goldfish_rtc_info); +} + +type_init(goldfish_rtc_register); |