summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorImre Vadász <ivadasz@google.com>2017-07-14 23:05:53 +0200
committerMartin Mares <mj@ucw.cz>2017-11-17 13:57:00 +0100
commit0f3d0ca73ecedaba180bf4607bb57fb8abe6d405 (patch)
tree1aa662a217fb2c382ce2c298e94a0d52bede60e5
parent7663958f7515a26d08ec6a5b22ce0de931d2c570 (diff)
downloadpciutils-0f3d0ca73ecedaba180bf4607bb57fb8abe6d405.tar.gz
fbsd-device: Use PCIOCGETCONF and PCIOCGETBAR when /dev/pci fd is readonly.
This way we can at least fulfill some of the common requests without root privileges. This allows various applications (for example the google chrome webbrowser) to successfully probe the list of PCI devices without needing read-write access to the /dev/pci device file. Signed-off-by: Imre Vadász <imrevdsz@gmail.com>
-rw-r--r--lib/fbsd-device.c209
1 files changed, 201 insertions, 8 deletions
diff --git a/lib/fbsd-device.c b/lib/fbsd-device.c
index 7cebb0d..78067dc 100644
--- a/lib/fbsd-device.c
+++ b/lib/fbsd-device.c
@@ -3,6 +3,7 @@
*
* Copyright (c) 1999 Jari Kirma <kirma@cs.hut.fi>
* Updated in 2003 by Samy Al Bahra <samy@kerneled.com>
+ * Updated in 2017 by Imre Vadász <imrevdsz@gmail.com>
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
@@ -10,6 +11,7 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <osreldate.h>
@@ -54,16 +56,195 @@ static void
fbsd_init(struct pci_access *a)
{
char *name = pci_get_param(a, "fbsd.path");
+ int fd;
- a->fd = open(name, O_RDWR, 0);
- if (a->fd < 0)
- a->error("fbsd_init: %s open failed", name);
+ a->fd = -1;
+ a->fd_rw = -1;
+ /*
+ * When opening /dev/pci as read-write fails, retry with readonly which
+ * will still allow us to gain some information via the PCIOCGETCONF and
+ * PCIOCGETBAR IOCTLs, even without generic read access to the PCI config
+ * space.
+ */
+ fd = open(name, O_RDWR, 0);
+ if (fd < 0)
+ {
+ fd = open(name, O_RDONLY, 0);
+ if (fd < 0)
+ a->error("fbsd_init: %s open failed", name);
+ else
+ {
+ a->debug("fbsd_init: Fallback to read-only opened %s", name);
+ a->fd = fd;
+ }
+ }
+ else
+ a->fd_rw = fd;
}
static void
fbsd_cleanup(struct pci_access *a)
{
- close(a->fd);
+ if (a->fd >= 0)
+ {
+ close(a->fd);
+ a->fd = -1;
+ }
+ if (a->fd_rw >= 0)
+ {
+ close(a->fd_rw);
+ a->fd_rw = -1;
+ }
+}
+
+static void
+fbsd_scan(struct pci_access *a)
+{
+ struct pci_conf_io conf;
+ struct pci_conf *matches;
+ struct pci_dev *t;
+ uint32_t offset = 0;
+ unsigned int i;
+
+ matches = calloc(32, sizeof(struct pci_conf));
+ if (matches == NULL)
+ {
+ a->error("calloc: %s", strerror(errno));
+ return;
+ }
+
+ conf.generation = 0;
+ do
+ {
+ conf.pat_buf_len = 0;
+ conf.num_patterns = 0;
+ conf.patterns = NULL;
+ conf.match_buf_len = 32 * sizeof(struct pci_conf);
+ conf.num_matches = 32;
+ conf.matches = matches;
+ conf.offset = offset;
+ conf.status = 0;
+ if (ioctl(a->fd_rw >= 0 ? a->fd_rw : a->fd, PCIOCGETCONF, &conf) < 0)
+ {
+ if (errno == ENODEV)
+ break;
+ a->error("fbsd_scan: ioctl(PCIOCGETCONF) failed: %s",
+ strerror(errno));
+ }
+ /* PCI_GETCONF_LIST_CHANGED would require us to start over. */
+ if (conf.status == PCI_GETCONF_ERROR ||
+ conf.status == PCI_GETCONF_LIST_CHANGED)
+ {
+ a->error("fbsd_scan: ioctl(PCIOCGETCONF) failed");
+ break;
+ }
+ for (i = 0; i < conf.num_matches; i++)
+ {
+ t = pci_alloc_dev(a);
+ t->bus = matches[i].pc_sel.pc_bus;
+ t->dev = matches[i].pc_sel.pc_dev;
+ t->dev = matches[i].pc_sel.pc_dev;
+ t->domain = matches[i].pc_sel.pc_domain;
+ t->domain_16 = matches[i].pc_sel.pc_domain;
+ t->vendor_id = matches[i].pc_vendor;
+ t->device_id = matches[i].pc_device;
+ t->known_fields = PCI_FILL_IDENT;
+ t->hdrtype = matches[i].pc_hdr;
+ pci_link_dev(a, t);
+ }
+ offset += conf.num_matches;
+ }
+ while (conf.status == PCI_GETCONF_MORE_DEVS);
+
+ free(matches);
+}
+
+static int
+fbsd_fill_info(struct pci_dev *d, int flags)
+{
+ struct pci_conf_io conf;
+ struct pci_bar_io bar;
+ struct pci_match_conf pattern;
+ struct pci_conf match;
+ int i;
+
+ if (d->access->fd_rw >= 0)
+ return pci_generic_fill_info(d, flags);
+
+ /*
+ * Can only handle PCI_FILL_IDENT, PCI_FILL_CLASS, PCI_FILL_BASES and
+ * PCI_FILL_SIZES requests with the PCIOCGETCONF and PCIOCGETBAR IOCTLs.
+ */
+
+ conf.pat_buf_len = sizeof(struct pci_match_conf);
+ conf.num_patterns = 1;
+ conf.patterns = &pattern;
+ conf.match_buf_len = sizeof(struct pci_conf);
+ conf.num_matches = 1;
+ conf.matches = &match;
+ conf.offset = 0;
+ conf.generation = 0;
+ conf.status = 0;
+
+ pattern.pc_sel.pc_domain = d->domain;
+ pattern.pc_sel.pc_bus = d->bus;
+ pattern.pc_sel.pc_dev = d->dev;
+ pattern.pc_sel.pc_func = d->func;
+ pattern.flags = PCI_GETCONF_MATCH_DOMAIN | PCI_GETCONF_MATCH_BUS |
+ PCI_GETCONF_MATCH_DEV | PCI_GETCONF_MATCH_FUNC;
+
+ if (ioctl(d->access->fd, PCIOCGETCONF, &conf) < 0)
+ {
+ if (errno == ENODEV)
+ return 0;
+ d->access->error("fbsd_fill_info: ioctl(PCIOCGETCONF) failed: %s", strerror(errno));
+ }
+
+ if (flags & PCI_FILL_IDENT)
+ {
+ d->vendor_id = match.pc_vendor;
+ d->device_id = match.pc_device;
+ }
+ if (flags & PCI_FILL_CLASS)
+ {
+ d->device_class = match.pc_class | (match.pc_subclass << 8);
+ }
+ if (flags & (PCI_FILL_BASES | PCI_FILL_SIZES))
+ {
+ d->rom_base_addr = 0;
+ d->rom_size = 0;
+ for (i = 0; i < 6; i++)
+ {
+ bar.pbi_sel.pc_domain = d->domain;
+ bar.pbi_sel.pc_bus = d->bus;
+ bar.pbi_sel.pc_dev = d->dev;
+ bar.pbi_sel.pc_func = d->func;
+ bar.pbi_reg = 0x10 + 4*i;
+ bar.pbi_enabled = 0;
+ bar.pbi_base = 0;
+ bar.pbi_length = 0;
+ if (ioctl(d->access->fd, PCIOCGETBAR, &bar) < 0)
+ {
+ if (errno == ENODEV)
+ return 0;
+ if (errno == EINVAL)
+ {
+ d->base_addr[i] = 0;
+ d->size[i] = 0;
+ }
+ else
+ d->access->error("fbsd_fill_info: ioctl(PCIOCGETBAR) failed: %s", strerror(errno));
+ }
+ else
+ {
+ d->base_addr[i] = bar.pbi_base;
+ d->size[i] = bar.pbi_length;
+ }
+ }
+ }
+
+ return flags & (PCI_FILL_IDENT | PCI_FILL_CLASS | PCI_FILL_BASES |
+ PCI_FILL_SIZES);
}
static int
@@ -71,6 +252,12 @@ fbsd_read(struct pci_dev *d, int pos, byte *buf, int len)
{
struct pci_io pi;
+ if (d->access->fd_rw < 0)
+ {
+ d->access->warn("fbsd_read: missing permissions");
+ return 0;
+ }
+
if (!(len == 1 || len == 2 || len == 4))
return pci_generic_block_read(d, pos, buf, len);
@@ -87,7 +274,7 @@ fbsd_read(struct pci_dev *d, int pos, byte *buf, int len)
pi.pi_reg = pos;
pi.pi_width = len;
- if (ioctl(d->access->fd, PCIOCREAD, &pi) < 0)
+ if (ioctl(d->access->fd_rw, PCIOCREAD, &pi) < 0)
{
if (errno == ENODEV)
return 0;
@@ -114,6 +301,12 @@ fbsd_write(struct pci_dev *d, int pos, byte *buf, int len)
{
struct pci_io pi;
+ if (d->access->fd_rw < 0)
+ {
+ d->access->warn("fbsd_write: missing permissions");
+ return 0;
+ }
+
if (!(len == 1 || len == 2 || len == 4))
return pci_generic_block_write(d, pos, buf, len);
@@ -143,7 +336,7 @@ fbsd_write(struct pci_dev *d, int pos, byte *buf, int len)
break;
}
- if (ioctl(d->access->fd, PCIOCWRITE, &pi) < 0)
+ if (ioctl(d->access->fd_rw, PCIOCWRITE, &pi) < 0)
{
if (errno == ENODEV)
return 0;
@@ -160,8 +353,8 @@ struct pci_methods pm_fbsd_device = {
fbsd_detect,
fbsd_init,
fbsd_cleanup,
- pci_generic_scan,
- pci_generic_fill_info,
+ fbsd_scan,
+ fbsd_fill_info,
fbsd_read,
fbsd_write,
NULL, /* read_vpd */