]> rtime.felk.cvut.cz Git - jailhouse.git/commitdiff
x86: Virtualize IOAPIC redir table for interrupt remapping support
authorJan Kiszka <jan.kiszka@siemens.com>
Sat, 7 Jun 2014 12:26:34 +0000 (14:26 +0200)
committerJan Kiszka <jan.kiszka@siemens.com>
Tue, 26 Aug 2014 17:56:37 +0000 (19:56 +0200)
Handover the IOAPIC on hypervisor setup by first masking all pins, then
requesting interrupt remapping from VT-d and finally reprogramming them
according to the index that VT-d reported. If vtd_map_interrupt returns
-ENOSYS, unconditionally right now due to a missing implementation,
later on when running in QEMU, we continue to write the redirection
table entry unmodified.

As we may lose edge-triggered interrupts while they are makes, we inject
them unconditionally into the target CPU. This may cause one spurious
interrupt per handover to/from the hypervisor, but Linux can deal with
that.

Whenever a cell is added or removed, we rewrite all root cell IOAPIC
redir table entries in order to validate them regarding the target CPU
masks.

Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
hypervisor/arch/x86/control.c
hypervisor/arch/x86/include/asm/cell.h
hypervisor/arch/x86/include/asm/ioapic.h
hypervisor/arch/x86/ioapic.c

index 824d0f3ef56a9084b7923f150058e7187d3f0ace..f88d2a0182116fc99e66fa1fd4f06aa7e45e9ca8 100644 (file)
@@ -112,11 +112,14 @@ void arch_config_commit(struct per_cpu *cpu_data,
        vmx_invept();
 
        vtd_config_commit(cell_added_removed);
+       ioapic_config_commit(cell_added_removed);
 }
 
 void arch_shutdown(void)
 {
+       ioapic_prepare_handover();
        vtd_shutdown();
+       ioapic_shutdown();
 }
 
 void arch_suspend_cpu(unsigned int cpu_id)
