~ubuntu-branches/ubuntu/utopic/xen/utopic

« back to all changes in this revision

Viewing changes to xen/arch/x86/hvm/vmsi.c

  • Committer: Bazaar Package Importer
  • Author(s): Bastian Blank
  • Date: 2010-05-06 15:47:38 UTC
  • mto: (1.3.1) (15.1.1 sid) (4.1.1 experimental)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20100506154738-agoz0rlafrh1fnq7
Tags: upstream-4.0.0
ImportĀ upstreamĀ versionĀ 4.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  Copyright (C) 2001  MandrakeSoft S.A.
 
3
 *
 
4
 *    MandrakeSoft S.A.
 
5
 *    43, rue d'Aboukir
 
6
 *    75002 Paris - France
 
7
 *    http://www.linux-mandrake.com/
 
8
 *    http://www.mandrakesoft.com/
 
9
 *
 
10
 *  This library is free software; you can redistribute it and/or
 
11
 *  modify it under the terms of the GNU Lesser General Public
 
12
 *  License as published by the Free Software Foundation; either
 
13
 *  version 2 of the License, or (at your option) any later version.
 
14
 *
 
15
 *  This library is distributed in the hope that it will be useful,
 
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
18
 *  Lesser General Public License for more details.
 
19
 *
 
20
 *  You should have received a copy of the GNU Lesser General Public
 
21
 *  License along with this library; if not, write to the Free Software
 
22
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 
23
 *
 
24
 * Support for virtual MSI logic
 
25
 * Will be merged it with virtual IOAPIC logic, since most is the same
 
26
*/
 
27
 
 
28
#include <xen/config.h>
 
29
#include <xen/types.h>
 
30
#include <xen/mm.h>
 
31
#include <xen/xmalloc.h>
 
32
#include <xen/lib.h>
 
33
#include <xen/errno.h>
 
34
#include <xen/sched.h>
 
35
#include <public/hvm/ioreq.h>
 
36
#include <asm/hvm/io.h>
 
37
#include <asm/hvm/vpic.h>
 
38
#include <asm/hvm/vlapic.h>
 
39
#include <asm/hvm/support.h>
 
40
#include <asm/current.h>
 
41
#include <asm/event.h>
 
42
 
 
43
static void vmsi_inj_irq(
 
44
    struct domain *d,
 
45
    struct vlapic *target,
 
46
    uint8_t vector,
 
47
    uint8_t trig_mode,
 
48
    uint8_t delivery_mode)
 
49
{
 
50
    HVM_DBG_LOG(DBG_LEVEL_IOAPIC, "vmsi_inj_irq "
 
51
                "irq %d trig %d delive mode %d\n",
 
52
                vector, trig_mode, delivery_mode);
 
53
 
 
54
    switch ( delivery_mode )
 
55
    {
 
56
    case dest_Fixed:
 
57
    case dest_LowestPrio:
 
58
        if ( vlapic_set_irq(target, vector, trig_mode) )
 
59
            vcpu_kick(vlapic_vcpu(target));
 
60
        break;
 
61
    default:
 
62
        gdprintk(XENLOG_WARNING, "error delivery mode %d\n", delivery_mode);
 
63
        break;
 
64
    }
 
65
}
 
66
 
 
67
int vmsi_deliver(struct domain *d, int pirq)
 
