--- trunk/src/devices/dev_mc146818.c 2007/10/08 16:19:01 16 +++ trunk/src/devices/dev_mc146818.c 2007/10/08 16:20:58 32 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2005 Anders Gavare. All rights reserved. + * Copyright (C) 2003-2006 Anders Gavare. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -25,7 +25,7 @@ * SUCH DAMAGE. * * - * $Id: dev_mc146818.c,v 1.75 2005/10/09 22:21:31 debug Exp $ + * $Id: dev_mc146818.c,v 1.91 2006/10/07 03:20:19 debug Exp $ * * MC146818 real-time clock, used by many different machines types. * (DS1687 as used in some other machines is also similar to the MC146818.) @@ -48,11 +48,13 @@ #include "machine.h" #include "memory.h" #include "misc.h" +#include "timer.h" #include "mc146818reg.h" #define to_bcd(x) ( ((x)/10) * 16 + ((x)%10) ) +#define from_bcd(x) ( ((x)>>4) * 10 + ((x)&15) ) /* #define MC146818_DEBUG */ @@ -62,100 +64,84 @@ /* 256 on DECstation, SGI uses reg at 72*4 as the Century */ #define N_REGISTERS 1024 struct mc_data { - int access_style; - int last_addr; + int access_style; + int last_addr; - int register_choice; - int reg[N_REGISTERS]; - int addrdiv; + int register_choice; + int reg[N_REGISTERS]; + int addrdiv; + + int use_bcd; + + int timebase_hz; + int interrupt_hz; + int old_interrupt_hz; + int irq_nr; + struct timer *timer; + volatile int pending_timer_interrupts; + + int previous_second; + int n_seconds_elapsed; + int uip_threshold; - int use_bcd; - - int timebase_hz; - int interrupt_hz; - int irq_nr; + int ugly_netbsd_prep_hack_done; + int ugly_netbsd_prep_hack_sec; +}; - int previous_second; - int interrupt_every_x_cycles; - int cycles_left_until_interrupt; -}; +/* + * Ugly hack to fool NetBSD/prep to accept the clock. (See mcclock_isa_match + * in NetBSD's arch/prep/isa/mcclock_isa.c for details.) + */ +#define NETBSD_HACK_INIT 0 +#define NETBSD_HACK_FIRST_1 1 +#define NETBSD_HACK_FIRST_2 2 +#define NETBSD_HACK_SECOND_1 3 +#define NETBSD_HACK_SECOND_2 4 +#define NETBSD_HACK_DONE 5 /* - * recalc_interrupt_cycle(): + * timer_tick(): * - * If automatic_clock_adjustment is turned on, then emulated_hz is modified - * dynamically. We have to recalculate how often interrupts are to be - * triggered. + * Called d->interrupt_hz times per (real-world) second. */ -static void recalc_interrupt_cycle(struct cpu *cpu, struct mc_data *d) +static void timer_tick(struct timer *timer, void *extra) { - int64_t emulated_hz = cpu->machine->emulated_hz; -#if 0 - static int warning_printed = 0; - - /* - * A hack to make Ultrix run, even on very fast host machines. - * - * (Ultrix was probably never meant to be run on machines with - * faster CPUs than around 33 MHz or so.) - */ - if (d->access_style == MC146818_DEC && emulated_hz > 30000000) { - if (!warning_printed) { - fatal("\n*********************************************" - "**********************************\n\n Your hos" - "t machine is too fast! The emulated CPU speed wil" - "l be limited to\n 30 MHz, and clocks inside the" - " emulated environment might go faster than\n in" - " the real world. You have been warned.\n\n******" - "*************************************************" - "************************\n\n"); - warning_printed = 1; - } - - emulated_hz = 30000000; - } -#endif - - if (d->interrupt_hz > 0) - d->interrupt_every_x_cycles = - emulated_hz / d->interrupt_hz; - else - d->interrupt_every_x_cycles = 0; + struct mc_data *d = (struct mc_data *) extra; + d->pending_timer_interrupts ++; } -/* - * dev_mc146818_tick(): - */ -void dev_mc146818_tick(struct cpu *cpu, void *extra) +DEVICE_TICK(mc146818) { struct mc_data *d = extra; + int pti = d->pending_timer_interrupts; - if (d == NULL) - return; - - recalc_interrupt_cycle(cpu, d); + if ((d->reg[MC_REGB * 4] & MC_REGB_PIE) && pti > 0) { + static int warned = 0; + if (pti > 800 && !warned) { + warned = 1; + fatal("[ WARNING: MC146818 interrupts lost, " + "host too slow? ]\n"); + } - if ((d->reg[MC_REGB * 4] & MC_REGB_PIE) && - d->interrupt_every_x_cycles > 0) { - d->cycles_left_until_interrupt -= (1 << TICK_SHIFT); - - if (d->cycles_left_until_interrupt < 0 || - d->cycles_left_until_interrupt >= - d->interrupt_every_x_cycles) { - /* debug("[ rtc interrupt (every %i cycles) ]\n", - d->interrupt_every_x_cycles); */ - cpu_interrupt(cpu, d->irq_nr); +#if 0 + /* For debugging, to see how much the interrupts are + lagging behind the real clock: */ + { + static int x = 0; + if (++x == 1) { + x = 0; + printf("%i ", pti); + fflush(stdout); + } + } +#endif - d->reg[MC_REGC * 4] |= MC_REGC_PF; + cpu_interrupt(cpu, d->irq_nr); - /* Reset the cycle countdown: */ - while (d->cycles_left_until_interrupt < 0) - d->cycles_left_until_interrupt += - d->interrupt_every_x_cycles; - } + d->reg[MC_REGC * 4] |= MC_REGC_PF; } if (d->reg[MC_REGC * 4] & MC_REGC_UF || @@ -226,6 +212,15 @@ * Special hacks for emulating the behaviour of various machines: */ switch (d->access_style) { + case MC146818_ALGOR: + /* + * NetBSD/evbmips sources indicate that the Algor year base + * is 1920. This makes the time work with NetBSD in Malta + * emulation. However, for Linux, commenting out this line + * works better. (TODO: Find a way to make both work?) + */ + d->reg[4 * MC_YEAR] += 80; + break; case MC146818_ARC_NEC: d->reg[4 * MC_YEAR] += (0x18 - 104); break; @@ -301,12 +296,14 @@ struct tm *tmp; time_t timet; struct mc_data *d = extra; - int i, relative_addr = r; + int relative_addr = r; + size_t i; relative_addr /= d->addrdiv; /* Different ways of accessing the registers: */ switch (d->access_style) { + case MC146818_ALGOR: case MC146818_CATS: case MC146818_PC_CMOS: if ((relative_addr & 1) == 0x00) { @@ -351,6 +348,10 @@ * should be ignored. It works _almost_ as DEC, if offsets are * divided by 0x40. */ + break; + case MC146818_PMPPC: + relative_addr *= 4; + break; default: ; } @@ -360,7 +361,7 @@ fatal("[ mc146818: write to addr=0x%04x (len %i): ", (int)relative_addr, (int)len); for (i=0; ireg[MC_REGC * 4] &= ~MC_REGC_UF; + if (relative_addr == MC_REGA*4 || relative_addr == MC_REGC*4) { + timet = time(NULL); + tmp = gmtime(&timet); + d->reg[MC_REGC * 4] &= ~MC_REGC_UF; + if (tmp->tm_sec != d->previous_second) { + d->n_seconds_elapsed ++; + d->previous_second = tmp->tm_sec; + } + if (d->n_seconds_elapsed > d->uip_threshold) { + d->n_seconds_elapsed = 0; - if (tmp->tm_sec != d->previous_second) { - d->reg[MC_REGA * 4] &= ~MC_REGA_UIP; + d->reg[MC_REGA * 4] |= MC_REGA_UIP; - d->reg[MC_REGC * 4] |= MC_REGC_UF; - d->reg[MC_REGC * 4] |= MC_REGC_IRQF; - d->previous_second = tmp->tm_sec; + d->reg[MC_REGC * 4] |= MC_REGC_UF; + d->reg[MC_REGC * 4] |= MC_REGC_IRQF; - /* For some reason, some Linux/DECstation KN04 kernels want - the PF (periodic flag) bit set, even though interrupts - are not enabled? */ - d->reg[MC_REGC * 4] |= MC_REGC_PF; - } else - d->reg[MC_REGA * 4] |= MC_REGA_UIP; + /* For some reason, some Linux/DECstation KN04 + kernels want the PF (periodic flag) bit set, + even though interrupts are not enabled? */ + d->reg[MC_REGC * 4] |= MC_REGC_PF; + } else + d->reg[MC_REGA * 4] &= ~MC_REGA_UIP; + } /* RTC data is in either BCD format or binary: */ if (d->use_bcd) @@ -439,34 +446,35 @@ case MC_RATE_8_Hz: d->interrupt_hz = 8; break; case MC_RATE_4_Hz: d->interrupt_hz = 4; break; case MC_RATE_2_Hz: d->interrupt_hz = 2; break; - default: - /* debug("[ mc146818: unimplemented " + default:/* debug("[ mc146818: unimplemented " "MC_REGA RS: %i ]\n", data[0] & MC_REGA_RSMASK); */ ; } - recalc_interrupt_cycle(cpu, d); - - d->cycles_left_until_interrupt = - d->interrupt_every_x_cycles; + if (d->interrupt_hz != d->old_interrupt_hz) { + debug("[ rtc changed to interrupt at %i Hz ]\n", + d->interrupt_hz); + + d->old_interrupt_hz = d->interrupt_hz; + + if (d->timer == NULL) + d->timer = timer_add(d->interrupt_hz, + timer_tick, d); + else + timer_update_frequency(d->timer, + d->interrupt_hz); + } d->reg[MC_REGA * 4] = data[0] & (MC_REGA_RSMASK | MC_REGA_DVMASK); - - debug("[ rtc set to interrupt every %i:th cycle ]\n", - d->interrupt_every_x_cycles); break; case MC_REGB*4: - if (((data[0] ^ d->reg[MC_REGB*4]) & MC_REGB_PIE)) - d->cycles_left_until_interrupt = - d->interrupt_every_x_cycles; d->reg[MC_REGB*4] = data[0]; if (!(data[0] & MC_REGB_PIE)) { cpu_interrupt_ack(cpu, d->irq_nr); - /* d->cycles_left_until_interrupt = - d->interrupt_every_x_cycles; */ } + /* debug("[ mc146818: write to MC_REGB, data[0] " "= 0x%02x ]\n", data[0]); */ break; @@ -478,10 +486,12 @@ case 0x128: d->reg[relative_addr] = data[0]; if (data[0] & 8) { + int j; + /* Used on SGI to power off the machine. */ fatal("[ md146818: power off ]\n"); - for (i=0; imachine->ncpus; i++) - cpu->machine->cpus[i]->running = 0; + for (j=0; jmachine->ncpus; j++) + cpu->machine->cpus[j]->running = 0; cpu->machine-> exit_without_entering_debugger = 1; } @@ -506,6 +516,24 @@ case 0x15: break; case 4 * MC_SEC: + if (d->ugly_netbsd_prep_hack_done < NETBSD_HACK_DONE) { + d->ugly_netbsd_prep_hack_done ++; + switch (d->ugly_netbsd_prep_hack_done) { + case NETBSD_HACK_FIRST_1: + d->ugly_netbsd_prep_hack_sec = + from_bcd(d->reg[relative_addr]); + break; + case NETBSD_HACK_FIRST_2: + d->reg[relative_addr] = to_bcd( + d->ugly_netbsd_prep_hack_sec); + break; + case NETBSD_HACK_SECOND_1: + case NETBSD_HACK_SECOND_2: + d->reg[relative_addr] = to_bcd((1 + + d->ugly_netbsd_prep_hack_sec) % 60); + break; + } + } case 4 * MC_MIN: case 4 * MC_HOUR: case 4 * MC_DOW: @@ -522,24 +550,31 @@ if (d->reg[MC_REGB * 4] & MC_REGB_SET) break; - mc146818_update_time(d); + if (d->ugly_netbsd_prep_hack_done >= NETBSD_HACK_DONE) + mc146818_update_time(d); + break; + case 4 * MC_REGA: break; case 4 * MC_REGC: /* Interrupt ack. */ /* NOTE: Acking is done below, _after_ the register has been read. */ break; - default: - debug("[ mc146818: read from relative_addr = " + default:debug("[ mc146818: read from relative_addr = " "%04x ]\n", (int)relative_addr); - ; } data[0] = d->reg[relative_addr]; if (relative_addr == MC_REGC*4) { cpu_interrupt_ack(cpu, d->irq_nr); - /* d->cycles_left_until_interrupt = - d->interrupt_every_x_cycles; */ + + /* + * Acknowledging an interrupt decreases the + * number of pending "real world" timer ticks. + */ + if (d->reg[MC_REGC * 4] & MC_REGC_PF) + d->pending_timer_interrupts --; + d->reg[MC_REGC * 4] = 0x00; } } @@ -549,7 +584,7 @@ fatal("[ mc146818: read from addr=0x%04x (len %i): ", (int)relative_addr, (int)len); for (i=0; iaccess_style = access_style; d->addrdiv = addrdiv; - /* Only SGIs and PCs use BCD format (?) */ d->use_bcd = 0; - if (access_style == MC146818_SGI || access_style == MC146818_PC_CMOS) + switch (access_style) { + case MC146818_SGI: + case MC146818_PC_CMOS: + case MC146818_PMPPC: d->use_bcd = 1; + } + + if (machine->machine_type != MACHINE_PREP) { + /* NetBSD/prep has a really ugly clock detection code; + no other machines/OSes don't need this. */ + d->ugly_netbsd_prep_hack_done = NETBSD_HACK_DONE; + } if (access_style == MC146818_DEC) { /* Station Ethernet Address, on DECstation 3100: */ @@ -625,9 +669,20 @@ d->reg[0xf8] = 1; } + /* + * uip_threshold should ideally be 1, but when Linux polls the UIP bit + * it looses speed. This hack gives Linux the impression that the cpu + * is uip_threshold times faster than the slow clock it would + * otherwise detect. + * + * TODO: Find out if this messes up Sprite emulation; if so, then + * this hack has to be removed. + */ + d->uip_threshold = 8; + if (access_style == MC146818_ARC_JAZZ) memory_device_register(mem, "mc146818_jazz", 0x90000070ULL, - 1, dev_mc146818_jazz_access, d, MEM_DEFAULT, NULL); + 1, dev_mc146818_jazz_access, d, DM_DEFAULT, NULL); dev_len = DEV_MC146818_LENGTH; switch (access_style) { @@ -641,10 +696,11 @@ memory_device_register(mem, "mc146818", baseaddr, dev_len * addrdiv, dev_mc146818_access, - d, MEM_DEFAULT, NULL); + d, DM_DEFAULT, NULL); mc146818_update_time(d); - machine_add_tickfunction(machine, dev_mc146818_tick, d, TICK_SHIFT); + machine_add_tickfunction(machine, dev_mc146818_tick, d, + TICK_SHIFT, 0.0); }