#include "qemu-timer.h"
#include "sysemu.h"
#include "pc.h"
-#include "apic.h"
#include "isa.h"
#include "mc146818rtc.h"
+#ifdef TARGET_I386
+#include "apic.h"
+#endif
+
//#define DEBUG_CMOS
//#define DEBUG_COALESCED
typedef struct RTCState {
ISADevice dev;
+ MemoryRegion io;
uint8_t cmos_data[128];
uint8_t cmos_index;
struct tm current_tm;
QEMUTimer *coalesced_timer;
QEMUTimer *second_timer;
QEMUTimer *second_timer2;
+ Notifier clock_reset_notifier;
+ LostTickPolicy lost_tick_policy;
+ Notifier suspend_notifier;
} RTCState;
static void rtc_set_time(RTCState *s);
} else {
/* divide each RTC interval to 2 - 8 smaller intervals */
int c = MIN(s->irq_coalesced, 7) + 1;
- int64_t next_clock = qemu_get_clock(rtc_clock) +
+ int64_t next_clock = qemu_get_clock_ns(rtc_clock) +
muldiv64(s->period / c, get_ticks_per_sec(), 32768);
qemu_mod_timer(s->coalesced_timer, next_clock);
}
RTCState *s = opaque;
rtc_timer_update(s, s->next_periodic_time);
+ s->cmos_data[RTC_REG_C] |= REG_C_PF;
if (s->cmos_data[RTC_REG_B] & REG_B_PIE) {
- s->cmos_data[RTC_REG_C] |= 0xc0;
+ s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
#ifdef TARGET_I386
- if(rtc_td_hack) {
+ if (s->lost_tick_policy == LOST_TICK_SLEW) {
if (s->irq_reinject_on_ack_count >= RTC_REINJECT_ON_ACK_COUNT)
s->irq_reinject_on_ack_count = 0;
apic_reset_irq_delivered();
/* UIP bit is read only */
s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) |
(s->cmos_data[RTC_REG_A] & REG_A_UIP);
- rtc_timer_update(s, qemu_get_clock(rtc_clock));
+ rtc_timer_update(s, qemu_get_clock_ns(rtc_clock));
break;
case RTC_REG_B:
if (data & REG_B_SET) {
} else {
s->cmos_data[RTC_REG_B] = data;
}
- rtc_timer_update(s, qemu_get_clock(rtc_clock));
+ rtc_timer_update(s, qemu_get_clock_ns(rtc_clock));
break;
case RTC_REG_C:
case RTC_REG_D:
tm->tm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]);
tm->tm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]);
tm->tm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS] & 0x7f);
- if (!(s->cmos_data[RTC_REG_B] & REG_B_24H) &&
- (s->cmos_data[RTC_HOURS] & 0x80)) {
- tm->tm_hour += 12;
+ if (!(s->cmos_data[RTC_REG_B] & REG_B_24H)) {
+ tm->tm_hour %= 12;
+ if (s->cmos_data[RTC_HOURS] & 0x80) {
+ tm->tm_hour += 12;
+ }
}
tm->tm_wday = rtc_from_bcd(s, s->cmos_data[RTC_DAY_OF_WEEK]) - 1;
tm->tm_mday = rtc_from_bcd(s, s->cmos_data[RTC_DAY_OF_MONTH]);
s->cmos_data[RTC_HOURS] = rtc_to_bcd(s, tm->tm_hour);
} else {
/* 12 hour format */
- s->cmos_data[RTC_HOURS] = rtc_to_bcd(s, tm->tm_hour % 12);
+ int h = (tm->tm_hour % 12) ? tm->tm_hour % 12 : 12;
+ s->cmos_data[RTC_HOURS] = rtc_to_bcd(s, h);
if (tm->tm_hour >= 12)
s->cmos_data[RTC_HOURS] |= 0x80;
}
}
/* check alarm */
- if (s->cmos_data[RTC_REG_B] & REG_B_AIE) {
- if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 ||
- rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]) == s->current_tm.tm_sec) &&
- ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 ||
- rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]) == s->current_tm.tm_min) &&
- ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 ||
- rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]) == s->current_tm.tm_hour)) {
-
- s->cmos_data[RTC_REG_C] |= 0xa0;
+ if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 ||
+ rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]) == s->current_tm.tm_sec) &&
+ ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 ||
+ rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]) == s->current_tm.tm_min) &&
+ ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 ||
+ rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]) == s->current_tm.tm_hour)) {
+
+ s->cmos_data[RTC_REG_C] |= REG_C_AF;
+ if (s->cmos_data[RTC_REG_B] & REG_B_AIE) {
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_RTC);
qemu_irq_raise(s->irq);
+ s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
}
}
case RTC_REG_C:
ret = s->cmos_data[s->cmos_index];
qemu_irq_lower(s->irq);
+ s->cmos_data[RTC_REG_C] = 0x00;
#ifdef TARGET_I386
if(s->irq_coalesced &&
+ (s->cmos_data[RTC_REG_B] & REG_B_PIE) &&
s->irq_reinject_on_ack_count < RTC_REINJECT_ON_ACK_COUNT) {
s->irq_reinject_on_ack_count++;
+ s->cmos_data[RTC_REG_C] |= REG_C_IRQF | REG_C_PF;
apic_reset_irq_delivered();
DPRINTF_C("cmos: injecting on ack\n");
qemu_irq_raise(s->irq);
DPRINTF_C("cmos: coalesced irqs decreased to %d\n",
s->irq_coalesced);
}
- break;
}
#endif
-
- s->cmos_data[RTC_REG_C] = 0x00;
break;
default:
ret = s->cmos_data[s->cmos_index];
RTCState *s = opaque;
if (version_id >= 2) {
- if (rtc_td_hack) {
+ if (s->lost_tick_policy == LOST_TICK_SLEW) {
rtc_coalesced_timer_update(s);
}
}
}
};
+static void rtc_notify_clock_reset(Notifier *notifier, void *data)
+{
+ RTCState *s = container_of(notifier, RTCState, clock_reset_notifier);
+ int64_t now = *(int64_t *)data;
+
+ rtc_set_date_from_host(&s->dev);
+ s->next_second_time = now + (get_ticks_per_sec() * 99) / 100;
+ qemu_mod_timer(s->second_timer2, s->next_second_time);
+ rtc_timer_update(s, now);
+#ifdef TARGET_I386
+ if (s->lost_tick_policy == LOST_TICK_SLEW) {
+ rtc_coalesced_timer_update(s);
+ }
+#endif
+}
+
+/* set CMOS shutdown status register (index 0xF) as S3_resume(0xFE)
+ BIOS will read it and start S3 resume at POST Entry */
+static void rtc_notify_suspend(Notifier *notifier, void *data)
+{
+ RTCState *s = container_of(notifier, RTCState, suspend_notifier);
+ rtc_set_memory(&s->dev, 0xF, 0xFE);
+}
+
static void rtc_reset(void *opaque)
{
RTCState *s = opaque;
qemu_irq_lower(s->irq);
#ifdef TARGET_I386
- if (rtc_td_hack)
- s->irq_coalesced = 0;
+ if (s->lost_tick_policy == LOST_TICK_SLEW) {
+ s->irq_coalesced = 0;
+ }
#endif
}
+static const MemoryRegionPortio cmos_portio[] = {
+ {0, 2, 1, .read = cmos_ioport_read, .write = cmos_ioport_write },
+ PORTIO_END_OF_LIST(),
+};
+
+static const MemoryRegionOps cmos_ops = {
+ .old_portio = cmos_portio
+};
+
+// FIXME add int32 visitor
+static void visit_type_int32(Visitor *v, int *value, const char *name, Error **errp)
+{
+ int64_t val = *value;
+ visit_type_int(v, &val, name, errp);
+}
+
+static void rtc_get_date(Object *obj, Visitor *v, void *opaque,
+ const char *name, Error **errp)
+{
+ ISADevice *isa = ISA_DEVICE(obj);
+ RTCState *s = DO_UPCAST(RTCState, dev, isa);
+
+ visit_start_struct(v, NULL, "struct tm", name, 0, errp);
+ visit_type_int32(v, &s->current_tm.tm_year, "tm_year", errp);
+ visit_type_int32(v, &s->current_tm.tm_mon, "tm_mon", errp);
+ visit_type_int32(v, &s->current_tm.tm_mday, "tm_mday", errp);
+ visit_type_int32(v, &s->current_tm.tm_hour, "tm_hour", errp);
+ visit_type_int32(v, &s->current_tm.tm_min, "tm_min", errp);
+ visit_type_int32(v, &s->current_tm.tm_sec, "tm_sec", errp);
+ visit_end_struct(v, errp);
+}
+
static int rtc_initfn(ISADevice *dev)
{
RTCState *s = DO_UPCAST(RTCState, dev, dev);
rtc_set_date_from_host(dev);
- s->periodic_timer = qemu_new_timer(rtc_clock, rtc_periodic_timer, s);
#ifdef TARGET_I386
- if (rtc_td_hack)
+ switch (s->lost_tick_policy) {
+ case LOST_TICK_SLEW:
s->coalesced_timer =
- qemu_new_timer(rtc_clock, rtc_coalesced_timer, s);
+ qemu_new_timer_ns(rtc_clock, rtc_coalesced_timer, s);
+ break;
+ case LOST_TICK_DISCARD:
+ break;
+ default:
+ return -EINVAL;
+ }
#endif
- s->second_timer = qemu_new_timer(rtc_clock, rtc_update_second, s);
- s->second_timer2 = qemu_new_timer(rtc_clock, rtc_update_second2, s);
+
+ s->periodic_timer = qemu_new_timer_ns(rtc_clock, rtc_periodic_timer, s);
+ s->second_timer = qemu_new_timer_ns(rtc_clock, rtc_update_second, s);
+ s->second_timer2 = qemu_new_timer_ns(rtc_clock, rtc_update_second2, s);
+
+ s->clock_reset_notifier.notify = rtc_notify_clock_reset;
+ qemu_register_clock_reset_notifier(rtc_clock, &s->clock_reset_notifier);
+
+ s->suspend_notifier.notify = rtc_notify_suspend;
+ qemu_register_suspend_notifier(&s->suspend_notifier);
s->next_second_time =
- qemu_get_clock(rtc_clock) + (get_ticks_per_sec() * 99) / 100;
+ qemu_get_clock_ns(rtc_clock) + (get_ticks_per_sec() * 99) / 100;
qemu_mod_timer(s->second_timer2, s->next_second_time);
- register_ioport_write(base, 2, 1, cmos_ioport_write, s);
- register_ioport_read(base, 2, 1, cmos_ioport_read, s);
- isa_init_ioport_range(dev, base, 2);
+ memory_region_init_io(&s->io, &cmos_ops, s, "rtc", 2);
+ isa_register_ioport(dev, &s->io, base);
qdev_set_legacy_instance_id(&dev->qdev, base, 2);
qemu_register_reset(rtc_reset, s);
+
+ object_property_add(OBJECT(s), "date", "struct tm",
+ rtc_get_date, NULL, NULL, s, NULL);
+
return 0;
}
-ISADevice *rtc_init(int base_year, qemu_irq intercept_irq)
+ISADevice *rtc_init(ISABus *bus, int base_year, qemu_irq intercept_irq)
{
ISADevice *dev;
RTCState *s;
- dev = isa_create("mc146818rtc");
+ dev = isa_create(bus, "mc146818rtc");
s = DO_UPCAST(RTCState, dev, dev);
qdev_prop_set_int32(&dev->qdev, "base_year", base_year);
qdev_init_nofail(&dev->qdev);
return dev;
}
-static ISADeviceInfo mc146818rtc_info = {
- .qdev.name = "mc146818rtc",
- .qdev.size = sizeof(RTCState),
- .qdev.no_user = 1,
- .qdev.vmsd = &vmstate_rtc,
- .init = rtc_initfn,
- .qdev.props = (Property[]) {
- DEFINE_PROP_INT32("base_year", RTCState, base_year, 1980),
- DEFINE_PROP_END_OF_LIST(),
- }
+static Property mc146818rtc_properties[] = {
+ DEFINE_PROP_INT32("base_year", RTCState, base_year, 1980),
+ DEFINE_PROP_LOSTTICKPOLICY("lost_tick_policy", RTCState,
+ lost_tick_policy, LOST_TICK_DISCARD),
+ DEFINE_PROP_END_OF_LIST(),
};
-static void mc146818rtc_register(void)
+static void rtc_class_initfn(ObjectClass *klass, void *data)
{
- isa_qdev_register(&mc146818rtc_info);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ISADeviceClass *ic = ISA_DEVICE_CLASS(klass);
+ ic->init = rtc_initfn;
+ dc->no_user = 1;
+ dc->vmsd = &vmstate_rtc;
+ dc->props = mc146818rtc_properties;
}
-device_init(mc146818rtc_register)
+
+static TypeInfo mc146818rtc_info = {
+ .name = "mc146818rtc",
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(RTCState),
+ .class_init = rtc_class_initfn,
+};
+
+static void mc146818rtc_register_types(void)
+{
+ type_register_static(&mc146818rtc_info);
+}
+
+type_init(mc146818rtc_register_types)