68
{
 
69
    struct hvm_irq_dpci *hvm_irq_dpci = d->arch.hvm_domain.irq.dpci;
 
70
    uint32_t flags = hvm_irq_dpci->mirq[pirq].gmsi.gflags;
 
71
    int vector = hvm_irq_dpci->mirq[pirq].gmsi.gvec;
 
72
    uint8_t dest = (uint8_t)flags;
 
73
    uint8_t dest_mode = !!(flags & VMSI_DM_MASK);
 
74
    uint8_t delivery_mode = (flags & VMSI_DELIV_MASK) >> GLFAGS_SHIFT_DELIV_MODE;
 
75
    uint8_t trig_mode = (flags & VMSI_TRIG_MODE) >> GLFAGS_SHIFT_TRG_MODE;
 
76
    struct vlapic *target;
 
77
    struct vcpu *v;
 
78
 
 
79
    HVM_DBG_LOG(DBG_LEVEL_IOAPIC,
 
80
                "msi: dest=%x dest_mode=%x delivery_mode=%x "
 
81
                "vector=%x trig_mode=%x\n",
 
82
                dest, dest_mode, delivery_mode, vector, trig_mode);
 
83
 
 
84
    if ( !( hvm_irq_dpci->mirq[pirq].flags & HVM_IRQ_DPCI_GUEST_MSI ) )
 
85
    {
 
86
        gdprintk(XENLOG_WARNING, "pirq %x not msi \n", pirq);
 
87
        return 0;
 
88
    }
 
89
 
 
90
    switch ( delivery_mode )
 
91
    {
 
92
    case dest_LowestPrio:
 
93
    {
 
94
        target = vlapic_lowest_prio(d, NULL, 0, dest, dest_mode);
 
95
        if ( target != NULL )
 
96
            vmsi_inj_irq(d, target, vector, trig_mode, delivery_mode);
 
97
        else
 
98
            HVM_DBG_LOG(DBG_LEVEL_IOAPIC, "null round robin: "
 
99
                        "vector=%x delivery_mode=%x\n",
 
100
                        vector, dest_LowestPrio);
 
101
        break;
 
102
    }
 
103
 
 
104
    case dest_Fixed:
 
105
    case dest_ExtINT:
 
106
    {
 
107
        for_each_vcpu ( d, v )
 
108
            if ( vlapic_match_dest(vcpu_vlapic(v), NULL,
 
109
                                   0, dest, dest_mode) )
 
110
                vmsi_inj_irq(d, vcpu_vlapic(v),
 
111
                             vector, trig_mode, delivery_mode);
 
112
        break;
 
113
    }
 
114
 
 
115
    case dest_SMI:
 
116
    case dest_NMI:
 
117
    case dest_INIT:
 
118
    case dest__reserved_2:
 
119
    default:
 
120
        gdprintk(XENLOG_WARNING, "Unsupported delivery mode %d\n",
 
121
                 delivery_mode);
 
122
        break;
 
123
    }
 
124
    return 1;
 
125
}
 
126
 
 
127
/* Return value, -1 : multi-dests, non-negative value: dest_vcpu_id */
 
128
int hvm_girq_dest_2_vcpu_id(struct domain *d, uint8_t dest, uint8_t dest_mode)
 
129
{
 
130
    int dest_vcpu_id = -1, w = 0;
 
131
    struct vcpu *v;
 
132
    
 
133
    if ( d->max_vcpus == 1 )
 
134
        return 0;
 
135
 
 
136
    for_each_vcpu ( d, v )
 
137
    {
 
138
        if ( vlapic_match_dest(vcpu_vlapic(v), NULL, 0, dest, dest_mode) ) 
 
139
        {
 
140
            w++;
 
141
            dest_vcpu_id = v->vcpu_id;
 
142
        }
 
143
    }
 
144
    if ( w > 1 )
 
145
        return -1;
 
146
 
 
147
    return dest_vcpu_id;
 
148
}
 
149
 
 
150
/* MSI-X mask bit hypervisor interception */
 
151
struct msixtbl_entry
 
152
{
 
153
    struct list_head list;
 
154
    atomic_t refcnt;    /* how many bind_pt_irq called for the device */
 
155
 
 
156
    /* TODO: resolve the potential race by destruction of pdev */
 
157
    struct pci_dev *pdev;
 
158
    unsigned long gtable;       /* gpa of msix table */
 
159
    unsigned long table_len;
 
160
    unsigned long table_flags[MAX_MSIX_TABLE_ENTRIES / BITS_PER_LONG + 1];
 
161
 
 
162
    struct rcu_head rcu;
 
163
};
 
164
 
 
165
static struct msixtbl_entry *msixtbl_find_entry(
 
166
    struct vcpu *v, unsigned long addr)
 
167
{
 
168
    struct msixtbl_entry *entry;
 
169
    struct domain *d = v->domain;
 
170
 
 
171
    list_for_each_entry( entry, &d->arch.hvm_domain.msixtbl_list, list )
 
172
        if ( addr >= entry->gtable &&
 
173
             addr < entry->gtable + entry->table_len )
 
174
            return entry;
 
175
 
 
176
    return NULL;
 
177
}
 
178
 
 
179
static void __iomem *msixtbl_addr_to_virt(
 
180
    struct msixtbl_entry *entry, unsigned long addr)
 
181
{
 
182
    int idx, nr_page;
 
183
 
 
184
    if ( !entry )
 
185
        return NULL;
 
186
 
 
187
    nr_page = (addr >> PAGE_SHIFT) -
 
188
              (entry->gtable >> PAGE_SHIFT);
 
189
 
 
190
    if ( !entry->pdev )
 
191
        return NULL;
 
192
 
 
193
    idx = entry->pdev->msix_table_idx[nr_page];
 
194
    if ( !idx )
 
195
        return NULL;
 
196
 
 
197
    return (void *)(fix_to_virt(idx) +
 
198
                    (addr & ((1UL << PAGE_SHIFT) - 1)));
 
199
}
 
200
 
 
201
static int msixtbl_read(
 
202
    struct vcpu *v, unsigned long address,
 
203
    unsigned long len, unsigned long *pval)
 
