diff options
author | John Levon <john.levon@nutanix.com> | 2020-11-27 14:48:07 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-27 14:48:07 +0000 |
commit | e94bd44d10d8019ea2c39356363a5743136bdb5d (patch) | |
tree | 93f71114f5e57682a5a5a1182f7c1e19ce963ff8 /lib/pci.c | |
parent | 40ac852fec651f54a4be8905ab8bb6b25ddb64e2 (diff) | |
download | libvfio-user-e94bd44d10d8019ea2c39356363a5743136bdb5d.zip libvfio-user-e94bd44d10d8019ea2c39356363a5743136bdb5d.tar.gz libvfio-user-e94bd44d10d8019ea2c39356363a5743136bdb5d.tar.bz2 |
rename to libvfio-user (#128)
The muser name no longer reflects the implementation, and will just serve to
confuse. Bite the bullet now, and rename ourselves to reflect the actual
implementation.
Signed-off-by: John Levon <john.levon@nutanix.com>
Reviewed-by: Thanos Makatos <thanos.makatos@nutanix.com>
Reviewed-by: Swapnil Ingle <swapnil.ingle@nutanix.com>
Diffstat (limited to 'lib/pci.c')
-rw-r--r-- | lib/pci.c | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/lib/pci.c b/lib/pci.c new file mode 100644 index 0000000..0a944e3 --- /dev/null +++ b/lib/pci.c @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2019 Nutanix Inc. All rights reserved. + * + * Authors: Thanos Makatos <thanos@nutanix.com> + * Swapnil Ingle <swapnil.ingle@nutanix.com> + * Felipe Franciosi <felipe@nutanix.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Nutanix nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + */ + +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <sys/param.h> +#include <errno.h> + +#include <linux/pci_regs.h> +#include <linux/vfio.h> + +#include "common.h" +#include "libvfio-user.h" +#include "pci.h" +#include "private.h" + +static inline void +vfu_pci_hdr_write_bar(vfu_ctx_t *vfu_ctx, uint16_t bar_index, const char *buf) +{ + uint32_t cfg_addr; + unsigned long mask; + vfu_reg_info_t *reg_info = vfu_get_region_info(vfu_ctx); + vfu_pci_hdr_t *hdr; + + assert(vfu_ctx != NULL); + + if (reg_info[bar_index].size == 0) { + return; + } + + hdr = &vfu_pci_get_config_space(vfu_ctx)->hdr; + + cfg_addr = *(uint32_t *) buf; + + vfu_log(vfu_ctx, VFU_DBG, "BAR%d addr 0x%x\n", bar_index, cfg_addr); + + if (cfg_addr == 0xffffffff) { + cfg_addr = ~(reg_info[bar_index].size) + 1; + } + + if ((reg_info[bar_index].flags & VFU_REG_FLAG_MEM)) { + mask = PCI_BASE_ADDRESS_MEM_MASK; + } else { + mask = PCI_BASE_ADDRESS_IO_MASK; + } + cfg_addr |= (hdr->bars[bar_index].raw & ~mask); + + hdr->bars[bar_index].raw = htole32(cfg_addr); +} + +#define BAR_INDEX(offset) ((offset - PCI_BASE_ADDRESS_0) >> 2) + +static int +handle_command_write(vfu_ctx_t *ctx, vfu_pci_config_space_t *pci, + const char *buf, size_t count) +{ + uint16_t v; + + assert(ctx != NULL); + + if (count != 2) { + vfu_log(ctx, VFU_ERR, "bad write command size %lu\n", count); + return -EINVAL; + } + + assert(pci != NULL); + assert(buf != NULL); + + v = *(uint16_t*)buf; + + if ((v & PCI_COMMAND_IO) == PCI_COMMAND_IO) { + if (!pci->hdr.cmd.iose) { + pci->hdr.cmd.iose = 0x1; + vfu_log(ctx, VFU_INF, "I/O space enabled\n"); + } + v &= ~PCI_COMMAND_IO; + } else { + if (pci->hdr.cmd.iose) { + pci->hdr.cmd.iose = 0x0; + vfu_log(ctx, VFU_INF, "I/O space disabled\n"); + } + } + + if ((v & PCI_COMMAND_MEMORY) == PCI_COMMAND_MEMORY) { + if (!pci->hdr.cmd.mse) { + pci->hdr.cmd.mse = 0x1; + vfu_log(ctx, VFU_INF, "memory space enabled\n"); + } + v &= ~PCI_COMMAND_MEMORY; + } else { + if (pci->hdr.cmd.mse) { + pci->hdr.cmd.mse = 0x0; + vfu_log(ctx, VFU_INF, "memory space disabled\n"); + } + } + + if ((v & PCI_COMMAND_MASTER) == PCI_COMMAND_MASTER) { + if (!pci->hdr.cmd.bme) { + pci->hdr.cmd.bme = 0x1; + vfu_log(ctx, VFU_INF, "bus master enabled\n"); + } + v &= ~PCI_COMMAND_MASTER; + } else { + if (pci->hdr.cmd.bme) { + pci->hdr.cmd.bme = 0x0; + vfu_log(ctx, VFU_INF, "bus master disabled\n"); + } + } + + if ((v & PCI_COMMAND_SERR) == PCI_COMMAND_SERR) { + if (!pci->hdr.cmd.see) { + pci->hdr.cmd.see = 0x1; + vfu_log(ctx, VFU_INF, "SERR# enabled\n"); + } + v &= ~PCI_COMMAND_SERR; + } else { + if (pci->hdr.cmd.see) { + pci->hdr.cmd.see = 0x0; + vfu_log(ctx, VFU_INF, "SERR# disabled\n"); + } + } + + if ((v & PCI_COMMAND_INTX_DISABLE) == PCI_COMMAND_INTX_DISABLE) { + if (!pci->hdr.cmd.id) { + pci->hdr.cmd.id = 0x1; + vfu_log(ctx, VFU_INF, "INTx emulation disabled\n"); + } + v &= ~PCI_COMMAND_INTX_DISABLE; + } else { + if (pci->hdr.cmd.id) { + pci->hdr.cmd.id = 0x0; + vfu_log(ctx, VFU_INF, "INTx emulation enabled\n"); + } + } + + if ((v & PCI_COMMAND_INVALIDATE) == PCI_COMMAND_INVALIDATE) { + if (!pci->hdr.cmd.mwie) { + pci->hdr.cmd.mwie = 1U; + vfu_log(ctx, VFU_INF, "memory write and invalidate enabled\n"); + } + v &= ~PCI_COMMAND_INVALIDATE; + } else { + if (pci->hdr.cmd.mwie) { + pci->hdr.cmd.mwie = 0; + vfu_log(ctx, VFU_INF, "memory write and invalidate disabled"); + } + } + + if ((v & PCI_COMMAND_VGA_PALETTE) == PCI_COMMAND_VGA_PALETTE) { + vfu_log(ctx, VFU_INF, "enabling VGA palette snooping ignored\n"); + v &= ~PCI_COMMAND_VGA_PALETTE; + } + + if (v != 0) { + vfu_log(ctx, VFU_ERR, "unconsumed command flags %x\n", v); + return -EINVAL; + } + + return 0; +} + +static int +handle_erom_write(vfu_ctx_t *ctx, vfu_pci_config_space_t *pci, + const char *buf, size_t count) +{ + uint32_t v; + + assert(ctx != NULL); + assert(pci != NULL); + + if (count != 0x4) { + vfu_log(ctx, VFU_ERR, "bad EROM count %lu\n", count); + return -EINVAL; + } + v = *(uint32_t*)buf; + + if (v == (uint32_t)PCI_ROM_ADDRESS_MASK) { + vfu_log(ctx, VFU_INF, "write mask to EROM ignored\n"); + } else if (v == 0) { + vfu_log(ctx, VFU_INF, "cleared EROM\n"); + pci->hdr.erom = 0; + } else if (v == (uint32_t)~PCI_ROM_ADDRESS_ENABLE) { + vfu_log(ctx, VFU_INF, "EROM disable ignored\n"); + } else { + vfu_log(ctx, VFU_ERR, "bad write to EROM 0x%x bytes\n", v); + return -EINVAL; + } + return 0; +} + +static inline int +vfu_pci_hdr_write(vfu_ctx_t *vfu_ctx, uint16_t offset, + const char *buf, size_t count) +{ + vfu_pci_config_space_t *pci; + int ret = 0; + + assert(vfu_ctx != NULL); + assert(buf != NULL); + + pci = vfu_pci_get_config_space(vfu_ctx); + + switch (offset) { + case PCI_COMMAND: + ret = handle_command_write(vfu_ctx, pci, buf, count); + break; + case PCI_STATUS: + vfu_log(vfu_ctx, VFU_INF, "write to status ignored\n"); + break; + case PCI_INTERRUPT_PIN: + vfu_log(vfu_ctx, VFU_ERR, "attempt to write read-only field IPIN\n"); + ret = -EINVAL; + break; + case PCI_INTERRUPT_LINE: + pci->hdr.intr.iline = buf[0]; + vfu_log(vfu_ctx, VFU_DBG, "ILINE=%0x\n", pci->hdr.intr.iline); + break; + case PCI_LATENCY_TIMER: + pci->hdr.mlt = (uint8_t)buf[0]; + vfu_log(vfu_ctx, VFU_INF, "set to latency timer to %hhx\n", pci->hdr.mlt); + break; + case PCI_BASE_ADDRESS_0: + case PCI_BASE_ADDRESS_1: + case PCI_BASE_ADDRESS_2: + case PCI_BASE_ADDRESS_3: + case PCI_BASE_ADDRESS_4: + case PCI_BASE_ADDRESS_5: + vfu_pci_hdr_write_bar(vfu_ctx, BAR_INDEX(offset), buf); + break; + case PCI_ROM_ADDRESS: + ret = handle_erom_write(vfu_ctx, pci, buf, count); + break; + default: + vfu_log(vfu_ctx, VFU_INF, "PCI config write %#x-%#lx not handled\n", + offset, offset + count); + ret = -EINVAL; + } + +#ifdef VFU_VERBOSE_LOGGING + dump_buffer("PCI header", (char*)pci->hdr.raw, 0xff); +#endif + + return ret; +} + +/* + * @pci_hdr: the PCI header + * @reg_info: region info + * @rw: the command + * @write: whether this is a PCI header write + * @count: output parameter that receives the number of bytes read/written + */ +static inline int +vfu_do_pci_hdr_access(vfu_ctx_t *vfu_ctx, uint32_t *count, + uint64_t *pos, bool is_write, + char *buf) +{ + uint32_t _count; + loff_t _pos; + int err = 0; + + assert(vfu_ctx != NULL); + assert(count != NULL); + assert(pos != NULL); + assert(buf != NULL); + + _pos = *pos - region_to_offset(VFU_PCI_DEV_CFG_REGION_IDX); + _count = MIN(*count, PCI_STD_HEADER_SIZEOF - _pos); + + if (is_write) { + err = vfu_pci_hdr_write(vfu_ctx, _pos, buf, _count); + } else { + memcpy(buf, vfu_pci_get_config_space(vfu_ctx)->hdr.raw + _pos, _count); + } + *pos += _count; + *count -= _count; + return err; +} + +static inline bool +vfu_is_pci_hdr_access(uint64_t pos) +{ + const uint64_t off = region_to_offset(VFU_PCI_DEV_CFG_REGION_IDX); + return pos >= off && pos - off < PCI_STD_HEADER_SIZEOF; +} + +/* FIXME this function is misleading, remove it */ +int +vfu_pci_hdr_access(vfu_ctx_t *vfu_ctx, uint32_t *count, + uint64_t *pos, bool is_write, char *buf) +{ + assert(vfu_ctx != NULL); + assert(count != NULL); + assert(pos != NULL); + + if (!vfu_is_pci_hdr_access(*pos)) { + return 0; + } + return vfu_do_pci_hdr_access(vfu_ctx, count, pos, is_write, buf); +} + +/* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */ |