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.

MFC after:	1 week
This commit is contained in:
John Baldwin 2014-01-20 20:56:09 +00:00
parent fe0f2d73ae
commit 84b755dfe5
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=260926
6 changed files with 250 additions and 19 deletions

View File

@ -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

View File

@ -406,6 +406,89 @@ pci_conf_match_old32(struct pci_match_conf_old32 *matches, int num_matches,
#endif /* COMPAT_FREEBSD32 */
#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)
{
@ -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;
if (!(flag & FWRITE) && cmd != PCIOCGETBAR &&
cmd != PCIOCGETCONF && cmd != PCIOCGETCONF_OLD)
return EPERM;
#else
if (!(flag & FWRITE) && cmd != PCIOCGETBAR && cmd != PCIOCGETCONF)
return EPERM;
#endif
switch(cmd) {
if (!(flag & FWRITE)) {
switch (cmd) {
#ifdef PRE7_COMPAT
#ifdef COMPAT_FREEBSD32
case PCIOCGETCONF_OLD32:
case PCIOCGETCONF_OLD32:
#endif
case PCIOCGETCONF_OLD:
#endif
case PCIOCGETCONF:
case PCIOCGETBAR:
case PCIOCLISTVPD:
break;
default:
return (EPERM);
}
}
switch (cmd) {
#ifdef PRE7_COMPAT
#ifdef COMPAT_FREEBSD32
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 @@ pci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *t
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;

View File

@ -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

View File

@ -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_ */

View File

@ -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

View File

@ -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.
*/