]> rtime.felk.cvut.cz Git - jailhouse.git/commitdiff
x86: Add handler of accesses to PCI configuration space via I/O ports
authorIvan Kolchin <ivan.kolchin@siemens.com>
Tue, 15 Apr 2014 06:15:26 +0000 (10:15 +0400)
committerJan Kiszka <jan.kiszka@siemens.com>
Sat, 19 Apr 2014 06:43:31 +0000 (08:43 +0200)
Guest attempts to access ports 0xcf8 and 0xcfc are processed. String
and REP-prefixed instructions are not supported for this space. Ownership
of a device a cell tries to access to is checked. If the cell doesn't own it,
then hypervisor returns 0xFFFFFFFF to it. All read accesses to owned device are
not restricted. Writes all 1's to specific registers such as BARs or expansion
ROM address are not currently supported.
Writes to registers are moderated by white lists.

Signed-off-by: Ivan Kolchin <ivan.kolchin@siemens.com>
Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
hypervisor/arch/x86/Makefile
hypervisor/arch/x86/include/asm/cell.h
hypervisor/arch/x86/include/asm/pci.h [new file with mode: 0644]
hypervisor/arch/x86/pci.c [new file with mode: 0644]
hypervisor/arch/x86/vmx.c
hypervisor/control.c
hypervisor/include/jailhouse/pci.h [new file with mode: 0644]
hypervisor/pci.c [new file with mode: 0644]

index e4a0e3ce39b32aaa3a6f51c7f5223892296cac08..6c17a53767cf901f6c2c5df82bb0169a22fbedd1 100644 (file)
@@ -13,4 +13,4 @@
 always := built-in.o
 
 obj-y := apic.o dbg-write.o entry.o setup.o vmx.o control.o mmio.o \
-        ../../acpi.o vtd.o paging.o
+        ../../acpi.o vtd.o paging.o ../../pci.o pci.o
index 50d419914c2d54849458ea6fa05ad84db0d1b269..1905903cba424890343954437eb5ab98aa69e833 100644 (file)
 #include <jailhouse/cell-config.h>
 #include <jailhouse/hypercall.h>
 