index b7f1ecbc7751b3c26606690f9d7c484c68e869a4..1767c691aabd0e62e148b5bacff9cd67b9951e33 100644 (file)
@@ -53,6 +53,7 @@ struct cell {
        u32 pci_addr_port_val;
 
        u32 ioapic_index_reg_val;
+       u16 ioapic_id;
        u64 ioapic_pin_bitmap;
 
        union {
index 5dc5d3471127223503d2bb8676dbbd26761a0f05..58e30b2d721840ea09f70d2a38722f41f925529a 100644 (file)
 
 #include <asm/cell.h>
 
+#define IOAPIC_NUM_PINS                24
+
+union ioapic_redir_entry {
+       struct {
+               u8 vector;
+               u8 delivery_mode:3;
+               u8 dest_logical:1;
+               u8 delivery_status:1;
+               u8 pin_polarity:1;
+               u8 remote_irr:1;
+               u8 level_triggered:1;
+               u32 mask:1;
+               u32 reserved:31;
+               u8 edid;
+               u8 destination;
+       } __attribute__((packed)) native;
+       struct {
+               u8 vector;
+               u8 zero:3;
+               u8 int_index15:1;
+               u8 delivery_status:1;
+               u8 pin_polarity:1;
+               u8 remote_irr:1;
+               u8 level_triggered:1;
+               u32 mask:1;
+               u32 reserved:31;
+               u16 remapped:1;
+               u16 int_index:15;
+       } __attribute__((packed)) remap;
+       u32 raw[2];
+} __attribute__((packed));
+
 int ioapic_init(void);
+void ioapic_prepare_handover(void);
 
 void ioapic_cell_init(struct cell *cell);
 void ioapic_cell_exit(struct cell *cell);
 
+void ioapic_config_commit(struct cell *cell_added_removed);
+
 int ioapic_access_handler(struct cell *cell, bool is_write, u64 addr,
                          u32 *value);
+
+void ioapic_shutdown(void);
index f0dc1f8978395195e7ae001d2578122cf2c4e731..acd179cfa59f3f208f65b904bff9f2e5b87a0233 100644 (file)
  * the COPYING file in the top-level directory.
  */
 
+#include <jailhouse/control.h>
 #include <jailhouse/mmio.h>
 #include <jailhouse/printk.h>
+#include <asm/apic.h>
 #include <asm/ioapic.h>
 #include <asm/spinlock.h>
+#include <asm/vtd.h>
 
 #include <jailhouse/cell-config.h>
 
 #define IOAPIC_VER             0x01
 #define IOAPIC_REDIR_TBL_START 0x10
 #define IOAPIC_REDIR_TBL_END   0x3f
+# define IOAPIC_REDIR_MASK     (1 << 16)
+
+enum ioapic_handover {PINS_ACTIVE, PINS_MASKED};
 
 static DEFINE_SPINLOCK(ioapic_lock);
 static void *ioapic;
+static union ioapic_redir_entry shadow_redir_table[IOAPIC_NUM_PINS];
 
 static u32 ioapic_reg_read(unsigned int reg)
 {
@@ -53,22 +60,121 @@ static void ioapic_reg_write(unsigned int reg, u32 value)
        spin_unlock(&ioapic_lock);
 }
 
+static struct apic_irq_message
+ioapic_translate_redir_entry(union ioapic_redir_entry entry)
+{
+       struct apic_irq_message irq_msg;
+
+       irq_msg.vector = entry.native.vector;
+       irq_msg.delivery_mode = entry.native.delivery_mode;
+       irq_msg.level_triggered = entry.native.level_triggered;
+       irq_msg.dest_logical = entry.native.dest_logical;
+       /* align redir_hint and dest_logical - required by vtd_map_interrupt */
+       irq_msg.redir_hint = irq_msg.dest_logical;
+       irq_msg.destination = entry.native.destination;
+
+       return irq_msg;
+}
+
+static int ioapic_virt_redir_write(struct cell *cell, unsigned int reg,
+                                  u32 value)
+{
+       unsigned int pin = (reg - IOAPIC_REDIR_TBL_START) / 2;
+       struct apic_irq_message irq_msg;
+       union ioapic_redir_entry entry;
+       int result;
+
+       entry = shadow_redir_table[pin];
+       entry.raw[reg & 1] = value;
+       shadow_redir_table[pin] = entry;
+
+       /* Do not map the interrupt while masked. */
+       if (entry.native.mask) {
+               /*
+                * The mask is part of the lower 32 bits. Apply it when that
+                * register half is written.
+                */
+               if ((reg & 1) == 0)
+                       ioapic_reg_write(reg, IOAPIC_REDIR_MASK);
+               return 0;
+       }
+
+       irq_msg = ioapic_translate_redir_entry(entry);
+
+       result = vtd_map_interrupt(cell, cell->ioapic_id, pin, irq_msg);
+       // HACK for QEMU
+       if (result == -ENOSYS) {
+               ioapic_reg_write(reg, entry.raw[reg & 1]);
+               return 0;
+       }
+       if (result < 0)
+               return result;
+
+       entry.remap.zero = 0;
+       entry.remap.int_index15 = result >> 15;
+       entry.remap.remapped = 1;
+       entry.remap.int_index = result;
+       ioapic_reg_write(reg, entry.raw[reg & 1]);
+
+       return 0;
+}
+
+static void ioapic_mask_pins(u64 pin_bitmap, enum ioapic_handover handover)
+{
+       union ioapic_redir_entry entry;
+       unsigned int pin, reg;
+
+       for (pin = 0; pin < IOAPIC_NUM_PINS; pin++) {
+               if (!(pin_bitmap & (1UL << pin)))
+                       continue;
+
+               reg = IOAPIC_REDIR_TBL_START + pin * 2;
+
+               entry.raw[0] = ioapic_reg_read(reg);
+               if (entry.remap.mask)
+                       continue;
+
+               ioapic_reg_write(reg, IOAPIC_REDIR_MASK);
+
+               if (handover == PINS_MASKED) {
+                       shadow_redir_table[pin].native.mask = 1;
+               } else if (!entry.native.level_triggered) {
+                       /*
+                        * Inject edge-triggered interrupts to avoid losing
+                        * events while masked. Linux can handle rare spurious
+                        * interrupts.
+                        */
+                       entry = shadow_redir_table[pin];
+                       apic_send_irq(ioapic_translate_redir_entry(entry));
+               }
+       }
+}
+
 int ioapic_init(void)
 {
+       unsigned int index;
+       void *ioapic_page;
        int err;
 
-       ioapic = page_alloc(&remap_pool, 1);
-       if (!ioapic)
+       ioapic_page = page_alloc(&remap_pool, 1);
+       if (!ioapic_page)
                return -ENOMEM;
        err = page_map_create(&hv_paging_structs, IOAPIC_BASE_ADDR, PAGE_SIZE,
-                             (unsigned long)ioapic,
+                             (unsigned long)ioapic_page,
                              PAGE_DEFAULT_FLAGS | PAGE_FLAG_UNCACHED,
                              PAGE_MAP_NON_COHERENT);
        if (err)
                return err;
+       ioapic = ioapic_page;
 
        ioapic_cell_init(&root_cell);
 
+       for (index = 0; index < IOAPIC_NUM_PINS * 2; index++)
+               shadow_redir_table[index / 2].raw[index % 2] =
+                       ioapic_reg_read(IOAPIC_REDIR_TBL_START + index);
+
+       ioapic_prepare_handover();
+
        return 0;
 }
 
@@ -85,15 +191,34 @@ ioapic_find_config(struct jailhouse_cell_desc *config)
        return NULL;
 }
 
+void ioapic_prepare_handover(void)
+{
+       const struct jailhouse_irqchip *irqchip =
+               ioapic_find_config(root_cell.config);
+       u64 pin_bitmap = 0;
+
+       if (!ioapic)
+               return;
+       if (irqchip) {
+               pin_bitmap = irqchip->pin_bitmap;
+               ioapic_mask_pins(pin_bitmap, PINS_ACTIVE);
+       }
+       ioapic_mask_pins(~pin_bitmap, PINS_MASKED);
+}
+
 void ioapic_cell_init(struct cell *cell)
 {
        const struct jailhouse_irqchip *irqchip =
                ioapic_find_config(cell->config);
 
        if (irqchip) {
+               cell->ioapic_id = irqchip->id;
                cell->ioapic_pin_bitmap = irqchip->pin_bitmap;
-               if (cell != &root_cell)
+
+               if (cell != &root_cell) {
                        root_cell.ioapic_pin_bitmap &= ~irqchip->pin_bitmap;
+                       ioapic_mask_pins(irqchip->pin_bitmap, PINS_MASKED);
+               }
        }
 }
 
@@ -104,11 +229,44 @@ void ioapic_cell_exit(struct cell *cell)
        const struct jailhouse_irqchip *root_irqchip =
                ioapic_find_config(root_cell.config);
 
-       if (cell_irqchip && root_irqchip)
+       if (!cell_irqchip)
+               return;
+
+       ioapic_mask_pins(cell_irqchip->pin_bitmap, PINS_MASKED);
+       if (root_irqchip)
                root_cell.ioapic_pin_bitmap |= cell_irqchip->pin_bitmap &
                        root_irqchip->pin_bitmap;
 }
 
