From 11f7b31bba25a7cdabb3cd64d3c6ba89975a2695 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sat, 27 Dec 2003 19:42:38 +0000 Subject: [PATCH] Added sysfs support * lib/pci.h (PCIADDR_PORT_FMT): Use %llx instead of %Lx, because the latter is not supported by all C libraries. * Makefile: Always enter the lib directory (remember that we don't have full dependecies for the library in the top-level Makefile; hmmm, another thing to rewrite some day). * lib/sysfs.c: Added Linux sysfs access method based on the patch written by Matthew Wilcox . * lib/proc.c: Renamed the access method name from "/proc/bus/pci" to "Linux-proc". * lib/pread.h: The hacks to support pread on various versions of Linux libc moved there. * lib/proc.c (proc_setup): The return value of snprintf() varies between glibc versions, so we need to check both for a negative values and for too large values. git-archimport-id: mj@ucw.cz--public/pciutils--main--2.2--patch-33 --- ChangeLog | 19 ++++ Makefile | 6 +- TODO | 6 ++ lib/Makefile | 9 +- lib/access.c | 5 + lib/configure | 4 +- lib/internal.h | 2 +- lib/pci.h | 8 +- lib/pread.h | 66 ++++++++++++ lib/proc.c | 69 ++----------- lib/sysfs.c | 269 +++++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 392 insertions(+), 71 deletions(-) create mode 100644 lib/pread.h create mode 100644 lib/sysfs.c diff --git a/ChangeLog b/ChangeLog index 0947e96..1e51c6c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,24 @@ 2003-12-27 Martin Mares + * lib/pci.h (PCIADDR_PORT_FMT): Use %llx instead of %Lx, because the latter + is not supported by all C libraries. + + * Makefile: Always enter the lib directory (remember that we don't have + full dependecies for the library in the top-level Makefile; hmmm, another + thing to rewrite some day). + + * lib/sysfs.c: Added Linux sysfs access method based on the patch + written by Matthew Wilcox . + + * lib/proc.c: Renamed the access method name from "/proc/bus/pci" to "Linux-proc". + + * lib/pread.h: The hacks to support pread on various versions + of Linux libc moved there. + + * lib/proc.c (proc_setup): The return value of snprintf() varies + between glibc versions, so we need to check both for a negative + values and for too large values. + * Removed last few references to the "Linux PCI Utilities", the package is pretty cross-platform now :) diff --git a/Makefile b/Makefile index fb5eeca..f8a869b 100644 --- a/Makefile +++ b/Makefile @@ -31,9 +31,11 @@ export all: $(PCILIB) lspci setpci lspci.8 setpci.8 update-pciids update-pciids.8 pci.ids -$(PCILIB): $(PCIINC) +$(PCILIB): $(PCIINC) force $(MAKE) -C lib all +force: + lib/config.h: cd lib && ./configure $(SHAREDIR) $(VERSION) @@ -73,4 +75,4 @@ uninstall: all get-ids: cp ~/tree/pciids/pci.ids pci.ids -.PHONY: all clean distclean install uninstall get-ids +.PHONY: all clean distclean install uninstall get-ids force diff --git a/TODO b/TODO index b239f63..86c2b09 100644 --- a/TODO +++ b/TODO @@ -2,3 +2,9 @@ "available only to root" things in the man page. - pci.ids: "Unknown mass storage controller" -> "Mass storage controller". + +- names.c: rewrite + +- support for domains: + - pci_get_dev + - filters diff --git a/lib/Makefile b/lib/Makefile index b48c4ce..b0a2de8 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -8,6 +8,10 @@ INCL=internal.h pci.h config.h PCILIB=libpci.a +ifdef HAVE_PM_LINUX_SYSFS +OBJS += sysfs.o +endif + ifdef HAVE_PM_LINUX_PROC OBJS += proc.o endif @@ -53,8 +57,9 @@ $(PCILIB): $(OBJS) ranlib $@ access.o: access.c $(INCL) -i386-ports.o: i386-ports.c $(INCL) -proc.o: proc.c $(INCL) +i386-ports.o: i386-ports.c $(INCL) i386-io-hurd.h i386-io-linux.h i386-io-sunos.h +proc.o: proc.c $(INCL) pread.h +sysfs.o: sysfs.c $(INCL) pread.h generic.o: generic.c $(INCL) syscalls.o: syscalls.c $(INCL) fbsd-device.o: fbsd-device.c $(INCL) diff --git a/lib/access.c b/lib/access.c index c0e04ef..55060b5 100644 --- a/lib/access.c +++ b/lib/access.c @@ -15,6 +15,11 @@ static struct pci_methods *pci_methods[PCI_ACCESS_MAX] = { NULL, +#ifdef HAVE_PM_LINUX_SYSFS + &pm_linux_sysfs, +#else + NULL, +#endif #ifdef HAVE_PM_LINUX_PROC &pm_linux_proc, #else diff --git a/lib/configure b/lib/configure index 250350f..6fd652a 100755 --- a/lib/configure +++ b/lib/configure @@ -33,10 +33,12 @@ echo_n "Looking for access methods..." case $sys in Linux) case $rel in - 2.[1-9]*|[3-9]*) echo_n " proc" + 2.[1-9]*|[3-9]*) echo_n "sysfs proc" + echo >>$c '#define HAVE_PM_LINUX_SYSFS' echo >>$c '#define HAVE_PM_LINUX_PROC' echo >>$c '#define HAVE_LINUX_BYTEORDER_H' echo >>$c '#define PATH_PROC_BUS_PCI "/proc/bus/pci"' + echo >>$c '#define PATH_SYS_BUS_PCI "/sys/bus/pci"' ok=1 ;; esac diff --git a/lib/internal.h b/lib/internal.h index 8f1f8f8..b18ccd1 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -99,4 +99,4 @@ struct pci_dev *pci_alloc_dev(struct pci_access *); int pci_link_dev(struct pci_access *, struct pci_dev *); extern struct pci_methods pm_intel_conf1, pm_intel_conf2, pm_linux_proc, - pm_fbsd_device, pm_aix_device, pm_nbsd_libpci, pm_dump; + pm_fbsd_device, pm_aix_device, pm_nbsd_libpci, pm_dump, pm_linux_sysfs; diff --git a/lib/pci.h b/lib/pci.h index 782d6bc..50c2961 100644 --- a/lib/pci.h +++ b/lib/pci.h @@ -32,8 +32,8 @@ typedef unsigned long u64; #define PCIADDR_PORT_FMT "%04lx" #else typedef unsigned long long u64; -#define PCIADDR_T_FMT "%016Lx" -#define PCIADDR_PORT_FMT "%04Lx" +#define PCIADDR_T_FMT "%016llx" +#define PCIADDR_PORT_FMT "%04llx" #endif typedef u64 pciaddr_t; #else @@ -61,6 +61,7 @@ struct nl_entry; enum pci_access_type { /* Known access methods, remember to update access.c as well */ PCI_ACCESS_AUTO, /* Autodetection (params: none) */ + PCI_ACCESS_SYS_BUS_PCI, /* Linux /sys/bus/pci (params: path) */ PCI_ACCESS_PROC_BUS_PCI, /* Linux /proc/bus/pci (params: path) */ PCI_ACCESS_I386_TYPE1, /* i386 ports, type 1 (params: none) */ PCI_ACCESS_I386_TYPE2, /* i386 ports, type 2 (params: none) */ @@ -114,7 +115,8 @@ void pci_free_dev(struct pci_dev *); struct pci_dev { struct pci_dev *next; /* Next device in the chain */ - word bus; /* Higher byte can select host bridges */ + word domain; /* PCI domain (host bridge) */ + byte bus; /* Bus inside domain */ byte dev, func; /* Device and function */ /* These fields are set by pci_fill_info() */ diff --git a/lib/pread.h b/lib/pread.h new file mode 100644 index 0000000..4637078 --- /dev/null +++ b/lib/pread.h @@ -0,0 +1,66 @@ +/* + * The PCI Library -- Portable interface to pread() and pwrite() + * + * Copyright (c) 1997--2003 Martin Mares + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/* + * We'd like to use pread/pwrite for configuration space accesses, but + * unfortunately it isn't simple at all since all libc's until glibc 2.1 + * don't define it. + */ + +#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ > 0 +/* glibc 2.1 or newer -> pread/pwrite supported automatically */ + +#elif defined(i386) && defined(__GLIBC__) +/* glibc 2.0 on i386 -> call syscalls directly */ +#include +#include +#ifndef SYS_pread +#define SYS_pread 180 +#endif +static int pread(unsigned int fd, void *buf, size_t size, loff_t where) +{ return syscall(SYS_pread, fd, buf, size, where); } +#ifndef SYS_pwrite +#define SYS_pwrite 181 +#endif +static int pwrite(unsigned int fd, void *buf, size_t size, loff_t where) +{ return syscall(SYS_pwrite, fd, buf, size, where); } + +#elif defined(i386) +/* old libc on i386 -> call syscalls directly the old way */ +#include +static _syscall5(int, pread, unsigned int, fd, void *, buf, size_t, size, u32, where_lo, u32, where_hi); +static _syscall5(int, pwrite, unsigned int, fd, void *, buf, size_t, size, u32, where_lo, u32, where_hi); +static int do_read(struct pci_dev *d UNUSED, int fd, void *buf, size_t size, int where) { return pread(fd, buf, size, where, 0); } +static int do_write(struct pci_dev *d UNUSED, int fd, void *buf, size_t size, int where) { return pwrite(fd, buf, size, where, 0); } +#define HAVE_DO_READ + +#else +/* In all other cases we use lseek/read/write instead to be safe */ +#define make_rw_glue(op) \ + static int do_##op(struct pci_dev *d, int fd, void *buf, size_t size, int where) \ + { \ + struct pci_access *a = d->access; \ + int r; \ + if (a->fd_pos != where && lseek(fd, where, SEEK_SET) < 0) \ + return -1; \ + r = op(fd, buf, size); \ + if (r < 0) \ + a->fd_pos = -1; \ + else \ + a->fd_pos = where + r; \ + return r; \ + } +make_rw_glue(read) +make_rw_glue(write) +#define HAVE_DO_READ +#endif + +#ifndef HAVE_DO_READ +#define do_read(d,f,b,l,p) pread(f,b,l,p) +#define do_write(d,f,b,l,p) pwrite(f,b,l,p) +#endif diff --git a/lib/proc.c b/lib/proc.c index 9f43fce..8afe7a3 100644 --- a/lib/proc.c +++ b/lib/proc.c @@ -16,65 +16,7 @@ #include #include "internal.h" - -/* - * We'd like to use pread/pwrite for configuration space accesses, but - * unfortunately it isn't simple at all since all libc's until glibc 2.1 - * don't define it. - */ - -#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ > 0 -/* glibc 2.1 or newer -> pread/pwrite supported automatically */ - -#elif defined(i386) && defined(__GLIBC__) -/* glibc 2.0 on i386 -> call syscalls directly */ -#include -#include -#ifndef SYS_pread -#define SYS_pread 180 -#endif -static int pread(unsigned int fd, void *buf, size_t size, loff_t where) -{ return syscall(SYS_pread, fd, buf, size, where); } -#ifndef SYS_pwrite -#define SYS_pwrite 181 -#endif -static int pwrite(unsigned int fd, void *buf, size_t size, loff_t where) -{ return syscall(SYS_pwrite, fd, buf, size, where); } - -#elif defined(i386) -/* old libc on i386 -> call syscalls directly the old way */ -#include -static _syscall5(int, pread, unsigned int, fd, void *, buf, size_t, size, u32, where_lo, u32, where_hi); -static _syscall5(int, pwrite, unsigned int, fd, void *, buf, size_t, size, u32, where_lo, u32, where_hi); -static int do_read(struct pci_dev *d UNUSED, int fd, void *buf, size_t size, int where) { return pread(fd, buf, size, where, 0); } -static int do_write(struct pci_dev *d UNUSED, int fd, void *buf, size_t size, int where) { return pwrite(fd, buf, size, where, 0); } -#define HAVE_DO_READ - -#else -/* In all other cases we use lseek/read/write instead to be safe */ -#define make_rw_glue(op) \ - static int do_##op(struct pci_dev *d, int fd, void *buf, size_t size, int where) \ - { \ - struct pci_access *a = d->access; \ - int r; \ - if (a->fd_pos != where && lseek(fd, where, SEEK_SET) < 0) \ - return -1; \ - r = op(fd, buf, size); \ - if (r < 0) \ - a->fd_pos = -1; \ - else \ - a->fd_pos = where + r; \ - return r; \ - } -make_rw_glue(read) -make_rw_glue(write) -#define HAVE_DO_READ -#endif - -#ifndef HAVE_DO_READ -#define do_read(d,f,b,l,p) pread(f,b,l,p) -#define do_write(d,f,b,l,p) pwrite(f,b,l,p) -#endif +#include "pread.h" static void proc_config(struct pci_access *a) @@ -179,10 +121,13 @@ proc_setup(struct pci_dev *d, int rw) if (a->cached_dev != d || a->fd_rw < rw) { char buf[1024]; + int e; if (a->fd >= 0) close(a->fd); - if (snprintf(buf, sizeof(buf), "%s/%02x/%02x.%d", a->method_params[PCI_ACCESS_PROC_BUS_PCI], - d->bus, d->dev, d->func) == sizeof(buf)) + e = snprintf(buf, sizeof(buf), "%s/%02x/%02x.%d", + a->method_params[PCI_ACCESS_PROC_BUS_PCI], + d->bus, d->dev, d->func); + if (e < 0 || e >= (int) sizeof(buf)) a->error("File name too long"); a->fd_rw = a->writeable || rw; a->fd = open(buf, a->fd_rw ? O_RDWR : O_RDONLY); @@ -246,7 +191,7 @@ proc_cleanup_dev(struct pci_dev *d) } struct pci_methods pm_linux_proc = { - "/proc/bus/pci", + "Linux-proc", proc_config, proc_detect, proc_init, diff --git a/lib/sysfs.c b/lib/sysfs.c new file mode 100644 index 0000000..70511b1 --- /dev/null +++ b/lib/sysfs.c @@ -0,0 +1,269 @@ +/* + * The PCI Library -- Configuration Access via /sys/bus/pci + * + * Copyright (c) 2003 Matthew Wilcox + * Copyright (c) 1997--2003 Martin Mares + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" +#include "pread.h" + +static void +sysfs_config(struct pci_access *a) +{ + a->method_params[PCI_ACCESS_SYS_BUS_PCI] = PATH_SYS_BUS_PCI; +} + +static inline char * +sysfs_name(struct pci_access *a) +{ + return a->method_params[PCI_ACCESS_SYS_BUS_PCI]; +} + +static int +sysfs_detect(struct pci_access *a) +{ + if (access(sysfs_name(a), R_OK)) + { + a->debug("Cannot open %s", sysfs_name(a)); + return 0; + } + a->debug("...using %s", sysfs_name(a)); + return 1; +} + +static void +sysfs_init(struct pci_access *a) +{ + a->fd = -1; +} + +static void +sysfs_cleanup(struct pci_access *a) +{ + if (a->fd >= 0) + { + close(a->fd); + a->fd = -1; + } +} + +#define OBJNAMELEN 1024 +static void +sysfs_obj_name(struct pci_dev *d, char *object, char *buf) +{ + int n = snprintf(buf, OBJNAMELEN, "%s/devices/%04x:%02x:%02x.%d/%s", + sysfs_name(d->access), d->domain, d->bus, d->dev, d->func, object); + if (n < 0 || n >= OBJNAMELEN) + d->access->error("File name too long"); +} + +static int +sysfs_get_value(struct pci_dev *d, char *object) +{ + struct pci_access *a = d->access; + int fd, n; + char namebuf[OBJNAMELEN], buf[256]; + + sysfs_obj_name(d, object, namebuf); + fd = open(namebuf, O_RDONLY); + if (fd < 0) + a->error("Cannot open %s: %s", namebuf, strerror(errno)); + n = read(fd, buf, sizeof(buf)); + close(fd); + if (n < 0) + a->error("Error reading %s: %s", namebuf, strerror(errno)); + if (n >= (int) sizeof(buf)) + a->error("Value in %s too long", namebuf); + buf[n] = 0; + return strtol(buf, NULL, 0); +} + +static void +sysfs_get_resources(struct pci_dev *d) +{ + struct pci_access *a = d->access; + char namebuf[OBJNAMELEN], buf[256]; + FILE *file; + int i; + + sysfs_obj_name(d, "resource", namebuf); + file = fopen(namebuf, "r"); + if (!file) + a->error("Cannot open %s: %s", namebuf, strerror(errno)); + for (i = 0; i < 8; i++) + { + unsigned long long start, end, size; + if (!fgets(buf, sizeof(buf), file)) + break; + if (sscanf(buf, "%llx %llx", &start, &end) != 2) + a->error("Syntax error in %s", namebuf); + if (start != (unsigned long long)(pciaddr_t) start || + end != (unsigned long long)(pciaddr_t) end) + { + a->warning("Resource %d in %s has a 64-bit address, ignoring", namebuf); + start = end = 0; + } + if (start) + size = end - start + 1; + else + size = 0; + if (i < 7) + { + d->base_addr[i] = start; + d->size[i] = size; + } + else + { + d->rom_base_addr = start; + d->rom_size = size; + } + } + fclose(file); +} + +static void sysfs_scan(struct pci_access *a) +{ + char dirname[1024]; + DIR *dir; + struct dirent *entry; + int n; + + n = snprintf(dirname, sizeof(dirname), "%s/devices", sysfs_name(a)); + if (n < 0 || n >= (int) sizeof(dirname)) + a->error("Directory name too long"); + dir = opendir(dirname); + if (!dir) + a->error("Cannot open %s", dirname); + while ((entry = readdir(dir))) + { + struct pci_dev *d; + unsigned int dom, bus, dev, func; + + /* ".", ".." or a special non-device perhaps */ + if (entry->d_name[0] == '.') + continue; + + d = pci_alloc_dev(a); + if (sscanf(entry->d_name, "%x:%x:%x.%d", &dom, &bus, &dev, &func) < 4) + a->error("sysfs_scan: Couldn't parse entry name %s", entry->d_name); + d->domain = dom; + d->bus = bus; + d->dev = dev; + d->func = func; + d->hdrtype = pci_read_byte(d, PCI_HEADER_TYPE) & 0x7f; + if (!a->buscentric) + { + sysfs_get_resources(d); + d->vendor_id = sysfs_get_value(d, "vendor"); + d->device_id = sysfs_get_value(d, "device"); + d->irq = sysfs_get_value(d, "irq"); + d->known_fields = PCI_FILL_IDENT | PCI_FILL_IRQ | PCI_FILL_BASES | PCI_FILL_ROM_BASE | PCI_FILL_SIZES; + } + pci_link_dev(a, d); + } + closedir(dir); +} + +static int +sysfs_setup(struct pci_dev *d, int rw) +{ + struct pci_access *a = d->access; + + if (a->cached_dev != d || a->fd_rw < rw) + { + char namebuf[OBJNAMELEN]; + if (a->fd >= 0) + close(a->fd); + sysfs_obj_name(d, "config", namebuf); + a->fd_rw = a->writeable || rw; + a->fd = open(namebuf, a->fd_rw ? O_RDWR : O_RDONLY); + if (a->fd < 0) + a->warning("Cannot open %s", namebuf); + a->cached_dev = d; + a->fd_pos = 0; + } + return a->fd; +} + +static int sysfs_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + int fd = sysfs_setup(d, 0); + int res; + + if (fd < 0) + return 0; + res = do_read(d, fd, buf, len, pos); + if (res < 0) + { + d->access->warning("sysfs_read: read failed: %s", strerror(errno)); + return 0; + } + else if (res != len) + { + d->access->warning("sysfs_read: tried to read %d bytes at %d, but got only %d", len, pos, res); + return 0; + } + return 1; +} + +static int sysfs_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + int fd = sysfs_setup(d, 1); + int res; + + if (fd < 0) + return 0; + res = do_write(d, fd, buf, len, pos); + if (res < 0) + { + d->access->warning("sysfs_write: write failed: %s", strerror(errno)); + return 0; + } + else if (res != len) + { + d->access->warning("sysfs_write: tried to write %d bytes at %d, but got only %d", len, pos, res); + return 0; + } + return 1; +} + +static void sysfs_cleanup_dev(struct pci_dev *d) +{ + struct pci_access *a = d->access; + + if (a->cached_dev == d) + { + a->cached_dev = NULL; + close(a->fd); + a->fd = -1; + } +} + +struct pci_methods pm_linux_sysfs = { + "Linux-sysfs", + sysfs_config, + sysfs_detect, + sysfs_init, + sysfs_cleanup, + sysfs_scan, + pci_generic_fill_info, + sysfs_read, + sysfs_write, + NULL, /* init_dev */ + sysfs_cleanup_dev +}; -- 2.39.2