Revision dff38e7b
b/hw/mc146818rtc.c | ||
---|---|---|
63 | 63 |
#define RTC_REG_C 12 |
64 | 64 |
#define RTC_REG_D 13 |
65 | 65 |
|
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 |
|
69 | 67 |
|
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 |
} |
|
71 | 122 |
|
72 | 123 |
static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) |
73 | 124 |
{ |
... | ... | |
80 | 131 |
printf("cmos: write index=0x%02x val=0x%02x\n", |
81 | 132 |
s->cmos_index, data); |
82 | 133 |
#endif |
83 |
switch(addr) {
|
|
134 |
switch(s->cmos_index) {
|
|
84 | 135 |
case RTC_SECONDS_ALARM: |
85 | 136 |
case RTC_MINUTES_ALARM: |
86 | 137 |
case RTC_HOURS_ALARM: |
... | ... | |
95 | 146 |
case RTC_MONTH: |
96 | 147 |
case RTC_YEAR: |
97 | 148 |
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 |
} |
|
98 | 153 |
break; |
99 | 154 |
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; |
|
100 | 160 |
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)); |
|
102 | 173 |
break; |
103 | 174 |
case RTC_REG_C: |
104 | 175 |
case RTC_REG_D: |
... | ... | |
111 | 182 |
} |
112 | 183 |
} |
113 | 184 |
|
114 |
static inline int to_bcd(int a) |
|
185 |
static inline int to_bcd(RTCState *s, int a)
|
|
115 | 186 |
{ |
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 |
} |
|
117 | 192 |
} |
118 | 193 |
|
119 |
static void cmos_update_time(RTCState *s)
|
|
194 |
static inline int from_bcd(RTCState *s, int a)
|
|
120 | 195 |
{ |
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; |
|
122 | 249 |
time_t ti; |
123 | 250 |
|
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); |
|
135 | 283 |
} |
136 | 284 |
|
137 | 285 |
static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) |
... | ... | |
149 | 297 |
case RTC_DAY_OF_MONTH: |
150 | 298 |
case RTC_MONTH: |
151 | 299 |
case RTC_YEAR: |
152 |
case REG_IBM_CENTURY_BYTE: |
|
153 |
case REG_IBM_PS2_CENTURY_BYTE: |
|
154 |
cmos_update_time(s); |
|
155 | 300 |
ret = s->cmos_data[s->cmos_index]; |
156 | 301 |
break; |
157 | 302 |
case RTC_REG_A: |
158 | 303 |
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; |
|
162 | 304 |
break; |
163 | 305 |
case RTC_REG_C: |
164 | 306 |
ret = s->cmos_data[s->cmos_index]; |
... | ... | |
177 | 319 |
} |
178 | 320 |
} |
179 | 321 |
|
180 |
void rtc_timer(void)
|
|
322 |
static void rtc_set_date_buf(RTCState *s, const struct tm *tm)
|
|
181 | 323 |
{ |
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; |
|
185 | 334 |
} |
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); |
|
186 | 380 |
} |
187 | 381 |
|
188 |
void rtc_init(int base, int irq)
|
|
382 |
static int rtc_load(QEMUFile *f, void *opaque, int version_id)
|
|
189 | 383 |
{ |
190 |
RTCState *s = &rtc_state; |
|
384 |
RTCState *s = opaque; |
|
385 |
|
|
386 |
if (version_id != 1) |
|
387 |
return -EINVAL; |
|
191 | 388 |
|
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; |
|
193 | 410 |
|
194 | 411 |
s->irq = irq; |
195 | 412 |
s->cmos_data[RTC_REG_A] = 0x26; |
... | ... | |
197 | 414 |
s->cmos_data[RTC_REG_C] = 0x00; |
198 | 415 |
s->cmos_data[RTC_REG_D] = 0x80; |
199 | 416 |
|
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 |
|
|
200 | 427 |
register_ioport_write(base, 2, 1, cmos_ioport_write, s); |
201 | 428 |
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; |
|
202 | 432 |
} |
203 | 433 |
|
Also available in: Unified diff