Statistics
| Branch: | Revision:

root / hw / hpet.c @ 67f0875e

History | View | Annotate | Download (18.4 kB)

1
/*
2
 *  High Precisition Event Timer emulation
3
 *
4
 *  Copyright (c) 2007 Alexander Graf
5
 *  Copyright (c) 2008 IBM Corporation
6
 *
7
 *  Authors: Beth Kon <bkon@us.ibm.com>
8
 *
9
 * This library is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU Lesser General Public
11
 * License as published by the Free Software Foundation; either
12
 * version 2 of the License, or (at your option) any later version.
13
 *
14
 * This library is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
 * Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public
20
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
21
 *
22
 * *****************************************************************
23
 *
24
 * This driver attempts to emulate an HPET device in software.
25
 */
26

    
27
#include "hw.h"
28
#include "pc.h"
29
#include "console.h"
30
#include "qemu-timer.h"
31
#include "hpet_emul.h"
32

    
33
//#define HPET_DEBUG
34
#ifdef HPET_DEBUG
35
#define dprintf printf
36
#else
37
#define dprintf(...)
38
#endif
39

    
40
static HPETState *hpet_statep;
41

    
42
uint32_t hpet_in_legacy_mode(void)
43
{
44
    if (hpet_statep)
45
        return hpet_statep->config & HPET_CFG_LEGACY;
46
    else
47
        return 0;
48
}
49

    
50
static uint32_t timer_int_route(struct HPETTimer *timer)
51
{
52
    uint32_t route;
53
    route = (timer->config & HPET_TN_INT_ROUTE_MASK) >> HPET_TN_INT_ROUTE_SHIFT;
54
    return route;
55
}
56

    
57
static uint32_t hpet_enabled(void)
58
{
59
    return hpet_statep->config & HPET_CFG_ENABLE;
60
}
61

    
62
static uint32_t timer_is_periodic(HPETTimer *t)
63
{
64
    return t->config & HPET_TN_PERIODIC;
65
}
66

    
67
static uint32_t timer_enabled(HPETTimer *t)
68
{
69
    return t->config & HPET_TN_ENABLE;
70
}
71

    
72
static uint32_t hpet_time_after(uint64_t a, uint64_t b)
73
{
74
    return ((int32_t)(b) - (int32_t)(a) < 0);
75
}
76

    
77
static uint32_t hpet_time_after64(uint64_t a, uint64_t b)
78
{
79
    return ((int64_t)(b) - (int64_t)(a) < 0);
80
}
81

    
82
static uint64_t ticks_to_ns(uint64_t value)
83
{
84
    return (muldiv64(value, HPET_CLK_PERIOD, FS_PER_NS));
85
}
86

    
87
static uint64_t ns_to_ticks(uint64_t value)
88
{
89
    return (muldiv64(value, FS_PER_NS, HPET_CLK_PERIOD));
90
}
91

    
92
static uint64_t hpet_fixup_reg(uint64_t new, uint64_t old, uint64_t mask)
93
{
94
    new &= mask;
95
    new |= old & ~mask;
96
    return new;
97
}
98

    
99
static int activating_bit(uint64_t old, uint64_t new, uint64_t mask)
100
{
101
    return (!(old & mask) && (new & mask));
102
}
103

    
104
static int deactivating_bit(uint64_t old, uint64_t new, uint64_t mask)
105
{
106
    return ((old & mask) && !(new & mask));
107
}
108

    
109
static uint64_t hpet_get_ticks(void)
110
{
111
    uint64_t ticks;
112
    ticks = ns_to_ticks(qemu_get_clock(vm_clock) + hpet_statep->hpet_offset);
113
    return ticks;
114
}
115

    
116
/*
117
 * calculate diff between comparator value and current ticks
118
 */
