aboutsummaryrefslogtreecommitdiffstats
path: root/xen/arch/x86/hvm/pmtimer.c
blob: 01ae31d38dde1b9b6e321bb6cf58cd78d559d1a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/*
 * hvm/pmtimer.c: emulation of the ACPI PM timer 
 *
 * Copyright (c) 2007, XenSource inc.
 * Copyright (c) 2006, Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307 USA.
 */

#include <asm/hvm/vpt.h>
#include <asm/hvm/io.h>
#include <asm/hvm/support.h>
#include <asm/acpi.h> /* for hvm_acpi_power_button prototype */
#include <public/hvm/params.h>

/* Slightly more readable port I/O addresses for the registers we intercept */
#define PM1a_STS_ADDR_V0 (ACPI_PM1A_EVT_BLK_ADDRESS_V0)
#define PM1a_EN_ADDR_V0  (ACPI_PM1A_EVT_BLK_ADDRESS_V0 + 2)
#define TMR_VAL_ADDR_V0  (ACPI_PM_TMR_BLK_ADDRESS_V0)
#define PM1a_STS_ADDR_V1 (ACPI_PM1A_EVT_BLK_ADDRESS_V1)
#define PM1a_EN_ADDR_V1  (ACPI_PM1A_EVT_BLK_ADDRESS_V1 + 2)
#define TMR_VAL_ADDR_V1  (ACPI_PM_TMR_BLK_ADDRESS_V1)

/* The interesting bits of the PM1a_STS register */
#define TMR_STS    (1 << 0)
#define GBL_STS    (1 << 5)
#define PWRBTN_STS (1 << 8)
#define SLPBTN_STS (1 << 9)

/* The same in PM1a_EN */
#define TMR_EN     (1 << 0)
#define GBL_EN     (1 << 5)
#define PWRBTN_EN  (1 << 8)
#define SLPBTN_EN  (1 << 9)

/* Mask of bits in PM1a_STS that can generate an SCI. */
#define SCI_MASK (TMR_STS|PWRBTN_STS|SLPBTN_STS|GBL_STS) 

/* SCI IRQ number (must match SCI_INT number in ACPI FADT in hvmloader) */
#define SCI_IRQ 9

/* We provide a 32-bit counter (must match the TMR_VAL_EXT bit in the FADT) */
#define TMR_VAL_MASK  (0xffffffff)
#define TMR_VAL_MSB   (0x80000000)

/* Dispatch SCIs based on the PM1a_STS and PM1a_EN registers */
static void pmt_update_sci(PMTState *s)
{
    ASSERT(spin_is_locked(&s->lock));

    if ( s->pm.pm1a_en & s->pm.pm1a_sts & SCI_MASK )
        hvm_isa_irq_assert(s->vcpu->domain, SCI_IRQ);
    else
        hvm_isa_irq_deassert(s->vcpu->domain, SCI_IRQ);
}

void hvm_acpi_power_button(struct domain *d)
{
    PMTState *s = &d->arch.hvm_domain.pl_time.vpmt;
    spin_lock(&s->lock);
    s->pm.pm1a_sts |= PWRBTN_STS;
    pmt_update_sci(s);
    spin_unlock(&s->lock);
}

void hvm_acpi_sleep_button(struct domain *d)
{
    PMTState *s = &d->arch.hvm_domain.pl_time.vpmt;
    spin_lock(&s->lock);
    s->pm.pm1a_sts |= SLPBTN_STS;
    pmt_update_sci(s);
    spin_unlock(&s->lock);
}

/* Set the correct value in the timer, accounting for time elapsed
 * since the last time we did that. */
static void pmt_update_time(PMTState *s)
{
    uint64_t curr_gtime, tmp;
    uint32_t tmr_val = s->pm.tmr_val, msb = tmr_val & TMR_VAL_MSB;
    
    ASSERT(spin_is_locked(&s->lock));

    /* Update the timer */
    curr_gtime = hvm_get_guest_time(s->vcpu);
    tmp = ((curr_gtime - s->last_gtime) * s->scale) + s->not_accounted;
    s->not_accounted = (uint32_t)tmp;
    tmr_val += tmp >> 32;
    tmr_val &= TMR_VAL_MASK;
    s->last_gtime = curr_gtime;

    /* Update timer value atomically wrt lock-free reads in handle_pmt_io(). */
    *(volatile uint32_t *)&s->pm.tmr_val = tmr_val;

    /* If the counter's MSB has changed, set the status bit */
    if ( (tmr_val & TMR_VAL_MSB) != msb )
    {
        s->pm.pm1a_sts |= TMR_STS;
        pmt_update_sci(s);
    }
}

