summaryrefslogtreecommitdiffstats
path: root/sys/i386
diff options
context:
space:
mode:
Diffstat (limited to 'sys/i386')
-rw-r--r--sys/i386/pci/pci_cfgreg.c219
1 files changed, 211 insertions, 8 deletions
diff --git a/sys/i386/pci/pci_cfgreg.c b/sys/i386/pci/pci_cfgreg.c
index e22874c..ab5c558 100644
--- a/sys/i386/pci/pci_cfgreg.c
+++ b/sys/i386/pci/pci_cfgreg.c
@@ -34,24 +34,54 @@ __FBSDID("$FreeBSD$");
#include <sys/bus.h>
#include <sys/lock.h>
#include <sys/mutex.h>
+#include <sys/malloc.h>
+#include <sys/queue.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <machine/pci_cfgreg.h>
#include <machine/pc/bios.h>
+#include <vm/vm.h>
+#include <vm/vm_param.h>
+#include <vm/vm_kern.h>
+#include <vm/vm_extern.h>
+#include <vm/pmap.h>
+#include <machine/pmap.h>
+
#define PRVERB(a) do { \
if (bootverbose) \
printf a ; \
} while(0)
+#define PCIE_CACHE 8
+struct pcie_cfg_elem {
+ TAILQ_ENTRY(pcie_cfg_elem) elem;
+ vm_offset_t vapage;
+ vm_paddr_t papage;
+};
+
+enum {
+ CFGMECH_NONE = 0,
+ CFGMECH_1,
+ CFGMECH_2,
+ CFGMECH_PCIE,
+};
+
+static TAILQ_HEAD(pcie_cfg_list, pcie_cfg_elem) pcie_list[MAXCPU];
+static uint32_t pciebar;
static int cfgmech;
static int devmax;
+static struct mtx pcicfg_mtx;
static int pcireg_cfgread(int bus, int slot, int func, int reg, int bytes);
static void pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes);
static int pcireg_cfgopen(void);
-static struct mtx pcicfg_mtx;
+static int pciereg_cfgopen(void);
+static int pciereg_cfgread(int bus, int slot, int func, int reg,
+ int bytes);
+static void pciereg_cfgwrite(int bus, int slot, int func, int reg,
+ int data, int bytes);
/*
* Some BIOS writers seem to want to ignore the spec and put
@@ -96,6 +126,7 @@ int
pci_cfgregopen(void)
{
static int opened = 0;
+ u_int16_t vid, did;
u_int16_t v;
if (opened)
@@ -114,6 +145,24 @@ pci_cfgregopen(void)
/* $PIR requires PCI BIOS 2.10 or greater. */
if (v >= 0x0210)
pci_pir_open();
+
+ /*
+ * Grope around in the PCI config space to see if this is a
+ * chipset that is capable of doing memory-mapped config cycles.
+ * This also implies that it can do PCIe extended config cycles.
+ */
+
+ /* Check for the Intel 7520 and 925 chipsets */
+ vid = pci_cfgregread(0, 0, 0, 0x0, 2);
+ did = pci_cfgregread(0, 0, 0, 0x2, 2);
+ if ((vid == 0x8086) && (did == 0x3590)) {
+ pciebar = pci_cfgregread(0, 0, 0, 0xce, 2) << 16;
+ pciereg_cfgopen();
+ } else if ((vid == 0x8086) && (did == 0x2580)) {
+ pciebar = pci_cfgregread(0, 0, 0, 0x48, 4);
+ pciereg_cfgopen();
+ }
+
return(1);
}
@@ -165,13 +214,13 @@ pci_cfgenable(unsigned bus, unsigned slot, unsigned func, int reg, int bytes)
&& (unsigned) bytes <= 4
&& (reg & (bytes - 1)) == 0) {
switch (cfgmech) {
- case 1:
+ case CFGMECH_1:
outl(CONF1_ADDR_PORT, (1 << 31)
| (bus << 16) | (slot << 11)
| (func << 8) | (reg & ~0x03));
dataport = CONF1_DATA_PORT + (reg & 0x03);
break;
- case 2:
+ case CFGMECH_2:
outb(CONF2_ENABLE_PORT, 0xf0 | (func << 1));
outb(CONF2_FORWARD_PORT, bus);
dataport = 0xc000 | (slot << 8) | reg;
@@ -186,10 +235,10 @@ static void
pci_cfgdisable(void)
{
switch (cfgmech) {
- case 1:
+ case CFGMECH_1:
outl(CONF1_ADDR_PORT, 0);
break;
- case 2:
+ case CFGMECH_2:
outb(CONF2_ENABLE_PORT, 0);
outb(CONF2_FORWARD_PORT, 0);
break;
@@ -202,6 +251,11 @@ pcireg_cfgread(int bus, int slot, int func, int reg, int bytes)
int data = -1;
int port;
+ if (cfgmech == CFGMECH_PCIE) {
+ data = pciereg_cfgread(bus, slot, func, reg, bytes);
+ return (data);
+ }
+
mtx_lock_spin(&pcicfg_mtx);
port = pci_cfgenable(bus, slot, func, reg, bytes);
if (port != 0) {
@@ -227,6 +281,11 @@ pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes)
{
int port;
+ if (cfgmech == CFGMECH_PCIE) {
+ pciereg_cfgwrite(bus, slot, func, reg, data, bytes);
+ return;
+ }
+
mtx_lock_spin(&pcicfg_mtx);
port = pci_cfgenable(bus, slot, func, reg, bytes);
if (port != 0) {
@@ -309,7 +368,7 @@ pcireg_cfgopen(void)
if ((oldval1 & CONF1_ENABLE_MSK) == 0) {
- cfgmech = 1;
+ cfgmech = CFGMECH_1;
devmax = 32;
outl(CONF1_ADDR_PORT, CONF1_ENABLE_CHK);
@@ -349,7 +408,7 @@ pcireg_cfgopen(void)
if ((oldval2 & 0xf0) == 0) {
- cfgmech = 2;
+ cfgmech = CFGMECH_2;
devmax = 16;
outb(CONF2_ENABLE_PORT, CONF2_ENABLE_CHK);
@@ -369,8 +428,152 @@ pcireg_cfgopen(void)
}
}
- cfgmech = 0;
+ cfgmech = CFGMECH_NONE;
devmax = 0;
return (cfgmech);
}
+static int
+pciereg_cfgopen(void)
+{
+ struct pcie_cfg_list *pcielist;
+ struct pcie_cfg_elem *pcie_array, *elem;
+#ifdef SMP
+ struct pcpu *pc;
+#endif
+ vm_offset_t va;
+ int i;
+
+ if (bootverbose)
+ printf("Setting up PCIe mappings for BAR 0x%x\n", pciebar);
+
+#ifdef SMP
+ SLIST_FOREACH(pc, &cpuhead, pc_allcpu)
+#endif
+ {
+
+ pcie_array = malloc(sizeof(struct pcie_cfg_elem) * PCIE_CACHE,
+ M_DEVBUF, M_NOWAIT);
+ if (pcie_array == NULL)
+ return (0);
+
+ va = kmem_alloc_nofault(kernel_map, PCIE_CACHE * PAGE_SIZE);
+ if (va == 0) {
+ free(pcie_array, M_DEVBUF);
+ return (0);
+ }
+
+#ifdef SMP
+ pcielist = &pcie_list[pc->pc_cpuid];
+#else
+ pcielist = &pcie_list[0];
+#endif
+ TAILQ_INIT(pcielist);
+ for (i = 0; i < PCIE_CACHE; i++) {
+ elem = &pcie_array[i];
+ elem->vapage = va + (i * PAGE_SIZE);
+ elem->papage = 0;
+ TAILQ_INSERT_HEAD(pcielist, elem, elem);
+ }
+ }
+
+
+ cfgmech = CFGMECH_PCIE;
+ devmax = 32;
+ return (1);
+}
+
+#define PCIE_PADDR(bar, reg, bus, slot, func) \
+ ((bar) | \
+ (((bus) & 0xff) << 20) | \
+ (((slot) & 0x1f) << 15) | \
+ (((func) & 0x7) << 12) | \
+ ((reg) & 0xfff))
+
+/*
+ * Find an element in the cache that matches the physical page desired, or
+ * create a new mapping from the least recently used element.
+ * A very simple LRU algorithm is used here, does it need to be more
+ * efficient?
+ */
+static __inline struct pcie_cfg_elem *
+pciereg_findelem(vm_paddr_t papage)
+{
+ struct pcie_cfg_list *pcielist;
+ struct pcie_cfg_elem *elem;
+
+ critical_enter();
+ pcielist = &pcie_list[PCPU_GET(cpuid)];
+ TAILQ_FOREACH(elem, pcielist, elem) {
+ if (elem->papage == papage)
+ break;
+ }
+
+ if (elem == NULL) {
+ elem = TAILQ_LAST(pcielist, pcie_cfg_list);
+ if (elem->papage != 0) {
+ pmap_kremove(elem->vapage);
+ invlpg(elem->vapage);
+ }
+ pmap_kenter(elem->vapage, papage);
+ elem->papage = papage;
+ }
+
+ if (elem != TAILQ_FIRST(pcielist)) {
+ TAILQ_REMOVE(pcielist, elem, elem);
+ TAILQ_INSERT_HEAD(pcielist, elem, elem);
+ }
+ critical_exit();
+ return (elem);
+}
+
+static int
+pciereg_cfgread(int bus, int slot, int func, int reg, int bytes)
+{
+ struct pcie_cfg_elem *elem;
+ volatile vm_offset_t va;
+ vm_paddr_t pa, papage;
+
+ pa = PCIE_PADDR(pciebar, reg, bus, slot, func);
+ papage = pa & ~PAGE_MASK;
+ elem = pciereg_findelem(papage);
+ va = elem->vapage | (pa & PAGE_MASK);
+
+ switch (bytes) {
+ case 4:
+ return (*(volatile uint32_t *)(va));
+ case 2:
+ return (*(volatile uint16_t *)(va));
+ case 1:
+ return (*(volatile uint8_t *)(va));
+ default:
+ panic("pciereg_cfgread: invalid width");
+ }
+}
+
+static void
+pciereg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes)
+{
+ struct pcie_cfg_elem *elem;
+ volatile vm_offset_t va;
+ vm_paddr_t pa, papage;
+
+ pa = PCIE_PADDR(pciebar, reg, bus, slot, func);
+ papage = pa & ~PAGE_MASK;
+ elem = pciereg_findelem(papage);
+ va = elem->vapage | (pa & PAGE_MASK);
+
+ switch (bytes) {
+ case 4:
+ *(volatile uint32_t *)(va) = data;
+ break;
+ case 2:
+ *(volatile uint16_t *)(va) = data;
+ break;
+ case 1:
+ *(volatile uint8_t *)(va) = data;
+ break;
+ default:
+ panic("pciereg_cfgwrite: invalid width");
+ }
+}
OpenPOWER on IntegriCloud