Skip to content

Commit dff38e7

Browse files
author
bellard
committed
more precise RTC emulation (periodic timers + time updates)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@688 c046a42c-6fe2-441c-8c8c-71466251a162
1 parent 1f1af9f commit dff38e7

File tree

1 file changed

+264
-34
lines changed

1 file changed

+264
-34
lines changed

hw/mc146818rtc.c

Lines changed: 264 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,62 @@
6363
#define RTC_REG_C 12
6464
#define RTC_REG_D 13
6565

66-
/* PC cmos mappings */
67-
#define REG_IBM_CENTURY_BYTE 0x32
68-
#define REG_IBM_PS2_CENTURY_BYTE 0x37
66+
#define REG_A_UIP 0x80
6967

70-
RTCState rtc_state;
68+
#define REG_B_SET 0x80
69+
#define REG_B_PIE 0x40
70+
#define REG_B_AIE 0x20
71+
#define REG_B_UIE 0x10
72+
73+
struct RTCState {
74+
uint8_t cmos_data[128];
75+
uint8_t cmos_index;
76+
int current_time; /* in seconds */
77+
int irq;
78+
uint8_t buf_data[10]; /* buffered data */
79+
/* periodic timer */
80+
QEMUTimer *periodic_timer;
81+
int64_t next_periodic_time;
82+
/* second update */
83+
int64_t next_second_time;
84+
QEMUTimer *second_timer;
85+
QEMUTimer *second_timer2;
86+
};
87+
88+
static void rtc_set_time(RTCState *s);
89+
static void rtc_set_date_buf(RTCState *s, const struct tm *tm);
90+
static void rtc_copy_date(RTCState *s);
91+
92+
static void rtc_timer_update(RTCState *s, int64_t current_time)
93+
{
94+
int period_code, period;
95+
int64_t cur_clock, next_irq_clock;
96+
97+
period_code = s->cmos_data[RTC_REG_A] & 0x0f;
98+
if (period_code != 0 &&
99+
(s->cmos_data[RTC_REG_B] & REG_B_PIE)) {
100+
if (period_code <= 2)
101+
period_code += 7;
102+
/* period in 32 Khz cycles */
103+
period = 1 << (period_code - 1);
104+
/* compute 32 khz clock */
105+
cur_clock = muldiv64(current_time, 32768, ticks_per_sec);
106+
next_irq_clock = (cur_clock & ~(period - 1)) + period;
107+
s->next_periodic_time = muldiv64(next_irq_clock, ticks_per_sec, 32768) + 1;
108+
qemu_mod_timer(s->periodic_timer, s->next_periodic_time);
109+
} else {
110+
qemu_del_timer(s->periodic_timer);
111+
}
112+
}
113+
114+
static void rtc_periodic_timer(void *opaque)
115+
{
116+
RTCState *s = opaque;
117+
118+
rtc_timer_update(s, s->next_periodic_time);
119+
s->cmos_data[RTC_REG_C] |= 0xc0;
120+
pic_set_irq(s->irq, 1);
121+
}
71122

72123
static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
73124
{
@@ -80,7 +131,7 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
80131
printf("cmos: write index=0x%02x val=0x%02x\n",
81132
s->cmos_index, data);
82133
#endif
83-
switch(addr) {
134+
switch(s->cmos_index) {
84135
case RTC_SECONDS_ALARM:
85136
case RTC_MINUTES_ALARM:
86137
case RTC_HOURS_ALARM:
@@ -95,10 +146,30 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
95146
case RTC_MONTH:
96147
case RTC_YEAR:
97148
s->cmos_data[s->cmos_index] = data;
149+
/* if in set mode, do not update the time */
150+
if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
151+
rtc_set_time(s);
152+
}
98153
break;
99154
case RTC_REG_A:
155+
/* UIP bit is read only */
156+
s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) |
157+
(s->cmos_data[RTC_REG_A] & REG_A_UIP);
158+
rtc_timer_update(s, qemu_get_clock(vm_clock));
159+
break;
100160
case RTC_REG_B:
101-
s->cmos_data[s->cmos_index] = data;
161+
if (data & REG_B_SET) {
162+
/* set mode: reset UIP mode */
163+
s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
164+
data &= ~REG_B_UIE;
165+
} else {
166+
/* if disabling set mode, update the time */
167+
if (s->cmos_data[RTC_REG_B] & REG_B_SET) {
168+
rtc_set_time(s);
169+
}
170+
}
171+
s->cmos_data[RTC_REG_B] = data;
172+
rtc_timer_update(s, qemu_get_clock(vm_clock));
102173
break;
103174
case RTC_REG_C:
104175
case RTC_REG_D:
@@ -111,27 +182,104 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
111182
}
112183
}
113184