/* This function should be called soon after each time the MSB of the
 * pmtimer register rolls over, to make sure we update the status
 * registers and SCI at least once per rollover */
static void pmt_timer_callback(void *opaque)
{
    PMTState *s = opaque;
    uint32_t pmt_cycles_until_flip;
    uint64_t time_until_flip;

    spin_lock(&s->lock);

    /* Recalculate the timer and make sure we get an SCI if we need one */
    pmt_update_time(s);

    /* How close are we to the next MSB flip? */
    pmt_cycles_until_flip = TMR_VAL_MSB - (s->pm.tmr_val & (TMR_VAL_MSB - 1));

    /* Overall time between MSB flips */
    time_until_flip = (1000000000ULL << 23) / FREQUENCE_PMTIMER;

    /* Reduced appropriately */
    time_until_flip = (time_until_flip * pmt_cycles_until_flip) >> 23;

    /* Wake up again near the next bit-flip */
    set_timer(&s->timer, NOW() + time_until_flip + MILLISECS(1));

    spin_unlock(&s->lock);
}

/* Handle port I/O to the PM1a_STS and PM1a_EN registers */
static int handle_evt_io(
    int dir, uint32_t port, uint32_t bytes, uint32_t *val)
{
    struct vcpu *v = current;
    PMTState *s = &v->domain->arch.hvm_domain.pl_time.vpmt;
    uint32_t addr, data, byte;
    int i;

    addr = port -
        ((v->domain->arch.hvm_domain.params[
            HVM_PARAM_ACPI_IOPORTS_LOCATION] == 0) ?
         PM1a_STS_ADDR_V0 : PM1a_STS_ADDR_V1);

    spin_lock(&s->lock);

    if ( dir == IOREQ_WRITE )
    {
        /* Handle this I/O one byte at a time */
        for ( i = bytes, data = *val;
              i > 0;
              i--, addr++, data >>= 8 )
        {
            byte = data & 0xff;
            switch ( addr )
            {
                /* PM1a_STS register bits are write-to-clear */
            case 0 /* PM1a_STS_ADDR */:
                s->pm.pm1a_sts &= ~byte;
                break;
            case 1 /* PM1a_STS_ADDR + 1 */:
                s->pm.pm1a_sts &= ~(byte << 8);
                break;
            case 2 /* PM1a_EN_ADDR */:
                s->pm.pm1a_en = (s->pm.pm1a_en & 0xff00) | byte;
                break;
            case 3 /* PM1a_EN_ADDR + 1 */:
                s->pm.pm1a_en = (s->pm.pm1a_en & 0xff) | (byte << 8);
                break;
            default:
                gdprintk(XENLOG_WARNING, 
                         "Bad ACPI PM register write: %x bytes (%x) at %x\n", 
                         bytes, *val, port);
            }
        }
        /* Fix up the SCI state to match the new register state */
        pmt_update_sci(s);
    }
    else /* p->dir == IOREQ_READ */
    {
        data = s->pm.pm1a_sts | (((uint32_t) s->pm.pm1a_en) << 16);
        data >>= 8 * addr;
        if ( bytes == 1 ) data &= 0xff;
        else if ( bytes == 2 ) data &= 0xffff;
        *val = data;
    }

    spin_unlock(&s->lock);

    return X86EMUL_OKAY;
}


/* Handle port I/O to the TMR_VAL register */
static int handle_pmt_io(
    int dir, uint32_t port, uint32_t bytes, uint32_t *val)
{
    struct vcpu *v = current;
    PMTState *s = &v->domain->arch.hvm_domain.pl_time.vpmt;

    if ( bytes != 4 )
    {
        gdprintk(XENLOG_WARNING, "HVM_PMT bad access\n");
        return X86EMUL_OKAY;
    }
    
    if ( dir == IOREQ_READ )
    {
        if ( spin_trylock(&s->lock) )
        {
            /* We hold the lock: update timer value and return it. */
            pmt_update_time(s);
            *val = s->pm.tmr_val;
            spin_unlock(&s->lock);
        }
        else
        {
            /*
             * Someone else is updating the timer: rather than do the work
             * again ourselves, wait for them to finish and then steal their
             * updated value with a lock-free atomic read.
             */
            spin_barrier(&s->lock);
            *val = *(volatile uint32_t *)&s->pm.tmr_val;
        }
        return X86EMUL_OKAY;
    }

    return X86EMUL_UNHANDLEABLE;
}

