summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjhb <jhb@FreeBSD.org>2014-02-17 22:19:49 +0000
committerjhb <jhb@FreeBSD.org>2014-02-17 22:19:49 +0000
commit6e1e8b0ada39b25a62d9235c22cfc2d2f9a63072 (patch)
tree813983fd35b494e0597706a51217b6688b64e517
parent547b2fb503640e47551e78e30d597c6075b3529f (diff)
downloadFreeBSD-src-6e1e8b0ada39b25a62d9235c22cfc2d2f9a63072.zip
FreeBSD-src-6e1e8b0ada39b25a62d9235c22cfc2d2f9a63072.tar.gz
MFC 260926:
Add support for displaying VPD for PCI devices via pciconf. - Store the length of each read-only VPD value since not all values are guaranteed to be ASCII values (though most are). - Add a new pciio ioctl to fetch VPD for a single PCI device. The values are returned as a list of variable length records, one for the device name and each keyword. - Add a new -V flag to pciconf's list mode which displays VPD data for each device.
-rw-r--r--sys/dev/pci/pci.c13
-rw-r--r--sys/dev/pci/pci_user.c128
-rw-r--r--sys/dev/pci/pcivar.h2
-rw-r--r--sys/sys/pciio.h21
-rw-r--r--usr.sbin/pciconf/pciconf.824
-rw-r--r--usr.sbin/pciconf/pciconf.c79
6 files changed, 249 insertions, 18 deletions
diff --git a/sys/dev/pci/pci.c b/sys/dev/pci/pci.c
index 4d8837f..84aab9d 100644
--- a/sys/dev/pci/pci.c
+++ b/sys/dev/pci/pci.c
@@ -985,7 +985,7 @@ pci_read_vpd(device_t pcib, pcicfgregs *cfg)
state = -2;
break;
}
- dflen = byte2;
+ cfg->vpd.vpd_ros[off].len = dflen = byte2;
if (dflen == 0 &&
strncmp(cfg->vpd.vpd_ros[off].keyword, "RV",
2) == 0) {
@@ -1179,6 +1179,17 @@ pci_get_vpd_readonly_method(device_t dev, device_t child, const char *kw,
return (ENXIO);
}
+struct pcicfg_vpd *
+pci_fetch_vpd_list(device_t dev)
+{
+ struct pci_devinfo *dinfo = device_get_ivars(dev);
+ pcicfgregs *cfg = &dinfo->cfg;
+
+ if (!cfg->vpd.vpd_cached && cfg->vpd.vpd_reg != 0)
+ pci_read_vpd(device_get_parent(device_get_parent(dev)), cfg);
+ return (&cfg->vpd);
+}
+
/*
* Find the requested HyperTransport capability and return the offset
* in configuration space via the pointer provided. The function
diff --git a/sys/dev/pci/pci_user.c b/sys/dev/pci/pci_user.c
index a5b58c3..38290c1 100644
--- a/sys/dev/pci/pci_user.c
+++ b/sys/dev/pci/pci_user.c
@@ -407,6 +407,89 @@ pci_conf_match_old32(struct pci_match_conf_old32 *matches, int num_matches,
#endif /* PRE7_COMPAT */
static int
+pci_list_vpd(device_t dev, struct pci_list_vpd_io *lvio)
+{
+ struct pci_vpd_element vpd_element, *vpd_user;
+ struct pcicfg_vpd *vpd;
+ size_t len;
+ int error, i;
+
+ vpd = pci_fetch_vpd_list(dev);
+ if (vpd->vpd_reg == 0 || vpd->vpd_ident == NULL)
+ return (ENXIO);
+
+ /*
+ * Calculate the amount of space needed in the data buffer. An
+ * identifier element is always present followed by the read-only
+ * and read-write keywords.
+ */
+ len = sizeof(struct pci_vpd_element) + strlen(vpd->vpd_ident);
+ for (i = 0; i < vpd->vpd_rocnt; i++)
+ len += sizeof(struct pci_vpd_element) + vpd->vpd_ros[i].len;
+ for (i = 0; i < vpd->vpd_wcnt; i++)
+ len += sizeof(struct pci_vpd_element) + vpd->vpd_w[i].len;
+
+ if (lvio->plvi_len == 0) {
+ lvio->plvi_len = len;
+ return (0);
+ }
+ if (lvio->plvi_len < len) {
+ lvio->plvi_len = len;
+ return (ENOMEM);
+ }
+
+ /*
+ * Copyout the identifier string followed by each keyword and
+ * value.
+ */
+ vpd_user = lvio->plvi_data;
+ vpd_element.pve_keyword[0] = '\0';
+ vpd_element.pve_keyword[1] = '\0';
+ vpd_element.pve_flags = PVE_FLAG_IDENT;
+ vpd_element.pve_datalen = strlen(vpd->vpd_ident);
+ error = copyout(&vpd_element, vpd_user, sizeof(vpd_element));
+ if (error)
+ return (error);
+ error = copyout(vpd->vpd_ident, vpd_user->pve_data,
+ strlen(vpd->vpd_ident));
+ if (error)
+ return (error);
+ vpd_user = PVE_NEXT(vpd_user);
+ vpd_element.pve_flags = 0;
+ for (i = 0; i < vpd->vpd_rocnt; i++) {
+ vpd_element.pve_keyword[0] = vpd->vpd_ros[i].keyword[0];
+ vpd_element.pve_keyword[1] = vpd->vpd_ros[i].keyword[1];
+ vpd_element.pve_datalen = vpd->vpd_ros[i].len;
+ error = copyout(&vpd_element, vpd_user, sizeof(vpd_element));
+ if (error)
+ return (error);
+ error = copyout(vpd->vpd_ros[i].value, vpd_user->pve_data,
+ vpd->vpd_ros[i].len);
+ if (error)
+ return (error);
+ vpd_user = PVE_NEXT(vpd_user);
+ }
+ vpd_element.pve_flags = PVE_FLAG_RW;
+ for (i = 0; i < vpd->vpd_wcnt; i++) {
+ vpd_element.pve_keyword[0] = vpd->vpd_w[i].keyword[0];
+ vpd_element.pve_keyword[1] = vpd->vpd_w[i].keyword[1];
+ vpd_element.pve_datalen = vpd->vpd_w[i].len;
+ error = copyout(&vpd_element, vpd_user, sizeof(vpd_element));
+ if (error)
+ return (error);
+ error = copyout(vpd->vpd_w[i].value, vpd_user->pve_data,
+ vpd->vpd_w[i].len);
+ if (error)
+ return (error);
+ vpd_user = PVE_NEXT(vpd_user);
+ }
+ KASSERT((char *)vpd_user - (char *)lvio->plvi_data == len,
+ ("length mismatch"));
+ lvio->plvi_len = len;
+ return (0);
+}
+
+static int
pci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
{
device_t pcidev, brdev;
@@ -417,6 +500,7 @@ pci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *t
struct pci_devinfo *dinfo;
struct pci_io *io;
struct pci_bar_io *bio;
+ struct pci_list_vpd_io *lvio;
struct pci_match_conf *pattern_buf;
struct pci_map *pm;
size_t confsz, iolen, pbufsz;
@@ -433,19 +517,29 @@ pci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *t
struct pci_match_conf_old *pattern_buf_old = NULL;
io_old = NULL;
+#endif
- if (!(flag & FWRITE) && cmd != PCIOCGETBAR &&
- cmd != PCIOCGETCONF && cmd != PCIOCGETCONF_OLD)
- return EPERM;
-#else
- if (!(flag & FWRITE) && cmd != PCIOCGETBAR && cmd != PCIOCGETCONF)
- return EPERM;
+ if (!(flag & FWRITE)) {
+ switch (cmd) {
+#ifdef PRE7_COMPAT
+#ifdef COMPAT_FREEBSD32
+ case PCIOCGETCONF_OLD32:
#endif
+ case PCIOCGETCONF_OLD:
+#endif
+ case PCIOCGETCONF:
+ case PCIOCGETBAR:
+ case PCIOCLISTVPD:
+ break;
+ default:
+ return (EPERM);
+ }
+ }
- switch(cmd) {
+ switch (cmd) {
#ifdef PRE7_COMPAT
#ifdef COMPAT_FREEBSD32
- case PCIOCGETCONF_OLD32:
+ case PCIOCGETCONF_OLD32:
cio32 = (struct pci_conf_io32 *)data;
cio = malloc(sizeof(struct pci_conf_io), M_TEMP, M_WAITOK);
cio->pat_buf_len = cio32->pat_buf_len;
@@ -466,7 +560,7 @@ pci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *t
cio = (struct pci_conf_io *)data;
}
- switch(cmd) {
+ switch (cmd) {
#ifdef PRE7_COMPAT
#ifdef COMPAT_FREEBSD32
case PCIOCGETCONF_OLD32:
@@ -912,6 +1006,22 @@ getconfexit:
else
error = ENODEV;
break;
+ case PCIOCLISTVPD:
+ lvio = (struct pci_list_vpd_io *)data;
+
+ /*
+ * Assume that the user-level bus number is
+ * in fact the physical PCI bus number.
+ */
+ pcidev = pci_find_dbsf(lvio->plvi_sel.pc_domain,
+ lvio->plvi_sel.pc_bus, lvio->plvi_sel.pc_dev,
+ lvio->plvi_sel.pc_func);
+ if (pcidev == NULL) {
+ error = ENODEV;
+ break;
+ }
+ error = pci_list_vpd(pcidev, lvio);
+ break;
default:
error = ENOTTY;
break;
diff --git a/sys/dev/pci/pcivar.h b/sys/dev/pci/pcivar.h
index 8f93f96..06e5771 100644
--- a/sys/dev/pci/pcivar.h
+++ b/sys/dev/pci/pcivar.h
@@ -57,6 +57,7 @@ struct pci_map {
struct vpd_readonly {
char keyword[2];
char *value;
+ int len;
};
struct vpd_write {
@@ -525,6 +526,7 @@ extern uint32_t pci_generation;
struct pci_map *pci_find_bar(device_t dev, int reg);
int pci_bar_enabled(device_t dev, struct pci_map *pm);
+struct pcicfg_vpd *pci_fetch_vpd_list(device_t dev);
#define VGA_PCI_BIOS_SHADOW_ADDR 0xC0000
#define VGA_PCI_BIOS_SHADOW_SIZE 131072
diff --git a/sys/sys/pciio.h b/sys/sys/pciio.h
index a0c4560..d70bfbc 100644
--- a/sys/sys/pciio.h
+++ b/sys/sys/pciio.h
@@ -116,10 +116,31 @@ struct pci_bar_io {
uint64_t pbi_length; /* length of BAR */
};
+struct pci_vpd_element {
+ char pve_keyword[2];
+ uint8_t pve_flags;
+ uint8_t pve_datalen;
+ uint8_t pve_data[0];
+};
+
+#define PVE_FLAG_IDENT 0x01 /* Element is the string identifier */
+#define PVE_FLAG_RW 0x02 /* Element is read/write */
+
+#define PVE_NEXT(pve) \
+ ((struct pci_vpd_element *)((char *)(pve) + \
+ sizeof(struct pci_vpd_element) + (pve)->pve_datalen))
+
+struct pci_list_vpd_io {
+ struct pcisel plvi_sel; /* device to operate on */
+ size_t plvi_len; /* size of the data area */
+ struct pci_vpd_element *plvi_data;
+};
+
#define PCIOCGETCONF _IOWR('p', 5, struct pci_conf_io)
#define PCIOCREAD _IOWR('p', 2, struct pci_io)
#define PCIOCWRITE _IOWR('p', 3, struct pci_io)
#define PCIOCATTACHED _IOWR('p', 4, struct pci_io)
#define PCIOCGETBAR _IOWR('p', 6, struct pci_bar_io)
+#define PCIOCLISTVPD _IOWR('p', 7, struct pci_list_vpd_io)
#endif /* !_SYS_PCIIO_H_ */
diff --git a/usr.sbin/pciconf/pciconf.8 b/usr.sbin/pciconf/pciconf.8
index 7eacfab..e0caf1f 100644
--- a/usr.sbin/pciconf/pciconf.8
+++ b/usr.sbin/pciconf/pciconf.8
@@ -33,7 +33,7 @@
.Nd diagnostic utility for the PCI bus
.Sh SYNOPSIS
.Nm
-.Fl l Oo Fl bcev Oc Op Ar device
+.Fl l Oo Fl bcevV Oc Op Ar device
.Nm
.Fl a Ar device
.Nm
@@ -182,6 +182,28 @@ option is supplied,
will attempt to load the vendor/device information database, and print
vendor, device, class and subclass identification strings for each device.
.Pp
+If the
+.Fl V
+option is supplied,
+.Nm
+will list any vital product data
+.Pq VPD
+provided by each device.
+Each VPD keyword is enumerated via a line in the following format:
+.Bd -literal
+ VPD ro PN = '110114640C0 '
+.Ed
+.Pp
+The first string after the
+.Dq Li VPD
+prefix indicates if the keyword is read-only
+.Dq ro
+or read-write
+.Dq rw .
+The second string provides the keyword name.
+The text after the the equals sign lists the value of the keyword which is
+usually an ASCII string.
+.Pp
If the optional
.Ar device
argument is given with the
diff --git a/usr.sbin/pciconf/pciconf.c b/usr.sbin/pciconf/pciconf.c
index 0d1bbde..0177dc9 100644
--- a/usr.sbin/pciconf/pciconf.c
+++ b/usr.sbin/pciconf/pciconf.c
@@ -71,8 +71,9 @@ TAILQ_HEAD(,pci_vendor_info) pci_vendors;
static struct pcisel getsel(const char *str);
static void list_bars(int fd, struct pci_conf *p);
static void list_devs(const char *name, int verbose, int bars, int caps,
- int errors);
+ int errors, int vpd);
static void list_verbose(struct pci_conf *p);
+static void list_vpd(int fd, struct pci_conf *p);
static const char *guess_class(struct pci_conf *p);
static const char *guess_subclass(struct pci_conf *p);
static int load_vendors(void);
@@ -86,7 +87,7 @@ static void
usage(void)
{
fprintf(stderr, "%s\n%s\n%s\n%s\n",
- "usage: pciconf -l [-bcev] [device]",
+ "usage: pciconf -l [-bcevV] [device]",
" pciconf -a device",
" pciconf -r [-b | -h] device addr[:addr2]",
" pciconf -w [-b | -h] device addr value");
@@ -98,13 +99,13 @@ main(int argc, char **argv)
{
int c;
int listmode, readmode, writemode, attachedmode;
- int bars, caps, errors, verbose;
+ int bars, caps, errors, verbose, vpd;
int byte, isshort;
listmode = readmode = writemode = attachedmode = 0;
- bars = caps = errors = verbose = byte = isshort = 0;
+ bars = caps = errors = verbose = vpd = byte = isshort = 0;
- while ((c = getopt(argc, argv, "abcehlrwv")) != -1) {
+ while ((c = getopt(argc, argv, "abcehlrwvV")) != -1) {
switch(c) {
case 'a':
attachedmode = 1;
@@ -143,6 +144,10 @@ main(int argc, char **argv)
verbose = 1;
break;
+ case 'V':
+ vpd = 1;
+ break;
+
default:
usage();
}
@@ -156,7 +161,7 @@ main(int argc, char **argv)
if (listmode) {
list_devs(optind + 1 == argc ? argv[optind] : NULL, verbose,
- bars, caps, errors);
+ bars, caps, errors, vpd);
} else if (attachedmode) {
chkattached(argv[optind]);
} else if (readmode) {
@@ -173,7 +178,8 @@ main(int argc, char **argv)
}
static void
-list_devs(const char *name, int verbose, int bars, int caps, int errors)
+list_devs(const char *name, int verbose, int bars, int caps, int errors,
+ int vpd)
{
int fd;
struct pci_conf_io pc;
@@ -246,6 +252,8 @@ list_devs(const char *name, int verbose, int bars, int caps, int errors)
list_caps(fd, p);
if (errors)
list_errors(fd, p);
+ if (vpd)
+ list_vpd(fd, p);
}
} while (pc.status == PCI_GETCONF_MORE_DEVS);
@@ -339,6 +347,63 @@ list_verbose(struct pci_conf *p)
printf(" subclass = %s\n", dp);
}
+static void
+list_vpd(int fd, struct pci_conf *p)
+{
+ struct pci_list_vpd_io list;
+ struct pci_vpd_element *vpd, *end;
+
+ list.plvi_sel = p->pc_sel;
+ list.plvi_len = 0;
+ list.plvi_data = NULL;
+ if (ioctl(fd, PCIOCLISTVPD, &list) < 0 || list.plvi_len == 0)
+ return;
+
+ list.plvi_data = malloc(list.plvi_len);
+ if (ioctl(fd, PCIOCLISTVPD, &list) < 0) {
+ free(list.plvi_data);
+ return;
+ }
+
+ vpd = list.plvi_data;
+ end = (struct pci_vpd_element *)((char *)vpd + list.plvi_len);
+ for (; vpd < end; vpd = PVE_NEXT(vpd)) {
+ if (vpd->pve_flags == PVE_FLAG_IDENT) {
+ printf(" VPD ident = '%.*s'\n",
+ (int)vpd->pve_datalen, vpd->pve_data);
+ continue;
+ }
+
+ /* Ignore the checksum keyword. */
+ if (!(vpd->pve_flags & PVE_FLAG_RW) &&
+ memcmp(vpd->pve_keyword, "RV", 2) == 0)
+ continue;
+
+ /* Ignore remaining read-write space. */
+ if (vpd->pve_flags & PVE_FLAG_RW &&
+ memcmp(vpd->pve_keyword, "RW", 2) == 0)
+ continue;
+
+ /* Handle extended capability keyword. */
+ if (!(vpd->pve_flags & PVE_FLAG_RW) &&
+ memcmp(vpd->pve_keyword, "CP", 2) == 0) {
+ printf(" VPD ro CP = ID %02x in map 0x%x[0x%x]\n",
+ (unsigned int)vpd->pve_data[0],
+ PCIR_BAR((unsigned int)vpd->pve_data[1]),
+ (unsigned int)vpd->pve_data[3] << 8 |
+ (unsigned int)vpd->pve_data[2]);
+ continue;
+ }
+
+ /* Remaining keywords should all have ASCII values. */
+ printf(" VPD %s %c%c = '%.*s'\n",
+ vpd->pve_flags & PVE_FLAG_RW ? "rw" : "ro",
+ vpd->pve_keyword[0], vpd->pve_keyword[1],
+ (int)vpd->pve_datalen, vpd->pve_data);
+ }
+ free(list.plvi_data);
+}
+
/*
* This is a direct cut-and-paste from the table in sys/dev/pci/pci.c.
*/
OpenPOWER on IntegriCloud