VirtualBox

source: vbox/trunk/src/VBox/Devices/PC/DevRTC.cpp@ 28345

Last change on this file since 28345 was 27976, checked in by vboxsync, 15 years ago

*: scm cleans up whitespace and adds a new line at the end of ApplianceimplPrivate.h.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 35.4 KB
Line 
1/* $Id: DevRTC.cpp 27976 2010-04-04 14:16:32Z vboxsync $ */
2/** @file
3 * Motorola MC146818 RTC/CMOS Device with PIIX4 extensions.
4 */
5
6/*
7 * Copyright (C) 2006-2007 Sun Microsystems, Inc.
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
20 * --------------------------------------------------------------------
21 *
22 * This code is based on:
23 *
24 * QEMU MC146818 RTC emulation
25 *
26 * Copyright (c) 2003-2004 Fabrice Bellard
27 *
28 * Permission is hereby granted, free of charge, to any person obtaining a copy
29 * of this software and associated documentation files (the "Software"), to deal
30 * in the Software without restriction, including without limitation the rights
31 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32 * copies of the Software, and to permit persons to whom the Software is
33 * furnished to do so, subject to the following conditions:
34 *
35 * The above copyright notice and this permission notice shall be included in
36 * all copies or substantial portions of the Software.
37 *
38 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
41 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
44 * THE SOFTWARE.
45 */
46
47/*******************************************************************************
48* Header Files *
49*******************************************************************************/
50#define LOG_GROUP LOG_GROUP_DEV_RTC
51#include <VBox/pdmdev.h>
52#include <VBox/log.h>
53#include <iprt/asm.h>
54#include <iprt/assert.h>
55#include <iprt/string.h>
56
57#ifdef IN_RING3
58# include <iprt/alloc.h>
59# include <iprt/uuid.h>
60#endif /* IN_RING3 */
61
62#include "../Builtins.h"
63
64struct RTCState;
65typedef struct RTCState RTCState;
66
67#define RTC_CRC_START 0x10
68#define RTC_CRC_LAST 0x2d
69#define RTC_CRC_HIGH 0x2e
70#define RTC_CRC_LOW 0x2f
71
72
73/*******************************************************************************
74* Internal Functions *
75*******************************************************************************/
76#ifndef VBOX_DEVICE_STRUCT_TESTCASE
77RT_C_DECLS_BEGIN
78PDMBOTHCBDECL(int) rtcIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb);
79PDMBOTHCBDECL(int) rtcIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
80PDMBOTHCBDECL(void) rtcTimerPeriodic(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser);
81PDMBOTHCBDECL(void) rtcTimerSecond(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser);
82PDMBOTHCBDECL(void) rtcTimerSecond2(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser);
83RT_C_DECLS_END
84#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
85
86
87/*******************************************************************************
88* Defined Constants And Macros *
89*******************************************************************************/
90/*#define DEBUG_CMOS*/
91
92#define RTC_SECONDS 0
93#define RTC_SECONDS_ALARM 1
94#define RTC_MINUTES 2
95#define RTC_MINUTES_ALARM 3
96#define RTC_HOURS 4
97#define RTC_HOURS_ALARM 5
98#define RTC_ALARM_DONT_CARE 0xC0
99
100#define RTC_DAY_OF_WEEK 6
101#define RTC_DAY_OF_MONTH 7
102#define RTC_MONTH 8
103#define RTC_YEAR 9
104
105#define RTC_REG_A 10
106#define RTC_REG_B 11
107#define RTC_REG_C 12
108#define RTC_REG_D 13
109
110#define REG_A_UIP 0x80
111
112#define REG_B_SET 0x80
113#define REG_B_PIE 0x40
114#define REG_B_AIE 0x20
115#define REG_B_UIE 0x10
116
117
118/** The saved state version. */
119#define RTC_SAVED_STATE_VERSION 4
120/** The saved state version used by VirtualBox pre-3.2.
121 * This does not include the second 128-byte bank. */
122#define RTC_SAVED_STATE_VERSION_VBOX_32PRE 3
123/** The saved state version used by VirtualBox 3.1 and earlier.
124 * This does not include disabled by HPET state. */
125#define RTC_SAVED_STATE_VERSION_VBOX_31 2
126/** The saved state version used by VirtualBox 3.0 and earlier.
127 * This does not include the configuration. */
128#define RTC_SAVED_STATE_VERSION_VBOX_30 1
129
130
131/*******************************************************************************
132* Structures and Typedefs *
133*******************************************************************************/
134/** @todo Replace struct my_tm with RTTIME. */
135struct my_tm
136{
137 int32_t tm_sec;
138 int32_t tm_min;
139 int32_t tm_hour;
140 int32_t tm_mday;
141 int32_t tm_mon;
142 int32_t tm_year;
143 int32_t tm_wday;
144 int32_t tm_yday;
145};
146
147
148struct RTCState {
149 uint8_t cmos_data[256];
150 uint8_t cmos_index[2];
151 uint8_t Alignment0[6];
152 struct my_tm current_tm;
153 /** The configured IRQ. */
154 int32_t irq;
155 /** The configured I/O port base. */
156 RTIOPORT IOPortBase;
157 /** Use UTC or local time initially. */
158 bool fUTC;
159 /** Disabled by HPET legacy mode. */
160 bool fDisabledByHpet;
161 /* periodic timer */
162 int64_t next_periodic_time;
163 /* second update */
164 int64_t next_second_time;
165
166 /** Pointer to the device instance - R3 Ptr. */
167 PPDMDEVINSR3 pDevInsR3;
168 /** The periodic timer (rtcTimerPeriodic) - R3 Ptr. */
169 PTMTIMERR3 pPeriodicTimerR3;
170 /** The second timer (rtcTimerSecond) - R3 Ptr. */
171 PTMTIMERR3 pSecondTimerR3;
172 /** The second second timer (rtcTimerSecond2) - R3 Ptr. */
173 PTMTIMERR3 pSecondTimer2R3;
174
175 /** Pointer to the device instance - R0 Ptr. */
176 PPDMDEVINSR0 pDevInsR0;
177 /** The periodic timer (rtcTimerPeriodic) - R0 Ptr. */
178 PTMTIMERR0 pPeriodicTimerR0;
179 /** The second timer (rtcTimerSecond) - R0 Ptr. */
180 PTMTIMERR0 pSecondTimerR0;
181 /** The second second timer (rtcTimerSecond2) - R0 Ptr. */
182 PTMTIMERR0 pSecondTimer2R0;
183
184 /** Pointer to the device instance - RC Ptr. */
185 PPDMDEVINSRC pDevInsRC;
186 /** The periodic timer (rtcTimerPeriodic) - RC Ptr. */
187 PTMTIMERRC pPeriodicTimerRC;
188 /** The second timer (rtcTimerSecond) - RC Ptr. */
189 PTMTIMERRC pSecondTimerRC;
190 /** The second second timer (rtcTimerSecond2) - RC Ptr. */
191 PTMTIMERRC pSecondTimer2RC;
192
193 /** The RTC registration structure. */
194 PDMRTCREG RtcReg;
195 /** The RTC device helpers. */
196 R3PTRTYPE(PCPDMRTCHLP) pRtcHlpR3;
197 /** Number of release log entries. Used to prevent flooding. */
198 uint32_t cRelLogEntries;
199 /** The current/previous timer period. Used to prevent flooding changes. */
200 int32_t CurPeriod;
201
202 /** HPET legacy mode notification interface. */
203 PDMIHPETLEGACYNOTIFY IHpetLegacyNotify;
204};
205
206#ifndef VBOX_DEVICE_STRUCT_TESTCASE
207static void rtc_set_time(RTCState *s);
208static void rtc_copy_date(RTCState *s);
209
210static void rtc_timer_update(RTCState *s, int64_t current_time)
211{
212 int period_code, period;
213 uint64_t cur_clock, next_irq_clock;
214 uint32_t freq;
215
216 period_code = s->cmos_data[RTC_REG_A] & 0x0f;
217 if (period_code != 0 &&
218 (s->cmos_data[RTC_REG_B] & REG_B_PIE)) {
219 if (period_code <= 2)
220 period_code += 7;
221 /* period in 32 kHz cycles */
222 period = 1 << (period_code - 1);
223 /* compute 32 kHz clock */
224 freq = TMTimerGetFreq(s->CTX_SUFF(pPeriodicTimer));
225
226 cur_clock = ASMMultU64ByU32DivByU32(current_time, 32768, freq);
227 next_irq_clock = (cur_clock & ~(uint64_t)(period - 1)) + period;
228 s->next_periodic_time = ASMMultU64ByU32DivByU32(next_irq_clock, freq, 32768) + 1;
229 TMTimerSet(s->CTX_SUFF(pPeriodicTimer), s->next_periodic_time);
230
231 if (period != s->CurPeriod)
232 {
233 if (s->cRelLogEntries++ < 64)
234 LogRel(("RTC: period=%#x (%d) %u Hz\n", period, period, _32K / period));
235 s->CurPeriod = period;
236 }
237 } else {
238 if (TMTimerIsActive(s->CTX_SUFF(pPeriodicTimer)) && s->cRelLogEntries++ < 64)
239 LogRel(("RTC: stopped the periodic timer\n"));
240 TMTimerStop(s->CTX_SUFF(pPeriodicTimer));
241 }
242}
243
244static void rtc_raise_irq(RTCState* pThis, uint32_t iLevel)
245{
246 if (!pThis->fDisabledByHpet)
247 PDMDevHlpISASetIrq(pThis->CTX_SUFF(pDevIns), pThis->irq, iLevel);
248}
249
250static void rtc_periodic_timer(void *opaque)
251{
252 RTCState *s = (RTCState*)opaque;
253
254 rtc_timer_update(s, s->next_periodic_time);
255 s->cmos_data[RTC_REG_C] |= 0xc0;
256
257 rtc_raise_irq(s, 1);
258}
259
260static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
261{
262 RTCState *s = (RTCState*)opaque;
263 uint32_t bank;
264
265 bank = (addr >> 1) & 1;
266 if ((addr & 1) == 0) {
267 s->cmos_index[bank] = (data & 0x7f) + (bank * 128);
268 } else {
269 Log(("CMOS: Write bank %d idx %#04x: %#04x (old %#04x)\n", bank,
270 s->cmos_index[bank], data, s->cmos_data[s->cmos_index[bank]]));
271 switch(s->cmos_index[bank]) {
272 case RTC_SECONDS_ALARM:
273 case RTC_MINUTES_ALARM:
274 case RTC_HOURS_ALARM:
275 s->cmos_data[s->cmos_index[0]] = data;
276 break;
277 case RTC_SECONDS:
278 case RTC_MINUTES:
279 case RTC_HOURS:
280 case RTC_DAY_OF_WEEK:
281 case RTC_DAY_OF_MONTH:
282 case RTC_MONTH:
283 case RTC_YEAR:
284 s->cmos_data[s->cmos_index[0]] = data;
285 /* if in set mode, do not update the time */
286 if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
287 rtc_set_time(s);
288 }
289 break;
290 case RTC_REG_A:
291 /* UIP bit is read only */
292 s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) |
293 (s->cmos_data[RTC_REG_A] & REG_A_UIP);
294 rtc_timer_update(s, TMTimerGet(s->CTX_SUFF(pPeriodicTimer)));
295 break;
296 case RTC_REG_B:
297 if (data & REG_B_SET) {
298 /* set mode: reset UIP mode */
299 s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
300#if 0 /* This is probably wrong as it breaks changing the time/date in OS/2. */
301 data &= ~REG_B_UIE;
302#endif
303 } else {
304 /* if disabling set mode, update the time */
305 if (s->cmos_data[RTC_REG_B] & REG_B_SET) {
306 rtc_set_time(s);
307 }
308 }
309 s->cmos_data[RTC_REG_B] = data;
310 rtc_timer_update(s, TMTimerGet(s->CTX_SUFF(pPeriodicTimer)));
311 break;
312 case RTC_REG_C:
313 case RTC_REG_D:
314 /* cannot write to them */
315 break;
316 default:
317 s->cmos_data[s->cmos_index[bank]] = data;
318 break;
319 }
320 }
321}
322
323static inline int to_bcd(RTCState *s, int a)
324{
325 if (s->cmos_data[RTC_REG_B] & 0x04) {
326 return a;
327 } else {
328 return ((a / 10) << 4) | (a % 10);
329 }
330}
331
332static inline int from_bcd(RTCState *s, int a)
333{
334 if (s->cmos_data[RTC_REG_B] & 0x04) {
335 return a;
336 } else {
337 return ((a >> 4) * 10) + (a & 0x0f);
338 }
339}
340
341static void rtc_set_time(RTCState *s)
342{
343 struct my_tm *tm = &s->current_tm;
344
345 tm->tm_sec = from_bcd(s, s->cmos_data[RTC_SECONDS]);
346 tm->tm_min = from_bcd(s, s->cmos_data[RTC_MINUTES]);
347 tm->tm_hour = from_bcd(s, s->cmos_data[RTC_HOURS] & 0x7f);
348 if (!(s->cmos_data[RTC_REG_B] & 0x02) &&
349 (s->cmos_data[RTC_HOURS] & 0x80)) {
350 tm->tm_hour += 12;
351 }
352 tm->tm_wday = from_bcd(s, s->cmos_data[RTC_DAY_OF_WEEK]);
353 tm->tm_mday = from_bcd(s, s->cmos_data[RTC_DAY_OF_MONTH]);
354 tm->tm_mon = from_bcd(s, s->cmos_data[RTC_MONTH]) - 1;
355 tm->tm_year = from_bcd(s, s->cmos_data[RTC_YEAR]) + 100;
356}
357
358static void rtc_copy_date(RTCState *s)
359{
360 const struct my_tm *tm = &s->current_tm;
361
362 s->cmos_data[RTC_SECONDS] = to_bcd(s, tm->tm_sec);
363 s->cmos_data[RTC_MINUTES] = to_bcd(s, tm->tm_min);
364 if (s->cmos_data[RTC_REG_B] & 0x02) {
365 /* 24 hour format */
366 s->cmos_data[RTC_HOURS] = to_bcd(s, tm->tm_hour);
367 } else {
368 /* 12 hour format */
369 s->cmos_data[RTC_HOURS] = to_bcd(s, tm->tm_hour % 12);
370 if (tm->tm_hour >= 12)
371 s->cmos_data[RTC_HOURS] |= 0x80;
372 }
373 s->cmos_data[RTC_DAY_OF_WEEK] = to_bcd(s, tm->tm_wday);
374 s->cmos_data[RTC_DAY_OF_MONTH] = to_bcd(s, tm->tm_mday);
375 s->cmos_data[RTC_MONTH] = to_bcd(s, tm->tm_mon + 1);
376 s->cmos_data[RTC_YEAR] = to_bcd(s, tm->tm_year % 100);
377}
378
379/* month is between 0 and 11. */
380static int get_days_in_month(int month, int year)
381{
382 static const int days_tab[12] = {
383 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
384 };
385 int d;
386 if ((unsigned )month >= 12)
387 return 31;
388 d = days_tab[month];
389 if (month == 1) {
390 if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0))
391 d++;
392 }
393 return d;
394}
395
396/* update 'tm' to the next second */
397static void rtc_next_second(struct my_tm *tm)
398{
399 int days_in_month;
400
401 tm->tm_sec++;
402 if ((unsigned)tm->tm_sec >= 60) {
403 tm->tm_sec = 0;
404 tm->tm_min++;
405 if ((unsigned)tm->tm_min >= 60) {
406 tm->tm_min = 0;
407 tm->tm_hour++;
408 if ((unsigned)tm->tm_hour >= 24) {
409 tm->tm_hour = 0;
410 /* next day */
411 tm->tm_wday++;
412 if ((unsigned)tm->tm_wday >= 7)
413 tm->tm_wday = 0;
414 days_in_month = get_days_in_month(tm->tm_mon,
415 tm->tm_year + 1900);
416 tm->tm_mday++;
417 if (tm->tm_mday < 1) {
418 tm->tm_mday = 1;
419 } else if (tm->tm_mday > days_in_month) {
420 tm->tm_mday = 1;
421 tm->tm_mon++;
422 if (tm->tm_mon >= 12) {
423 tm->tm_mon = 0;
424 tm->tm_year++;
425 }
426 }
427 }
428 }
429 }
430}
431
432
433static void rtc_update_second(void *opaque)
434{
435 RTCState *s = (RTCState*)opaque;
436
437 /* if the oscillator is not in normal operation, we do not update */
438 if ((s->cmos_data[RTC_REG_A] & 0x70) != 0x20) {
439 s->next_second_time += TMTimerGetFreq(s->CTX_SUFF(pSecondTimer));
440 TMTimerSet(s->CTX_SUFF(pSecondTimer), s->next_second_time);
441 } else {
442 rtc_next_second(&s->current_tm);
443
444 if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
445 /* update in progress bit */
446 Log2(("RTC: UIP %x -> 1\n", !!(s->cmos_data[RTC_REG_A] & REG_A_UIP)));
447 s->cmos_data[RTC_REG_A] |= REG_A_UIP;
448 }
449
450 /* 244140 ns = 8 / 32768 seconds */
451 uint64_t delay = TMTimerFromNano(s->CTX_SUFF(pSecondTimer2), 244140);
452 TMTimerSet(s->CTX_SUFF(pSecondTimer2), s->next_second_time + delay);
453 }
454}
455
456static void rtc_update_second2(void *opaque)
457{
458 RTCState *s = (RTCState*)opaque;
459
460 if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
461 rtc_copy_date(s);
462 }
463
464 /* check alarm */
465 if (s->cmos_data[RTC_REG_B] & REG_B_AIE) {
466 if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 ||
467 from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]) == s->current_tm.tm_sec) &&
468 ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 ||
469 from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]) == s->current_tm.tm_min) &&
470 ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 ||
471 from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]) == s->current_tm.tm_hour)) {
472
473 s->cmos_data[RTC_REG_C] |= 0xa0;
474 rtc_raise_irq(s, 1);
475 }
476 }
477
478 /* update ended interrupt */
479 if (s->cmos_data[RTC_REG_B] & REG_B_UIE) {
480 s->cmos_data[RTC_REG_C] |= 0x90;
481 rtc_raise_irq(s, 1);
482 }
483
484 /* clear update in progress bit */
485 Log2(("RTC: UIP %x -> 0\n", !!(s->cmos_data[RTC_REG_A] & REG_A_UIP)));
486 s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
487
488 s->next_second_time += TMTimerGetFreq(s->CTX_SUFF(pSecondTimer));
489 TMTimerSet(s->CTX_SUFF(pSecondTimer), s->next_second_time);
490}
491
492static uint32_t cmos_ioport_read(void *opaque, uint32_t addr)
493{
494 RTCState *s = (RTCState*)opaque;
495 int ret;
496 unsigned bank;
497
498 bank = (addr >> 1) & 1;
499 if ((addr & 1) == 0) {
500 return 0xff;
501 } else {
502 switch(s->cmos_index[bank]) {
503 case RTC_SECONDS:
504 case RTC_MINUTES:
505 case RTC_HOURS:
506 case RTC_DAY_OF_WEEK:
507 case RTC_DAY_OF_MONTH:
508 case RTC_MONTH:
509 case RTC_YEAR:
510 ret = s->cmos_data[s->cmos_index[0]];
511 break;
512 case RTC_REG_A:
513 ret = s->cmos_data[s->cmos_index[0]];
514 break;
515 case RTC_REG_C:
516 ret = s->cmos_data[s->cmos_index[0]];
517 rtc_raise_irq(s, 0);
518 s->cmos_data[RTC_REG_C] = 0x00;
519 break;
520 default:
521 ret = s->cmos_data[s->cmos_index[bank]];
522 break;
523 }
524 Log(("CMOS: Read bank %d idx %#04x: %#04x\n", bank, s->cmos_index[bank], ret));
525 return ret;
526 }
527}
528
529#ifdef IN_RING3
530static void rtc_set_memory(RTCState *s, int addr, int val)
531{
532 if (addr >= 0 && addr <= 127)
533 s->cmos_data[addr] = val;
534}
535
536static void rtc_set_date(RTCState *s, const struct my_tm *tm)
537{
538 s->current_tm = *tm;
539 rtc_copy_date(s);
540}
541
542#endif /* IN_RING3 */
543
544/* -=-=-=-=-=- wrappers / stuff -=-=-=-=-=- */
545
546/**
547 * Port I/O Handler for IN operations.
548 *
549 * @returns VBox status code.
550 *
551 * @param pDevIns The device instance.
552 * @param pvUser User argument - ignored.
553 * @param uPort Port number used for the IN operation.
554 * @param pu32 Where to store the result.
555 * @param cb Number of bytes read.
556 */
557PDMBOTHCBDECL(int) rtcIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
558{
559 NOREF(pvUser);
560 if (cb == 1)
561 {
562 *pu32 = cmos_ioport_read(PDMINS_2_DATA(pDevIns, RTCState *), Port);
563 return VINF_SUCCESS;
564 }
565 return VERR_IOM_IOPORT_UNUSED;
566}
567
568
569/**
570 * Port I/O Handler for OUT operations.
571 *
572 * @returns VBox status code.
573 *
574 * @param pDevIns The device instance.
575 * @param pvUser User argument - ignored.
576 * @param uPort Port number used for the IN operation.
577 * @param u32 The value to output.
578 * @param cb The value size in bytes.
579 */
580PDMBOTHCBDECL(int) rtcIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
581{
582 NOREF(pvUser);
583 if (cb == 1)
584 cmos_ioport_write(PDMINS_2_DATA(pDevIns, RTCState *), Port, u32);
585 return VINF_SUCCESS;
586}
587
588
589/**
590 * Device timer callback function, periodic.
591 *
592 * @param pDevIns Device instance of the device which registered the timer.
593 * @param pTimer The timer handle.
594 * @param pvUser Pointer to the RTC state.
595 */
596PDMBOTHCBDECL(void) rtcTimerPeriodic(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
597{
598 rtc_periodic_timer((RTCState *)pvUser);
599}
600
601
602/**
603 * Device timer callback function, second.
604 *
605 * @param pDevIns Device instance of the device which registered the timer.
606 * @param pTimer The timer handle.
607 * @param pvUser Pointer to the RTC state.
608 */
609PDMBOTHCBDECL(void) rtcTimerSecond(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
610{
611 rtc_update_second((RTCState *)pvUser);
612}
613
614
615/**
616 * Device timer callback function, second2.
617 *
618 * @param pDevIns Device instance of the device which registered the timer.
619 * @param pTimer The timer handle.
620 * @param pvUser Pointer to the RTC state.
621 */
622PDMBOTHCBDECL(void) rtcTimerSecond2(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
623{
624 rtc_update_second2((RTCState *)pvUser);
625}
626
627#ifdef IN_RING3
628
629/**
630 * @copydoc FNSSMDEVLIVEEXEC
631 */
632static DECLCALLBACK(int) rtcLiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
633{
634 RTCState *pThis = PDMINS_2_DATA(pDevIns, RTCState *);
635
636 SSMR3PutU8( pSSM, pThis->irq);
637 SSMR3PutIOPort(pSSM, pThis->IOPortBase);
638 SSMR3PutBool( pSSM, pThis->fUTC);
639
640 return VINF_SSM_DONT_CALL_AGAIN;
641}
642
643
644/**
645 * @copydoc FNSSMDEVSAVEEXEC
646 */
647static DECLCALLBACK(int) rtcSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
648{
649 RTCState *pThis = PDMINS_2_DATA(pDevIns, RTCState *);
650
651 /* The config. */
652 rtcLiveExec(pDevIns, pSSM, SSM_PASS_FINAL);
653
654 /* The state. */
655 SSMR3PutMem(pSSM, pThis->cmos_data, 128);
656 SSMR3PutU8(pSSM, pThis->cmos_index[0]);
657
658 SSMR3PutS32(pSSM, pThis->current_tm.tm_sec);
659 SSMR3PutS32(pSSM, pThis->current_tm.tm_min);
660 SSMR3PutS32(pSSM, pThis->current_tm.tm_hour);
661 SSMR3PutS32(pSSM, pThis->current_tm.tm_wday);
662 SSMR3PutS32(pSSM, pThis->current_tm.tm_mday);
663 SSMR3PutS32(pSSM, pThis->current_tm.tm_mon);
664 SSMR3PutS32(pSSM, pThis->current_tm.tm_year);
665
666 TMR3TimerSave(pThis->CTX_SUFF(pPeriodicTimer), pSSM);
667
668 SSMR3PutS64(pSSM, pThis->next_periodic_time);
669
670 SSMR3PutS64(pSSM, pThis->next_second_time);
671 TMR3TimerSave(pThis->CTX_SUFF(pSecondTimer), pSSM);
672 TMR3TimerSave(pThis->CTX_SUFF(pSecondTimer2), pSSM);
673
674 SSMR3PutBool(pSSM, pThis->fDisabledByHpet);
675
676 SSMR3PutMem(pSSM, &pThis->cmos_data[128], 128);
677 return SSMR3PutU8(pSSM, pThis->cmos_index[1]);
678}
679
680
681/**
682 * @copydoc FNSSMDEVLOADEXEC
683 */
684static DECLCALLBACK(int) rtcLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
685{
686 RTCState *pThis = PDMINS_2_DATA(pDevIns, RTCState *);
687 int rc;
688
689 if ( uVersion != RTC_SAVED_STATE_VERSION
690 && uVersion != RTC_SAVED_STATE_VERSION_VBOX_32PRE
691 && uVersion != RTC_SAVED_STATE_VERSION_VBOX_31
692 && uVersion != RTC_SAVED_STATE_VERSION_VBOX_30)
693 return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
694
695 /* The config. */
696 if (uVersion > RTC_SAVED_STATE_VERSION_VBOX_30)
697 {
698 uint8_t u8Irq;
699 rc = SSMR3GetU8(pSSM, &u8Irq); AssertRCReturn(rc, rc);
700 if (u8Irq != pThis->irq)
701 return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch - u8Irq: saved=%#x config=%#x"), u8Irq, pThis->irq);
702
703 RTIOPORT IOPortBase;
704 rc = SSMR3GetIOPort(pSSM, &IOPortBase); AssertRCReturn(rc, rc);
705 if (IOPortBase != pThis->IOPortBase)
706 return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch - IOPortBase: saved=%RTiop config=%RTiop"), IOPortBase, pThis->IOPortBase);
707
708 bool fUTC;
709 rc = SSMR3GetBool(pSSM, &fUTC); AssertRCReturn(rc, rc);
710 if (fUTC != pThis->fUTC)
711 LogRel(("RTC: Config mismatch - fUTC: saved=%RTbool config=%RTbool\n", fUTC, pThis->fUTC));
712 }
713
714 if (uPass != SSM_PASS_FINAL)
715 return VINF_SUCCESS;
716
717 /* The state. */
718 SSMR3GetMem(pSSM, pThis->cmos_data, 128);
719 SSMR3GetU8(pSSM, &pThis->cmos_index[0]);
720
721 SSMR3GetS32(pSSM, &pThis->current_tm.tm_sec);
722 SSMR3GetS32(pSSM, &pThis->current_tm.tm_min);
723 SSMR3GetS32(pSSM, &pThis->current_tm.tm_hour);
724 SSMR3GetS32(pSSM, &pThis->current_tm.tm_wday);
725 SSMR3GetS32(pSSM, &pThis->current_tm.tm_mday);
726 SSMR3GetS32(pSSM, &pThis->current_tm.tm_mon);
727 SSMR3GetS32(pSSM, &pThis->current_tm.tm_year);
728
729 TMR3TimerLoad(pThis->CTX_SUFF(pPeriodicTimer), pSSM);
730
731 SSMR3GetS64(pSSM, &pThis->next_periodic_time);
732
733 SSMR3GetS64(pSSM, &pThis->next_second_time);
734 TMR3TimerLoad(pThis->CTX_SUFF(pSecondTimer), pSSM);
735 TMR3TimerLoad(pThis->CTX_SUFF(pSecondTimer2), pSSM);
736
737 if (uVersion > RTC_SAVED_STATE_VERSION_VBOX_31)
738 SSMR3GetBool(pSSM, &pThis->fDisabledByHpet);
739
740 if (uVersion > RTC_SAVED_STATE_VERSION_VBOX_32PRE)
741 {
742 /* Second CMOS bank. */
743 SSMR3GetMem(pSSM, &pThis->cmos_data[128], 128);
744 SSMR3GetU8(pSSM, &pThis->cmos_index[1]);
745 }
746
747 int period_code = pThis->cmos_data[RTC_REG_A] & 0x0f;
748 if ( period_code != 0
749 && (pThis->cmos_data[RTC_REG_B] & REG_B_PIE)) {
750 if (period_code <= 2)
751 period_code += 7;
752 int period = 1 << (period_code - 1);
753 LogRel(("RTC: period=%#x (%d) %u Hz (restore)\n", period, period, _32K / period));
754 pThis->CurPeriod = period;
755 } else {
756 LogRel(("RTC: stopped the periodic timer (restore)\n"));
757 pThis->CurPeriod = 0;
758 }
759 pThis->cRelLogEntries = 0;
760
761 return VINF_SUCCESS;
762}
763
764
765/* -=-=-=-=-=- PDM Interface provided by the RTC device -=-=-=-=-=- */
766
767/**
768 * Calculate and update the standard CMOS checksum.
769 *
770 * @param pThis Pointer to the RTC state data.
771 */
772static void rtcCalcCRC(RTCState *pThis)
773{
774 uint16_t u16;
775 unsigned i;
776
777 for (i = RTC_CRC_START, u16 = 0; i <= RTC_CRC_LAST; i++)
778 u16 += pThis->cmos_data[i];
779 pThis->cmos_data[RTC_CRC_LOW] = u16 & 0xff;
780 pThis->cmos_data[RTC_CRC_HIGH] = (u16 >> 8) & 0xff;
781}
782
783
784/**
785 * Write to a CMOS register and update the checksum if necessary.
786 *
787 * @returns VBox status code.
788 * @param pDevIns Device instance of the RTC.
789 * @param iReg The CMOS register index; bit 8 determines bank.
790 * @param u8Value The CMOS register value.
791 */
792static DECLCALLBACK(int) rtcCMOSWrite(PPDMDEVINS pDevIns, unsigned iReg, uint8_t u8Value)
793{
794 RTCState *pThis = PDMINS_2_DATA(pDevIns, RTCState *);
795 if (iReg < RT_ELEMENTS(pThis->cmos_data))
796 {
797 pThis->cmos_data[iReg] = u8Value;
798
799 /* does it require checksum update? */
800 if ( iReg >= RTC_CRC_START
801 && iReg <= RTC_CRC_LAST)
802 rtcCalcCRC(pThis);
803
804 return VINF_SUCCESS;
805 }
806 AssertMsgFailed(("iReg=%d\n", iReg));
807 return VERR_INVALID_PARAMETER;
808}
809
810
811/**
812 * Read a CMOS register.
813 *
814 * @returns VBox status code.
815 * @param pDevIns Device instance of the RTC.
816 * @param iReg The CMOS register index; bit 8 determines bank.
817 * @param pu8Value Where to store the CMOS register value.
818 */
819static DECLCALLBACK(int) rtcCMOSRead(PPDMDEVINS pDevIns, unsigned iReg, uint8_t *pu8Value)
820{
821 RTCState *pThis = PDMINS_2_DATA(pDevIns, RTCState *);
822 if (iReg < RT_ELEMENTS(pThis->cmos_data))
823 {
824 *pu8Value = pThis->cmos_data[iReg];
825 return VINF_SUCCESS;
826 }
827 AssertMsgFailed(("iReg=%d\n", iReg));
828 return VERR_INVALID_PARAMETER;
829}
830
831
832/* -=-=-=-=-=- based on bits from pc.c -=-=-=-=-=- */
833
834/** @copydoc FNPDMDEVINITCOMPLETE */
835static DECLCALLBACK(int) rtcInitComplete(PPDMDEVINS pDevIns)
836{
837 /** @todo this should be (re)done at power on if we didn't load a state... */
838 RTCState *pThis = PDMINS_2_DATA(pDevIns, RTCState *);
839
840 /*
841 * Set the CMOS date/time.
842 */
843 RTTIMESPEC Now;
844 PDMDevHlpTMUtcNow(pDevIns, &Now);
845 RTTIME Time;
846 if (pThis->fUTC)
847 RTTimeExplode(&Time, &Now);
848 else
849 RTTimeLocalExplode(&Time, &Now);
850
851 struct my_tm Tm;
852 memset(&Tm, 0, sizeof(Tm));
853 Tm.tm_year = Time.i32Year - 1900;
854 Tm.tm_mon = Time.u8Month - 1;
855 Tm.tm_mday = Time.u8MonthDay;
856 Tm.tm_wday = (Time.u8WeekDay + 1 + 7) % 7; /* 0 = monday -> sunday */
857 Tm.tm_yday = Time.u16YearDay - 1;
858 Tm.tm_hour = Time.u8Hour;
859 Tm.tm_min = Time.u8Minute;
860 Tm.tm_sec = Time.u8Second;
861
862 rtc_set_date(pThis, &Tm);
863
864 int iYear = to_bcd(pThis, (Tm.tm_year / 100) + 19); /* tm_year is 1900 based */
865 rtc_set_memory(pThis, 0x32, iYear); /* 32h - Century Byte (BCD value for the century */
866 rtc_set_memory(pThis, 0x37, iYear); /* 37h - (IBM PS/2) Date Century Byte */
867
868 /*
869 * Recalculate the checksum just in case.
870 */
871 rtcCalcCRC(pThis);
872
873 Log(("CMOS bank 0: \n%16.128Rhxd\n", &pThis->cmos_data[0]));
874 Log(("CMOS bank 1: \n%16.128Rhxd\n", &pThis->cmos_data[128]));
875 return VINF_SUCCESS;
876}
877
878
879/* -=-=-=-=-=- real code -=-=-=-=-=- */
880
881/**
882 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
883 */
884static DECLCALLBACK(void *) rtcQueryInterface(PPDMIBASE pInterface, const char *pszIID)
885{
886 PPDMDEVINS pDevIns = RT_FROM_MEMBER(pInterface, PDMDEVINS, IBase);
887 RTCState *pThis = PDMINS_2_DATA(pDevIns, RTCState *);
888 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDevIns->IBase);
889 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHPETLEGACYNOTIFY, &pThis->IHpetLegacyNotify);
890 return NULL;
891}
892
893
894/**
895 * @interface_method_impl{PDMIHPETLEGACYNOTIFY,pfnModeChanged}
896 */
897static DECLCALLBACK(void) rtcHpetLegacyNotify_ModeChanged(PPDMIHPETLEGACYNOTIFY pInterface, bool fActivated)
898{
899 RTCState *pThis = RT_FROM_MEMBER(pInterface, RTCState, IHpetLegacyNotify);
900 pThis->fDisabledByHpet = fActivated;
901}
902
903
904/**
905 * @copydoc
906 */
907static DECLCALLBACK(void) rtcRelocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
908{
909 RTCState *pThis = PDMINS_2_DATA(pDevIns, RTCState *);
910
911 pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
912 pThis->pPeriodicTimerRC = TMTimerRCPtr(pThis->pPeriodicTimerR3);
913 pThis->pSecondTimerRC = TMTimerRCPtr(pThis->pSecondTimerR3);
914 pThis->pSecondTimer2RC = TMTimerRCPtr(pThis->pSecondTimer2R3);
915}
916
917
918/**
919 * @interface_method_impl{PDMDEVREG,pfnConstruct}
920 */
921static DECLCALLBACK(int) rtcConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
922{
923 RTCState *pThis = PDMINS_2_DATA(pDevIns, RTCState *);
924 int rc;
925 Assert(iInstance == 0);
926
927 /*
928 * Validate configuration.
929 */
930 if (!CFGMR3AreValuesValid(pCfg,
931 "Irq\0"
932 "Base\0"
933 "UseUTC\0"
934 "GCEnabled\0"
935 "R0Enabled\0"))
936 return VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES;
937
938 /*
939 * Init the data.
940 */
941 uint8_t u8Irq;
942 rc = CFGMR3QueryU8Def(pCfg, "Irq", &u8Irq, 8);
943 if (RT_FAILURE(rc))
944 return PDMDEV_SET_ERROR(pDevIns, rc,
945 N_("Configuration error: Querying \"Irq\" as a uint8_t failed"));
946 pThis->irq = u8Irq;
947
948 rc = CFGMR3QueryPortDef(pCfg, "Base", &pThis->IOPortBase, 0x70);
949 if (RT_FAILURE(rc))
950 return PDMDEV_SET_ERROR(pDevIns, rc,
951 N_("Configuration error: Querying \"Base\" as a RTIOPORT failed"));
952
953 rc = CFGMR3QueryBoolDef(pCfg, "UseUTC", &pThis->fUTC, false);
954 if (RT_FAILURE(rc))
955 return PDMDEV_SET_ERROR(pDevIns, rc,
956 N_("Configuration error: Querying \"UseUTC\" as a bool failed"));
957
958 bool fGCEnabled;
959 rc = CFGMR3QueryBoolDef(pCfg, "GCEnabled", &fGCEnabled, true);
960 if (RT_FAILURE(rc))
961 return PDMDEV_SET_ERROR(pDevIns, rc,
962 N_("Configuration error: failed to read GCEnabled as boolean"));
963
964 bool fR0Enabled;
965 rc = CFGMR3QueryBoolDef(pCfg, "R0Enabled", &fR0Enabled, true);
966 if (RT_FAILURE(rc))
967 return PDMDEV_SET_ERROR(pDevIns, rc,
968 N_("Configuration error: failed to read R0Enabled as boolean"));
969
970 Log(("RTC: Irq=%#x Base=%#x fGCEnabled=%RTbool fR0Enabled=%RTbool\n",
971 u8Irq, pThis->IOPortBase, fGCEnabled, fR0Enabled));
972
973
974 pThis->pDevInsR3 = pDevIns;
975 pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
976 pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
977 pThis->cmos_data[RTC_REG_A] = 0x26;
978 pThis->cmos_data[RTC_REG_B] = 0x02;
979 pThis->cmos_data[RTC_REG_C] = 0x00;
980 pThis->cmos_data[RTC_REG_D] = 0x80;
981 pThis->RtcReg.u32Version = PDM_RTCREG_VERSION;
982 pThis->RtcReg.pfnRead = rtcCMOSRead;
983 pThis->RtcReg.pfnWrite = rtcCMOSWrite;
984 pThis->fDisabledByHpet = false;
985
986 /* IBase */
987 pDevIns->IBase.pfnQueryInterface = rtcQueryInterface;
988 /* IHpetLegacyNotify */
989 pThis->IHpetLegacyNotify.pfnModeChanged = rtcHpetLegacyNotify_ModeChanged;
990
991 /*
992 * Create timers, arm them, register I/O Ports and save state.
993 */
994 rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, rtcTimerPeriodic, pThis,
995 TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "MC146818 RTC/CMOS - Periodic",
996 &pThis->pPeriodicTimerR3);
997 if (RT_FAILURE(rc))
998 return rc;
999 pThis->pPeriodicTimerR0 = TMTimerR0Ptr(pThis->pPeriodicTimerR3);
1000 pThis->pPeriodicTimerRC = TMTimerRCPtr(pThis->pPeriodicTimerR3);
1001
1002 rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, rtcTimerSecond, pThis,
1003 TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "MC146818 RTC/CMOS - Second",
1004 &pThis->pSecondTimerR3);
1005 if (RT_FAILURE(rc))
1006 return rc;
1007 pThis->pSecondTimerR0 = TMTimerR0Ptr(pThis->pSecondTimerR3);
1008 pThis->pSecondTimerRC = TMTimerRCPtr(pThis->pSecondTimerR3);
1009
1010 rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, rtcTimerSecond2, pThis,
1011 TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "MC146818 RTC/CMOS - Second2",
1012 &pThis->pSecondTimer2R3);
1013 if (RT_FAILURE(rc))
1014 return rc;
1015 pThis->pSecondTimer2R0 = TMTimerR0Ptr(pThis->pSecondTimer2R3);
1016 pThis->pSecondTimer2RC = TMTimerRCPtr(pThis->pSecondTimer2R3);
1017 pThis->next_second_time = TMTimerGet(pThis->CTX_SUFF(pSecondTimer2))
1018 + (TMTimerGetFreq(pThis->CTX_SUFF(pSecondTimer2)) * 99) / 100;
1019 rc = TMTimerSet(pThis->CTX_SUFF(pSecondTimer2), pThis->next_second_time);
1020 if (RT_FAILURE(rc))
1021 return rc;
1022
1023 rc = PDMDevHlpIOPortRegister(pDevIns, pThis->IOPortBase, 4, NULL,
1024 rtcIOPortWrite, rtcIOPortRead, NULL, NULL, "MC146818 RTC/CMOS");
1025 if (RT_FAILURE(rc))
1026 return rc;
1027 if (fGCEnabled)
1028 {
1029 rc = PDMDevHlpIOPortRegisterRC(pDevIns, pThis->IOPortBase, 4, 0,
1030 "rtcIOPortWrite", "rtcIOPortRead", NULL, NULL, "MC146818 RTC/CMOS");
1031 if (RT_FAILURE(rc))
1032 return rc;
1033 }
1034 if (fR0Enabled)
1035 {
1036 rc = PDMDevHlpIOPortRegisterR0(pDevIns, pThis->IOPortBase, 4, 0,
1037 "rtcIOPortWrite", "rtcIOPortRead", NULL, NULL, "MC146818 RTC/CMOS");
1038 if (RT_FAILURE(rc))
1039 return rc;
1040 }
1041
1042 rc = PDMDevHlpSSMRegister3(pDevIns, RTC_SAVED_STATE_VERSION, sizeof(*pThis), rtcLiveExec, rtcSaveExec, rtcLoadExec);
1043 if (RT_FAILURE(rc))
1044 return rc;
1045
1046 /*
1047 * Register ourselves as the RTC/CMOS with PDM.
1048 */
1049 rc = PDMDevHlpRTCRegister(pDevIns, &pThis->RtcReg, &pThis->pRtcHlpR3);
1050 if (RT_FAILURE(rc))
1051 return rc;
1052
1053 return VINF_SUCCESS;
1054}
1055
1056
1057/**
1058 * The device registration structure.
1059 */
1060const PDMDEVREG g_DeviceMC146818 =
1061{
1062 /* u32Version */
1063 PDM_DEVREG_VERSION,
1064 /* szName */
1065 "mc146818",
1066 /* szRCMod */
1067 "VBoxDDGC.gc",
1068 /* szR0Mod */
1069 "VBoxDDR0.r0",
1070 /* pszDescription */
1071 "Motorola MC146818 RTC/CMOS Device.",
1072 /* fFlags */
1073 PDM_DEVREG_FLAGS_HOST_BITS_DEFAULT | PDM_DEVREG_FLAGS_GUEST_BITS_32_64 | PDM_DEVREG_FLAGS_PAE36 | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0,
1074 /* fClass */
1075 PDM_DEVREG_CLASS_RTC,
1076 /* cMaxInstances */
1077 1,
1078 /* cbInstance */
1079 sizeof(RTCState),
1080 /* pfnConstruct */
1081 rtcConstruct,
1082 /* pfnDestruct */
1083 NULL,
1084 /* pfnRelocate */
1085 rtcRelocate,
1086 /* pfnIOCtl */
1087 NULL,
1088 /* pfnPowerOn */
1089 NULL,
1090 /* pfnReset */
1091 NULL,
1092 /* pfnSuspend */
1093 NULL,
1094 /* pfnResume */
1095 NULL,
1096 /* pfnAttach */
1097 NULL,
1098 /* pfnDetach */
1099 NULL,
1100 /* pfnQueryInterface */
1101 NULL,
1102 /* pfnInitComplete */
1103 rtcInitComplete,
1104 /* pfnPowerOff */
1105 NULL,
1106 /* pfnSoftReset */
1107 NULL,
1108 /* u32VersionEnd */
1109 PDM_DEVREG_VERSION
1110};
1111
1112#endif /* IN_RING3 */
1113#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
1114
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette