|
|
|
@ -63,11 +63,62 @@ |
|
|
|
#define RTC_REG_C 12 |
|
|
|
#define RTC_REG_D 13 |
|
|
|
|
|
|
|
/* PC cmos mappings */ |
|
|
|
#define REG_IBM_CENTURY_BYTE 0x32 |
|
|
|
#define REG_IBM_PS2_CENTURY_BYTE 0x37 |
|
|
|
#define REG_A_UIP 0x80 |
|
|
|
|
|
|
|
RTCState rtc_state; |
|
|
|
#define REG_B_SET 0x80 |
|
|
|
#define REG_B_PIE 0x40 |
|
|
|
#define REG_B_AIE 0x20 |
|
|
|
#define REG_B_UIE 0x10 |
|
|
|
|
|
|
|
struct RTCState { |
|
|
|
uint8_t cmos_data[128]; |
|
|
|
uint8_t cmos_index; |
|
|
|
int current_time; /* in seconds */ |
|
|
|
int irq; |
|
|
|
uint8_t buf_data[10]; /* buffered data */ |
|
|
|
/* periodic timer */ |
|
|
|
QEMUTimer *periodic_timer; |
|
|
|
int64_t next_periodic_time; |
|
|
|
/* second update */ |
|
|
|
int64_t next_second_time; |
|
|
|
QEMUTimer *second_timer; |
|
|
|
QEMUTimer *second_timer2; |
|
|
|
}; |
|
|
|
|
|
|
|
static void rtc_set_time(RTCState *s); |
|
|
|
static void rtc_set_date_buf(RTCState *s, const struct tm *tm); |
|
|
|
static void rtc_copy_date(RTCState *s); |
|
|
|
|
|
|
|
static void rtc_timer_update(RTCState *s, int64_t current_time) |
|
|
|
{ |
|
|
|
int period_code, period; |
|
|
|
int64_t cur_clock, next_irq_clock; |
|
|
|
|
|
|
|
period_code = s->cmos_data[RTC_REG_A] & 0x0f; |
|
|
|
if (period_code != 0 && |
|
|
|
(s->cmos_data[RTC_REG_B] & REG_B_PIE)) { |
|
|
|
if (period_code <= 2) |
|
|
|
period_code += 7; |
|
|
|
/* period in 32 Khz cycles */ |
|
|
|
period = 1 << (period_code - 1); |
|
|
|
/* compute 32 khz clock */ |
|
|
|
cur_clock = muldiv64(current_time, 32768, ticks_per_sec); |
|
|
|
next_irq_clock = (cur_clock & ~(period - 1)) + period; |
|
|
|
s->next_periodic_time = muldiv64(next_irq_clock, ticks_per_sec, 32768) + 1; |
|
|
|
qemu_mod_timer(s->periodic_timer, s->next_periodic_time); |
|
|
|
} else { |
|
|
|
qemu_del_timer(s->periodic_timer); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void rtc_periodic_timer(void *opaque) |
|
|
|
{ |
|
|
|
RTCState *s = opaque; |
|
|
|
|
|
|
|
rtc_timer_update(s, s->next_periodic_time); |
|
|
|
s->cmos_data[RTC_REG_C] |= 0xc0; |
|
|
|
pic_set_irq(s->irq, 1); |
|
|
|
} |
|
|
|
|
|
|
|
static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) |
|
|
|
{ |
|
|
|
@ -80,7 +131,7 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) |
|
|
|
printf("cmos: write index=0x%02x val=0x%02x\n", |
|
|
|
s->cmos_index, data); |
|
|
|
#endif |
|
|
|
switch(addr) { |
|
|
|
switch(s->cmos_index) { |
|
|
|
case RTC_SECONDS_ALARM: |
|
|
|
case RTC_MINUTES_ALARM: |
|
|
|
case RTC_HOURS_ALARM: |
|
|
|
@ -95,10 +146,30 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) |
|
|
|
case RTC_MONTH: |
|
|
|
case RTC_YEAR: |
|
|
|
s->cmos_data[s->cmos_index] = data; |
|
|
|
/* if in set mode, do not update the time */ |
|
|
|
if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { |
|
|
|
rtc_set_time(s); |
|
|
|
} |
|
|
|
break; |
|
|
|
case RTC_REG_A: |
|
|
|
/* 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(vm_clock)); |
|
|
|
break; |
|
|
|
case RTC_REG_B: |
|
|
|
s->cmos_data[s->cmos_index] = data; |
|
|
|
if (data & REG_B_SET) { |
|
|
|
/* set mode: reset UIP mode */ |
|
|
|
s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; |
|
|
|
data &= ~REG_B_UIE; |
|
|
|
} else { |
|
|
|
/* if disabling set mode, update the time */ |
|
|
|
if (s->cmos_data[RTC_REG_B] & REG_B_SET) { |
|
|
|
rtc_set_time(s); |
|
|
|
} |
|
|
|
} |
|
|
|
s->cmos_data[RTC_REG_B] = data; |
|
|
|
rtc_timer_update(s, qemu_get_clock(vm_clock)); |
|
|
|
break; |
|
|
|
case RTC_REG_C: |
|
|
|
case RTC_REG_D: |
|
|
|
@ -111,27 +182,104 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static inline int to_bcd(int a) |
|
|
|
static inline int to_bcd(RTCState *s, int a) |
|
|
|
{ |
|
|
|
return ((a / 10) << 4) | (a % 10); |
|
|
|
if (s->cmos_data[RTC_REG_B] & 0x04) { |
|
|
|
return a; |
|
|
|
} else { |
|
|
|
return ((a / 10) << 4) | (a % 10); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void cmos_update_time(RTCState *s) |
|
|
|
static inline int from_bcd(RTCState *s, int a) |
|
|
|
{ |
|
|
|
struct tm *tm; |
|
|
|
if (s->cmos_data[RTC_REG_B] & 0x04) { |
|
|
|
return a; |
|
|
|
} else { |
|
|
|
return ((a >> 4) * 10) + (a & 0x0f); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void rtc_set_time(RTCState *s) |
|
|
|
{ |
|
|
|
struct tm tm1, *tm = &tm1; |
|
|
|
|
|
|
|
tm->tm_sec = from_bcd(s, s->cmos_data[RTC_SECONDS]); |
|
|
|
tm->tm_min = from_bcd(s, s->cmos_data[RTC_MINUTES]); |
|
|
|
tm->tm_hour = from_bcd(s, s->cmos_data[RTC_HOURS]); |
|
|
|
tm->tm_wday = from_bcd(s, s->cmos_data[RTC_DAY_OF_WEEK]); |
|
|
|
tm->tm_mday = from_bcd(s, s->cmos_data[RTC_DAY_OF_MONTH]); |
|
|
|
tm->tm_mon = from_bcd(s, s->cmos_data[RTC_MONTH]) - 1; |
|
|
|
tm->tm_year = from_bcd(s, s->cmos_data[RTC_YEAR]) + 100; |
|
|
|
|
|
|
|
/* update internal state */ |
|
|
|
s->buf_data[RTC_SECONDS] = s->cmos_data[RTC_SECONDS]; |
|
|
|
s->buf_data[RTC_MINUTES] = s->cmos_data[RTC_MINUTES]; |
|
|
|
s->buf_data[RTC_HOURS] = s->cmos_data[RTC_HOURS]; |
|
|
|
s->buf_data[RTC_DAY_OF_WEEK] = s->cmos_data[RTC_DAY_OF_WEEK]; |
|
|
|
s->buf_data[RTC_DAY_OF_MONTH] = s->cmos_data[RTC_DAY_OF_MONTH]; |
|
|
|
s->buf_data[RTC_MONTH] = s->cmos_data[RTC_MONTH]; |
|
|
|
s->buf_data[RTC_YEAR] = s->cmos_data[RTC_YEAR]; |
|
|
|
s->current_time = mktime(tm); |
|
|
|
} |
|
|
|
|
|
|
|
static void rtc_update_second(void *opaque) |
|
|
|
{ |
|
|
|
RTCState *s = opaque; |
|
|
|
|
|
|
|
/* if the oscillator is not in normal operation, we do not update */ |
|
|
|
if ((s->cmos_data[RTC_REG_A] & 0x70) != 0x20) { |
|
|
|
s->next_second_time += ticks_per_sec; |
|
|
|
qemu_mod_timer(s->second_timer, s->next_second_time); |
|
|
|
} else { |
|
|
|
s->current_time++; |
|
|
|
|
|
|
|
if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { |
|
|
|
/* update in progress bit */ |
|
|
|
s->cmos_data[RTC_REG_A] |= REG_A_UIP; |
|
|
|
} |
|
|
|
qemu_mod_timer(s->second_timer2, |
|
|
|
s->next_second_time + (ticks_per_sec * 99) / 100); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void rtc_update_second2(void *opaque) |
|
|
|
{ |
|
|
|
RTCState *s = opaque; |
|
|
|
time_t ti; |
|
|
|
|
|
|
|
ti = time(NULL); |
|
|
|
tm = gmtime(&ti); |
|
|
|
s->cmos_data[RTC_SECONDS] = to_bcd(tm->tm_sec); |
|
|
|
s->cmos_data[RTC_MINUTES] = to_bcd(tm->tm_min); |
|
|
|
s->cmos_data[RTC_HOURS] = to_bcd(tm->tm_hour); |
|
|
|
s->cmos_data[RTC_DAY_OF_WEEK] = to_bcd(tm->tm_wday); |
|
|
|
s->cmos_data[RTC_DAY_OF_MONTH] = to_bcd(tm->tm_mday); |
|
|
|
s->cmos_data[RTC_MONTH] = to_bcd(tm->tm_mon + 1); |
|
|
|
s->cmos_data[RTC_YEAR] = to_bcd(tm->tm_year % 100); |
|
|
|
s->cmos_data[REG_IBM_CENTURY_BYTE] = to_bcd((tm->tm_year / 100) + 19); |
|
|
|
s->cmos_data[REG_IBM_PS2_CENTURY_BYTE] = s->cmos_data[REG_IBM_CENTURY_BYTE]; |
|
|
|
ti = s->current_time; |
|
|
|
rtc_set_date_buf(s, gmtime(&ti)); |
|
|
|
|
|
|
|
if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { |
|
|
|
rtc_copy_date(s); |
|
|
|
} |
|
|
|
|
|
|
|
/* check alarm */ |
|
|
|
if (s->cmos_data[RTC_REG_B] & REG_B_AIE) { |
|
|
|
if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 || |
|
|
|
s->cmos_data[RTC_SECONDS_ALARM] == s->buf_data[RTC_SECONDS]) && |
|
|
|
((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 || |
|
|
|
s->cmos_data[RTC_MINUTES_ALARM] == s->buf_data[RTC_MINUTES]) && |
|
|
|
((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 || |
|
|
|
s->cmos_data[RTC_HOURS_ALARM] == s->buf_data[RTC_HOURS])) { |
|
|
|
|
|
|
|
s->cmos_data[RTC_REG_C] |= 0xa0; |
|
|
|
pic_set_irq(s->irq, 1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* update ended interrupt */ |
|
|
|
if (s->cmos_data[RTC_REG_B] & REG_B_UIE) { |
|
|
|
s->cmos_data[RTC_REG_C] |= 0x90; |
|
|
|
pic_set_irq(s->irq, 1); |
|
|
|
} |
|
|
|
|
|
|
|
/* clear update in progress bit */ |
|
|
|
s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; |
|
|
|
|
|
|
|
s->next_second_time += ticks_per_sec; |
|
|
|
qemu_mod_timer(s->second_timer, s->next_second_time); |
|
|
|
} |
|
|
|
|
|
|
|
static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) |
|
|
|
@ -149,16 +297,10 @@ static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) |
|
|
|
case RTC_DAY_OF_MONTH: |
|
|
|
case RTC_MONTH: |
|
|
|
case RTC_YEAR: |
|
|
|
case REG_IBM_CENTURY_BYTE: |
|
|
|
case REG_IBM_PS2_CENTURY_BYTE: |
|
|
|
cmos_update_time(s); |
|
|
|
ret = s->cmos_data[s->cmos_index]; |
|
|
|
break; |
|
|
|
case RTC_REG_A: |
|
|
|
ret = s->cmos_data[s->cmos_index]; |
|
|
|
/* toggle update-in-progress bit for Linux (same hack as
|
|
|
|
plex86) */ |
|
|
|
s->cmos_data[RTC_REG_A] ^= 0x80; |
|
|
|
break; |
|
|
|
case RTC_REG_C: |
|
|
|
ret = s->cmos_data[s->cmos_index]; |
|
|
|
@ -177,19 +319,94 @@ static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void rtc_timer(void) |
|
|
|
static void rtc_set_date_buf(RTCState *s, const struct tm *tm) |
|
|
|
{ |
|
|
|
RTCState *s = &rtc_state; |
|
|
|
if (s->cmos_data[RTC_REG_B] & 0x50) { |
|
|
|
pic_set_irq(s->irq, 1); |
|
|
|
s->buf_data[RTC_SECONDS] = to_bcd(s, tm->tm_sec); |
|
|
|
s->buf_data[RTC_MINUTES] = to_bcd(s, tm->tm_min); |
|
|
|
if (s->cmos_data[RTC_REG_B] & 0x02) { |
|
|
|
/* 24 hour format */ |
|
|
|
s->buf_data[RTC_HOURS] = to_bcd(s, tm->tm_hour); |
|
|
|
} else { |
|
|
|
/* 12 hour format */ |
|
|
|
s->buf_data[RTC_HOURS] = to_bcd(s, tm->tm_hour % 12); |
|
|
|
if (tm->tm_hour >= 12) |
|
|
|
s->buf_data[RTC_HOURS] |= 0x80; |
|
|
|
} |
|
|
|
s->buf_data[RTC_DAY_OF_WEEK] = to_bcd(s, tm->tm_wday); |
|
|
|
s->buf_data[RTC_DAY_OF_MONTH] = to_bcd(s, tm->tm_mday); |
|
|
|
s->buf_data[RTC_MONTH] = to_bcd(s, tm->tm_mon + 1); |
|
|
|
s->buf_data[RTC_YEAR] = to_bcd(s, tm->tm_year % 100); |
|
|
|
} |
|
|
|
|
|
|
|
static void rtc_copy_date(RTCState *s) |
|
|
|
{ |
|
|
|
s->cmos_data[RTC_SECONDS] = s->buf_data[RTC_SECONDS]; |
|
|
|
s->cmos_data[RTC_MINUTES] = s->buf_data[RTC_MINUTES]; |
|
|
|
s->cmos_data[RTC_HOURS] = s->buf_data[RTC_HOURS]; |
|
|
|
s->cmos_data[RTC_DAY_OF_WEEK] = s->buf_data[RTC_DAY_OF_WEEK]; |
|
|
|
s->cmos_data[RTC_DAY_OF_MONTH] = s->buf_data[RTC_DAY_OF_MONTH]; |
|
|
|
s->cmos_data[RTC_MONTH] = s->buf_data[RTC_MONTH]; |
|
|
|
s->cmos_data[RTC_YEAR] = s->buf_data[RTC_YEAR]; |
|
|
|
} |
|
|
|
|
|
|
|
void rtc_set_memory(RTCState *s, int addr, int val) |
|
|
|
{ |
|
|
|
if (addr >= 0 && addr <= 127) |
|
|
|
s->cmos_data[addr] = val; |
|
|
|
} |
|
|
|
|
|
|
|
void rtc_set_date(RTCState *s, const struct tm *tm) |
|
|
|
{ |
|
|
|
s->current_time = mktime((struct tm *)tm); |
|
|
|
rtc_set_date_buf(s, tm); |
|
|
|
rtc_copy_date(s); |
|
|
|
} |
|
|
|
|
|
|
|
static void rtc_save(QEMUFile *f, void *opaque) |
|
|
|
{ |
|
|
|
RTCState *s = opaque; |
|
|
|
|
|
|
|
qemu_put_buffer(f, s->cmos_data, 128); |
|
|
|
qemu_put_8s(f, &s->cmos_index); |
|
|
|
qemu_put_be32s(f, &s->current_time); |
|
|
|
qemu_put_buffer(f, s->buf_data, 10); |
|
|
|
|
|
|
|
qemu_put_timer(f, s->periodic_timer); |
|
|
|
qemu_put_be64s(f, &s->next_periodic_time); |
|
|
|
|
|
|
|
qemu_put_be64s(f, &s->next_second_time); |
|
|
|
qemu_put_timer(f, s->second_timer); |
|
|
|
qemu_put_timer(f, s->second_timer2); |
|
|
|
} |
|
|
|
|
|
|
|
void rtc_init(int base, int irq) |
|
|
|
static int rtc_load(QEMUFile *f, void *opaque, int version_id) |
|
|
|
{ |
|
|
|
RTCState *s = &rtc_state; |
|
|
|
RTCState *s = opaque; |
|
|
|
|
|
|
|
if (version_id != 1) |
|
|
|
return -EINVAL; |
|
|
|
|
|
|
|
cmos_update_time(s); |
|
|
|
qemu_get_buffer(f, s->cmos_data, 128); |
|
|
|
qemu_get_8s(f, &s->cmos_index); |
|
|
|
qemu_get_be32s(f, &s->current_time); |
|
|
|
qemu_get_buffer(f, s->buf_data, 10); |
|
|
|
|
|
|
|
qemu_get_timer(f, s->periodic_timer); |
|
|
|
qemu_get_be64s(f, &s->next_periodic_time); |
|
|
|
|
|
|
|
qemu_get_be64s(f, &s->next_second_time); |
|
|
|
qemu_get_timer(f, s->second_timer); |
|
|
|
qemu_get_timer(f, s->second_timer2); |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
RTCState *rtc_init(int base, int irq) |
|
|
|
{ |
|
|
|
RTCState *s; |
|
|
|
|
|
|
|
s = qemu_mallocz(sizeof(RTCState)); |
|
|
|
if (!s) |
|
|
|
return NULL; |
|
|
|
|
|
|
|
s->irq = irq; |
|
|
|
s->cmos_data[RTC_REG_A] = 0x26; |
|
|
|
@ -197,7 +414,20 @@ void rtc_init(int base, int irq) |
|
|
|
s->cmos_data[RTC_REG_C] = 0x00; |
|
|
|
s->cmos_data[RTC_REG_D] = 0x80; |
|
|
|
|
|
|
|
s->periodic_timer = qemu_new_timer(vm_clock, |
|
|
|
rtc_periodic_timer, s); |
|
|
|
s->second_timer = qemu_new_timer(vm_clock, |
|
|
|
rtc_update_second, s); |
|
|
|
s->second_timer2 = qemu_new_timer(vm_clock, |
|
|
|
rtc_update_second2, s); |
|
|
|
|
|
|
|
s->next_second_time = qemu_get_clock(vm_clock) + (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); |
|
|
|
|
|
|
|
register_savevm("mc146818rtc", base, 1, rtc_save, rtc_load, s); |
|
|
|
return s; |
|
|
|
} |
|
|
|
|
|
|
|
|