static int pmtimer_save(struct domain *d, hvm_domain_context_t *h)
{
    PMTState *s = &d->arch.hvm_domain.pl_time.vpmt;
    uint32_t x, msb = s->pm.tmr_val & TMR_VAL_MSB;
    int rc;

    spin_lock(&s->lock);

    /* Update the counter to the guest's current time.  We always save
     * with the domain paused, so the saved time should be after the
     * last_gtime, but just in case, make sure we only go forwards */
    x = ((s->vcpu->arch.hvm_vcpu.guest_time - s->last_gtime) * s->scale) >> 32;
    if ( x < 1UL<<31 )
        s->pm.tmr_val += x;
    if ( (s->pm.tmr_val & TMR_VAL_MSB) != msb )
        s->pm.pm1a_sts |= TMR_STS;
    /* No point in setting the SCI here because we'll already have saved the 
     * IRQ and *PIC state; we'll fix it up when we restore the domain */

    rc = hvm_save_entry(PMTIMER, 0, h, &s->pm);

    spin_unlock(&s->lock);

    return rc;
}

static int pmtimer_load(struct domain *d, hvm_domain_context_t *h)
{
    PMTState *s = &d->arch.hvm_domain.pl_time.vpmt;

    spin_lock(&s->lock);

    /* Reload the registers */
    if ( hvm_load_entry(PMTIMER, h, &s->pm) )
    {
        spin_unlock(&s->lock);
        return -EINVAL;
    }

    /* Calculate future counter values from now. */
    s->last_gtime = hvm_get_guest_time(s->vcpu);
    s->not_accounted = 0;

    /* Set the SCI state from the registers */ 
    pmt_update_sci(s);

    spin_unlock(&s->lock);
    
    return 0;
}

HVM_REGISTER_SAVE_RESTORE(PMTIMER, pmtimer_save, pmtimer_load, 
                          1, HVMSR_PER_DOM);

int pmtimer_change_ioport(struct domain *d, unsigned int version)
{
    unsigned int old_version;

    /* Check that version is changing. */
    old_version = d->arch.hvm_domain.params[HVM_PARAM_ACPI_IOPORTS_LOCATION];
    if ( version == old_version )
        return 0;

    /* Only allow changes between versions 0 and 1. */
    if ( (version ^ old_version) != 1 )
        return -EINVAL;

    if ( version == 1 )
    {
        /* Moving from version 0 to version 1. */
        relocate_portio_handler(d, TMR_VAL_ADDR_V0, TMR_VAL_ADDR_V1, 4);
        relocate_portio_handler(d, PM1a_STS_ADDR_V0, PM1a_STS_ADDR_V1, 4);
    }
    else
    {
        /* Moving from version 1 to version 0. */
        relocate_portio_handler(d, TMR_VAL_ADDR_V1, TMR_VAL_ADDR_V0, 4);
        relocate_portio_handler(d, PM1a_STS_ADDR_V1, PM1a_STS_ADDR_V0, 4);
    }

    return 0;
}

void pmtimer_init(struct vcpu *v)
{
    PMTState *s = &v->domain->arch.hvm_domain.pl_time.vpmt;

    spin_lock_init(&s->lock);

    s->scale = ((uint64_t)FREQUENCE_PMTIMER << 32) / SYSTEM_TIME_HZ;
    s->not_accounted = 0;
    s->vcpu = v;

    /* Intercept port I/O (need two handlers because PM1a_CNT is between
     * PM1a_EN and TMR_VAL and is handled by qemu) */
    register_portio_handler(v->domain, TMR_VAL_ADDR_V0, 4, handle_pmt_io);
    register_portio_handler(v->domain, PM1a_STS_ADDR_V0, 4, handle_evt_io);

    /* Set up callback to fire SCIs when the MSB of TMR_VAL changes */
    init_timer(&s->timer, pmt_timer_callback, s, v->processor);
    pmt_timer_callback(s);
}


void pmtimer_deinit(struct domain *d)
{
    PMTState *s = &d->arch.hvm_domain.pl_time.vpmt;
    kill_timer(&s->timer);
}

void pmtimer_reset(struct domain *d)
{
    /* Reset the counter. */
    d->arch.hvm_domain.pl_time.vpmt.pm.tmr_val = 0;
}