204
{
 
205
    unsigned long offset;
 
206
    struct msixtbl_entry *entry;
 
207
    void *virt;
 
208
    int r = X86EMUL_UNHANDLEABLE;
 
209
 
 
210
    rcu_read_lock();
 
211
 
 
212
    if ( len != 4 )
 
213
        goto out;
 
214
 
 
215
    offset = address & (PCI_MSIX_ENTRY_SIZE - 1);
 
216
    if ( offset != PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET)
 
217
        goto out;
 
218
 
 
219
    entry = msixtbl_find_entry(v, address);
 
220
    virt = msixtbl_addr_to_virt(entry, address);
 
221
    if ( !virt )
 
222
        goto out;
 
223
 
 
224
    *pval = readl(virt);
 
225
    r = X86EMUL_OKAY;
 
226
 
 
227
out:
 
228
    rcu_read_unlock();
 
229
    return r;
 
230
}
 
231
 
 
232
static int msixtbl_write(struct vcpu *v, unsigned long address,
 
233
                        unsigned long len, unsigned long val)
 
234
{
 
235
    unsigned long offset;
 
236
    struct msixtbl_entry *entry;
 
237
    void *virt;
 
238
    int nr_entry;
 
239
    int r = X86EMUL_UNHANDLEABLE;
 
240
 
 
241
    rcu_read_lock();
 
242
 
 
243
    if ( len != 4 )
 
244
        goto out;
 
245
 
 
246
    entry = msixtbl_find_entry(v, address);
 
247
    nr_entry = (address - entry->gtable) / PCI_MSIX_ENTRY_SIZE;
 
248
 
 
249
    offset = address & (PCI_MSIX_ENTRY_SIZE - 1);
 
250
    if ( offset != PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET)
 
251
    {
 
252
        set_bit(nr_entry, &entry->table_flags);
 
253
        goto out;
 
254
    }
 
255
 
 
256
    /* exit to device model if address/data has been modified */
 
257
    if ( test_and_clear_bit(nr_entry, &entry->table_flags) )
 
258
        goto out;
 
259
 
 
260
    virt = msixtbl_addr_to_virt(entry, address);
 
261
    if ( !virt )
 
262
        goto out;
 
263
 
 
264
    writel(val, virt);
 
265
    r = X86EMUL_OKAY;
 
266
 
 
267
out:
 
268
    rcu_read_unlock();
 
269
    return r;
 
270
}
 
271
 
 
272
static int msixtbl_range(struct vcpu *v, unsigned long addr)
 
273
{
 
274
    struct msixtbl_entry *entry;
 
275
    void *virt;
 
276
 
 
277
    rcu_read_lock();
 
278
 
 
279
    entry = msixtbl_find_entry(v, addr);
 
280
    virt = msixtbl_addr_to_virt(entry, addr);
 
281
 
 
282
    rcu_read_unlock();
 
283
 
 
284
    return !!virt;
 
285
}
 
286
 
 
287
const struct hvm_mmio_handler msixtbl_mmio_handler = {
 
288
    .check_handler = msixtbl_range,
 
289
    .read_handler = msixtbl_read,
 
290
    .write_handler = msixtbl_write
 
291
};
 
292
 
 
293
static void add_msixtbl_entry(struct domain *d,
 
294
                              struct pci_dev *pdev,
 
295
                              uint64_t gtable,
 
296
                              struct msixtbl_entry *entry)
 
297
{
 
298
    u32 len;
 
299
 
 
300
    memset(entry, 0, sizeof(struct msixtbl_entry));
 
301
        
 
302
    INIT_LIST_HEAD(&entry->list);
 
303
    INIT_RCU_HEAD(&entry->rcu);
 
304
    atomic_set(&entry->refcnt, 0);
 
305
 
 
306
    len = pci_msix_get_table_len(pdev);
 
307
    entry->table_len = len;
 
308
    entry->pdev = pdev;
 
309
    entry->gtable = (unsigned long) gtable;
 
310
 
 
311
    list_add_rcu(&entry->list, &d->arch.hvm_domain.msixtbl_list);
 
312
}
 
313
 
 
314
static void free_msixtbl_entry(struct rcu_head *rcu)
 
315
{
 
316
    struct msixtbl_entry *entry;
 
317
 
 
318
    entry = container_of (rcu, struct msixtbl_entry, rcu);
 
319
 
 
320
    xfree(entry);
 
321
}
 
322
 
 
323
static void del_msixtbl_entry(struct msixtbl_entry *entry)
 
324
{
 
325
    list_del_rcu(&entry->list);
 
326
    call_rcu(&entry->rcu, free_msixtbl_entry);
 
327
}
 
328
 
 
329
int msixtbl_pt_register(struct domain *d, int pirq, uint64_t gtable)
 