119
static inline uint64_t hpet_calculate_diff(HPETTimer *t, uint64_t current)
120
{
121

    
122
    if (t->config & HPET_TN_32BIT) {
123
        uint32_t diff, cmp;
124
        cmp = (uint32_t)t->cmp;
125
        diff = cmp - (uint32_t)current;
126
        diff = (int32_t)diff > 0 ? diff : (uint32_t)0;
127
        return (uint64_t)diff;
128
    } else {
129
        uint64_t diff, cmp;
130
        cmp = t->cmp;
131
        diff = cmp - current;
132
        diff = (int64_t)diff > 0 ? diff : (uint64_t)0;
133
        return diff;
134
    }
135
}
136

    
137
static void update_irq(struct HPETTimer *timer)
138
{
139
    qemu_irq irq;
140
    int route;
141

    
142
    if (timer->tn <= 1 && hpet_in_legacy_mode()) {
143
        /* if LegacyReplacementRoute bit is set, HPET specification requires
144
         * timer0 be routed to IRQ0 in NON-APIC or IRQ2 in the I/O APIC,
145
         * timer1 be routed to IRQ8 in NON-APIC or IRQ8 in the I/O APIC.
146
         */
147
        if (timer->tn == 0) {
148
            irq=timer->state->irqs[0];
149
        } else
150
            irq=timer->state->irqs[8];
151
    } else {
152
        route=timer_int_route(timer);
153
        irq=timer->state->irqs[route];
154
    }
155
    if (timer_enabled(timer) && hpet_enabled()) {
156
        qemu_irq_pulse(irq);
157
    }
158
}
159

    
160
static void hpet_save(QEMUFile *f, void *opaque)
161
{
162
    HPETState *s = opaque;
163
    int i;
164
    qemu_put_be64s(f, &s->config);
165
    qemu_put_be64s(f, &s->isr);
166
    /* save current counter value */
167
    s->hpet_counter = hpet_get_ticks();
168
    qemu_put_be64s(f, &s->hpet_counter);
169

    
170
    for (i = 0; i < HPET_NUM_TIMERS; i++) {
171
        qemu_put_8s(f, &s->timer[i].tn);
172
        qemu_put_be64s(f, &s->timer[i].config);
173
        qemu_put_be64s(f, &s->timer[i].cmp);
174
        qemu_put_be64s(f, &s->timer[i].fsb);
175
        qemu_put_be64s(f, &s->timer[i].period);
176
        qemu_put_8s(f, &s->timer[i].wrap_flag);
177
        qemu_put_timer(f, s->timer[i].qemu_timer);
178
    }
179
}
180

    
181
static int hpet_load(QEMUFile *f, void *opaque, int version_id)
182
{
183
    HPETState *s = opaque;
184
    int i;
185

    
186
    if (version_id != 1)
187
        return -EINVAL;
188

    
189
    qemu_get_be64s(f, &s->config);
190
    qemu_get_be64s(f, &s->isr);
191
    qemu_get_be64s(f, &s->hpet_counter);
192
    /* Recalculate the offset between the main counter and guest time */
193
    s->hpet_offset = ticks_to_ns(s->hpet_counter) - qemu_get_clock(vm_clock);
194

    
195
    for (i = 0; i < HPET_NUM_TIMERS; i++) {
196
        qemu_get_8s(f, &s->timer[i].tn);
197
        qemu_get_be64s(f, &s->timer[i].config);
198
        qemu_get_be64s(f, &s->timer[i].cmp);
199
        qemu_get_be64s(f, &s->timer[i].fsb);
200
        qemu_get_be64s(f, &s->timer[i].period);
201
        qemu_get_8s(f, &s->timer[i].wrap_flag);
202
        qemu_get_timer(f, s->timer[i].qemu_timer);
203
    }
204
    return 0;
205
}
206

    
207
/*
208
 * timer expiration callback
209
 */
