X-Git-Url: https://vcs.maemo.org/git/?a=blobdiff_plain;f=hw%2Fi8254.c;h=54407de4de971d937d8941dc61c7407f478bfa7a;hb=cd346349b45ef056f138a184f660b8c34c3213cc;hp=7dc5f3c25fbdd0563cfe8e2984aed71fd9adb76f;hpb=80cabfad16384ca47f783a7c494bd1c3c6e3c4bc;p=qemu diff --git a/hw/i8254.c b/hw/i8254.c index 7dc5f3c..54407de 100644 --- a/hw/i8254.c +++ b/hw/i8254.c @@ -1,8 +1,8 @@ /* * QEMU 8253/8254 interval timer emulation - * + * * Copyright (c) 2003-2004 Fabrice Bellard - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -21,43 +21,49 @@ * 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 RW_STATE_LSB 0 -#define RW_STATE_MSB 1 -#define RW_STATE_WORD0 2 -#define RW_STATE_WORD1 3 -#define RW_STATE_LATCHED_WORD0 4 -#define RW_STATE_LATCHED_WORD1 5 +//#define DEBUG_PIT + +#define RW_STATE_LSB 1 +#define RW_STATE_MSB 2 +#define RW_STATE_WORD0 3 +#define RW_STATE_WORD1 4 + +typedef struct PITChannelState { + int count; /* can be 65536 */ + uint16_t latched_count; + uint8_t count_latched; + uint8_t status_latched; + uint8_t status; + uint8_t read_state; + uint8_t write_state; + uint8_t write_latch; + uint8_t rw_mode; + uint8_t mode; + uint8_t bcd; /* not supported */ + uint8_t gate; /* timer start */ + int64_t count_load_time; + /* irq handling */ + int64_t next_transition_time; + QEMUTimer *irq_timer; + qemu_irq irq; +} PITChannelState; -PITChannelState pit_channels[3]; +struct PITState { + PITChannelState channels[3]; +}; + +static PITState pit_state; + +static void pit_irq_timer_update(PITChannelState *s, int64_t current_time); static int pit_get_count(PITChannelState *s) { uint64_t d; int counter; - d = muldiv64(cpu_get_ticks() - s->count_load_time, PIT_FREQ, ticks_per_sec); + d = muldiv64(qemu_get_clock(vm_clock) - s->count_load_time, PIT_FREQ, ticks_per_sec); switch(s->mode) { case 0: case 1: @@ -77,12 +83,12 @@ static int pit_get_count(PITChannelState *s) } /* get pit output bit */ -int pit_get_out(PITChannelState *s) +static int pit_get_out1(PITChannelState *s, int64_t current_time) { uint64_t d; int out; - d = muldiv64(cpu_get_ticks() - s->count_load_time, PIT_FREQ, ticks_per_sec); + d = muldiv64(current_time - s->count_load_time, PIT_FREQ, ticks_per_sec); switch(s->mode) { default: case 0: @@ -108,58 +114,68 @@ int pit_get_out(PITChannelState *s) return out; } -/* get the number of 0 to 1 transitions we had since we call this - function */ -/* XXX: maybe better to use ticks precision to avoid getting edges - twice if checks are done at very small intervals */ -int pit_get_out_edges(PITChannelState *s) +int pit_get_out(PITState *pit, int channel, int64_t current_time) +{ + PITChannelState *s = &pit->channels[channel]; + return pit_get_out1(s, current_time); +} + +/* return -1 if no transition will occur. */ +static int64_t pit_get_next_transition_time(PITChannelState *s, + int64_t current_time) { - uint64_t d1, d2; - int64_t ticks; - int ret, v; - - ticks = cpu_get_ticks(); - d1 = muldiv64(s->count_last_edge_check_time - s->count_load_time, - PIT_FREQ, ticks_per_sec); - d2 = muldiv64(ticks - s->count_load_time, - PIT_FREQ, ticks_per_sec); - s->count_last_edge_check_time = ticks; + uint64_t d, next_time, base; + int period2; + + d = muldiv64(current_time - s->count_load_time, PIT_FREQ, ticks_per_sec); switch(s->mode) { default: case 0: - if (d1 < s->count && d2 >= s->count) - ret = 1; - else - ret = 0; - break; case 1: - ret = 0; + if (d < s->count) + next_time = s->count; + else + return -1; break; case 2: - d1 /= s->count; - d2 /= s->count; - ret = d2 - d1; + base = (d / s->count) * s->count; + if ((d - base) == 0 && d != 0) + next_time = base + s->count; + else + next_time = base + s->count + 1; break; case 3: - v = s->count - ((s->count + 1) >> 1); - d1 = (d1 + v) / s->count; - d2 = (d2 + v) / s->count; - ret = d2 - d1; + base = (d / s->count) * s->count; + period2 = ((s->count + 1) >> 1); + if ((d - base) < period2) + next_time = base + period2; + else + next_time = base + s->count; break; case 4: case 5: - if (d1 < s->count && d2 >= s->count) - ret = 1; + if (d < s->count) + next_time = s->count; + else if (d == s->count) + next_time = s->count + 1; else - ret = 0; + return -1; break; } - return ret; + /* convert to timer units */ + next_time = s->count_load_time + muldiv64(next_time, ticks_per_sec, PIT_FREQ); + /* fix potential rounding problems */ + /* XXX: better solution: use a clock at PIT_FREQ Hz */ + if (next_time <= current_time) + next_time = current_time + 1; + return next_time; } /* val must be 0 or 1 */ -void pit_set_gate(PITChannelState *s, int val) +void pit_set_gate(PITState *pit, int channel, int val) { + PITChannelState *s = &pit->channels[channel]; + switch(s->mode) { default: case 0: @@ -170,16 +186,16 @@ void pit_set_gate(PITChannelState *s, int val) case 5: if (s->gate < val) { /* restart counting on rising edge */ - s->count_load_time = cpu_get_ticks(); - s->count_last_edge_check_time = s->count_load_time; + s->count_load_time = qemu_get_clock(vm_clock); + pit_irq_timer_update(s, s->count_load_time); } break; case 2: case 3: if (s->gate < val) { /* restart counting on rising edge */ - s->count_load_time = cpu_get_ticks(); - s->count_last_edge_check_time = s->count_load_time; + s->count_load_time = qemu_get_clock(vm_clock); + pit_irq_timer_update(s, s->count_load_time); } /* XXX: disable/enable counting */ break; @@ -187,46 +203,89 @@ void pit_set_gate(PITChannelState *s, int val) s->gate = val; } +int pit_get_gate(PITState *pit, int channel) +{ + PITChannelState *s = &pit->channels[channel]; + return s->gate; +} + +int pit_get_initial_count(PITState *pit, int channel) +{ + PITChannelState *s = &pit->channels[channel]; + return s->count; +} + +int pit_get_mode(PITState *pit, int channel) +{ + PITChannelState *s = &pit->channels[channel]; + return s->mode; +} + static inline void pit_load_count(PITChannelState *s, int val) { if (val == 0) val = 0x10000; - s->count_load_time = cpu_get_ticks(); - s->count_last_edge_check_time = s->count_load_time; + s->count_load_time = qemu_get_clock(vm_clock); s->count = val; - if (s == &pit_channels[0] && val <= pit_min_timer_count) { - fprintf(stderr, - "\nWARNING: qemu: on your system, accurate timer emulation is impossible if its frequency is more than %d Hz. If using a 2.6 guest Linux kernel, you must patch asm/param.h to change HZ from 1000 to 100.\n\n", - PIT_FREQ / pit_min_timer_count); + pit_irq_timer_update(s, s->count_load_time); +} + +/* if already latched, do not latch again */ +static void pit_latch_count(PITChannelState *s) +{ + if (!s->count_latched) { + s->latched_count = pit_get_count(s); + s->count_latched = s->rw_mode; } } -void pit_ioport_write(CPUState *env, uint32_t addr, uint32_t val) +static void pit_ioport_write(void *opaque, uint32_t addr, uint32_t val) { + PITState *pit = opaque; int channel, access; PITChannelState *s; addr &= 3; if (addr == 3) { channel = val >> 6; - if (channel == 3) - return; - s = &pit_channels[channel]; - access = (val >> 4) & 3; - switch(access) { - case 0: - s->latched_count = pit_get_count(s); - s->rw_state = RW_STATE_LATCHED_WORD0; - break; - default: - s->mode = (val >> 1) & 7; - s->bcd = val & 1; - s->rw_state = access - 1 + RW_STATE_LSB; - break; + if (channel == 3) { + /* read back command */ + for(channel = 0; channel < 3; channel++) { + s = &pit->channels[channel]; + if (val & (2 << channel)) { + if (!(val & 0x20)) { + pit_latch_count(s); + } + if (!(val & 0x10) && !s->status_latched) { + /* status latch */ + /* XXX: add BCD and null count */ + s->status = (pit_get_out1(s, qemu_get_clock(vm_clock)) << 7) | + (s->rw_mode << 4) | + (s->mode << 1) | + s->bcd; + s->status_latched = 1; + } + } + } + } else { + s = &pit->channels[channel]; + access = (val >> 4) & 3; + if (access == 0) { + pit_latch_count(s); + } else { + s->rw_mode = access; + s->read_state = access; + s->write_state = access; + + s->mode = (val >> 1) & 7; + s->bcd = val & 1; + /* XXX: update irq timer ? */ + } } } else { - s = &pit_channels[addr]; - switch(s->rw_state) { + s = &pit->channels[addr]; + switch(s->write_state) { + default: case RW_STATE_LSB: pit_load_count(s, val); break; @@ -234,64 +293,190 @@ void pit_ioport_write(CPUState *env, uint32_t addr, uint32_t val) pit_load_count(s, val << 8); break; case RW_STATE_WORD0: + s->write_latch = val; + s->write_state = RW_STATE_WORD1; + break; case RW_STATE_WORD1: - if (s->rw_state & 1) { - pit_load_count(s, (s->latched_count & 0xff) | (val << 8)); - } else { - s->latched_count = val; - } - s->rw_state ^= 1; + pit_load_count(s, s->write_latch | (val << 8)); + s->write_state = RW_STATE_WORD0; break; } } } -uint32_t pit_ioport_read(CPUState *env, uint32_t addr) +static uint32_t pit_ioport_read(void *opaque, uint32_t addr) { + PITState *pit = opaque; int ret, count; PITChannelState *s; - + addr &= 3; - s = &pit_channels[addr]; - switch(s->rw_state) { - case RW_STATE_LSB: - case RW_STATE_MSB: - case RW_STATE_WORD0: - case RW_STATE_WORD1: - count = pit_get_count(s); - if (s->rw_state & 1) - ret = (count >> 8) & 0xff; - else - ret = count & 0xff; - if (s->rw_state & 2) - s->rw_state ^= 1; - break; - default: - case RW_STATE_LATCHED_WORD0: - case RW_STATE_LATCHED_WORD1: - if (s->rw_state & 1) + s = &pit->channels[addr]; + if (s->status_latched) { + s->status_latched = 0; + ret = s->status; + } else if (s->count_latched) { + switch(s->count_latched) { + default: + case RW_STATE_LSB: + ret = s->latched_count & 0xff; + s->count_latched = 0; + break; + case RW_STATE_MSB: ret = s->latched_count >> 8; - else + s->count_latched = 0; + break; + case RW_STATE_WORD0: ret = s->latched_count & 0xff; - s->rw_state ^= 1; - break; + s->count_latched = RW_STATE_MSB; + break; + } + } else { + switch(s->read_state) { + default: + case RW_STATE_LSB: + count = pit_get_count(s); + ret = count & 0xff; + break; + case RW_STATE_MSB: + count = pit_get_count(s); + ret = (count >> 8) & 0xff; + break; + case RW_STATE_WORD0: + count = pit_get_count(s); + ret = count & 0xff; + s->read_state = RW_STATE_WORD1; + break; + case RW_STATE_WORD1: + count = pit_get_count(s); + ret = (count >> 8) & 0xff; + s->read_state = RW_STATE_WORD0; + break; + } } return ret; } -void pit_init(void) +static void pit_irq_timer_update(PITChannelState *s, int64_t current_time) +{ + int64_t expire_time; + int irq_level; + + if (!s->irq_timer) + return; + expire_time = pit_get_next_transition_time(s, current_time); + irq_level = pit_get_out1(s, current_time); + qemu_set_irq(s->irq, irq_level); +#ifdef DEBUG_PIT + printf("irq_level=%d next_delay=%f\n", + irq_level, + (double)(expire_time - current_time) / ticks_per_sec); +#endif + s->next_transition_time = expire_time; + if (expire_time != -1) + qemu_mod_timer(s->irq_timer, expire_time); + else + qemu_del_timer(s->irq_timer); +} + +static void pit_irq_timer(void *opaque) +{ + PITChannelState *s = opaque; + + pit_irq_timer_update(s, s->next_transition_time); +} + +static void pit_save(QEMUFile *f, void *opaque) { + PITState *pit = opaque; + PITChannelState *s; + int i; + + for(i = 0; i < 3; i++) { + s = &pit->channels[i]; + qemu_put_be32s(f, &s->count); + qemu_put_be16s(f, &s->latched_count); + qemu_put_8s(f, &s->count_latched); + qemu_put_8s(f, &s->status_latched); + qemu_put_8s(f, &s->status); + qemu_put_8s(f, &s->read_state); + qemu_put_8s(f, &s->write_state); + qemu_put_8s(f, &s->write_latch); + qemu_put_8s(f, &s->rw_mode); + qemu_put_8s(f, &s->mode); + qemu_put_8s(f, &s->bcd); + qemu_put_8s(f, &s->gate); + qemu_put_be64s(f, &s->count_load_time); + if (s->irq_timer) { + qemu_put_be64s(f, &s->next_transition_time); + qemu_put_timer(f, s->irq_timer); + } + } +} + +static int pit_load(QEMUFile *f, void *opaque, int version_id) +{ + PITState *pit = opaque; + PITChannelState *s; + int i; + + if (version_id != 1) + return -EINVAL; + + for(i = 0; i < 3; i++) { + s = &pit->channels[i]; + qemu_get_be32s(f, &s->count); + qemu_get_be16s(f, &s->latched_count); + qemu_get_8s(f, &s->count_latched); + qemu_get_8s(f, &s->status_latched); + qemu_get_8s(f, &s->status); + qemu_get_8s(f, &s->read_state); + qemu_get_8s(f, &s->write_state); + qemu_get_8s(f, &s->write_latch); + qemu_get_8s(f, &s->rw_mode); + qemu_get_8s(f, &s->mode); + qemu_get_8s(f, &s->bcd); + qemu_get_8s(f, &s->gate); + qemu_get_be64s(f, &s->count_load_time); + if (s->irq_timer) { + qemu_get_be64s(f, &s->next_transition_time); + qemu_get_timer(f, s->irq_timer); + } + } + return 0; +} + +static void pit_reset(void *opaque) +{ + PITState *pit = opaque; PITChannelState *s; int i; for(i = 0;i < 3; i++) { - s = &pit_channels[i]; + s = &pit->channels[i]; s->mode = 3; s->gate = (i != 2); pit_load_count(s, 0); } - - register_ioport_write(0x40, 4, pit_ioport_write, 1); - register_ioport_read(0x40, 3, pit_ioport_read, 1); } +PITState *pit_init(int base, qemu_irq irq) +{ + PITState *pit = &pit_state; + PITChannelState *s; + + s = &pit->channels[0]; + /* the timer 0 is connected to an IRQ */ + s->irq_timer = qemu_new_timer(vm_clock, pit_irq_timer, s); + s->irq = irq; + + register_savevm("i8254", base, 1, pit_save, pit_load, pit); + + qemu_register_reset(pit_reset, pit); + register_ioport_write(base, 4, 1, pit_ioport_write, pit); + register_ioport_read(base, 3, 1, pit_ioport_read, pit); + + pit_reset(pit); + + return pit; +}