diff options
Diffstat (limited to 'roms/skiboot/core/pci-virt.c')
-rw-r--r-- | roms/skiboot/core/pci-virt.c | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/roms/skiboot/core/pci-virt.c b/roms/skiboot/core/pci-virt.c new file mode 100644 index 000000000..e0cb9949c --- /dev/null +++ b/roms/skiboot/core/pci-virt.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Support virtual PCI devices + * + * Copyright 2013-2016 IBM Corp. + */ + +#include <skiboot.h> +#include <pci.h> +#include <pci-virt.h> + +void pci_virt_cfg_read_raw(struct pci_virt_device *pvd, + uint32_t space, uint32_t offset, + uint32_t size, uint32_t *data) +{ + uint32_t i; + + if (space >= PCI_VIRT_CFG_MAX || !pvd->config[space]) + return; + + for (*data = 0, i = 0; i < size; i++) + *data |= ((uint32_t)(pvd->config[space][offset + i]) << (i * 8)); +} + +void pci_virt_cfg_write_raw(struct pci_virt_device *pvd, + uint32_t space, uint32_t offset, + uint32_t size, uint32_t data) +{ + int i; + + if (space >= PCI_VIRT_CFG_MAX || !pvd->config[space]) + return; + + for (i = 0; i < size; i++) { + pvd->config[space][offset + i] = data; + data = (data >> 8); + } +} + +static struct pci_cfg_reg_filter *pci_virt_find_filter( + struct pci_virt_device *pvd, + uint32_t start, uint32_t len) +{ + struct pci_cfg_reg_filter *pcrf; + + if (!pvd || !len || start >= pvd->cfg_size) + return NULL; + + /* Return filter if there is overlapped region. We don't + * require strict matching for more flexibility. It also + * means the associated handler should validate the register + * offset and length. + */ + list_for_each(&pvd->pcrf, pcrf, link) { + if (start < (pcrf->start + pcrf->len) && + (start + len) > pcrf->start) + return pcrf; + } + + return NULL; +} + +struct pci_cfg_reg_filter *pci_virt_add_filter(struct pci_virt_device *pvd, + uint32_t start, + uint32_t len, + uint32_t flags, + pci_cfg_reg_func func, + void *data) +{ + struct pci_cfg_reg_filter *pcrf; + + if (!pvd || !len || (start + len) >= pvd->cfg_size) + return NULL; + if (!(flags & PCI_REG_FLAG_MASK)) + return NULL; + + pcrf = pci_virt_find_filter(pvd, start, len); + if (pcrf) { + prlog(PR_ERR, "%s: Filter [%x, %x] overlapped with [%x, %x]\n", + __func__, start, len, pcrf->start, pcrf->len); + return NULL; + } + + pcrf = zalloc(sizeof(*pcrf)); + if (!pcrf) { + prlog(PR_ERR, "%s: Out of memory!\n", __func__); + return NULL; + } + + pcrf->start = start; + pcrf->len = len; + pcrf->flags = flags; + pcrf->func = func; + pcrf->data = data; + list_add_tail(&pvd->pcrf, &pcrf->link); + + return pcrf; +} + +struct pci_virt_device *pci_virt_find_device(struct phb *phb, + uint32_t bdfn) +{ + struct pci_virt_device *pvd; + + list_for_each(&phb->virt_devices, pvd, node) { + if (pvd->bdfn == bdfn) + return pvd; + } + + return NULL; +} + +static inline bool pci_virt_cfg_valid(struct pci_virt_device *pvd, + uint32_t offset, uint32_t size) +{ + if ((offset + size) > pvd->cfg_size) + return false; + + if (!size || (size > 4)) + return false; + + if ((size & (size - 1)) || (offset & (size - 1))) + return false; + + return true; +} + +int64_t pci_virt_cfg_read(struct phb *phb, uint32_t bdfn, + uint32_t offset, uint32_t size, + uint32_t *data) +{ + struct pci_virt_device *pvd; + struct pci_cfg_reg_filter *pcrf; + int64_t ret = OPAL_SUCCESS; + + *data = 0xffffffff; + + /* Search for PCI virtual device */ + pvd = pci_virt_find_device(phb, bdfn); + if (!pvd) + return OPAL_PARAMETER; + + /* Check if config address is valid or not */ + if (!pci_virt_cfg_valid(pvd, offset, size)) + return OPAL_PARAMETER; + + /* The value is fetched from the normal config space when the + * trap handler returns OPAL_PARTIAL. Otherwise, the trap handler + * should provide the return value. + */ + pcrf = pci_virt_find_filter(pvd, offset, size); + if (!pcrf || !pcrf->func || !(pcrf->flags & PCI_REG_FLAG_READ)) + goto out; + + ret = pcrf->func(pvd, pcrf, offset, size, data, false); + if (ret != OPAL_PARTIAL) + return ret; +out: + pci_virt_cfg_read_raw(pvd, PCI_VIRT_CFG_NORMAL, offset, size, data); + return OPAL_SUCCESS; +} + +int64_t pci_virt_cfg_write(struct phb *phb, uint32_t bdfn, + uint32_t offset, uint32_t size, + uint32_t data) +{ + struct pci_virt_device *pvd; + struct pci_cfg_reg_filter *pcrf; + uint32_t val, v, r, c, i; + int64_t ret = OPAL_SUCCESS; + + /* Search for PCI virtual device */ + pvd = pci_virt_find_device(phb, bdfn); + if (!pvd) + return OPAL_PARAMETER; + + /* Check if config address is valid or not */ + if (!pci_virt_cfg_valid(pvd, offset, size)) + return OPAL_PARAMETER; + + /* The value is written to the config space if the trap handler + * returns OPAL_PARTIAL. Otherwise, the value to be written is + * dropped. + */ + pcrf = pci_virt_find_filter(pvd, offset, size); + if (!pcrf || !pcrf->func || !(pcrf->flags & PCI_REG_FLAG_WRITE)) + goto out; + + ret = pcrf->func(pvd, pcrf, offset, size, &data, true); + if (ret != OPAL_PARTIAL) + return ret; +out: + val = data; + for (i = 0; i < size; i++) { + PCI_VIRT_CFG_NORMAL_RD(pvd, offset + i, 1, &v); + PCI_VIRT_CFG_RDONLY_RD(pvd, offset + i, 1, &r); + PCI_VIRT_CFG_W1CLR_RD(pvd, offset + i, 1, &c); + + /* Drop read-only bits */ + val &= ~(r << (i * 8)); + val |= (r & v) << (i * 8); + + /* Drop W1C bits */ + val &= ~(val & ((c & v) << (i * 8))); + } + + PCI_VIRT_CFG_NORMAL_WR(pvd, offset, size, val); + return OPAL_SUCCESS; +} + +struct pci_virt_device *pci_virt_add_device(struct phb *phb, uint32_t bdfn, + uint32_t cfg_size, void *data) +{ + struct pci_virt_device *pvd; + uint8_t *cfg; + uint32_t i; + + /* The standard config header size is 64 bytes */ + if (!phb || (bdfn & 0xffff0000) || (cfg_size < 64)) + return NULL; + + /* Check if the bdfn is available */ + pvd = pci_virt_find_device(phb, bdfn); + if (pvd) { + prlog(PR_ERR, "%s: bdfn 0x%x was reserved\n", + __func__, bdfn); + return NULL; + } + + /* Populate the PCI virtual device */ + pvd = zalloc(sizeof(*pvd)); + if (!pvd) { + prlog(PR_ERR, "%s: Cannot alloate PCI virtual device (0x%x)\n", + __func__, bdfn); + return NULL; + } + + cfg = zalloc(cfg_size * PCI_VIRT_CFG_MAX); + if (!cfg) { + prlog(PR_ERR, "%s: Cannot allocate config space (0x%x)\n", + __func__, bdfn); + free(pvd); + return NULL; + } + + for (i = 0; i < PCI_VIRT_CFG_MAX; i++, cfg += cfg_size) + pvd->config[i] = cfg; + + pvd->bdfn = bdfn; + pvd->cfg_size = cfg_size; + pvd->data = data; + list_head_init(&pvd->pcrf); + list_add_tail(&phb->virt_devices, &pvd->node); + + return pvd; +} |