/* Copyright 2013-2016 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include 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; }