/*
* Copyright (C) 2001 MandrakeSoft S.A.
*
* MandrakeSoft S.A.
* 43, rue d'Aboukir
* 75002 Paris - France
* http://www.linux-mandrake.com/
* http://www.mandrakesoft.com/
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; If not, see .
*
* Support for virtual MSI logic
* Will be merged it with virtual IOAPIC logic, since most is the same
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static void vmsi_inj_irq(
struct vlapic *target,
uint8_t vector,
uint8_t trig_mode,
uint8_t delivery_mode)
{
HVM_DBG_LOG(DBG_LEVEL_VLAPIC, "vmsi_inj_irq: vec %02x trig %d dm %d\n",
vector, trig_mode, delivery_mode);
switch ( delivery_mode )
{
case dest_Fixed:
case dest_LowestPrio:
vlapic_set_irq(target, vector, trig_mode);
break;
default:
BUG();
}
}
int vmsi_deliver(
struct domain *d, int vector,
uint8_t dest, uint8_t dest_mode,
uint8_t delivery_mode, uint8_t trig_mode)
{
struct vlapic *target;
struct vcpu *v;
switch ( delivery_mode )
{
case dest_LowestPrio:
target = vlapic_lowest_prio(d, NULL, 0, dest, dest_mode);
if ( target != NULL )
{
vmsi_inj_irq(target, vector, trig_mode, delivery_mode);
break;
}
HVM_DBG_LOG(DBG_LEVEL_VLAPIC, "null MSI round robin: vector=%02x\n",
vector);
return -ESRCH;
case dest_Fixed:
for_each_vcpu ( d, v )
{
target = vcpu_vlapic(v);
if ( vlapic_enabled(target) &&
vlapic_match_dest(target, NULL, 0, dest, dest_mode) )
vmsi_inj_irq(target, vector, trig_mode, delivery_mode);
}
break;
default:
printk(XENLOG_G_WARNING
"%pv: Unsupported MSI delivery mode %d for Dom%d\n",
current, delivery_mode, d->domain_id);
return -EINVAL;
}
return 0;
}
void vmsi_deliver_pirq(struct domain *d, const struct hvm_pirq_dpci *pirq_dpci)
{
uint32_t flags = pirq_dpci->gmsi.gflags;
int vector = pirq_dpci->gmsi.gvec;
uint8_t dest = (uint8_t)flags;
bool dest_mode = flags & XEN_DOMCTL_VMSI_X86_DM_MASK;
uint8_t delivery_mode = MASK_EXTR(flags, XEN_DOMCTL_VMSI_X86_DELIV_MASK);
bool trig_mode = flags & XEN_DOMCTL_VMSI_X86_TRIG_MASK;
HVM_DBG_LOG(DBG_LEVEL_IOAPIC,
"msi: dest=%x dest_mode=%x delivery_mode=%x "
"vector=%x trig_mode=%x\n",
dest, dest_mode, delivery_mode, vector, trig_mode);
ASSERT(pirq_dpci->flags & HVM_IRQ_DPCI_GUEST_MSI);
vmsi_deliver(d, vector, dest, dest_mode, delivery_mode, trig_mode);
}
/* Return value, -1 : multi-dests, non-negative value: dest_vcpu_id */
int hvm_girq_dest_2_vcpu_id(struct domain *d, uint8_t dest, uint8_t dest_mode)
{
int dest_vcpu_id = -1, w = 0;
struct vcpu *v;
if ( d->max_vcpus == 1 )
return 0;
for_each_vcpu ( d, v )
{
if ( vlapic_match_dest(vcpu_vlapic(v), NULL, 0, dest, dest_mode) )
{
w++;
dest_vcpu_id = v->vcpu_id;
}
}
if ( w > 1 )
return -1;
return dest_vcpu_id;
}
/* MSI-X mask bit hypervisor interception */
struct msixtbl_entry
{
struct list_head list;
atomic_t refcnt; /* how many bind_pt_irq called for the device */
/* TODO: resolve the potential race by destruction of pdev */
struct pci_dev *pdev;
unsigned long gtable; /* gpa of msix table */
DECLARE_BITMAP(table_flags, MAX_MSIX_TABLE_ENTRIES);
#define MAX_MSIX_ACC_ENTRIES 3
unsigned int table_len;
struct {
uint32_t msi_ad[3]; /* Shadow of address low, high and data */
} gentries[MAX_MSIX_ACC_ENTRIES];
DECLARE_BITMAP(acc_valid, 3 * MAX_MSIX_ACC_ENTRIES);
#define acc_bit(what, ent, slot, idx) \
what##_bit((slot) * 3 + (idx), (ent)->acc_valid)
struct rcu_head rcu;
};
static DEFINE_RCU_READ_LOCK(msixtbl_rcu_lock);
/*
* MSI-X table infrastructure is dynamically initialised when an MSI-X capable
* device is passed through to a domain, rather than unconditionally for all
* domains.
*/
static bool msixtbl_initialised(const struct domain *d)
{
return d->arch.hvm.msixtbl_list.next;
}
static struct msixtbl_entry *msixtbl_find_entry(
struct vcpu *v, unsigned long addr)
{
struct msixtbl_entry *entry;
struct domain *d = v->domain;
list_for_each_entry( entry, &d->arch.hvm.msixtbl_list, list )
if ( addr >= entry->gtable &&
addr < entry->gtable + entry->table_len )
return entry;
return NULL;
}
static struct msi_desc *msixtbl_addr_to_desc(
const struct msixtbl_entry *entry, unsigned long addr)
{
unsigned int nr_entry;
struct msi_desc *desc;
if ( !entry || !entry->pdev )
return NULL;
nr_entry = (addr - entry->gtable) / PCI_MSIX_ENTRY_SIZE;
list_for_each_entry( desc, &entry->pdev->msi_list, list )
if ( desc->msi_attrib.type == PCI_CAP_ID_MSIX &&
desc->msi_attrib.entry_nr == nr_entry )
return desc;
return NULL;
}
static int cf_check msixtbl_read(
const struct hvm_io_handler *handler, uint64_t address, uint32_t len,
uint64_t *pval)
{
unsigned long offset;
struct msixtbl_entry *entry;
unsigned int nr_entry, index;
int r = X86EMUL_UNHANDLEABLE;
if ( (len != 4 && len != 8) || (address & (len - 1)) )
return r;
rcu_read_lock(&msixtbl_rcu_lock);
entry = msixtbl_find_entry(current, address);
if ( !entry )
goto out;
offset = address & (PCI_MSIX_ENTRY_SIZE - 1);
if ( offset != PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET )
{
nr_entry = (address - entry->gtable) / PCI_MSIX_ENTRY_SIZE;
index = offset / sizeof(uint32_t);
if ( nr_entry >= ARRAY_SIZE(entry->gentries) )
goto out;
nr_entry = array_index_nospec(nr_entry, ARRAY_SIZE(entry->gentries));
if ( !acc_bit(test, entry, nr_entry, index) )
goto out;
*pval = entry->gentries[nr_entry].msi_ad[index];
if ( len == 8 )
{
if ( index )
offset = PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET;
else if ( acc_bit(test, entry, nr_entry, 1) )
*pval |= (u64)entry->gentries[nr_entry].msi_ad[1] << 32;
else
goto out;
}
}
if ( offset == PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET )
{
const struct msi_desc *msi_desc = msixtbl_addr_to_desc(entry, address);
if ( !msi_desc )
goto out;
if ( len == 4 )
*pval = MASK_INSR(msi_desc->msi_attrib.guest_masked,
PCI_MSIX_VECTOR_BITMASK);
else
*pval |= (u64)MASK_INSR(msi_desc->msi_attrib.guest_masked,
PCI_MSIX_VECTOR_BITMASK) << 32;
}
r = X86EMUL_OKAY;
out:
rcu_read_unlock(&msixtbl_rcu_lock);
return r;
}
static int msixtbl_write(struct vcpu *v, unsigned long address,
unsigned int len, unsigned long val)
{
unsigned long offset;
struct msixtbl_entry *entry;
const struct msi_desc *msi_desc;
unsigned int nr_entry, index;
int r = X86EMUL_UNHANDLEABLE;
unsigned long flags;
struct irq_desc *desc;
if ( (len != 4 && len != 8) || (address & (len - 1)) )
return r;
rcu_read_lock(&msixtbl_rcu_lock);
entry = msixtbl_find_entry(v, address);
if ( !entry )
goto out;
nr_entry = array_index_nospec(((address - entry->gtable) /
PCI_MSIX_ENTRY_SIZE),
MAX_MSIX_TABLE_ENTRIES);
offset = address & (PCI_MSIX_ENTRY_SIZE - 1);
if ( offset != PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET )
{
index = offset / sizeof(uint32_t);
if ( nr_entry < ARRAY_SIZE(entry->gentries) )
{
nr_entry = array_index_nospec(nr_entry,
ARRAY_SIZE(entry->gentries));
entry->gentries[nr_entry].msi_ad[index] = val;
acc_bit(set, entry, nr_entry, index);
if ( len == 8 && !index )
{
entry->gentries[nr_entry].msi_ad[1] = val >> 32;
acc_bit(set, entry, nr_entry, 1);
}
}
set_bit(nr_entry, &entry->table_flags);
if ( len != 8 || !index )
goto out;
val >>= 32;
address += 4;
}
/* Exit to device model when unmasking and address/data got modified. */
if ( !(val & PCI_MSIX_VECTOR_BITMASK) &&
test_and_clear_bit(nr_entry, &entry->table_flags) )
{
v->arch.hvm.hvm_io.msix_unmask_address = address;
goto out;
}
msi_desc = msixtbl_addr_to_desc(entry, address);
if ( !msi_desc || msi_desc->irq < 0 )
goto out;
desc = irq_to_desc(msi_desc->irq);
if ( !desc )
goto out;
spin_lock_irqsave(&desc->lock, flags);
if ( !desc->msi_desc )
goto unlock;
ASSERT(msi_desc == desc->msi_desc);
guest_mask_msi_irq(desc, !!(val & PCI_MSIX_VECTOR_BITMASK));
unlock:
spin_unlock_irqrestore(&desc->lock, flags);
if ( len == 4 )
r = X86EMUL_OKAY;
out:
rcu_read_unlock(&msixtbl_rcu_lock);
return r;
}
static int cf_check _msixtbl_write(
const struct hvm_io_handler *handler, uint64_t address, uint32_t len,
uint64_t val)
{
return msixtbl_write(current, address, len, val);
}
static bool cf_check msixtbl_range(
const struct hvm_io_handler *handler, const ioreq_t *r)
{
struct vcpu *curr = current;
unsigned long addr = r->addr;
const struct msi_desc *desc;
ASSERT(r->type == IOREQ_TYPE_COPY);
rcu_read_lock(&msixtbl_rcu_lock);
desc = msixtbl_addr_to_desc(msixtbl_find_entry(curr, addr), addr);
rcu_read_unlock(&msixtbl_rcu_lock);
if ( desc )
return 1;
if ( r->state == STATE_IOREQ_READY && r->dir == IOREQ_WRITE )
{
unsigned int size = r->size;
if ( !r->data_is_ptr )
{
uint64_t data = r->data;
if ( size == 8 )
{
BUILD_BUG_ON(!(PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET & 4));
data >>= 32;
addr += size = 4;
}
if ( size == 4 &&
((addr & (PCI_MSIX_ENTRY_SIZE - 1)) ==
PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET) &&
!(data & PCI_MSIX_VECTOR_BITMASK) )
{
curr->arch.hvm.hvm_io.msix_snoop_address = addr;
curr->arch.hvm.hvm_io.msix_snoop_gpa = 0;
}
}
else if ( (size == 4 || size == 8) &&
/* Only support forward REP MOVS for now. */
!r->df &&
/*
* Only fully support accesses to a single table entry for
* now (if multiple ones get written to in one go, only the
* final one gets dealt with).
*/
r->count && r->count <= PCI_MSIX_ENTRY_SIZE / size &&
!((addr + (size * r->count)) & (PCI_MSIX_ENTRY_SIZE - 1)) )
{
BUILD_BUG_ON((PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET + 4) &
(PCI_MSIX_ENTRY_SIZE - 1));
curr->arch.hvm.hvm_io.msix_snoop_address =
addr + size * r->count - 4;
curr->arch.hvm.hvm_io.msix_snoop_gpa =
r->data + size * r->count - 4;
}
}
return 0;
}
static const struct hvm_io_ops msixtbl_mmio_ops = {
.accept = msixtbl_range,
.read = msixtbl_read,
.write = _msixtbl_write,
};
static void add_msixtbl_entry(struct domain *d,
struct pci_dev *pdev,
uint64_t gtable,
struct msixtbl_entry *entry)
{
INIT_LIST_HEAD(&entry->list);
INIT_RCU_HEAD(&entry->rcu);
atomic_set(&entry->refcnt, 0);
entry->table_len = pdev->msix->nr_entries * PCI_MSIX_ENTRY_SIZE;
entry->pdev = pdev;
entry->gtable = (unsigned long) gtable;
list_add_rcu(&entry->list, &d->arch.hvm.msixtbl_list);
}
static void cf_check free_msixtbl_entry(struct rcu_head *rcu)
{
struct msixtbl_entry *entry;
entry = container_of (rcu, struct msixtbl_entry, rcu);
xfree(entry);
}
static void del_msixtbl_entry(struct msixtbl_entry *entry)
{
list_del_rcu(&entry->list);
call_rcu(&entry->rcu, free_msixtbl_entry);
}
int msixtbl_pt_register(struct domain *d, struct pirq *pirq, uint64_t gtable)
{
struct irq_desc *irq_desc;
struct msi_desc *msi_desc;
struct pci_dev *pdev;
struct msixtbl_entry *entry, *new_entry;
int r = -EINVAL;
ASSERT(pcidevs_locked());
ASSERT(rw_is_write_locked(&d->event_lock));
if ( !msixtbl_initialised(d) )
return -ENODEV;
/*
* xmalloc() with irq_disabled causes the failure of check_lock()
* for xenpool->lock. So we allocate an entry beforehand.
*/
new_entry = xzalloc(struct msixtbl_entry);
if ( !new_entry )
return -ENOMEM;
irq_desc = pirq_spin_lock_irq_desc(pirq, NULL);
if ( !irq_desc )
{
xfree(new_entry);
return r;
}
msi_desc = irq_desc->msi_desc;
if ( !msi_desc )
goto out;
pdev = msi_desc->dev;
list_for_each_entry( entry, &d->arch.hvm.msixtbl_list, list )
if ( pdev == entry->pdev )
goto found;
entry = new_entry;
new_entry = NULL;
add_msixtbl_entry(d, pdev, gtable, entry);
found:
atomic_inc(&entry->refcnt);
r = 0;
out:
spin_unlock_irq(&irq_desc->lock);
xfree(new_entry);
if ( !r )
{
struct vcpu *v;
for_each_vcpu ( d, v )
{
if ( (v->pause_flags & VPF_blocked_in_xen) &&
!v->arch.hvm.hvm_io.msix_snoop_gpa &&
v->arch.hvm.hvm_io.msix_snoop_address ==
(gtable + msi_desc->msi_attrib.entry_nr *
PCI_MSIX_ENTRY_SIZE +
PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET) )
v->arch.hvm.hvm_io.msix_unmask_address =
v->arch.hvm.hvm_io.msix_snoop_address;
}
}
return r;
}
void msixtbl_pt_unregister(struct domain *d, struct pirq *pirq)
{
struct irq_desc *irq_desc;
struct msi_desc *msi_desc;
struct pci_dev *pdev;
struct msixtbl_entry *entry;
ASSERT(pcidevs_locked());
ASSERT(rw_is_write_locked(&d->event_lock));
if ( !msixtbl_initialised(d) )
return;
irq_desc = pirq_spin_lock_irq_desc(pirq, NULL);
if ( !irq_desc )
return;
msi_desc = irq_desc->msi_desc;
if ( !msi_desc )
goto out;
pdev = msi_desc->dev;
list_for_each_entry( entry, &d->arch.hvm.msixtbl_list, list )
if ( pdev == entry->pdev )
goto found;
out:
spin_unlock_irq(&irq_desc->lock);
return;
found:
if ( !atomic_dec_and_test(&entry->refcnt) )
del_msixtbl_entry(entry);
spin_unlock_irq(&irq_desc->lock);
}
void msixtbl_init(struct domain *d)
{
struct hvm_io_handler *handler;
if ( !is_hvm_domain(d) || !has_vlapic(d) || msixtbl_initialised(d) )
return;
INIT_LIST_HEAD(&d->arch.hvm.msixtbl_list);
handler = hvm_next_io_handler(d);
if ( handler )
{
handler->type = IOREQ_TYPE_COPY;
handler->ops = &msixtbl_mmio_ops;
}
}
void msixtbl_pt_cleanup(struct domain *d)
{
struct msixtbl_entry *entry, *temp;
if ( !msixtbl_initialised(d) )
return;
write_lock(&d->event_lock);
list_for_each_entry_safe( entry, temp,
&d->arch.hvm.msixtbl_list, list )
del_msixtbl_entry(entry);
write_unlock(&d->event_lock);
}
void msix_write_completion(struct vcpu *v)
{
unsigned long ctrl_address = v->arch.hvm.hvm_io.msix_unmask_address;
unsigned long snoop_addr = v->arch.hvm.hvm_io.msix_snoop_address;
v->arch.hvm.hvm_io.msix_snoop_address = 0;
if ( !ctrl_address && snoop_addr &&
v->arch.hvm.hvm_io.msix_snoop_gpa )
{
unsigned int token = hvmemul_cache_disable(v);
const struct msi_desc *desc;
uint32_t data;
rcu_read_lock(&msixtbl_rcu_lock);
desc = msixtbl_addr_to_desc(msixtbl_find_entry(v, snoop_addr),
snoop_addr);
rcu_read_unlock(&msixtbl_rcu_lock);
if ( desc &&
hvm_copy_from_guest_phys(&data,
v->arch.hvm.hvm_io.msix_snoop_gpa,
sizeof(data)) == HVMTRANS_okay &&
!(data & PCI_MSIX_VECTOR_BITMASK) )
ctrl_address = snoop_addr;
hvmemul_cache_restore(v, token);
}
if ( !ctrl_address )
return;
v->arch.hvm.hvm_io.msix_unmask_address = 0;
if ( msixtbl_write(v, ctrl_address, 4, 0) != X86EMUL_OKAY )
gdprintk(XENLOG_WARNING, "MSI-X write completion failure\n");
}
#ifdef CONFIG_HAS_VPCI
static unsigned int msi_gflags(uint16_t data, uint64_t addr, bool masked)
{
/*
* We need to use the DOMCTL constants here because the output of this
* function is used as input to pt_irq_create_bind, which also takes the
* input from the DOMCTL itself.
*/
return MASK_INSR(MASK_EXTR(addr, MSI_ADDR_DEST_ID_MASK),
XEN_DOMCTL_VMSI_X86_DEST_ID_MASK) |
MASK_INSR(MASK_EXTR(addr, MSI_ADDR_REDIRECTION_MASK),
XEN_DOMCTL_VMSI_X86_RH_MASK) |
MASK_INSR(MASK_EXTR(addr, MSI_ADDR_DESTMODE_MASK),
XEN_DOMCTL_VMSI_X86_DM_MASK) |
MASK_INSR(MASK_EXTR(data, MSI_DATA_DELIVERY_MODE_MASK),
XEN_DOMCTL_VMSI_X86_DELIV_MASK) |
MASK_INSR(MASK_EXTR(data, MSI_DATA_TRIGGER_MASK),
XEN_DOMCTL_VMSI_X86_TRIG_MASK) |
/* NB: by default MSI vectors are bound masked. */
(masked ? 0 : XEN_DOMCTL_VMSI_X86_UNMASKED);
}
static void vpci_mask_pirq(struct domain *d, int pirq, bool mask)
{
unsigned long flags;
struct irq_desc *desc = domain_spin_lock_irq_desc(d, pirq, &flags);
if ( !desc )
return;
guest_mask_msi_irq(desc, mask);
spin_unlock_irqrestore(&desc->lock, flags);
}
void vpci_msi_arch_mask(struct vpci_msi *msi, const struct pci_dev *pdev,
unsigned int entry, bool mask)
{
vpci_mask_pirq(pdev->domain, msi->arch.pirq + entry, mask);
}
static int vpci_msi_update(const struct pci_dev *pdev, uint32_t data,
uint64_t address, unsigned int vectors,
unsigned int pirq, uint32_t mask)
{
unsigned int i;
ASSERT(pcidevs_locked());
if ( (address & MSI_ADDR_BASE_MASK) != MSI_ADDR_HEADER )
{
gdprintk(XENLOG_ERR, "%pp: PIRQ %u: unsupported address %lx\n",
&pdev->sbdf, pirq, address);
return -EOPNOTSUPP;
}
for ( i = 0; i < vectors; i++ )
{
uint8_t vector = MASK_EXTR(data, MSI_DATA_VECTOR_MASK);
uint8_t vector_mask = 0xff >> (8 - fls(vectors) + 1);
struct xen_domctl_bind_pt_irq bind = {
.machine_irq = pirq + i,
.irq_type = PT_IRQ_TYPE_MSI,
.u.msi.gvec = (vector & ~vector_mask) |
((vector + i) & vector_mask),
.u.msi.gflags = msi_gflags(data, address, (mask >> i) & 1),
};
int rc = pt_irq_create_bind(pdev->domain, &bind);
if ( rc )
{
gdprintk(XENLOG_ERR, "%pp: failed to bind PIRQ %u: %d\n",
&pdev->sbdf, pirq + i, rc);
while ( bind.machine_irq-- > pirq )
pt_irq_destroy_bind(pdev->domain, &bind);
return rc;
}
}
return 0;
}
void vpci_msi_arch_update(struct vpci_msi *msi, const struct pci_dev *pdev)
{
unsigned int i;
int rc;
ASSERT(msi->arch.pirq != INVALID_PIRQ);
pcidevs_lock();
for ( i = 0; i < msi->vectors && msi->arch.bound; i++ )
{
struct xen_domctl_bind_pt_irq unbind = {
.machine_irq = msi->arch.pirq + i,
.irq_type = PT_IRQ_TYPE_MSI,
};
rc = pt_irq_destroy_bind(pdev->domain, &unbind);
if ( rc )
{
ASSERT_UNREACHABLE();
domain_crash(pdev->domain);
return;
}
}
msi->arch.bound = !vpci_msi_update(pdev, msi->data, msi->address,
msi->vectors, msi->arch.pirq, msi->mask);
pcidevs_unlock();
}
static int vpci_msi_enable(const struct pci_dev *pdev, unsigned int nr,
paddr_t table_base)
{
struct msi_info msi_info = {
.sbdf = pdev->sbdf,
.table_base = table_base,
.entry_nr = nr,
};
int rc, pirq = INVALID_PIRQ;
/* Get a PIRQ. */
rc = allocate_and_map_msi_pirq(pdev->domain, -1, &pirq,
table_base ? MAP_PIRQ_TYPE_MSI
: MAP_PIRQ_TYPE_MULTI_MSI,
&msi_info);
if ( rc )
{
gdprintk(XENLOG_ERR, "%pp: failed to map PIRQ: %d\n", &pdev->sbdf, rc);
return rc;
}
return pirq;
}
int vpci_msi_arch_enable(struct vpci_msi *msi, const struct pci_dev *pdev,
unsigned int vectors)
{
int rc;
ASSERT(msi->arch.pirq == INVALID_PIRQ);
rc = vpci_msi_enable(pdev, vectors, 0);
if ( rc < 0 )
return rc;
msi->arch.pirq = rc;
pcidevs_lock();
msi->arch.bound = !vpci_msi_update(pdev, msi->data, msi->address, vectors,
msi->arch.pirq, msi->mask);
pcidevs_unlock();
return 0;
}
static void vpci_msi_disable(const struct pci_dev *pdev, int pirq,
unsigned int nr, bool bound)
{
unsigned int i;
ASSERT(pirq != INVALID_PIRQ);
pcidevs_lock();
for ( i = 0; i < nr && bound; i++ )
{
struct xen_domctl_bind_pt_irq bind = {
.machine_irq = pirq + i,
.irq_type = PT_IRQ_TYPE_MSI,
};
int rc;
rc = pt_irq_destroy_bind(pdev->domain, &bind);
ASSERT(!rc);
}
write_lock(&pdev->domain->event_lock);
unmap_domain_pirq(pdev->domain, pirq);
write_unlock(&pdev->domain->event_lock);
pcidevs_unlock();
}
void vpci_msi_arch_disable(struct vpci_msi *msi, const struct pci_dev *pdev)
{
vpci_msi_disable(pdev, msi->arch.pirq, msi->vectors, msi->arch.bound);
msi->arch.pirq = INVALID_PIRQ;
}
void vpci_msi_arch_init(struct vpci_msi *msi)
{
msi->arch.pirq = INVALID_PIRQ;
}
void vpci_msi_arch_print(const struct vpci_msi *msi)
{
printk("vec=%#02x%7s%6s%3sassert%5s%7s dest_id=%lu pirq: %d\n",
MASK_EXTR(msi->data, MSI_DATA_VECTOR_MASK),
msi->data & MSI_DATA_DELIVERY_LOWPRI ? "lowest" : "fixed",
msi->data & MSI_DATA_TRIGGER_LEVEL ? "level" : "edge",
msi->data & MSI_DATA_LEVEL_ASSERT ? "" : "de",
msi->address & MSI_ADDR_DESTMODE_LOGIC ? "log" : "phys",
msi->address & MSI_ADDR_REDIRECTION_LOWPRI ? "lowest" : "fixed",
MASK_EXTR(msi->address, MSI_ADDR_DEST_ID_MASK),
msi->arch.pirq);
}
void vpci_msix_arch_mask_entry(struct vpci_msix_entry *entry,
const struct pci_dev *pdev, bool mask)
{
if ( entry->arch.pirq != INVALID_PIRQ )
vpci_mask_pirq(pdev->domain, entry->arch.pirq, mask);
}
int vpci_msix_arch_enable_entry(struct vpci_msix_entry *entry,
const struct pci_dev *pdev, paddr_t table_base)
{
int rc;
ASSERT(entry->arch.pirq == INVALID_PIRQ);
rc = vpci_msi_enable(pdev, vmsix_entry_nr(pdev->vpci->msix, entry),
table_base);
if ( rc < 0 )
return rc;
entry->arch.pirq = rc;
pcidevs_lock();
rc = vpci_msi_update(pdev, entry->data, entry->addr, 1, entry->arch.pirq,
entry->masked);
if ( rc )
{
vpci_msi_disable(pdev, entry->arch.pirq, 1, false);
entry->arch.pirq = INVALID_PIRQ;
}
pcidevs_unlock();
return rc;
}
int vpci_msix_arch_disable_entry(struct vpci_msix_entry *entry,
const struct pci_dev *pdev)
{
if ( entry->arch.pirq == INVALID_PIRQ )
return -ENOENT;
vpci_msi_disable(pdev, entry->arch.pirq, 1, true);
entry->arch.pirq = INVALID_PIRQ;
return 0;
}
void vpci_msix_arch_init_entry(struct vpci_msix_entry *entry)
{
entry->arch.pirq = INVALID_PIRQ;
}
int vpci_msix_arch_print(const struct vpci_msix *msix)
{
unsigned int i;
for ( i = 0; i < msix->max_entries; i++ )
{
const struct vpci_msix_entry *entry = &msix->entries[i];
printk("%6u vec=%02x%7s%6s%3sassert%5s%7s dest_id=%lu mask=%u pirq: %d\n",
i, MASK_EXTR(entry->data, MSI_DATA_VECTOR_MASK),
entry->data & MSI_DATA_DELIVERY_LOWPRI ? "lowest" : "fixed",
entry->data & MSI_DATA_TRIGGER_LEVEL ? "level" : "edge",
entry->data & MSI_DATA_LEVEL_ASSERT ? "" : "de",
entry->addr & MSI_ADDR_DESTMODE_LOGIC ? "log" : "phys",
entry->addr & MSI_ADDR_REDIRECTION_LOWPRI ? "lowest" : "fixed",
MASK_EXTR(entry->addr, MSI_ADDR_DEST_ID_MASK),
entry->masked, entry->arch.pirq);
if ( i && !(i % 64) )
{
struct pci_dev *pdev = msix->pdev;
spin_unlock(&msix->pdev->vpci->lock);
process_pending_softirqs();
/* NB: we assume that pdev cannot go away for an alive domain. */
if ( !pdev->vpci || !spin_trylock(&pdev->vpci->lock) )
return -EBUSY;
if ( pdev->vpci->msix != msix )
{
spin_unlock(&pdev->vpci->lock);
return -EAGAIN;
}
}
}
return 0;
}
#endif /* CONFIG_HAS_VPCI */