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:
parent
fe0f2d73ae
commit
84b755dfe5
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=260926
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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_ */
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user