summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/pci.c53
-rw-r--r--hw/phb3.c33
-rw-r--r--include/pci.h28
3 files changed, 114 insertions, 0 deletions
diff --git a/core/pci.c b/core/pci.c
index c133dfeb..963d805b 100644
--- a/core/pci.c
+++ b/core/pci.c
@@ -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;
+}
diff --git a/hw/phb3.c b/hw/phb3.c
index f9efd81c..58b4f733 100644
--- a/hw/phb3.c
+++ b/hw/phb3.c
@@ -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);
OpenPOWER on IntegriCloud