210
static void hpet_timer(void *opaque)
211
{
212
    HPETTimer *t = (HPETTimer*)opaque;
213
    uint64_t diff;
214

    
215
    uint64_t period = t->period;
216
    uint64_t cur_tick = hpet_get_ticks();
217

    
218
    if (timer_is_periodic(t) && period != 0) {
219
        if (t->config & HPET_TN_32BIT) {
220
            while (hpet_time_after(cur_tick, t->cmp))
221
                t->cmp = (uint32_t)(t->cmp + t->period);
222
        } else
223
            while (hpet_time_after64(cur_tick, t->cmp))
224
                t->cmp += period;
225

    
226
        diff = hpet_calculate_diff(t, cur_tick);
227
        qemu_mod_timer(t->qemu_timer, qemu_get_clock(vm_clock)
228
                       + (int64_t)ticks_to_ns(diff));
229
    } else if (t->config & HPET_TN_32BIT && !timer_is_periodic(t)) {
230
        if (t->wrap_flag) {
231
            diff = hpet_calculate_diff(t, cur_tick);
232
            qemu_mod_timer(t->qemu_timer, qemu_get_clock(vm_clock)
233
                           + (int64_t)ticks_to_ns(diff));
234
            t->wrap_flag = 0;
235
        }
236
    }
237
    update_irq(t);
238
}
239

    
240
static void hpet_set_timer(HPETTimer *t)
241
{
242
    uint64_t diff;
243
    uint32_t wrap_diff;  /* how many ticks until we wrap? */
244
    uint64_t cur_tick = hpet_get_ticks();
245

    
246
    /* whenever new timer is being set up, make sure wrap_flag is 0 */
247
    t->wrap_flag = 0;
248
    diff = hpet_calculate_diff(t, cur_tick);
249

    
250
    /* hpet spec says in one-shot 32-bit mode, generate an interrupt when
251
     * counter wraps in addition to an interrupt with comparator match.
252
     */
253
    if (t->config & HPET_TN_32BIT && !timer_is_periodic(t)) {
254
        wrap_diff = 0xffffffff - (uint32_t)cur_tick;
255
        if (wrap_diff < (uint32_t)diff) {
256
            diff = wrap_diff;
257
            t->wrap_flag = 1;
258
        }
259
    }
260
    qemu_mod_timer(t->qemu_timer, qemu_get_clock(vm_clock)
261
                   + (int64_t)ticks_to_ns(diff));
262
}
263

    
264
static void hpet_del_timer(HPETTimer *t)
265
{
266
    qemu_del_timer(t->qemu_timer);
267
}
268

    
269
#ifdef HPET_DEBUG
270
static uint32_t hpet_ram_readb(void *opaque, target_phys_addr_t addr)
271
{
272
    printf("qemu: hpet_read b at %" PRIx64 "\n", addr);
273
    return 0;
274
}
275

    
276
static uint32_t hpet_ram_readw(void *opaque, target_phys_addr_t addr)
277
{
278
    printf("qemu: hpet_read w at %" PRIx64 "\n", addr);
279
    return 0;
280
}
281
#endif
282

    
283
static uint32_t hpet_ram_readl(void *opaque, target_phys_addr_t addr)
284
{
285
    HPETState *s = (HPETState *)opaque;
286
    uint64_t cur_tick, index;
287

    
288
    dprintf("qemu: Enter hpet_ram_readl at %" PRIx64 "\n", addr);
289
    index = addr;
290
    /*address range of all TN regs*/
291
    if (index >= 0x100 && index <= 0x3ff) {
292
        uint8_t timer_id = (addr - 0x100) / 0x20;
293
        if (timer_id > HPET_NUM_TIMERS - 1) {
294
            printf("qemu: timer id out of range\n");
295
            return 0;
296
        }
297
        HPETTimer *timer = &s->timer[timer_id];
298

    
299
        switch ((addr - 0x100) % 0x20) {
300
            case HPET_TN_CFG:
301
                return timer->config;
302
            case HPET_TN_CFG + 4: // Interrupt capabilities
303
                return timer->config >> 32;
304
            case HPET_TN_CMP: // comparator register
305
                return timer->cmp;
306
            case HPET_TN_CMP + 4:
307
                return timer->cmp >> 32;
308
            case HPET_TN_ROUTE:
309
                return timer->fsb >> 32;
310
            default:
311
                dprintf("qemu: invalid hpet_ram_readl\n");
312
                break;
313
        }
314
    } else {
315
        switch (index) {
316
            case HPET_ID:
317
                return s->capability;
318
            case HPET_PERIOD:
319
                return s->capability >> 32;
320
            case HPET_CFG:
321
                return s->config;
322
            case HPET_CFG + 4:
323
                dprintf("qemu: invalid HPET_CFG + 4 hpet_ram_readl \n");
324
                return 0;
325
            case HPET_COUNTER:
326
                if (hpet_enabled())
327
                    cur_tick = hpet_get_ticks();
328
                else
329
                    cur_tick = s->hpet_counter;
330
                dprintf("qemu: reading counter  = %" PRIx64 "\n", cur_tick);
331
                return cur_tick;
332
            case HPET_COUNTER + 4:
333
                if (hpet_enabled())
334
                    cur_tick = hpet_get_ticks();
335
                else
336
                    cur_tick = s->hpet_counter;
337
                dprintf("qemu: reading counter + 4  = %" PRIx64 "\n", cur_tick);
338
                return cur_tick >> 32;
339
            case HPET_STATUS:
340
                return s->isr;
341
            default:
342
                dprintf("qemu: invalid hpet_ram_readl\n");
343
                break;
344
        }
345
    }
346
    return 0;
347
}
348

    
349
#ifdef HPET_DEBUG
350
static void hpet_ram_writeb(void *opaque, target_phys_addr_t addr,
351
                            uint32_t value)