+/**
+ * struct cell - cell-related state information
+ * ...
+ * @pci_addr_port_val: virtual address port for PCI config space
+ * ...
+ */
+/* TODO: factor out arch-independent bits, define struct arch_cell */
 struct cell {
        struct {
                /* should be first as it requires page alignment */
@@ -38,6 +45,8 @@ struct cell {
 
        struct cell *next;
 
+       u32 pci_addr_port_val;
+
        union {
                struct jailhouse_comm_region comm_region;
                u8 padding[PAGE_SIZE];
diff --git a/hypervisor/arch/x86/include/asm/pci.h b/hypervisor/arch/x86/include/asm/pci.h
new file mode 100644 (file)
index 0000000..382cc9a
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Jailhouse, a Linux-based partitioning hypervisor
+ *
+ * Copyright (c) Siemens AG, 2014
+ *
+ * Authors:
+ *  Ivan Kolchin <ivan.kolchin@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#ifndef _JAILHOUSE_ASM_PCI_H
+#define _JAILHOUSE_ASM_PCI_H
+
+#include <asm/percpu.h>
+#include <asm/types.h>
+
+/* --- PCI configuration ports --- */
+#define PCI_REG_ADDR_PORT              0xcf8
+#define PCI_REG_DATA_PORT              0xcfc
+
+/* --- Address register fields --- */
+/* Bits 31: Enable bit*/
+#define PCI_ADDR_ENABLE                        0x80000000
+/* Bits 23-16: Bus number */
+#define PCI_ADDR_BUS_MASK              0x00ff0000
+/* Bits 15-11: Device number */
+#define PCI_ADDR_DEV_MASK              0x0000f800
+/* Bits 10-8: Function number */
+#define PCI_ADDR_FUNC_MASK             0x00000700
+/* Bits 7-2: Register number */
+#define PCI_ADDR_REGNUM_MASK           0x000000fc
+#define PCI_ADDR_VALID_MASK \
+       (PCI_ADDR_ENABLE | PCI_ADDR_BUS_MASK | PCI_ADDR_DEV_MASK | \
+        PCI_ADDR_FUNC_MASK | PCI_ADDR_REGNUM_MASK)
+#define PCI_ADDR_BDF_SHIFT             8
+
+int x86_pci_config_handler(struct registers *guest_regs, struct cell *cell,
+                          u16 port, bool dir_in, unsigned int size);
+
+#endif /* !_JAILHOUSE_ASM_PCI_H */
diff --git a/hypervisor/arch/x86/pci.c b/hypervisor/arch/x86/pci.c
new file mode 100644 (file)
index 0000000..e8815f2
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * Jailhouse, a Linux-based partitioning hypervisor
+ *
+ * Copyright (c) Siemens AG, 2014
+ *
+ * Authors:
+ *  Ivan Kolchin <ivan.kolchin@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include <jailhouse/pci.h>
+#include <jailhouse/utils.h>
+#include <asm/io.h>
+#include <asm/pci.h>
+
+/* protects the root bridge's PIO interface to the PCI config space */
+static DEFINE_SPINLOCK(pci_lock);
+
+/**
+ * set_rax_reg() - Set value of RAX in guest register set
+ * @guest_regs:        Guest register set
+ * @value_new: New value to be written
+ * @size:      Access size (1, 2 or 4 bytes)
+ */
+static void set_rax_reg(struct registers *guest_regs,
+       u32 value_new, u8 size)
+{
+       u64 value_old = guest_regs->rax;
+       /* 32-bit access is special, since it clears all the upper
+        *  part of RAX. Another types of access leave it intact */
+       u64 mask = (size == 4 ? BYTE_MASK(8) : BYTE_MASK(size));
+
+       guest_regs->rax = (value_old & ~mask) | (value_new & mask);
+}
+
+/**
+ * get_rax_reg() - Get value of RAX from guest register set
+ * @guest_regs:        Guest register set
+ * @size:      Access size (1, 2 or 4 bytes)
+ *
+ * Return: Register value
+ */
+static u32 get_rax_reg(struct registers *guest_regs, u8 size)
+{
+       return guest_regs->rax & BYTE_MASK(size);
+}
+
+/**
+ * data_port_in_handler() - Handler for IN accesses to data port
+ * @guest_regs:        Guest register set
+ * @port:      I/O port number
+ * @size:      Access size (1, 2 or 4 bytes)
+ * @device:    Structure describing PCI device
+ */
+static int
+data_port_in_handler(struct registers *guest_regs, u16 port, unsigned int size,
+                    const struct jailhouse_pci_device *device)
+{
+       u32 reg_data;
+
+       if (!device)
+               reg_data = -1;
+       else
+               reg_data = inl(PCI_REG_DATA_PORT);
+       reg_data >>= (port - PCI_REG_DATA_PORT) * 8;
+
+       set_rax_reg(guest_regs, reg_data, size);
+
+       return 1;
+}
+
+/**
+ * data_port_out_handler() - Handler for OUT accesses to data port
+ * @guest_regs:        Guest register set
+ * @port:      I/O port number
+ * @size:      Access size (1, 2 or 4 bytes)
+ * @device:    Structure describing PCI device
+ * @reg_num:   Register number in PCI configuration space
+ */
+static int data_port_out_handler(struct registers *guest_regs, u16 port,
+                                unsigned int size,
+                                const struct jailhouse_pci_device *device,
+                                u8 reg_num)
+{
+       u32 reg_data;
+
+       if (!device)
+               return 1;
+
+       reg_data = get_rax_reg(guest_regs, size);
+
+       if (reg_num < PCI_CONFIG_HEADER_SIZE) {
+               if (pci_cfg_write_allowed(device->type, reg_num,
+                                         port - PCI_REG_DATA_PORT, size)) {
+                       if (size == 1)
+                               outb(reg_data, port);
+                       else if (size == 2)
+                               outw(reg_data, port);
+                       else if (size == 4)
+                               outl(reg_data, port);
+                       return 1;
+               }
+       }
+
+       return -1;
+}
+
+/**
+ * x86_pci_config_handler() - Handler for accesses to PCI config space
+ * @guest_regs:                Guest registers
+ * @cell:              Issuing cell
+ * @port:              I/O port number
+ * @dir_in:            True for input, false for output
+ * @size:              Size of access in bytes (1, 2 or 4 bytes)
+ *
+ * Return: 1 if handled successfully, 0 if unhandled, -1 on access error
+ */
+int x86_pci_config_handler(struct registers *guest_regs, struct cell *cell,
+                          u16 port, bool dir_in, unsigned int size)
+{
+       const struct jailhouse_pci_device *device = NULL;
+       u32 addr_port_val;
+       int result = 0;
+       u8 reg_num;
+       u16 bdf;
+
+       if (port == PCI_REG_ADDR_PORT) {
+               /* only 4-byte accesses are valid */
+               if (size != 4)
+                       return -1;
+
+               if (dir_in)
+                       set_rax_reg(guest_regs, cell->pci_addr_port_val, size);
+               else
+                       cell->pci_addr_port_val =
+                               get_rax_reg(guest_regs, size);
+               result = 1;
+       } else if (port >= PCI_REG_DATA_PORT &&
+                  port < (PCI_REG_DATA_PORT + 4)) {
+               /* overflowing accesses are invalid */
+               if (port + size > PCI_REG_DATA_PORT + 4)
+                       return -1;
+
+               /*
+                * Decode which register in PCI config space is accessed. It is
+                * essential to store the address port value locally so that we
+                * are not affected by concurrent manipulations by other CPUs
+                * of this cell.
+                */
+               addr_port_val = cell->pci_addr_port_val;
+               reg_num = addr_port_val & PCI_ADDR_REGNUM_MASK;
+
+               /* get device (NULL if it doesn't belong to the cell) */
+               bdf = addr_port_val >> PCI_ADDR_BDF_SHIFT;
+               device = pci_get_assigned_device(cell, bdf);
+
+               spin_lock(&pci_lock);
+
+               outl(addr_port_val & PCI_ADDR_VALID_MASK, PCI_REG_ADDR_PORT);
+
+               if (dir_in)
+                       result = data_port_in_handler(guest_regs, port, size,
+                                                     device);
+               else
+                       result = data_port_out_handler(guest_regs, port, size,
+                                                      device, reg_num);
+
+               spin_unlock(&pci_lock);
+       }
+
+       return result;
+}
index 8121d45f34ffd089b6d9b60f566f31496a6e36b2..01d06404f70e5486ea1a22981c1ff888aabbec4f 100644 (file)
@@ -21,6 +21,8 @@
 #include <asm/control.h>
 #include <asm/vmx.h>
 #include <asm/vtd.h>
+#include <asm/io.h>
+#include <asm/pci.h>
 
 static const struct segment invalid_seg = {
        .access_rights = 0x10000
@@ -1015,6 +1017,32 @@ static void dump_guest_regs(struct registers *guest_regs)
        panic_printk("EFER: %p\n", vmcs_read64(GUEST_IA32_EFER));
 }
 
+static bool vmx_handle_io_access(struct registers *guest_regs,
+                                struct per_cpu *cpu_data)
+{
+       /* parse exit qualification for I/O instructions (see SDM, 27.2.1 ) */
+       u64 exitq = vmcs_read64(EXIT_QUALIFICATION);
+       u16 port = (exitq >> 16) & 0xFFFF;
+       bool dir_in = (exitq & 0x8) >> 3;
+       unsigned int size = (exitq & 0x3) + 1;
+
+       vmx_skip_emulated_instruction(vmcs_read64(VM_EXIT_INSTRUCTION_LEN));
+
+       /* string and REP-prefixed instructions are not supported */
+       if (exitq & 0x30)
+               return false;
+
+       if (x86_pci_config_handler(guest_regs, cpu_data->cell, port, dir_in,
+                                  size) == 1)
+               return true;
+
+       panic_printk("FATAL: Invalid PIO %s, port: %x size: %d\n",
+                    dir_in ? "read" : "write", port, size);
+       panic_printk("PCI address port: %x\n",
+                    cpu_data->cell->pci_addr_port_val);
+       return false;
+}
+
 void vmx_handle_exit(struct registers *guest_regs, struct per_cpu *cpu_data)
 {
        u32 reason = vmcs_read32(VM_EXIT_REASON);
@@ -1102,6 +1130,10 @@ void vmx_handle_exit(struct registers *guest_regs, struct per_cpu *cpu_data)
                             "xcr[%d] = %08x:%08x\n", guest_regs->rcx,
                             guest_regs->rdx, guest_regs->rax);
                break;
+       case EXIT_REASON_IO_INSTRUCTION:
+               if (vmx_handle_io_access(guest_regs, cpu_data))
+                       return;
+               break;
        default:
                panic_printk("FATAL: Unhandled VM-Exit, reason %d, ",
                             (u16)reason);
index 23b6b870bcf6644f640e39ad6d73e7fc9cafce3c..ebc3dd954ee023ef0f189c706542a5eed6533aa8 100644 (file)
@@ -76,6 +76,7 @@ retry:
        return id;
 }
 
+/* cell must be zero-initialized */
 int cell_init(struct cell *cell, bool copy_cpu_set)
 {
        const unsigned long *config_cpu_set =
diff --git a/hypervisor/include/jailhouse/pci.h b/hypervisor/include/jailhouse/pci.h
new file mode 100644 (file)
index 0000000..a11b5d2
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Jailhouse, a Linux-based partitioning hypervisor
+ *
+ * Copyright (c) Siemens AG, 2014
+ *
+ * Authors:
+ *  Ivan Kolchin <ivan.kolchin@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#ifndef _JAILHOUSE_PCI_H
+#define _JAILHOUSE_PCI_H
+
+#include <asm/cell.h>
+
+#define PCI_CONFIG_HEADER_SIZE         0x40
+
+const struct jailhouse_pci_device *
+pci_get_assigned_device(const struct cell *cell, u16 bdf);
+
+bool pci_cfg_write_allowed(u32 type, u8 reg_num, unsigned int reg_bias,
+                          unsigned int size);
+
+#endif /* !_JAILHOUSE_PCI_H */
diff --git a/hypervisor/pci.c b/hypervisor/pci.c
new file mode 100644 (file)
index 0000000..a1ad804
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Jailhouse, a Linux-based partitioning hypervisor
+ *
+ * Copyright (c) Siemens AG, 2014
+ *
+ * Authors:
+ *  Ivan Kolchin <ivan.kolchin@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include <jailhouse/pci.h>
+#include <jailhouse/utils.h>
+
+/* entry for PCI config space whitelist (granting access) */
+struct pci_cfg_access {
+       u32 reg_num; /** Register number (4-byte aligned) */
+       u32 mask; /** Bit set: access allowed */
+};
+
+/* --- Whilelist for writing to PCI config space registers --- */
+/* Type 1: Endpoints */
+static const struct pci_cfg_access endpoint_write_access[] = {
+       { 0x04, 0xffffffff }, /* Command, Status */
+       { 0x0c, 0xff000000 }, /* BIST */
+       { 0x3c, 0x000000ff }, /* Int Line */
+};
+/* Type 2: Bridges */
+static const struct pci_cfg_access bridge_write_access[] = {
+       { 0x04, 0xffffffff }, /* Command, Status */
+       { 0x0c, 0xff000000 }, /* BIST */
+       { 0x3c, 0xffff00ff }, /* Int Line, Bridge Control */
+};
+
+/**
+ * pci_get_assigned_device() - Look up device owned by a cell
+ * @cell:      Owning cell
+ * @bdf:       16-bit bus/device/function ID
+ *
+ * Return: Valid pointer - owns, NULL - doesn't own.
+ */
+const struct jailhouse_pci_device *
+pci_get_assigned_device(const struct cell *cell, u16 bdf)
+{
+       const struct jailhouse_pci_device *device =
+               jailhouse_cell_pci_devices(cell->config);
+       u32 n;
+
+       for (n = 0; n < cell->config->num_pci_devices; n++)
+               if (((device[n].bus << 8) | device[n].devfn) == bdf)
+                       return &device[n];
+
+       return NULL;
+}
+
+/**
+ * pci_cfg_write_allowed() - Check general config space write permission
+ * @type:      JAILHOUSE_PCI_TYPE_DEVICE or JAILHOUSE_PCI_TYPE_BRIDGE
+ * @reg_num:   Register number (4-byte aligned)
+ * @bias:      Bias from register base address in bytes
+ * @size:      Access size (1, 2 or 4 bytes)
+ *
+ * Return: True if writing is allowed, false otherwise.
+ */
+bool pci_cfg_write_allowed(u32 type, u8 reg_num, unsigned int reg_bias,
+                          unsigned int size)
+{
+       /* initialize list to work around wrong compiler warning */
+       const struct pci_cfg_access *list = NULL;
+       unsigned int n, len = 0;
+
+       if (type == JAILHOUSE_PCI_TYPE_DEVICE) {
+               list = endpoint_write_access;
+               len = ARRAY_SIZE(endpoint_write_access);
+       } else if (type == JAILHOUSE_PCI_TYPE_BRIDGE) {
+               list = bridge_write_access;
+               len = ARRAY_SIZE(bridge_write_access);
+       }
+
+       for (n = 0; n < len; n++)
+               if (list[n].reg_num == reg_num)
+                       return ((list[n].mask >> (reg_bias * 8)) &
+                                BYTE_MASK(size)) == BYTE_MASK(size);
+
+       return false;
+}