aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/core/pci-virt.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/core/pci-virt.c')
-rw-r--r--roms/skiboot/core/pci-virt.c256
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;
+}