352
{
353
    printf("qemu: invalid hpet_write b at %" PRIx64 " = %#x\n",
354
           addr, value);
355
}
356

    
357
static void hpet_ram_writew(void *opaque, target_phys_addr_t addr,
358
                            uint32_t value)
359
{
360
    printf("qemu: invalid hpet_write w at %" PRIx64 " = %#x\n",
361
           addr, value);
362
}
363
#endif
364

    
365
static void hpet_ram_writel(void *opaque, target_phys_addr_t addr,
366
                            uint32_t value)
367
{
368
    int i;
369
    HPETState *s = (HPETState *)opaque;
370
    uint64_t old_val, new_val, val, index;
371

    
372
    dprintf("qemu: Enter hpet_ram_writel at %" PRIx64 " = %#x\n", addr, value);
373
    index = addr;
374
    old_val = hpet_ram_readl(opaque, addr);
375
    new_val = value;
376

    
377
    /*address range of all TN regs*/
378
    if (index >= 0x100 && index <= 0x3ff) {
379
        uint8_t timer_id = (addr - 0x100) / 0x20;
380
        dprintf("qemu: hpet_ram_writel timer_id = %#x \n", timer_id);
381
        HPETTimer *timer = &s->timer[timer_id];
382

    
383
        switch ((addr - 0x100) % 0x20) {
384
            case HPET_TN_CFG:
385
                dprintf("qemu: hpet_ram_writel HPET_TN_CFG\n");
386
                val = hpet_fixup_reg(new_val, old_val, HPET_TN_CFG_WRITE_MASK);
387
                timer->config = (timer->config & 0xffffffff00000000ULL) | val;
388
                if (new_val & HPET_TN_32BIT) {
389
                    timer->cmp = (uint32_t)timer->cmp;
390
                    timer->period = (uint32_t)timer->period;
391
                }
392
                if (new_val & HPET_TIMER_TYPE_LEVEL) {
393
                    printf("qemu: level-triggered hpet not supported\n");
394
                    exit (-1);
395
                }
396

    
397
                break;
398
            case HPET_TN_CFG + 4: // Interrupt capabilities
399
                dprintf("qemu: invalid HPET_TN_CFG+4 write\n");
400
                break;
401
            case HPET_TN_CMP: // comparator register
402
                dprintf("qemu: hpet_ram_writel HPET_TN_CMP \n");
403
                if (timer->config & HPET_TN_32BIT)
404
                    new_val = (uint32_t)new_val;
405
                if (!timer_is_periodic(timer) ||
406
                           (timer->config & HPET_TN_SETVAL))
407
                    timer->cmp = (timer->cmp & 0xffffffff00000000ULL)
408
                                  | new_val;
409
                if (timer_is_periodic(timer)) {
410
                    /*
411
                     * FIXME: Clamp period to reasonable min value?
412
                     * Clamp period to reasonable max value
413
                     */
414
                    new_val &= (timer->config & HPET_TN_32BIT ? ~0u : ~0ull) >> 1;
415
                    timer->period = (timer->period & 0xffffffff00000000ULL)
416
                                     | new_val;
417
                }
418
                timer->config &= ~HPET_TN_SETVAL;
419
                if (hpet_enabled())
420
                    hpet_set_timer(timer);
421
                break;
422
            case HPET_TN_CMP + 4: // comparator register high order
423
                dprintf("qemu: hpet_ram_writel HPET_TN_CMP + 4\n");
424
                if (!timer_is_periodic(timer) ||
425
                           (timer->config & HPET_TN_SETVAL))
426
                    timer->cmp = (timer->cmp & 0xffffffffULL)
427
                                  | new_val << 32;
428
                else {
429
                    /*
430
                     * FIXME: Clamp period to reasonable min value?
431
                     * Clamp period to reasonable max value
432
                     */
433
                    new_val &= (timer->config
434
                                & HPET_TN_32BIT ? ~0u : ~0ull) >> 1;
435
                    timer->period = (timer->period & 0xffffffffULL)
436
                                     | new_val << 32;
437
                }
438
                timer->config &= ~HPET_TN_SETVAL;
439
                if (hpet_enabled())
440
                    hpet_set_timer(timer);
441
                break;
442
            case HPET_TN_ROUTE + 4:
443
                dprintf("qemu: hpet_ram_writel HPET_TN_ROUTE + 4\n");
444
                break;
445
            default:
446
                dprintf("qemu: invalid hpet_ram_writel\n");
447
                break;
448
        }
449
        return;
450
    } else {
451
        switch (index) {
452
            case HPET_ID:
453
                return;
454
            case HPET_CFG:
455
                val = hpet_fixup_reg(new_val, old_val, HPET_CFG_WRITE_MASK);
456
                s->config = (s->config & 0xffffffff00000000ULL) | val;
457
                if (activating_bit(old_val, new_val, HPET_CFG_ENABLE)) {
458
                    /* Enable main counter and interrupt generation. */
459
                    s->hpet_offset = ticks_to_ns(s->hpet_counter)
460
                                     - qemu_get_clock(vm_clock);
461
                    for (i = 0; i < HPET_NUM_TIMERS; i++)
462
                        if ((&s->timer[i])->cmp != ~0ULL)
463
                            hpet_set_timer(&s->timer[i]);
464
                }
465
                else if (deactivating_bit(old_val, new_val, HPET_CFG_ENABLE)) {
466
                    /* Halt main counter and disable interrupt generation. */
467
                    s->hpet_counter = hpet_get_ticks();
468
                    for (i = 0; i < HPET_NUM_TIMERS; i++)
469
                        hpet_del_timer(&s->timer[i]);
470
                }
471
                /* i8254 and RTC are disabled when HPET is in legacy mode */
472
                if (activating_bit(old_val, new_val, HPET_CFG_LEGACY)) {
473
                    hpet_pit_disable();
474
                } else if (deactivating_bit(old_val, new_val, HPET_CFG_LEGACY)) {
475
                    hpet_pit_enable();
476
                }
477
                break;
478
            case HPET_CFG + 4:
479
                dprintf("qemu: invalid HPET_CFG+4 write \n");
480
                break;
481
            case HPET_STATUS:
482
                /* FIXME: need to handle level-triggered interrupts */
483
                break;
484
            case HPET_COUNTER:
485
               if (hpet_enabled())
486
                   printf("qemu: Writing counter while HPET enabled!\n");
487
               s->hpet_counter = (s->hpet_counter & 0xffffffff00000000ULL)
488
                                  | value;
489
               dprintf("qemu: HPET counter written. ctr = %#x -> %" PRIx64 "\n",
490
                        value, s->hpet_counter);
491
               break;
492
            case HPET_COUNTER + 4:
493
               if (hpet_enabled())
494
                   printf("qemu: Writing counter while HPET enabled!\n");
495
               s->hpet_counter = (s->hpet_counter & 0xffffffffULL)
496
                                  | (((uint64_t)value) << 32);
497
               dprintf("qemu: HPET counter + 4 written. ctr = %#x -> %" PRIx64 "\n",
498
                        value, s->hpet_counter);
499
               break;
500
            default:
501
               dprintf("qemu: invalid hpet_ram_writel\n");
502
               break;
503
        }
504
    }
505
}
506

    
507
static CPUReadMemoryFunc * const hpet_ram_read[] = {
508
#ifdef HPET_DEBUG
509
    hpet_ram_readb,
510
    hpet_ram_readw,
511
#else
512
    NULL,
513
    NULL,
514
#endif
515
    hpet_ram_readl,
516
};
517

    
518
static CPUWriteMemoryFunc * const hpet_ram_write[] = {
519
#ifdef HPET_DEBUG
520
    hpet_ram_writeb,
521
    hpet_ram_writew,
522
#else
523
    NULL,
524
    NULL,
525
#endif
526
    hpet_ram_writel,
527
};
528

    
529
static void hpet_reset(void *opaque) {
530
    HPETState *s = opaque;
531
    int i;
532
    static int count = 0;
533

    
534
    for (i=0; i<HPET_NUM_TIMERS; i++) {
535
        HPETTimer *timer = &s->timer[i];
536
        hpet_del_timer(timer);
537
        timer->tn = i;
538
        timer->cmp = ~0ULL;
539
        timer->config =  HPET_TN_PERIODIC_CAP | HPET_TN_SIZE_CAP;
540
        /* advertise availability of ioapic inti2 */
541
        timer->config |=  0x00000004ULL << 32;
542
        timer->state = s;
543
        timer->period = 0ULL;
544
        timer->wrap_flag = 0;
545
    }
546

    
547
    s->hpet_counter = 0ULL;
548
    s->hpet_offset = 0ULL;
549
    /* 64-bit main counter; 3 timers supported; LegacyReplacementRoute. */
550
    s->capability = 0x8086a201ULL;
551
    s->capability |= ((HPET_CLK_PERIOD) << 32);
552
    s->config = 0ULL;
553
    if (count > 0)
554
        /* we don't enable pit when hpet_reset is first called (by hpet_init)
555
         * because hpet is taking over for pit here. On subsequent invocations,
556
         * hpet_reset is called due to system reset. At this point control must
557
         * be returned to pit until SW reenables hpet.
558
         */
559
        hpet_pit_enable();
560
    count = 1;
561
}
562

    
563

    
564
void hpet_init(qemu_irq *irq) {
565
    int i, iomemtype;
566
    HPETState *s;
567

    
568
    dprintf ("hpet_init\n");
569

    
570
    s = qemu_mallocz(sizeof(HPETState));
571
    hpet_statep = s;
572
    s->irqs = irq;
573
    for (i=0; i<HPET_NUM_TIMERS; i++) {
574
        HPETTimer *timer = &s->timer[i];
575
        timer->qemu_timer = qemu_new_timer(vm_clock, hpet_timer, timer);
576
    }
577
    hpet_reset(s);
578
    register_savevm("hpet", -1, 1, hpet_save, hpet_load, s);
579
    qemu_register_reset(hpet_reset, s);
580
    /* HPET Area */
581
    iomemtype = cpu_register_io_memory(hpet_ram_read,
582
                                       hpet_ram_write, s);
583
    cpu_register_physical_memory(HPET_BASE, 0x400, iomemtype);
584
}