114-
static inline int to_bcd(int a)
185+
static inline int to_bcd(RTCState *s, int a)
115186
{
116-
return ((a / 10) << 4) | (a % 10);
187+
if (s->cmos_data[RTC_REG_B] & 0x04) {
188+
return a;
189+
} else {
190+
return ((a / 10) << 4) | (a % 10);
191+
}
117192
}
118193

119-
static void cmos_update_time(RTCState *s)
194+
static inline int from_bcd(RTCState *s, int a)
120195
{
121-
struct tm *tm;
196+
if (s->cmos_data[RTC_REG_B] & 0x04) {
197+
return a;
198+
} else {
199+
return ((a >> 4) * 10) + (a & 0x0f);
200+
}
201+
}
202+
203+
static void rtc_set_time(RTCState *s)
204+
{
205+
struct tm tm1, *tm = &tm1;
206+
207+
tm->tm_sec = from_bcd(s, s->cmos_data[RTC_SECONDS]);
208+
tm->tm_min = from_bcd(s, s->cmos_data[RTC_MINUTES]);
209+
tm->tm_hour = from_bcd(s, s->cmos_data[RTC_HOURS]);
210+
tm->tm_wday = from_bcd(s, s->cmos_data[RTC_DAY_OF_WEEK]);
211+
tm->tm_mday = from_bcd(s, s->cmos_data[RTC_DAY_OF_MONTH]);
212+
tm->tm_mon = from_bcd(s, s->cmos_data[RTC_MONTH]) - 1;
213+
tm->tm_year = from_bcd(s, s->cmos_data[RTC_YEAR]) + 100;
214+
215+
/* update internal state */
216+
s->buf_data[RTC_SECONDS] = s->cmos_data[RTC_SECONDS];
217+
s->buf_data[RTC_MINUTES] = s->cmos_data[RTC_MINUTES];
218+
s->buf_data[RTC_HOURS] = s->cmos_data[RTC_HOURS];
219+
s->buf_data[RTC_DAY_OF_WEEK] = s->cmos_data[RTC_DAY_OF_WEEK];
220+
s->buf_data[RTC_DAY_OF_MONTH] = s->cmos_data[RTC_DAY_OF_MONTH];
221+
s->buf_data[RTC_MONTH] = s->cmos_data[RTC_MONTH];
222+
s->buf_data[RTC_YEAR] = s->cmos_data[RTC_YEAR];
223+
s->current_time = mktime(tm);
224+
}
225+
226+
static void rtc_update_second(void *opaque)
227+
{
228+
RTCState *s = opaque;
229+
230+
/* if the oscillator is not in normal operation, we do not update */
231+
if ((s->cmos_data[RTC_REG_A] & 0x70) != 0x20) {
232+
s->next_second_time += ticks_per_sec;
233+
qemu_mod_timer(s->second_timer, s->next_second_time);
234+
} else {
235+
s->current_time++;
236+
237+
if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
238+
/* update in progress bit */
239+
s->cmos_data[RTC_REG_A] |= REG_A_UIP;
240+
}
241+
qemu_mod_timer(s->second_timer2,
242+
s->next_second_time + (ticks_per_sec * 99) / 100);
243+
}
244+
}
245+
246+
static void rtc_update_second2(void *opaque)
247+
{
248+
RTCState *s = opaque;
122249
time_t ti;
123250

124-
ti = time(NULL);
125-
tm = gmtime(&ti);
126-
s->cmos_data[RTC_SECONDS] = to_bcd(tm->tm_sec);
127-
s->cmos_data[RTC_MINUTES] = to_bcd(tm->tm_min);
128-
s->cmos_data[RTC_HOURS] = to_bcd(tm->tm_hour);
129-
s->cmos_data[RTC_DAY_OF_WEEK] = to_bcd(tm->tm_wday);
130-
s->cmos_data[RTC_DAY_OF_MONTH] = to_bcd(tm->tm_mday);
131-
s->cmos_data[RTC_MONTH] = to_bcd(tm->tm_mon + 1);
132-
s->cmos_data[RTC_YEAR] = to_bcd(tm->tm_year % 100);
133-
s->cmos_data[REG_IBM_CENTURY_BYTE] = to_bcd((tm->tm_year / 100) + 19);
134-
s->cmos_data[REG_IBM_PS2_CENTURY_BYTE] = s->cmos_data[REG_IBM_CENTURY_BYTE];
251+
ti = s->current_time;
252+
rtc_set_date_buf(s, gmtime(&ti));
253+
254+
if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
255+
rtc_copy_date(s);
256+
}
257+
258+
/* check alarm */
259+
if (s->cmos_data[RTC_REG_B] & REG_B_AIE) {
260+
if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 ||
261+
s->cmos_data[RTC_SECONDS_ALARM] == s->buf_data[RTC_SECONDS]) &&
262+
((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 ||
263+
s->cmos_data[RTC_MINUTES_ALARM] == s->buf_data[RTC_MINUTES]) &&
264+
((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 ||
265+
s->cmos_data[RTC_HOURS_ALARM] == s->buf_data[RTC_HOURS])) {
266+
267+
s->cmos_data[RTC_REG_C] |= 0xa0;
268+
pic_set_irq(s->irq, 1);
269+
}
270+
}
271+
272+
/* update ended interrupt */
273+
if (s->cmos_data[RTC_REG_B] & REG_B_UIE) {
274+
s->cmos_data[RTC_REG_C] |= 0x90;
275+
pic_set_irq(s->irq, 1);
276+
}
277+
278+
/* clear update in progress bit */
279+
s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
280+
281+
s->next_second_time += ticks_per_sec;
282+
qemu_mod_timer(s->second_timer, s->next_second_time);
135283
}
136284

