summaryrefslogtreecommitdiffstats
path: root/sys/dev/pci/pci.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/pci/pci.c')
-rw-r--r--sys/dev/pci/pci.c342
1 files changed, 339 insertions, 3 deletions
diff --git a/sys/dev/pci/pci.c b/sys/dev/pci/pci.c
index 1816a26..ca4aee1 100644
--- a/sys/dev/pci/pci.c
+++ b/sys/dev/pci/pci.c
@@ -41,7 +41,7 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h>
#include <sys/queue.h>
#include <sys/sysctl.h>
-#include <sys/types.h>
+#include <sys/endian.h>
#include <vm/vm.h>
#include <vm/pmap.h>
@@ -90,6 +90,13 @@ static int pci_modevent(module_t mod, int what, void *arg);
static void pci_hdrtypedata(device_t pcib, int b, int s, int f,
pcicfgregs *cfg);
static void pci_read_extcap(device_t pcib, pcicfgregs *cfg);
+static uint32_t pci_read_vpd_reg(device_t pcib, pcicfgregs *cfg,
+ int reg);
+#if 0
+static void pci_write_vpd_reg(device_t pcib, pcicfgregs *cfg,
+ int reg, uint32_t data);
+#endif
+static void pci_read_vpd(device_t pcib, pcicfgregs *cfg);
static device_method_t pci_methods[] = {
/* Device interface */
@@ -127,6 +134,8 @@ static device_method_t pci_methods[] = {
DEVMETHOD(pci_disable_busmaster, pci_disable_busmaster_method),
DEVMETHOD(pci_enable_io, pci_enable_io_method),
DEVMETHOD(pci_disable_io, pci_disable_io_method),
+ DEVMETHOD(pci_get_vpd_ident, pci_get_vpd_ident_method),
+ DEVMETHOD(pci_get_vpd_readonly, pci_get_vpd_readonly_method),
DEVMETHOD(pci_get_powerstate, pci_get_powerstate_method),
DEVMETHOD(pci_set_powerstate, pci_set_powerstate_method),
DEVMETHOD(pci_assign_interrupt, pci_assign_interrupt_method),
@@ -344,7 +353,6 @@ pci_hdrtypedata(device_t pcib, int b, int s, int f, pcicfgregs *cfg)
}
/* read configuration header into pcicfgregs structure */
-
struct pci_devinfo *
pci_read_device(device_t pcib, int b, int s, int f, size_t size)
{
@@ -428,7 +436,7 @@ pci_read_extcap(device_t pcib, pcicfgregs *cfg)
ptrptr = PCIR_CAP_PTR;
break;
case 2:
- ptrptr = PCIR_CAP_PTR_2;
+ ptrptr = PCIR_CAP_PTR_2; /* cardbus capabilities ptr */
break;
default:
return; /* no extended capabilities support */
@@ -468,13 +476,304 @@ pci_read_extcap(device_t pcib, pcicfgregs *cfg)
cfg->msi.msi_data = PCIR_MSI_DATA;
cfg->msi.msi_msgnum = 1 << ((cfg->msi.msi_ctrl &
PCIM_MSICTRL_MMC_MASK)>>1);
+ break;
+ case PCIY_VPD: /* PCI Vital Product Data */
+ cfg->vpd.vpd_reg = ptr;
+ pci_read_vpd(pcib, cfg);
+ break;
+ default:
+ break;
+ }
+ }
+/* REG use carry through to next functions */
+}
+
+/*
+ * PCI Vital Product Data
+ */
+static uint32_t
+pci_read_vpd_reg(device_t pcib, pcicfgregs *cfg, int reg)
+{
+#define WREG(n, v, w) PCIB_WRITE_CONFIG(pcib, cfg->bus, cfg->slot, cfg->func, n, v, w)
+
+ KASSERT((reg & 3) == 0, ("VPD register must by 4 byte aligned"));
+
+ WREG(cfg->vpd.vpd_reg + 2, reg, 2);
+ while ((REG(cfg->vpd.vpd_reg + 2, 2) & 0x8000) != 0x8000)
+ DELAY(1); /* limit looping */
+
+ return REG(cfg->vpd.vpd_reg + 4, 4);
+}
+
+#if 0
+static void
+pci_write_vpd_reg(device_t pcib, pcicfgregs *cfg, int reg, uint32_t data)
+{
+ KASSERT((reg & 3) == 0, ("VPD register must by 4 byte aligned"));
+
+ WREG(cfg->vpd.vpd_reg + 4, data, 4);
+ WREG(cfg->vpd.vpd_reg + 2, reg | 0x8000, 2);
+ while ((REG(cfg->vpd.vpd_reg + 2, 2) & 0x8000) == 0x8000)
+ DELAY(1); /* limit looping */
+
+ return;
+}
+#endif
+#undef WREG
+
+struct vpd_readstate {
+ device_t pcib;
+ pcicfgregs *cfg;
+ uint32_t val;
+ int bytesinval;
+ int off;
+ uint8_t cksum;
+};
+
+static uint8_t
+vpd_nextbyte(struct vpd_readstate *vrs)
+{
+ uint8_t byte;
+
+ if (vrs->bytesinval == 0) {
+ vrs->val = le32toh(pci_read_vpd_reg(vrs->pcib, vrs->cfg,
+ vrs->off));
+ vrs->off += 4;
+ byte = vrs->val & 0xff;
+ vrs->bytesinval = 3;
+ } else {
+ vrs->val = vrs->val >> 8;
+ byte = vrs->val & 0xff;
+ vrs->bytesinval--;
+ }
+
+ vrs->cksum += byte;
+ return byte;
+}
+
+static void
+pci_read_vpd(device_t pcib, pcicfgregs *cfg)
+{
+ struct vpd_readstate vrs;
+ int state;
+ int name;
+ int remain;
+ int end;
+ int i;
+ uint8_t byte;
+ int alloc, off; /* alloc/off for RO/W arrays */
+ int cksumvalid;
+ int dflen;
+
+ /* init vpd reader */
+ vrs.bytesinval = 0;
+ vrs.off = 0;
+ vrs.pcib = pcib;
+ vrs.cfg = cfg;
+ vrs.cksum = 0;
+
+ state = 0;
+ name = remain = i = 0; /* shut up stupid gcc */
+ alloc = off = 0; /* shut up stupid gcc */
+ dflen = 0; /* shut up stupid gcc */
+ end = 0;
+ cksumvalid = -1;
+ for (; !end;) {
+ byte = vpd_nextbyte(&vrs);
+#if 0
+ printf("vpd: val: %#x, off: %d, bytesinval: %d, byte: %#hhx, " \
+ "state: %d, remain: %d, name: %#x, i: %d\n", vrs.val,
+ vrs.off, vrs.bytesinval, byte, state, remain, name, i);
+#endif
+ switch (state) {
+ case 0: /* item name */
+ if (byte & 0x80) {
+ remain = vpd_nextbyte(&vrs);
+ remain |= vpd_nextbyte(&vrs) << 8;
+ if (remain > (0x7f*4 - vrs.off)) {
+ end = 1;
+ printf(
+ "pci%d:%d:%d: invalid vpd data, remain %#x\n",
+ cfg->bus, cfg->slot, cfg->func,
+ remain);
+ }
+ name = byte & 0x7f;
+ } else {
+ remain = byte & 0x7;
+ name = (byte >> 3) & 0xf;
+ }
+ switch (name) {
+ case 0x2: /* String */
+ cfg->vpd.vpd_ident = malloc(remain + 1,
+ M_DEVBUF, M_WAITOK);
+ i = 0;
+ state = 1;
+ break;
+ case 0xf: /* End */
+ end = 1;
+ state = -1;
+ break;
+ case 0x10: /* VPD-R */
+ alloc = 8;
+ off = 0;
+ cfg->vpd.vpd_ros = malloc(alloc *
+ sizeof *cfg->vpd.vpd_ros, M_DEVBUF,
+ M_WAITOK);
+ state = 2;
+ break;
+ case 0x11: /* VPD-W */
+ alloc = 8;
+ off = 0;
+ cfg->vpd.vpd_w = malloc(alloc *
+ sizeof *cfg->vpd.vpd_w, M_DEVBUF,
+ M_WAITOK);
+ state = 5;
+ break;
+ default: /* XXX - unimplemented */
+ state = 4;
+ break;
+ }
+ break;
+
+ case 1: /* Identifier String */
+ cfg->vpd.vpd_ident[i++] = byte;
+ remain--;
+ if (remain == 0) {
+ cfg->vpd.vpd_ident[i] = '\0';
+ state = 0;
+ }
+ break;
+
+ case 2: /* VPD-R Keyword Header */
+ if (off == alloc) {
+ cfg->vpd.vpd_ros = reallocf(cfg->vpd.vpd_ros,
+ (alloc *= 2) * sizeof *cfg->vpd.vpd_ros,
+ M_DEVBUF, M_WAITOK);
+ }
+ cfg->vpd.vpd_ros[off].keyword[0] = byte;
+ cfg->vpd.vpd_ros[off].keyword[1] = vpd_nextbyte(&vrs);
+ dflen = vpd_nextbyte(&vrs);
+ cfg->vpd.vpd_ros[off].value = malloc((dflen + 1) *
+ sizeof *cfg->vpd.vpd_ros[off].value,
+ M_DEVBUF, M_WAITOK);
+ remain -= 3;
+ i = 0;
+ state = 3;
+ break;
+
+ case 3: /* VPD-R Keyword Value */
+ cfg->vpd.vpd_ros[off].value[i++] = byte;
+ if (strncmp(cfg->vpd.vpd_ros[off].keyword,
+ "RV", 2) == 0 && cksumvalid == -1) {
+ if (vrs.cksum == 0)
+ cksumvalid = 1;
+ else {
+ printf(
+ "pci%d:%d:%d: bad VPD cksum, remain %hhu\n",
+ cfg->bus, cfg->slot, cfg->func,
+ vrs.cksum);
+ cksumvalid = 0;
+ }
+ }
+ dflen--;
+ remain--;
+ if (dflen == 0)
+ cfg->vpd.vpd_ros[off++].value[i++] = '\0';
+ if (dflen == 0 && remain == 0) {
+ cfg->vpd.vpd_rocnt = off;
+ cfg->vpd.vpd_ros = reallocf(cfg->vpd.vpd_ros,
+ off * sizeof *cfg->vpd.vpd_ros,
+ M_DEVBUF, M_WAITOK);
+ state = 0;
+ } else if (dflen == 0)
+ state = 2;
+ break;
+
+ case 4:
+ remain--;
+ if (remain == 0)
+ state = 0;
+ break;
+
+ case 5: /* VPD-W Keyword Header */
+ if (off == alloc) {
+ cfg->vpd.vpd_w = reallocf(cfg->vpd.vpd_w,
+ (alloc *= 2) * sizeof *cfg->vpd.vpd_w,
+ M_DEVBUF, M_WAITOK);
+ }
+ cfg->vpd.vpd_w[off].keyword[0] = byte;
+ cfg->vpd.vpd_w[off].keyword[1] = vpd_nextbyte(&vrs);
+ cfg->vpd.vpd_w[off].len = dflen = vpd_nextbyte(&vrs);
+ cfg->vpd.vpd_w[off].start = vrs.off - vrs.bytesinval;
+ cfg->vpd.vpd_w[off].value = malloc((dflen + 1) *
+ sizeof *cfg->vpd.vpd_w[off].value,
+ M_DEVBUF, M_WAITOK);
+ remain -= 3;
+ i = 0;
+ state = 6;
+ break;
+
+ case 6: /* VPD-W Keyword Value */
+ cfg->vpd.vpd_w[off].value[i++] = byte;
+ dflen--;
+ remain--;
+ if (dflen == 0)
+ cfg->vpd.vpd_w[off++].value[i++] = '\0';
+ if (dflen == 0 && remain == 0) {
+ cfg->vpd.vpd_wcnt = off;
+ cfg->vpd.vpd_w = reallocf(cfg->vpd.vpd_w,
+ off * sizeof *cfg->vpd.vpd_w,
+ M_DEVBUF, M_WAITOK);
+ state = 0;
+ } else if (dflen == 0)
+ state = 5;
+ break;
+
default:
+ printf("pci%d:%d:%d: invalid state: %d\n",
+ cfg->bus, cfg->slot, cfg->func, state);
+ end = 1;
break;
}
}
#undef REG
}
+int
+pci_get_vpd_ident_method(device_t dev, device_t child, const char **identptr)
+{
+ struct pci_devinfo *dinfo = device_get_ivars(child);
+ pcicfgregs *cfg = &dinfo->cfg;
+
+ *identptr = cfg->vpd.vpd_ident;
+
+ if (*identptr == NULL)
+ return ENXIO;
+
+ return 0;
+}
+
+int
+pci_get_vpd_readonly_method(device_t dev, device_t child, const char *kw,
+ const char **vptr)
+{
+ struct pci_devinfo *dinfo = device_get_ivars(child);
+ pcicfgregs *cfg = &dinfo->cfg;
+ int i;
+
+ for (i = 0; i < cfg->vpd.vpd_rocnt; i++)
+ if (memcmp(kw, cfg->vpd.vpd_ros[i].keyword,
+ sizeof cfg->vpd.vpd_ros[i].keyword) == 0) {
+ *vptr = cfg->vpd.vpd_ros[i].value;
+ }
+
+ if (i != cfg->vpd.vpd_rocnt)
+ return 0;
+
+ *vptr = NULL;
+ return ENXIO;
+}
+
/*
* Return the offset in configuration space of the requested extended
* capability entry or 0 if the specified capability was not found.
@@ -532,9 +831,19 @@ int
pci_freecfg(struct pci_devinfo *dinfo)
{
struct devlist *devlist_head;
+ int i;
devlist_head = &pci_devq;
+ if (dinfo->cfg.vpd.vpd_reg) {
+ free(dinfo->cfg.vpd.vpd_ident, M_DEVBUF);
+ for (i = 0; i < dinfo->cfg.vpd.vpd_rocnt; i++)
+ free(dinfo->cfg.vpd.vpd_ros[i].value, M_DEVBUF);
+ free(dinfo->cfg.vpd.vpd_ros, M_DEVBUF);
+ for (i = 0; i < dinfo->cfg.vpd.vpd_wcnt; i++)
+ free(dinfo->cfg.vpd.vpd_w[i].value, M_DEVBUF);
+ free(dinfo->cfg.vpd.vpd_w, M_DEVBUF);
+ }
STAILQ_REMOVE(devlist_head, dinfo, pci_devinfo, pci_links);
free(dinfo, M_DEVBUF);
@@ -766,6 +1075,8 @@ pci_disable_io_method(device_t dev, device_t child, int space)
void
pci_print_verbose(struct pci_devinfo *dinfo)
{
+ int i;
+
if (bootverbose) {
pcicfgregs *cfg = &dinfo->cfg;
@@ -794,6 +1105,31 @@ pci_print_verbose(struct pci_devinfo *dinfo)
cfg->pp.pp_cap & PCIM_PCAP_D2SUPP ? " D2" : "",
status & PCIM_PSTAT_DMASK);
}
+ if (cfg->vpd.vpd_reg) {
+ printf("\tVPD Ident: %s\n", cfg->vpd.vpd_ident);
+ for (i = 0; i < cfg->vpd.vpd_rocnt; i++) {
+ struct vpd_readonly *vrop;
+ vrop = &cfg->vpd.vpd_ros[i];
+ if (strncmp("CP", vrop->keyword, 2) == 0)
+ printf("CP: id %d, BAR%d, off %#x\n",
+ vrop->value[0], vrop->value[1],
+ le16toh(
+ *(uint16_t *)&vrop->value[2]));
+ else if (strncmp("RV", vrop->keyword, 2) == 0)
+ printf("RV: %#hhx\n", vrop->value[0]);
+ else
+ printf("\t%.2s: %s\n", vrop->keyword,
+ vrop->value);
+ }
+ for (i = 0; i < cfg->vpd.vpd_wcnt; i++) {
+ struct vpd_write *vwp;
+ vwp = &cfg->vpd.vpd_w[i];
+ if (strncmp("RW", vwp->keyword, 2) != 0)
+ printf("\t%.2s(%#x-%#x): %s\n",
+ vwp->keyword, vwp->start,
+ vwp->start + vwp->len, vwp->value);
+ }
+ }
if (cfg->msi.msi_data) {
int ctrl;
OpenPOWER on IntegriCloud