diff options
-rw-r--r-- | core/pci.c | 53 | ||||
-rw-r--r-- | hw/phb3.c | 33 | ||||
-rw-r--r-- | include/pci.h | 28 |
3 files changed, 114 insertions, 0 deletions
@@ -162,6 +162,7 @@ static struct pci_device *pci_scan_one(struct phb *phb, struct pci_device *paren } pd->bdfn = bdfn; pd->parent = parent; + list_head_init(&pd->pcrf); list_head_init(&pd->children); rc = pci_cfg_read8(phb, bdfn, PCI_CFG_HDR_TYPE, &htype); if (rc) { @@ -1593,3 +1594,55 @@ void pci_restore_bridge_buses(struct phb *phb) { pci_walk_dev(phb, __pci_restore_bridge_buses, NULL); } + +struct pci_cfg_reg_filter *pci_find_cfg_reg_filter(struct pci_device *pd, + uint32_t start, uint32_t len) +{ + struct pci_cfg_reg_filter *pcrf; + + /* Check on the cached range, which contains holes */ + if ((start + len) <= pd->pcrf_start || + pd->pcrf_end <= start) + return NULL; + + list_for_each(&pd->pcrf, pcrf, link) { + if (start >= pcrf->start && + (start + len) <= (pcrf->start + pcrf->len)) + return pcrf; + } + + return NULL; +} + +struct pci_cfg_reg_filter *pci_add_cfg_reg_filter(struct pci_device *pd, + uint32_t start, uint32_t len, + uint32_t flags, + pci_cfg_reg_func func) +{ + struct pci_cfg_reg_filter *pcrf; + + pcrf = pci_find_cfg_reg_filter(pd, start, len); + if (pcrf) + return pcrf; + + pcrf = zalloc(sizeof(*pcrf) + ((len + 0x4) & ~0x3)); + if (!pcrf) + return NULL; + + /* Don't validate the flags so that the private flags + * can be supported for debugging purpose. + */ + pcrf->flags = flags; + pcrf->start = start; + pcrf->len = len; + pcrf->func = func; + pcrf->data = (uint8_t *)(pcrf + 1); + + if (start < pd->pcrf_start) + pd->pcrf_start = start; + if (pd->pcrf_end < (start + len)) + pd->pcrf_end = start + len; + list_add_tail(&pd->pcrf, &pcrf->link); + + return pcrf; +} @@ -149,6 +149,33 @@ static int64_t phb3_pcicfg_check(struct phb3 *p, uint32_t bdfn, return OPAL_SUCCESS; } +static void phb3_pcicfg_filter(struct phb *phb, uint32_t bdfn, + uint32_t offset, uint32_t len, + uint32_t *data, bool write) +{ + struct pci_device *pd; + struct pci_cfg_reg_filter *pcrf; + uint32_t flags; + + /* FIXME: It harms the performance to search the PCI + * device which doesn't have any filters at all. So + * it's worthy to maintain a table in PHB to indicate + * the PCI devices who have filters. However, bitmap + * seems not supported by skiboot yet. To implement + * it after bitmap is supported. + */ + pd = pci_find_dev(phb, bdfn); + pcrf = pd ? pci_find_cfg_reg_filter(pd, offset, len) : NULL; + if (!pcrf || !pcrf->func) + return; + + flags = write ? PCI_REG_FLAG_WRITE : PCI_REG_FLAG_READ; + if ((pcrf->flags & flags) != flags) + return; + + pcrf->func(pd, pcrf, offset, len, data, write); +} + #define PHB3_PCI_CFG_READ(size, type) \ static int64_t phb3_pcicfg_read##size(struct phb *phb, uint32_t bdfn, \ uint32_t offset, type *data) \ @@ -189,6 +216,9 @@ static int64_t phb3_pcicfg_read##size(struct phb *phb, uint32_t bdfn, \ (offset & (4 - sizeof(type)))); \ } \ \ + phb3_pcicfg_filter(phb, bdfn, offset, sizeof(type), \ + (uint32_t *)data, false); \ + \ return OPAL_SUCCESS; \ } @@ -214,6 +244,9 @@ static int64_t phb3_pcicfg_write##size(struct phb *phb, uint32_t bdfn, \ return OPAL_HARDWARE; \ } \ \ + phb3_pcicfg_filter(phb, bdfn, offset, sizeof(type), \ + (uint32_t *)&data, true); \ + \ addr = PHB_CA_ENABLE; \ addr = SETFIELD(PHB_CA_BDFN, addr, bdfn); \ addr = SETFIELD(PHB_CA_REG, addr, offset); \ diff --git a/include/pci.h b/include/pci.h index 764ae5b4..f74c9615 100644 --- a/include/pci.h +++ b/include/pci.h @@ -90,6 +90,25 @@ struct pci_slot_info { int slot_index; }; +struct pci_device; +struct pci_cfg_reg_filter; + +typedef void (*pci_cfg_reg_func)(struct pci_device *pd, + struct pci_cfg_reg_filter *pcrf, + uint32_t offset, uint32_t len, + uint32_t *data, bool write); +struct pci_cfg_reg_filter { + uint32_t flags; +#define PCI_REG_FLAG_READ 0x1 +#define PCI_REG_FLAG_WRITE 0x2 +#define PCI_REG_FLAG_MASK 0x3 + uint32_t start; + uint32_t len; + uint8_t *data; + pci_cfg_reg_func func; + struct list_node link; +}; + /* * While this might not be necessary in the long run, the existing * Linux kernels expect us to provide a device-tree that contains @@ -123,6 +142,10 @@ struct pci_device { uint32_t cap[64]; uint32_t mps; /* Max payload size capability */ + uint32_t pcrf_start; + uint32_t pcrf_end; + struct list_head pcrf; + struct pci_slot_info *slot_info; struct pci_device *parent; struct list_head children; @@ -491,6 +514,11 @@ extern struct pci_device *pci_walk_dev(struct phb *phb, void *userdata); extern struct pci_device *pci_find_dev(struct phb *phb, uint16_t bdfn); extern void pci_restore_bridge_buses(struct phb *phb); +extern struct pci_cfg_reg_filter *pci_find_cfg_reg_filter(struct pci_device *pd, + uint32_t start, uint32_t len); +extern struct pci_cfg_reg_filter *pci_add_cfg_reg_filter(struct pci_device *pd, + uint32_t start, uint32_t len, + uint32_t flags, pci_cfg_reg_func func); /* Manage PHBs */ extern int64_t pci_register_phb(struct phb *phb, int opal_id); |