330
{
 
331
    struct irq_desc *irq_desc;
 
332
    struct msi_desc *msi_desc;
 
333
    struct pci_dev *pdev;
 
334
    struct msixtbl_entry *entry, *new_entry;
 
335
    int r = -EINVAL;
 
336
 
 
337
    ASSERT(spin_is_locked(&pcidevs_lock));
 
338
 
 
339
    /*
 
340
     * xmalloc() with irq_disabled causes the failure of check_lock() 
 
341
     * for xenpool->lock. So we allocate an entry beforehand.
 
342
     */
 
343
    new_entry = xmalloc(struct msixtbl_entry);
 
344
    if ( !new_entry )
 
345
        return -ENOMEM;
 
346
 
 
347
    irq_desc = domain_spin_lock_irq_desc(d, pirq, NULL);
 
348
    if ( !irq_desc )
 
349
    {
 
350
        xfree(new_entry);
 
351
        return r;
 
352
    }
 
353
 
 
354
    if ( irq_desc->handler != &pci_msi_type )
 
355
        goto out;
 
356
 
 
357
    msi_desc = irq_desc->msi_desc;
 
358
    if ( !msi_desc )
 
359
        goto out;
 
360
 
 
361
    pdev = msi_desc->dev;
 
362
 
 
363
    spin_lock(&d->arch.hvm_domain.msixtbl_list_lock);
 
364
 
 
365
    list_for_each_entry( entry, &d->arch.hvm_domain.msixtbl_list, list )
 
366
        if ( pdev == entry->pdev )
 
367
            goto found;
 
368
 
 
369
    entry = new_entry;
 
370
    new_entry = NULL;
 
371
    add_msixtbl_entry(d, pdev, gtable, entry);
 
372
 
 
373
found:
 
374
    atomic_inc(&entry->refcnt);
 
375
    spin_unlock(&d->arch.hvm_domain.msixtbl_list_lock);
 
376
    r = 0;
 
377
 
 
378
out:
 
379
    spin_unlock_irq(&irq_desc->lock);
 
380
    xfree(new_entry);
 
381
    return r;
 
382
}
 
383
 
 
384
void msixtbl_pt_unregister(struct domain *d, int pirq)
 
385
{
 
386
    struct irq_desc *irq_desc;
 
387
    struct msi_desc *msi_desc;
 
388
    struct pci_dev *pdev;
 
389
    struct msixtbl_entry *entry;
 
390
 
 
391
    ASSERT(spin_is_locked(&pcidevs_lock));
 
392
 
 
393
    irq_desc = domain_spin_lock_irq_desc(d, pirq, NULL);
 
394
    if ( !irq_desc )
 
395
        return;
 
396
 
 
397
    if ( irq_desc->handler != &pci_msi_type )
 
398
        goto out;
 
399
 
 
400
    msi_desc = irq_desc->msi_desc;
 
401
    if ( !msi_desc )
 
402
        goto out;
 
403
 
 
404
    pdev = msi_desc->dev;
 
405
 
 
406
    spin_lock(&d->arch.hvm_domain.msixtbl_list_lock);
 
407
 
 
408
    list_for_each_entry( entry, &d->arch.hvm_domain.msixtbl_list, list )
 
409
        if ( pdev == entry->pdev )
 
410
            goto found;
 
411
 
 
412
    spin_unlock(&d->arch.hvm_domain.msixtbl_list_lock);
 
413
 
 
414
 
 
415
out:
 
416
    spin_unlock_irq(&irq_desc->lock);
 
417
    return;
 
418
 
 
419
found:
 
420
    if ( !atomic_dec_and_test(&entry->refcnt) )
 
421
        del_msixtbl_entry(entry);
 
422
 
 
423
    spin_unlock(&d->arch.hvm_domain.msixtbl_list_lock);
 
424
    spin_unlock_irq(&irq_desc->lock);
 
425
}
 
426
 
 
427
void msixtbl_pt_cleanup(struct domain *d, int pirq)
 
428
{
 
429
    struct msixtbl_entry *entry, *temp;
 
430
    unsigned long flags;
 
431
 
 
432
    /* msixtbl_list_lock must be acquired with irq_disabled for check_lock() */
 
433
    local_irq_save(flags); 
 
434
    spin_lock(&d->arch.hvm_domain.msixtbl_list_lock);
 
435
 
 
436
    list_for_each_entry_safe( entry, temp,
 
437
                              &d->arch.hvm_domain.msixtbl_list, list )
 
438
        del_msixtbl_entry(entry);
 
439
 
 
440
    spin_unlock(&d->arch.hvm_domain.msixtbl_list_lock);
 
441
    local_irq_restore(flags);
 
442
}