+void ioapic_config_commit(struct cell *cell_added_removed)
+{
+       const struct jailhouse_irqchip *irqchip =
+               ioapic_find_config(root_cell.config);
+       union ioapic_redir_entry entry;
+       unsigned int pin, reg;
+
+       if (!irqchip || !cell_added_removed)
+               return;
+
+       for (pin = 0; pin < IOAPIC_NUM_PINS; pin++) {
+               if (!(root_cell.ioapic_pin_bitmap & (1UL << pin)))
+                       continue;
+
+               entry = shadow_redir_table[pin];
+               reg = IOAPIC_REDIR_TBL_START + pin * 2;
+
+               /* write high word first to preserve mask initially */
+               if (ioapic_virt_redir_write(&root_cell, reg + 1,
+                                           entry.raw[1]) < 0 ||
+                   ioapic_virt_redir_write(&root_cell, reg,
+                                           entry.raw[0]) < 0) {
+                       panic_printk("FATAL: Unsupported IOAPIC state, "
+                                    "pin %d\n", pin);
+                       panic_stop(NULL);
+               }
+       }
+}
+
 /**
  * x86_ioapic_handler() - Handler for accesses to IOAPIC
  * @cell:      Request issuing cell
@@ -152,10 +310,13 @@ int ioapic_access_handler(struct cell *cell, bool is_write, u64 addr,
                if ((cell->ioapic_pin_bitmap & (1UL << entry)) == 0)
                        goto invalid_access;
 
-               if (is_write)
-                       ioapic_reg_write(index, *value);
-               else
-                       *value = ioapic_reg_read(index);
+               if (is_write) {
+                       if (ioapic_virt_redir_write(cell, index, *value) < 0)
+                               goto invalid_access;
+               } else {
+                       index -= IOAPIC_REDIR_TBL_START;
+                       *value = shadow_redir_table[index / 2].raw[index % 2];
+               }
                return 1;
        case IOAPIC_REG_EOI:
                if (!is_write || cell->ioapic_pin_bitmap == 0)
@@ -177,3 +338,15 @@ invalid_access:
                     cell->ioapic_index_reg_val);
        return -1;
 }
+
+void ioapic_shutdown(void)
+{
+       int index;
+
+       if (!ioapic)
+               return;
+       /* write in reverse order to preserve the mask as long as needed */
+       for (index = IOAPIC_NUM_PINS * 2 - 1; index >= 0; index--)
+               ioapic_reg_write(IOAPIC_REDIR_TBL_START + index,
+                       shadow_redir_table[index / 2].raw[index % 2]);
+}