X-Git-Url: http://vcs.maemo.org/git/?a=blobdiff_plain;f=hw%2Fmc146818rtc.c;h=9d4cbed90bfe39d854c38b31ab0cc652344118f2;hb=fedc54adaa4c36da28a726a1f56e1db53f699a50;hp=cab76cfab256708c1419ac9628b6cc3f92de2b07;hpb=80cabfad16384ca47f783a7c494bd1c3c6e3c4bc;p=qemu diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c index cab76cf..9d4cbed 100644 --- a/hw/mc146818rtc.c +++ b/hw/mc146818rtc.c @@ -21,26 +21,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cpu.h" #include "vl.h" //#define DEBUG_CMOS @@ -63,15 +43,64 @@ #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 -static void cmos_ioport_write(CPUState *env, uint32_t addr, uint32_t data) +struct RTCState { + uint8_t cmos_data[128]; + uint8_t cmos_index; + struct tm current_tm; + int irq; + /* 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_copy_date(RTCState *s); + +static void rtc_timer_update(RTCState *s, int64_t current_time) { - RTCState *s = &rtc_state; + 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) +{ + RTCState *s = opaque; if ((addr & 1) == 0) { s->cmos_index = data & 0x7f; @@ -80,7 +109,7 @@ static void cmos_ioport_write(CPUState *env, 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 +124,30 @@ static void cmos_ioport_write(CPUState *env, 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,32 +160,180 @@ static void cmos_ioport_write(CPUState *env, uint32_t addr, uint32_t data) } } -static inline int to_bcd(int a) +static inline int to_bcd(RTCState *s, int a) +{ + if (s->cmos_data[RTC_REG_B] & 0x04) { + return a; + } else { + return ((a / 10) << 4) | (a % 10); + } +} + +static inline int from_bcd(RTCState *s, int a) +{ + if (s->cmos_data[RTC_REG_B] & 0x04) { + return a; + } else { + return ((a >> 4) * 10) + (a & 0x0f); + } +} + +static void rtc_set_time(RTCState *s) { - return ((a / 10) << 4) | (a % 10); + struct tm *tm = &s->current_tm; + + 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] & 0x7f); + if (!(s->cmos_data[RTC_REG_B] & 0x02) && + (s->cmos_data[RTC_HOURS] & 0x80)) { + tm->tm_hour += 12; + } + 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; } -static void cmos_update_time(RTCState *s) +static void rtc_copy_date(RTCState *s) { - struct tm *tm; - time_t ti; + const struct tm *tm = &s->current_tm; - 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]; + s->cmos_data[RTC_SECONDS] = to_bcd(s, tm->tm_sec); + s->cmos_data[RTC_MINUTES] = to_bcd(s, tm->tm_min); + if (s->cmos_data[RTC_REG_B] & 0x02) { + /* 24 hour format */ + s->cmos_data[RTC_HOURS] = to_bcd(s, tm->tm_hour); + } else { + /* 12 hour format */ + s->cmos_data[RTC_HOURS] = to_bcd(s, tm->tm_hour % 12); + if (tm->tm_hour >= 12) + s->cmos_data[RTC_HOURS] |= 0x80; + } + s->cmos_data[RTC_DAY_OF_WEEK] = to_bcd(s, tm->tm_wday); + s->cmos_data[RTC_DAY_OF_MONTH] = to_bcd(s, tm->tm_mday); + s->cmos_data[RTC_MONTH] = to_bcd(s, tm->tm_mon + 1); + s->cmos_data[RTC_YEAR] = to_bcd(s, tm->tm_year % 100); } -static uint32_t cmos_ioport_read(CPUState *env, uint32_t addr) +/* month is between 0 and 11. */ +static int get_days_in_month(int month, int year) { - RTCState *s = &rtc_state; + static const int days_tab[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + int d; + if ((unsigned )month >= 12) + return 31; + d = days_tab[month]; + if (month == 1) { + if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) + d++; + } + return d; +} + +/* update 'tm' to the next second */ +static void rtc_next_second(struct tm *tm) +{ + int days_in_month; + + tm->tm_sec++; + if ((unsigned)tm->tm_sec >= 60) { + tm->tm_sec = 0; + tm->tm_min++; + if ((unsigned)tm->tm_min >= 60) { + tm->tm_min = 0; + tm->tm_hour++; + if ((unsigned)tm->tm_hour >= 24) { + tm->tm_hour = 0; + /* next day */ + tm->tm_wday++; + if ((unsigned)tm->tm_wday >= 7) + tm->tm_wday = 0; + days_in_month = get_days_in_month(tm->tm_mon, + tm->tm_year + 1900); + tm->tm_mday++; + if (tm->tm_mday < 1) { + tm->tm_mday = 1; + } else if (tm->tm_mday > days_in_month) { + tm->tm_mday = 1; + tm->tm_mon++; + if (tm->tm_mon >= 12) { + tm->tm_mon = 0; + tm->tm_year++; + } + } + } + } + } +} + + +static void rtc_update_second(void *opaque) +{ + RTCState *s = opaque; + int64_t delay; + + /* 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 { + rtc_next_second(&s->current_tm); + + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { + /* update in progress bit */ + s->cmos_data[RTC_REG_A] |= REG_A_UIP; + } + /* should be 244 us = 8 / 32768 seconds, but currently the + timers do not have the necessary resolution. */ + delay = (ticks_per_sec * 1) / 100; + if (delay < 1) + delay = 1; + qemu_mod_timer(s->second_timer2, + s->next_second_time + delay); + } +} + +static void rtc_update_second2(void *opaque) +{ + RTCState *s = opaque; + + 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->current_tm.tm_sec) && + ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 || + s->cmos_data[RTC_MINUTES_ALARM] == s->current_tm.tm_mon) && + ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 || + s->cmos_data[RTC_HOURS_ALARM] == s->current_tm.tm_hour)) { + + 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) +{ + RTCState *s = opaque; int ret; if ((addr & 1) == 0) { return 0xff; @@ -149,16 +346,10 @@ static uint32_t cmos_ioport_read(CPUState *env, 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 +368,75 @@ static uint32_t cmos_ioport_read(CPUState *env, uint32_t addr) } } -void rtc_timer(void) +void rtc_set_memory(RTCState *s, int addr, int val) { - RTCState *s = &rtc_state; - if (s->cmos_data[RTC_REG_B] & 0x50) { - pic_set_irq(s->irq, 1); - } + if (addr >= 0 && addr <= 127) + s->cmos_data[addr] = val; } -void rtc_init(int base, int irq) +void rtc_set_date(RTCState *s, const struct tm *tm) { - RTCState *s = &rtc_state; + s->current_tm = *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_tm.tm_sec); + qemu_put_be32s(f, &s->current_tm.tm_min); + qemu_put_be32s(f, &s->current_tm.tm_hour); + qemu_put_be32s(f, &s->current_tm.tm_wday); + qemu_put_be32s(f, &s->current_tm.tm_mday); + qemu_put_be32s(f, &s->current_tm.tm_mon); + qemu_put_be32s(f, &s->current_tm.tm_year); + + 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); +} + +static int rtc_load(QEMUFile *f, void *opaque, int version_id) +{ + 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_tm.tm_sec); + qemu_get_be32s(f, &s->current_tm.tm_min); + qemu_get_be32s(f, &s->current_tm.tm_hour); + qemu_get_be32s(f, &s->current_tm.tm_wday); + qemu_get_be32s(f, &s->current_tm.tm_mday); + qemu_get_be32s(f, &s->current_tm.tm_mon); + qemu_get_be32s(f, &s->current_tm.tm_year); + + 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 +444,20 @@ void rtc_init(int base, int irq) s->cmos_data[RTC_REG_C] = 0x00; s->cmos_data[RTC_REG_D] = 0x80; - register_ioport_write(base, 2, cmos_ioport_write, 1); - register_ioport_read(base, 2, cmos_ioport_read, 1); + 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; }