diff options
Diffstat (limited to 'kvm-all.c')
-rw-r--r-- | kvm-all.c | 236 |
1 files changed, 226 insertions, 10 deletions
@@ -24,6 +24,7 @@ #include "qemu-barrier.h" #include "sysemu.h" #include "hw/hw.h" +#include "hw/msi.h" #include "gdbstub.h" #include "kvm.h" #include "bswap.h" @@ -48,6 +49,8 @@ do { } while (0) #endif +#define KVM_MSI_HASHTAB_SIZE 256 + typedef struct KVMSlot { target_phys_addr_t start_addr; @@ -59,6 +62,11 @@ typedef struct KVMSlot typedef struct kvm_dirty_log KVMDirtyLog; +typedef struct KVMMSIRoute { + struct kvm_irq_routing_entry kroute; + QTAILQ_ENTRY(KVMMSIRoute) entry; +} KVMMSIRoute; + struct KVMState { KVMSlot slots[32]; @@ -86,7 +94,9 @@ struct KVMState struct kvm_irq_routing *irq_routes; int nr_allocated_irq_routes; uint32_t *used_gsi_bitmap; - unsigned int max_gsi; + unsigned int gsi_count; + QTAILQ_HEAD(msi_hashtab, KVMMSIRoute) msi_hashtab[KVM_MSI_HASHTAB_SIZE]; + bool direct_msi; #endif }; @@ -859,14 +869,17 @@ int kvm_irqchip_set_irq(KVMState *s, int irq, int level) #ifdef KVM_CAP_IRQ_ROUTING static void set_gsi(KVMState *s, unsigned int gsi) { - assert(gsi < s->max_gsi); - s->used_gsi_bitmap[gsi / 32] |= 1U << (gsi % 32); } +static void clear_gsi(KVMState *s, unsigned int gsi) +{ + s->used_gsi_bitmap[gsi / 32] &= ~(1U << (gsi % 32)); +} + static void kvm_init_irq_routing(KVMState *s) { - int gsi_count; + int gsi_count, i; gsi_count = kvm_check_extension(s, KVM_CAP_IRQ_ROUTING); if (gsi_count > 0) { @@ -875,7 +888,7 @@ static void kvm_init_irq_routing(KVMState *s) /* Round up so we can search ints using ffs */ gsi_bits = ALIGN(gsi_count, 32); s->used_gsi_bitmap = g_malloc0(gsi_bits / 8); - s->max_gsi = gsi_bits; + s->gsi_count = gsi_count; /* Mark any over-allocated bits as already in use */ for (i = gsi_count; i < gsi_bits; i++) { @@ -886,9 +899,24 @@ static void kvm_init_irq_routing(KVMState *s) s->irq_routes = g_malloc0(sizeof(*s->irq_routes)); s->nr_allocated_irq_routes = 0; + if (!s->direct_msi) { + for (i = 0; i < KVM_MSI_HASHTAB_SIZE; i++) { + QTAILQ_INIT(&s->msi_hashtab[i]); + } + } + kvm_arch_init_irq_routing(s); } +static void kvm_irqchip_commit_routes(KVMState *s) +{ + int ret; + + s->irq_routes->flags = 0; + ret = kvm_vm_ioctl(s, KVM_SET_GSI_ROUTING, s->irq_routes); + assert(ret == 0); +} + static void kvm_add_routing_entry(KVMState *s, struct kvm_irq_routing_entry *entry) { @@ -914,12 +942,16 @@ static void kvm_add_routing_entry(KVMState *s, new->u = entry->u; set_gsi(s, entry->gsi); + + kvm_irqchip_commit_routes(s); } -void kvm_irqchip_add_route(KVMState *s, int irq, int irqchip, int pin) +void kvm_irqchip_add_irq_route(KVMState *s, int irq, int irqchip, int pin) { struct kvm_irq_routing_entry e; + assert(pin < s->gsi_count); + e.gsi = irq; e.type = KVM_IRQ_ROUTING_IRQCHIP; e.flags = 0; @@ -928,10 +960,167 @@ void kvm_irqchip_add_route(KVMState *s, int irq, int irqchip, int pin) kvm_add_routing_entry(s, &e); } -int kvm_irqchip_commit_routes(KVMState *s) +void kvm_irqchip_release_virq(KVMState *s, int virq) { - s->irq_routes->flags = 0; - return kvm_vm_ioctl(s, KVM_SET_GSI_ROUTING, s->irq_routes); + struct kvm_irq_routing_entry *e; + int i; + + for (i = 0; i < s->irq_routes->nr; i++) { + e = &s->irq_routes->entries[i]; + if (e->gsi == virq) { + s->irq_routes->nr--; + *e = s->irq_routes->entries[s->irq_routes->nr]; + } + } + clear_gsi(s, virq); + + kvm_irqchip_commit_routes(s); +} + +static unsigned int kvm_hash_msi(uint32_t data) +{ + /* This is optimized for IA32 MSI layout. However, no other arch shall + * repeat the mistake of not providing a direct MSI injection API. */ + return data & 0xff; +} + +static void kvm_flush_dynamic_msi_routes(KVMState *s) +{ + KVMMSIRoute *route, *next; + unsigned int hash; + + for (hash = 0; hash < KVM_MSI_HASHTAB_SIZE; hash++) { + QTAILQ_FOREACH_SAFE(route, &s->msi_hashtab[hash], entry, next) { + kvm_irqchip_release_virq(s, route->kroute.gsi); + QTAILQ_REMOVE(&s->msi_hashtab[hash], route, entry); + g_free(route); + } + } +} + +static int kvm_irqchip_get_virq(KVMState *s) +{ + uint32_t *word = s->used_gsi_bitmap; + int max_words = ALIGN(s->gsi_count, 32) / 32; + int i, bit; + bool retry = true; + +again: + /* Return the lowest unused GSI in the bitmap */ + for (i = 0; i < max_words; i++) { + bit = ffs(~word[i]); + if (!bit) { + continue; + } + + return bit - 1 + i * 32; + } + if (!s->direct_msi && retry) { + retry = false; + kvm_flush_dynamic_msi_routes(s); + goto again; + } + return -ENOSPC; + +} + +static KVMMSIRoute *kvm_lookup_msi_route(KVMState *s, MSIMessage msg) +{ + unsigned int hash = kvm_hash_msi(msg.data); + KVMMSIRoute *route; + + QTAILQ_FOREACH(route, &s->msi_hashtab[hash], entry) { + if (route->kroute.u.msi.address_lo == (uint32_t)msg.address && + route->kroute.u.msi.address_hi == (msg.address >> 32) && + route->kroute.u.msi.data == msg.data) { + return route; + } + } + return NULL; +} + +int kvm_irqchip_send_msi(KVMState *s, MSIMessage msg) +{ + struct kvm_msi msi; + KVMMSIRoute *route; + + if (s->direct_msi) { + msi.address_lo = (uint32_t)msg.address; + msi.address_hi = msg.address >> 32; + msi.data = msg.data; + msi.flags = 0; + memset(msi.pad, 0, sizeof(msi.pad)); + + return kvm_vm_ioctl(s, KVM_SIGNAL_MSI, &msi); + } + + route = kvm_lookup_msi_route(s, msg); + if (!route) { + int virq; + + virq = kvm_irqchip_get_virq(s); + if (virq < 0) { + return virq; + } + + route = g_malloc(sizeof(KVMMSIRoute)); + route->kroute.gsi = virq; + route->kroute.type = KVM_IRQ_ROUTING_MSI; + route->kroute.flags = 0; + route->kroute.u.msi.address_lo = (uint32_t)msg.address; + route->kroute.u.msi.address_hi = msg.address >> 32; + route->kroute.u.msi.data = msg.data; + + kvm_add_routing_entry(s, &route->kroute); + + QTAILQ_INSERT_TAIL(&s->msi_hashtab[kvm_hash_msi(msg.data)], route, + entry); + } + + assert(route->kroute.type == KVM_IRQ_ROUTING_MSI); + + return kvm_irqchip_set_irq(s, route->kroute.gsi, 1); +} + +int kvm_irqchip_add_msi_route(KVMState *s, MSIMessage msg) +{ + struct kvm_irq_routing_entry kroute; + int virq; + + if (!kvm_irqchip_in_kernel()) { + return -ENOSYS; + } + + virq = kvm_irqchip_get_virq(s); + if (virq < 0) { + return virq; + } + + kroute.gsi = virq; + kroute.type = KVM_IRQ_ROUTING_MSI; + kroute.flags = 0; + kroute.u.msi.address_lo = (uint32_t)msg.address; + kroute.u.msi.address_hi = msg.address >> 32; + kroute.u.msi.data = msg.data; + + kvm_add_routing_entry(s, &kroute); + + return virq; +} + +static int kvm_irqchip_assign_irqfd(KVMState *s, int fd, int virq, bool assign) +{ + struct kvm_irqfd irqfd = { + .fd = fd, + .gsi = virq, + .flags = assign ? 0 : KVM_IRQFD_FLAG_DEASSIGN, + }; + + if (!kvm_irqchip_in_kernel()) { + return -ENOSYS; + } + + return kvm_vm_ioctl(s, KVM_IRQFD, &irqfd); } #else /* !KVM_CAP_IRQ_ROUTING */ @@ -939,8 +1128,33 @@ int kvm_irqchip_commit_routes(KVMState *s) static void kvm_init_irq_routing(KVMState *s) { } + +int kvm_irqchip_send_msi(KVMState *s, MSIMessage msg) +{ + abort(); +} + +int kvm_irqchip_add_msi_route(KVMState *s, MSIMessage msg) +{ + abort(); +} + +static int kvm_irqchip_assign_irqfd(KVMState *s, int fd, int virq, bool assign) +{ + abort(); +} #endif /* !KVM_CAP_IRQ_ROUTING */ +int kvm_irqchip_add_irqfd(KVMState *s, int fd, int virq) +{ + return kvm_irqchip_assign_irqfd(s, fd, virq, true); +} + +int kvm_irqchip_remove_irqfd(KVMState *s, int fd, int virq) +{ + return kvm_irqchip_assign_irqfd(s, fd, virq, false); +} + static int kvm_irqchip_create(KVMState *s) { QemuOptsList *list = qemu_find_opts("machine"); @@ -948,7 +1162,7 @@ static int kvm_irqchip_create(KVMState *s) if (QTAILQ_EMPTY(&list->head) || !qemu_opt_get_bool(QTAILQ_FIRST(&list->head), - "kernel_irqchip", false) || + "kernel_irqchip", true) || !kvm_check_extension(s, KVM_CAP_IRQCHIP)) { return 0; } @@ -1072,6 +1286,8 @@ int kvm_init(void) s->pit_state2 = kvm_check_extension(s, KVM_CAP_PIT_STATE2); #endif + s->direct_msi = (kvm_check_extension(s, KVM_CAP_SIGNAL_MSI) > 0); + ret = kvm_arch_init(s); if (ret < 0) { goto err; |