From patchwork Tue Sep 27 15:57:15 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Roger_Pau_Monn=C3=A9?= X-Patchwork-Id: 9352197 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 066BF60757 for ; Tue, 27 Sep 2016 16:00:57 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E811E2927A for ; Tue, 27 Sep 2016 16:00:56 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id DCC0C2928E; Tue, 27 Sep 2016 16:00:56 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.xenproject.org (lists.xenproject.org [192.237.175.120]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 15F4A2928F for ; Tue, 27 Sep 2016 16:00:53 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=lists.xenproject.org) by lists.xenproject.org with esmtp (Exim 4.84_2) (envelope-from ) id 1boumE-0008AG-PL; Tue, 27 Sep 2016 15:58:38 +0000 Received: from mail6.bemta3.messagelabs.com ([195.245.230.39]) by lists.xenproject.org with esmtp (Exim 4.84_2) (envelope-from ) id 1boumC-00085P-Li for xen-devel@lists.xenproject.org; Tue, 27 Sep 2016 15:58:36 +0000 Received: from [85.158.137.68] by server-8.bemta-3.messagelabs.com id 79/D8-10540-B279AE75; Tue, 27 Sep 2016 15:58:35 +0000 X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFtrKIsWRWlGSWpSXmKPExsXitHSDva7G9Ff hBu8/cVl83zKZyYHR4/CHKywBjFGsmXlJ+RUJrBnrlx1gL9j/grFi4q5ZjA2M/+YydjFyckgI +EtM+n+RHcRmE9CRuDh3J1sXIweHiICKxO29Bl2MXBzMAjcYJX5/amQGqREWiJf4+f4fWD2Lg KrE7I1fmUBsXgFXia9ffrJDzNSVeHjuNyuIzQkU721bAFYjJOAicXfmR1aIekGJkzOfsIDYzA KaEq3bf7ND2PISzVtnM0PUK0r0z3sAdo+EALfE3277CYz8s5B0z0LSPQtJ9wJG5lWM6sWpRWW pRbqGeklFmekZJbmJmTm6hgbGermpxcWJ6ak5iUnFesn5uZsYgUHIAAQ7GJd/dDrEKMnBpCTK q9H+KlyILyk/pTIjsTgjvqg0J7X4EKMMB4eSBK/HVKCcYFFqempFWmYOMB5g0hIcPEoivDzTg NK8xQWJucWZ6RCpU4y6HFsW3FjLJMSSl5+XKiXO2wAyQwCkKKM0D24ELDYvMcpKCfMyAh0lxF OQWpSbWYIq/4pRnINRSZhXAWQVT2ZeCdymV0BHMAEdsfTEC5AjShIRUlINjEy8ljem6ke90ZK 6zFe/i1vzr0ej5fLC+PnCPev8N5X77Wr7/1nzZ6vwCrXK/9K8huxRrsnz15bMtrinZ215Za65 x6HPr8u978k/9r8uo7C1aMGMA/ZTft+Tcdog7ijnWHnET5BzxuXKivSpvh+u1DBUMAoWBr6cd rh3ZeS/hS8Y7p05Pa9FSImlOCPRUIu5qDgRAAbJOpvIAgAA X-Env-Sender: prvs=071b8e69e=roger.pau@citrix.com X-Msg-Ref: server-12.tower-31.messagelabs.com!1474991908!46201907!2 X-Originating-IP: [66.165.176.63] X-SpamReason: No, hits=0.0 required=7.0 tests=sa_preprocessor: VHJ1c3RlZCBJUDogNjYuMTY1LjE3Ni42MyA9PiAzMDYwNDg=\n, received_headers: No Received headers X-StarScan-Received: X-StarScan-Version: 8.84; banners=-,-,- X-VirusChecked: Checked Received: (qmail 17882 invoked from network); 27 Sep 2016 15:58:31 -0000 Received: from smtp02.citrix.com (HELO SMTP02.CITRIX.COM) (66.165.176.63) by server-12.tower-31.messagelabs.com with RC4-SHA encrypted SMTP; 27 Sep 2016 15:58:31 -0000 X-IronPort-AV: E=Sophos;i="5.30,405,1470700800"; d="scan'208";a="389169735" From: Roger Pau Monne To: Date: Tue, 27 Sep 2016 17:57:15 +0200 Message-ID: <1474991845-27962-21-git-send-email-roger.pau@citrix.com> X-Mailer: git-send-email 2.7.4 (Apple Git-66) In-Reply-To: <1474991845-27962-1-git-send-email-roger.pau@citrix.com> References: <1474991845-27962-1-git-send-email-roger.pau@citrix.com> MIME-Version: 1.0 X-DLP: MIA1 Cc: Andrew Cooper , Paul Durrant , Jan Beulich , boris.ostrovsky@oracle.com, Roger Pau Monne Subject: [Xen-devel] [PATCH v2 20/30] xen/x86: add the basic infrastructure to import QEMU passthrough code X-BeenThere: xen-devel@lists.xen.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Xen developer discussion List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Errors-To: xen-devel-bounces@lists.xen.org Sender: "Xen-devel" X-Virus-Scanned: ClamAV using ClamSMTP Most of this code has been picked up from QEMU and modified so it can be plugged into the internal Xen IO handlers. The structure of the handlers has been keep quite similar to QEMU, so existing handlers can be imported without a lot of effort. Signed-off-by: Roger Pau MonnĂ© --- Cc: Jan Beulich Cc: Andrew Cooper Cc: Paul Durrant --- docs/misc/xen-command-line.markdown | 8 + xen/arch/x86/hvm/hvm.c | 2 + xen/arch/x86/hvm/io.c | 621 ++++++++++++++++++++++++++++++++++++ xen/include/asm-x86/hvm/domain.h | 4 + xen/include/asm-x86/hvm/io.h | 176 ++++++++++ xen/include/xen/pci.h | 5 + 6 files changed, 816 insertions(+) diff --git a/docs/misc/xen-command-line.markdown b/docs/misc/xen-command-line.markdown index 59d7210..78130c8 100644 --- a/docs/misc/xen-command-line.markdown +++ b/docs/misc/xen-command-line.markdown @@ -670,6 +670,14 @@ Flag that makes a 64bit dom0 boot in PVH mode. No 32bit support at present. Flag that makes a dom0 boot in PVHv2 mode. +### dom0permissive +> `= ` + +> Default: `true` + +Select mode of PCI pass-through when using a PVHv2 Dom0, either permissive or +not. + ### dtuart (ARM) > `= path [:options]` diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c index a291f82..bc4f7bc 100644 --- a/xen/arch/x86/hvm/hvm.c +++ b/xen/arch/x86/hvm/hvm.c @@ -632,6 +632,8 @@ int hvm_domain_initialise(struct domain *d) goto fail1; } memset(d->arch.hvm_domain.io_bitmap, ~0, HVM_IOBITMAP_SIZE); + INIT_LIST_HEAD(&d->arch.hvm_domain.pt_devices); + rwlock_init(&d->arch.hvm_domain.pt_lock); } else d->arch.hvm_domain.io_bitmap = hvm_io_bitmap; diff --git a/xen/arch/x86/hvm/io.c b/xen/arch/x86/hvm/io.c index 31d54dc..7de1de3 100644 --- a/xen/arch/x86/hvm/io.c +++ b/xen/arch/x86/hvm/io.c @@ -46,6 +46,10 @@ #include #include +/* Set permissive mode for HVM Dom0 PCI pass-through by default */ +static bool_t opt_dom0permissive = 1; +boolean_param("dom0permissive", opt_dom0permissive); + void send_timeoffset_req(unsigned long timeoff) { ioreq_t p = { @@ -258,12 +262,403 @@ static bool_t hw_dpci_portio_accept(const struct hvm_io_handler *handler, return 0; } +static struct hvm_pt_device *hw_dpci_get_device(struct domain *d) +{ + uint8_t bus, slot, func; + uint32_t addr; + struct hvm_pt_device *dev; + + /* Decode bus, slot and func. */ + addr = CF8_BDF(d->arch.pci_cf8); + bus = PCI_BUS(addr); + slot = PCI_SLOT(addr); + func = PCI_FUNC(addr); + + list_for_each_entry( dev, &d->arch.hvm_domain.pt_devices, entries ) + { + if ( dev->pdev->seg != 0 || dev->pdev->bus != bus || + dev->pdev->devfn != PCI_DEVFN(slot,func) ) + continue; + + return dev; + } + + return NULL; +} + +/* Dispatchers */ + +/* Find emulate register group entry */ +struct hvm_pt_reg_group *hvm_pt_find_reg_grp(struct hvm_pt_device *d, + uint32_t address) +{ + struct hvm_pt_reg_group *entry = NULL; + + /* Find register group entry */ + list_for_each_entry( entry, &d->register_groups, entries ) + { + /* check address */ + if ( (entry->base_offset <= address) + && ((entry->base_offset + entry->size) > address) ) + return entry; + } + + /* Group entry not found */ + return NULL; +} + +/* Find emulate register entry */ +struct hvm_pt_reg *hvm_pt_find_reg(struct hvm_pt_reg_group *reg_grp, + uint32_t address) +{ + struct hvm_pt_reg *reg_entry = NULL; + struct hvm_pt_reg_handler *handler = NULL; + uint32_t real_offset = 0; + + /* Find register entry */ + list_for_each_entry( reg_entry, ®_grp->registers, entries ) + { + handler = reg_entry->handler; + real_offset = reg_grp->base_offset + handler->offset; + /* Check address */ + if ( (real_offset <= address) + && ((real_offset + handler->size) > address) ) + return reg_entry; + } + + return NULL; +} + +static int hvm_pt_pci_config_access_check(struct hvm_pt_device *d, + uint32_t addr, int len) +{ + /* Check offset range */ + if ( addr >= 0xFF ) + { + printk_pdev(d->pdev, XENLOG_DEBUG, + "failed to access register with offset exceeding 0xFF. " + "(addr: 0x%02x, len: %d)\n", addr, len); + return -EDOM; + } + + /* Check read size */ + if ( (len != 1) && (len != 2) && (len != 4) ) + { + printk_pdev(d->pdev, XENLOG_DEBUG, + "failed to access register with invalid access length. " + "(addr: 0x%02x, len: %d)\n", addr, len); + return -EINVAL; + } + + /* Check offset alignment */ + if ( addr & (len - 1) ) + { + printk_pdev(d->pdev, XENLOG_DEBUG, + "failed to access register with invalid access size " + "alignment. (addr: 0x%02x, len: %d)\n", addr, len); + return -EINVAL; + } + + return 0; +} + +static int hvm_pt_pci_read_config(struct hvm_pt_device *d, uint32_t addr, + uint32_t *data, int len) +{ + uint32_t val = 0; + struct hvm_pt_reg_group *reg_grp_entry = NULL; + struct hvm_pt_reg *reg_entry = NULL; + int rc = 0; + int emul_len = 0; + uint32_t find_addr = addr; + unsigned int seg = d->pdev->seg; + unsigned int bus = d->pdev->bus; + unsigned int slot = PCI_SLOT(d->pdev->devfn); + unsigned int func = PCI_FUNC(d->pdev->devfn); + + /* Sanity checks. */ + if ( hvm_pt_pci_config_access_check(d, addr, len) ) + return X86EMUL_UNHANDLEABLE; + + /* Find register group entry. */ + reg_grp_entry = hvm_pt_find_reg_grp(d, addr); + if ( reg_grp_entry == NULL ) + return X86EMUL_UNHANDLEABLE; + + /* Read I/O device register value. */ + switch( len ) + { + case 1: + val = pci_conf_read8(seg, bus, slot, func, addr); + break; + case 2: + val = pci_conf_read16(seg, bus, slot, func, addr); + break; + case 4: + val = pci_conf_read32(seg, bus, slot, func, addr); + break; + default: + BUG(); + } + + /* Adjust the read value to appropriate CFC-CFF window. */ + val <<= (addr & 3) << 3; + emul_len = len; + + /* Loop around the guest requested size. */ + while ( emul_len > 0 ) + { + /* Find register entry to be emulated. */ + reg_entry = hvm_pt_find_reg(reg_grp_entry, find_addr); + if ( reg_entry ) + { + struct hvm_pt_reg_handler *handler = reg_entry->handler; + uint32_t real_offset = reg_grp_entry->base_offset + handler->offset; + uint32_t valid_mask = 0xFFFFFFFF >> ((4 - emul_len) << 3); + uint8_t *ptr_val = NULL; + + valid_mask <<= (find_addr - real_offset) << 3; + ptr_val = (uint8_t *)&val + (real_offset & 3); + + /* Do emulation based on register size. */ + switch ( handler->size ) + { + case 1: + if ( handler->u.b.read ) + rc = handler->u.b.read(d, reg_entry, ptr_val, valid_mask); + break; + case 2: + if ( handler->u.w.read ) + rc = handler->u.w.read(d, reg_entry, (uint16_t *)ptr_val, + valid_mask); + break; + case 4: + if ( handler->u.dw.read ) + rc = handler->u.dw.read(d, reg_entry, (uint32_t *)ptr_val, + valid_mask); + break; + } + + if ( rc < 0 ) + { + gdprintk(XENLOG_WARNING, + "Invalid read emulation, shutting down domain\n"); + domain_crash(current->domain); + return X86EMUL_UNHANDLEABLE; + } + + /* Calculate next address to find. */ + emul_len -= handler->size; + if ( emul_len > 0 ) + find_addr = real_offset + handler->size; + } + else + { + /* Nothing to do with passthrough type register */ + emul_len--; + find_addr++; + } + } + + /* Need to shift back before returning them to pci bus emulator */ + val >>= ((addr & 3) << 3); + *data = val; + + return X86EMUL_OKAY; +} + +static int hvm_pt_pci_write_config(struct hvm_pt_device *d, uint32_t addr, + uint32_t val, int len) +{ + int index = 0; + struct hvm_pt_reg_group *reg_grp_entry = NULL; + int rc = 0; + uint32_t read_val = 0, wb_mask; + int emul_len = 0; + struct hvm_pt_reg *reg_entry = NULL; + uint32_t find_addr = addr; + struct hvm_pt_reg_handler *handler = NULL; + bool wp_flag = false; + unsigned int seg = d->pdev->seg; + unsigned int bus = d->pdev->bus; + unsigned int slot = PCI_SLOT(d->pdev->devfn); + unsigned int func = PCI_FUNC(d->pdev->devfn); + + /* Sanity checks. */ + if ( hvm_pt_pci_config_access_check(d, addr, len) ) + return X86EMUL_UNHANDLEABLE; + + /* Find register group entry. */ + reg_grp_entry = hvm_pt_find_reg_grp(d, addr); + if ( reg_grp_entry == NULL ) + return X86EMUL_UNHANDLEABLE; + + /* Read I/O device register value. */ + switch( len ) + { + case 1: + read_val = pci_conf_read8(seg, bus, slot, func, addr); + break; + case 2: + read_val = pci_conf_read16(seg, bus, slot, func, addr); + break; + case 4: + read_val = pci_conf_read32(seg, bus, slot, func, addr); + break; + default: + BUG(); + } + wb_mask = 0xFFFFFFFF >> ((4 - len) << 3); + + /* Adjust the read and write value to appropriate CFC-CFF window */ + read_val <<= (addr & 3) << 3; + val <<= (addr & 3) << 3; + emul_len = len; + + /* Loop around the guest requested size */ + while ( emul_len > 0 ) + { + /* Find register entry to be emulated */ + reg_entry = hvm_pt_find_reg(reg_grp_entry, find_addr); + if ( reg_entry ) + { + handler = reg_entry->handler; + uint32_t real_offset = reg_grp_entry->base_offset + handler->offset; + uint32_t valid_mask = 0xFFFFFFFF >> ((4 - emul_len) << 3); + uint8_t *ptr_val = NULL; + uint32_t wp_mask = handler->emu_mask | handler->ro_mask; + + valid_mask <<= (find_addr - real_offset) << 3; + ptr_val = (uint8_t *)&val + (real_offset & 3); + if ( !d->permissive ) + wp_mask |= handler->res_mask; + if ( wp_mask == (0xFFFFFFFF >> ((4 - handler->size) << 3)) ) + wb_mask &= ~((wp_mask >> ((find_addr - real_offset) << 3)) + << ((len - emul_len) << 3)); + + /* Do emulation based on register size */ + switch ( handler->size ) + { + case 1: + if ( handler->u.b.write ) + rc = handler->u.b.write(d, reg_entry, ptr_val, + read_val >> ((real_offset & 3) << 3), + valid_mask); + break; + case 2: + if ( handler->u.w.write ) + rc = handler->u.w.write(d, reg_entry, (uint16_t *)ptr_val, + (read_val >> ((real_offset & 3) << 3)), + valid_mask); + break; + case 4: + if ( handler->u.dw.write ) + rc = handler->u.dw.write(d, reg_entry, (uint32_t *)ptr_val, + (read_val >> ((real_offset & 3) << 3)), + valid_mask); + break; + } + + if ( rc < 0 ) + { + gdprintk(XENLOG_WARNING, + "Invalid write emulation, shutting down domain\n"); + domain_crash(current->domain); + return X86EMUL_UNHANDLEABLE; + } + + /* Calculate next address to find */ + emul_len -= handler->size; + if ( emul_len > 0 ) + find_addr = real_offset + handler->size; + } + else + { + /* Nothing to do with passthrough type register */ + if ( !d->permissive ) + { + wb_mask &= ~(0xff << ((len - emul_len) << 3)); + /* + * Unused BARs will make it here, but we don't want to issue + * warnings for writes to them (bogus writes get dealt with + * above). + */ + if ( index < 0 ) + wp_flag = true; + } + emul_len--; + find_addr++; + } + } + + /* Need to shift back before passing them to xen_host_pci_set_block */ + val >>= (addr & 3) << 3; + + if ( wp_flag && !d->permissive_warned ) + { + d->permissive_warned = true; + gdprintk(XENLOG_WARNING, + "Write-back to unknown field 0x%02x (partially) inhibited (0x%0*x)\n", + addr, len * 2, wb_mask); + gdprintk(XENLOG_WARNING, + "If the device doesn't work, try enabling permissive mode\n"); + gdprintk(XENLOG_WARNING, + "(unsafe) and if it helps report the problem to xen-devel\n"); + } + for ( index = 0; wb_mask; index += len ) + { + /* Unknown regs are passed through */ + while ( !(wb_mask & 0xff) ) + { + index++; + wb_mask >>= 8; + } + len = 0; + do { + len++; + wb_mask >>= 8; + } while ( wb_mask & 0xff ); + + switch( len ) + { + case 1: + { + uint8_t value; + memcpy(&value, (uint8_t *)&val + index, 1); + pci_conf_write8(seg, bus, slot, func, addr + index, value); + break; + } + case 2: + { + uint16_t value; + memcpy(&value, (uint8_t *)&val + index, 2); + pci_conf_write16(seg, bus, slot, func, addr + index, value); + break; + } + case 4: + { + uint32_t value; + memcpy(&value, (uint8_t *)&val + index, 4); + pci_conf_write32(seg, bus, slot, func, addr + index, value); + break; + } + default: + BUG(); + } + } + return X86EMUL_OKAY; +} + static int hw_dpci_portio_read(const struct hvm_io_handler *handler, uint64_t addr, uint32_t size, uint64_t *data) { struct domain *currd = current->domain; + struct hvm_pt_device *dev; + uint32_t data32; + uint8_t reg; + int rc; if ( addr == 0xcf8 ) { @@ -276,6 +671,22 @@ static int hw_dpci_portio_read(const struct hvm_io_handler *handler, size = min(size, 4 - ((uint32_t)addr & 3)); if ( size == 3 ) size = 2; + + read_lock(&currd->arch.hvm_domain.pt_lock); + dev = hw_dpci_get_device(currd); + if ( dev != NULL ) + { + reg = (currd->arch.pci_cf8 & 0xfc) | (addr & 0x3); + rc = hvm_pt_pci_read_config(dev, reg, &data32, size); + if ( rc == X86EMUL_OKAY ) + { + read_unlock(&currd->arch.hvm_domain.pt_lock); + *data = data32; + return rc; + } + } + read_unlock(&currd->arch.hvm_domain.pt_lock); + if ( pci_cfg_ok(currd, addr & 3, size, NULL) ) *data = pci_conf_read(currd->arch.pci_cf8, addr & 3, size); @@ -288,7 +699,10 @@ static int hw_dpci_portio_write(const struct hvm_io_handler *handler, uint64_t data) { struct domain *currd = current->domain; + struct hvm_pt_device *dev; uint32_t data32; + uint8_t reg; + int rc; if ( addr == 0xcf8 ) { @@ -302,12 +716,219 @@ static int hw_dpci_portio_write(const struct hvm_io_handler *handler, if ( size == 3 ) size = 2; data32 = data; + + read_lock(&currd->arch.hvm_domain.pt_lock); + dev = hw_dpci_get_device(currd); + if ( dev != NULL ) + { + reg = (currd->arch.pci_cf8 & 0xfc) | (addr & 0x3); + rc = hvm_pt_pci_write_config(dev, reg, data32, size); + if ( rc == X86EMUL_OKAY ) + { + read_unlock(&currd->arch.hvm_domain.pt_lock); + return rc; + } + } + read_unlock(&currd->arch.hvm_domain.pt_lock); + if ( pci_cfg_ok(currd, addr & 3, size, &data32) ) pci_conf_write(currd->arch.pci_cf8, addr & 3, size, data); return X86EMUL_OKAY; } +static void hvm_pt_free_device(struct hvm_pt_device *dev) +{ + struct hvm_pt_reg_group *group, *g; + + list_for_each_entry_safe( group, g, &dev->register_groups, entries ) + { + struct hvm_pt_reg *reg, *r; + + list_for_each_entry_safe( reg, r, &group->registers, entries ) + { + list_del(®->entries); + xfree(reg); + } + + list_del(&group->entries); + xfree(group); + } + + xfree(dev); +} + +static int hvm_pt_add_register(struct hvm_pt_device *dev, + struct hvm_pt_reg_group *group, + struct hvm_pt_reg_handler *handler) +{ + struct pci_dev *pdev = dev->pdev; + struct hvm_pt_reg *reg; + + reg = xmalloc(struct hvm_pt_reg); + if ( reg == NULL ) + return -ENOMEM; + + memset(reg, 0, sizeof(*reg)); + reg->handler = handler; + if ( handler->init != NULL ) + { + uint32_t host_mask, size_mask, data = 0; + uint8_t seg, bus, slot, func; + unsigned int offset; + uint32_t val; + int rc; + + /* Initialize emulate register */ + rc = handler->init(dev, reg->handler, + group->base_offset + reg->handler->offset, &data); + if ( rc < 0 ) + return rc; + + if ( data == HVM_PT_INVALID_REG ) + { + xfree(reg); + return 0; + } + + /* Sync up the data to val */ + offset = group->base_offset + reg->handler->offset; + size_mask = 0xFFFFFFFF >> ((4 - reg->handler->size) << 3); + + seg = pdev->seg; + bus = pdev->bus; + slot = PCI_SLOT(pdev->devfn); + func = PCI_FUNC(pdev->devfn); + + switch ( reg->handler->size ) + { + case 1: + val = pci_conf_read8(seg, bus, slot, func, offset); + break; + case 2: + val = pci_conf_read16(seg, bus, slot, func, offset); + break; + case 4: + val = pci_conf_read32(seg, bus, slot, func, offset); + break; + default: + BUG(); + } + + /* + * Set bits in emu_mask are the ones we emulate. The reg shall + * contain the emulated view of the guest - therefore we flip + * the mask to mask out the host values (which reg initially + * has). + */ + host_mask = size_mask & ~reg->handler->emu_mask; + + if ( (data & host_mask) != (val & host_mask) ) + { + uint32_t new_val; + + /* Mask out host (including past size). */ + new_val = val & host_mask; + /* Merge emulated ones (excluding the non-emulated ones). */ + new_val |= data & host_mask; + /* + * Leave intact host and emulated values past the size - + * even though we do not care as we write per reg->size + * granularity, but for the logging below lets have the + * proper value. + */ + new_val |= ((val | data)) & ~size_mask; + printk_pdev(pdev, XENLOG_ERR, +"offset 0x%04x mismatch! Emulated=0x%04x, host=0x%04x, syncing to 0x%04x.\n", + offset, data, val, new_val); + val = new_val; + } + else + val = data; + + if ( val & ~size_mask ) + { + printk_pdev(pdev, XENLOG_ERR, + "Offset 0x%04x:0x%04x expands past register size(%d)!\n", + offset, val, reg->handler->size); + return -EINVAL; + } + + reg->val.dword = val; + } + list_add_tail(®->entries, &group->registers); + + return 0; +} + +static struct hvm_pt_handler_init *hwdom_pt_handlers[] = { +}; + +int hwdom_add_device(struct pci_dev *pdev) +{ + struct domain *d = pdev->domain; + struct hvm_pt_device *dev; + int j, i, rc; + + ASSERT( is_hardware_domain(d) ); + ASSERT( pcidevs_locked() ); + + dev = xmalloc(struct hvm_pt_device); + if ( dev == NULL ) + return -ENOMEM; + + memset(dev, 0 , sizeof(*dev)); + + dev->pdev = pdev; + INIT_LIST_HEAD(&dev->register_groups); + + dev->permissive = opt_dom0permissive; + + for ( j = 0; j < ARRAY_SIZE(hwdom_pt_handlers); j++ ) + { + struct hvm_pt_handler_init *handler_init = hwdom_pt_handlers[j]; + struct hvm_pt_reg_group *group; + + group = xmalloc(struct hvm_pt_reg_group); + if ( group == NULL ) + { + xfree(dev); + return -ENOMEM; + } + INIT_LIST_HEAD(&group->registers); + + rc = handler_init->init(dev, group); + if ( rc == 0 ) + { + for ( i = 0; handler_init->handlers[i].size != 0; i++ ) + { + int rc; + + rc = hvm_pt_add_register(dev, group, + &handler_init->handlers[i]); + if ( rc ) + { + printk_pdev(pdev, XENLOG_ERR, "error adding register: %d\n", + rc); + hvm_pt_free_device(dev); + return rc; + } + } + + list_add_tail(&group->entries, &dev->register_groups); + } + else + xfree(group); + } + + write_lock(&d->arch.hvm_domain.pt_lock); + list_add_tail(&dev->entries, &d->arch.hvm_domain.pt_devices); + write_unlock(&d->arch.hvm_domain.pt_lock); + printk_pdev(pdev, XENLOG_DEBUG, "added for pass-through\n"); + + return 0; +} + static const struct hvm_io_ops dpci_portio_ops = { .accept = dpci_portio_accept, .read = dpci_portio_read, diff --git a/xen/include/asm-x86/hvm/domain.h b/xen/include/asm-x86/hvm/domain.h index f34d784..1b1a52f 100644 --- a/xen/include/asm-x86/hvm/domain.h +++ b/xen/include/asm-x86/hvm/domain.h @@ -152,6 +152,10 @@ struct hvm_domain { struct vmx_domain vmx; struct svm_domain svm; }; + + /* List of passed-through devices (hw domain only). */ + struct list_head pt_devices; + rwlock_t pt_lock; }; #define hap_enabled(d) ((d)->arch.hvm_domain.hap_enabled) diff --git a/xen/include/asm-x86/hvm/io.h b/xen/include/asm-x86/hvm/io.h index e9b3f83..80f830d 100644 --- a/xen/include/asm-x86/hvm/io.h +++ b/xen/include/asm-x86/hvm/io.h @@ -153,6 +153,182 @@ extern void hvm_dpci_msi_eoi(struct domain *d, int vector); void register_dpci_portio_handler(struct domain *d); +/* Structures for pci-passthrough state and handlers. */ +struct hvm_pt_device; +struct hvm_pt_reg_handler; +struct hvm_pt_reg; +struct hvm_pt_reg_group; + +/* Return code when register should be ignored. */ +#define HVM_PT_INVALID_REG 0xFFFFFFFF + +/* function type for config reg */ +typedef int (*hvm_pt_conf_reg_init) + (struct hvm_pt_device *, struct hvm_pt_reg_handler *, uint32_t real_offset, + uint32_t *data); + +typedef int (*hvm_pt_conf_dword_write) + (struct hvm_pt_device *, struct hvm_pt_reg *cfg_entry, + uint32_t *val, uint32_t dev_value, uint32_t valid_mask); +typedef int (*hvm_pt_conf_word_write) + (struct hvm_pt_device *, struct hvm_pt_reg *cfg_entry, + uint16_t *val, uint16_t dev_value, uint16_t valid_mask); +typedef int (*hvm_pt_conf_byte_write) + (struct hvm_pt_device *, struct hvm_pt_reg *cfg_entry, + uint8_t *val, uint8_t dev_value, uint8_t valid_mask); +typedef int (*hvm_pt_conf_dword_read) + (struct hvm_pt_device *, struct hvm_pt_reg *cfg_entry, + uint32_t *val, uint32_t valid_mask); +typedef int (*hvm_pt_conf_word_read) + (struct hvm_pt_device *, struct hvm_pt_reg *cfg_entry, + uint16_t *val, uint16_t valid_mask); +typedef int (*hvm_pt_conf_byte_read) + (struct hvm_pt_device *, struct hvm_pt_reg *cfg_entry, + uint8_t *val, uint8_t valid_mask); + +typedef int (*hvm_pt_group_init) + (struct hvm_pt_device *, struct hvm_pt_reg_group *); + +/* + * Emulated register information. + * + * This should be shared between all the consumers that trap on accesses + * to certain PCI registers. + */ +struct hvm_pt_reg_handler { + uint32_t offset; + uint32_t size; + uint32_t init_val; + /* reg reserved field mask (ON:reserved, OFF:defined) */ + uint32_t res_mask; + /* reg read only field mask (ON:RO/ROS, OFF:other) */ + uint32_t ro_mask; + /* reg read/write-1-clear field mask (ON:RW1C/RW1CS, OFF:other) */ + uint32_t rw1c_mask; + /* reg emulate field mask (ON:emu, OFF:passthrough) */ + uint32_t emu_mask; + hvm_pt_conf_reg_init init; + /* read/write function pointer + * for double_word/word/byte size */ + union { + struct { + hvm_pt_conf_dword_write write; + hvm_pt_conf_dword_read read; + } dw; + struct { + hvm_pt_conf_word_write write; + hvm_pt_conf_word_read read; + } w; + struct { + hvm_pt_conf_byte_write write; + hvm_pt_conf_byte_read read; + } b; + } u; +}; + +struct hvm_pt_handler_init { + struct hvm_pt_reg_handler *handlers; + hvm_pt_group_init init; +}; + +/* + * Emulated register value. + * + * This is the representation of each specific emulated register. + */ +struct hvm_pt_reg { + struct list_head entries; + struct hvm_pt_reg_handler *handler; + union { + uint8_t byte; + uint16_t word; + uint32_t dword; + } val; +}; + +/* + * Emulated register group. + * + * In order to speed up (and logically group) emulated registers search, + * groups are used that represent specific emulated features, like MSI. + */ +struct hvm_pt_reg_group { + struct list_head entries; + uint32_t base_offset; + uint8_t size; + struct list_head registers; +}; + +/* + * Guest MSI information. + * + * MSI values set by the guest. + */ +struct hvm_pt_msi { + uint16_t flags; + uint32_t addr_lo; /* guest message address */ + uint32_t addr_hi; /* guest message upper address */ + uint16_t data; /* guest message data */ + uint32_t ctrl_offset; /* saved control offset */ + int pirq; /* guest pirq corresponding */ + bool_t initialized; /* when guest MSI is initialized */ + bool_t mapped; /* when pirq is mapped */ +}; + +/* + * Guest passed-through PCI device. + */ +struct hvm_pt_device { + struct list_head entries; + + struct pci_dev *pdev; + + bool_t permissive; + bool_t permissive_warned; + + /* MSI status. */ + struct hvm_pt_msi msi; + + struct list_head register_groups; +}; + +/* + * The hierarchy of the above structures is the following: + * + * +---------------+ +---------------+ + * | | entries | | ... + * | hvm_pt_device +---------+ hvm_pt_device +----+ + * | | | | + * +-+-------------+ +---------------+ + * | + * | register_groups + * | + * +-v----------------+ +------------------+ + * | | entries | | ... + * | hvm_pt_reg_group +----------+ hvm_pt_reg_group +----+ + * | | | | + * +-+----------------+ +------------------+ + * | + * | registers + * | + * +-v----------+ +------------+ + * | | entries | | ... + * | hvm_pt_reg +------------+ hvm_pt_reg +----+ + * | | | | + * +-+----------+ +-+----------+ + * | | + * | handler | handler + * | | + * +-v------------------+ +-v------------------+ + * | | | | + * | hvm_pt_reg_handler | | hvm_pt_reg_handler | + * | | | | + * +--------------------+ +--------------------+ + */ + +/* Helper to add passed-through devices to the hardware domain. */ +int hwdom_add_device(struct pci_dev *pdev); + #endif /* __ASM_X86_HVM_IO_H__ */ diff --git a/xen/include/xen/pci.h b/xen/include/xen/pci.h index f191773..b21a891 100644 --- a/xen/include/xen/pci.h +++ b/xen/include/xen/pci.h @@ -90,6 +90,11 @@ struct pci_dev { u64 vf_rlen[6]; }; +/* Helper for printing pci_dev related messages. */ +#define printk_pdev(pdev, lvl, fmt, ...) \ + printk(lvl "PCI %04x:%02x:%02x.%u: " fmt, pdev->seg, pdev->bus, \ + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), ##__VA_ARGS__) + #define for_each_pdev(domain, pdev) \ list_for_each_entry(pdev, &(domain->arch.pdev_list), domain_list)