137285
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)
149297
case RTC_DAY_OF_MONTH:
150298
case RTC_MONTH:
151299
case RTC_YEAR:
152-
case REG_IBM_CENTURY_BYTE:
153-
case REG_IBM_PS2_CENTURY_BYTE:
154-
cmos_update_time(s);
155300
ret = s->cmos_data[s->cmos_index];
156301
break;
157302
case RTC_REG_A:
158303
ret = s->cmos_data[s->cmos_index];
159-
/* toggle update-in-progress bit for Linux (same hack as
160-
plex86) */
161-
s->cmos_data[RTC_REG_A] ^= 0x80;
162304
break;
163305
case RTC_REG_C:
164306
ret = s->cmos_data[s->cmos_index];
@@ -177,27 +319,115 @@ static uint32_t cmos_ioport_read(void *opaque, uint32_t addr)
177319
}
178320
}
179321

180-
void rtc_timer(void)
322+
static void rtc_set_date_buf(RTCState *s, const struct tm *tm)
181323
{
182-
RTCState *s = &rtc_state;
183-
if (s->cmos_data[RTC_REG_B] & 0x50) {
184-
pic_set_irq(s->irq, 1);
324+
s->buf_data[RTC_SECONDS] = to_bcd(s, tm->tm_sec);
325+
s->buf_data[RTC_MINUTES] = to_bcd(s, tm->tm_min);
326+
if (s->cmos_data[RTC_REG_B] & 0x02) {
327+
/* 24 hour format */
328+
s->buf_data[RTC_HOURS] = to_bcd(s, tm->tm_hour);
329+
} else {
330+
/* 12 hour format */
331+
s->buf_data[RTC_HOURS] = to_bcd(s, tm->tm_hour % 12);
332+
if (tm->tm_hour >= 12)
333+
s->buf_data[RTC_HOURS] |= 0x80;
185334
}
335+
s->buf_data[RTC_DAY_OF_WEEK] = to_bcd(s, tm->tm_wday);
336+
s->buf_data[RTC_DAY_OF_MONTH] = to_bcd(s, tm->tm_mday);
337+
s->buf_data[RTC_MONTH] = to_bcd(s, tm->tm_mon + 1);
338+
s->buf_data[RTC_YEAR] = to_bcd(s, tm->tm_year % 100);
339+
}
340+
341+
static void rtc_copy_date(RTCState *s)
342+
{
343+
s->cmos_data[RTC_SECONDS] = s->buf_data[RTC_SECONDS];
344+
s->cmos_data[RTC_MINUTES] = s->buf_data[RTC_MINUTES];
345+
s->cmos_data[RTC_HOURS] = s->buf_data[RTC_HOURS];
346+
s->cmos_data[RTC_DAY_OF_WEEK] = s->buf_data[RTC_DAY_OF_WEEK];
347+
s->cmos_data[RTC_DAY_OF_MONTH] = s->buf_data[RTC_DAY_OF_MONTH];
348+
s->cmos_data[RTC_MONTH] = s->buf_data[RTC_MONTH];
349+
s->cmos_data[RTC_YEAR] = s->buf_data[RTC_YEAR];
350+
}
351+
352+
void rtc_set_memory(RTCState *s, int addr, int val)
353+
{
354+
if (addr >= 0 && addr <= 127)
355+
s->cmos_data[addr] = val;
356+
}
357+
358+
void rtc_set_date(RTCState *s, const struct tm *tm)
359+
{
360+
s->current_time = mktime((struct tm *)tm);
361+
rtc_set_date_buf(s, tm);
362+
rtc_copy_date(s);
363+
}
364+
365+
static void rtc_save(QEMUFile *f, void *opaque)
366+
{
367+
RTCState *s = opaque;
368+
369+
qemu_put_buffer(f, s->cmos_data, 128);
370+
qemu_put_8s(f, &s->cmos_index);
371+
qemu_put_be32s(f, &s->current_time);
372+
qemu_put_buffer(f, s->buf_data, 10);
373+
374+
qemu_put_timer(f, s->periodic_timer);
375+
qemu_put_be64s(f, &s->next_periodic_time);
376+
377+
qemu_put_be64s(f, &s->next_second_time);
378+
qemu_put_timer(f, s->second_timer);
379+
qemu_put_timer(f, s->second_timer2);
186380
}
187381

188-
void rtc_init(int base, int irq)
382+
static int rtc_load(QEMUFile *f, void *opaque, int version_id)
189383
{
190-
RTCState *s = &rtc_state;
384+
RTCState *s = opaque;
385+
386+
if (version_id != 1)
387+
return -EINVAL;
191388

192-
cmos_update_time(s);
389+
qemu_get_buffer(f, s->cmos_data, 128);
390+
qemu_get_8s(f, &s->cmos_index);
391+
qemu_get_be32s(f, &s->current_time);
392+
qemu_get_buffer(f, s->buf_data, 10);
393+
394+
qemu_get_timer(f, s->periodic_timer);
395+
qemu_get_be64s(f, &s->next_periodic_time);
396+
397+
qemu_get_be64s(f, &s->next_second_time);
398+
qemu_get_timer(f, s->second_timer);
399+
qemu_get_timer(f, s->second_timer2);
400+
return 0;
401+
}
402+
403+
RTCState *rtc_init(int base, int irq)
404+
{
405+
RTCState *s;
406+
407+
s = qemu_mallocz(sizeof(RTCState));
408+
if (!s)
409+
return NULL;
193410

194411
s->irq = irq;
195412
s->cmos_data[RTC_REG_A] = 0x26;
196413
s->cmos_data[RTC_REG_B] = 0x02;
197414
s->cmos_data[RTC_REG_C] = 0x00;
198415
s->cmos_data[RTC_REG_D] = 0x80;
199416

417+
s->periodic_timer = qemu_new_timer(vm_clock,
418+
rtc_periodic_timer, s);
419+
s->second_timer = qemu_new_timer(vm_clock,
420+
rtc_update_second, s);
421+
s->second_timer2 = qemu_new_timer(vm_clock,
422+
rtc_update_second2, s);
423+
424+
s->next_second_time = qemu_get_clock(vm_clock) + (ticks_per_sec * 99) / 100;
425+
qemu_mod_timer(s->second_timer2, s->next_second_time);
426+
200427
register_ioport_write(base, 2, 1, cmos_ioport_write, s);
201428
register_ioport_read(base, 2, 1, cmos_ioport_read, s);
429+
430+
register_savevm("mc146818rtc", base, 1, rtc_save, rtc_load, s);
431+
return s;
202432
}
203433

0 commit comments

Comments
 (0)