diff options
Diffstat (limited to 'lib')
58 files changed, 16292 insertions, 0 deletions
diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000..9bed745 --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1,3 @@ +config.h +config.mk +libpci.pc diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..33698db --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,177 @@ +# Makefile for The PCI Library +# (c) 1999--2014 Martin Mares <mj@ucw.cz> + +# Expects to be invoked from the top-level Makefile and uses lots of its variables. + +OBJS=init access generic dump names filter names-hash names-parse names-net names-cache names-hwdb params caps +INCL=internal.h pci.h config.h header.h sysdep.h types.h + +ifdef PCI_HAVE_PM_LINUX_SYSFS +OBJS += sysfs +endif + +ifdef PCI_HAVE_PM_LINUX_PROC +OBJS += proc +endif + +ifdef PCI_HAVE_PM_INTEL_CONF +OBJS += i386-ports +endif + +ifdef PCI_HAVE_PM_MMIO_CONF +OBJS += mmio-ports +PCI_USE_PHYSMEM = 1 +endif + +ifdef PCI_HAVE_PM_ECAM +OBJS += ecam +PCI_USE_PHYSMEM = 1 +endif + +ifdef PCI_HAVE_PM_DUMP +OBJS += dump +endif + +ifdef PCI_HAVE_PM_FBSD_DEVICE +OBJS += fbsd-device +CFLAGS += -I/usr/src/sys +ifdef FREEBSD_SYS +CFLAGS += -I${FREEBSD_SYS} +endif +endif + +ifdef PCI_HAVE_PM_OBSD_DEVICE +OBJS += obsd-device +endif + +ifdef PCI_HAVE_PM_AIX_DEVICE +OBJS += aix-device +endif + +ifdef PCI_HAVE_PM_NBSD_LIBPCI +OBJS += nbsd-libpci +endif + +ifdef PCI_HAVE_PM_DARWIN_DEVICE +OBJS += darwin +endif + +ifdef PCI_HAVE_PM_SYLIXOS_DEVICE +OBJS += sylixos-device +endif + +ifdef PCI_HAVE_PM_HURD_CONF +OBJS += hurd +endif + +ifdef PCI_HAVE_PM_WIN32_CFGMGR32 +OBJS += emulated +OBJS += win32-cfgmgr32 +endif + +ifdef PCI_HAVE_PM_WIN32_KLDBG +OBJS += win32-kldbg +endif + +ifdef PCI_HAVE_PM_WIN32_SYSDBG +OBJS += win32-sysdbg +endif + +ifdef PCI_OS_WINDOWS +OBJS += win32-helpers +endif + +ifdef PCI_USE_PHYSMEM +ifndef PCI_OS_WINDOWS +ifndef PCI_OS_DJGPP +OBJS += physmem-posix +endif +endif +endif + +ifdef PCI_HAVE_PM_AOS_EXPANSION +OBJS += aos-expansion +endif + +all: $(PCILIB) $(PCILIBPC) + +ifeq ($(SHARED),no) +$(PCILIB): $(addsuffix .o,$(OBJS)) + rm -f $@ + $(AR) rcs $@ $^ + $(RANLIB) $@ +else +ifeq ($(LIBEXT),dll) +all: $(PCIIMPDEF) $(PCIIMPLIB) +build.def: $(PCIIMPDEF) +$(PCIIMPDEF): libpci.ver ver2def.pl + perl ver2def.pl libpci.ver $(PCILIB) build.def $(PCIIMPDEF) +$(PCIIMPLIB): $(PCIIMPDEF) + $(DLLTOOL) --input-def $< --output-lib $@ +comma := , +dllrsrc.rc: winrsrc.rc.in + sed <$< >$@ -e 's,@PCILIB_VERSION@,$(PCILIB_VERSION),' \ + -e 's,@PCILIB_VERSION_WINRC@,$(subst .,\$(comma),$(PCILIB_VERSION).0),' \ + -e 's,@FILENAME@,$(PCILIB),' \ + -e 's,@DESCRIPTION@,libpci,' \ + -e 's,@LIBRARY_BUILD@,1,' \ + -e 's,@DEBUG_BUILD@,$(if $(findstring -g,$(CFLAGS)),1,0),' +dllrsrc.o: dllrsrc.rc + $(WINDRES) --input=$< --output=$@ --input-format=rc --output-format=coff +OBJS += dllrsrc +endif +CFLAGS += -fPIC -fvisibility=hidden +$(PCILIB): $(addsuffix .o,$(OBJS)) + $(CC) -shared $(CFLAGS) $(LDFLAGS) $(PCILIB_LDFLAGS) -o $@ $^ $(LIB_LDLIBS) +ifeq ($(LIBEXT),dll) +$(PCILIB): build.def +endif +endif + +$(PCILIBPC): libpci.pc.in + sed <$< >$@ -e 's,@PREFIX@,$(PREFIX),' \ + -e 's,@INCDIR@,$(INCDIR),' \ + -e 's,@LIBDIR@,$(LIBDIR),' \ + -e 's,@IDSDIR@,$(IDSDIR),' \ + -e 's,@VERSION@,$(VERSION),' \ + -e 's,@LDLIBS@,$(LDLIBS),' \ + -e 's,@WITH_LIBS@,$(WITH_LIBS),' + +init.o: init.c $(INCL) +access.o: access.c $(INCL) +params.o: params.c $(INCL) +i386-ports.o: i386-ports.c $(INCL) i386-io-access.h i386-io-beos.h i386-io-cygwin.h i386-io-djgpp.h i386-io-haiku.h i386-io-hurd.h i386-io-linux.h i386-io-openbsd.h i386-io-sunos.h i386-io-windows.h +mmio-ports.o: mmio-ports.c $(INCL) physmem.h physmem-access.h +ecam.o: ecam.c $(INCL) physmem.h physmem-access.h +proc.o: proc.c $(INCL) +sysfs.o: sysfs.c $(INCL) +generic.o: generic.c $(INCL) +emulated.o: emulated.c $(INCL) +syscalls.o: syscalls.c $(INCL) +obsd-device.o: obsd-device.c $(INCL) +fbsd-device.o: fbsd-device.c $(INCL) +aix-device.o: aix-device.c $(INCL) +dump.o: dump.c $(INCL) +names.o: names.c $(INCL) names.h +names-cache.o: names-cache.c $(INCL) names.h +names-hash.o: names-hash.c $(INCL) names.h +names-net.o: names-net.c $(INCL) names.h +names-parse.o: names-parse.c $(INCL) names.h +names-hwdb.o: names-hwdb.c $(INCL) names.h +filter.o: filter.c $(INCL) +nbsd-libpci.o: nbsd-libpci.c $(INCL) +hurd.o: hurd.c $(INCL) +win32-helpers.o: win32-helpers.c $(INCL) win32-helpers.h +win32-cfgmgr32.o: win32-cfgmgr32.c $(INCL) win32-helpers.h +win32-kldbg.o: win32-kldbg.c $(INCL) win32-helpers.h +win32-sysdbg.o: win32-sysdbg.c $(INCL) win32-helpers.h +i386-io-windows.h: win32-helpers.h + +# MinGW32 toolchain has some required Win32 header files in /ddk subdirectory. +# But these header files include another header files from /ddk subdirectory +# and expect that build system has already set /ddk subdirectory into includes. +# So include /ddk subdirectory of each system predefined include path via -I. +ifdef PCI_HAVE_PM_WIN32_CFGMGR32 +DDKCFLAGS:=$(shell echo | $(CC) $(CFLAGS) -E -Wp,-v -o /dev/null - 2>&1 | sed -n 's/^ \(.*\)/-I\1\/ddk/p') +win32-cfgmgr32.o: override CFLAGS+=$(DDKCFLAGS) +endif diff --git a/lib/access.c b/lib/access.c new file mode 100644 index 0000000..7d66123 --- /dev/null +++ b/lib/access.c @@ -0,0 +1,270 @@ +/* + * The PCI Library -- User Access + * + * Copyright (c) 1997--2022 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +#include "internal.h" + +void +pci_scan_bus(struct pci_access *a) +{ + a->methods->scan(a); +} + +struct pci_dev * +pci_alloc_dev(struct pci_access *a) +{ + struct pci_dev *d = pci_malloc(a, sizeof(struct pci_dev)); + + memset(d, 0, sizeof(*d)); + d->access = a; + d->methods = a->methods; + d->hdrtype = -1; + d->numa_node = -1; + if (d->methods->init_dev) + d->methods->init_dev(d); + return d; +} + +int +pci_link_dev(struct pci_access *a, struct pci_dev *d) +{ + d->next = a->devices; + a->devices = d; + + /* + * Applications compiled with older versions of libpci do not expect + * 32-bit domain numbers. To keep them working, we keep a 16-bit + * version of the domain number at the previous location in struct + * pci_dev. This will keep backward compatibility on systems which + * don't require large domain numbers. + */ + if (d->domain > 0xffff) + d->domain_16 = 0xffff; + else + d->domain_16 = d->domain; + + return 1; +} + +struct pci_dev * +pci_get_dev(struct pci_access *a, int domain, int bus, int dev, int func) +{ + struct pci_dev *d = pci_alloc_dev(a); + + d->domain = domain; + d->bus = bus; + d->dev = dev; + d->func = func; + return d; +} + +static void +pci_free_properties(struct pci_dev *d) +{ + struct pci_property *p; + + while (p = d->properties) + { + d->properties = p->next; + pci_mfree(p); + } +} + +void pci_free_dev(struct pci_dev *d) +{ + if (d->methods->cleanup_dev) + d->methods->cleanup_dev(d); + + pci_free_caps(d); + pci_free_properties(d); + pci_mfree(d); +} + +static inline void +pci_read_data(struct pci_dev *d, void *buf, int pos, int len) +{ + if (pos & (len-1)) + d->access->error("Unaligned read: pos=%02x, len=%d", pos, len); + if (pos + len <= d->cache_len) + memcpy(buf, d->cache + pos, len); + else if (!d->methods->read(d, pos, buf, len)) + memset(buf, 0xff, len); +} + +byte +pci_read_byte(struct pci_dev *d, int pos) +{ + byte buf; + pci_read_data(d, &buf, pos, 1); + return buf; +} + +word +pci_read_word(struct pci_dev *d, int pos) +{ + word buf; + pci_read_data(d, &buf, pos, 2); + return le16_to_cpu(buf); +} + +u32 +pci_read_long(struct pci_dev *d, int pos) +{ + u32 buf; + pci_read_data(d, &buf, pos, 4); + return le32_to_cpu(buf); +} + +int +pci_read_block(struct pci_dev *d, int pos, byte *buf, int len) +{ + return d->methods->read(d, pos, buf, len); +} + +int +pci_read_vpd(struct pci_dev *d, int pos, byte *buf, int len) +{ + return d->methods->read_vpd ? d->methods->read_vpd(d, pos, buf, len) : 0; +} + +static inline int +pci_write_data(struct pci_dev *d, void *buf, int pos, int len) +{ + if (pos & (len-1)) + d->access->error("Unaligned write: pos=%02x,len=%d", pos, len); + if (pos + len <= d->cache_len) + memcpy(d->cache + pos, buf, len); + return d->methods->write(d, pos, buf, len); +} + +int +pci_write_byte(struct pci_dev *d, int pos, byte data) +{ + return pci_write_data(d, &data, pos, 1); +} + +int +pci_write_word(struct pci_dev *d, int pos, word data) +{ + word buf = cpu_to_le16(data); + return pci_write_data(d, &buf, pos, 2); +} + +int +pci_write_long(struct pci_dev *d, int pos, u32 data) +{ + u32 buf = cpu_to_le32(data); + return pci_write_data(d, &buf, pos, 4); +} + +int +pci_write_block(struct pci_dev *d, int pos, byte *buf, int len) +{ + if (pos < d->cache_len) + { + int l = (pos + len >= d->cache_len) ? (d->cache_len - pos) : len; + memcpy(d->cache + pos, buf, l); + } + return d->methods->write(d, pos, buf, len); +} + +static void +pci_reset_properties(struct pci_dev *d) +{ + d->known_fields = 0; + d->phy_slot = NULL; + d->module_alias = NULL; + d->label = NULL; + pci_free_caps(d); + pci_free_properties(d); +} + +int +pci_fill_info_v38(struct pci_dev *d, int flags) +{ + unsigned int uflags = flags; + if (uflags & PCI_FILL_RESCAN) + { + uflags &= ~PCI_FILL_RESCAN; + pci_reset_properties(d); + } + if (uflags & ~d->known_fields) + d->methods->fill_info(d, uflags); + return d->known_fields; +} + +/* In version 3.1, pci_fill_info got new flags => versioned alias */ +/* In versions 3.2, 3.3, 3.4, 3.5 and 3.8, the same has happened */ +STATIC_ALIAS(int pci_fill_info(struct pci_dev *d, int flags), pci_fill_info_v38(d, flags)); +DEFINE_ALIAS(int pci_fill_info_v30(struct pci_dev *d, int flags), pci_fill_info_v38); +DEFINE_ALIAS(int pci_fill_info_v31(struct pci_dev *d, int flags), pci_fill_info_v38); +DEFINE_ALIAS(int pci_fill_info_v32(struct pci_dev *d, int flags), pci_fill_info_v38); +DEFINE_ALIAS(int pci_fill_info_v33(struct pci_dev *d, int flags), pci_fill_info_v38); +DEFINE_ALIAS(int pci_fill_info_v34(struct pci_dev *d, int flags), pci_fill_info_v38); +DEFINE_ALIAS(int pci_fill_info_v35(struct pci_dev *d, int flags), pci_fill_info_v38); +SYMBOL_VERSION(pci_fill_info_v30, pci_fill_info@LIBPCI_3.0); +SYMBOL_VERSION(pci_fill_info_v31, pci_fill_info@LIBPCI_3.1); +SYMBOL_VERSION(pci_fill_info_v32, pci_fill_info@LIBPCI_3.2); +SYMBOL_VERSION(pci_fill_info_v33, pci_fill_info@LIBPCI_3.3); +SYMBOL_VERSION(pci_fill_info_v34, pci_fill_info@LIBPCI_3.4); +SYMBOL_VERSION(pci_fill_info_v35, pci_fill_info@LIBPCI_3.5); +SYMBOL_VERSION(pci_fill_info_v38, pci_fill_info@@LIBPCI_3.8); + +void +pci_setup_cache(struct pci_dev *d, byte *cache, int len) +{ + d->cache = cache; + d->cache_len = len; +} + +char * +pci_set_property(struct pci_dev *d, u32 key, char *value) +{ + struct pci_property *p; + struct pci_property **pp = &d->properties; + + while (p = *pp) + { + if (p->key == key) + { + *pp = p->next; + pci_mfree(p); + } + else + pp = &p->next; + } + + if (!value) + return NULL; + + p = pci_malloc(d->access, sizeof(*p) + strlen(value)); + *pp = p; + p->next = NULL; + p->key = key; + strcpy(p->value, value); + + return p->value; +} + +char * +pci_get_string_property(struct pci_dev *d, u32 prop) +{ + struct pci_property *p; + + for (p = d->properties; p; p = p->next) + if (p->key == prop) + return p->value; + + return NULL; +} diff --git a/lib/aix-device.c b/lib/aix-device.c new file mode 100644 index 0000000..028d92a --- /dev/null +++ b/lib/aix-device.c @@ -0,0 +1,276 @@ +/* + * The PCI Library -- AIX /dev/pci[0-n] access + * + * Copyright (c) 1999 Jari Kirma <kirma@cs.hut.fi> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Read functionality of this driver is briefly tested, and seems + * to supply basic information correctly, but I promise no more. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/mdio.h> + +#include "internal.h" + +#define AIX_LSDEV_CMD "/usr/sbin/lsdev -C -c bus -t pci\\* -S available -F name" +#define AIX_ODMGET_CMD \ + "/usr/bin/odmget -q 'name=%s and attribute=bus_number' CuAt | \ + /usr/bin/awk '$1 == \"value\" { print $3 }'" + + +/* AIX PCI bus device information */ + +typedef struct aix_pci_bus { + char *bus_name; + int bus_number; + int bus_fd; +} aix_pci_bus; + +#define PCI_BUS_MAX 16 /* arbitrary choice */ +static aix_pci_bus pci_buses[PCI_BUS_MAX]; +static int pci_bus_count = 0; + + +/* Utility Routines */ + +static aix_pci_bus * +aix_find_bus(struct pci_access *a, int bus_number) +{ + int i; + + for (i = 0; i < pci_bus_count; i++) + { + if (pci_buses[i].bus_number == bus_number) + { + return &pci_buses[i]; + } + } + + a->error("aix_find_bus: bus number %d not found", bus_number); +} + +static int +aix_bus_open(struct pci_dev *d) +{ + struct pci_access *a = d->access; + aix_pci_bus *bp = aix_find_bus(a, d->bus); + + if (bp->bus_fd < 0) + { + char devbuf[256]; + int mode = a->writeable ? O_RDWR : O_RDONLY; + + snprintf(devbuf, sizeof (devbuf), "/dev/%s", bp->bus_name); + bp->bus_fd = open(devbuf, mode, 0); + if (bp->bus_fd < 0) + a->error("aix_open_bus: %s open failed", devbuf); + } + + return bp->bus_fd; +} + +static int +aix_bus_number(char *name) +{ + int bus_number; + FILE *odmget_pipe; + char command[256]; + char buf[256]; + char *bp; + char *ep; + + snprintf(command, sizeof (command), AIX_ODMGET_CMD, name); + odmget_pipe = popen(command, "r"); + if (odmget_pipe == NULL) + { + /* popen failed */ + return -1; + } + + if (fgets(buf, sizeof (buf) - 1, odmget_pipe) != NULL) + { + bp = buf + 1; /* skip leading double quote */ + bus_number = strtol(bp, &ep, 0); + if (bp == ep) + { + /* strtol failed */ + bus_number = -1; + } + } + else + { + /* first PCI bus_number is not recorded in ODM CuAt; default to 0 */ + bus_number = 0; + } + + (void) pclose(odmget_pipe); + + return bus_number; +} + + +/* Method entries */ + +static int +aix_detect(struct pci_access *a) +{ + int len; + int mode = a->writeable ? W_OK : R_OK; + char *command = AIX_LSDEV_CMD; + FILE *lsdev_pipe; + char buf[256]; + char *name; + + lsdev_pipe = popen(command, "r"); + if (lsdev_pipe == NULL) + { + a->error("aix_config: popen(\"%s\") failed", command); + } + + while (fgets(buf, sizeof (buf) - 1, lsdev_pipe) != NULL) + { + len = strlen(buf); + while (buf[len-1] == '\n' || buf[len-1] == '\r') + len--; + buf[len] = '\0'; /* clobber the newline */ + + name = (char *) pci_malloc(a, len + 1); + strcpy(name, buf); + pci_buses[pci_bus_count].bus_name = name; + pci_buses[pci_bus_count].bus_number = 0; + pci_buses[pci_bus_count].bus_fd = -1; + if (!pci_bus_count) + a->debug("...using %s", name); + else + a->debug(", %s", name); + pci_bus_count++; + if (pci_bus_count >= PCI_BUS_MAX) + break; + } + + (void) pclose(lsdev_pipe); + + return pci_bus_count; +} + +static void +aix_init(struct pci_access *a) +{ + char *name; + int i; + + for (i = 0; i < pci_bus_count; i++) + { + name = pci_buses[i].bus_name; + pci_buses[i].bus_number = aix_bus_number(name); + } +} + +static void +aix_cleanup(struct pci_access *a) +{ + aix_pci_bus *bp; + + while (pci_bus_count-- > 0) + { + bp = &pci_buses[pci_bus_count]; + (void) free(bp->bus_name); + if (bp->bus_fd >= 0) + { + (void) close(bp->bus_fd); + bp->bus_fd = -1; + } + } +} + +void +aix_scan(struct pci_access *a) +{ + int i; + int bus_number; + byte busmap[256]; + + memset(busmap, 0, sizeof(busmap)); + for (i = 0; i < pci_bus_count; i++) + { + bus_number = pci_buses[i].bus_number; + if (!busmap[bus_number]) + { + pci_generic_scan_bus(a, busmap, 0, bus_number); + } + } +} + +static int +aix_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + struct mdio mdio; + int fd; + + if (d->domain || pos + len > 256) + return 0; + + fd = aix_bus_open(d); + mdio.md_addr = (ulong) pos; + mdio.md_size = len; + mdio.md_incr = MV_BYTE; + mdio.md_data = (char *) buf; + mdio.md_sla = PCI_DEVFN(d->dev, d->func); + + if (ioctl(fd, MIOPCFGET, &mdio) < 0) + d->access->error("aix_read: ioctl(MIOPCFGET) failed"); + + return 1; +} + +static int +aix_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + struct mdio mdio; + int fd; + + if (d->domain || pos + len > 256) + return 0; + + fd = aix_bus_open(d); + mdio.md_addr = (ulong) pos; + mdio.md_size = len; + mdio.md_incr = MV_BYTE; + mdio.md_data = (char *) buf; + mdio.md_sla = PCI_DEVFN(d->dev, d->func); + + if (ioctl(fd, MIOPCFPUT, &mdio) < 0) + { + d->access->error("aix_write: ioctl(MIOPCFPUT) failed"); + } + + return 1; +} + +struct pci_methods pm_aix_device = { + "aix-device", + "AIX /dev/pci[0-n]", + NULL, + aix_detect, + aix_init, + aix_cleanup, + aix_scan, + pci_generic_fill_info, + aix_read, + aix_write, + NULL, /* read_vpd */ + NULL, /* dev_init */ + NULL /* dev_cleanup */ +}; diff --git a/lib/aos-expansion.c b/lib/aos-expansion.c new file mode 100644 index 0000000..3e11f47 --- /dev/null +++ b/lib/aos-expansion.c @@ -0,0 +1,237 @@ +/* + * The PCI Library -- Configuration Access via AmigaOS 4.x expansion.library + * + * Copyright (c) 2024 Olrick Lefebvre <olrick.lefebvre@olrick.fr> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define _GNU_SOURCE + +#include <proto/exec.h> +#include <exec/types.h> +#include <proto/expansion.h> +#include <interfaces/expansion.h> + + +// have to undef PCI values to avoid redefine warning +#undef PCI_BASE_ADDRESS_MEM_MASK +#undef PCI_BASE_ADDRESS_IO_MASK +#undef PCI_ROM_ADDRESS_MASK +#include <expansion/pci.h> + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <sys/unistd.h> + +#include "internal.h" + + +// custom Amiga x.y version tag +#define VERSTAG "\0$VER: pciutils " PCILIB_VERSION " (" PCILIB_DATE_AMIGAOS ") AmigaOS4 port" + + +/*** AmigaOS access support ***/ + +typedef struct _PCIAccess { + struct ExpansionBase *expansion; + struct PCIIFace *ipci; +} PCIAccess; + +static void +aos_close_pci_interface(struct pci_access *a) +{ + PCIAccess *pci = a->backend_data; + + if (pci) { + if (pci->expansion) { + if (pci->ipci) { + IExec->DropInterface((struct Interface *)pci->ipci); + pci->ipci = NULL; + } + IExec->CloseLibrary((struct Library *)pci->expansion); + pci->expansion = NULL; + } + pci_mfree(pci); + a->backend_data = NULL; + } +} + +static BOOL +aos_open_pci_interface(struct pci_access *a) +{ + PCIAccess *pci; + BOOL res = FALSE; + + if (NULL == a->backend_data) { + pci = pci_malloc(a, sizeof(PCIAccess)); + a->backend_data = pci; + pci->expansion = (struct ExpansionBase *)IExec->OpenLibrary("expansion.library", 0); + if(NULL == pci->expansion) { + a->warning("Unable to open expansion.library"); + aos_close_pci_interface(a); + } else { + pci->ipci = (struct PCIIFace *)IExec->GetInterface((struct Library *)pci->expansion, "pci", 1, TAG_DONE); + if(NULL == pci->ipci) { + a->warning("Unable to obtain pci interface"); + aos_close_pci_interface(a); + } else { + res = TRUE; + } + } + } else { + res = TRUE; // already opened + } + + return res; +} + +static int +aos_expansion_detect(struct pci_access *a) +{ + int res = FALSE; + struct PCIDevice *device = NULL; + PCIAccess *pci; + + if(TRUE == aos_open_pci_interface(a)) { + pci = a->backend_data; + + // Try to read PCI first device + device = pci->ipci->FindDeviceTags(FDT_Index, 0); + if(NULL == device) { + a->warning("AmigaOS Expansion PCI interface cannot find any device"); + aos_close_pci_interface(a); + } else { + pci->ipci->FreeDevice(device); + res = TRUE; + } + } + + return res; +} + +static void +aos_expansion_init(struct pci_access *a) +{ + // to avoid flushing of version tag + static STRPTR USED ver = (STRPTR)VERSTAG; + + if (!aos_open_pci_interface(a)) { + a->debug("\n"); + a->error("AmigaOS Expansion PCI interface cannot be accessed."); + } +} + +static void +aos_expansion_cleanup(struct pci_access *a) +{ + aos_close_pci_interface(a); +} + +static void +aos_expansion_scan(struct pci_access *a) +{ + struct PCIDevice *device = NULL; + PCIAccess *pci = NULL; + UBYTE bus_num; + UBYTE dev_num; + UBYTE fn_num; + struct pci_dev *d; + int found_devs = 0; + + pci = a->backend_data; + + // X1000 has a bug which left shifts secondary bus by one bit, so we don't scan but get all devices identified by the system + device = pci->ipci->FindDeviceTags(FDT_Index, found_devs); + while (device) { + d = pci_alloc_dev(a); + d->domain = 0; // only one domain for AmigaOS + device->GetAddress(&bus_num, &dev_num, &fn_num); + d->bus = bus_num; + d->dev = dev_num; + d->func = fn_num; + d->backend_data = device; + d->vendor_id = device->ReadConfigWord(PCI_VENDOR_ID); + d->device_id = device->ReadConfigWord(PCI_DEVICE_ID); + d->known_fields = PCI_FILL_IDENT; + d->hdrtype = device->ReadConfigByte(PCI_HEADER_TYPE) & ~PCI_HEADER_TYPE_MULTIFUNCTION; + pci_link_dev(a, d); + a->debug(" Found device %02x:%02x.%d %04x:%04x\n", d->bus, d->dev, d->func, d->vendor_id, d->device_id); + + found_devs++; + device = pci->ipci->FindDeviceTags(FDT_Index, found_devs); + } +} + +static int +aos_expansion_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + int res = FALSE; + byte *ptr = buf; + if (d->backend_data) { + for (int i = 0; i < len; i++) { + // byte by byte to avoid endianness troubles + *ptr = ((struct PCIDevice *)(d->backend_data))->ReadConfigByte(pos + i); + ptr++; + res = TRUE; + } + } + + return res; +} + +static int +aos_expansion_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + int res = FALSE; + byte *ptr = buf; + + if (d->backend_data) { + for (int i = 0; i < len; i++) { + // byte by byte to avoid endianness troubles + ((struct PCIDevice *)(d->backend_data))->WriteConfigByte(pos + i, *ptr); + ptr++; + res = TRUE; + } + } + + return res; +} + +static void +aos_expansion_init_dev(struct pci_dev *d) +{ + d->backend_data = NULL; // struct PCIDevice * to be obtained +} + +static void +aos_expansion_cleanup_dev(struct pci_dev *d) +{ + PCIAccess *pci; + + if (d->backend_data && d->access->backend_data) { + pci = d->access->backend_data; + pci->ipci->FreeDevice((struct PCIDevice *)d->backend_data); + d->backend_data = NULL; + } +} + +struct pci_methods pm_aos_expansion = { + "aos-expansion", + "The Expansion.library on AmigaOS 4.x", + NULL, // config, called after allocation of pci_access, if assigned + aos_expansion_detect, // detect, mandatory because called without check + aos_expansion_init, // init, called once access chosen, eventually after detect + aos_expansion_cleanup, // cleanup, called at the end + aos_expansion_scan, + pci_generic_fill_info, + aos_expansion_read, + aos_expansion_write, + NULL, // read_vpd + aos_expansion_init_dev, + aos_expansion_cleanup_dev, +}; diff --git a/lib/caps.c b/lib/caps.c new file mode 100644 index 0000000..cf1df5d --- /dev/null +++ b/lib/caps.c @@ -0,0 +1,148 @@ +/* + * The PCI Library -- Capabilities + * + * Copyright (c) 2008 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <string.h> + +#include "internal.h" + +static void +pci_add_cap(struct pci_dev *d, unsigned int addr, unsigned int id, unsigned int type) +{ + struct pci_cap *cap = pci_malloc(d->access, sizeof(*cap)); + + if (d->last_cap) + d->last_cap->next = cap; + else + d->first_cap = cap; + d->last_cap = cap; + cap->next = NULL; + cap->addr = addr; + cap->id = id; + cap->type = type; + d->access->debug("%04x:%02x:%02x.%d: Found capability %04x of type %d at %04x\n", + d->domain, d->bus, d->dev, d->func, id, type, addr); +} + +static void +pci_scan_trad_caps(struct pci_dev *d) +{ + word status = pci_read_word(d, PCI_STATUS); + byte been_there[256]; + int where; + + if (!(status & PCI_STATUS_CAP_LIST)) + return; + + memset(been_there, 0, 256); + where = pci_read_byte(d, PCI_CAPABILITY_LIST) & ~3; + while (where) + { + byte id = pci_read_byte(d, where + PCI_CAP_LIST_ID); + byte next = pci_read_byte(d, where + PCI_CAP_LIST_NEXT) & ~3; + if (been_there[where]++) + break; + if (id == 0xff) + break; + pci_add_cap(d, where, id, PCI_CAP_NORMAL); + where = next; + } +} + +static void +pci_scan_ext_caps(struct pci_dev *d) +{ + byte been_there[0x1000]; + int where = 0x100; + + if (!pci_find_cap(d, PCI_CAP_ID_EXP, PCI_CAP_NORMAL)) + return; + + memset(been_there, 0, 0x1000); + do + { + u32 header; + int id; + + header = pci_read_long(d, where); + if (!header || header == 0xffffffff) + break; + id = header & 0xffff; + if (been_there[where]++) + break; + pci_add_cap(d, where, id, PCI_CAP_EXTENDED); + where = (header >> 20) & ~3; + } + while (where); +} + +void +pci_scan_caps(struct pci_dev *d, unsigned int want_fields) +{ + if (want_fields & PCI_FILL_EXT_CAPS) + want_fields |= PCI_FILL_CAPS; + + if (want_fill(d, want_fields, PCI_FILL_CAPS)) + pci_scan_trad_caps(d); + if (want_fill(d, want_fields, PCI_FILL_EXT_CAPS)) + pci_scan_ext_caps(d); +} + +void +pci_free_caps(struct pci_dev *d) +{ + struct pci_cap *cap; + + while (cap = d->first_cap) + { + d->first_cap = cap->next; + pci_mfree(cap); + } +} + +struct pci_cap * +pci_find_cap(struct pci_dev *d, unsigned int id, unsigned int type) +{ + return pci_find_cap_nr(d, id, type, NULL); +} + +/** + * Finds a particular capability of a device + * + * To select one capability if there are more than one with the same id, you + * can provide a pointer to an unsigned int that contains the index which you + * want as cap_number. If you don't care and are fine with the first one you + * can supply NULL. The cap_number will be replaced by the actual number + * of capabilities with that id. + */ +struct pci_cap * +pci_find_cap_nr(struct pci_dev *d, unsigned int id, unsigned int type, + unsigned int *cap_number) +{ + struct pci_cap *c; + struct pci_cap *found = NULL; + unsigned int target = (cap_number ? *cap_number : 0); + unsigned int index = 0; + + pci_fill_info_v38(d, ((type == PCI_CAP_NORMAL) ? PCI_FILL_CAPS : PCI_FILL_EXT_CAPS)); + + for (c=d->first_cap; c; c=c->next) + { + if (c->type == type && c->id == id) + { + if (target == index) + found = c; + index++; + } + } + + if (cap_number) + *cap_number = index; + return found; +} diff --git a/lib/configure b/lib/configure new file mode 100755 index 0000000..3df057a --- /dev/null +++ b/lib/configure @@ -0,0 +1,365 @@ +#!/bin/sh +# Configuration script for the PCI library +# (c) 1998--2013 Martin Mares <mj@ucw.cz> + +LC_ALL=C +export LC_ALL + +echo_n() { + printf '%s' "$*" +} + +if [ -z "$VERSION" ] ; then + echo >&2 "Please run the configure script from the top-level Makefile" + exit 1 +fi + +echo_n "Configuring libpci for your system..." +if [ -z "$HOST" ] ; then + sys=`uname -s` + rel=`uname -r` + realsys="$sys" + if [ "$sys" = "AIX" -a -x /usr/bin/oslevel -a -x /usr/sbin/lsattr ] + then + rel=`/usr/bin/oslevel` + proc=`/usr/sbin/lsdev -C -c processor -S available -F name | head -1` + cpu=`/usr/sbin/lsattr -F value -l $proc -a type | sed 's/_.*//'` + else + cpu=`uname -m | sed 's/^i.86-AT386/i386/;s/^i.86$/i386/;s/^sun4u$/sparc64/;s/^i86pc$/i386/;s/^BePC$/i386/;s/^BeMac$/powerpc/;s/^BeBox$/powerpc/'` + fi + if [ "$sys" = "DragonFly" ] + then + sys=freebsd + fi + if [ "$sys" = "GNU/kFreeBSD" ] + then + sys=kfreebsd + fi + if [ "$sys" = "GNU" ] + then + sys=gnu + fi + if [ "$sys" = "CYGWIN_NT-5.1" -o "$sys" = "CYGWIN_NT-6.0" ] + then + sys=cygwin + fi + HOST=${3:-$cpu-$sys} +fi +[ -n "$RELEASE" ] && rel="${RELEASE}" +# CAVEAT: tr on Solaris is a bit weird and the extra [] is otherwise harmless. +host=`echo $HOST | sed -e 's/^\([^-]*\)-\([^-]*\)-\([^-]*\)-\([^-]*\)$/\1-\3/' -e 's/^\([^-]*\)-\([^-]*\)-\([^-]*\)$/\1-\2/' -e 's/^\([^-]*\)-\([^-]*\)$/\1--\2/' | tr '[A-Z]' '[a-z]'` +cpu=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` +sys=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` +echo " $host $rel $cpu $sys" + +c=config.h +m=config.mk +echo >$c '#define PCI_CONFIG_H' +echo >>$c "#define PCI_ARCH_`echo $cpu | tr '[a-z]' '[A-Z]'`" +echo >>$c "#define PCI_OS_`echo $sys | tr '[a-z]' '[A-Z]'`" +echo >$m 'WITH_LIBS=' + +echo_n "Looking for access methods..." +LIBRESOLV=-lresolv +LIBEXT=so +EXEEXT= +SYSINCLUDE=/usr/include +LSPCIDIR=SBINDIR + +case $sys in + linux*) + echo_n " sysfs proc mem-ports ecam" + echo >>$c '#define PCI_HAVE_PM_LINUX_SYSFS' + echo >>$c '#define PCI_HAVE_PM_LINUX_PROC' + echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' + echo >>$c '#define PCI_HAVE_PM_ECAM' + echo >>$c '#define PCI_HAVE_LINUX_BYTEORDER_H' + echo >>$c '#define PCI_PATH_PROC_BUS_PCI "/proc/bus/pci"' + echo >>$c '#define PCI_PATH_SYS_BUS_PCI "/sys/bus/pci"' + echo >>$c '#define PCI_PATH_DEVMEM_DEVICE "/dev/mem"' + echo >>$c '#define PCI_PATH_ACPI_MCFG "/sys/firmware/acpi/tables/MCFG"' + echo >>$c '#define PCI_PATH_EFI_SYSTAB "/sys/firmware/efi/systab"' + case $cpu in + i?86|x86_64) echo_n " i386-ports" + echo >>$c '#define PCI_HAVE_PM_INTEL_CONF' + ;; + esac + echo >>$c '#define PCI_HAVE_64BIT_ADDRESS' + LSPCIDIR=BINDIR + ;; + sunos) + echo_n " mem-ports ecam" + case $cpu in + i?86) echo_n " i386-ports" + echo >>$c "#define PCI_HAVE_PM_INTEL_CONF" + ;; + esac + echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' + echo >>$c '#define PCI_HAVE_PM_ECAM' + echo >>$c '#define PCI_PATH_DEVMEM_DEVICE "/dev/xsvc"' + echo >>$c '#define PCI_PATH_ACPI_MCFG ""' + echo >>$c '#define PCI_PATH_EFI_SYSTAB ""' + ;; + freebsd*|kfreebsd*) + echo_n " fbsd-device mem-ports ecam" + echo >>$c '#define PCI_HAVE_PM_FBSD_DEVICE' + echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' + echo >>$c '#define PCI_HAVE_PM_ECAM' + echo >>$c '#define PCI_PATH_FBSD_DEVICE "/dev/pci"' + echo >>$c '#define PCI_PATH_DEVMEM_DEVICE "/dev/mem"' + echo >>$c '#define PCI_PATH_ACPI_MCFG ""' + echo >>$c '#define PCI_PATH_EFI_SYSTAB ""' + if [ "$sys" != "kfreebsd" ] ; then + LIBRESOLV= + fi + ;; + openbsd) + echo_n " obsd-device mem-ports ecam" + echo >>$c '#define PCI_HAVE_PM_OBSD_DEVICE' + echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' + echo >>$c '#define PCI_HAVE_PM_ECAM' + echo >>$c '#define PCI_PATH_OBSD_DEVICE "/dev/pci"' + echo >>$c '#define PCI_PATH_DEVMEM_DEVICE "/dev/mem"' + echo >>$c '#define PCI_PATH_ACPI_MCFG "/var/db/acpi/MCFG.*"' + echo >>$c '#define PCI_PATH_EFI_SYSTAB ""' + case $cpu in + i386|amd64) echo_n " i386-ports" + echo >>$c '#define PCI_HAVE_PM_INTEL_CONF' + echo >>$m 'WITH_LIBS+=-l'$cpu + ;; + esac + LIBRESOLV= + ;; + + darwin*) + echo_n " darwin" + echo >>$c '#define PCI_HAVE_PM_DARWIN_DEVICE' + echo >>$m 'WITH_LIBS+=-lresolv -framework CoreFoundation -framework IOKit' + echo >>$c '#define PCI_HAVE_64BIT_ADDRESS' + LIBRESOLV= + LIBEXT=dylib + SYSINCLUDE=$(xcrun --sdk macosx --show-sdk-path)/usr/include + ;; + aix) + echo_n " aix-device" + echo >>$c '#define PCI_HAVE_PM_AIX_DEVICE' + echo >>$m 'CFLAGS=-g' + echo >>$m 'INSTALL=installbsd' + echo >>$m 'DIRINSTALL=mkdir -p' + ;; + netbsd) + echo_n " nbsd-libpci mem-ports ecam" + echo >>$c '#define PCI_HAVE_PM_NBSD_LIBPCI' + echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' + echo >>$c '#define PCI_HAVE_PM_ECAM' + echo >>$c '#define PCI_PATH_NBSD_DEVICE "/dev/pci0"' + echo >>$c '#define PCI_PATH_DEVMEM_DEVICE "/dev/mem"' + echo >>$c '#define PCI_PATH_ACPI_MCFG ""' + echo >>$c '#define PCI_PATH_EFI_SYSTAB ""' + echo >>$c '#define PCI_HAVE_64BIT_ADDRESS' + echo >>$m 'LIBNAME=libpciutils' + echo >>$m 'WITH_LIBS+=-lpci' + LIBRESOLV= + ;; + gnu) + echo_n " hurd i386-ports" + echo >>$c '#define PCI_HAVE_PM_HURD_CONF' + echo >>$c '#define PCI_HAVE_PM_INTEL_CONF' + ;; + djgpp) + echo_n " i386-ports" + echo >>$c '#define PCI_HAVE_PM_INTEL_CONF' + EXEEXT=.exe + ;; + cygwin|windows) + echo_n " win32-cfgmgr32 win32-kldbg win32-sysdbg" + echo >>$c '#define PCI_HAVE_64BIT_ADDRESS' + echo >>$c '#define PCI_HAVE_PM_WIN32_CFGMGR32' + echo >>$c '#define PCI_HAVE_PM_WIN32_KLDBG' + echo >>$c '#define PCI_HAVE_PM_WIN32_SYSDBG' + # Warning: MinGW-w64 (incorrectly) provides cfgmgr32 functions + # also in other import libraries, not only in libcfgmgr32.a. + # So always set -lcfgmgr32 as a first library parameter which + # instruct linker to prefer symbols from cfgmgr32.dll. + echo >>$m 'WITH_LIBS+=-lcfgmgr32' + case $cpu in i?86|x86_64) + echo_n " i386-ports" + echo >>$c '#define PCI_HAVE_PM_INTEL_CONF' + if [ "$sys" = "cygwin" ] ; then + # ioperm is cygwin specific library and used only by lib/i386-io-cygwin.h + echo >>$m 'WITH_LIBS+=-lioperm' + elif [ "$sys" = "windows" ] ; then + # advapi32 is windows system library and used only by lib/i386-io-windows.h + echo >>$m 'WITH_LIBS+=-ladvapi32' + fi + ;; esac + EXEEXT=.exe + LIBEXT=dll + ;; + beos|haiku) + echo_n " mem-ports ecam" + case $cpu in + i?86|x86_64) echo_n " i386-ports" + echo >>$c '#define PCI_HAVE_PM_INTEL_CONF' + ;; + esac + echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' + echo >>$c '#define PCI_HAVE_PM_ECAM' + echo >>$c '#define PCI_PATH_DEVMEM_DEVICE "/dev/misc/mem"' + echo >>$c '#define PCI_PATH_ACPI_MCFG ""' + echo >>$c '#define PCI_PATH_EFI_SYSTAB ""' + ;; + sylixos) + echo >>$c '#define PCI_PATH_SYLIXOS_DEVICE "/proc/pci"' + echo >>$c '#define PCI_HAVE_64BIT_ADDRESS' + echo >>$c '#define PCI_HAVE_PM_SYLIXOS_DEVICE' + IDSDIR="/etc/pci" + LIBRESOLV= + ;; + amigaos) + echo_n " aos-expansion" + echo >>$c '#define PCI_HAVE_STDINT_H' + echo >>$c '#define PCI_HAVE_PM_AOS_EXPANSION' + IDSDIR="DEVS:" + echo >>$m 'CC=gcc' + ;; + *) + echo " Unfortunately, your OS is not supported by the PCI Library" + exit 1 + ;; +esac + +echo >>$m "LIBEXT="$LIBEXT +echo >>$m "EXEEXT="$EXEEXT +echo >>$m "LSPCIDIR=\$($LSPCIDIR)" +echo >>$c '#define PCI_HAVE_PM_DUMP' +echo " dump" + +echo_n "Checking for zlib support... " +if [ "$ZLIB" = yes -o "$ZLIB" = no ] ; then + echo "$ZLIB (set manually)" +else + if [ -f "$SYSINCLUDE/zlib.h" -o -f /usr/local/include/zlib.h ] ; then + ZLIB=yes + else + ZLIB=no + fi + echo "$ZLIB (auto-detected)" +fi +if [ "$ZLIB" = yes ] ; then + echo >>$c '#define PCI_COMPRESSED_IDS' + echo >>$c '#define PCI_IDS "pci.ids.gz"' + echo >>$m 'LIBZ=-lz' + echo >>$m 'WITH_LIBS+=$(LIBZ)' +else + echo >>$c '#define PCI_IDS "pci.ids"' +fi +echo >>$c "#define PCI_PATH_IDS_DIR \"$IDSDIR\"" + +echo_n "Checking for DNS support... " +if [ "$DNS" = yes -o "$DNS" = no ] ; then + echo "$DNS (set manually)" +else + if [ "$sys" != "windows" -a -f "$SYSINCLUDE/resolv.h" ] ; then + DNS=yes + else + DNS=no + fi + echo "$DNS (auto-detected)" +fi +if [ "$DNS" = yes ] ; then + echo >>$c "#define PCI_USE_DNS" + echo >>$c "#define PCI_ID_DOMAIN \"pci.id.ucw.cz\"" + echo >>$m "WITH_LIBS+=$LIBRESOLV" +fi + +if [ "$sys" = linux ] ; then + echo_n "Checking for libkmod... " + LIBKMOD_DETECTED= + if [ -z "$PKG_CONFIG" ] ; then + PKG_CONFIG=pkg-config + fi + if [ "$LIBKMOD" != no ] ; then + if ! command -v $PKG_CONFIG >/dev/null ; then + echo_n "($PKG_CONFIG not found) " + elif $PKG_CONFIG libkmod ; then + LIBKMOD_DETECTED=1 + fi + fi + if [ "$LIBKMOD" = yes -o "$LIBKMOD" = no ] ; then + echo "$LIBKMOD (set manually)" + if [ "$LIBKMOD" = yes -a -z "$LIBKMOD_DETECTED" ] ; then + echo "Requested use of libkmod, but it is not available. Giving up." + exit 1 + fi + else + if [ -n "$LIBKMOD_DETECTED" ] ; then + LIBKMOD=yes + else + LIBKMOD=no + fi + echo "$LIBKMOD (auto-detected)" + fi + if [ "$LIBKMOD" = yes ] ; then + echo >>$c "#define PCI_USE_LIBKMOD" + echo >>$m "LIBKMOD_CFLAGS=$($PKG_CONFIG --cflags libkmod)" + echo >>$m "LIBKMOD_LIBS=$($PKG_CONFIG --libs libkmod)" + fi + + echo_n "Checking for udev hwdb support... " + if [ "$HWDB" = yes -o "$HWDB" = no ] ; then + echo "$HWDB (set manually)" + else + if `command -v $PKG_CONFIG >/dev/null && $PKG_CONFIG --atleast-version=196 libudev` ; then + HWDB=yes + else + HWDB=no + fi + echo "$HWDB (auto-detected)" + fi + if [ "$HWDB" = yes ] ; then + echo >>$c '#define PCI_HAVE_HWDB' + echo >>$m 'LIBUDEV=-ludev' + echo >>$m 'WITH_LIBS+=$(LIBUDEV)' + fi +fi + +echo "Checking whether to build a shared library... $SHARED (set manually)" +if [ "$SHARED" = no ] ; then + echo >>$m 'PCILIB=$(LIBNAME).a' + echo >>$m 'LDLIBS=$(WITH_LIBS)' + echo >>$m 'LIB_LDLIBS=' +else + if [ "$LIBEXT" = so ]; then + echo >>$m 'PCILIB=$(LIBNAME).$(LIBEXT).$(VERSION)' + elif [ "$LIBEXT" = dll ]; then + echo >>$m 'PCILIB=$(LIBNAME)$(ABI_VERSION).$(LIBEXT)' + else + echo >>$m 'PCILIB=$(LIBNAME).$(VERSION).$(LIBEXT)' + fi + # We link the dependencies _to_ the library, so we do not need explicit deps in .pc + echo >>$m 'LDLIBS=' + echo >>$m 'LIB_LDLIBS=$(WITH_LIBS)' + echo >>$c '#define PCI_SHARED_LIB' + if [ "$LIBEXT" = so ]; then + echo >>$m 'PCILIB_LDFLAGS+=-Wl,-soname,$(LIBNAME).$(LIBEXT).$(ABI_VERSION)' + echo >>$m 'PCILIB_LDFLAGS+=-Wl,--version-script=libpci.ver' + elif [ "$LIBEXT" = dylib ]; then + echo >>$m 'PCILIB_LDFLAGS+=-Wl,-install_name,$(LIBDIR)/$(PCILIB)' + elif [ "$LIBEXT" = dll ]; then + echo >>$m 'PCIIMPDEF=$(LIBNAME)$(ABI_VERSION).def' + # GCC's -fvisibility=hidden is broken for Windows targets, use -Wl,--exclude-all-symbols instead (supported since GNU LD 2.21) + echo >>$m 'PCILIB_LDFLAGS+=-Wl,--exclude-all-symbols' + fi +fi +echo >>$m 'PCILIBPC=$(LIBNAME).pc' + +if [ "$SHARED" != no ] && [ "$LIBEXT" = dll ]; then + echo >>$m 'PCIIMPLIB=$(PCILIB).a' +else + echo >>$m 'PCIIMPLIB=$(PCILIB)' +fi + +echo >>$c "#define PCILIB_VERSION \"$VERSION\"" +echo >>$c "#define PCILIB_DATE_AMIGAOS \"`echo $DATE | sed 's/\(....\)-\(..\)-\(..\)/\3.\2.\1/'`\"" +sed '/"/{s/^#define \([^ ]*\) "\(.*\)"$/\1=\2/;p;d;};s/^#define \(.*\)/\1=1/' <$c >>$m diff --git a/lib/darwin.c b/lib/darwin.c new file mode 100644 index 0000000..8ae9008 --- /dev/null +++ b/lib/darwin.c @@ -0,0 +1,213 @@ +/* + * The PCI Library -- Darwin kIOACPI access + * + * Copyright (c) 2013 Apple, Inc. + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdint.h> + +#include "internal.h" + +#include <mach/mach_error.h> +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/IOKitKeys.h> + +enum { + kACPIMethodAddressSpaceRead = 0, + kACPIMethodAddressSpaceWrite = 1, + kACPIMethodDebuggerCommand = 2, + kACPIMethodCount +}; + +#pragma pack(1) + +typedef UInt32 IOACPIAddressSpaceID; + +enum { + kIOACPIAddressSpaceIDSystemMemory = 0, + kIOACPIAddressSpaceIDSystemIO = 1, + kIOACPIAddressSpaceIDPCIConfiguration = 2, + kIOACPIAddressSpaceIDEmbeddedController = 3, + kIOACPIAddressSpaceIDSMBus = 4 +}; + +/* + * 64-bit ACPI address + */ +union IOACPIAddress { + UInt64 addr64; + struct { + unsigned int offset :16; + unsigned int function :3; + unsigned int device :5; + unsigned int bus :8; + unsigned int segment :16; + unsigned int reserved :16; + } pci; +}; +typedef union IOACPIAddress IOACPIAddress; + +#pragma pack() + +struct AddressSpaceParam { + UInt64 value; + UInt32 spaceID; + IOACPIAddress address; + UInt32 bitWidth; + UInt32 bitOffset; + UInt32 options; +}; +typedef struct AddressSpaceParam AddressSpaceParam; + +static void +darwin_config(struct pci_access *a UNUSED) +{ +} + +static int +darwin_detect(struct pci_access *a) +{ + io_registry_entry_t service; + io_connect_t connect; + kern_return_t status; + + service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleACPIPlatformExpert")); + if (service) + { + status = IOServiceOpen(service, mach_task_self(), 0, &connect); + IOObjectRelease(service); + } + + if (!service || (kIOReturnSuccess != status)) + { + a->warning("Cannot open AppleACPIPlatformExpert (add boot arg debug=0x144 & run as root)"); + return 0; + } + a->debug("...using AppleACPIPlatformExpert"); + a->fd = connect; + return 1; +} + +static void +darwin_init(struct pci_access *a UNUSED) +{ +} + +static void +darwin_cleanup(struct pci_access *a UNUSED) +{ +} + +static int +darwin_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + if (!(len == 1 || len == 2 || len == 4)) + return pci_generic_block_read(d, pos, buf, len); + + AddressSpaceParam param; + kern_return_t status; + + param.spaceID = kIOACPIAddressSpaceIDPCIConfiguration; + param.bitWidth = len * 8; + param.bitOffset = 0; + param.options = 0; + + param.address.pci.offset = pos; + param.address.pci.function = d->func; + param.address.pci.device = d->dev; + param.address.pci.bus = d->bus; + param.address.pci.segment = d->domain; + param.address.pci.reserved = 0; + param.value = -1ULL; + + size_t outSize = sizeof(param); + status = IOConnectCallStructMethod(d->access->fd, kACPIMethodAddressSpaceRead, + ¶m, sizeof(param), + ¶m, &outSize); + if ((kIOReturnSuccess != status)) + d->access->error("darwin_read: kACPIMethodAddressSpaceRead failed: %s", mach_error_string(status)); + + switch (len) + { + case 1: + buf[0] = (u8) param.value; + break; + case 2: + ((u16 *) buf)[0] = cpu_to_le16((u16) param.value); + break; + case 4: + ((u32 *) buf)[0] = cpu_to_le32((u32) param.value); + break; + } + return 1; +} + +static int +darwin_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + if (!(len == 1 || len == 2 || len == 4)) + return pci_generic_block_write(d, pos, buf, len); + + AddressSpaceParam param; + kern_return_t status; + + param.spaceID = kIOACPIAddressSpaceIDPCIConfiguration; + param.bitWidth = len * 8; + param.bitOffset = 0; + param.options = 0; + + param.address.pci.offset = pos; + param.address.pci.function = d->func; + param.address.pci.device = d->dev; + param.address.pci.bus = d->bus; + param.address.pci.segment = d->domain; + param.address.pci.reserved = 0; + + switch (len) + { + case 1: + param.value = buf[0]; + break; + case 2: + param.value = le16_to_cpu(((u16 *) buf)[0]); + break; + case 4: + param.value = le32_to_cpu(((u32 *) buf)[0]); + break; + } + + size_t outSize = 0; + status = IOConnectCallStructMethod(d->access->fd, kACPIMethodAddressSpaceWrite, + ¶m, sizeof(param), + NULL, &outSize); + if ((kIOReturnSuccess != status)) + d->access->error("darwin_read: kACPIMethodAddressSpaceWrite failed: %s", mach_error_string(status)); + + return 1; +} + +struct pci_methods pm_darwin = { + "darwin", + "Darwin", + darwin_config, + darwin_detect, + darwin_init, + darwin_cleanup, + pci_generic_scan, + pci_generic_fill_info, + darwin_read, + darwin_write, + NULL, /* read_vpd */ + NULL, /* dev_init */ + NULL /* dev_cleanup */ +}; diff --git a/lib/dump.c b/lib/dump.c new file mode 100644 index 0000000..829071f --- /dev/null +++ b/lib/dump.c @@ -0,0 +1,194 @@ +/* + * The PCI Library -- Reading of Bus Dumps + * + * Copyright (c) 1997--2008 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <errno.h> + +#include "internal.h" + +struct dump_data { + int len, allocated; + byte data[1]; +}; + +static void +dump_config(struct pci_access *a) +{ + pci_define_param(a, "dump.name", "", "Name of the bus dump file to read from"); +} + +static int +dump_detect(struct pci_access *a) +{ + char *name = pci_get_param(a, "dump.name"); + return name && name[0]; +} + +static void +dump_alloc_data(struct pci_dev *dev, int len) +{ + struct dump_data *dd = pci_malloc(dev->access, sizeof(struct dump_data) + len - 1); + dd->allocated = len; + dd->len = 0; + memset(dd->data, 0xff, len); + dev->backend_data = dd; +} + +static int +dump_validate(char *s, char *fmt) +{ + while (*fmt) + { + if (*fmt == '#' ? !isxdigit(*s) : *fmt != *s) + return 0; + fmt++, s++; + } + return 1; +} + +static void +dump_init(struct pci_access *a) +{ + char *name = pci_get_param(a, "dump.name"); + FILE *f; + char buf[256]; + struct pci_dev *dev = NULL; + int len, mn, bn, dn, fn, i, j; + + if (!name) + a->error("dump: File name not given."); + if (!(f = fopen(name, "r"))) + a->error("dump: Cannot open %s: %s", name, strerror(errno)); + while (fgets(buf, sizeof(buf)-1, f)) + { + char *z = strchr(buf, '\n'); + if (!z) + { + fclose(f); + a->error("dump: line too long or unterminated"); + } + *z-- = 0; + if (z >= buf && *z == '\r') + *z-- = 0; + len = z - buf + 1; + mn = 0; + if (dump_validate(buf, "##:##.# ") && sscanf(buf, "%x:%x.%d", &bn, &dn, &fn) == 3 || + dump_validate(buf, "####:##:##.# ") && sscanf(buf, "%x:%x:%x.%d", &mn, &bn, &dn, &fn) == 4 || + dump_validate(buf, "#####:##:##.# ") && sscanf(buf, "%x:%x:%x.%d", &mn, &bn, &dn, &fn) == 4) + { + dev = pci_get_dev(a, mn, bn, dn, fn); + dump_alloc_data(dev, 256); + pci_link_dev(a, dev); + } + else if (!len) + dev = NULL; + else if (dev && + (dump_validate(buf, "##: ") || dump_validate(buf, "###: ") || dump_validate(buf, "####: ") || + dump_validate(buf, "#####: ") || dump_validate(buf, "######: ") || + dump_validate(buf, "#######: ") || dump_validate(buf, "########: ")) && + sscanf(buf, "%x: ", &i) == 1) + { + struct dump_data *dd = dev->backend_data; + z = strchr(buf, ' ') + 1; + while (isxdigit(z[0]) && isxdigit(z[1]) && (!z[2] || z[2] == ' ') && + sscanf(z, "%x", &j) == 1 && j < 256) + { + if (i >= 4096) + { + fclose(f); + a->error("dump: At most 4096 bytes of config space are supported"); + } + if (i >= dd->allocated) /* Need to re-allocate the buffer */ + { + dump_alloc_data(dev, 4096); + memcpy(((struct dump_data *) dev->backend_data)->data, dd->data, 256); + pci_mfree(dd); + dd = dev->backend_data; + } + dd->data[i++] = j; + if (i > dd->len) + dd->len = i; + z += 2; + if (*z) + z++; + } + if (*z) + { + fclose(f); + a->error("dump: Malformed line"); + } + } + } + fclose(f); +} + +static void +dump_cleanup(struct pci_access *a UNUSED) +{ +} + +static void +dump_scan(struct pci_access *a UNUSED) +{ +} + +static int +dump_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + struct dump_data *dd; + if (!(dd = d->backend_data)) + { + struct pci_dev *e = d->access->devices; + while (e && (e->domain != d->domain || e->bus != d->bus || e->dev != d->dev || e->func != d->func)) + e = e->next; + if (!e) + return 0; + dd = e->backend_data; + } + if (pos + len > dd->len) + return 0; + memcpy(buf, dd->data + pos, len); + return 1; +} + +static int +dump_write(struct pci_dev *d UNUSED, int pos UNUSED, byte *buf UNUSED, int len UNUSED) +{ + d->access->error("Writing to dump files is not supported."); + return 0; +} + +static void +dump_cleanup_dev(struct pci_dev *d) +{ + if (d->backend_data) + { + pci_mfree(d->backend_data); + d->backend_data = NULL; + } +} + +struct pci_methods pm_dump = { + "dump", + "Reading of register dumps (set the `dump.name' parameter)", + dump_config, + dump_detect, + dump_init, + dump_cleanup, + dump_scan, + pci_generic_fill_info, + dump_read, + dump_write, + NULL, /* read_vpd */ + NULL, /* init_dev */ + dump_cleanup_dev +}; diff --git a/lib/ecam.c b/lib/ecam.c new file mode 100644 index 0000000..f088c0c --- /dev/null +++ b/lib/ecam.c @@ -0,0 +1,1104 @@ +/* + * The PCI Library -- Direct Configuration access via PCIe ECAM + * + * Copyright (c) 2023 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "internal.h" +#include "physmem.h" +#include "physmem-access.h" + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include <glob.h> +#include <unistd.h> + +#if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) +#include <sys/sysctl.h> +#endif + +#if defined (__FreeBSD__) || defined (__DragonFly__) +#include <kenv.h> +#endif + +struct acpi_rsdp { + char signature[8]; + u8 checksum; + char oem_id[6]; + u8 revision; + u32 rsdt_address; + struct { + u32 length; + u64 xsdt_address; + u8 ext_checksum; + u8 reserved[3]; + } rsdp20[0]; +} PCI_PACKED; + +struct acpi_sdt { + char signature[4]; + u32 length; + u8 revision; + u8 checksum; + char oem_id[6]; + char oem_table_id[8]; + u32 oem_revision; + char asl_compiler_id[4]; + u32 asl_compiler_revision; +} PCI_PACKED; + +struct acpi_rsdt { + struct acpi_sdt sdt; + u32 sdt_addresses[0]; +} PCI_PACKED; + +struct acpi_xsdt { + struct acpi_sdt sdt; + u64 sdt_addresses[0]; +} PCI_PACKED; + +struct acpi_mcfg { + struct acpi_sdt sdt; + u64 reserved; + struct { + u64 address; + u16 pci_segment; + u8 start_bus_number; + u8 end_bus_number; + u32 reserved; + } allocations[0]; +} PCI_PACKED; + +struct mmap_cache { + void *map; + u64 addr; + u32 length; + int domain; + u8 bus; + int w; +}; + +// Back-end data linked to struct pci_access +struct ecam_access { + struct acpi_mcfg *mcfg; + struct mmap_cache *cache; + struct physmem *physmem; + long pagesize; +}; + +static unsigned int +get_rsdt_addresses_count(struct acpi_rsdt *rsdt) +{ + return (rsdt->sdt.length - ((unsigned char*)&rsdt->sdt_addresses - (unsigned char *)rsdt)) / sizeof(rsdt->sdt_addresses[0]); +} + +static unsigned int +get_xsdt_addresses_count(struct acpi_xsdt *xsdt) +{ + return (xsdt->sdt.length - ((unsigned char*)&xsdt->sdt_addresses - (unsigned char *)xsdt)) / sizeof(xsdt->sdt_addresses[0]); +} + +static unsigned int +get_mcfg_allocations_count(struct acpi_mcfg *mcfg) +{ + return (mcfg->sdt.length - ((unsigned char *)&mcfg->allocations - (unsigned char *)mcfg)) / sizeof(mcfg->allocations[0]); +} + +static u8 +calculate_checksum(const u8 *bytes, int len) +{ + u8 checksum = 0; + + while (len-- > 0) + checksum -= *(bytes++); + return checksum; +} + +static struct acpi_sdt * +check_and_map_sdt(struct physmem *physmem, long pagesize, u64 addr, const char *signature, void **map_addr, u32 *map_length) +{ + struct acpi_sdt *sdt; + char sdt_signature[sizeof(sdt->signature)]; + u32 length; + void *map; + + if (addr + sizeof(*sdt) < addr) + return NULL; + + map = physmem_map(physmem, addr & ~(pagesize-1), sizeof(*sdt) + (addr & (pagesize-1)), 0); + if (map == (void *)-1) + return NULL; + + sdt = (struct acpi_sdt *)((unsigned char *)map + (addr & (pagesize-1))); + length = sdt->length; + memcpy(sdt_signature, sdt->signature, sizeof(sdt->signature)); + + physmem_unmap(physmem, map, sizeof(*sdt) + (addr & (pagesize-1))); + + if (memcmp(sdt_signature, signature, sizeof(sdt_signature)) != 0) + return NULL; + if (length < sizeof(*sdt)) + return NULL; + + map = physmem_map(physmem, addr & ~(pagesize-1), length + (addr & (pagesize-1)), 0); + if (map == (void *)-1) + return NULL; + + sdt = (struct acpi_sdt *)((unsigned char *)map + (addr & (pagesize-1))); + + if (calculate_checksum((u8 *)sdt, sdt->length) != 0) + { + physmem_unmap(physmem, map, length + (addr & (pagesize-1))); + return NULL; + } + + *map_addr = map; + *map_length = length + (addr & (pagesize-1)); + return sdt; +} + +static int +check_rsdp(struct acpi_rsdp *rsdp) +{ + if (memcmp(rsdp->signature, "RSD PTR ", sizeof(rsdp->signature)) != 0) + return 0; + if (calculate_checksum((u8 *)rsdp, sizeof(*rsdp)) != 0) + return 0; + return 1; +} + +static int +check_and_parse_rsdp(struct physmem *physmem, long pagesize, u64 addr, u32 *rsdt_address, u64 *xsdt_address) +{ + struct acpi_rsdp *rsdp; + unsigned char buf[sizeof(*rsdp) + sizeof(*rsdp->rsdp20)]; + void *map; + + map = physmem_map(physmem, addr & ~(pagesize-1), sizeof(buf) + (addr & (pagesize-1)), 0); + if (map == (void *)-1) + return 0; + + rsdp = (struct acpi_rsdp *)buf; + memcpy(rsdp, (unsigned char *)map + (addr & (pagesize-1)), sizeof(buf)); + + physmem_unmap(physmem, map, sizeof(buf)); + + if (!check_rsdp(rsdp)) + return 0; + + *rsdt_address = rsdp->rsdt_address; + + if (rsdp->revision != 0 && + (*rsdp->rsdp20).length == sizeof(*rsdp) + sizeof(*rsdp->rsdp20) && + calculate_checksum((u8 *)rsdp, (*rsdp->rsdp20).length) == 0) + *xsdt_address = (*rsdp->rsdp20).xsdt_address; + else + *xsdt_address = 0; + + return 1; +} + +static u64 +find_rsdp_address(struct pci_access *a, const char *efisystab, int use_bsd UNUSED, int use_x86bios UNUSED) +{ + u64 ullnum; +#if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) + unsigned long ulnum; +#endif + char buf[1024]; + char *endptr; + u64 acpi20; + u64 acpi; +#if defined(__amd64__) || defined(__i386__) + struct ecam_access *eacc = a->backend_data; + struct physmem *physmem = eacc->physmem; + u64 rsdp_addr; + u64 addr; + void *map; +#endif + size_t len; + FILE *f; + + if (efisystab[0]) + { + acpi = 0; + acpi20 = 0; + a->debug("reading EFI system table: %s...", efisystab); + f = fopen(efisystab, "r"); + if (f) + { + while (fgets(buf, sizeof(buf), f)) + { + len = strlen(buf); + while (len > 0 && buf[len-1] == '\n') + buf[--len] = '\0'; + if (strncmp(buf, "ACPI20=", 7) == 0 && isxdigit(buf[7])) + { + errno = 0; + ullnum = strtoull(buf+7, &endptr, 16); + if (!errno && !*endptr) + acpi20 = ullnum; + } + else if (strncmp(buf, "ACPI=", 5) == 0 && isxdigit(buf[5])) + { + errno = 0; + ullnum = strtoull(buf+5, &endptr, 16); + if (!errno && !*endptr) + acpi = ullnum; + } + } + fclose(f); + } + else + a->debug("opening failed: %s...", strerror(errno)); + + if (acpi20) + return acpi20; + else if (acpi) + return acpi; + } + +#if defined (__FreeBSD__) || defined (__DragonFly__) + if (use_bsd) + { + /* First try FreeBSD kenv hint.acpi.0.rsdp */ + a->debug("calling kenv hint.acpi.0.rsdp..."); + if (kenv(KENV_GET, "hint.acpi.0.rsdp", buf, sizeof(buf)) > 0) + { + errno = 0; + ullnum = strtoull(buf, &endptr, 16); + if (!errno && !*endptr) + return ullnum; + } + + /* Then try FreeBSD sysctl machdep.acpi_root */ + a->debug("calling sysctl machdep.acpi_root..."); + len = sizeof(ulnum); + if (sysctlbyname("machdep.acpi_root", &ulnum, &len, NULL, 0) == 0) + return ulnum; + } +#endif + +#if defined(__NetBSD__) + if (use_bsd) + { + /* Try NetBSD sysctl hw.acpi.root */ + a->debug("calling sysctl hw.acpi.root..."); + len = sizeof(ulnum); + if (sysctlbyname("hw.acpi.root", &ulnum, &len, NULL, 0) == 0) + return ulnum; + } +#endif + +#if defined(__amd64__) || defined(__i386__) + if (use_x86bios) + { + rsdp_addr = 0; + + /* Scan first kB of Extended BIOS Data Area */ + a->debug("scanning first kB of EBDA..."); + map = physmem_map(physmem, 0, 0x40E + 1024, 0); + if (map != (void *)-1) + { + for (addr = 0x40E; addr < 0x40E + 1024; addr += 16) + { + if (check_rsdp((struct acpi_rsdp *)((unsigned char *)map + addr))) + { + rsdp_addr = addr; + break; + } + } + if (physmem_unmap(physmem, map, 0x40E + 1024) != 0) + a->debug("unmapping of EBDA failed: %s...", strerror(errno)); + } + else + a->debug("mapping of EBDA failed: %s...", strerror(errno)); + + if (rsdp_addr) + return rsdp_addr; + + /* Scan the main BIOS area below 1 MB */ + a->debug("scanning BIOS below 1 MB..."); + map = physmem_map(physmem, 0xE0000, 0x20000, 0); + if (map != (void *)-1) + { + for (addr = 0x0; addr < 0x20000; addr += 16) + { + if (check_rsdp((struct acpi_rsdp *)((unsigned char *)map + addr))) + { + rsdp_addr = 0xE0000 + addr; + break; + } + } + if (physmem_unmap(physmem, map, 0x20000) != 0) + a->debug("unmapping of BIOS failed: %s...", strerror(errno)); + } + else + a->debug("mapping of BIOS failed: %s...", strerror(errno)); + + if (rsdp_addr) + return rsdp_addr; + } +#endif + + return 0; +} + +static struct acpi_mcfg * +find_mcfg(struct pci_access *a, const char *acpimcfg, const char *efisystab, int use_bsd, int use_x86bios) +{ + struct ecam_access *eacc = a->backend_data; + struct physmem *physmem = eacc->physmem; + long pagesize = eacc->pagesize; + struct acpi_xsdt *xsdt; + struct acpi_rsdt *rsdt; + struct acpi_mcfg *mcfg; + struct acpi_sdt *sdt; + unsigned int i, count; + u64 rsdp_address; + u64 xsdt_address; + u32 rsdt_address; + void *map_addr; + u32 map_length; + void *map2_addr; + u32 map2_length; + long length; + FILE *mcfg_file; + const char *path; + glob_t mcfg_glob; + int ret; + + if (acpimcfg[0]) + { + ret = glob(acpimcfg, GLOB_NOCHECK, NULL, &mcfg_glob); + if (ret == 0) + { + path = mcfg_glob.gl_pathv[0]; + a->debug("reading acpi mcfg file: %s...", path); + mcfg_file = fopen(path, "rb"); + globfree(&mcfg_glob); + if (mcfg_file) + { + if (fseek(mcfg_file, 0, SEEK_END) == 0) + length = ftell(mcfg_file); + else + length = -1; + if (length > 0 && (size_t)length > sizeof(*mcfg)) + { + rewind(mcfg_file); + mcfg = pci_malloc(a, length); + if (fread(mcfg, 1, length, mcfg_file) == (size_t)length && + memcmp(mcfg->sdt.signature, "MCFG", 4) == 0 && + mcfg->sdt.length <= (size_t)length && + calculate_checksum((u8 *)mcfg, mcfg->sdt.length) == 0) + { + fclose(mcfg_file); + return mcfg; + } + } + fclose(mcfg_file); + } + a->debug("failed..."); + } + else + a->debug("glob(%s) failed: %d...", acpimcfg, ret); + } + + a->debug("searching for ACPI RSDP..."); + rsdp_address = find_rsdp_address(a, efisystab, use_bsd, use_x86bios); + if (!rsdp_address) + { + a->debug("not found..."); + return NULL; + } + a->debug("found at 0x%" PCI_U64_FMT_X "...", rsdp_address); + + if (!check_and_parse_rsdp(physmem, pagesize, rsdp_address, &rsdt_address, &xsdt_address)) + { + a->debug("invalid..."); + return NULL; + } + + mcfg = NULL; + a->debug("searching for ACPI MCFG (XSDT=0x%" PCI_U64_FMT_X ", RSDT=0x%lx)...", xsdt_address, (unsigned long)rsdt_address); + + xsdt = xsdt_address ? (struct acpi_xsdt *)check_and_map_sdt(physmem, pagesize, xsdt_address, "XSDT", &map_addr, &map_length) : NULL; + if (xsdt) + { + a->debug("via XSDT..."); + count = get_xsdt_addresses_count(xsdt); + for (i = 0; i < count; i++) + { + sdt = check_and_map_sdt(physmem, pagesize, xsdt->sdt_addresses[i], "MCFG", &map2_addr, &map2_length); + if (sdt) + { + mcfg = pci_malloc(a, sdt->length); + memcpy(mcfg, sdt, sdt->length); + physmem_unmap(physmem, map2_addr, map2_length); + break; + } + } + physmem_unmap(physmem, map_addr, map_length); + if (mcfg) + { + a->debug("found..."); + return mcfg; + } + } + + rsdt = (struct acpi_rsdt *)check_and_map_sdt(physmem, pagesize, rsdt_address, "RSDT", &map_addr, &map_length); + if (rsdt) + { + a->debug("via RSDT..."); + count = get_rsdt_addresses_count(rsdt); + for (i = 0; i < count; i++) + { + sdt = check_and_map_sdt(physmem, pagesize, rsdt->sdt_addresses[i], "MCFG", &map2_addr, &map2_length); + if (sdt) + { + mcfg = pci_malloc(a, sdt->length); + memcpy(mcfg, sdt, sdt->length); + physmem_unmap(physmem, map2_addr, map2_length); + break; + } + } + physmem_unmap(physmem, map_addr, map_length); + if (mcfg) + { + a->debug("found..."); + return mcfg; + } + } + + a->debug("not found..."); + return NULL; +} + +static void +get_mcfg_allocation(struct acpi_mcfg *mcfg, unsigned int i, int *domain, u8 *start_bus, u8 *end_bus, u64 *addr, u32 *length) +{ + int buses = (int)mcfg->allocations[i].end_bus_number - (int)mcfg->allocations[i].start_bus_number + 1; + + if (domain) + *domain = mcfg->allocations[i].pci_segment; + if (start_bus) + *start_bus = mcfg->allocations[i].start_bus_number; + if (end_bus) + *end_bus = mcfg->allocations[i].end_bus_number; + if (addr) + *addr = mcfg->allocations[i].address; + if (length) + *length = (buses > 0) ? (buses * 32 * 8 * 4096) : 0; +} + +static int +parse_next_addrs(const char *addrs, const char **next, int *domain, u8 *start_bus, u8 *end_bus, u64 *addr, u32 *length) +{ + u64 ullnum; + const char *sep1, *sep2; + int addr_len; + char *endptr; + long num; + int buses; + u64 start_addr; + + if (!*addrs) + { + if (next) + *next = NULL; + return 0; + } + + endptr = strchr(addrs, ','); + if (endptr) + addr_len = endptr - addrs; + else + addr_len = strlen(addrs); + + if (next) + *next = endptr ? (endptr+1) : NULL; + + sep1 = memchr(addrs, ':', addr_len); + if (!sep1) + return 0; + + sep2 = memchr(sep1+1, ':', addr_len - (sep1+1 - addrs)); + if (!sep2) + { + sep2 = sep1; + sep1 = NULL; + } + + if (!sep1) + { + if (domain) + *domain = 0; + } + else + { + if (!isxdigit(*addrs)) + return 0; + errno = 0; + num = strtol(addrs, &endptr, 16); + if (errno || endptr != sep1 || num < 0 || num > INT_MAX) + return 0; + if (domain) + *domain = num; + } + + errno = 0; + num = strtol(sep1 ? (sep1+1) : addrs, &endptr, 16); + if (errno || num < 0 || num > 0xff) + return 0; + if (start_bus) + *start_bus = num; + + buses = -num; + + if (endptr != sep2) + { + if (*endptr != '-') + return 0; + errno = 0; + num = strtol(endptr+1, &endptr, 16); + if (errno || endptr != sep2 || num < 0 || num > 0xff) + return 0; + buses = num - -buses + 1; + if (buses <= 0) + return 0; + if (end_bus) + *end_bus = num; + } + + if (!isxdigit(*(sep2+1))) + return 0; + + errno = 0; + ullnum = strtoull(sep2+1, &endptr, 16); + if (errno || (ullnum & 3)) + return 0; + if (addr) + *addr = ullnum; + start_addr = ullnum; + + if (endptr == addrs + addr_len) + { + if (buses <= 0) + { + buses = 0xff - -buses + 1; + if (end_bus) + *end_bus = 0xff; + } + if (start_addr + (unsigned)buses * 32 * 8 * 4096 < start_addr) + return 0; + if (length) + *length = buses * 32 * 8 * 4096; + } + else + { + if (*endptr != '+' || !isxdigit(*(endptr+1))) + return 0; + errno = 0; + ullnum = strtoull(endptr+1, &endptr, 16); + if (errno || endptr != addrs + addr_len || (ullnum & 3) || ullnum > 256 * 32 * 8 * 4096) + return 0; + if (start_addr + ullnum < start_addr) + return 0; + if (buses > 0 && ullnum > (unsigned)buses * 32 * 8 * 4096) + return 0; + if (buses <= 0 && ullnum > (0xff - (unsigned)-buses + 1) * 32 * 8 * 4096) + return 0; + if (length) + *length = ullnum; + if (buses <= 0 && end_bus) + *end_bus = -buses + (ullnum + 32 * 8 * 4096 - 1) / (32 * 8 * 4096); + } + + return 1; +} + +static int +validate_addrs(const char *addrs) +{ + if (!*addrs) + return 1; + + while (addrs) + if (!parse_next_addrs(addrs, &addrs, NULL, NULL, NULL, NULL, NULL)) + return 0; + + return 1; +} + +static int +calculate_bus_addr(u8 start_bus, u64 start_addr, u32 total_length, u8 bus, u64 *addr, u32 *length) +{ + u32 offset; + + offset = 32*8*4096 * (bus - start_bus); + if (offset >= total_length) + return 0; + + *addr = start_addr + offset; + *length = total_length - offset; + + if (*length > 32*8*4096) + *length = 32*8*4096; + + return 1; +} + +static int +get_bus_addr(struct acpi_mcfg *mcfg, const char *addrs, int domain, u8 bus, u64 *addr, u32 *length) +{ + int cur_domain; + u8 start_bus; + u8 end_bus; + u64 start_addr; + u32 total_length; + int i, count; + + if (mcfg) + { + count = get_mcfg_allocations_count(mcfg); + for (i = 0; i < count; i++) + { + get_mcfg_allocation(mcfg, i, &cur_domain, &start_bus, &end_bus, &start_addr, &total_length); + if (domain == cur_domain && bus >= start_bus && bus <= end_bus) + return calculate_bus_addr(start_bus, start_addr, total_length, bus, addr, length); + } + return 0; + } + else + { + while (addrs) + { + if (!parse_next_addrs(addrs, &addrs, &cur_domain, &start_bus, &end_bus, &start_addr, &total_length)) + return 0; + if (domain == cur_domain && bus >= start_bus && bus <= end_bus) + return calculate_bus_addr(start_bus, start_addr, total_length, bus, addr, length); + } + return 0; + } +} + +static void +munmap_reg(struct pci_access *a) +{ + struct ecam_access *eacc = a->backend_data; + struct mmap_cache *cache = eacc->cache; + struct physmem *physmem = eacc->physmem; + long pagesize = eacc->pagesize; + + if (!cache) + return; + + physmem_unmap(physmem, cache->map, cache->length + (cache->addr & (pagesize-1))); + pci_mfree(cache); + eacc->cache = NULL; +} + +static int +mmap_reg(struct pci_access *a, int w, int domain, u8 bus, u8 dev, u8 func, int pos, volatile void **reg) +{ + struct ecam_access *eacc = a->backend_data; + struct mmap_cache *cache = eacc->cache; + struct physmem *physmem = eacc->physmem; + long pagesize = eacc->pagesize; + const char *addrs; + void *map; + u64 addr; + u32 length; + u32 offset; + + if (cache && cache->domain == domain && cache->bus == bus && !!cache->w == !!w) + { + map = cache->map; + addr = cache->addr; + length = cache->length; + } + else + { + addrs = pci_get_param(a, "ecam.addrs"); + if (!get_bus_addr(eacc->mcfg, addrs, domain, bus, &addr, &length)) + return 0; + + map = physmem_map(physmem, addr & ~(pagesize-1), length + (addr & (pagesize-1)), w); + if (map == (void *)-1) + return 0; + + if (cache) + physmem_unmap(physmem, cache->map, cache->length + (cache->addr & (pagesize-1))); + else + cache = eacc->cache = pci_malloc(a, sizeof(*cache)); + + cache->map = map; + cache->addr = addr; + cache->length = length; + cache->domain = domain; + cache->bus = bus; + cache->w = w; + } + + /* + * Enhanced Configuration Access Mechanism (ECAM) offset according to: + * PCI Express Base Specification, Revision 5.0, Version 1.0, Section 7.2.2, Table 7-1, p. 677 + */ + offset = ((dev & 0x1f) << 15) | ((func & 0x7) << 12) | (pos & 0xfff); + + if (offset + 4 > length) + return 0; + + *reg = (unsigned char *)map + (addr & (pagesize-1)) + offset; + return 1; +} + +static void +ecam_config(struct pci_access *a) +{ + physmem_init_config(a); + pci_define_param(a, "ecam.acpimcfg", PCI_PATH_ACPI_MCFG, "Path to the ACPI MCFG table"); + pci_define_param(a, "ecam.efisystab", PCI_PATH_EFI_SYSTAB, "Path to the EFI system table"); +#if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) + pci_define_param(a, "ecam.bsd", "1", "Use BSD kenv or sysctl to find ACPI MCFG table"); +#endif +#if defined(__amd64__) || defined(__i386__) + pci_define_param(a, "ecam.x86bios", "1", "Scan x86 BIOS memory for ACPI MCFG table"); +#endif + pci_define_param(a, "ecam.addrs", "", "Physical addresses of memory mapped PCIe ECAM interface"); /* format: [domain:]start_bus[-end_bus]:start_addr[+length],... */ +} + +static int +ecam_detect(struct pci_access *a) +{ + int use_addrs = 1, use_acpimcfg = 1, use_efisystab = 1, use_bsd = 1, use_x86bios = 1; + const char *acpimcfg = pci_get_param(a, "ecam.acpimcfg"); + const char *efisystab = pci_get_param(a, "ecam.efisystab"); +#if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) + const char *bsd = pci_get_param(a, "ecam.bsd"); +#endif +#if defined(__amd64__) || defined(__i386__) + const char *x86bios = pci_get_param(a, "ecam.x86bios"); +#endif + const char *addrs = pci_get_param(a, "ecam.addrs"); + struct ecam_access *eacc; + glob_t mcfg_glob; + int ret; + + if (!*addrs) + { + a->debug("ecam.addrs was not specified..."); + use_addrs = 0; + } + + if (acpimcfg[0]) + { + ret = glob(acpimcfg, GLOB_NOCHECK, NULL, &mcfg_glob); + if (ret == 0) + { + if (access(mcfg_glob.gl_pathv[0], R_OK)) + { + a->debug("cannot access acpimcfg: %s: %s...", mcfg_glob.gl_pathv[0], strerror(errno)); + use_acpimcfg = 0; + } + globfree(&mcfg_glob); + } + else + { + a->debug("glob(%s) failed: %d...", acpimcfg, ret); + use_acpimcfg = 0; + } + } + else + use_acpimcfg = 0; + + if (!efisystab[0] || access(efisystab, R_OK)) + { + if (efisystab[0]) + a->debug("cannot access efisystab: %s: %s...", efisystab, strerror(errno)); + use_efisystab = 0; + } + +#if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) + if (strcmp(bsd, "0") == 0) + { + a->debug("not using BSD kenv/sysctl..."); + use_bsd = 0; + } +#else + use_bsd = 0; +#endif + +#if defined(__amd64__) || defined(__i386__) + if (strcmp(x86bios, "0") == 0) + { + a->debug("not using x86 BIOS..."); + use_x86bios = 0; + } +#else + use_x86bios = 0; +#endif + + if (!use_addrs && !use_acpimcfg && !use_efisystab && !use_bsd && !use_x86bios) + { + a->debug("no ecam source provided"); + return 0; + } + + if (!validate_addrs(addrs)) + { + a->debug("ecam.addrs has invalid format %s", addrs); + return 0; + } + + if (physmem_access(a, 0)) + { + a->debug("cannot access physical memory: %s", strerror(errno)); + return 0; + } + + if (!use_addrs) + { + eacc = pci_malloc(a, sizeof(*eacc)); + + eacc->physmem = physmem_open(a, a->writeable); + if (!eacc->physmem) + { + a->debug("cannot open physcal memory: %s.", strerror(errno)); + pci_mfree(eacc); + return 0; + } + + eacc->pagesize = physmem_get_pagesize(eacc->physmem); + if (eacc->pagesize <= 0) + { + a->debug("Cannot get page size: %s.", strerror(errno)); + physmem_close(eacc->physmem); + pci_mfree(eacc); + return 0; + } + + eacc->mcfg = NULL; + eacc->cache = NULL; + a->backend_data = eacc; + eacc->mcfg = find_mcfg(a, acpimcfg, efisystab, use_bsd, use_x86bios); + if (!eacc->mcfg) + { + physmem_close(eacc->physmem); + pci_mfree(eacc); + a->backend_data = NULL; + return 0; + } + } + + if (use_addrs) + a->debug("using with ecam addresses %s", addrs); + else + a->debug("using with%s%s%s%s%s%s", use_acpimcfg ? " acpimcfg=" : "", use_acpimcfg ? acpimcfg : "", use_efisystab ? " efisystab=" : "", use_efisystab ? efisystab : "", use_bsd ? " bsd" : "", use_x86bios ? " x86bios" : ""); + + return 1; +} + +static void +ecam_init(struct pci_access *a) +{ + const char *acpimcfg = pci_get_param(a, "ecam.acpimcfg"); + const char *efisystab = pci_get_param(a, "ecam.efisystab"); +#if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) + const char *bsd = pci_get_param(a, "ecam.bsd"); +#endif +#if defined(__amd64__) || defined(__i386__) + const char *x86bios = pci_get_param(a, "ecam.x86bios"); +#endif + const char *addrs = pci_get_param(a, "ecam.addrs"); + struct physmem *physmem = NULL; + struct ecam_access *eacc = a->backend_data; + long pagesize = 0; + int use_bsd = 0; + int use_x86bios = 0; + int test_domain = 0; + u8 test_bus = 0; + volatile void *test_reg; + + if (!validate_addrs(addrs)) + a->error("Option ecam.addrs has invalid address format \"%s\".", addrs); + + if (!eacc) + { + physmem = physmem_open(a, a->writeable); + if (!physmem) + a->error("Cannot open physcal memory: %s.", strerror(errno)); + + pagesize = physmem_get_pagesize(physmem); + if (pagesize <= 0) + a->error("Cannot get page size: %s.", strerror(errno)); + + eacc = pci_malloc(a, sizeof(*eacc)); + eacc->mcfg = NULL; + eacc->cache = NULL; + eacc->physmem = physmem; + eacc->pagesize = pagesize; + a->backend_data = eacc; + } + + if (!*addrs) + { +#if defined (__FreeBSD__) || defined (__DragonFly__) + if (strcmp(bsd, "0") != 0) + use_bsd = 1; +#endif +#if defined(__amd64__) || defined(__i386__) + if (strcmp(x86bios, "0") != 0) + use_x86bios = 1; +#endif + if (!eacc->mcfg) + eacc->mcfg = find_mcfg(a, acpimcfg, efisystab, use_bsd, use_x86bios); + if (!eacc->mcfg) + a->error("Option ecam.addrs was not specified and ACPI MCFG table cannot be found."); + } + + if (eacc->mcfg) + get_mcfg_allocation(eacc->mcfg, 0, &test_domain, &test_bus, NULL, NULL, NULL); + else + parse_next_addrs(addrs, NULL, &test_domain, &test_bus, NULL, NULL, NULL); + + errno = 0; + if (!mmap_reg(a, 0, test_domain, test_bus, 0, 0, 0, &test_reg)) + a->error("Cannot map ecam region: %s.", errno ? strerror(errno) : "Unknown error"); +} + +static void +ecam_cleanup(struct pci_access *a) +{ + struct ecam_access *eacc = a->backend_data; + + munmap_reg(a); + physmem_close(eacc->physmem); + pci_mfree(eacc->mcfg); + pci_mfree(eacc); + a->backend_data = NULL; +} + +static void +ecam_scan(struct pci_access *a) +{ + const char *addrs = pci_get_param(a, "ecam.addrs"); + struct ecam_access *eacc = a->backend_data; + u32 *segments; + int i, j, count; + int domain; + + segments = pci_malloc(a, 0xFFFF/8); + memset(segments, 0, 0xFFFF/8); + + if (eacc->mcfg) + { + count = get_mcfg_allocations_count(eacc->mcfg); + for (i = 0; i < count; i++) + segments[eacc->mcfg->allocations[i].pci_segment / 32] |= 1 << (eacc->mcfg->allocations[i].pci_segment % 32); + } + else + { + while (addrs) + { + if (parse_next_addrs(addrs, &addrs, &domain, NULL, NULL, NULL, NULL)) + segments[domain / 32] |= 1 << (domain % 32); + } + } + + for (i = 0; i < 0xFFFF/32; i++) + { + if (!segments[i]) + continue; + for (j = 0; j < 32; j++) + if (segments[i] & (1 << j)) + pci_generic_scan_domain(a, 32*i + j); + } + + pci_mfree(segments); +} + +static int +ecam_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + volatile void *reg; + + if (pos >= 4096) + return 0; + + if (len != 1 && len != 2 && len != 4) + return pci_generic_block_read(d, pos, buf, len); + + if (!mmap_reg(d->access, 0, d->domain, d->bus, d->dev, d->func, pos, ®)) + return 0; + + switch (len) + { + case 1: + buf[0] = physmem_readb(reg); + break; + case 2: + ((u16 *) buf)[0] = physmem_readw(reg); + break; + case 4: + ((u32 *) buf)[0] = physmem_readl(reg); + break; + } + + return 1; +} + +static int +ecam_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + volatile void *reg; + + if (pos >= 4096) + return 0; + + if (len != 1 && len != 2 && len != 4) + return pci_generic_block_read(d, pos, buf, len); + + if (!mmap_reg(d->access, 1, d->domain, d->bus, d->dev, d->func, pos, ®)) + return 0; + + switch (len) + { + case 1: + physmem_writeb(buf[0], reg); + break; + case 2: + physmem_writew(((u16 *) buf)[0], reg); + break; + case 4: + physmem_writel(((u32 *) buf)[0], reg); + break; + } + + return 1; +} + +struct pci_methods pm_ecam = { + "ecam", + "Raw memory mapped access using PCIe ECAM interface", + ecam_config, + ecam_detect, + ecam_init, + ecam_cleanup, + ecam_scan, + pci_generic_fill_info, + ecam_read, + ecam_write, + NULL, /* read_vpd */ + NULL, /* init_dev */ + NULL /* cleanup_dev */ +}; diff --git a/lib/emulated.c b/lib/emulated.c new file mode 100644 index 0000000..92cd06e --- /dev/null +++ b/lib/emulated.c @@ -0,0 +1,301 @@ +/* + * The PCI Library -- Virtual Emulated Config Space Access Functions + * + * Copyright (c) 2022 Pali Rohár + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "internal.h" + +static u32 +ioflg_to_pciflg(pciaddr_t ioflg) +{ + u32 flg = 0; + + if ((ioflg & PCI_IORESOURCE_TYPE_BITS) == PCI_IORESOURCE_IO) + flg = PCI_BASE_ADDRESS_SPACE_IO; + else if ((ioflg & PCI_IORESOURCE_TYPE_BITS) == PCI_IORESOURCE_MEM) + { + flg = PCI_BASE_ADDRESS_SPACE_MEMORY; + if (ioflg & PCI_IORESOURCE_MEM_64) + flg |= PCI_BASE_ADDRESS_MEM_TYPE_64; + else + flg |= PCI_BASE_ADDRESS_MEM_TYPE_32; + if (ioflg & PCI_IORESOURCE_PREFETCH) + flg |= PCI_BASE_ADDRESS_MEM_PREFETCH; + } + + return flg; +} + +static u32 +baseres_to_pcires(pciaddr_t addr, pciaddr_t ioflg, int *have_sec, u32 *sec_val) +{ + u32 val = ioflg_to_pciflg(ioflg); + + if (have_sec) + *have_sec = 0; + + if ((val & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO && addr <= 0xffffffff) + val |= addr & PCI_BASE_ADDRESS_IO_MASK; + else if ((val & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_MEMORY) + { + val |= addr & PCI_BASE_ADDRESS_MEM_MASK; + if ((val & PCI_BASE_ADDRESS_MEM_TYPE_64) && have_sec) + { + *have_sec = 1; + *sec_val = addr >> 32; + } + } + + return val; +} + +static inline u32 +even_baseres_to_pcires(pciaddr_t addr, pciaddr_t ioflg) +{ + return baseres_to_pcires(addr, ioflg, NULL, NULL); +} + +static inline u32 +odd_baseres_to_pcires(pciaddr_t addr0, pciaddr_t ioflg0, pciaddr_t addr, pciaddr_t ioflg) +{ + int have_sec; + u32 val; + baseres_to_pcires(addr0, ioflg0, &have_sec, &val); + if (!have_sec) + val = baseres_to_pcires(addr, ioflg, NULL, NULL); + return val; +} + +int +pci_emulated_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + u32 ht = PCI_HEADER_TYPE_NORMAL; + u32 val = 0; + int i; + + if (pos >= 64) + return 0; + + if (len > 4) + return pci_generic_block_read(d, pos, buf, len); + + if (d->device_class == PCI_CLASS_BRIDGE_PCI) + ht = PCI_HEADER_TYPE_BRIDGE; + else if (d->device_class == PCI_CLASS_BRIDGE_CARDBUS) + ht = PCI_HEADER_TYPE_CARDBUS; + + switch (pos & ~3) + { + case PCI_COMMAND: + for (i = 0; i < 6; i++) + { + if (!d->size[i]) + continue; + if ((d->flags[i] & PCI_IORESOURCE_TYPE_BITS) == PCI_IORESOURCE_IO) + val |= PCI_COMMAND_IO; + else if ((d->flags[i] & PCI_IORESOURCE_TYPE_BITS) == PCI_IORESOURCE_MEM) + val |= PCI_COMMAND_MEMORY; + } + break; + case PCI_VENDOR_ID: + val = (d->device_id << 16) | d->vendor_id; + break; + case PCI_CLASS_REVISION: + val = (d->device_class << 16) | (d->prog_if << 8) | d->rev_id; + break; + case PCI_CACHE_LINE_SIZE: + val = ht << 16; + break; + case PCI_BASE_ADDRESS_0: + val = even_baseres_to_pcires(d->base_addr[0], d->flags[0]); + break; + case PCI_INTERRUPT_LINE: + val = (d->irq >= 0 && d->irq <= 0xff) ? d->irq : 0; + break; + } + + if ((pos & ~3) == PCI_BASE_ADDRESS_1 && (ht == PCI_HEADER_TYPE_NORMAL || ht == PCI_HEADER_TYPE_BRIDGE)) + val = odd_baseres_to_pcires(d->base_addr[0], d->flags[0], d->base_addr[1], d->flags[1]); + + if (ht == PCI_HEADER_TYPE_NORMAL) + switch (pos & ~3) + { + case PCI_BASE_ADDRESS_2: + val = even_baseres_to_pcires(d->base_addr[2], d->flags[2]); + break; + case PCI_BASE_ADDRESS_3: + val = odd_baseres_to_pcires(d->base_addr[2], d->flags[2], d->base_addr[3], d->flags[3]); + break; + case PCI_BASE_ADDRESS_4: + val = even_baseres_to_pcires(d->base_addr[4], d->flags[4]); + break; + case PCI_BASE_ADDRESS_5: + val = odd_baseres_to_pcires(d->base_addr[4], d->flags[4], d->base_addr[5], d->flags[5]); + break; + case PCI_SUBSYSTEM_VENDOR_ID: + val = (d->subsys_id << 16) | d->subsys_vendor_id; + break; + case PCI_ROM_ADDRESS: + val = d->rom_base_addr & PCI_ROM_ADDRESS_MASK; + if (val) + val |= PCI_ROM_ADDRESS_ENABLE; + break; + } + else if (ht == PCI_HEADER_TYPE_BRIDGE) + switch (pos & ~3) + { + case PCI_COMMAND: + if (d->bridge_size[0]) + val |= PCI_COMMAND_IO; + if (d->bridge_size[1] || d->bridge_size[2]) + val |= PCI_COMMAND_MEMORY; + break; + case PCI_PRIMARY_BUS: + val = d->bus; + break; + case PCI_IO_BASE: + if (d->bridge_size[0]) + { + val = (((((d->bridge_base_addr[0] + d->bridge_size[0] - 1) >> 8) & PCI_IO_RANGE_MASK) << 8) & 0xff00) | + (((d->bridge_base_addr[0] >> 8) & PCI_IO_RANGE_MASK) & 0x00ff); + if ((d->bridge_flags[0] & PCI_IORESOURCE_IO_16BIT_ADDR) && + d->bridge_base_addr[0] + d->bridge_size[0] - 1 <= 0xffff) + val |= (PCI_IO_RANGE_TYPE_16 << 8) | PCI_IO_RANGE_TYPE_16; + else + val |= (PCI_IO_RANGE_TYPE_32 << 8) | PCI_IO_RANGE_TYPE_32; + } + else + val = 0xff & PCI_IO_RANGE_MASK; + break; + case PCI_MEMORY_BASE: + if (d->bridge_size[1]) + val = (((((d->bridge_base_addr[1] + d->bridge_size[1] - 1) >> 16) & PCI_MEMORY_RANGE_MASK) << 16) & 0xffff0000) | + (((d->bridge_base_addr[1] >> 16) & PCI_MEMORY_RANGE_MASK) & 0x0000ffff); + else + val = 0xffff & PCI_MEMORY_RANGE_MASK; + break; + case PCI_PREF_MEMORY_BASE: + if (d->bridge_size[2]) + { + val = (((((d->bridge_base_addr[2] + d->bridge_size[2] - 1) >> 16) & PCI_PREF_RANGE_MASK) << 16) & 0xffff0000) | + (((d->bridge_base_addr[2] >> 16) & PCI_PREF_RANGE_MASK) & 0x0000ffff); + if ((d->bridge_flags[2] & PCI_IORESOURCE_MEM_64) || + d->bridge_base_addr[2] + d->bridge_size[2] - 1 > 0xffffffff) + val |= (PCI_PREF_RANGE_TYPE_64 << 16) | PCI_PREF_RANGE_TYPE_64; + else + val |= (PCI_PREF_RANGE_TYPE_32 << 16) | PCI_PREF_RANGE_TYPE_32; + } + else + val = 0xffff & PCI_PREF_RANGE_MASK; + break; + case PCI_PREF_BASE_UPPER32: + if (d->bridge_size[2]) + val = d->bridge_base_addr[2] >> 32; + break; + case PCI_PREF_LIMIT_UPPER32: + if (d->bridge_size[2]) + val = (d->bridge_base_addr[2] + d->bridge_size[2] - 1) >> 32; + break; + case PCI_IO_BASE_UPPER16: + if (d->bridge_size[0]) + val = ((((d->bridge_base_addr[0] + d->bridge_size[0] - 1) >> 16) << 16) & 0xffff0000) | + ((d->bridge_base_addr[0] >> 16) & 0x0000ffff); + break; + case PCI_ROM_ADDRESS1: + val = d->rom_base_addr & PCI_ROM_ADDRESS_MASK; + if (val) + val |= PCI_ROM_ADDRESS_ENABLE; + break; + } + else if (ht == PCI_HEADER_TYPE_CARDBUS) + switch (pos & ~3) + { + case PCI_COMMAND: + if (d->bridge_size[0] || d->bridge_size[1]) + val |= PCI_COMMAND_MEMORY; + if (d->bridge_size[2] || d->bridge_size[3]) + val |= PCI_COMMAND_IO; + break; + case PCI_CB_PRIMARY_BUS: + val = d->bus; + break; + case PCI_CB_MEMORY_BASE_0: + if (d->bridge_size[0]) + val = d->bridge_base_addr[0] & ~0xfff; + else + val = 0xffffffff & ~0xfff; + break; + case PCI_CB_MEMORY_LIMIT_0: + if (d->bridge_size[0]) + val = (d->bridge_base_addr[0] + d->bridge_size[0] - 1) & ~0xfff; + break; + case PCI_CB_MEMORY_BASE_1: + if (d->bridge_size[1]) + val = d->bridge_base_addr[1] & ~0xfff; + else + val = 0xffffffff & ~0xfff; + break; + case PCI_CB_MEMORY_LIMIT_1: + if (d->bridge_size[1]) + val = (d->bridge_base_addr[1] + d->bridge_size[1] - 1) & ~0xfff; + break; + case PCI_CB_IO_BASE_0: + if (d->bridge_size[2]) + { + val = d->bridge_base_addr[2] & PCI_CB_IO_RANGE_MASK; + if ((d->bridge_flags[2] & PCI_IORESOURCE_IO_16BIT_ADDR) || + d->bridge_base_addr[2] + d->bridge_size[2] - 1 <= 0xffff) + val |= PCI_IO_RANGE_TYPE_16; + else + val |= PCI_IO_RANGE_TYPE_32; + } + else + val = 0x0000ffff & PCI_CB_IO_RANGE_MASK; + break; + case PCI_CB_IO_LIMIT_0: + if (d->bridge_size[2]) + val = (d->bridge_base_addr[2] + d->bridge_size[2] - 1) & PCI_CB_IO_RANGE_MASK; + break; + case PCI_CB_IO_BASE_1: + if (d->bridge_size[3]) + { + val = d->bridge_base_addr[3] & PCI_CB_IO_RANGE_MASK; + if ((d->bridge_flags[3] & PCI_IORESOURCE_IO_16BIT_ADDR) || + d->bridge_base_addr[3] + d->bridge_size[3] - 1 <= 0xffff) + val |= PCI_IO_RANGE_TYPE_16; + else + val |= PCI_IO_RANGE_TYPE_32; + } + else + val = 0x0000ffff & PCI_CB_IO_RANGE_MASK; + break; + case PCI_CB_IO_LIMIT_1: + if (d->bridge_size[3]) + val = (d->bridge_base_addr[3] + d->bridge_size[3] - 1) & PCI_CB_IO_RANGE_MASK; + break; + case PCI_CB_BRIDGE_CONTROL: + if (d->bridge_flags[0] & PCI_IORESOURCE_PREFETCH) + val |= PCI_CB_BRIDGE_CTL_PREFETCH_MEM0; + if (d->bridge_flags[1] & PCI_IORESOURCE_PREFETCH) + val |= PCI_CB_BRIDGE_CTL_PREFETCH_MEM1; + break; + case PCI_CB_SUBSYSTEM_VENDOR_ID: + val = (d->subsys_id << 16) | d->subsys_vendor_id; + break; + } + + if (len <= 2) + val = (val >> (8 * (pos & 3))) & ((1 << (len * 8)) - 1); + + while (len-- > 0) + { + *(buf++) = val & 0xff; + val >>= 8; + } + return 1; +} diff --git a/lib/fbsd-device.c b/lib/fbsd-device.c new file mode 100644 index 0000000..2ea5e84 --- /dev/null +++ b/lib/fbsd-device.c @@ -0,0 +1,366 @@ +/* + * The PCI Library -- FreeBSD /dev/pci access + * + * 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 v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <osreldate.h> +#include <stdint.h> + +#ifdef __FreeBSD_kernel_version +# ifndef __FreeBSD_version +# define __FreeBSD_version __FreeBSD_kernel_version +# endif +#endif + +#if __FreeBSD_version < 430000 && !defined(__DragonFly__) +# include <pci/pcivar.h> +# include <pci/pci_ioctl.h> +#else +# include <sys/pciio.h> +#endif + +#include "internal.h" + +static void +fbsd_config(struct pci_access *a) +{ + pci_define_param(a, "fbsd.path", PCI_PATH_FBSD_DEVICE, "Path to the FreeBSD PCI device"); +} + +static int +fbsd_detect(struct pci_access *a) +{ + char *name = pci_get_param(a, "fbsd.path"); + + if (access(name, R_OK)) + { + a->warning("Cannot open %s", name); + return 0; + } + a->debug("...using %s", name); + return 1; +} + +static void +fbsd_init(struct pci_access *a) +{ + char *name = pci_get_param(a, "fbsd.path"); + int fd; + + 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) +{ + 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->func = matches[i].pc_sel.pc_func; + 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 void +fbsd_fill_info(struct pci_dev *d, unsigned 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) + d->access->error("fbsd_fill_info: ioctl(PCIOCGETCONF) failed: %s", strerror(errno)); + return; + } + + if (want_fill(d, flags, PCI_FILL_IDENT)) + { + d->vendor_id = match.pc_vendor; + d->device_id = match.pc_device; + } + if (want_fill(d, flags, PCI_FILL_CLASS)) + d->device_class = (match.pc_class << 8) | match.pc_subclass; + if (want_fill(d, 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; + 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; + } + } + } +} + +static int +fbsd_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + struct pci_io pi; + + if (d->access->fd_rw < 0) + { + d->access->warning("fbsd_read: missing permissions"); + return 0; + } + + if (!(len == 1 || len == 2 || len == 4)) + return pci_generic_block_read(d, pos, buf, len); + + if (pos >= 4096) + return 0; + +#if __FreeBSD_version >= 700053 || defined(__DragonFly__) + pi.pi_sel.pc_domain = d->domain; +#else + if (d->domain) + return 0; +#endif + pi.pi_sel.pc_bus = d->bus; + pi.pi_sel.pc_dev = d->dev; + pi.pi_sel.pc_func = d->func; + + pi.pi_reg = pos; + pi.pi_width = len; + + if (ioctl(d->access->fd_rw, PCIOCREAD, &pi) < 0) + { + if (errno == ENODEV) + return 0; + d->access->error("fbsd_read: ioctl(PCIOCREAD) failed: %s", strerror(errno)); + } + + switch (len) + { + case 1: + buf[0] = (u8) pi.pi_data; + break; + case 2: + ((u16 *) buf)[0] = cpu_to_le16((u16) pi.pi_data); + break; + case 4: + ((u32 *) buf)[0] = cpu_to_le32((u32) pi.pi_data); + break; + } + return 1; +} + +static int +fbsd_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + struct pci_io pi; + + if (d->access->fd_rw < 0) + { + d->access->warning("fbsd_write: missing permissions"); + return 0; + } + + if (!(len == 1 || len == 2 || len == 4)) + return pci_generic_block_write(d, pos, buf, len); + + if (pos >= 4096) + return 0; + +#if __FreeBSD_version >= 700053 || defined(__DragonFly__) + pi.pi_sel.pc_domain = d->domain; +#else + if (d->domain) + return 0; +#endif + pi.pi_sel.pc_bus = d->bus; + pi.pi_sel.pc_dev = d->dev; + pi.pi_sel.pc_func = d->func; + + pi.pi_reg = pos; + pi.pi_width = len; + + switch (len) + { + case 1: + pi.pi_data = buf[0]; + break; + case 2: + pi.pi_data = le16_to_cpu(((u16 *) buf)[0]); + break; + case 4: + pi.pi_data = le32_to_cpu(((u32 *) buf)[0]); + break; + } + + if (ioctl(d->access->fd_rw, PCIOCWRITE, &pi) < 0) + { + if (errno == ENODEV) + return 0; + d->access->error("fbsd_write: ioctl(PCIOCWRITE) failed: %s", strerror(errno)); + } + + return 1; +} + +struct pci_methods pm_fbsd_device = { + "fbsd-device", + "FreeBSD /dev/pci device", + fbsd_config, + fbsd_detect, + fbsd_init, + fbsd_cleanup, + fbsd_scan, + fbsd_fill_info, + fbsd_read, + fbsd_write, + NULL, /* read_vpd */ + NULL, /* dev_init */ + NULL /* dev_cleanup */ +}; diff --git a/lib/filter.c b/lib/filter.c new file mode 100644 index 0000000..0301f49 --- /dev/null +++ b/lib/filter.c @@ -0,0 +1,334 @@ +/* + * The PCI Library -- Device Filtering + * + * Copyright (c) 1998--2022 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdlib.h> +#include <string.h> + +#include "internal.h" + +void pci_filter_init_v38(struct pci_access *a UNUSED, struct pci_filter *f) VERSIONED_ABI; +char *pci_filter_parse_slot_v38(struct pci_filter *f, char *str) VERSIONED_ABI; +char *pci_filter_parse_id_v38(struct pci_filter *f, char *str) VERSIONED_ABI; +int pci_filter_match_v38(struct pci_filter *f, struct pci_dev *d) VERSIONED_ABI; + +void +pci_filter_init_v38(struct pci_access *a UNUSED, struct pci_filter *f) +{ + memset((byte *) f, 0, sizeof(*f)); + f->domain = f->bus = f->slot = f->func = -1; + f->vendor = f->device = -1; + f->device_class = -1; + f->device_class_mask = ~0U; + f->prog_if = -1; +} + +#define BUF_SIZE 64 + +static char * +split_to_fields(char *str, char *buffer, int sep, char **fields, int num_fields) +{ + if (buffer) + { + if (strlen(str) >= BUF_SIZE) + return "Expression too long"; + strcpy(buffer, str); + str = buffer; + } + + int i = 0; + + for (;;) + { + if (i >= num_fields) + return "Too many fields"; + fields[i++] = str; + while (*str && *str != sep) + str++; + if (!*str) + break; + *str++ = 0; + } + + while (i < num_fields) + fields[i++] = NULL; + + return NULL; +} + +static int +field_defined(char *field) +{ + return field && field[0] && strcmp(field, "*"); +} + +static int +parse_hex_field(char *str, int *outp, unsigned int *maskp, unsigned int max) +{ + unsigned int out = 0; + unsigned int mask = ~0U; + unsigned int bound = 0; + + if (!field_defined(str)) + return 1; // and keep the defaults + + // Historically, filters allowed writing hexadecimal numbers with leading "0x". + // This was never intentional nor documented, but some people relied on it. + if (!maskp && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) + str += 2; + + while (*str) + { + int c = *str++; + int d; + + if ((c == 'x' || c == 'X') && maskp) + { + out = out << 4; + bound = (bound << 4) | 1; + mask = mask << 4; + } + else + { + if (c >= '0' && c <= '9') + d = c - '0'; + else if (c >= 'A' && c <= 'F') + d = c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + d = c - 'a' + 10; + else + return 0; + + out = (out << 4) | d; + bound = (bound << 4) | d; + mask = (mask << 4) | 0xf; + } + + if (bound > max) + return 0; + } + + *outp = out; + if (maskp) + *maskp = mask; + return 1; +} + +/* Slot filter syntax: [[[domain]:][bus]:][slot][.[func]] */ + +char * +pci_filter_parse_slot_v38(struct pci_filter *f, char *str) +{ + char buf[BUF_SIZE]; + char *fields[3]; + char *err; + + if (err = split_to_fields(str, buf, ':', fields, 3)) + return err; + + int i = 0; + if (fields[2]) + { + if (!parse_hex_field(fields[0], &f->domain, NULL, 0x7fffffff)) + return "Invalid domain number"; + i++; + } + + if (fields[i+1]) + { + if (!parse_hex_field(fields[i], &f->bus, NULL, 0xff)) + return "Invalid bus number"; + i++; + } + + char *fdev = fields[i]; + if (field_defined(fdev)) + { + char *sfields[2]; + if (split_to_fields(fdev, NULL, '.', sfields, 2)) + return "Invalid slot/function number"; + + if (!parse_hex_field(sfields[0], &f->slot, NULL, 0x1f)) + return "Invalid slot number"; + + if (!parse_hex_field(sfields[1], &f->func, NULL, 7)) + return "Invalid function number"; + } + + return NULL; +} + +/* ID filter syntax: [vendor]:[device][:class[:progif]] */ + +char * +pci_filter_parse_id_v38(struct pci_filter *f, char *str) +{ + char buf[BUF_SIZE]; + char *fields[4]; + char *err; + + if (err = split_to_fields(str, buf, ':', fields, 4)) + return err; + + if (!fields[1]) + return "At least two fields must be given"; + + if (!parse_hex_field(fields[0], &f->vendor, NULL, 0xffff)) + return "Invalid vendor ID"; + + if (!parse_hex_field(fields[1], &f->device, NULL, 0xffff)) + return "Invalid device ID"; + + if (!parse_hex_field(fields[2], &f->device_class, &f->device_class_mask, 0xffff)) + return "Invalid class code"; + + if (!parse_hex_field(fields[3], &f->prog_if, NULL, 0xff)) + return "Invalid programming interface code"; + + return NULL; +} + +int +pci_filter_match_v38(struct pci_filter *f, struct pci_dev *d) +{ + if ((f->domain >= 0 && f->domain != d->domain) || + (f->bus >= 0 && f->bus != d->bus) || + (f->slot >= 0 && f->slot != d->dev) || + (f->func >= 0 && f->func != d->func)) + return 0; + if (f->device >= 0 || f->vendor >= 0) + { + pci_fill_info_v38(d, PCI_FILL_IDENT); + if ((f->device >= 0 && f->device != d->device_id) || + (f->vendor >= 0 && f->vendor != d->vendor_id)) + return 0; + } + if (f->device_class >= 0) + { + pci_fill_info_v38(d, PCI_FILL_CLASS); + if ((f->device_class ^ d->device_class) & f->device_class_mask) + return 0; + } + if (f->prog_if >= 0) + { + pci_fill_info_v38(d, PCI_FILL_CLASS_EXT); + if (f->prog_if != d->prog_if) + return 0; + } + return 1; +} + +/* + * Before pciutils v3.3, struct pci_filter had fewer fields, + * so we have to provide compatibility wrappers. + */ + +struct pci_filter_v30 { + int domain, bus, slot, func; /* -1 = ANY */ + int vendor, device; +}; + +void pci_filter_init_v30(struct pci_access *a, struct pci_filter_v30 *f) VERSIONED_ABI; +char *pci_filter_parse_slot_v30(struct pci_filter_v30 *f, char *str) VERSIONED_ABI; +char *pci_filter_parse_id_v30(struct pci_filter_v30 *f, char *str) VERSIONED_ABI; +int pci_filter_match_v30(struct pci_filter_v30 *f, struct pci_dev *d) VERSIONED_ABI; + +static void +pci_filter_import_v30(struct pci_filter_v30 *old, struct pci_filter *new) +{ + new->domain = old->domain; + new->bus = old->bus; + new->slot = old->slot; + new->func = old->func; + new->vendor = old->vendor; + new->device = old->device; + new->device_class = -1; + new->device_class_mask = ~0U; + new->prog_if = -1; +} + +static void +pci_filter_export_v30(struct pci_filter *new, struct pci_filter_v30 *old) +{ + old->domain = new->domain; + old->bus = new->bus; + old->slot = new->slot; + old->func = new->func; + old->vendor = new->vendor; + old->device = new->device; +} + +void +pci_filter_init_v30(struct pci_access *a, struct pci_filter_v30 *f) +{ + struct pci_filter new; + pci_filter_init_v38(a, &new); + pci_filter_export_v30(&new, f); +} + +char * +pci_filter_parse_slot_v30(struct pci_filter_v30 *f, char *str) +{ + struct pci_filter new; + char *err; + pci_filter_import_v30(f, &new); + if (err = pci_filter_parse_slot_v38(&new, str)) + return err; + pci_filter_export_v30(&new, f); + return NULL; +} + +char * +pci_filter_parse_id_v30(struct pci_filter_v30 *f, char *str) +{ + struct pci_filter new; + char *err; + pci_filter_import_v30(f, &new); + if (err = pci_filter_parse_id_v38(&new, str)) + return err; + if (new.device_class >= 0 || new.prog_if >= 0) + return "Filtering by class or programming interface not supported in this program"; + pci_filter_export_v30(&new, f); + return NULL; +} + +int +pci_filter_match_v30(struct pci_filter_v30 *f, struct pci_dev *d) +{ + struct pci_filter new; + pci_filter_import_v30(f, &new); + return pci_filter_match_v38(&new, d); +} + +// Version 3.3 is the same as version 3.8, only device_class_mask and prog_if were not implemented +// (their positions in struct pci_filter were declared as RFU). + +STATIC_ALIAS(void pci_filter_init(struct pci_access *a, struct pci_filter *f), pci_filter_init_v38(a, f)); +DEFINE_ALIAS(void pci_filter_init_v33(struct pci_access *a, struct pci_filter *f), pci_filter_init_v38); +SYMBOL_VERSION(pci_filter_init_v30, pci_filter_init@LIBPCI_3.0); +SYMBOL_VERSION(pci_filter_init_v33, pci_filter_init@LIBPCI_3.3); +SYMBOL_VERSION(pci_filter_init_v38, pci_filter_init@@LIBPCI_3.8); + +STATIC_ALIAS(char *pci_filter_parse_slot(struct pci_filter *f, char *str), pci_filter_parse_slot_v38(f, str)); +DEFINE_ALIAS(char *pci_filter_parse_slot_v33(struct pci_filter *f, char *str), pci_filter_parse_slot_v38); +SYMBOL_VERSION(pci_filter_parse_slot_v30, pci_filter_parse_slot@LIBPCI_3.0); +SYMBOL_VERSION(pci_filter_parse_slot_v33, pci_filter_parse_slot@LIBPCI_3.3); +SYMBOL_VERSION(pci_filter_parse_slot_v38, pci_filter_parse_slot@@LIBPCI_3.8); + +STATIC_ALIAS(char *pci_filter_parse_id(struct pci_filter *f, char *str), pci_filter_parse_id_v38(f, str)); +DEFINE_ALIAS(char *pci_filter_parse_id_v33(struct pci_filter *f, char *str), pci_filter_parse_id_v38); +SYMBOL_VERSION(pci_filter_parse_id_v30, pci_filter_parse_id@LIBPCI_3.0); +SYMBOL_VERSION(pci_filter_parse_id_v33, pci_filter_parse_id@LIBPCI_3.3); +SYMBOL_VERSION(pci_filter_parse_id_v38, pci_filter_parse_id@@LIBPCI_3.8); + +STATIC_ALIAS(int pci_filter_match(struct pci_filter *f, struct pci_dev *d), pci_filter_match_v38(f, d)); +DEFINE_ALIAS(int pci_filter_match_v33(struct pci_filter *f, struct pci_dev *d), pci_filter_match_v38); +SYMBOL_VERSION(pci_filter_match_v30, pci_filter_match@LIBPCI_3.0); +SYMBOL_VERSION(pci_filter_match_v33, pci_filter_match@LIBPCI_3.3); +SYMBOL_VERSION(pci_filter_match_v38, pci_filter_match@@LIBPCI_3.8); diff --git a/lib/generic.c b/lib/generic.c new file mode 100644 index 0000000..f7340a2 --- /dev/null +++ b/lib/generic.c @@ -0,0 +1,259 @@ +/* + * The PCI Library -- Generic Direct Access Functions + * + * Copyright (c) 1997--2022 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <string.h> + +#include "internal.h" + +void +pci_generic_scan_bus(struct pci_access *a, byte *busmap, int domain, int bus) +{ + int dev, multi, ht; + struct pci_dev *t; + + a->debug("Scanning bus %02x for devices...\n", bus); + if (busmap[bus]) + { + a->warning("Bus %02x seen twice (firmware bug). Ignored.", bus); + return; + } + busmap[bus] = 1; + t = pci_alloc_dev(a); + t->domain = domain; + t->bus = bus; + for (dev=0; dev<32; dev++) + { + t->dev = dev; + multi = 0; + for (t->func=0; !t->func || multi && t->func<8; t->func++) + { + u32 vd = pci_read_long(t, PCI_VENDOR_ID); + struct pci_dev *d; + + if (!vd || vd == 0xffffffff) + continue; + ht = pci_read_byte(t, PCI_HEADER_TYPE); + if (!t->func) + multi = ht & 0x80; + ht &= 0x7f; + d = pci_alloc_dev(a); + d->domain = t->domain; + d->bus = t->bus; + d->dev = t->dev; + d->func = t->func; + d->vendor_id = vd & 0xffff; + d->device_id = vd >> 16U; + d->known_fields = PCI_FILL_IDENT; + d->hdrtype = ht; + pci_link_dev(a, d); + switch (ht) + { + case PCI_HEADER_TYPE_NORMAL: + break; + case PCI_HEADER_TYPE_BRIDGE: + case PCI_HEADER_TYPE_CARDBUS: + pci_generic_scan_bus(a, busmap, domain, pci_read_byte(t, PCI_SECONDARY_BUS)); + break; + default: + a->debug("Device %04x:%02x:%02x.%d has unknown header type %02x.\n", d->domain, d->bus, d->dev, d->func, ht); + } + } + } + pci_free_dev(t); +} + +void +pci_generic_scan_domain(struct pci_access *a, int domain) +{ + byte busmap[256]; + + memset(busmap, 0, sizeof(busmap)); + pci_generic_scan_bus(a, busmap, domain, 0); +} + +void +pci_generic_scan(struct pci_access *a) +{ + pci_generic_scan_domain(a, 0); +} + +static int +get_hdr_type(struct pci_dev *d) +{ + if (d->hdrtype < 0) + d->hdrtype = pci_read_byte(d, PCI_HEADER_TYPE) & 0x7f; + return d->hdrtype; +} + +void +pci_generic_fill_info(struct pci_dev *d, unsigned int flags) +{ + struct pci_access *a = d->access; + struct pci_cap *cap; + + if (want_fill(d, flags, PCI_FILL_IDENT)) + { + d->vendor_id = pci_read_word(d, PCI_VENDOR_ID); + d->device_id = pci_read_word(d, PCI_DEVICE_ID); + } + + if (want_fill(d, flags, PCI_FILL_CLASS)) + d->device_class = pci_read_word(d, PCI_CLASS_DEVICE); + + if (want_fill(d, flags, PCI_FILL_CLASS_EXT)) + { + d->prog_if = pci_read_byte(d, PCI_CLASS_PROG); + d->rev_id = pci_read_byte(d, PCI_REVISION_ID); + } + + if (want_fill(d, flags, PCI_FILL_SUBSYS)) + { + switch (get_hdr_type(d)) + { + case PCI_HEADER_TYPE_NORMAL: + d->subsys_vendor_id = pci_read_word(d, PCI_SUBSYSTEM_VENDOR_ID); + d->subsys_id = pci_read_word(d, PCI_SUBSYSTEM_ID); + break; + case PCI_HEADER_TYPE_BRIDGE: + cap = pci_find_cap(d, PCI_CAP_ID_SSVID, PCI_CAP_NORMAL); + if (cap) + { + d->subsys_vendor_id = pci_read_word(d, cap->addr + PCI_SSVID_VENDOR); + d->subsys_id = pci_read_word(d, cap->addr + PCI_SSVID_DEVICE); + } + break; + case PCI_HEADER_TYPE_CARDBUS: + d->subsys_vendor_id = pci_read_word(d, PCI_CB_SUBSYSTEM_VENDOR_ID); + d->subsys_id = pci_read_word(d, PCI_CB_SUBSYSTEM_ID); + break; + default: + clear_fill(d, PCI_FILL_SUBSYS); + } + } + + if (want_fill(d, flags, PCI_FILL_IRQ)) + d->irq = pci_read_byte(d, PCI_INTERRUPT_LINE); + + if (want_fill(d, flags, PCI_FILL_BASES)) + { + int cnt = 0, i; + memset(d->base_addr, 0, sizeof(d->base_addr)); + switch (get_hdr_type(d)) + { + case PCI_HEADER_TYPE_NORMAL: + cnt = 6; + break; + case PCI_HEADER_TYPE_BRIDGE: + cnt = 2; + break; + case PCI_HEADER_TYPE_CARDBUS: + cnt = 1; + break; + } + if (cnt) + { + for (i=0; i<cnt; i++) + { + u32 x = pci_read_long(d, PCI_BASE_ADDRESS_0 + i*4); + if (!x || x == (u32) ~0) + continue; + if ((x & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) + d->base_addr[i] = x; + else + { + if ((x & PCI_BASE_ADDRESS_MEM_TYPE_MASK) != PCI_BASE_ADDRESS_MEM_TYPE_64) + d->base_addr[i] = x; + else if (i >= cnt-1) + a->warning("%04x:%02x:%02x.%d: Invalid 64-bit address seen for BAR %d.", d->domain, d->bus, d->dev, d->func, i); + else + { + u32 y = pci_read_long(d, PCI_BASE_ADDRESS_0 + (++i)*4); +#ifdef PCI_HAVE_64BIT_ADDRESS + d->base_addr[i-1] = x | (((pciaddr_t) y) << 32); +#else + if (y) + a->warning("%04x:%02x:%02x.%d 64-bit device address ignored.", d->domain, d->bus, d->dev, d->func); + else + d->base_addr[i-1] = x; +#endif + } + } + } + } + } + + if (want_fill(d, flags, PCI_FILL_ROM_BASE)) + { + int reg = 0; + d->rom_base_addr = 0; + switch (get_hdr_type(d)) + { + case PCI_HEADER_TYPE_NORMAL: + reg = PCI_ROM_ADDRESS; + break; + case PCI_HEADER_TYPE_BRIDGE: + reg = PCI_ROM_ADDRESS1; + break; + } + if (reg) + { + u32 u = pci_read_long(d, reg); + if (u != 0xffffffff) + d->rom_base_addr = u; + } + } + + pci_scan_caps(d, flags); +} + +static int +pci_generic_block_op(struct pci_dev *d, int pos, byte *buf, int len, + int (*r)(struct pci_dev *d, int pos, byte *buf, int len)) +{ + if ((pos & 1) && len >= 1) + { + if (!r(d, pos, buf, 1)) + return 0; + pos++; buf++; len--; + } + if ((pos & 3) && len >= 2) + { + if (!r(d, pos, buf, 2)) + return 0; + pos += 2; buf += 2; len -= 2; + } + while (len >= 4) + { + if (!r(d, pos, buf, 4)) + return 0; + pos += 4; buf += 4; len -= 4; + } + if (len >= 2) + { + if (!r(d, pos, buf, 2)) + return 0; + pos += 2; buf += 2; len -= 2; + } + if (len && !r(d, pos, buf, 1)) + return 0; + return 1; +} + +int +pci_generic_block_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + return pci_generic_block_op(d, pos, buf, len, d->access->methods->read); +} + +int +pci_generic_block_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + return pci_generic_block_op(d, pos, buf, len, d->access->methods->write); +} diff --git a/lib/header.h b/lib/header.h new file mode 100644 index 0000000..2cee94f --- /dev/null +++ b/lib/header.h @@ -0,0 +1,1579 @@ +/* + * The PCI Library -- PCI Header Structure (based on <linux/pci.h>) + * + * Copyright (c) 1997--2010 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Under PCI, each device has 256 bytes of configuration address space, + * of which the first 64 bytes are standardized as follows: + */ +#define PCI_VENDOR_ID 0x00 /* 16 bits */ +#define PCI_DEVICE_ID 0x02 /* 16 bits */ +#define PCI_COMMAND 0x04 /* 16 bits */ +#define PCI_COMMAND_IO 0x1 /* Enable response in I/O space */ +#define PCI_COMMAND_MEMORY 0x2 /* Enable response in Memory space */ +#define PCI_COMMAND_MASTER 0x4 /* Enable bus mastering */ +#define PCI_COMMAND_SPECIAL 0x8 /* Enable response to special cycles */ +#define PCI_COMMAND_INVALIDATE 0x10 /* Use memory write and invalidate */ +#define PCI_COMMAND_VGA_PALETTE 0x20 /* Enable palette snooping */ +#define PCI_COMMAND_PARITY 0x40 /* Enable parity checking */ +#define PCI_COMMAND_WAIT 0x80 /* Enable address/data stepping */ +#define PCI_COMMAND_SERR 0x100 /* Enable SERR */ +#define PCI_COMMAND_FAST_BACK 0x200 /* Enable back-to-back writes */ +#define PCI_COMMAND_DISABLE_INTx 0x400 /* PCIE: Disable INTx interrupts */ + +#define PCI_STATUS 0x06 /* 16 bits */ +#define PCI_STATUS_INTx 0x08 /* PCIE: INTx interrupt pending */ +#define PCI_STATUS_CAP_LIST 0x10 /* Support Capability List */ +#define PCI_STATUS_66MHZ 0x20 /* Support 66 Mhz PCI 2.1 bus */ +#define PCI_STATUS_UDF 0x40 /* Support User Definable Features [obsolete] */ +#define PCI_STATUS_FAST_BACK 0x80 /* Accept fast-back to back */ +#define PCI_STATUS_PARITY 0x100 /* Detected parity error */ +#define PCI_STATUS_DEVSEL_MASK 0x600 /* DEVSEL timing */ +#define PCI_STATUS_DEVSEL_FAST 0x000 +#define PCI_STATUS_DEVSEL_MEDIUM 0x200 +#define PCI_STATUS_DEVSEL_SLOW 0x400 +#define PCI_STATUS_SIG_TARGET_ABORT 0x800 /* Set on target abort */ +#define PCI_STATUS_REC_TARGET_ABORT 0x1000 /* Master ack of " */ +#define PCI_STATUS_REC_MASTER_ABORT 0x2000 /* Set on master abort */ +#define PCI_STATUS_SIG_SYSTEM_ERROR 0x4000 /* Set when we drive SERR */ +#define PCI_STATUS_DETECTED_PARITY 0x8000 /* Set on parity error */ + +#define PCI_CLASS_REVISION 0x08 /* High 24 bits are class, low 8 + revision */ +#define PCI_REVISION_ID 0x08 /* Revision ID */ +#define PCI_CLASS_PROG 0x09 /* Reg. Level Programming Interface */ +#define PCI_CLASS_DEVICE 0x0a /* Device class */ + +#define PCI_CACHE_LINE_SIZE 0x0c /* 8 bits */ +#define PCI_LATENCY_TIMER 0x0d /* 8 bits */ +#define PCI_HEADER_TYPE 0x0e /* 8 bits */ +#define PCI_HEADER_TYPE_NORMAL 0 +#define PCI_HEADER_TYPE_BRIDGE 1 +#define PCI_HEADER_TYPE_CARDBUS 2 + +#define PCI_BIST 0x0f /* 8 bits */ +#define PCI_BIST_CODE_MASK 0x0f /* Return result */ +#define PCI_BIST_START 0x40 /* 1 to start BIST, 2 secs or less */ +#define PCI_BIST_CAPABLE 0x80 /* 1 if BIST capable */ + +/* + * Base addresses specify locations in memory or I/O space. + * Decoded size can be determined by writing a value of + * 0xffffffff to the register, and reading it back. Only + * 1 bits are decoded. + */ +#define PCI_BASE_ADDRESS_0 0x10 /* 32 bits */ +#define PCI_BASE_ADDRESS_1 0x14 /* 32 bits [htype 0,1 only] */ +#define PCI_BASE_ADDRESS_2 0x18 /* 32 bits [htype 0 only] */ +#define PCI_BASE_ADDRESS_3 0x1c /* 32 bits */ +#define PCI_BASE_ADDRESS_4 0x20 /* 32 bits */ +#define PCI_BASE_ADDRESS_5 0x24 /* 32 bits */ +#define PCI_BASE_ADDRESS_SPACE 0x01 /* 0 = memory, 1 = I/O */ +#define PCI_BASE_ADDRESS_SPACE_IO 0x01 +#define PCI_BASE_ADDRESS_SPACE_MEMORY 0x00 +#define PCI_BASE_ADDRESS_MEM_TYPE_MASK 0x06 +#define PCI_BASE_ADDRESS_MEM_TYPE_32 0x00 /* 32 bit address */ +#define PCI_BASE_ADDRESS_MEM_TYPE_1M 0x02 /* Below 1M [obsolete] */ +#define PCI_BASE_ADDRESS_MEM_TYPE_64 0x04 /* 64 bit address */ +#define PCI_BASE_ADDRESS_MEM_PREFETCH 0x08 /* prefetchable? */ +#define PCI_BASE_ADDRESS_MEM_MASK (~(pciaddr_t)0x0f) +#define PCI_BASE_ADDRESS_IO_MASK (~(pciaddr_t)0x03) +/* bit 1 is reserved if address_space = 1 */ + +/* Header type 0 (normal devices) */ +#define PCI_CARDBUS_CIS 0x28 +#define PCI_SUBSYSTEM_VENDOR_ID 0x2c +#define PCI_SUBSYSTEM_ID 0x2e +#define PCI_ROM_ADDRESS 0x30 /* Bits 31..11 are address, 10..1 reserved */ +#define PCI_ROM_ADDRESS_ENABLE 0x01 +#define PCI_ROM_ADDRESS_MASK (~(pciaddr_t)0x7ff) + +#define PCI_CAPABILITY_LIST 0x34 /* Offset of first capability list entry */ + +/* 0x35-0x3b are reserved */ +#define PCI_INTERRUPT_LINE 0x3c /* 8 bits */ +#define PCI_INTERRUPT_PIN 0x3d /* 8 bits */ +#define PCI_MIN_GNT 0x3e /* 8 bits */ +#define PCI_MAX_LAT 0x3f /* 8 bits */ + +/* Header type 1 (PCI-to-PCI bridges) */ +#define PCI_PRIMARY_BUS 0x18 /* Primary bus number */ +#define PCI_SECONDARY_BUS 0x19 /* Secondary bus number */ +#define PCI_SUBORDINATE_BUS 0x1a /* Highest bus number behind the bridge */ +#define PCI_SEC_LATENCY_TIMER 0x1b /* Latency timer for secondary interface */ +#define PCI_IO_BASE 0x1c /* I/O range behind the bridge */ +#define PCI_IO_LIMIT 0x1d +#define PCI_IO_RANGE_TYPE_MASK 0x0f /* I/O bridging type */ +#define PCI_IO_RANGE_TYPE_16 0x00 +#define PCI_IO_RANGE_TYPE_32 0x01 +#define PCI_IO_RANGE_MASK ~0x0f +#define PCI_SEC_STATUS 0x1e /* Secondary status register */ +#define PCI_MEMORY_BASE 0x20 /* Memory range behind */ +#define PCI_MEMORY_LIMIT 0x22 +#define PCI_MEMORY_RANGE_TYPE_MASK 0x0f +#define PCI_MEMORY_RANGE_MASK ~0x0f +#define PCI_PREF_MEMORY_BASE 0x24 /* Prefetchable memory range behind */ +#define PCI_PREF_MEMORY_LIMIT 0x26 +#define PCI_PREF_RANGE_TYPE_MASK 0x0f +#define PCI_PREF_RANGE_TYPE_32 0x00 +#define PCI_PREF_RANGE_TYPE_64 0x01 +#define PCI_PREF_RANGE_MASK ~0x0f +#define PCI_PREF_BASE_UPPER32 0x28 /* Upper half of prefetchable memory range */ +#define PCI_PREF_LIMIT_UPPER32 0x2c +#define PCI_IO_BASE_UPPER16 0x30 /* Upper half of I/O addresses */ +#define PCI_IO_LIMIT_UPPER16 0x32 +/* 0x34 same as for htype 0 */ +/* 0x35-0x3b is reserved */ +#define PCI_ROM_ADDRESS1 0x38 /* Same as PCI_ROM_ADDRESS, but for htype 1 */ +/* 0x3c-0x3d are same as for htype 0 */ +#define PCI_BRIDGE_CONTROL 0x3e +#define PCI_BRIDGE_CTL_PARITY 0x01 /* Enable parity detection on secondary interface */ +#define PCI_BRIDGE_CTL_SERR 0x02 /* The same for SERR forwarding */ +#define PCI_BRIDGE_CTL_NO_ISA 0x04 /* Disable bridging of ISA ports */ +#define PCI_BRIDGE_CTL_VGA 0x08 /* Forward VGA addresses */ +#define PCI_BRIDGE_CTL_VGA_16BIT 0x10 /* VGA 16-bit decode */ +#define PCI_BRIDGE_CTL_MASTER_ABORT 0x20 /* Report master aborts */ +#define PCI_BRIDGE_CTL_BUS_RESET 0x40 /* Secondary bus reset */ +#define PCI_BRIDGE_CTL_FAST_BACK 0x80 /* Fast Back2Back enabled on secondary interface */ +#define PCI_BRIDGE_CTL_PRI_DISCARD_TIMER 0x100 /* PCI-X? */ +#define PCI_BRIDGE_CTL_SEC_DISCARD_TIMER 0x200 /* PCI-X? */ +#define PCI_BRIDGE_CTL_DISCARD_TIMER_STATUS 0x400 /* PCI-X? */ +#define PCI_BRIDGE_CTL_DISCARD_TIMER_SERR_EN 0x800 /* PCI-X? */ + +/* Header type 2 (CardBus bridges) */ +#define PCI_CB_CAPABILITY_LIST 0x14 +/* 0x15 reserved */ +#define PCI_CB_SEC_STATUS 0x16 /* Secondary status */ +#define PCI_CB_PRIMARY_BUS 0x18 /* PCI bus number */ +#define PCI_CB_CARD_BUS 0x19 /* CardBus bus number */ +#define PCI_CB_SUBORDINATE_BUS 0x1a /* Subordinate bus number */ +#define PCI_CB_LATENCY_TIMER 0x1b /* CardBus latency timer */ +#define PCI_CB_MEMORY_BASE_0 0x1c +#define PCI_CB_MEMORY_LIMIT_0 0x20 +#define PCI_CB_MEMORY_BASE_1 0x24 +#define PCI_CB_MEMORY_LIMIT_1 0x28 +#define PCI_CB_IO_BASE_0 0x2c +#define PCI_CB_IO_BASE_0_HI 0x2e +#define PCI_CB_IO_LIMIT_0 0x30 +#define PCI_CB_IO_LIMIT_0_HI 0x32 +#define PCI_CB_IO_BASE_1 0x34 +#define PCI_CB_IO_BASE_1_HI 0x36 +#define PCI_CB_IO_LIMIT_1 0x38 +#define PCI_CB_IO_LIMIT_1_HI 0x3a +#define PCI_CB_IO_RANGE_MASK ~0x03 +/* 0x3c-0x3d are same as for htype 0 */ +#define PCI_CB_BRIDGE_CONTROL 0x3e +#define PCI_CB_BRIDGE_CTL_PARITY 0x01 /* Similar to standard bridge control register */ +#define PCI_CB_BRIDGE_CTL_SERR 0x02 +#define PCI_CB_BRIDGE_CTL_ISA 0x04 +#define PCI_CB_BRIDGE_CTL_VGA 0x08 +#define PCI_CB_BRIDGE_CTL_MASTER_ABORT 0x20 +#define PCI_CB_BRIDGE_CTL_CB_RESET 0x40 /* CardBus reset */ +#define PCI_CB_BRIDGE_CTL_16BIT_INT 0x80 /* Enable interrupt for 16-bit cards */ +#define PCI_CB_BRIDGE_CTL_PREFETCH_MEM0 0x100 /* Prefetch enable for both memory regions */ +#define PCI_CB_BRIDGE_CTL_PREFETCH_MEM1 0x200 +#define PCI_CB_BRIDGE_CTL_POST_WRITES 0x400 +#define PCI_CB_SUBSYSTEM_VENDOR_ID 0x40 +#define PCI_CB_SUBSYSTEM_ID 0x42 +#define PCI_CB_LEGACY_MODE_BASE 0x44 /* 16-bit PC Card legacy mode base address (ExCa) */ +/* 0x48-0x7f reserved */ + +/* Capability lists */ + +#define PCI_CAP_LIST_ID 0 /* Capability ID */ +#define PCI_CAP_ID_NULL 0x00 /* Null Capability */ +#define PCI_CAP_ID_PM 0x01 /* Power Management */ +#define PCI_CAP_ID_AGP 0x02 /* Accelerated Graphics Port */ +#define PCI_CAP_ID_VPD 0x03 /* Vital Product Data */ +#define PCI_CAP_ID_SLOTID 0x04 /* Slot Identification */ +#define PCI_CAP_ID_MSI 0x05 /* Message Signaled Interrupts */ +#define PCI_CAP_ID_CHSWP 0x06 /* CompactPCI HotSwap */ +#define PCI_CAP_ID_PCIX 0x07 /* PCI-X */ +#define PCI_CAP_ID_HT 0x08 /* HyperTransport */ +#define PCI_CAP_ID_VNDR 0x09 /* Vendor specific */ +#define PCI_CAP_ID_DBG 0x0A /* Debug port */ +#define PCI_CAP_ID_CCRC 0x0B /* CompactPCI Central Resource Control */ +#define PCI_CAP_ID_HOTPLUG 0x0C /* PCI hot-plug */ +#define PCI_CAP_ID_SSVID 0x0D /* Bridge subsystem vendor/device ID */ +#define PCI_CAP_ID_AGP3 0x0E /* AGP 8x */ +#define PCI_CAP_ID_SECURE 0x0F /* Secure device (?) */ +#define PCI_CAP_ID_EXP 0x10 /* PCI Express */ +#define PCI_CAP_ID_MSIX 0x11 /* MSI-X */ +#define PCI_CAP_ID_SATA 0x12 /* Serial-ATA HBA */ +#define PCI_CAP_ID_AF 0x13 /* Advanced features of PCI devices integrated in PCIe root cplx */ +#define PCI_CAP_ID_EA 0x14 /* Enhanced Allocation */ +#define PCI_CAP_LIST_NEXT 1 /* Next capability in the list */ +#define PCI_CAP_FLAGS 2 /* Capability defined flags (16 bits) */ +#define PCI_CAP_SIZEOF 4 + +/* Capabilities residing in the PCI Express extended configuration space */ + +#define PCI_EXT_CAP_ID_NULL 0x00 /* Null Capability */ +#define PCI_EXT_CAP_ID_AER 0x01 /* Advanced Error Reporting */ +#define PCI_EXT_CAP_ID_VC 0x02 /* Virtual Channel */ +#define PCI_EXT_CAP_ID_DSN 0x03 /* Device Serial Number */ +#define PCI_EXT_CAP_ID_PB 0x04 /* Power Budgeting */ +#define PCI_EXT_CAP_ID_RCLINK 0x05 /* Root Complex Link Declaration */ +#define PCI_EXT_CAP_ID_RCILINK 0x06 /* Root Complex Internal Link Declaration */ +#define PCI_EXT_CAP_ID_RCEC 0x07 /* Root Complex Event Collector */ +#define PCI_EXT_CAP_ID_MFVC 0x08 /* Multi-Function Virtual Channel */ +#define PCI_EXT_CAP_ID_VC2 0x09 /* Virtual Channel (2nd ID) */ +#define PCI_EXT_CAP_ID_RCRB 0x0a /* Root Complex Register Block */ +#define PCI_EXT_CAP_ID_VNDR 0x0b /* Vendor specific */ +#define PCI_EXT_CAP_ID_ACS 0x0d /* Access Controls */ +#define PCI_EXT_CAP_ID_ARI 0x0e /* Alternative Routing-ID Interpretation */ +#define PCI_EXT_CAP_ID_ATS 0x0f /* Address Translation Service */ +#define PCI_EXT_CAP_ID_SRIOV 0x10 /* Single Root I/O Virtualization */ +#define PCI_EXT_CAP_ID_MRIOV 0x11 /* Multi-Root I/O Virtualization */ +#define PCI_EXT_CAP_ID_MCAST 0x12 /* Multicast */ +#define PCI_EXT_CAP_ID_PRI 0x13 /* Page Request Interface */ +#define PCI_EXT_CAP_ID_REBAR 0x15 /* Resizable BAR */ +#define PCI_EXT_CAP_ID_DPA 0x16 /* Dynamic Power Allocation */ +#define PCI_EXT_CAP_ID_TPH 0x17 /* Transaction processing hints */ +#define PCI_EXT_CAP_ID_LTR 0x18 /* Latency Tolerance Reporting */ +#define PCI_EXT_CAP_ID_SECPCI 0x19 /* Secondary PCI Express */ +#define PCI_EXT_CAP_ID_PMUX 0x1a /* Protocol Multiplexing */ +#define PCI_EXT_CAP_ID_PASID 0x1b /* Process Address Space ID */ +#define PCI_EXT_CAP_ID_LNR 0x1c /* LN Requester */ +#define PCI_EXT_CAP_ID_DPC 0x1d /* Downstream Port Containment */ +#define PCI_EXT_CAP_ID_L1PM 0x1e /* L1 PM Substates */ +#define PCI_EXT_CAP_ID_PTM 0x1f /* Precision Time Measurement */ +#define PCI_EXT_CAP_ID_M_PCIE 0x20 /* PCIe over M-PHY */ +#define PCI_EXT_CAP_ID_FRS 0x21 /* FRS Queuing */ +#define PCI_EXT_CAP_ID_RTR 0x22 /* Readiness Time Reporting */ +#define PCI_EXT_CAP_ID_DVSEC 0x23 /* Designated Vendor-Specific */ +#define PCI_EXT_CAP_ID_VF_REBAR 0x24 /* VF Resizable BAR */ +#define PCI_EXT_CAP_ID_DLNK 0x25 /* Data Link Feature */ +#define PCI_EXT_CAP_ID_16GT 0x26 /* Physical Layer 16.0 GT/s */ +#define PCI_EXT_CAP_ID_LMR 0x27 /* Lane Margining at Receiver */ +#define PCI_EXT_CAP_ID_HIER_ID 0x28 /* Hierarchy ID */ +#define PCI_EXT_CAP_ID_NPEM 0x29 /* Native PCIe Enclosure Management */ +#define PCI_EXT_CAP_ID_32GT 0x2a /* Physical Layer 32.0 GT/s */ +#define PCI_EXT_CAP_ID_DOE 0x2e /* Data Object Exchange */ + +/*** Definitions of capabilities ***/ + +/* Power Management Registers */ + +#define PCI_PM_CAP_VER_MASK 0x0007 /* Version (2=PM1.1) */ +#define PCI_PM_CAP_PME_CLOCK 0x0008 /* Clock required for PME generation */ +#define PCI_PM_CAP_DSI 0x0020 /* Device specific initialization required */ +#define PCI_PM_CAP_AUX_C_MASK 0x01c0 /* Maximum aux current required in D3cold */ +#define PCI_PM_CAP_D1 0x0200 /* D1 power state support */ +#define PCI_PM_CAP_D2 0x0400 /* D2 power state support */ +#define PCI_PM_CAP_PME_D0 0x0800 /* PME can be asserted from D0 */ +#define PCI_PM_CAP_PME_D1 0x1000 /* PME can be asserted from D1 */ +#define PCI_PM_CAP_PME_D2 0x2000 /* PME can be asserted from D2 */ +#define PCI_PM_CAP_PME_D3_HOT 0x4000 /* PME can be asserted from D3hot */ +#define PCI_PM_CAP_PME_D3_COLD 0x8000 /* PME can be asserted from D3cold */ +#define PCI_PM_CTRL 4 /* PM control and status register */ +#define PCI_PM_CTRL_STATE_MASK 0x0003 /* Current power state (D0 to D3) */ +#define PCI_PM_CTRL_NO_SOFT_RST 0x0008 /* No Soft Reset from D3hot to D0 */ +#define PCI_PM_CTRL_PME_ENABLE 0x0100 /* PME pin enable */ +#define PCI_PM_CTRL_DATA_SEL_MASK 0x1e00 /* PM table data index */ +#define PCI_PM_CTRL_DATA_SCALE_MASK 0x6000 /* PM table data scaling factor */ +#define PCI_PM_CTRL_PME_STATUS 0x8000 /* PME pin status */ +#define PCI_PM_PPB_EXTENSIONS 6 /* PPB support extensions */ +#define PCI_PM_PPB_B2_B3 0x40 /* If bridge enters D3hot, bus enters: 0=B3, 1=B2 */ +#define PCI_PM_BPCC_ENABLE 0x80 /* Secondary bus is power managed */ +#define PCI_PM_DATA_REGISTER 7 /* PM table contents read here */ +#define PCI_PM_SIZEOF 8 + +/* AGP registers */ + +#define PCI_AGP_VERSION 2 /* BCD version number */ +#define PCI_AGP_RFU 3 /* Rest of capability flags */ +#define PCI_AGP_STATUS 4 /* Status register */ +#define PCI_AGP_STATUS_RQ_MASK 0xff000000 /* Maximum number of requests - 1 */ +#define PCI_AGP_STATUS_ISOCH 0x10000 /* Isochronous transactions supported */ +#define PCI_AGP_STATUS_ARQSZ_MASK 0xe000 /* log2(optimum async req size in bytes) - 4 */ +#define PCI_AGP_STATUS_CAL_MASK 0x1c00 /* Calibration cycle timing */ +#define PCI_AGP_STATUS_SBA 0x0200 /* Sideband addressing supported */ +#define PCI_AGP_STATUS_ITA_COH 0x0100 /* In-aperture accesses always coherent */ +#define PCI_AGP_STATUS_GART64 0x0080 /* 64-bit GART entries supported */ +#define PCI_AGP_STATUS_HTRANS 0x0040 /* If 0, core logic can xlate host CPU accesses thru aperture */ +#define PCI_AGP_STATUS_64BIT 0x0020 /* 64-bit addressing cycles supported */ +#define PCI_AGP_STATUS_FW 0x0010 /* Fast write transfers supported */ +#define PCI_AGP_STATUS_AGP3 0x0008 /* AGP3 mode supported */ +#define PCI_AGP_STATUS_RATE4 0x0004 /* 4x transfer rate supported (RFU in AGP3 mode) */ +#define PCI_AGP_STATUS_RATE2 0x0002 /* 2x transfer rate supported (8x in AGP3 mode) */ +#define PCI_AGP_STATUS_RATE1 0x0001 /* 1x transfer rate supported (4x in AGP3 mode) */ +#define PCI_AGP_COMMAND 8 /* Control register */ +#define PCI_AGP_COMMAND_RQ_MASK 0xff000000 /* Master: Maximum number of requests */ +#define PCI_AGP_COMMAND_ARQSZ_MASK 0xe000 /* log2(optimum async req size in bytes) - 4 */ +#define PCI_AGP_COMMAND_CAL_MASK 0x1c00 /* Calibration cycle timing */ +#define PCI_AGP_COMMAND_SBA 0x0200 /* Sideband addressing enabled */ +#define PCI_AGP_COMMAND_AGP 0x0100 /* Allow processing of AGP transactions */ +#define PCI_AGP_COMMAND_GART64 0x0080 /* 64-bit GART entries enabled */ +#define PCI_AGP_COMMAND_64BIT 0x0020 /* Allow generation of 64-bit addr cycles */ +#define PCI_AGP_COMMAND_FW 0x0010 /* Enable FW transfers */ +#define PCI_AGP_COMMAND_RATE4 0x0004 /* Use 4x rate (RFU in AGP3 mode) */ +#define PCI_AGP_COMMAND_RATE2 0x0002 /* Use 2x rate (8x in AGP3 mode) */ +#define PCI_AGP_COMMAND_RATE1 0x0001 /* Use 1x rate (4x in AGP3 mode) */ +#define PCI_AGP_SIZEOF 12 + +/* Vital Product Data */ + +#define PCI_VPD_ADDR 2 /* Address to access (15 bits!) */ +#define PCI_VPD_ADDR_MASK 0x7fff /* Address mask */ +#define PCI_VPD_ADDR_F 0x8000 /* Write 0, 1 indicates completion */ +#define PCI_VPD_DATA 4 /* 32-bits of data returned here */ + +/* Slot Identification */ + +#define PCI_SID_ESR 2 /* Expansion Slot Register */ +#define PCI_SID_ESR_NSLOTS 0x1f /* Number of expansion slots available */ +#define PCI_SID_ESR_FIC 0x20 /* First In Chassis Flag */ +#define PCI_SID_CHASSIS_NR 3 /* Chassis Number */ + +/* Message Signaled Interrupts registers */ + +#define PCI_MSI_FLAGS 2 /* Various flags */ +#define PCI_MSI_FLAGS_MASK_BIT 0x100 /* interrupt masking & reporting supported */ +#define PCI_MSI_FLAGS_64BIT 0x080 /* 64-bit addresses allowed */ +#define PCI_MSI_FLAGS_QSIZE 0x070 /* Message queue size configured */ +#define PCI_MSI_FLAGS_QMASK 0x00e /* Maximum queue size available */ +#define PCI_MSI_FLAGS_ENABLE 0x001 /* MSI feature enabled */ +#define PCI_MSI_RFU 3 /* Rest of capability flags */ +#define PCI_MSI_ADDRESS_LO 4 /* Lower 32 bits */ +#define PCI_MSI_ADDRESS_HI 8 /* Upper 32 bits (if PCI_MSI_FLAGS_64BIT set) */ +#define PCI_MSI_DATA_32 8 /* 16 bits of data for 32-bit devices */ +#define PCI_MSI_DATA_64 12 /* 16 bits of data for 64-bit devices */ +#define PCI_MSI_MASK_BIT_32 12 /* per-vector masking for 32-bit devices */ +#define PCI_MSI_MASK_BIT_64 16 /* per-vector masking for 64-bit devices */ +#define PCI_MSI_PENDING_32 16 /* per-vector interrupt pending for 32-bit devices */ +#define PCI_MSI_PENDING_64 20 /* per-vector interrupt pending for 64-bit devices */ + +/* PCI-X */ +#define PCI_PCIX_COMMAND 2 /* Command register offset */ +#define PCI_PCIX_COMMAND_DPERE 0x0001 /* Data Parity Error Recover Enable */ +#define PCI_PCIX_COMMAND_ERO 0x0002 /* Enable Relaxed Ordering */ +#define PCI_PCIX_COMMAND_MAX_MEM_READ_BYTE_COUNT 0x000c /* Maximum Memory Read Byte Count */ +#define PCI_PCIX_COMMAND_MAX_OUTSTANDING_SPLIT_TRANS 0x0070 +#define PCI_PCIX_COMMAND_RESERVED 0xf80 +#define PCI_PCIX_STATUS 4 /* Status register offset */ +#define PCI_PCIX_STATUS_FUNCTION 0x00000007 +#define PCI_PCIX_STATUS_DEVICE 0x000000f8 +#define PCI_PCIX_STATUS_BUS 0x0000ff00 +#define PCI_PCIX_STATUS_64BIT 0x00010000 +#define PCI_PCIX_STATUS_133MHZ 0x00020000 +#define PCI_PCIX_STATUS_SC_DISCARDED 0x00040000 /* Split Completion Discarded */ +#define PCI_PCIX_STATUS_UNEXPECTED_SC 0x00080000 /* Unexpected Split Completion */ +#define PCI_PCIX_STATUS_DEVICE_COMPLEXITY 0x00100000 /* 0 = simple device, 1 = bridge device */ +#define PCI_PCIX_STATUS_DESIGNED_MAX_MEM_READ_BYTE_COUNT 0x00600000 /* 0 = 512 bytes, 1 = 1024, 2 = 2048, 3 = 4096 */ +#define PCI_PCIX_STATUS_DESIGNED_MAX_OUTSTANDING_SPLIT_TRANS 0x03800000 +#define PCI_PCIX_STATUS_DESIGNED_MAX_CUMULATIVE_READ_SIZE 0x1c000000 +#define PCI_PCIX_STATUS_RCVD_SC_ERR_MESS 0x20000000 /* Received Split Completion Error Message */ +#define PCI_PCIX_STATUS_266MHZ 0x40000000 /* 266 MHz capable */ +#define PCI_PCIX_STATUS_533MHZ 0x80000000 /* 533 MHz capable */ +#define PCI_PCIX_SIZEOF 4 + +/* PCI-X Bridges */ +#define PCI_PCIX_BRIDGE_SEC_STATUS 2 /* Secondary bus status register offset */ +#define PCI_PCIX_BRIDGE_SEC_STATUS_64BIT 0x0001 +#define PCI_PCIX_BRIDGE_SEC_STATUS_133MHZ 0x0002 +#define PCI_PCIX_BRIDGE_SEC_STATUS_SC_DISCARDED 0x0004 /* Split Completion Discarded on secondary bus */ +#define PCI_PCIX_BRIDGE_SEC_STATUS_UNEXPECTED_SC 0x0008 /* Unexpected Split Completion on secondary bus */ +#define PCI_PCIX_BRIDGE_SEC_STATUS_SC_OVERRUN 0x0010 /* Split Completion Overrun on secondary bus */ +#define PCI_PCIX_BRIDGE_SEC_STATUS_SPLIT_REQUEST_DELAYED 0x0020 +#define PCI_PCIX_BRIDGE_SEC_STATUS_CLOCK_FREQ 0x01c0 +#define PCI_PCIX_BRIDGE_SEC_STATUS_RESERVED 0xfe00 +#define PCI_PCIX_BRIDGE_STATUS 4 /* Primary bus status register offset */ +#define PCI_PCIX_BRIDGE_STATUS_FUNCTION 0x00000007 +#define PCI_PCIX_BRIDGE_STATUS_DEVICE 0x000000f8 +#define PCI_PCIX_BRIDGE_STATUS_BUS 0x0000ff00 +#define PCI_PCIX_BRIDGE_STATUS_64BIT 0x00010000 +#define PCI_PCIX_BRIDGE_STATUS_133MHZ 0x00020000 +#define PCI_PCIX_BRIDGE_STATUS_SC_DISCARDED 0x00040000 /* Split Completion Discarded */ +#define PCI_PCIX_BRIDGE_STATUS_UNEXPECTED_SC 0x00080000 /* Unexpected Split Completion */ +#define PCI_PCIX_BRIDGE_STATUS_SC_OVERRUN 0x00100000 /* Split Completion Overrun */ +#define PCI_PCIX_BRIDGE_STATUS_SPLIT_REQUEST_DELAYED 0x00200000 +#define PCI_PCIX_BRIDGE_STATUS_RESERVED 0xffc00000 +#define PCI_PCIX_BRIDGE_UPSTREAM_SPLIT_TRANS_CTRL 8 /* Upstream Split Transaction Register offset */ +#define PCI_PCIX_BRIDGE_DOWNSTREAM_SPLIT_TRANS_CTRL 12 /* Downstream Split Transaction Register offset */ +#define PCI_PCIX_BRIDGE_STR_CAPACITY 0x0000ffff +#define PCI_PCIX_BRIDGE_STR_COMMITMENT_LIMIT 0xffff0000 +#define PCI_PCIX_BRIDGE_SIZEOF 12 + +/* HyperTransport (as of spec rev. 2.00) */ +#define PCI_HT_CMD 2 /* Command Register */ +#define PCI_HT_CMD_TYP_HI 0xe000 /* Capability Type high part */ +#define PCI_HT_CMD_TYP_HI_PRI 0x0000 /* Slave or Primary Interface */ +#define PCI_HT_CMD_TYP_HI_SEC 0x2000 /* Host or Secondary Interface */ +#define PCI_HT_CMD_TYP 0xf800 /* Capability Type */ +#define PCI_HT_CMD_TYP_SW 0x4000 /* Switch */ +#define PCI_HT_CMD_TYP_IDC 0x8000 /* Interrupt Discovery and Configuration */ +#define PCI_HT_CMD_TYP_RID 0x8800 /* Revision ID */ +#define PCI_HT_CMD_TYP_UIDC 0x9000 /* UnitID Clumping */ +#define PCI_HT_CMD_TYP_ECSA 0x9800 /* Extended Configuration Space Access */ +#define PCI_HT_CMD_TYP_AM 0xa000 /* Address Mapping */ +#define PCI_HT_CMD_TYP_MSIM 0xa800 /* MSI Mapping */ +#define PCI_HT_CMD_TYP_DR 0xb000 /* DirectRoute */ +#define PCI_HT_CMD_TYP_VCS 0xb800 /* VCSet */ +#define PCI_HT_CMD_TYP_RM 0xc000 /* Retry Mode */ +#define PCI_HT_CMD_TYP_X86 0xc800 /* X86 (reserved) */ + + /* Link Control Register */ +#define PCI_HT_LCTR_CFLE 0x0002 /* CRC Flood Enable */ +#define PCI_HT_LCTR_CST 0x0004 /* CRC Start Test */ +#define PCI_HT_LCTR_CFE 0x0008 /* CRC Force Error */ +#define PCI_HT_LCTR_LKFAIL 0x0010 /* Link Failure */ +#define PCI_HT_LCTR_INIT 0x0020 /* Initialization Complete */ +#define PCI_HT_LCTR_EOC 0x0040 /* End of Chain */ +#define PCI_HT_LCTR_TXO 0x0080 /* Transmitter Off */ +#define PCI_HT_LCTR_CRCERR 0x0f00 /* CRC Error */ +#define PCI_HT_LCTR_ISOCEN 0x1000 /* Isochronous Flow Control Enable */ +#define PCI_HT_LCTR_LSEN 0x2000 /* LDTSTOP# Tristate Enable */ +#define PCI_HT_LCTR_EXTCTL 0x4000 /* Extended CTL Time */ +#define PCI_HT_LCTR_64B 0x8000 /* 64-bit Addressing Enable */ + + /* Link Configuration Register */ +#define PCI_HT_LCNF_MLWI 0x0007 /* Max Link Width In */ +#define PCI_HT_LCNF_LW_8B 0x0 /* Link Width 8 bits */ +#define PCI_HT_LCNF_LW_16B 0x1 /* Link Width 16 bits */ +#define PCI_HT_LCNF_LW_32B 0x3 /* Link Width 32 bits */ +#define PCI_HT_LCNF_LW_2B 0x4 /* Link Width 2 bits */ +#define PCI_HT_LCNF_LW_4B 0x5 /* Link Width 4 bits */ +#define PCI_HT_LCNF_LW_NC 0x7 /* Link physically not connected */ +#define PCI_HT_LCNF_DFI 0x0008 /* Doubleword Flow Control In */ +#define PCI_HT_LCNF_MLWO 0x0070 /* Max Link Width Out */ +#define PCI_HT_LCNF_DFO 0x0080 /* Doubleword Flow Control Out */ +#define PCI_HT_LCNF_LWI 0x0700 /* Link Width In */ +#define PCI_HT_LCNF_DFIE 0x0800 /* Doubleword Flow Control In Enable */ +#define PCI_HT_LCNF_LWO 0x7000 /* Link Width Out */ +#define PCI_HT_LCNF_DFOE 0x8000 /* Doubleword Flow Control Out Enable */ + + /* Revision ID Register */ +#define PCI_HT_RID_MIN 0x1f /* Minor Revision */ +#define PCI_HT_RID_MAJ 0xe0 /* Major Revision */ + + /* Link Frequency/Error Register */ +#define PCI_HT_LFRER_FREQ 0x0f /* Transmitter Clock Frequency */ +#define PCI_HT_LFRER_200 0x00 /* 200MHz */ +#define PCI_HT_LFRER_300 0x01 /* 300MHz */ +#define PCI_HT_LFRER_400 0x02 /* 400MHz */ +#define PCI_HT_LFRER_500 0x03 /* 500MHz */ +#define PCI_HT_LFRER_600 0x04 /* 600MHz */ +#define PCI_HT_LFRER_800 0x05 /* 800MHz */ +#define PCI_HT_LFRER_1000 0x06 /* 1.0GHz */ +#define PCI_HT_LFRER_1200 0x07 /* 1.2GHz */ +#define PCI_HT_LFRER_1400 0x08 /* 1.4GHz */ +#define PCI_HT_LFRER_1600 0x09 /* 1.6GHz */ +#define PCI_HT_LFRER_VEND 0x0f /* Vendor-Specific */ +#define PCI_HT_LFRER_ERR 0xf0 /* Link Error */ +#define PCI_HT_LFRER_PROT 0x10 /* Protocol Error */ +#define PCI_HT_LFRER_OV 0x20 /* Overflow Error */ +#define PCI_HT_LFRER_EOC 0x40 /* End of Chain Error */ +#define PCI_HT_LFRER_CTLT 0x80 /* CTL Timeout */ + + /* Link Frequency Capability Register */ +#define PCI_HT_LFCAP_200 0x0001 /* 200MHz */ +#define PCI_HT_LFCAP_300 0x0002 /* 300MHz */ +#define PCI_HT_LFCAP_400 0x0004 /* 400MHz */ +#define PCI_HT_LFCAP_500 0x0008 /* 500MHz */ +#define PCI_HT_LFCAP_600 0x0010 /* 600MHz */ +#define PCI_HT_LFCAP_800 0x0020 /* 800MHz */ +#define PCI_HT_LFCAP_1000 0x0040 /* 1.0GHz */ +#define PCI_HT_LFCAP_1200 0x0080 /* 1.2GHz */ +#define PCI_HT_LFCAP_1400 0x0100 /* 1.4GHz */ +#define PCI_HT_LFCAP_1600 0x0200 /* 1.6GHz */ +#define PCI_HT_LFCAP_VEND 0x8000 /* Vendor-Specific */ + + /* Feature Register */ +#define PCI_HT_FTR_ISOCFC 0x0001 /* Isochronous Flow Control Mode */ +#define PCI_HT_FTR_LDTSTOP 0x0002 /* LDTSTOP# Supported */ +#define PCI_HT_FTR_CRCTM 0x0004 /* CRC Test Mode */ +#define PCI_HT_FTR_ECTLT 0x0008 /* Extended CTL Time Required */ +#define PCI_HT_FTR_64BA 0x0010 /* 64-bit Addressing */ +#define PCI_HT_FTR_UIDRD 0x0020 /* UnitID Reorder Disable */ + + /* Error Handling Register */ +#define PCI_HT_EH_PFLE 0x0001 /* Protocol Error Flood Enable */ +#define PCI_HT_EH_OFLE 0x0002 /* Overflow Error Flood Enable */ +#define PCI_HT_EH_PFE 0x0004 /* Protocol Error Fatal Enable */ +#define PCI_HT_EH_OFE 0x0008 /* Overflow Error Fatal Enable */ +#define PCI_HT_EH_EOCFE 0x0010 /* End of Chain Error Fatal Enable */ +#define PCI_HT_EH_RFE 0x0020 /* Response Error Fatal Enable */ +#define PCI_HT_EH_CRCFE 0x0040 /* CRC Error Fatal Enable */ +#define PCI_HT_EH_SERRFE 0x0080 /* System Error Fatal Enable (B */ +#define PCI_HT_EH_CF 0x0100 /* Chain Fail */ +#define PCI_HT_EH_RE 0x0200 /* Response Error */ +#define PCI_HT_EH_PNFE 0x0400 /* Protocol Error Nonfatal Enable */ +#define PCI_HT_EH_ONFE 0x0800 /* Overflow Error Nonfatal Enable */ +#define PCI_HT_EH_EOCNFE 0x1000 /* End of Chain Error Nonfatal Enable */ +#define PCI_HT_EH_RNFE 0x2000 /* Response Error Nonfatal Enable */ +#define PCI_HT_EH_CRCNFE 0x4000 /* CRC Error Nonfatal Enable */ +#define PCI_HT_EH_SERRNFE 0x8000 /* System Error Nonfatal Enable */ + +/* HyperTransport: Slave or Primary Interface */ +#define PCI_HT_PRI_CMD 2 /* Command Register */ +#define PCI_HT_PRI_CMD_BUID 0x001f /* Base UnitID */ +#define PCI_HT_PRI_CMD_UC 0x03e0 /* Unit Count */ +#define PCI_HT_PRI_CMD_MH 0x0400 /* Master Host */ +#define PCI_HT_PRI_CMD_DD 0x0800 /* Default Direction */ +#define PCI_HT_PRI_CMD_DUL 0x1000 /* Drop on Uninitialized Link */ + +#define PCI_HT_PRI_LCTR0 4 /* Link Control 0 Register */ +#define PCI_HT_PRI_LCNF0 6 /* Link Config 0 Register */ +#define PCI_HT_PRI_LCTR1 8 /* Link Control 1 Register */ +#define PCI_HT_PRI_LCNF1 10 /* Link Config 1 Register */ +#define PCI_HT_PRI_RID 12 /* Revision ID Register */ +#define PCI_HT_PRI_LFRER0 13 /* Link Frequency/Error 0 Register */ +#define PCI_HT_PRI_LFCAP0 14 /* Link Frequency Capability 0 Register */ +#define PCI_HT_PRI_FTR 16 /* Feature Register */ +#define PCI_HT_PRI_LFRER1 17 /* Link Frequency/Error 1 Register */ +#define PCI_HT_PRI_LFCAP1 18 /* Link Frequency Capability 1 Register */ +#define PCI_HT_PRI_ES 20 /* Enumeration Scratchpad Register */ +#define PCI_HT_PRI_EH 22 /* Error Handling Register */ +#define PCI_HT_PRI_MBU 24 /* Memory Base Upper Register */ +#define PCI_HT_PRI_MLU 25 /* Memory Limit Upper Register */ +#define PCI_HT_PRI_BN 26 /* Bus Number Register */ +#define PCI_HT_PRI_SIZEOF 28 + +/* HyperTransport: Host or Secondary Interface */ +#define PCI_HT_SEC_CMD 2 /* Command Register */ +#define PCI_HT_SEC_CMD_WR 0x0001 /* Warm Reset */ +#define PCI_HT_SEC_CMD_DE 0x0002 /* Double-Ended */ +#define PCI_HT_SEC_CMD_DN 0x007c /* Device Number */ +#define PCI_HT_SEC_CMD_CS 0x0080 /* Chain Side */ +#define PCI_HT_SEC_CMD_HH 0x0100 /* Host Hide */ +#define PCI_HT_SEC_CMD_AS 0x0400 /* Act as Slave */ +#define PCI_HT_SEC_CMD_HIECE 0x0800 /* Host Inbound End of Chain Error */ +#define PCI_HT_SEC_CMD_DUL 0x1000 /* Drop on Uninitialized Link */ + +#define PCI_HT_SEC_LCTR 4 /* Link Control Register */ +#define PCI_HT_SEC_LCNF 6 /* Link Config Register */ +#define PCI_HT_SEC_RID 8 /* Revision ID Register */ +#define PCI_HT_SEC_LFRER 9 /* Link Frequency/Error Register */ +#define PCI_HT_SEC_LFCAP 10 /* Link Frequency Capability Register */ +#define PCI_HT_SEC_FTR 12 /* Feature Register */ +#define PCI_HT_SEC_FTR_EXTRS 0x0100 /* Extended Register Set */ +#define PCI_HT_SEC_FTR_UCNFE 0x0200 /* Upstream Configuration Enable */ +#define PCI_HT_SEC_ES 16 /* Enumeration Scratchpad Register */ +#define PCI_HT_SEC_EH 18 /* Error Handling Register */ +#define PCI_HT_SEC_MBU 20 /* Memory Base Upper Register */ +#define PCI_HT_SEC_MLU 21 /* Memory Limit Upper Register */ +#define PCI_HT_SEC_SIZEOF 24 + +/* HyperTransport: Switch */ +#define PCI_HT_SW_CMD 2 /* Switch Command Register */ +#define PCI_HT_SW_CMD_VIBERR 0x0080 /* VIB Error */ +#define PCI_HT_SW_CMD_VIBFL 0x0100 /* VIB Flood */ +#define PCI_HT_SW_CMD_VIBFT 0x0200 /* VIB Fatal */ +#define PCI_HT_SW_CMD_VIBNFT 0x0400 /* VIB Nonfatal */ +#define PCI_HT_SW_PMASK 4 /* Partition Mask Register */ +#define PCI_HT_SW_SWINF 8 /* Switch Info Register */ +#define PCI_HT_SW_SWINF_DP 0x0000001f /* Default Port */ +#define PCI_HT_SW_SWINF_EN 0x00000020 /* Enable Decode */ +#define PCI_HT_SW_SWINF_CR 0x00000040 /* Cold Reset */ +#define PCI_HT_SW_SWINF_PCIDX 0x00000f00 /* Performance Counter Index */ +#define PCI_HT_SW_SWINF_BLRIDX 0x0003f000 /* Base/Limit Range Index */ +#define PCI_HT_SW_SWINF_SBIDX 0x00002000 /* Secondary Base Range Index */ +#define PCI_HT_SW_SWINF_HP 0x00040000 /* Hot Plug */ +#define PCI_HT_SW_SWINF_HIDE 0x00080000 /* Hide Port */ +#define PCI_HT_SW_PCD 12 /* Performance Counter Data Register */ +#define PCI_HT_SW_BLRD 16 /* Base/Limit Range Data Register */ +#define PCI_HT_SW_SBD 20 /* Secondary Base Data Register */ +#define PCI_HT_SW_SIZEOF 24 + + /* Counter indices */ +#define PCI_HT_SW_PC_PCR 0x0 /* Posted Command Receive */ +#define PCI_HT_SW_PC_NPCR 0x1 /* Nonposted Command Receive */ +#define PCI_HT_SW_PC_RCR 0x2 /* Response Command Receive */ +#define PCI_HT_SW_PC_PDWR 0x3 /* Posted DW Receive */ +#define PCI_HT_SW_PC_NPDWR 0x4 /* Nonposted DW Receive */ +#define PCI_HT_SW_PC_RDWR 0x5 /* Response DW Receive */ +#define PCI_HT_SW_PC_PCT 0x6 /* Posted Command Transmit */ +#define PCI_HT_SW_PC_NPCT 0x7 /* Nonposted Command Transmit */ +#define PCI_HT_SW_PC_RCT 0x8 /* Response Command Transmit */ +#define PCI_HT_SW_PC_PDWT 0x9 /* Posted DW Transmit */ +#define PCI_HT_SW_PC_NPDWT 0xa /* Nonposted DW Transmit */ +#define PCI_HT_SW_PC_RDWT 0xb /* Response DW Transmit */ + + /* Base/Limit Range indices */ +#define PCI_HT_SW_BLR_BASE0_LO 0x0 /* Base 0[31:1], Enable */ +#define PCI_HT_SW_BLR_BASE0_HI 0x1 /* Base 0 Upper */ +#define PCI_HT_SW_BLR_LIM0_LO 0x2 /* Limit 0 Lower */ +#define PCI_HT_SW_BLR_LIM0_HI 0x3 /* Limit 0 Upper */ + + /* Secondary Base indices */ +#define PCI_HT_SW_SB_LO 0x0 /* Secondary Base[31:1], Enable */ +#define PCI_HT_SW_S0_HI 0x1 /* Secondary Base Upper */ + +/* HyperTransport: Interrupt Discovery and Configuration */ +#define PCI_HT_IDC_IDX 2 /* Index Register */ +#define PCI_HT_IDC_DATA 4 /* Data Register */ +#define PCI_HT_IDC_SIZEOF 8 + + /* Register indices */ +#define PCI_HT_IDC_IDX_LINT 0x01 /* Last Interrupt Register */ +#define PCI_HT_IDC_LINT 0x00ff0000 /* Last interrupt definition */ +#define PCI_HT_IDC_IDX_IDR 0x10 /* Interrupt Definition Registers */ + /* Low part (at index) */ +#define PCI_HT_IDC_IDR_MASK 0x10000001 /* Mask */ +#define PCI_HT_IDC_IDR_POL 0x10000002 /* Polarity */ +#define PCI_HT_IDC_IDR_II_2 0x1000001c /* IntrInfo[4:2]: Message Type */ +#define PCI_HT_IDC_IDR_II_5 0x10000020 /* IntrInfo[5]: Request EOI */ +#define PCI_HT_IDC_IDR_II_6 0x00ffffc0 /* IntrInfo[23:6] */ +#define PCI_HT_IDC_IDR_II_24 0xff000000 /* IntrInfo[31:24] */ + /* High part (at index + 1) */ +#define PCI_HT_IDC_IDR_II_32 0x00ffffff /* IntrInfo[55:32] */ +#define PCI_HT_IDC_IDR_PASSPW 0x40000000 /* PassPW setting for messages */ +#define PCI_HT_IDC_IDR_WEOI 0x80000000 /* Waiting for EOI */ + +/* HyperTransport: Revision ID */ +#define PCI_HT_RID_RID 2 /* Revision Register */ +#define PCI_HT_RID_SIZEOF 4 + +/* HyperTransport: UnitID Clumping */ +#define PCI_HT_UIDC_CS 4 /* Clumping Support Register */ +#define PCI_HT_UIDC_CE 8 /* Clumping Enable Register */ +#define PCI_HT_UIDC_SIZEOF 12 + +/* HyperTransport: Extended Configuration Space Access */ +#define PCI_HT_ECSA_ADDR 4 /* Configuration Address Register */ +#define PCI_HT_ECSA_ADDR_REG 0x00000ffc /* Register */ +#define PCI_HT_ECSA_ADDR_FUN 0x00007000 /* Function */ +#define PCI_HT_ECSA_ADDR_DEV 0x000f1000 /* Device */ +#define PCI_HT_ECSA_ADDR_BUS 0x0ff00000 /* Bus Number */ +#define PCI_HT_ECSA_ADDR_TYPE 0x10000000 /* Access Type */ +#define PCI_HT_ECSA_DATA 8 /* Configuration Data Register */ +#define PCI_HT_ECSA_SIZEOF 12 + +/* HyperTransport: Address Mapping */ +#define PCI_HT_AM_CMD 2 /* Command Register */ +#define PCI_HT_AM_CMD_NDMA 0x000f /* Number of DMA Mappings */ +#define PCI_HT_AM_CMD_IOSIZ 0x01f0 /* I/O Size */ +#define PCI_HT_AM_CMD_MT 0x0600 /* Map Type */ +#define PCI_HT_AM_CMD_MT_40B 0x0000 /* 40-bit */ +#define PCI_HT_AM_CMD_MT_64B 0x0200 /* 64-bit */ + + /* Window Control Register bits */ +#define PCI_HT_AM_SBW_CTR_COMP 0x1 /* Compat */ +#define PCI_HT_AM_SBW_CTR_NCOH 0x2 /* NonCoherent */ +#define PCI_HT_AM_SBW_CTR_ISOC 0x4 /* Isochronous */ +#define PCI_HT_AM_SBW_CTR_EN 0x8 /* Enable */ + +/* HyperTransport: 40-bit Address Mapping */ +#define PCI_HT_AM40_SBNPW 4 /* Secondary Bus Non-Prefetchable Window Register */ +#define PCI_HT_AM40_SBW_BASE 0x000fffff /* Window Base */ +#define PCI_HT_AM40_SBW_CTR 0xf0000000 /* Window Control */ +#define PCI_HT_AM40_SBPW 8 /* Secondary Bus Prefetchable Window Register */ +#define PCI_HT_AM40_DMA_PBASE0 12 /* DMA Window Primary Base 0 Register */ +#define PCI_HT_AM40_DMA_CTR0 15 /* DMA Window Control 0 Register */ +#define PCI_HT_AM40_DMA_CTR_CTR 0xf0 /* Window Control */ +#define PCI_HT_AM40_DMA_SLIM0 16 /* DMA Window Secondary Limit 0 Register */ +#define PCI_HT_AM40_DMA_SBASE0 18 /* DMA Window Secondary Base 0 Register */ +#define PCI_HT_AM40_SIZEOF 12 /* size is variable: 12 + 8 * NDMA */ + +/* HyperTransport: 64-bit Address Mapping */ +#define PCI_HT_AM64_IDX 4 /* Index Register */ +#define PCI_HT_AM64_DATA_LO 8 /* Data Lower Register */ +#define PCI_HT_AM64_DATA_HI 12 /* Data Upper Register */ +#define PCI_HT_AM64_SIZEOF 16 + + /* Register indices */ +#define PCI_HT_AM64_IDX_SBNPW 0x00 /* Secondary Bus Non-Prefetchable Window Register */ +#define PCI_HT_AM64_W_BASE_LO 0xfff00000 /* Window Base Lower */ +#define PCI_HT_AM64_W_CTR 0x0000000f /* Window Control */ +#define PCI_HT_AM64_IDX_SBPW 0x01 /* Secondary Bus Prefetchable Window Register */ +#define PCI_HT_AM64_IDX_PBNPW 0x02 /* Primary Bus Non-Prefetchable Window Register */ +#define PCI_HT_AM64_IDX_DMAPB0 0x04 /* DMA Window Primary Base 0 Register */ +#define PCI_HT_AM64_IDX_DMASB0 0x05 /* DMA Window Secondary Base 0 Register */ +#define PCI_HT_AM64_IDX_DMASL0 0x06 /* DMA Window Secondary Limit 0 Register */ + +/* HyperTransport: MSI Mapping */ +#define PCI_HT_MSIM_CMD 2 /* Command Register */ +#define PCI_HT_MSIM_CMD_EN 0x0001 /* Mapping Active */ +#define PCI_HT_MSIM_CMD_FIXD 0x0002 /* MSI Mapping Address Fixed */ +#define PCI_HT_MSIM_ADDR_LO 4 /* MSI Mapping Address Lower Register */ +#define PCI_HT_MSIM_ADDR_HI 8 /* MSI Mapping Address Upper Register */ +#define PCI_HT_MSIM_SIZEOF 12 + +/* HyperTransport: DirectRoute */ +#define PCI_HT_DR_CMD 2 /* Command Register */ +#define PCI_HT_DR_CMD_NDRS 0x000f /* Number of DirectRoute Spaces */ +#define PCI_HT_DR_CMD_IDX 0x01f0 /* Index */ +#define PCI_HT_DR_EN 4 /* Enable Vector Register */ +#define PCI_HT_DR_DATA 8 /* Data Register */ +#define PCI_HT_DR_SIZEOF 12 + + /* Register indices */ +#define PCI_HT_DR_IDX_BASE_LO 0x00 /* DirectRoute Base Lower Register */ +#define PCI_HT_DR_OTNRD 0x00000001 /* Opposite to Normal Request Direction */ +#define PCI_HT_DR_BL_LO 0xffffff00 /* Base/Limit Lower */ +#define PCI_HT_DR_IDX_BASE_HI 0x01 /* DirectRoute Base Upper Register */ +#define PCI_HT_DR_IDX_LIMIT_LO 0x02 /* DirectRoute Limit Lower Register */ +#define PCI_HT_DR_IDX_LIMIT_HI 0x03 /* DirectRoute Limit Upper Register */ + +/* HyperTransport: VCSet */ +#define PCI_HT_VCS_SUP 4 /* VCSets Supported Register */ +#define PCI_HT_VCS_L1EN 5 /* Link 1 VCSets Enabled Register */ +#define PCI_HT_VCS_L0EN 6 /* Link 0 VCSets Enabled Register */ +#define PCI_HT_VCS_SBD 8 /* Stream Bucket Depth Register */ +#define PCI_HT_VCS_SINT 9 /* Stream Interval Register */ +#define PCI_HT_VCS_SSUP 10 /* Number of Streaming VCs Supported Register */ +#define PCI_HT_VCS_SSUP_0 0x00 /* Streaming VC 0 */ +#define PCI_HT_VCS_SSUP_3 0x01 /* Streaming VCs 0-3 */ +#define PCI_HT_VCS_SSUP_15 0x02 /* Streaming VCs 0-15 */ +#define PCI_HT_VCS_NFCBD 12 /* Non-FC Bucket Depth Register */ +#define PCI_HT_VCS_NFCINT 13 /* Non-FC Bucket Interval Register */ +#define PCI_HT_VCS_SIZEOF 16 + +/* HyperTransport: Retry Mode */ +#define PCI_HT_RM_CTR0 4 /* Control 0 Register */ +#define PCI_HT_RM_CTR_LRETEN 0x01 /* Link Retry Enable */ +#define PCI_HT_RM_CTR_FSER 0x02 /* Force Single Error */ +#define PCI_HT_RM_CTR_ROLNEN 0x04 /* Rollover Nonfatal Enable */ +#define PCI_HT_RM_CTR_FSS 0x08 /* Force Single Stomp */ +#define PCI_HT_RM_CTR_RETNEN 0x10 /* Retry Nonfatal Enable */ +#define PCI_HT_RM_CTR_RETFEN 0x20 /* Retry Fatal Enable */ +#define PCI_HT_RM_CTR_AA 0xc0 /* Allowed Attempts */ +#define PCI_HT_RM_STS0 5 /* Status 0 Register */ +#define PCI_HT_RM_STS_RETSNT 0x01 /* Retry Sent */ +#define PCI_HT_RM_STS_CNTROL 0x02 /* Count Rollover */ +#define PCI_HT_RM_STS_SRCV 0x04 /* Stomp Received */ +#define PCI_HT_RM_CTR1 6 /* Control 1 Register */ +#define PCI_HT_RM_STS1 7 /* Status 1 Register */ +#define PCI_HT_RM_CNT0 8 /* Retry Count 0 Register */ +#define PCI_HT_RM_CNT1 10 /* Retry Count 1 Register */ +#define PCI_HT_RM_SIZEOF 12 + +/* Vendor-Specific Capability (see PCI_EVNDR_xxx for the PCIe version) */ +#define PCI_VNDR_LENGTH 2 /* Length byte */ + +/* PCI Express */ +#define PCI_EXP_FLAGS 0x2 /* Capabilities register */ +#define PCI_EXP_FLAGS_VERS 0x000f /* Capability version */ +#define PCI_EXP_FLAGS_TYPE 0x00f0 /* Device/Port type */ +#define PCI_EXP_TYPE_ENDPOINT 0x0 /* Express Endpoint */ +#define PCI_EXP_TYPE_LEG_END 0x1 /* Legacy Endpoint */ +#define PCI_EXP_TYPE_ROOT_PORT 0x4 /* Root Port */ +#define PCI_EXP_TYPE_UPSTREAM 0x5 /* Upstream Port */ +#define PCI_EXP_TYPE_DOWNSTREAM 0x6 /* Downstream Port */ +#define PCI_EXP_TYPE_PCI_BRIDGE 0x7 /* PCIe to PCI/PCI-X Bridge */ +#define PCI_EXP_TYPE_PCIE_BRIDGE 0x8 /* PCI/PCI-X to PCIe Bridge */ +#define PCI_EXP_TYPE_ROOT_INT_EP 0x9 /* Root Complex Integrated Endpoint */ +#define PCI_EXP_TYPE_ROOT_EC 0xa /* Root Complex Event Collector */ +#define PCI_EXP_FLAGS_SLOT 0x0100 /* Slot implemented */ +#define PCI_EXP_FLAGS_IRQ 0x3e00 /* Interrupt message number */ +#define PCI_EXP_DEVCAP 0x4 /* Device capabilities */ +#define PCI_EXP_DEVCAP_PAYLOAD 0x07 /* Max_Payload_Size */ +#define PCI_EXP_DEVCAP_PHANTOM 0x18 /* Phantom functions */ +#define PCI_EXP_DEVCAP_EXT_TAG 0x20 /* Extended tags */ +#define PCI_EXP_DEVCAP_L0S 0x1c0 /* L0s Acceptable Latency */ +#define PCI_EXP_DEVCAP_L1 0xe00 /* L1 Acceptable Latency */ +#define PCI_EXP_DEVCAP_ATN_BUT 0x1000 /* Attention Button Present */ +#define PCI_EXP_DEVCAP_ATN_IND 0x2000 /* Attention Indicator Present */ +#define PCI_EXP_DEVCAP_PWR_IND 0x4000 /* Power Indicator Present */ +#define PCI_EXP_DEVCAP_RBE 0x8000 /* Role-Based Error Reporting */ +#define PCI_EXP_DEVCAP_PWR_VAL 0x3fc0000 /* Slot Power Limit Value */ +#define PCI_EXP_DEVCAP_PWR_SCL 0xc000000 /* Slot Power Limit Scale */ +#define PCI_EXP_DEVCAP_FLRESET 0x10000000 /* Function-Level Reset */ +#define PCI_EXP_DEVCTL 0x8 /* Device Control */ +#define PCI_EXP_DEVCTL_CERE 0x0001 /* Correctable Error Reporting En. */ +#define PCI_EXP_DEVCTL_NFERE 0x0002 /* Non-Fatal Error Reporting Enable */ +#define PCI_EXP_DEVCTL_FERE 0x0004 /* Fatal Error Reporting Enable */ +#define PCI_EXP_DEVCTL_URRE 0x0008 /* Unsupported Request Reporting En. */ +#define PCI_EXP_DEVCTL_RELAXED 0x0010 /* Enable Relaxed Ordering */ +#define PCI_EXP_DEVCTL_PAYLOAD 0x00e0 /* Max_Payload_Size */ +#define PCI_EXP_DEVCTL_EXT_TAG 0x0100 /* Extended Tag Field Enable */ +#define PCI_EXP_DEVCTL_PHANTOM 0x0200 /* Phantom Functions Enable */ +#define PCI_EXP_DEVCTL_AUX_PME 0x0400 /* Auxiliary Power PM Enable */ +#define PCI_EXP_DEVCTL_NOSNOOP 0x0800 /* Enable No Snoop */ +#define PCI_EXP_DEVCTL_READRQ 0x7000 /* Max_Read_Request_Size */ +#define PCI_EXP_DEVCTL_BCRE 0x8000 /* Bridge Configuration Retry Enable */ +#define PCI_EXP_DEVCTL_FLRESET 0x8000 /* Function-Level Reset [bit shared with BCRE] */ +#define PCI_EXP_DEVSTA 0xa /* Device Status */ +#define PCI_EXP_DEVSTA_CED 0x01 /* Correctable Error Detected */ +#define PCI_EXP_DEVSTA_NFED 0x02 /* Non-Fatal Error Detected */ +#define PCI_EXP_DEVSTA_FED 0x04 /* Fatal Error Detected */ +#define PCI_EXP_DEVSTA_URD 0x08 /* Unsupported Request Detected */ +#define PCI_EXP_DEVSTA_AUXPD 0x10 /* AUX Power Detected */ +#define PCI_EXP_DEVSTA_TRPND 0x20 /* Transactions Pending */ +#define PCI_EXP_LNKCAP 0xc /* Link Capabilities */ +#define PCI_EXP_LNKCAP_SPEED 0x0000f /* Maximum Link Speed */ +#define PCI_EXP_LNKCAP_WIDTH 0x003f0 /* Maximum Link Width */ +#define PCI_EXP_LNKCAP_ASPM 0x00c00 /* Active State Power Management */ +#define PCI_EXP_LNKCAP_L0S 0x07000 /* L0s Exit Latency */ +#define PCI_EXP_LNKCAP_L1 0x38000 /* L1 Exit Latency */ +#define PCI_EXP_LNKCAP_CLOCKPM 0x40000 /* Clock Power Management */ +#define PCI_EXP_LNKCAP_SURPRISE 0x80000 /* Surprise Down Error Reporting */ +#define PCI_EXP_LNKCAP_DLLA 0x100000 /* Data Link Layer Active Reporting */ +#define PCI_EXP_LNKCAP_LBNC 0x200000 /* Link Bandwidth Notification Capability */ +#define PCI_EXP_LNKCAP_AOC 0x400000 /* ASPM Optionality Compliance */ +#define PCI_EXP_LNKCAP_PORT 0xff000000 /* Port Number */ +#define PCI_EXP_LNKCTL 0x10 /* Link Control */ +#define PCI_EXP_LNKCTL_ASPM 0x0003 /* ASPM Control */ +#define PCI_EXP_LNKCTL_RCB 0x0008 /* Read Completion Boundary */ +#define PCI_EXP_LNKCTL_DISABLE 0x0010 /* Link Disable */ +#define PCI_EXP_LNKCTL_RETRAIN 0x0020 /* Retrain Link */ +#define PCI_EXP_LNKCTL_CLOCK 0x0040 /* Common Clock Configuration */ +#define PCI_EXP_LNKCTL_XSYNCH 0x0080 /* Extended Synch */ +#define PCI_EXP_LNKCTL_CLOCKPM 0x0100 /* Clock Power Management */ +#define PCI_EXP_LNKCTL_HWAUTWD 0x0200 /* Hardware Autonomous Width Disable */ +#define PCI_EXP_LNKCTL_BWMIE 0x0400 /* Bandwidth Mgmt Interrupt Enable */ +#define PCI_EXP_LNKCTL_AUTBWIE 0x0800 /* Autonomous Bandwidth Mgmt Interrupt Enable */ +#define PCI_EXP_LNKSTA 0x12 /* Link Status */ +#define PCI_EXP_LNKSTA_SPEED 0x000f /* Negotiated Link Speed */ +#define PCI_EXP_LNKSTA_WIDTH 0x03f0 /* Negotiated Link Width */ +#define PCI_EXP_LNKSTA_TR_ERR 0x0400 /* Training Error (obsolete) */ +#define PCI_EXP_LNKSTA_TRAIN 0x0800 /* Link Training */ +#define PCI_EXP_LNKSTA_SL_CLK 0x1000 /* Slot Clock Configuration */ +#define PCI_EXP_LNKSTA_DL_ACT 0x2000 /* Data Link Layer in DL_Active State */ +#define PCI_EXP_LNKSTA_BWMGMT 0x4000 /* Bandwidth Mgmt Status */ +#define PCI_EXP_LNKSTA_AUTBW 0x8000 /* Autonomous Bandwidth Mgmt Status */ +#define PCI_EXP_SLTCAP 0x14 /* Slot Capabilities */ +#define PCI_EXP_SLTCAP_ATNB 0x0001 /* Attention Button Present */ +#define PCI_EXP_SLTCAP_PWRC 0x0002 /* Power Controller Present */ +#define PCI_EXP_SLTCAP_MRL 0x0004 /* MRL Sensor Present */ +#define PCI_EXP_SLTCAP_ATNI 0x0008 /* Attention Indicator Present */ +#define PCI_EXP_SLTCAP_PWRI 0x0010 /* Power Indicator Present */ +#define PCI_EXP_SLTCAP_HPS 0x0020 /* Hot-Plug Surprise */ +#define PCI_EXP_SLTCAP_HPC 0x0040 /* Hot-Plug Capable */ +#define PCI_EXP_SLTCAP_PWR_VAL 0x00007f80 /* Slot Power Limit Value */ +#define PCI_EXP_SLTCAP_PWR_SCL 0x00018000 /* Slot Power Limit Scale */ +#define PCI_EXP_SLTCAP_INTERLOCK 0x020000 /* Electromechanical Interlock Present */ +#define PCI_EXP_SLTCAP_NOCMDCOMP 0x040000 /* No Command Completed Support */ +#define PCI_EXP_SLTCAP_PSN 0xfff80000 /* Physical Slot Number */ +#define PCI_EXP_SLTCTL 0x18 /* Slot Control */ +#define PCI_EXP_SLTCTL_ATNB 0x0001 /* Attention Button Pressed Enable */ +#define PCI_EXP_SLTCTL_PWRF 0x0002 /* Power Fault Detected Enable */ +#define PCI_EXP_SLTCTL_MRLS 0x0004 /* MRL Sensor Changed Enable */ +#define PCI_EXP_SLTCTL_PRSD 0x0008 /* Presence Detect Changed Enable */ +#define PCI_EXP_SLTCTL_CMDC 0x0010 /* Command Completed Interrupt Enable */ +#define PCI_EXP_SLTCTL_HPIE 0x0020 /* Hot-Plug Interrupt Enable */ +#define PCI_EXP_SLTCTL_ATNI 0x00c0 /* Attention Indicator Control */ +#define PCI_EXP_SLTCTL_PWRI 0x0300 /* Power Indicator Control */ +#define PCI_EXP_SLTCTL_PWRC 0x0400 /* Power Controller Control */ +#define PCI_EXP_SLTCTL_INTERLOCK 0x0800 /* Electromechanical Interlock Control */ +#define PCI_EXP_SLTCTL_LLCHG 0x1000 /* Data Link Layer State Changed Enable */ +#define PCI_EXP_SLTSTA 0x1a /* Slot Status */ +#define PCI_EXP_SLTSTA_ATNB 0x0001 /* Attention Button Pressed */ +#define PCI_EXP_SLTSTA_PWRF 0x0002 /* Power Fault Detected */ +#define PCI_EXP_SLTSTA_MRLS 0x0004 /* MRL Sensor Changed */ +#define PCI_EXP_SLTSTA_PRSD 0x0008 /* Presence Detect Changed */ +#define PCI_EXP_SLTSTA_CMDC 0x0010 /* Command Completed */ +#define PCI_EXP_SLTSTA_MRL_ST 0x0020 /* MRL Sensor State */ +#define PCI_EXP_SLTSTA_PRES 0x0040 /* Presence Detect State */ +#define PCI_EXP_SLTSTA_INTERLOCK 0x0080 /* Electromechanical Interlock Status */ +#define PCI_EXP_SLTSTA_LLCHG 0x0100 /* Data Link Layer State Changed */ +#define PCI_EXP_RTCTL 0x1c /* Root Control */ +#define PCI_EXP_RTCTL_SECEE 0x0001 /* System Error on Correctable Error */ +#define PCI_EXP_RTCTL_SENFEE 0x0002 /* System Error on Non-Fatal Error */ +#define PCI_EXP_RTCTL_SEFEE 0x0004 /* System Error on Fatal Error */ +#define PCI_EXP_RTCTL_PMEIE 0x0008 /* PME Interrupt Enable */ +#define PCI_EXP_RTCTL_CRSVIS 0x0010 /* Configuration Request Retry Status Visible to SW */ +#define PCI_EXP_RTCAP 0x1e /* Root Capabilities */ +#define PCI_EXP_RTCAP_CRSVIS 0x0001 /* Configuration Request Retry Status Visible to SW */ +#define PCI_EXP_RTSTA 0x20 /* Root Status */ +#define PCI_EXP_RTSTA_PME_REQID 0x0000ffff /* PME Requester ID */ +#define PCI_EXP_RTSTA_PME_STATUS 0x00010000 /* PME Status */ +#define PCI_EXP_RTSTA_PME_PENDING 0x00020000 /* PME is Pending */ +#define PCI_EXP_DEVCAP2 0x24 /* Device capabilities 2 */ +#define PCI_EXP_DEVCAP2_TIMEOUT_RANGE(x) ((x) & 0xf) /* Completion Timeout Ranges Supported */ +#define PCI_EXP_DEVCAP2_TIMEOUT_DIS 0x0010 /* Completion Timeout Disable Supported */ +#define PCI_EXP_DEVCAP2_ARI 0x0020 /* ARI Forwarding Supported */ +#define PCI_EXP_DEVCAP2_ATOMICOP_ROUTING 0x0040 /* AtomicOp Routing Supported */ +#define PCI_EXP_DEVCAP2_32BIT_ATOMICOP_COMP 0x0080 /* 32bit AtomicOp Completer Supported */ +#define PCI_EXP_DEVCAP2_64BIT_ATOMICOP_COMP 0x0100 /* 64bit AtomicOp Completer Supported */ +#define PCI_EXP_DEVCAP2_128BIT_CAS_COMP 0x0200 /* 128bit CAS Completer Supported */ +#define PCI_EXP_DEVCAP2_NROPRPRP 0x0400 /* No RO-enabled PR-PR Passing */ +#define PCI_EXP_DEVCAP2_LTR 0x0800 /* LTR supported */ +#define PCI_EXP_DEVCAP2_TPH_COMP(x) (((x) >> 12) & 3) /* TPH Completer Supported */ +#define PCI_EXP_DEVCAP2_LN_CLS(x) (((x) >> 14) & 3) /* LN System CLS Supported */ +#define PCI_EXP_DEVCAP2_10BIT_TAG_COMP 0x00010000 /* 10 Bit Tag Completer */ +#define PCI_EXP_DEVCAP2_10BIT_TAG_REQ 0x00020000 /* 10 Bit Tag Requester */ +#define PCI_EXP_DEVCAP2_OBFF(x) (((x) >> 18) & 3) /* OBFF supported */ +#define PCI_EXP_DEVCAP2_EXTFMT 0x00100000 /* Extended Fmt Field Supported */ +#define PCI_EXP_DEVCAP2_EE_TLP 0x00200000 /* End-End TLP Prefix Supported */ +#define PCI_EXP_DEVCAP2_MEE_TLP(x) (((x) >> 22) & 3) /* Max End-End TLP Prefixes */ +#define PCI_EXP_DEVCAP2_EPR(x) (((x) >> 24) & 3) /* Emergency Power Reduction Supported */ +#define PCI_EXP_DEVCAP2_EPR_INIT 0x04000000 /* Emergency Power Reduction Initialization Required */ +#define PCI_EXP_DEVCAP2_FRS 0x80000000 /* FRS supported */ +#define PCI_EXP_DEVCTL2 0x28 /* Device Control */ +#define PCI_EXP_DEVCTL2_TIMEOUT_VALUE(x) ((x) & 0xf) /* Completion Timeout Value */ +#define PCI_EXP_DEVCTL2_TIMEOUT_DIS 0x0010 /* Completion Timeout Disable */ +#define PCI_EXP_DEVCTL2_ARI 0x0020 /* ARI Forwarding */ +#define PCI_EXP_DEVCTL2_ATOMICOP_REQUESTER_EN 0x0040 /* AtomicOp RequesterEnable */ +#define PCI_EXP_DEVCTL2_ATOMICOP_EGRESS_BLOCK 0x0080 /* AtomicOp Egress Blocking */ +#define PCI_EXP_DEVCTL2_IDO_REQ_EN 0x0100 /* Allow IDO for requests */ +#define PCI_EXP_DEVCTL2_IDO_CMP_EN 0x0200 /* Allow IDO for completions */ +#define PCI_EXP_DEVCTL2_LTR 0x0400 /* LTR enabled */ +#define PCI_EXP_DEVCTL2_EPR_REQ 0x0800 /* Emergency Power Reduction Request */ +#define PCI_EXP_DEVCTL2_10BIT_TAG_REQ 0x1000 /* 10 Bit Tag Requester enabled */ +#define PCI_EXP_DEVCTL2_OBFF(x) (((x) >> 13) & 3) /* OBFF enabled */ +#define PCI_EXP_DEVCTL2_EE_TLP_BLK 0x8000 /* End-End TLP Prefix Blocking */ +#define PCI_EXP_DEVSTA2 0x2a /* Device Status */ +#define PCI_EXP_LNKCAP2 0x2c /* Link Capabilities */ +#define PCI_EXP_LNKCAP2_SPEED(x) (((x) >> 1) & 0x7f) +#define PCI_EXP_LNKCAP2_CROSSLINK 0x00000100 /* Crosslink Supported */ +#define PCI_EXP_LNKCAP2_RETIMER 0x00800000 /* Retimer Supported */ +#define PCI_EXP_LNKCAP2_2RETIMERS 0x01000000 /* 2 Retimers Supported */ +#define PCI_EXP_LNKCAP2_DRS 0x80000000 /* Device Readiness Status */ +#define PCI_EXP_LNKCTL2 0x30 /* Link Control */ +#define PCI_EXP_LNKCTL2_SPEED(x) ((x) & 0xf) /* Target Link Speed */ +#define PCI_EXP_LNKCTL2_CMPLNC 0x0010 /* Enter Compliance */ +#define PCI_EXP_LNKCTL2_SPEED_DIS 0x0020 /* Hardware Autonomous Speed Disable */ +#define PCI_EXP_LNKCTL2_DEEMPHASIS(x) (((x) >> 6) & 1) /* Selectable De-emphasis */ +#define PCI_EXP_LNKCTL2_MARGIN(x) (((x) >> 7) & 7) /* Transmit Margin */ +#define PCI_EXP_LNKCTL2_MOD_CMPLNC 0x0400 /* Enter Modified Compliance */ +#define PCI_EXP_LNKCTL2_CMPLNC_SOS 0x0800 /* Compliance SOS */ +#define PCI_EXP_LNKCTL2_COM_DEEMPHASIS(x) (((x) >> 12) & 0xf) /* Compliance Preset/De-emphasis */ +#define PCI_EXP_LNKSTA2 0x32 /* Link Status */ +#define PCI_EXP_LINKSTA2_DEEMPHASIS(x) ((x) & 1) /* Current De-emphasis Level */ +#define PCI_EXP_LINKSTA2_EQU_COMP 0x02 /* Equalization Complete */ +#define PCI_EXP_LINKSTA2_EQU_PHASE1 0x04 /* Equalization Phase 1 Successful */ +#define PCI_EXP_LINKSTA2_EQU_PHASE2 0x08 /* Equalization Phase 2 Successful */ +#define PCI_EXP_LINKSTA2_EQU_PHASE3 0x10 /* Equalization Phase 3 Successful */ +#define PCI_EXP_LINKSTA2_EQU_REQ 0x20 /* Link Equalization Request */ +#define PCI_EXP_LINKSTA2_RETIMER 0x0040 /* Retimer Detected */ +#define PCI_EXP_LINKSTA2_2RETIMERS 0x0080 /* 2 Retimers Detected */ +#define PCI_EXP_LINKSTA2_CROSSLINK(x) (((x) >> 8) & 0x3) /* Crosslink Res */ +#define PCI_EXP_LINKSTA2_COMPONENT(x) (((x) >> 12) & 0x7) /* Presence */ +#define PCI_EXP_LINKSTA2_DRS_RCVD 0x8000 /* DRS Msg Received */ +#define PCI_EXP_SLTCAP2 0x34 /* Slot Capabilities */ +#define PCI_EXP_SLTCTL2 0x38 /* Slot Control */ +#define PCI_EXP_SLTSTA2 0x3a /* Slot Status */ + +/* MSI-X */ +#define PCI_MSIX_ENABLE 0x8000 +#define PCI_MSIX_MASK 0x4000 +#define PCI_MSIX_TABSIZE 0x07ff +#define PCI_MSIX_TABLE 4 +#define PCI_MSIX_PBA 8 +#define PCI_MSIX_BIR 0x7 + +/* Subsystem vendor/device ID for PCI bridges */ +#define PCI_SSVID_VENDOR 4 +#define PCI_SSVID_DEVICE 6 + +/* PCI Advanced Features */ +#define PCI_AF_CAP 3 +#define PCI_AF_CAP_TP 0x01 +#define PCI_AF_CAP_FLR 0x02 +#define PCI_AF_CTRL 4 +#define PCI_AF_CTRL_FLR 0x01 +#define PCI_AF_STATUS 5 +#define PCI_AF_STATUS_TP 0x01 + +/* SATA Host Bus Adapter */ +#define PCI_SATA_HBA_BARS 4 +#define PCI_SATA_HBA_REG0 8 + +/* Enhanced Allocation (EA) */ +#define PCI_EA_CAP_TYPE1_SECONDARY 4 +#define PCI_EA_CAP_TYPE1_SUBORDINATE 5 +/* EA Entry header */ +#define PCI_EA_CAP_ENT_WRITABLE 0x40000000 /* Writable: 1 = RW, 0 = HwInit */ +#define PCI_EA_CAP_ENT_ENABLE 0x80000000 /* Enable for this entry */ + +/*** Definitions of extended capabilities ***/ + +/* Advanced Error Reporting */ +#define PCI_ERR_UNCOR_STATUS 4 /* Uncorrectable Error Status */ +#define PCI_ERR_UNC_TRAIN 0x00000001 /* Undefined in PCIe rev1.1 & 2.0 spec */ +#define PCI_ERR_UNC_DLP 0x00000010 /* Data Link Protocol */ +#define PCI_ERR_UNC_SDES 0x00000020 /* Surprise Down Error */ +#define PCI_ERR_UNC_POISON_TLP 0x00001000 /* Poisoned TLP */ +#define PCI_ERR_UNC_FCP 0x00002000 /* Flow Control Protocol */ +#define PCI_ERR_UNC_COMP_TIME 0x00004000 /* Completion Timeout */ +#define PCI_ERR_UNC_COMP_ABORT 0x00008000 /* Completer Abort */ +#define PCI_ERR_UNC_UNX_COMP 0x00010000 /* Unexpected Completion */ +#define PCI_ERR_UNC_RX_OVER 0x00020000 /* Receiver Overflow */ +#define PCI_ERR_UNC_MALF_TLP 0x00040000 /* Malformed TLP */ +#define PCI_ERR_UNC_ECRC 0x00080000 /* ECRC Error Status */ +#define PCI_ERR_UNC_UNSUP 0x00100000 /* Unsupported Request */ +#define PCI_ERR_UNC_ACS_VIOL 0x00200000 /* ACS Violation */ +#define PCI_ERR_UNCOR_MASK 8 /* Uncorrectable Error Mask */ + /* Same bits as above */ +#define PCI_ERR_UNCOR_SEVER 12 /* Uncorrectable Error Severity */ + /* Same bits as above */ +#define PCI_ERR_COR_STATUS 16 /* Correctable Error Status */ +#define PCI_ERR_COR_RCVR 0x00000001 /* Receiver Error Status */ +#define PCI_ERR_COR_BAD_TLP 0x00000040 /* Bad TLP Status */ +#define PCI_ERR_COR_BAD_DLLP 0x00000080 /* Bad DLLP Status */ +#define PCI_ERR_COR_REP_ROLL 0x00000100 /* REPLAY_NUM Rollover */ +#define PCI_ERR_COR_REP_TIMER 0x00001000 /* Replay Timer Timeout */ +#define PCI_ERR_COR_REP_ANFE 0x00002000 /* Advisory Non-Fatal Error */ +#define PCI_ERR_COR_MASK 20 /* Correctable Error Mask */ + /* Same bits as above */ +#define PCI_ERR_CAP 24 /* Advanced Error Capabilities */ +#define PCI_ERR_CAP_FEP(x) ((x) & 31) /* First Error Pointer */ +#define PCI_ERR_CAP_ECRC_GENC 0x00000020 /* ECRC Generation Capable */ +#define PCI_ERR_CAP_ECRC_GENE 0x00000040 /* ECRC Generation Enable */ +#define PCI_ERR_CAP_ECRC_CHKC 0x00000080 /* ECRC Check Capable */ +#define PCI_ERR_CAP_ECRC_CHKE 0x00000100 /* ECRC Check Enable */ +#define PCI_ERR_CAP_MULT_HDRC 0x00000200 /* Multiple Header Capable */ +#define PCI_ERR_CAP_MULT_HDRE 0x00000400 /* Multiple Header Enable */ +#define PCI_ERR_CAP_TLP_PFX 0x00000800 /* TLP Prefix Log Present */ +#define PCI_ERR_CAP_HDR_LOG 0x00001000 /* Completion Timeout Prefix/Header Log Capable */ +#define PCI_ERR_HEADER_LOG 28 /* Header Log Register (16 bytes) */ +#define PCI_ERR_ROOT_COMMAND 44 /* Root Error Command */ +#define PCI_ERR_ROOT_CMD_COR_EN 0x00000001 /* Correctable Error Reporting Enable */ +#define PCI_ERR_ROOT_CMD_NONFATAL_EN 0x00000002 /* Non-Fatal Error Reporting Enable*/ +#define PCI_ERR_ROOT_CMD_FATAL_EN 0x00000004 /* Fatal Error Reporting Enable */ +#define PCI_ERR_ROOT_STATUS 48 /* Root Error Status */ +#define PCI_ERR_ROOT_COR_RCV 0x00000001 /* ERR_COR Received */ +#define PCI_ERR_ROOT_MULTI_COR_RCV 0x00000002 /* Multiple ERR_COR Received */ +#define PCI_ERR_ROOT_UNCOR_RCV 0x00000004 /* ERR_FATAL/NONFATAL Received */ +#define PCI_ERR_ROOT_MULTI_UNCOR_RCV 0x00000008 /* Multiple ERR_FATAL/NONFATAL Received */ +#define PCI_ERR_ROOT_FIRST_FATAL 0x00000010 /* First Uncorrectable Fatal */ +#define PCI_ERR_ROOT_NONFATAL_RCV 0x00000020 /* Non-Fatal Error Messages Received */ +#define PCI_ERR_ROOT_FATAL_RCV 0x00000040 /* Fatal Error Messages Received */ +#define PCI_ERR_MSG_NUM(x) (((x) >> 27) & 0x1f) /* MSI/MSI-X vector */ +#define PCI_ERR_ROOT_COR_SRC 52 +#define PCI_ERR_ROOT_SRC 54 + +/* Virtual Channel */ +#define PCI_VC_PORT_REG1 4 +#define PCI_VC_PORT_REG2 8 +#define PCI_VC_PORT_CTRL 12 +#define PCI_VC_PORT_STATUS 14 +#define PCI_VC_RES_CAP 16 +#define PCI_VC_RES_CTRL 20 +#define PCI_VC_RES_STATUS 26 + +/* Power Budgeting */ +#define PCI_PWR_DSR 4 /* Data Select Register */ +#define PCI_PWR_DATA 8 /* Data Register */ +#define PCI_PWR_DATA_BASE(x) ((x) & 0xff) /* Base Power */ +#define PCI_PWR_DATA_SCALE(x) (((x) >> 8) & 3) /* Data Scale */ +#define PCI_PWR_DATA_PM_SUB(x) (((x) >> 10) & 7) /* PM Sub State */ +#define PCI_PWR_DATA_PM_STATE(x) (((x) >> 13) & 3) /* PM State */ +#define PCI_PWR_DATA_TYPE(x) (((x) >> 15) & 7) /* Type */ +#define PCI_PWR_DATA_RAIL(x) (((x) >> 18) & 7) /* Power Rail */ +#define PCI_PWR_CAP 12 /* Capability */ +#define PCI_PWR_CAP_BUDGET(x) ((x) & 1) /* Included in system budget */ + +/* Root Complex Link */ +#define PCI_RCLINK_ESD 4 /* Element Self Description */ +#define PCI_RCLINK_LINK1 16 /* First Link Entry */ +#define PCI_RCLINK_LINK_DESC 0 /* Link Entry: Description */ +#define PCI_RCLINK_LINK_ADDR 8 /* Link Entry: Address (64-bit) */ +#define PCI_RCLINK_LINK_SIZE 16 /* Link Entry: sizeof */ + +/* Root Complex Event Collector Endpoint Association */ +#define PCI_RCEC_EP_CAP_VER(reg) (((reg) >> 16) & 0xf) +#define PCI_RCEC_BUSN_REG_VER 0x02 /* as per PCIe sec 7.9.10.1 */ +#define PCI_RCEC_RCIEP_BMAP 0x0004 /* as per PCIe sec 7.9.10.2 */ +#define PCI_RCEC_BUSN_REG 0x0008 /* as per PCIe sec 7.9.10.3 */ + +/* PCIe Vendor-Specific Capability */ +#define PCI_EVNDR_HEADER 4 /* Vendor-Specific Header */ +#define PCI_EVNDR_REGISTERS 8 /* Vendor-Specific Registers */ + +/* PCIe Designated Vendor-Specific Capability */ +#define PCI_DVSEC_HEADER1 4 /* Designated Vendor-Specific Header 1 */ +#define PCI_DVSEC_HEADER2 8 /* Designated Vendor-Specific Header 2 */ +#define PCI_DVSEC_VENDOR_ID_CXL 0x1e98 /* Designated Vendor-Specific Vendor ID for CXL */ +#define PCI_DVSEC_ID_CXL 0 /* Designated Vendor-Specific ID for Intel CXL */ + +/* PCIe CXL Designated Vendor-Specific Capabilities for Devices: Control, Status */ +#define PCI_CXL_DEV_LEN 0x38 /* CXL Device DVSEC Length for Rev1 */ +#define PCI_CXL_DEV_LEN_REV2 0x3c /* ... for Rev2 */ +#define PCI_CXL_DEV_CAP 0x0a /* CXL Capability Register */ +#define PCI_CXL_DEV_CAP_CACHE 0x0001 /* CXL.cache Protocol Support */ +#define PCI_CXL_DEV_CAP_IO 0x0002 /* CXL.io Protocol Support */ +#define PCI_CXL_DEV_CAP_MEM 0x0004 /* CXL.mem Protocol Support */ +#define PCI_CXL_DEV_CAP_MEM_HWINIT 0x0008 /* CXL.mem Initializes with HW/FW Support */ +#define PCI_CXL_DEV_CAP_HDM_CNT(x) (((x) & (3 << 4)) >> 4) /* CXL Number of HDM ranges */ +#define PCI_CXL_DEV_CAP_VIRAL 0x4000 /* CXL Viral Handling Support */ +#define PCI_CXL_DEV_CTRL 0x0c /* CXL Control Register */ +#define PCI_CXL_DEV_CTRL_CACHE 0x0001 /* CXL.cache Protocol Enable */ +#define PCI_CXL_DEV_CTRL_IO 0x0002 /* CXL.io Protocol Enable */ +#define PCI_CXL_DEV_CTRL_MEM 0x0004 /* CXL.mem Protocol Enable */ +#define PCI_CXL_DEV_CTRL_CACHE_SF_COV(x) (((x) & (0x1f << 3)) >> 3) /* Snoop Filter Coverage */ +#define PCI_CXL_DEV_CTRL_CACHE_SF_GRAN(x) (((x) & (0x7 << 8)) >> 8) /* Snoop Filter Granularity */ +#define PCI_CXL_DEV_CTRL_CACHE_CLN 0x0800 /* CXL.cache Performance Hint on Clean Evictions */ +#define PCI_CXL_DEV_CTRL_VIRAL 0x4000 /* CXL Viral Handling Enable */ +#define PCI_CXL_DEV_STATUS 0x0e /* CXL Status Register */ +#define PCI_CXL_DEV_STATUS_VIRAL 0x4000 /* CXL Viral Handling Status */ +#define PCI_CXL_DEV_CTRL2 0x10 /* CXL Control Register 2 */ +#define PCI_CXL_DEV_CTRL2_DISABLE_CACHING 0x0001 +#define PCI_CXL_DEV_CTRL2_INIT_WB_INVAL 0x0002 +#define PCI_CXL_DEV_CTRL2_INIT_CXL_RST 0x0003 +#define PCI_CXL_DEV_CTRL2_INIT_CXL_RST_CLR_EN 0x0004 +#define PCI_CXL_DEV_CTRL2_INIT_CXL_HDM_STATE_HOTRST 0x0005 +#define PCI_CXL_DEV_STATUS2 0x12 +#define PCI_CXL_DEV_STATUS_CACHE_INV 0x0001 +#define PCI_CXL_DEV_STATUS_RC 0x0002 /* Device Reset Complete */ +#define PCI_CXL_DEV_STATUS_RE 0x0004 /* Device Reset Error */ +#define PCI_CXL_DEV_STATUS_PMC 0x8000 /* Power Management Init Complete */ +#define PCI_CXL_DEV_CAP2 0x16 +#define PCI_CXL_DEV_CAP2_CACHE_UNK 0x0000 /* Cache Size Isn't Reported */ +#define PCI_CXL_DEV_CAP2_CACHE_64K 0x0001 /* Unit Size 64K */ +#define PCI_CXL_DEV_CAP2_CACHE_1M 0x0002 /* Unit Size 1M */ +#define PCI_CXL_DEV_RANGE1_SIZE_HI 0x18 +#define PCI_CXL_DEV_RANGE1_SIZE_LO 0x1c +#define PCI_CXL_RANGE_VALID 0x0001 +#define PCI_CXL_RANGE_ACTIVE 0x0002 +#define PCI_CXL_RANGE_TYPE(x) (((x) >> 2) & 0x7) +#define PCI_CXL_RANGE_CLASS(x) (((x) >> 5) & 0x7) +#define PCI_CXL_RANGE_INTERLEAVE(x) (((x) >> 8) & 0x1f) +#define PCI_CXL_RANGE_TIMEOUT(x) (((x) >> 13) & 0x7) +#define PCI_CXL_DEV_RANGE1_BASE_HI 0x20 +#define PCI_CXL_DEV_RANGE1_BASE_LO 0x24 +#define PCI_CXL_DEV_RANGE2_SIZE_HI 0x28 +#define PCI_CXL_DEV_RANGE2_SIZE_LO 0x2c +#define PCI_CXL_DEV_RANGE2_BASE_HI 0x30 +#define PCI_CXL_DEV_RANGE2_BASE_LO 0x34 +/* From Rev2 */ +#define PCI_CXL_DEV_CAP3 0x38 +#define PCI_CXL_DEV_CAP3_HDM_STATE_RST_COLD 0x0001 +#define PCI_CXL_DEV_CAP3_HDM_STATE_RST_WARM 0x0002 +#define PCI_CXL_DEV_CAP3_HDM_STATE_RST_HOT 0x0003 +#define PCI_CXL_DEV_CAP3_HDM_STATE_RST_HOT_CFG 0x0004 + + +/* PCIe CXL 2.0 Designated Vendor-Specific Capabilities for Ports */ +#define PCI_CXL_PORT_EXT_LEN 0x28 /* CXL Extensions DVSEC for Ports Length */ +#define PCI_CXL_PORT_EXT_STATUS 0x0a /* Port Extension Status */ +#define PCI_CXL_PORT_PM_INIT_COMPLETE 0x1 /* Port Power Management Initialization Complete */ +#define PCI_CXL_PORT_CTRL 0x0c /* Port Control Override */ +#define PCI_CXL_PORT_UNMASK_SBR 0x0001 /* Unmask SBR */ +#define PCI_CXL_PORT_UNMASK_LINK 0x0002 /* Unmask Link Disable */ +#define PCI_CXL_PORT_ALT_MEMORY 0x0004 /* Alt Memory and ID Space Enable */ +#define PCI_CXL_PORT_ALT_BME 0x0008 /* Alt BME */ +#define PCI_CXL_PORT_VIRAL_EN 0x4000 /* Viral Enable */ +#define PCI_CXL_PORT_ALT_BUS_BASE 0xe +#define PCI_CXL_PORT_ALT_BUS_LIMIT 0xf +#define PCI_CXL_PORT_ALT_MEM_BASE 0x10 +#define PCI_CXL_PORT_ALT_MEM_LIMIT 0x12 + +/* PCIe CXL 2.0 Designated Vendor-Specific Capabilities for Register Locator */ +#define PCI_CXL_RL_BLOCK1_LO 0x0c + +/* PCIe CXL Designated Vendor-Specific Capabilities for Global Persistent Flush */ +#define PCI_CXL_GPF_DEV_LEN 0x10 +#define PCI_CXL_GPF_DEV_PHASE2_DUR 0x0a /* GPF Phase 2 Duration Register */ +#define PCI_CXL_GPF_DEV_PHASE2_POW 0x0c /* GPF Phase 2 Power Register */ +#define PCI_CXL_GPF_DEV_1US 0x0 +#define PCI_CXL_GPF_DEV_10US 0x1 +#define PCI_CXL_GPF_DEV_100US 0x2 +#define PCI_CXL_GPF_DEV_1MS 0x3 +#define PCI_CXL_GPF_DEV_10MS 0x4 +#define PCI_CXL_GPF_DEV_100MS 0x5 +#define PCI_CXL_GPF_DEV_1S 0x6 +#define PCI_CXL_GPF_DEV_10S 0x7 +#define PCI_CXL_GPF_PORT_LEN 0x10 +#define PCI_CXL_GPF_PORT_PHASE1_CTRL 0x0c /* GPF Phase 1 Control Register */ +#define PCI_CXL_GPF_PORT_PHASE2_CTRL 0x0e /* GPF Phase 2 Control Register */ +#define PCI_CXL_GPF_PORT_1US 0x0 +#define PCI_CXL_GPF_PORT_10US 0x1 +#define PCI_CXL_GPF_PORT_100US 0x2 +#define PCI_CXL_GPF_PORT_1MS 0x3 +#define PCI_CXL_GPF_PORT_10MS 0x4 +#define PCI_CXL_GPF_PORT_100MS 0x5 +#define PCI_CXL_GPF_PORT_1S 0x6 +#define PCI_CXL_GPF_PORT_10S 0x7 + +/* PCIe CXL Designated Vendor-Specific Capabilities for Flex Bus Port */ +#define PCI_CXL_FB_LEN 0x20 +#define PCI_CXL_FB_PORT_CAP 0x0a /* CXL Flex Bus Port Capability Register */ +#define PCI_CXL_FB_CAP_CACHE 0x0001 /* CXL.cache Capable */ +#define PCI_CXL_FB_CAP_IO 0x0002 /* CXL.io Capable */ +#define PCI_CXL_FB_CAP_MEM 0x0004 /* CXL.mem Capable */ +#define PCI_CXL_FB_CAP_68B_FLIT 0x0020 /* CXL 68B Flit and VH Capable */ +#define PCI_CXL_FB_CAP_MULT_LOG_DEV 0x0040 /* CXL Multi-Logical Device Capable */ +#define PCI_CXL_FB_CAP_256B_FLIT 0x2000 /* CXL Latency Optimized 256B Flit Capable */ +#define PCI_CXL_FB_CAP_PBR_FLIT 0x4000 /* CXL PBR Flit Capable */ +#define PCI_CXL_FB_PORT_CTRL 0x0c /* CXL Flex Bus Port Control Register */ +#define PCI_CXL_FB_CTRL_CACHE 0x0001 /* CXL.cache Enable */ +#define PCI_CXL_FB_CTRL_IO 0x0002 /* CXL.io Enable */ +#define PCI_CXL_FB_CTRL_MEM 0x0004 /* CXL.mem Enable */ +#define PCI_CXL_FB_CTRL_SYNC_HDR_BYP 0x0008 /* CXL Sync Header Bypass Enable */ +#define PCI_CXL_FB_CTRL_DRFT_BUF 0x0010 /* Drift Buffer Enable */ +#define PCI_CXL_FB_CTRL_68B_FLIT 0x0020 /* CXL 68B Flit and VH Enable */ +#define PCI_CXL_FB_CTRL_MULT_LOG_DEV 0x0040 /* CXL Multi Logical Device Enable */ +#define PCI_CXL_FB_CTRL_RCD 0x0080 /* Disable RCD Training */ +#define PCI_CXL_FB_CTRL_RETIMER1 0x0100 /* Retimer1 Present */ +#define PCI_CXL_FB_CTRL_RETIMER2 0x0200 /* Retimer2 Present */ +#define PCI_CXL_FB_CTRL_256B_FLIT 0x2000 /* CXL Latency Optimized 256B Flit Enable */ +#define PCI_CXL_FB_CTRL_PBR_FLIT 0x4000 /* CXL PBR Flit Enable */ +#define PCI_CXL_FB_PORT_STATUS 0x0e /* CXL Flex Bus Port Status Register */ +#define PCI_CXL_FB_STAT_CACHE 0x0001 /* CXL.cache Enabled */ +#define PCI_CXL_FB_STAT_IO 0x0002 /* CXL.io Enabled */ +#define PCI_CXL_FB_STAT_MEM 0x0004 /* CXL.mem Enabled */ +#define PCI_CXL_FB_STAT_SYNC_HDR_BYP 0x0008 /* CXL Sync Header Bypass Enabled */ +#define PCI_CXL_FB_STAT_DRFT_BUF 0x0010 /* Drift Buffer Enabled */ +#define PCI_CXL_FB_STAT_68B_FLIT 0x0020 /* CXL 68B Flit and VH Enabled */ +#define PCI_CXL_FB_STAT_MULT_LOG_DEV 0x0040 /* CXL Multi Logical Device Enabled */ +#define PCI_CXL_FB_STAT_256B_FLIT 0x2000 /* CXL Latency Optimized 256B Flit Enabled */ +#define PCI_CXL_FB_STAT_PBR_FLIT 0x4000 /* CXL PBR Flit Enabled */ +#define PCI_CXL_FB_MOD_TS_DATA 0x10 /* CXL Flex Bus Port Received Modified TS Data Phase1 Register */ +#define PCI_CXL_FB_PORT_CAP2 0x14 /* CXL Flex Bus Port Capability2 Register */ +#define PCI_CXL_FB_CAP2_NOP_HINT 0x01 /* NOP Hint Capable */ +#define PCI_CXL_FB_PORT_CTRL2 0x18 /* CXL Flex Bus Port Control2 Register */ +#define PCI_CXL_FB_CTRL2_NOP_HINT 0x01 /* NOP Hint Enable */ +#define PCI_CXL_FB_PORT_STATUS2 0x1c /* CXL Flex Bus Port Status2 Register */ +#define PCI_CXL_FB_NEXT_UNSUPPORTED 0x20 + +/* PCIe CXL Designated Vendor-Specific Capabilities for Multi-Logical Device */ +#define PCI_CXL_MLD_LEN 0x10 +#define PCI_CXL_MLD_NUM_LD 0xa +#define PCI_CXL_MLD_MAX_LD 0x10 + +/* PCIe CXL Designated Vendor-Specific Capabilities for Non-CXL Function Map */ +#define PCI_CXL_FUN_MAP_LEN 0x2c +#define PCI_CXL_FUN_MAP_REG_0 0x0c +#define PCI_CXL_FUN_MAP_REG_1 0x10 +#define PCI_CXL_FUN_MAP_REG_2 0x14 +#define PCI_CXL_FUN_MAP_REG_3 0x18 +#define PCI_CXL_FUN_MAP_REG_4 0x1c +#define PCI_CXL_FUN_MAP_REG_5 0x20 +#define PCI_CXL_FUN_MAP_REG_6 0x24 +#define PCI_CXL_FUN_MAP_REG_7 0x28 + +/* Access Control Services */ +#define PCI_ACS_CAP 0x04 /* ACS Capability Register */ +#define PCI_ACS_CAP_VALID 0x0001 /* ACS Source Validation */ +#define PCI_ACS_CAP_BLOCK 0x0002 /* ACS Translation Blocking */ +#define PCI_ACS_CAP_REQ_RED 0x0004 /* ACS P2P Request Redirect */ +#define PCI_ACS_CAP_CMPLT_RED 0x0008 /* ACS P2P Completion Redirect */ +#define PCI_ACS_CAP_FORWARD 0x0010 /* ACS Upstream Forwarding */ +#define PCI_ACS_CAP_EGRESS 0x0020 /* ACS P2P Egress Control */ +#define PCI_ACS_CAP_TRANS 0x0040 /* ACS Direct Translated P2P */ +#define PCI_ACS_CAP_VECTOR(x) (((x) >> 8) & 0xff) /* Egress Control Vector Size */ +#define PCI_ACS_CTRL 0x06 /* ACS Control Register */ +#define PCI_ACS_CTRL_VALID 0x0001 /* ACS Source Validation Enable */ +#define PCI_ACS_CTRL_BLOCK 0x0002 /* ACS Translation Blocking Enable */ +#define PCI_ACS_CTRL_REQ_RED 0x0004 /* ACS P2P Request Redirect Enable */ +#define PCI_ACS_CTRL_CMPLT_RED 0x0008 /* ACS P2P Completion Redirect Enable */ +#define PCI_ACS_CTRL_FORWARD 0x0010 /* ACS Upstream Forwarding Enable */ +#define PCI_ACS_CTRL_EGRESS 0x0020 /* ACS P2P Egress Control Enable */ +#define PCI_ACS_CTRL_TRANS 0x0040 /* ACS Direct Translated P2P Enable */ +#define PCI_ACS_EGRESS_CTRL 0x08 /* Egress Control Vector */ + +/* Alternative Routing-ID Interpretation */ +#define PCI_ARI_CAP 0x04 /* ARI Capability Register */ +#define PCI_ARI_CAP_MFVC 0x0001 /* MFVC Function Groups Capability */ +#define PCI_ARI_CAP_ACS 0x0002 /* ACS Function Groups Capability */ +#define PCI_ARI_CAP_NFN(x) (((x) >> 8) & 0xff) /* Next Function Number */ +#define PCI_ARI_CTRL 0x06 /* ARI Control Register */ +#define PCI_ARI_CTRL_MFVC 0x0001 /* MFVC Function Groups Enable */ +#define PCI_ARI_CTRL_ACS 0x0002 /* ACS Function Groups Enable */ +#define PCI_ARI_CTRL_FG(x) (((x) >> 4) & 7) /* Function Group */ + +/* Address Translation Service */ +#define PCI_ATS_CAP 0x04 /* ATS Capability Register */ +#define PCI_ATS_CAP_IQD(x) ((x) & 0x1f) /* Invalidate Queue Depth */ +#define PCI_ATS_CTRL 0x06 /* ATS Control Register */ +#define PCI_ATS_CTRL_STU(x) ((x) & 0x1f) /* Smallest Translation Unit */ +#define PCI_ATS_CTRL_ENABLE 0x8000 /* ATS Enable */ + +/* Single Root I/O Virtualization */ +#define PCI_IOV_CAP 0x04 /* SR-IOV Capability Register */ +#define PCI_IOV_CAP_VFM 0x00000001 /* VF Migration Capable */ +#define PCI_IOV_CAP_VF_10BIT_TAG_REQ 0x00000004 /* VF 10-Bit Tag Requester Supported */ +#define PCI_IOV_CAP_IMN(x) ((x) >> 21) /* VF Migration Interrupt Message Number */ +#define PCI_IOV_CTRL 0x08 /* SR-IOV Control Register */ +#define PCI_IOV_CTRL_VFE 0x0001 /* VF Enable */ +#define PCI_IOV_CTRL_VFME 0x0002 /* VF Migration Enable */ +#define PCI_IOV_CTRL_VFMIE 0x0004 /* VF Migration Interrupt Enable */ +#define PCI_IOV_CTRL_MSE 0x0008 /* VF MSE */ +#define PCI_IOV_CTRL_ARI 0x0010 /* ARI Capable Hierarchy */ +#define PCI_IOV_CTRL_VF_10BIT_TAG_REQ_EN 0x0020 /* VF 10-Bit Tag Requester Enable */ +#define PCI_IOV_STATUS 0x0a /* SR-IOV Status Register */ +#define PCI_IOV_STATUS_MS 0x0001 /* VF Migration Status */ +#define PCI_IOV_INITIALVF 0x0c /* Number of VFs that are initially associated */ +#define PCI_IOV_TOTALVF 0x0e /* Maximum number of VFs that could be associated */ +#define PCI_IOV_NUMVF 0x10 /* Number of VFs that are available */ +#define PCI_IOV_FDL 0x12 /* Function Dependency Link */ +#define PCI_IOV_OFFSET 0x14 /* First VF Offset */ +#define PCI_IOV_STRIDE 0x16 /* Routing ID offset from one VF to the next one */ +#define PCI_IOV_DID 0x1a /* VF Device ID */ +#define PCI_IOV_SUPPS 0x1c /* Supported Page Sizes */ +#define PCI_IOV_SYSPS 0x20 /* System Page Size */ +#define PCI_IOV_BAR_BASE 0x24 /* VF BAR0, VF BAR1, ... VF BAR5 */ +#define PCI_IOV_NUM_BAR 6 /* Number of VF BARs */ +#define PCI_IOV_MSAO 0x3c /* VF Migration State Array Offset */ +#define PCI_IOV_MSA_BIR(x) ((x) & 7) /* VF Migration State BIR */ +#define PCI_IOV_MSA_OFFSET(x) ((x) & 0xfffffff8) /* VF Migration State Offset */ + +/* Multicast */ +#define PCI_MCAST_CAP 0x04 /* Multicast Capability */ +#define PCI_MCAST_CAP_MAX_GROUP(x) ((x) & 0x3f) +#define PCI_MCAST_CAP_WIN_SIZE(x) (((x) >> 8) & 0x3f) +#define PCI_MCAST_CAP_ECRC 0x8000 /* ECRC Regeneration Supported */ +#define PCI_MCAST_CTRL 0x06 /* Multicast Control */ +#define PCI_MCAST_CTRL_NUM_GROUP(x) ((x) & 0x3f) +#define PCI_MCAST_CTRL_ENABLE 0x8000 /* MC Enabled */ +#define PCI_MCAST_BAR 0x08 /* Base Address */ +#define PCI_MCAST_BAR_INDEX_POS(x) ((u32) ((x) & 0x3f)) +#define PCI_MCAST_BAR_MASK (~0xfffUL) +#define PCI_MCAST_RCV 0x10 /* Receive */ +#define PCI_MCAST_BLOCK 0x18 /* Block All */ +#define PCI_MCAST_BLOCK_UNTRANS 0x20 /* Block Untranslated */ +#define PCI_MCAST_OVL_BAR 0x28 /* Overlay BAR */ +#define PCI_MCAST_OVL_SIZE(x) ((u32) ((x) & 0x3f)) +#define PCI_MCAST_OVL_MASK (~0x3fUL) + +/* Page Request Interface */ +#define PCI_PRI_CTRL 0x04 /* PRI Control Register */ +#define PCI_PRI_CTRL_ENABLE 0x01 /* Enable */ +#define PCI_PRI_CTRL_RESET 0x02 /* Reset */ +#define PCI_PRI_STATUS 0x06 /* PRI status register */ +#define PCI_PRI_STATUS_RF 0x0001 /* Response Failure */ +#define PCI_PRI_STATUS_UPRGI 0x0002 /* Unexpected PRG index */ +#define PCI_PRI_STATUS_STOPPED 0x0100 /* PRI Stopped */ +#define PCI_PRI_STATUS_PASID 0x8000 /* PASID required in PRG response */ +#define PCI_PRI_MAX_REQ 0x08 /* PRI max reqs supported */ +#define PCI_PRI_ALLOC_REQ 0x0c /* PRI max reqs allowed */ + +/* Transaction Processing Hints */ +#define PCI_TPH_CAPABILITIES 4 +#define PCI_TPH_INTVEC_SUP (1<<1) /* Supports interrupt vector mode */ +#define PCI_TPH_DEV_SUP (1<<2) /* Device specific mode supported */ +#define PCI_TPH_EXT_REQ_SUP (1<<8) /* Supports extended requests */ +#define PCI_TPH_ST_LOC_MASK (3<<9) /* Steering table location bits */ +#define PCI_TPH_ST_NONE (0<<9) /* No steering table */ +#define PCI_TPH_ST_CAP (1<<9) /* Steering table in TPH cap */ +#define PCI_TPH_ST_MSIX (2<<9) /* Steering table in MSI-X table */ +#define PCI_TPH_ST_SIZE_SHIFT (16) /* Encoded as size - 1 */ + +/* Latency Tolerance Reporting */ +#define PCI_LTR_MAX_SNOOP 4 /* 16 bit value */ +#define PCI_LTR_VALUE_MASK (0x3ff) +#define PCI_LTR_SCALE_SHIFT (10) +#define PCI_LTR_SCALE_MASK (7) +#define PCI_LTR_MAX_NOSNOOP 6 /* 16 bit value */ + +/* Secondary PCI Express Extended Capability */ +#define PCI_SEC_LNKCTL3 4 /* Link Control 3 register */ +#define PCI_SEC_LNKCTL3_PERFORM_LINK_EQU 0x01 +#define PCI_SEC_LNKCTL3_LNK_EQU_REQ_INTR_EN 0x02 +#define PCI_SEC_LNKCTL3_ENBL_LOWER_SKP_OS_GEN_VEC(x) ((x >> 8) & 0x7F) +#define PCI_SEC_LANE_ERR 8 /* Lane Error status register */ +#define PCI_SEC_LANE_EQU_CTRL 12 /* Lane Equalization control register */ + +/* Process Address Space ID */ +#define PCI_PASID_CAP 0x04 /* PASID feature register */ +#define PCI_PASID_CAP_EXEC 0x02 /* Exec permissions Supported */ +#define PCI_PASID_CAP_PRIV 0x04 /* Privilege Mode Supported */ +#define PCI_PASID_CAP_WIDTH(x) (((x) >> 8) & 0x1f) /* Max PASID Width */ +#define PCI_PASID_CTRL 0x06 /* PASID control register */ +#define PCI_PASID_CTRL_ENABLE 0x01 /* Enable bit */ +#define PCI_PASID_CTRL_EXEC 0x02 /* Exec permissions Enable */ +#define PCI_PASID_CTRL_PRIV 0x04 /* Privilege Mode Enable */ + +#define PCI_DPC_CAP 4 /* DPC Capability */ +#define PCI_DPC_CAP_INT_MSG(x) ((x) & 0x1f) /* DPC Interrupt Message Number */ +#define PCI_DPC_CAP_RP_EXT 0x20 /* DPC Root Port Extensions */ +#define PCI_DPC_CAP_TLP_BLOCK 0x40 /* DPC Poisoned TLP Egress Blocking */ +#define PCI_DPC_CAP_SW_TRIGGER 0x80 /* DPC Software Trigger */ +#define PCI_DPC_CAP_RP_LOG(x) (((x) >> 8) & 0xf) /* DPC RP PIO Log Size */ +#define PCI_DPC_CAP_DL_ACT_ERR 0x1000 /* DPC DL_Active ERR_COR Signal */ +#define PCI_DPC_CTL 6 /* DPC Control */ +#define PCI_DPC_CTL_TRIGGER(x) ((x) & 0x3) /* DPC Trigger Enable */ +#define PCI_DPC_CTL_CMPL 0x4 /* DPC Completion Control */ +#define PCI_DPC_CTL_INT 0x8 /* DPC Interrupt Enabled */ +#define PCI_DPC_CTL_ERR_COR 0x10 /* DPC ERR_COR Enabled */ +#define PCI_DPC_CTL_TLP 0x20 /* DPC Poisoned TLP Egress Blocking Enabled */ +#define PCI_DPC_CTL_SW_TRIGGER 0x40 /* DPC Software Trigger */ +#define PCI_DPC_CTL_DL_ACTIVE 0x80 /* DPC DL_Active ERR_COR Enable */ +#define PCI_DPC_STATUS 8 /* DPC STATUS */ +#define PCI_DPC_STS_TRIGGER 0x01 /* DPC Trigger Status */ +#define PCI_DPC_STS_REASON(x) (((x) >> 1) & 0x3) /* DPC Trigger Reason */ +#define PCI_DPC_STS_INT 0x08 /* DPC Interrupt Status */ +#define PCI_DPC_STS_RP_BUSY 0x10 /* DPC Root Port Busy */ +#define PCI_DPC_STS_TRIGGER_EXT(x) (((x) >> 5) & 0x3) /* Trigger Reason Extension */ +#define PCI_DPC_STS_PIO_FEP(x) (((x) >> 8) & 0x1f) /* DPC PIO First Error Pointer */ +#define PCI_DPC_SOURCE 10 /* DPC Source ID */ + +/* L1 PM Substates Extended Capability */ +#define PCI_L1PM_SUBSTAT_CAP 0x4 /* L1 PM Substate Capability */ +#define PCI_L1PM_SUBSTAT_CAP_PM_L12 0x1 /* PCI-PM L1.2 Supported */ +#define PCI_L1PM_SUBSTAT_CAP_PM_L11 0x2 /* PCI-PM L1.1 Supported */ +#define PCI_L1PM_SUBSTAT_CAP_ASPM_L12 0x4 /* ASPM L1.2 Supported */ +#define PCI_L1PM_SUBSTAT_CAP_ASPM_L11 0x8 /* ASPM L1.1 Supported */ +#define PCI_L1PM_SUBSTAT_CAP_L1PM_SUPP 0x10 /* L1 PM Substates supported */ +#define PCI_L1PM_SUBSTAT_CTL1 0x8 /* L1 PM Substate Control 1 */ +#define PCI_L1PM_SUBSTAT_CTL1_PM_L12 0x1 /* PCI-PM L1.2 Enable */ +#define PCI_L1PM_SUBSTAT_CTL1_PM_L11 0x2 /* PCI-PM L1.1 Enable */ +#define PCI_L1PM_SUBSTAT_CTL1_ASPM_L12 0x4 /* ASPM L1.2 Enable */ +#define PCI_L1PM_SUBSTAT_CTL1_ASPM_L11 0x8 /* ASPM L1.1 Enable */ +#define PCI_L1PM_SUBSTAT_CTL2 0xC /* L1 PM Substate Control 2 */ + +/* Data Object Exchange Extended Capability */ +#define PCI_DOE_CAP 0x4 /* DOE Capabilities Register */ +#define PCI_DOE_CAP_INT_SUPP 0x1 /* Interrupt Support */ +#define PCI_DOE_CAP_INT_MSG(x) (((x) >> 1) & 0x7ff) /* DOE Interrupt Message Number */ +#define PCI_DOE_CTL 0x8 /* DOE Control Register */ +#define PCI_DOE_CTL_ABORT 0x1 /* DOE Abort */ +#define PCI_DOE_CTL_INT 0x2 /* DOE Interrupt Enable */ +#define PCI_DOE_CTL_GO 0x80000000 /* DOE Go */ +#define PCI_DOE_STS 0xC /* DOE Status Register */ +#define PCI_DOE_STS_BUSY 0x1 /* DOE Busy */ +#define PCI_DOE_STS_INT 0x2 /* DOE Interrupt Status */ +#define PCI_DOE_STS_ERROR 0x4 /* DOE Error */ +#define PCI_DOE_STS_OBJECT_READY 0x80000000 /* Data Object Ready */ + +/* Lane Margining at the Receiver Extended Capability */ +#define PCI_LMR_CAPS 0x4 /* Margining Port Capabilities Register */ +#define PCI_LMR_CAPS_DRVR 0x1 /* Margining uses Driver Software */ +#define PCI_LMR_PORT_STS 0x6 /* Margining Port Status Register */ +#define PCI_LMR_PORT_STS_READY 0x1 /* Margining Ready */ +#define PCI_LMR_PORT_STS_SOFT_READY 0x2 /* Margining Software Ready */ + +/* + * The PCI interface treats multi-function devices as independent + * devices. The slot/function address of each device is encoded + * in a single byte as follows: + * + * 7:3 = slot + * 2:0 = function + */ +#define PCI_DEVFN(slot,func) ((((slot) & 0x1f) << 3) | ((func) & 0x07)) +#define PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) +#define PCI_FUNC(devfn) ((devfn) & 0x07) + +/* Device classes and subclasses */ + +#define PCI_CLASS_NOT_DEFINED 0x0000 +#define PCI_CLASS_NOT_DEFINED_VGA 0x0001 + +#define PCI_BASE_CLASS_STORAGE 0x01 +#define PCI_CLASS_STORAGE_SCSI 0x0100 +#define PCI_CLASS_STORAGE_IDE 0x0101 +#define PCI_CLASS_STORAGE_FLOPPY 0x0102 +#define PCI_CLASS_STORAGE_IPI 0x0103 +#define PCI_CLASS_STORAGE_RAID 0x0104 +#define PCI_CLASS_STORAGE_ATA 0x0105 +#define PCI_CLASS_STORAGE_SATA 0x0106 +#define PCI_CLASS_STORAGE_SAS 0x0107 +#define PCI_CLASS_STORAGE_OTHER 0x0180 + +#define PCI_BASE_CLASS_NETWORK 0x02 +#define PCI_CLASS_NETWORK_ETHERNET 0x0200 +#define PCI_CLASS_NETWORK_TOKEN_RING 0x0201 +#define PCI_CLASS_NETWORK_FDDI 0x0202 +#define PCI_CLASS_NETWORK_ATM 0x0203 +#define PCI_CLASS_NETWORK_ISDN 0x0204 +#define PCI_CLASS_NETWORK_OTHER 0x0280 + +#define PCI_BASE_CLASS_DISPLAY 0x03 +#define PCI_CLASS_DISPLAY_VGA 0x0300 +#define PCI_CLASS_DISPLAY_XGA 0x0301 +#define PCI_CLASS_DISPLAY_3D 0x0302 +#define PCI_CLASS_DISPLAY_OTHER 0x0380 + +#define PCI_BASE_CLASS_MULTIMEDIA 0x04 +#define PCI_CLASS_MULTIMEDIA_VIDEO 0x0400 +#define PCI_CLASS_MULTIMEDIA_AUDIO 0x0401 +#define PCI_CLASS_MULTIMEDIA_PHONE 0x0402 +#define PCI_CLASS_MULTIMEDIA_AUDIO_DEV 0x0403 +#define PCI_CLASS_MULTIMEDIA_OTHER 0x0480 + +#define PCI_BASE_CLASS_MEMORY 0x05 +#define PCI_CLASS_MEMORY_RAM 0x0500 +#define PCI_CLASS_MEMORY_FLASH 0x0501 +#define PCI_CLASS_MEMORY_OTHER 0x0580 + +#define PCI_BASE_CLASS_BRIDGE 0x06 +#define PCI_CLASS_BRIDGE_HOST 0x0600 +#define PCI_CLASS_BRIDGE_ISA 0x0601 +#define PCI_CLASS_BRIDGE_EISA 0x0602 +#define PCI_CLASS_BRIDGE_MC 0x0603 +#define PCI_CLASS_BRIDGE_PCI 0x0604 +#define PCI_CLASS_BRIDGE_PCMCIA 0x0605 +#define PCI_CLASS_BRIDGE_NUBUS 0x0606 +#define PCI_CLASS_BRIDGE_CARDBUS 0x0607 +#define PCI_CLASS_BRIDGE_RACEWAY 0x0608 +#define PCI_CLASS_BRIDGE_PCI_SEMI 0x0609 +#define PCI_CLASS_BRIDGE_IB_TO_PCI 0x060a +#define PCI_CLASS_BRIDGE_OTHER 0x0680 + +#define PCI_BASE_CLASS_COMMUNICATION 0x07 +#define PCI_CLASS_COMMUNICATION_SERIAL 0x0700 +#define PCI_CLASS_COMMUNICATION_PARALLEL 0x0701 +#define PCI_CLASS_COMMUNICATION_MSERIAL 0x0702 +#define PCI_CLASS_COMMUNICATION_MODEM 0x0703 +#define PCI_CLASS_COMMUNICATION_OTHER 0x0780 + +#define PCI_BASE_CLASS_SYSTEM 0x08 +#define PCI_CLASS_SYSTEM_PIC 0x0800 +#define PCI_CLASS_SYSTEM_DMA 0x0801 +#define PCI_CLASS_SYSTEM_TIMER 0x0802 +#define PCI_CLASS_SYSTEM_RTC 0x0803 +#define PCI_CLASS_SYSTEM_PCI_HOTPLUG 0x0804 +#define PCI_CLASS_SYSTEM_OTHER 0x0880 + +#define PCI_BASE_CLASS_INPUT 0x09 +#define PCI_CLASS_INPUT_KEYBOARD 0x0900 +#define PCI_CLASS_INPUT_PEN 0x0901 +#define PCI_CLASS_INPUT_MOUSE 0x0902 +#define PCI_CLASS_INPUT_SCANNER 0x0903 +#define PCI_CLASS_INPUT_GAMEPORT 0x0904 +#define PCI_CLASS_INPUT_OTHER 0x0980 + +#define PCI_BASE_CLASS_DOCKING 0x0a +#define PCI_CLASS_DOCKING_GENERIC 0x0a00 +#define PCI_CLASS_DOCKING_OTHER 0x0a80 + +#define PCI_BASE_CLASS_PROCESSOR 0x0b +#define PCI_CLASS_PROCESSOR_386 0x0b00 +#define PCI_CLASS_PROCESSOR_486 0x0b01 +#define PCI_CLASS_PROCESSOR_PENTIUM 0x0b02 +#define PCI_CLASS_PROCESSOR_ALPHA 0x0b10 +#define PCI_CLASS_PROCESSOR_POWERPC 0x0b20 +#define PCI_CLASS_PROCESSOR_MIPS 0x0b30 +#define PCI_CLASS_PROCESSOR_CO 0x0b40 + +#define PCI_BASE_CLASS_SERIAL 0x0c +#define PCI_CLASS_SERIAL_FIREWIRE 0x0c00 +#define PCI_CLASS_SERIAL_ACCESS 0x0c01 +#define PCI_CLASS_SERIAL_SSA 0x0c02 +#define PCI_CLASS_SERIAL_USB 0x0c03 +#define PCI_CLASS_SERIAL_FIBER 0x0c04 +#define PCI_CLASS_SERIAL_SMBUS 0x0c05 +#define PCI_CLASS_SERIAL_INFINIBAND 0x0c06 + +#define PCI_BASE_CLASS_WIRELESS 0x0d +#define PCI_CLASS_WIRELESS_IRDA 0x0d00 +#define PCI_CLASS_WIRELESS_CONSUMER_IR 0x0d01 +#define PCI_CLASS_WIRELESS_RF 0x0d10 +#define PCI_CLASS_WIRELESS_OTHER 0x0d80 + +#define PCI_BASE_CLASS_INTELLIGENT 0x0e +#define PCI_CLASS_INTELLIGENT_I2O 0x0e00 + +#define PCI_BASE_CLASS_SATELLITE 0x0f +#define PCI_CLASS_SATELLITE_TV 0x0f00 +#define PCI_CLASS_SATELLITE_AUDIO 0x0f01 +#define PCI_CLASS_SATELLITE_VOICE 0x0f03 +#define PCI_CLASS_SATELLITE_DATA 0x0f04 + +#define PCI_BASE_CLASS_CRYPT 0x10 +#define PCI_CLASS_CRYPT_NETWORK 0x1000 +#define PCI_CLASS_CRYPT_ENTERTAINMENT 0x1010 +#define PCI_CLASS_CRYPT_OTHER 0x1080 + +#define PCI_BASE_CLASS_SIGNAL 0x11 +#define PCI_CLASS_SIGNAL_DPIO 0x1100 +#define PCI_CLASS_SIGNAL_PERF_CTR 0x1101 +#define PCI_CLASS_SIGNAL_SYNCHRONIZER 0x1110 +#define PCI_CLASS_SIGNAL_OTHER 0x1180 + +#define PCI_CLASS_OTHERS 0xff + +/* Several ID's we need in the library */ + +#define PCI_VENDOR_ID_INTEL 0x8086 +#define PCI_VENDOR_ID_COMPAQ 0x0e11 + +/* I/O resource flags, compatible with <include/linux/ioport.h> */ + +#define PCI_IORESOURCE_TYPE_BITS 0x00001f00 +#define PCI_IORESOURCE_IO 0x00000100 +#define PCI_IORESOURCE_MEM 0x00000200 +#define PCI_IORESOURCE_PREFETCH 0x00002000 +#define PCI_IORESOURCE_MEM_64 0x00100000 +#define PCI_IORESOURCE_IO_16BIT_ADDR (1<<0) +#define PCI_IORESOURCE_PCI_EA_BEI (1<<5) diff --git a/lib/hurd.c b/lib/hurd.c new file mode 100644 index 0000000..3e65fb8 --- /dev/null +++ b/lib/hurd.c @@ -0,0 +1,372 @@ +/* + * The PCI Library -- Hurd access via RPCs + * + * Copyright (c) 2017 Joan Lledó <jlledom@member.fsf.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define _GNU_SOURCE + +#include "internal.h" + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include <fcntl.h> +#include <string.h> +#include <hurd.h> +#include <hurd/pci.h> +#include <hurd/paths.h> + +/* Server path */ +#define _SERVERS_BUS_PCI _SERVERS_BUS "/pci" + +/* File names */ +#define FILE_CONFIG_NAME "config" +#define FILE_ROM_NAME "rom" + +/* Level in the fs tree */ +typedef enum +{ + LEVEL_NONE, + LEVEL_DOMAIN, + LEVEL_BUS, + LEVEL_DEV, + LEVEL_FUNC +} tree_level; + +/* Check whether there's a pci server */ +static int +hurd_detect(struct pci_access *a) +{ + int err; + struct stat st; + + err = stat(_SERVERS_BUS_PCI, &st); + if (err) + { + a->error("Could not open file `%s'", _SERVERS_BUS_PCI); + return 0; + } + + /* The node must be a directory and a translator */ + return S_ISDIR(st.st_mode) && ((st.st_mode & S_ITRANS) == S_IROOT); +} + +/* Empty callbacks, we don't need any special init or cleanup */ +static void +hurd_init(struct pci_access *a UNUSED) +{ +} + +static void +hurd_cleanup(struct pci_access *a UNUSED) +{ +} + +/* Each device has its own server path. Allocate space for the port. */ +static void +hurd_init_dev(struct pci_dev *d) +{ + d->backend_data = pci_malloc(d->access, sizeof(mach_port_t)); + *((mach_port_t *) d->backend_data) = MACH_PORT_NULL; +} + +/* Deallocate the port and free its space */ +static void +hurd_cleanup_dev(struct pci_dev *d) +{ + mach_port_t device_port; + + device_port = *((mach_port_t *) d->backend_data); + mach_port_deallocate(mach_task_self(), device_port); + + pci_mfree(d->backend_data); + d->backend_data = NULL; +} + +static mach_port_t +device_port_lookup(struct pci_dev *d) +{ + char server[NAME_MAX]; + mach_port_t device_port = *((mach_port_t *) d->backend_data); + + if (device_port != MACH_PORT_NULL) + return device_port; + + snprintf(server, NAME_MAX, "%s/%04x/%02x/%02x/%01u/%s", + _SERVERS_BUS_PCI, d->domain, d->bus, d->dev, d->func, + FILE_CONFIG_NAME); + device_port = file_name_lookup(server, 0, 0); + + if (device_port == MACH_PORT_NULL) + d->access->error("Cannot find the PCI arbiter"); + + *((mach_port_t *) d->backend_data) = device_port; + return device_port; +} + +/* Walk through the FS tree to see what is allowed for us */ +static void +enum_devices(const char *parent, struct pci_access *a, int domain, int bus, + int dev, int func, tree_level lev) +{ + int ret; + DIR *dir; + struct dirent *entry; + char path[NAME_MAX]; + struct pci_dev *d; + + dir = opendir(parent); + if (!dir) + { + if (errno == EPERM || errno == EACCES) + /* The client lacks the permissions to access this function, skip */ + return; + else + a->error("Cannot open directory: %s (%s)", parent, strerror(errno)); + } + + while ((entry = readdir(dir)) != 0) + { + snprintf(path, NAME_MAX, "%s/%s", parent, entry->d_name); + if (entry->d_type == DT_DIR) + { + if (!strncmp(entry->d_name, ".", NAME_MAX) + || !strncmp(entry->d_name, "..", NAME_MAX)) + continue; + + errno = 0; + ret = strtol(entry->d_name, 0, 16); + if (errno) + { + if (closedir(dir) < 0) + a->warning("Cannot close directory: %s (%s)", parent, + strerror(errno)); + a->error("Wrong directory name: %s (number expected) probably " + "not connected to an arbiter", entry->d_name); + } + + /* + * We found a valid directory. + * Update the address and switch to the next level. + */ + switch (lev) + { + case LEVEL_DOMAIN: + domain = ret; + break; + case LEVEL_BUS: + bus = ret; + break; + case LEVEL_DEV: + dev = ret; + break; + case LEVEL_FUNC: + func = ret; + break; + default: + if (closedir(dir) < 0) + a->warning("Cannot close directory: %s (%s)", parent, + strerror(errno)); + a->error("Wrong directory tree, probably not connected to an arbiter"); + } + + enum_devices(path, a, domain, bus, dev, func, lev + 1); + } + else + { + if (strncmp(entry->d_name, FILE_CONFIG_NAME, NAME_MAX)) + /* We are looking for the config file */ + continue; + + /* We found an available virtual device, add it to our list */ + d = pci_alloc_dev(a); + d->domain = domain; + d->bus = bus; + d->dev = dev; + d->func = func; + pci_link_dev(a, d); + } + } + + if (closedir(dir) < 0) + a->error("Cannot close directory: %s (%s)", parent, strerror(errno)); +} + +/* Enumerate devices */ +static void +hurd_scan(struct pci_access *a) +{ + enum_devices(_SERVERS_BUS_PCI, a, -1, -1, -1, -1, LEVEL_DOMAIN); +} + +/* + * Read `len' bytes to `buf'. + * + * Returns error when the number of read bytes does not match `len'. + */ +static int +hurd_read(struct pci_dev *d, int pos, byte * buf, int len) +{ + int err; + size_t nread; + char *data; + mach_port_t device_port = device_port_lookup(d); + + if (len > 4) + return pci_generic_block_read(d, pos, buf, len); + + data = (char *) buf; + err = pci_conf_read(device_port, pos, &data, &nread, len); + + if (data != (char *) buf) + { + if (nread > (size_t) len) /* Sanity check for bogus server. */ + { + vm_deallocate(mach_task_self(), (vm_address_t) data, nread); + return 0; + } + + memcpy(buf, data, nread); + vm_deallocate(mach_task_self(), (vm_address_t) data, nread); + } + + return !err && nread == (size_t) len; +} + +/* + * Write `len' bytes from `buf'. + * + * Returns error when the number of written bytes does not match `len'. + */ +static int +hurd_write(struct pci_dev *d, int pos, byte * buf, int len) +{ + int err; + size_t nwrote; + mach_port_t device_port = device_port_lookup(d); + + if (len > 4) + return pci_generic_block_write(d, pos, buf, len); + + err = pci_conf_write(device_port, pos, (char *) buf, len, &nwrote); + + return !err && nwrote == (size_t) len; +} + +/* Get requested info from the server */ + +static int +hurd_fill_regions(struct pci_dev *d) +{ + mach_port_t device_port = device_port_lookup(d); + struct pci_bar regions[6]; + char *buf = (char *) ®ions; + size_t size = sizeof(regions); + + int err = pci_get_dev_regions(device_port, &buf, &size); + if (err) + return 0; + + if ((char *) ®ions != buf) + { + /* Sanity check for bogus server. */ + if (size > sizeof(regions)) + { + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + return 0; + } + + memcpy(®ions, buf, size); + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + } + + for (int i = 0; i < 6; i++) + { + if (regions[i].size == 0) + continue; + + d->base_addr[i] = regions[i].base_addr; + d->base_addr[i] |= regions[i].is_IO; + d->base_addr[i] |= regions[i].is_64 << 2; + d->base_addr[i] |= regions[i].is_prefetchable << 3; + + d->size[i] = regions[i].size; + } + + return 1; +} + +static int +hurd_fill_rom(struct pci_dev *d) +{ + struct pci_xrom_bar rom; + mach_port_t device_port = device_port_lookup(d); + char *buf = (char *) &rom; + size_t size = sizeof(rom); + + int err = pci_get_dev_rom(device_port, &buf, &size); + if (err) + return 0; + + if ((char *) &rom != buf) + { + /* Sanity check for bogus server. */ + if (size > sizeof(rom)) + { + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + return 0; + } + + memcpy(&rom, buf, size); + vm_deallocate(mach_task_self(), (vm_address_t) buf, size); + } + + d->rom_base_addr = rom.base_addr; + d->rom_size = rom.size; + + return 1; +} + +static void +hurd_fill_info(struct pci_dev *d, unsigned int flags) +{ + if (!d->access->buscentric) + { + if (want_fill(d, flags, PCI_FILL_BASES | PCI_FILL_SIZES)) + { + if (hurd_fill_regions(d)) + clear_fill(d, PCI_FILL_BASES | PCI_FILL_SIZES); + } + if (want_fill(d, flags, PCI_FILL_ROM_BASE)) + { + if (hurd_fill_rom(d)) + clear_fill(d, PCI_FILL_ROM_BASE); + } + } + + pci_generic_fill_info(d, flags); +} + +struct pci_methods pm_hurd = { + "hurd", + "Hurd access using RPCs", + NULL, /* config */ + hurd_detect, + hurd_init, + hurd_cleanup, + hurd_scan, + hurd_fill_info, + hurd_read, + hurd_write, + NULL, /* read_vpd */ + hurd_init_dev, + hurd_cleanup_dev +}; diff --git a/lib/i386-io-access.h b/lib/i386-io-access.h new file mode 100644 index 0000000..8b1ad5f --- /dev/null +++ b/lib/i386-io-access.h @@ -0,0 +1,75 @@ +/* + * The PCI Library -- Compiler-specific wrappers around x86 I/O port access instructions + * + * Copyright (c) 2023 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#if defined(__GNUC__) + +static inline unsigned char +intel_inb(unsigned short int port) +{ + unsigned char value; + asm volatile ("inb %w1, %0" : "=a" (value) : "Nd" (port)); + return value; +} + +static inline unsigned short int +intel_inw(unsigned short int port) +{ + unsigned short value; + asm volatile ("inw %w1, %0" : "=a" (value) : "Nd" (port)); + return value; +} + +static inline unsigned int +intel_inl(unsigned short int port) +{ + u32 value; + asm volatile ("inl %w1, %0" : "=a" (value) : "Nd" (port)); + return value; +} + +static inline void +intel_outb(unsigned char value, unsigned short int port) +{ + asm volatile ("outb %b0, %w1" : : "a" (value), "Nd" (port)); +} + +static inline void +intel_outw(unsigned short int value, unsigned short int port) +{ + asm volatile ("outw %w0, %w1" : : "a" (value), "Nd" (port)); +} + +static inline void +intel_outl(u32 value, unsigned short int port) +{ + asm volatile ("outl %0, %w1" : : "a" (value), "Nd" (port)); +} + +#elif defined(_MSC_VER) + +#pragma intrinsic(_outp) +#pragma intrinsic(_outpw) +#pragma intrinsic(_outpd) +#pragma intrinsic(_inp) +#pragma intrinsic(_inpw) +#pragma intrinsic(_inpd) + +#define intel_outb(x, y) _outp(y, x) +#define intel_outw(x, y) _outpw(y, x) +#define intel_outl(x, y) _outpd(y, x) +#define intel_inb(x) _inp(x) +#define intel_inw(x) _inpw(x) +#define intel_inl(x) _inpd(x) + +#else + +#error Do not know how to access I/O ports on this compiler + +#endif diff --git a/lib/i386-io-beos.h b/lib/i386-io-beos.h new file mode 100644 index 0000000..dac0e4b --- /dev/null +++ b/lib/i386-io-beos.h @@ -0,0 +1,68 @@ +/* + * The PCI Library -- Access to i386 I/O ports on BeOS + * + * Copyright (c) 2009 Francois Revol <revol@free.fr> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* those are private syscalls */ +extern int read_isa_io(int pci_bus, void *addr, int size); +extern int write_isa_io(int pci_bus, void *addr, int size, u32 value); + +static int +intel_setup_io(struct pci_access *a UNUSED) +{ + return 1; +} + +static inline void +intel_cleanup_io(struct pci_access *a UNUSED) +{ +} + +static inline u8 +intel_inb (u16 port) +{ + return (u8)read_isa_io(0, (void *)(u32)port, sizeof(u8)); +} + +static inline u16 +intel_inw (u16 port) +{ + return (u16)read_isa_io(0, (void *)(u32)port, sizeof(u16)); +} + +static inline u32 +intel_inl (u16 port) +{ + return (u32)read_isa_io(0, (void *)(u32)port, sizeof(u32)); +} + +static inline void +intel_outb (u8 value, u16 port) +{ + write_isa_io(0, (void *)(u32)port, sizeof(value), value); +} + +static inline void +intel_outw (u16 value, u16 port) +{ + write_isa_io(0, (void *)(u32)port, sizeof(value), value); +} + +static inline void +intel_outl (u32 value, u16 port) +{ + write_isa_io(0, (void *)(u32)port, sizeof(value), value); +} + +static inline void intel_io_lock(void) +{ +} + +static inline void intel_io_unlock(void) +{ +} diff --git a/lib/i386-io-cygwin.h b/lib/i386-io-cygwin.h new file mode 100644 index 0000000..4118057 --- /dev/null +++ b/lib/i386-io-cygwin.h @@ -0,0 +1,33 @@ +/* + * The PCI Library -- Access to i386 I/O ports under Windows with CYGWIN + * + * Copyright (c) 1997--2006 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/io.h> + +#include "i386-io-access.h" + +static int +intel_setup_io(struct pci_access *a UNUSED) +{ + return (iopl(3) < 0) ? 0 : 1; +} + +static inline void +intel_cleanup_io(struct pci_access *a UNUSED) +{ + iopl(0); +} + +static inline void intel_io_lock(void) +{ +} + +static inline void intel_io_unlock(void) +{ +} diff --git a/lib/i386-io-djgpp.h b/lib/i386-io-djgpp.h new file mode 100644 index 0000000..1afb00e --- /dev/null +++ b/lib/i386-io-djgpp.h @@ -0,0 +1,40 @@ +/* + * The PCI Library -- Access to i386 I/O ports on DJGPP + * + * Copyright (c) 2010, 2017 Rudolf Marek <r.marek@assembler.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <dos.h> + +#include "i386-io-access.h" + +static int irq_enabled; + +static int +intel_setup_io(struct pci_access *a UNUSED) +{ + return 1; +} + +static inline void +intel_cleanup_io(struct pci_access *a UNUSED) +{ +} + +static inline void intel_io_lock(void) +{ + asm volatile("" : : : "memory"); + irq_enabled = disable(); +} + +static inline void intel_io_unlock(void) +{ + asm volatile("" : : : "memory"); + if (irq_enabled) { + enable(); + } +} diff --git a/lib/i386-io-haiku.h b/lib/i386-io-haiku.h new file mode 100644 index 0000000..23843ea --- /dev/null +++ b/lib/i386-io-haiku.h @@ -0,0 +1,142 @@ +/* + * The PCI Library -- Access to i386 I/O ports on Haiku + * + * Copyright (c) 2009 Francois Revol <revol@free.fr> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <Drivers.h> +#include <ISA.h> +#include <PCI.h> + +/* from haiku/trunk/headers/private/drivers/poke.h */ + +#define POKE_DEVICE_NAME "poke" +#define POKE_DEVICE_FULLNAME "/dev/misc/poke" +#define POKE_SIGNATURE 'wltp' // "We Like To Poke" + +enum { + POKE_PORT_READ = B_DEVICE_OP_CODES_END + 1, + POKE_PORT_WRITE, + POKE_PORT_INDEXED_READ, + POKE_PORT_INDEXED_WRITE, + POKE_PCI_READ_CONFIG, + POKE_PCI_WRITE_CONFIG, + POKE_GET_NTH_PCI_INFO, + POKE_GET_PHYSICAL_ADDRESS, + POKE_MAP_MEMORY, + POKE_UNMAP_MEMORY +}; + + +typedef struct { + uint32 signature; + uint8 index; + pci_info* info; + status_t status; +} pci_info_args; + + +typedef struct { + uint32 signature; + uint16 port; + uint8 size; // == index for POKE_PORT_INDEXED_* + uint32 value; +} port_io_args; + + +typedef struct { + uint32 signature; + uint8 bus; + uint8 device; + uint8 function; + uint8 size; + uint8 offset; + uint32 value; +} pci_io_args; + + +/* en poke.h*/ + +static int poke_driver_fd; + +static int +intel_setup_io(struct pci_access *a UNUSED) +{ + /* + * Opening poke device on systems with the linked change below + * automatically changes process IOPL to 3 and closing its file + * descriptor changes process IOPL back to 0, which give access + * to all x86 IO ports via x86 in/out instructions for this + * userspace process. To support also older systems without this + * change, access IO ports via ioctl() instead of x86 in/out. + * https://review.haiku-os.org/c/haiku/+/1077 + */ + poke_driver_fd = open(POKE_DEVICE_FULLNAME, O_RDWR); + return (poke_driver_fd < 0) ? 0 : 1; +} + +static inline void +intel_cleanup_io(struct pci_access *a UNUSED) +{ + close(poke_driver_fd); +} + +static inline u8 +intel_inb (u16 port) +{ + port_io_args args = { POKE_SIGNATURE, port, sizeof(u8), 0 }; + if (ioctl(poke_driver_fd, POKE_PORT_READ, &args, sizeof(args)) < 0) + return 0; + return (u8)args.value; +} + +static inline u16 +intel_inw (u16 port) +{ + port_io_args args = { POKE_SIGNATURE, port, sizeof(u16), 0 }; + if (ioctl(poke_driver_fd, POKE_PORT_READ, &args, sizeof(args)) < 0) + return 0; + return (u16)args.value; +} + +static inline u32 +intel_inl (u16 port) +{ + port_io_args args = { POKE_SIGNATURE, port, sizeof(u32), 0 }; + if (ioctl(poke_driver_fd, POKE_PORT_READ, &args, sizeof(args)) < 0) + return 0; + return (u32)args.value; +} + +static inline void +intel_outb (u8 value, u16 port) +{ + port_io_args args = { POKE_SIGNATURE, port, sizeof(u8), value }; + ioctl(poke_driver_fd, POKE_PORT_WRITE, &args, sizeof(args)); +} + +static inline void +intel_outw (u16 value, u16 port) +{ + port_io_args args = { POKE_SIGNATURE, port, sizeof(u16), value }; + ioctl(poke_driver_fd, POKE_PORT_WRITE, &args, sizeof(args)); +} + +static inline void +intel_outl (u32 value, u16 port) +{ + port_io_args args = { POKE_SIGNATURE, port, sizeof(u32), value }; + ioctl(poke_driver_fd, POKE_PORT_WRITE, &args, sizeof(args)); +} + +static inline void intel_io_lock(void) +{ +} + +static inline void intel_io_unlock(void) +{ +} diff --git a/lib/i386-io-hurd.h b/lib/i386-io-hurd.h new file mode 100644 index 0000000..01d684e --- /dev/null +++ b/lib/i386-io-hurd.h @@ -0,0 +1,37 @@ +/* + * The PCI Library -- Access to i386 I/O ports on GNU Hurd + * + * Copyright (c) 2003 Marco Gerards <metgerards@student.han.nl> + * Copyright (c) 2003 Martin Mares <mj@ucw.cz> + * Copyright (c) 2006 Samuel Thibault <samuel.thibault@ens-lyon.org> and + * Thomas Schwinge <tschwinge@gnu.org> + * Copyright (c) 2007 Thomas Schwinge <tschwinge@gnu.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/io.h> + +#include "i386-io-access.h" + +static inline int +intel_setup_io(struct pci_access *a UNUSED) +{ + return (ioperm (0, 65535, 1) == -1) ? 0 : 1; +} + +static inline void +intel_cleanup_io(struct pci_access *a UNUSED) +{ + ioperm (0, 65535, 0); +} + +static inline void intel_io_lock(void) +{ +} + +static inline void intel_io_unlock(void) +{ +} diff --git a/lib/i386-io-linux.h b/lib/i386-io-linux.h new file mode 100644 index 0000000..317f079 --- /dev/null +++ b/lib/i386-io-linux.h @@ -0,0 +1,77 @@ +/* + * The PCI Library -- Access to i386 I/O ports on Linux + * + * Copyright (c) 1997--2006 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/io.h> +#include <errno.h> + +#include "i386-io-access.h" + +static int ioperm_enabled; +static int iopl_enabled; + +static int +intel_setup_io(struct pci_access *a UNUSED) +{ + if (ioperm_enabled || iopl_enabled) + return 1; + + /* + * Before Linux 2.6.8, only the first 0x3ff I/O ports permissions can be + * modified via ioperm(). Since 2.6.8 all ports are supported. + * Since Linux 5.5, EFLAGS-based iopl() implementation was removed and + * replaced by new TSS-IOPB-map-all-based emulator. Before Linux 5.5, + * EFLAGS-based iopl() allowed userspace to enable/disable interrupts, + * which is dangerous. So prefer usage of ioperm() and fallback to iopl(). + */ + if (ioperm(0xcf8, 8, 1) < 0) /* conf1 + conf2 ports */ + { + if (errno == EINVAL) /* ioperm() unsupported */ + { + if (iopl(3) < 0) + return 0; + iopl_enabled = 1; + return 1; + } + return 0; + } + if (ioperm(0xc000, 0xfff, 1) < 0) /* remaining conf2 ports */ + { + ioperm(0xcf8, 8, 0); + return 0; + } + + ioperm_enabled = 1; + return 1; +} + +static inline void +intel_cleanup_io(struct pci_access *a UNUSED) +{ + if (ioperm_enabled) + { + ioperm(0xcf8, 8, 0); + ioperm(0xc000, 0xfff, 0); + ioperm_enabled = 0; + } + + if (iopl_enabled) + { + iopl(0); + iopl_enabled = 0; + } +} + +static inline void intel_io_lock(void) +{ +} + +static inline void intel_io_unlock(void) +{ +} diff --git a/lib/i386-io-openbsd.h b/lib/i386-io-openbsd.h new file mode 100644 index 0000000..8a9b4a4 --- /dev/null +++ b/lib/i386-io-openbsd.h @@ -0,0 +1,54 @@ +/* + * The PCI Library -- Access to i386 I/O ports on OpenBSD + * + * Copyright (c) 2023 Grant Pannell <grant@pannell.net.au> + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include <sys/types.h> +#include <machine/sysarch.h> +#include <machine/pio.h> + +#include "i386-io-access.h" + +#if defined(__amd64__) + #define obsd_iopl amd64_iopl +#else + #define obsd_iopl i386_iopl +#endif + +static int iopl_enabled; + +static int +intel_setup_io(struct pci_access *a UNUSED) +{ + if (iopl_enabled) + return 1; + + if (obsd_iopl(3) < 0) + { + return 0; + } + + iopl_enabled = 1; + return 1; +} + +static inline void +intel_cleanup_io(struct pci_access *a UNUSED) +{ + if (iopl_enabled) + { + obsd_iopl(0); + iopl_enabled = 0; + } +} + +static inline void intel_io_lock(void) +{ +} + +static inline void intel_io_unlock(void) +{ +} diff --git a/lib/i386-io-sunos.h b/lib/i386-io-sunos.h new file mode 100644 index 0000000..1caefd5 --- /dev/null +++ b/lib/i386-io-sunos.h @@ -0,0 +1,35 @@ +/* + * The PCI Library -- Access to i386 I/O ports on Solaris + * + * Copyright (c) 2003 Bill Moore <billm@eng.sun.com> + * Copyright (c) 2003--2006 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/sysi86.h> +#include <sys/psw.h> + +#include "i386-io-access.h" + +static int +intel_setup_io(struct pci_access *a UNUSED) +{ + return (sysi86(SI86V86, V86SC_IOPL, PS_IOPL) < 0) ? 0 : 1; +} + +static inline void +intel_cleanup_io(struct pci_access *a UNUSED) +{ + sysi86(SI86V86, V86SC_IOPL, 0); +} + +static inline void intel_io_lock(void) +{ +} + +static inline void intel_io_unlock(void) +{ +} diff --git a/lib/i386-io-windows.h b/lib/i386-io-windows.h new file mode 100644 index 0000000..d2da452 --- /dev/null +++ b/lib/i386-io-windows.h @@ -0,0 +1,248 @@ +/* + * The PCI Library -- Access to i386 I/O ports on Windows + * + * Copyright (c) 2004 Alexander Stock <stock.alexander@gmx.de> + * Copyright (c) 2006 Martin Mares <mj@ucw.cz> + * Copyright (c) 2021 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <windows.h> +#include "win32-helpers.h" + +#include "i386-io-access.h" + +/* + * Define __readeflags() for MSVC and GCC compilers. + * MSVC since version 14.00 included in WDK 6001 and since version 15.00 + * included in VS 2008 provides __readeflags() intrinsic for both 32 and 64-bit + * modes. WDK 6001 defines macro __BUILDMACHINE__ to value WinDDK. VS 2008 does + * not define this macro at all. MSVC throws error if name of user defined + * function conflicts with some MSVC intrinsic. + * MSVC supports inline assembly via __asm keyword in 32-bit mode only. + * GCC version 4.9.0 and higher provides __builtin_ia32_readeflags_uXX() + * builtin for XX-mode. This builtin is also available as __readeflags() + * function indirectly via <x86intrin.h> header file. + * + * CAVEAT: Semicolon in MSVC __asm block means start of the comment, and not + * end of the __asm statement, like it is for all other C statements. Also + * function which uses MSVC inline assembly cannot be inlined to another function + * (compiler reports a warning about it, not a fatal error). So we add explicit + * curly brackets for __asm blocks, remove misleading semicolons and do not + * declare functions as inline. + */ +#if defined(_MSC_VER) && (_MSC_VER >= 1500 || (_MSC_VER >= 1400 && defined(__BUILDMACHINE__))) +#pragma intrinsic(__readeflags) +#elif defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 9) || (__GNUC__ > 4)) +#include <x86intrin.h> +#elif defined(_MSC_VER) && defined(_M_IX86) +static unsigned int +__readeflags(void) +{ + __asm { + pushfd + pop eax + } +} +#elif defined(__GNUC__) +static inline unsigned +#ifdef __x86_64__ +long long +#endif +int +__readeflags(void) +{ + unsigned +#ifdef __x86_64__ + long long +#endif + int eflags; + asm volatile ("pushf\n\tpop %0\n" : "=r" (eflags)); + return eflags; +} +#else +#error "Unsupported compiler" +#endif + +/* Read IOPL of the current process, IOPL is stored in eflag bits [13:12]. */ +#define read_iopl() ((__readeflags() >> 12) & 0x3) + +/* + * Unfortunately NtSetInformationProcess() function, ProcessUserModeIOPL + * constant and all other helpers for its usage are not specified in any + * standard WinAPI header file. So define all of required constants and types. + * Function NtSetInformationProcess() is available in ntdll.dll library on all + * Windows systems but marked as it can be removed in some future version. + */ +#ifndef NTSTATUS +#define NTSTATUS LONG +#endif +#ifndef STATUS_NOT_IMPLEMENTED +#define STATUS_NOT_IMPLEMENTED (NTSTATUS)0xC0000002 +#endif +#ifndef STATUS_PRIVILEGE_NOT_HELD +#define STATUS_PRIVILEGE_NOT_HELD (NTSTATUS)0xC0000061 +#endif +#ifndef PROCESSINFOCLASS +#define PROCESSINFOCLASS DWORD +#endif +#ifndef ProcessUserModeIOPL +#define ProcessUserModeIOPL 16 +#endif +typedef NTSTATUS (NTAPI *NtSetInformationProcessProt)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength); +typedef ULONG (NTAPI *RtlNtStatusToDosErrorProt)(NTSTATUS Status); + +/* + * ProcessUserModeIOPL is syscall for NT kernel to change x86 IOPL + * of the current running process to 3. + * + * Process handle argument for ProcessUserModeIOPL is ignored and + * IOPL is always changed for the current running process. So pass + * GetCurrentProcess() handle for documentation purpose. Process + * information buffer and length are unused for ProcessUserModeIOPL. + * + * ProcessUserModeIOPL may success (return value >= 0) or may fail + * because it is not implemented or because of missing privilege. + * Other errors are not defined, so handle them as unknown. + */ +static BOOL +SetProcessUserModeIOPLFunc(LPVOID Arg) +{ + RtlNtStatusToDosErrorProt RtlNtStatusToDosErrorPtr = (RtlNtStatusToDosErrorProt)(((LPVOID *)Arg)[1]); + NtSetInformationProcessProt NtSetInformationProcessPtr = (NtSetInformationProcessProt)(((LPVOID *)Arg)[0]); + NTSTATUS nt_status = NtSetInformationProcessPtr(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0); + if (nt_status >= 0) + return TRUE; + + /* + * If we have optional RtlNtStatusToDosError() function then use it for + * translating NT status to Win32 error. If we do not have it then translate + * two important status codes which we use later STATUS_NOT_IMPLEMENTED and + * STATUS_PRIVILEGE_NOT_HELD. + */ + if (RtlNtStatusToDosErrorPtr) + SetLastError(RtlNtStatusToDosErrorPtr(nt_status)); + else if (nt_status == STATUS_NOT_IMPLEMENTED) + SetLastError(ERROR_INVALID_FUNCTION); + else if (nt_status == STATUS_PRIVILEGE_NOT_HELD) + SetLastError(ERROR_PRIVILEGE_NOT_HELD); + else + SetLastError(ERROR_GEN_FAILURE); + + return FALSE; +} + +/* + * Set x86 I/O Privilege Level to 3 for the whole current NT process. Do it via + * NtSetInformationProcess() call with ProcessUserModeIOPL information class, + * which is supported by 32-bit Windows NT kernel versions and requires Tcb + * privilege. + */ +static BOOL +SetProcessUserModeIOPL(VOID) +{ + LPVOID Arg[2]; + UINT prev_error_mode; + HMODULE ntdll; + BOOL ret; + + /* + * Load ntdll.dll library with disabled critical-error-handler message box. + * It means that NT kernel does not show unwanted GUI message box to user + * when LoadLibrary() function fails. + */ + prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS); + ntdll = LoadLibrary(TEXT("ntdll.dll")); + win32_change_error_mode(prev_error_mode); + if (!ntdll) + { + SetLastError(ERROR_INVALID_FUNCTION); + return FALSE; + } + + /* Retrieve pointer to NtSetInformationProcess() function. */ + Arg[0] = (LPVOID)GetProcAddress(ntdll, "NtSetInformationProcess"); + if (!Arg[0]) + { + FreeLibrary(ntdll); + SetLastError(ERROR_INVALID_FUNCTION); + return FALSE; + } + + /* Retrieve pointer to optional RtlNtStatusToDosError() function, it may be NULL. */ + Arg[1] = (LPVOID)GetProcAddress(ntdll, "RtlNtStatusToDosError"); + + /* Call ProcessUserModeIOPL with Tcb privilege. */ + ret = win32_call_func_with_tcb_privilege(SetProcessUserModeIOPLFunc, (LPVOID)&Arg); + + FreeLibrary(ntdll); + + if (!ret) + return FALSE; + + /* + * Some Windows NT kernel versions (e.g. Windows 2003 x64) do not + * implement ProcessUserModeIOPL syscall at all but incorrectly + * returns success when it is called by user process. So always + * after this call verify that IOPL is set to 3. + */ + if (read_iopl() != 3) + { + SetLastError(ERROR_INVALID_FUNCTION); + return FALSE; + } + + return TRUE; +} + +static int +intel_setup_io(struct pci_access *a) +{ +#ifndef _WIN64 + /* 16/32-bit non-NT systems allow applications to access PCI I/O ports without any special setup. */ + if (win32_is_non_nt_system()) + { + a->debug("Detected 16/32-bit non-NT system, skipping NT setup..."); + return 1; + } +#endif + + /* Check if we have I/O permission */ + if (read_iopl() == 3) + { + a->debug("IOPL is already set to 3, skipping NT setup..."); + return 1; + } + + /* On NT-based systems issue ProcessUserModeIOPL syscall which changes IOPL to 3. */ + if (!SetProcessUserModeIOPL()) + { + DWORD error = GetLastError(); + a->debug("NT ProcessUserModeIOPL call failed: %s.", error == ERROR_INVALID_FUNCTION ? "Call is not supported" : win32_strerror(error)); + return 0; + } + + a->debug("NT ProcessUserModeIOPL call succeeded..."); + return 1; +} + +static inline void +intel_cleanup_io(struct pci_access *a UNUSED) +{ + /* + * 16/32-bit non-NT systems do not use any special setup and on NT-based + * systems ProcessUserModeIOPL permanently changes IOPL to 3 for the current + * NT process, no revert for current process is possible. + */ +} + +static inline void intel_io_lock(void) +{ +} + +static inline void intel_io_unlock(void) +{ +} diff --git a/lib/i386-ports.c b/lib/i386-ports.c new file mode 100644 index 0000000..5f8aea4 --- /dev/null +++ b/lib/i386-ports.c @@ -0,0 +1,327 @@ +/* + * The PCI Library -- Direct Configuration access via i386 Ports + * + * Copyright (c) 1997--2006 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define _GNU_SOURCE + +#include "internal.h" + +#include <string.h> + +#if defined(PCI_OS_LINUX) +#include "i386-io-linux.h" +#elif defined(PCI_OS_GNU) +#include "i386-io-hurd.h" +#elif defined(PCI_OS_SUNOS) +#include "i386-io-sunos.h" +#elif defined(PCI_OS_WINDOWS) +#include "i386-io-windows.h" +#elif defined(PCI_OS_CYGWIN) +#include "i386-io-cygwin.h" +#elif defined(PCI_OS_HAIKU) +#include "i386-io-haiku.h" +#elif defined(PCI_OS_BEOS) +#include "i386-io-beos.h" +#elif defined(PCI_OS_DJGPP) +#include "i386-io-djgpp.h" +#elif defined(PCI_OS_OPENBSD) +#include "i386-io-openbsd.h" +#else +#error Do not know how to access I/O ports on this OS. +#endif + +static int conf12_io_enabled = -1; /* -1=haven't tried, 0=failed, 1=succeeded */ + +static int +conf12_setup_io(struct pci_access *a) +{ + if (conf12_io_enabled < 0) + conf12_io_enabled = intel_setup_io(a); + return conf12_io_enabled; +} + +static void +conf12_init(struct pci_access *a) +{ + if (!conf12_setup_io(a)) + a->error("No permission to access I/O ports (you probably have to be root)."); +} + +static void +conf12_cleanup(struct pci_access *a) +{ + if (conf12_io_enabled > 0) + { + intel_cleanup_io(a); + conf12_io_enabled = -1; + } +} + +/* + * Before we decide to use direct hardware access mechanisms, we try to do some + * trivial checks to ensure it at least _seems_ to be working -- we just test + * whether bus 00 contains a host bridge (this is similar to checking + * techniques used in XFree86, but ours should be more reliable since we + * attempt to make use of direct access hints provided by the PCI BIOS). + * + * This should be close to trivial, but it isn't, because there are buggy + * chipsets (yes, you guessed it, by Intel and Compaq) that have no class ID. + */ + +static int +intel_sanity_check(struct pci_access *a, struct pci_methods *m) +{ + struct pci_dev d; + + memset(&d, 0, sizeof(d)); + a->debug("...sanity check"); + d.bus = 0; + d.func = 0; + for (d.dev = 0; d.dev < 32; d.dev++) + { + u16 class, vendor; + if (m->read(&d, PCI_CLASS_DEVICE, (byte *) &class, sizeof(class)) && + (class == cpu_to_le16(PCI_CLASS_BRIDGE_HOST) || class == cpu_to_le16(PCI_CLASS_DISPLAY_VGA)) || + m->read(&d, PCI_VENDOR_ID, (byte *) &vendor, sizeof(vendor)) && + (vendor == cpu_to_le16(PCI_VENDOR_ID_INTEL) || vendor == cpu_to_le16(PCI_VENDOR_ID_COMPAQ))) + { + a->debug("...outside the Asylum at 0/%02x/0", d.dev); + return 1; + } + } + a->debug("...insane"); + return 0; +} + +/* + * Configuration type 1 + */ + +#define CONFIG_CMD(bus, device_fn, where) (0x80000000 | (bus << 16) | (device_fn << 8) | (where & ~3)) + +static int +conf1_detect(struct pci_access *a) +{ + unsigned int tmp; + int res = 0; + + if (!conf12_setup_io(a)) + { + a->debug("...no I/O permission"); + return 0; + } + + intel_io_lock(); + intel_outb (0x01, 0xCFB); + tmp = intel_inl (0xCF8); + intel_outl (0x80000000, 0xCF8); + if (intel_inl (0xCF8) == 0x80000000) + res = 1; + intel_outl (tmp, 0xCF8); + intel_io_unlock(); + + if (res) + res = intel_sanity_check(a, &pm_intel_conf1); + return res; +} + +static int +conf1_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + int addr = 0xcfc + (pos&3); + int res = 1; + + if (d->domain || pos >= 256) + return 0; + + if (len != 1 && len != 2 && len != 4) + return pci_generic_block_read(d, pos, buf, len); + + intel_io_lock(); + intel_outl(0x80000000 | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos&~3), 0xcf8); + + switch (len) + { + case 1: + buf[0] = intel_inb(addr); + break; + case 2: + ((u16 *) buf)[0] = cpu_to_le16(intel_inw(addr)); + break; + case 4: + ((u32 *) buf)[0] = cpu_to_le32(intel_inl(addr)); + break; + } + + intel_io_unlock(); + return res; +} + +static int +conf1_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + int addr = 0xcfc + (pos&3); + int res = 1; + + if (d->domain || pos >= 256) + return 0; + + if (len != 1 && len != 2 && len != 4) + return pci_generic_block_write(d, pos, buf, len); + + intel_io_lock(); + intel_outl(0x80000000 | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos&~3), 0xcf8); + + switch (len) + { + case 1: + intel_outb(buf[0], addr); + break; + case 2: + intel_outw(le16_to_cpu(((u16 *) buf)[0]), addr); + break; + case 4: + intel_outl(le32_to_cpu(((u32 *) buf)[0]), addr); + break; + } + intel_io_unlock(); + return res; +} + +/* + * Configuration type 2. Obsolete and brain-damaged, but existing. + */ + +static int +conf2_detect(struct pci_access *a) +{ + int res = 0; + + if (!conf12_setup_io(a)) + { + a->debug("...no I/O permission"); + return 0; + } + + /* This is ugly and tends to produce false positives. Beware. */ + + intel_io_lock(); + intel_outb(0x00, 0xCFB); + intel_outb(0x00, 0xCF8); + intel_outb(0x00, 0xCFA); + if (intel_inb(0xCF8) == 0x00 && intel_inb(0xCFA) == 0x00) + res = intel_sanity_check(a, &pm_intel_conf2); + intel_io_unlock(); + return res; +} + +static int +conf2_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + int res = 1; + int addr = 0xc000 | (d->dev << 8) | pos; + + if (d->domain || pos >= 256) + return 0; + + if (d->dev >= 16) + /* conf2 supports only 16 devices per bus */ + return 0; + + if (len != 1 && len != 2 && len != 4) + return pci_generic_block_read(d, pos, buf, len); + + intel_io_lock(); + intel_outb((d->func << 1) | 0xf0, 0xcf8); + intel_outb(d->bus, 0xcfa); + switch (len) + { + case 1: + buf[0] = intel_inb(addr); + break; + case 2: + ((u16 *) buf)[0] = cpu_to_le16(intel_inw(addr)); + break; + case 4: + ((u32 *) buf)[0] = cpu_to_le32(intel_inl(addr)); + break; + } + intel_outb(0, 0xcf8); + intel_io_unlock(); + return res; +} + +static int +conf2_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + int res = 1; + int addr = 0xc000 | (d->dev << 8) | pos; + + if (d->domain || pos >= 256) + return 0; + + if (d->dev >= 16) + /* conf2 supports only 16 devices per bus */ + return 0; + + if (len != 1 && len != 2 && len != 4) + return pci_generic_block_write(d, pos, buf, len); + + intel_io_lock(); + intel_outb((d->func << 1) | 0xf0, 0xcf8); + intel_outb(d->bus, 0xcfa); + switch (len) + { + case 1: + intel_outb(buf[0], addr); + break; + case 2: + intel_outw(le16_to_cpu(* (u16 *) buf), addr); + break; + case 4: + intel_outl(le32_to_cpu(* (u32 *) buf), addr); + break; + } + + intel_outb(0, 0xcf8); + intel_io_unlock(); + return res; +} + +struct pci_methods pm_intel_conf1 = { + "intel-conf1", + "Raw I/O port access using Intel conf1 interface", + NULL, /* config */ + conf1_detect, + conf12_init, + conf12_cleanup, + pci_generic_scan, + pci_generic_fill_info, + conf1_read, + conf1_write, + NULL, /* read_vpd */ + NULL, /* init_dev */ + NULL /* cleanup_dev */ +}; + +struct pci_methods pm_intel_conf2 = { + "intel-conf2", + "Raw I/O port access using Intel conf2 interface", + NULL, /* config */ + conf2_detect, + conf12_init, + conf12_cleanup, + pci_generic_scan, + pci_generic_fill_info, + conf2_read, + conf2_write, + NULL, /* read_vpd */ + NULL, /* init_dev */ + NULL /* cleanup_dev */ +}; diff --git a/lib/init.c b/lib/init.c new file mode 100644 index 0000000..c26720a --- /dev/null +++ b/lib/init.c @@ -0,0 +1,564 @@ +/* + * The PCI Library -- Initialization and related things + * + * Copyright (c) 1997--2024 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +#include "internal.h" + +#ifdef PCI_OS_DJGPP +#include <crt0.h> /* for __dos_argv0 */ +#endif + +#ifdef PCI_OS_WINDOWS + +#include <windows.h> + +/* Force usage of ANSI (char*) variant of GetModuleFileName() function */ +#ifdef _WIN32 +#ifdef GetModuleFileName +#undef GetModuleFileName +#endif +#define GetModuleFileName GetModuleFileNameA +#endif + +/* Define __ImageBase for all linkers */ +#ifdef _WIN32 +/* GNU LD provides __ImageBase symbol since 2.19, in previous versions it is + * under name _image_base__, so add weak alias for compatibility. */ +#ifdef __GNUC__ +asm(".weak\t" PCI_STRINGIFY(__MINGW_USYMBOL(__ImageBase)) "\n\t" + ".set\t" PCI_STRINGIFY(__MINGW_USYMBOL(__ImageBase)) "," PCI_STRINGIFY(__MINGW_USYMBOL(_image_base__))); +#endif +/* + * MSVC link.exe provides __ImageBase symbol since 12.00 (MSVC 6.0), for + * previous versions resolve it at runtime via GetModuleHandleA() which + * returns base for main executable or via VirtualQuery() for DLL builds. + */ +#if defined(_MSC_VER) && _MSC_VER < 1200 +static HMODULE +get_current_module_handle(void) +{ +#ifdef PCI_SHARED_LIB + MEMORY_BASIC_INFORMATION info; + size_t len = VirtualQuery(&get_current_module_handle, &info, sizeof(info)); + if (len != sizeof(info)) + return NULL; + return (HMODULE)info.AllocationBase; +#else + return GetModuleHandleA(NULL); +#endif +} +#define __ImageBase (*(IMAGE_DOS_HEADER *)get_current_module_handle()) +#else +extern IMAGE_DOS_HEADER __ImageBase; +#endif +#endif + +#if defined(_WINDLL) +extern HINSTANCE _hModule; +#elif defined(_WINDOWS) +extern HINSTANCE _hInstance; +#endif + +#endif + +static struct pci_methods *pci_methods[PCI_ACCESS_MAX] = { + NULL, +#ifdef PCI_HAVE_PM_LINUX_SYSFS + &pm_linux_sysfs, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_LINUX_PROC + &pm_linux_proc, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_INTEL_CONF + &pm_intel_conf1, + &pm_intel_conf2, +#else + NULL, + NULL, +#endif +#ifdef PCI_HAVE_PM_FBSD_DEVICE + &pm_fbsd_device, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_AIX_DEVICE + &pm_aix_device, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_NBSD_LIBPCI + &pm_nbsd_libpci, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_OBSD_DEVICE + &pm_obsd_device, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_DUMP + &pm_dump, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_DARWIN_DEVICE + &pm_darwin, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_SYLIXOS_DEVICE + &pm_sylixos_device, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_HURD_CONF + &pm_hurd, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_WIN32_CFGMGR32 + &pm_win32_cfgmgr32, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_WIN32_KLDBG + &pm_win32_kldbg, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_WIN32_SYSDBG + &pm_win32_sysdbg, +#else + NULL, +#endif +#ifdef PCI_HAVE_PM_MMIO_CONF + &pm_mmio_conf1, + &pm_mmio_conf1_ext, +#else + NULL, + NULL, +#endif +#if defined(PCI_HAVE_PM_ECAM) + &pm_ecam, +#else + NULL, +#endif +#if defined(PCI_HAVE_PM_AOS_EXPANSION) + &pm_aos_expansion, +#else + NULL, +#endif +}; + +// If PCI_ACCESS_AUTO is selected, we probe the access methods in this order +static int probe_sequence[] = { + // System-specific methods + PCI_ACCESS_SYS_BUS_PCI, + PCI_ACCESS_PROC_BUS_PCI, + PCI_ACCESS_FBSD_DEVICE, + PCI_ACCESS_AIX_DEVICE, + PCI_ACCESS_NBSD_LIBPCI, + PCI_ACCESS_OBSD_DEVICE, + PCI_ACCESS_DARWIN, + PCI_ACCESS_SYLIXOS_DEVICE, + PCI_ACCESS_HURD, + PCI_ACCESS_WIN32_CFGMGR32, + PCI_ACCESS_WIN32_KLDBG, + PCI_ACCESS_WIN32_SYSDBG, + PCI_ACCESS_AOS_EXPANSION, + // Low-level methods poking the hardware directly + PCI_ACCESS_ECAM, + PCI_ACCESS_I386_TYPE1, + PCI_ACCESS_I386_TYPE2, + PCI_ACCESS_MMIO_TYPE1_EXT, + PCI_ACCESS_MMIO_TYPE1, + -1, +}; + +static void PCI_NONRET +pci_generic_error(char *msg, ...) +{ + va_list args; + + va_start(args, msg); + fputs("pcilib: ", stderr); + vfprintf(stderr, msg, args); + va_end(args); + fputc('\n', stderr); + exit(1); +} + +static void +pci_generic_warn(char *msg, ...) +{ + va_list args; + + va_start(args, msg); + fputs("pcilib: ", stderr); + vfprintf(stderr, msg, args); + va_end(args); + fputc('\n', stderr); +} + +static void +pci_generic_debug(char *msg, ...) +{ + va_list args; + + va_start(args, msg); + vfprintf(stdout, msg, args); + va_end(args); +} + +static void +pci_null_debug(char *msg UNUSED, ...) +{ +} + +// Memory allocation functions are safe to call if pci_access is not fully initalized or even NULL + +void * +pci_malloc(struct pci_access *a, int size) +{ + void *x = malloc(size); + + if (!x) + (a && a->error ? a->error : pci_generic_error)("Out of memory (allocation of %d bytes failed)", size); + return x; +} + +void +pci_mfree(void *x) +{ + if (x) + free(x); +} + +char * +pci_strdup(struct pci_access *a, const char *s) +{ + int len = strlen(s) + 1; + char *t = pci_malloc(a, len); + memcpy(t, s, len); + return t; +} + +int +pci_lookup_method(char *name) +{ + int i; + + for (i=0; i<PCI_ACCESS_MAX; i++) + if (pci_methods[i] && !strcmp(pci_methods[i]->name, name)) + return i; + return -1; +} + +char * +pci_get_method_name(int index) +{ + if (index < 0 || index >= PCI_ACCESS_MAX) + return NULL; + else if (!pci_methods[index]) + return ""; + else + return pci_methods[index]->name; +} + +#if defined(PCI_OS_WINDOWS) || defined(PCI_OS_DJGPP) + +static void +pci_init_name_list_path(struct pci_access *a) +{ + if ((PCI_PATH_IDS_DIR)[0]) + pci_set_name_list_path(a, PCI_PATH_IDS_DIR "\\" PCI_IDS, 0); + else + { + char *path, *sep; + size_t len; + +#if defined(PCI_OS_WINDOWS) && (defined(_WIN32) || defined(_WINDLL) || defined(_WINDOWS)) + + HMODULE module; + size_t size; + +#if defined(_WIN32) + module = (HINSTANCE)&__ImageBase; +#elif defined(_WINDLL) + module = _hModule; +#elif defined(_WINDOWS) + module = _hInstance; +#endif + + /* + * Module file name can have arbitrary length despite all MS examples say + * about MAX_PATH upper limit. This limit does not apply for example when + * executable is running from network disk with very long UNC paths or + * when using "\\??\\" prefix for specifying executable binary path. + * Function GetModuleFileName() returns passed size argument when passed + * buffer is too small and does not signal any error. In this case retry + * again with larger buffer. + */ + size = 256; /* initial buffer size (more than sizeof(PCI_IDS)-4) */ +retry: + path = pci_malloc(a, size); + len = GetModuleFileName(module, path, size-sizeof(PCI_IDS)-4); /* 4 for "\\\\?\\" */ + if (len >= size-sizeof(PCI_IDS)-4) + { + free(path); + size *= 2; + goto retry; + } + else if (len == 0) + path[0] = '\0'; + + /* + * GetModuleFileName() has bugs. On Windows 10 it prepends current drive + * letter if path is just pure NT namespace (with "\\??\\" prefix). Such + * extra drive letter makes path fully invalid and unusable. So remove + * extra drive letter to make path valid again. + * Reproduce: CreateProcessW("\\??\\C:\\lspci.exe", ...) + */ + if (((path[0] >= 'a' && path[0] <= 'z') || + (path[0] >= 'A' && path[0] <= 'Z')) && + strncmp(path+1, ":\\??\\", 5) == 0) + { + memmove(path, path+2, len-2); + len -= 2; + path[len] = '\0'; + } + + /* + * GetModuleFileName() has bugs. On Windows 10 it does not add "\\\\?\\" + * prefix when path is in native NT UNC namespace. Such path is treated by + * WinAPI/DOS functions as standard DOS path relative to the current + * directory, hence something completely different. So prepend missing + * "\\\\?\\" prefix to make path valid again. + * Reproduce: CreateProcessW("\\??\\UNC\\10.0.2.4\\qemu\\lspci.exe", ...) + * + * If path starts with DOS drive letter and with appended PCI_IDS is + * longer than 260 bytes and is without "\\\\?\\" prefix then append it. + * This prefix is required for paths and file names with DOS drive letter + * longer than 260 bytes. + */ + if (strncmp(path, "\\UNC\\", 5) == 0 || + strncmp(path, "UNC\\", 4) == 0 || + (((path[0] >= 'a' && path[0] <= 'z') || (path[0] >= 'A' && path[0] <= 'Z')) && + len + sizeof(PCI_IDS) >= 260)) + { + memmove(path+4, path, len); + memcpy(path, "\\\\?\\", 4); + len += 4; + path[len] = '\0'; + } + +#elif defined(PCI_OS_DJGPP) || defined(PCI_OS_WINDOWS) + + const char *exe_path; + +#ifdef PCI_OS_DJGPP + exe_path = __dos_argv0; +#else + exe_path = _pgmptr; +#endif + + len = strlen(exe_path); + path = pci_malloc(a, len+sizeof(PCI_IDS)); + memcpy(path, exe_path, len+1); + +#endif + + sep = strrchr(path, '\\'); + if (!sep) + { + /* + * If current module path (current executable for static builds or + * current DLL library for shared build) cannot be determined then + * fallback to the current directory. + */ + free(path); + pci_set_name_list_path(a, PCI_IDS, 0); + } + else + { + memcpy(sep+1, PCI_IDS, sizeof(PCI_IDS)); + pci_set_name_list_path(a, path, 1); + } + } +} + +#elif defined PCI_OS_AMIGAOS + +static void +pci_init_name_list_path(struct pci_access *a) +{ + int len = strlen(PCI_PATH_IDS_DIR); + + if (!len) + pci_set_name_list_path(a, PCI_IDS, 0); + else + { + char last_char = PCI_PATH_IDS_DIR[len - 1]; + if (last_char == ':' || last_char == '/') // root or parent char + pci_set_name_list_path(a, PCI_PATH_IDS_DIR PCI_IDS, 0); + else + pci_set_name_list_path(a, PCI_PATH_IDS_DIR "/" PCI_IDS, 0); + } +} + +#else + +static void +pci_init_name_list_path(struct pci_access *a) +{ + pci_set_name_list_path(a, PCI_PATH_IDS_DIR "/" PCI_IDS, 0); +} + +#endif + +#ifdef PCI_USE_DNS + +static void +pci_init_dns(struct pci_access *a) +{ + pci_define_param(a, "net.domain", PCI_ID_DOMAIN, "DNS domain used for resolving of ID's"); + a->id_lookup_mode = PCI_LOOKUP_CACHE; + + char *cache_dir = getenv("XDG_CACHE_HOME"); + if (!cache_dir) + cache_dir = "~/.cache"; + + int name_len = strlen(cache_dir) + 32; + char *cache_name = pci_malloc(NULL, name_len); + snprintf(cache_name, name_len, "%s/pci-ids", cache_dir); + struct pci_param *param = pci_define_param(a, "net.cache_name", cache_name, "Name of the ID cache file"); + param->value_malloced = 1; +} + +#endif + +struct pci_access * +pci_alloc(void) +{ + struct pci_access *a = pci_malloc(NULL, sizeof(struct pci_access)); + int i; + + memset(a, 0, sizeof(*a)); + pci_init_name_list_path(a); +#ifdef PCI_USE_DNS + pci_init_dns(a); +#endif +#ifdef PCI_HAVE_HWDB + pci_define_param(a, "hwdb.disable", "0", "Do not look up names in UDEV's HWDB if non-zero"); +#endif + for (i=0; i<PCI_ACCESS_MAX; i++) + if (pci_methods[i] && pci_methods[i]->config) + pci_methods[i]->config(a); + return a; +} + +int +pci_init_internal(struct pci_access *a, int skip_method) +{ + if (!a->error) + a->error = pci_generic_error; + if (!a->warning) + a->warning = pci_generic_warn; + if (!a->debug) + a->debug = pci_generic_debug; + if (!a->debugging) + a->debug = pci_null_debug; + + if (a->method != PCI_ACCESS_AUTO) + { + if (a->method >= PCI_ACCESS_MAX || !pci_methods[a->method]) + a->error("This access method is not supported."); + a->methods = pci_methods[a->method]; + } + else + { + unsigned int i; + for (i=0; probe_sequence[i] >= 0; i++) + { + struct pci_methods *m = pci_methods[probe_sequence[i]]; + if (!m) + continue; + if (skip_method == probe_sequence[i]) + continue; + a->debug("Trying method %s...", m->name); + if (m->detect(a)) + { + a->debug("...OK\n"); + a->methods = m; + a->method = probe_sequence[i]; + break; + } + a->debug("...No.\n"); + } + if (!a->methods) + return 0; + } + a->debug("Decided to use %s\n", a->methods->name); + a->methods->init(a); + return 1; +} + +void +pci_init_v35(struct pci_access *a) +{ + if (!pci_init_internal(a, -1)) + a->error("Cannot find any working access method."); +} + +STATIC_ALIAS(void pci_init(struct pci_access *a), pci_init_v35(a)); +DEFINE_ALIAS(void pci_init_v30(struct pci_access *a), pci_init_v35); +SYMBOL_VERSION(pci_init_v30, pci_init@LIBPCI_3.0); +SYMBOL_VERSION(pci_init_v35, pci_init@@LIBPCI_3.5); + +struct pci_access * +pci_clone_access(struct pci_access *a) +{ + struct pci_access *b = pci_alloc(); + + b->writeable = a->writeable; + b->buscentric = a->buscentric; + b->debugging = a->debugging; + b->error = a->error; + b->warning = a->warning; + b->debug = a->debug; + + return b; +} + +void +pci_cleanup(struct pci_access *a) +{ + struct pci_dev *d, *e; + + for (d=a->devices; d; d=e) + { + e = d->next; + pci_free_dev(d); + } + if (a->methods) + a->methods->cleanup(a); + pci_free_name_list(a); + pci_free_params(a); + pci_set_name_list_path(a, NULL, 0); + pci_mfree(a); +} diff --git a/lib/internal.h b/lib/internal.h new file mode 100644 index 0000000..68e9fa0 --- /dev/null +++ b/lib/internal.h @@ -0,0 +1,148 @@ +/* + * The PCI Library -- Internal Stuff + * + * Copyright (c) 1997--2022 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef _INTERNAL_H +#define _INTERNAL_H + +#include "config.h" + +#ifdef PCI_SHARED_LIB +#define PCI_ABI __attribute__((visibility("default"))) +// Functions, which are bound to externally visible symbols by the versioning +// mechanism, have to be declared as VERSIONED. Otherwise, GCC with global +// optimizations is happy to optimize them away, leading to linker failures. +#define VERSIONED_ABI __attribute__((used)) PCI_ABI +#ifdef __APPLE__ +#define STATIC_ALIAS(_decl, _for) VERSIONED_ABI _decl { return _for; } +#define DEFINE_ALIAS(_decl, _for) +#define SYMBOL_VERSION(_int, _ext) +#else +#define DEFINE_ALIAS(_decl, _for) extern _decl __attribute__((alias(#_for))) VERSIONED_ABI +#ifdef _WIN32 +#define STATIC_ALIAS(_decl, _for) VERSIONED_ABI _decl { return _for; } +/* GCC does not support asm .symver directive for Windows targets, so define new external global function symbol as alias to internal symbol */ +#define SYMBOL_VERSION(_int, _ext) asm(".globl\t" PCI_STRINGIFY(__MINGW_USYMBOL(_ext)) "\n\t" \ + ".def\t" PCI_STRINGIFY(__MINGW_USYMBOL(_ext)) ";\t.scl\t2;\t.type\t32;\t.endef\n\t" \ + ".set\t" PCI_STRINGIFY(__MINGW_USYMBOL(_ext)) "," PCI_STRINGIFY(__MINGW_USYMBOL(_int))) +#else +#define STATIC_ALIAS(_decl, _for) +#define SYMBOL_VERSION(_int, _ext) asm(".symver " #_int "," #_ext) +#endif +#endif +#else +#define VERSIONED_ABI +#define STATIC_ALIAS(_decl, _for) _decl { return _for; } +#define DEFINE_ALIAS(_decl, _for) +#define SYMBOL_VERSION(_int, _ext) +#endif + +#include "pci.h" +#include "sysdep.h" + +/* Old 32-bit-only versions of MinGW32 do not define __MINGW_USYMBOL macro */ +#ifdef __MINGW32__ +#ifndef __MINGW_USYMBOL +#define __MINGW_USYMBOL(sym) _##sym +#endif +#endif + +#define _PCI_STRINGIFY(x) #x +#define PCI_STRINGIFY(x) _PCI_STRINGIFY(x) + +struct pci_methods { + char *name; + char *help; + void (*config)(struct pci_access *); + int (*detect)(struct pci_access *); + void (*init)(struct pci_access *); + void (*cleanup)(struct pci_access *); + void (*scan)(struct pci_access *); + void (*fill_info)(struct pci_dev *, unsigned int flags); + int (*read)(struct pci_dev *, int pos, byte *buf, int len); + int (*write)(struct pci_dev *, int pos, byte *buf, int len); + int (*read_vpd)(struct pci_dev *, int pos, byte *buf, int len); + void (*init_dev)(struct pci_dev *); + void (*cleanup_dev)(struct pci_dev *); +}; + +/* generic.c */ +void pci_generic_scan_bus(struct pci_access *, byte *busmap, int domain, int bus); +void pci_generic_scan_domain(struct pci_access *, int domain); +void pci_generic_scan(struct pci_access *); +void pci_generic_fill_info(struct pci_dev *, unsigned int flags); +int pci_generic_block_read(struct pci_dev *, int pos, byte *buf, int len); +int pci_generic_block_write(struct pci_dev *, int pos, byte *buf, int len); + +/* emulated.c */ +int pci_emulated_read(struct pci_dev *d, int pos, byte *buf, int len); + +/* init.c */ +void *pci_malloc(struct pci_access *, int); +void pci_mfree(void *); +char *pci_strdup(struct pci_access *a, const char *s); +struct pci_access *pci_clone_access(struct pci_access *a); +int pci_init_internal(struct pci_access *a, int skip_method); + +void pci_init_v30(struct pci_access *a) VERSIONED_ABI; +void pci_init_v35(struct pci_access *a) VERSIONED_ABI; + +/* access.c */ +struct pci_dev *pci_alloc_dev(struct pci_access *); +int pci_link_dev(struct pci_access *, struct pci_dev *); + +int pci_fill_info_v30(struct pci_dev *, int flags) VERSIONED_ABI; +int pci_fill_info_v31(struct pci_dev *, int flags) VERSIONED_ABI; +int pci_fill_info_v32(struct pci_dev *, int flags) VERSIONED_ABI; +int pci_fill_info_v33(struct pci_dev *, int flags) VERSIONED_ABI; +int pci_fill_info_v34(struct pci_dev *, int flags) VERSIONED_ABI; +int pci_fill_info_v35(struct pci_dev *, int flags) VERSIONED_ABI; +int pci_fill_info_v38(struct pci_dev *, int flags) VERSIONED_ABI; + +static inline int want_fill(struct pci_dev *d, unsigned want_fields, unsigned int try_fields) +{ + want_fields &= try_fields; + if ((d->known_fields & want_fields) == want_fields) + return 0; + else + { + d->known_fields |= try_fields; + return 1; + } +} + +static inline void clear_fill(struct pci_dev *d, unsigned clear_fields) +{ + d->known_fields &= ~clear_fields; +} + +struct pci_property { + struct pci_property *next; + u32 key; + char value[1]; +}; + +char *pci_set_property(struct pci_dev *d, u32 key, char *value); + +/* params.c */ +struct pci_param *pci_define_param(struct pci_access *acc, char *param, char *val, char *help); +int pci_set_param_internal(struct pci_access *acc, char *param, char *val, int copy); +void pci_free_params(struct pci_access *acc); + +/* caps.c */ +void pci_scan_caps(struct pci_dev *, unsigned int want_fields); +void pci_free_caps(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_obsd_device, + pm_dump, pm_linux_sysfs, pm_darwin, pm_sylixos_device, pm_hurd, + pm_mmio_conf1, pm_mmio_conf1_ext, pm_ecam, + pm_win32_cfgmgr32, pm_win32_kldbg, pm_win32_sysdbg, pm_aos_expansion; + +#endif diff --git a/lib/libpci.pc.in b/lib/libpci.pc.in new file mode 100644 index 0000000..29c6910 --- /dev/null +++ b/lib/libpci.pc.in @@ -0,0 +1,11 @@ +prefix=@PREFIX@ +includedir=@INCDIR@ +libdir=@LIBDIR@ +idsdir=@IDSDIR@ + +Name: libpci +Description: libpci +Version: @VERSION@ +Libs: -L${libdir} -lpci +Libs.private: @LDLIBS@ @WITH_LIBS@ +Cflags: -I${includedir} diff --git a/lib/libpci.ver b/lib/libpci.ver new file mode 100644 index 0000000..33ee024 --- /dev/null +++ b/lib/libpci.ver @@ -0,0 +1,100 @@ +/* Version script for the libpci */ + +/* + * Visibility declarations in the source take precedence over this script, + * so we can boldly declare pci_* as public and still keep the internal + * functions properly hidden. + * + * To preserve compatibility of Windows DLL file, always add new symbol at + * the end of file and never change order of symbols nor version sections. + * On Windows the last referenced version of the symbol is the default one. + + * For PE/COFF targets this file is processed by ver2def.pl script and not + * by GNU LD linker like for ELF targets. + */ + +LIBPCI_3.0 { + global: + pci_alloc; + pci_cleanup; + pci_fill_info; + pci_filter_init; + pci_filter_match; + pci_filter_parse_id; + pci_filter_parse_slot; + pci_free_dev; + pci_free_name_list; + pci_get_dev; + pci_get_method_name; + pci_get_param; + pci_id_cache_flush; + pci_init; + pci_load_name_list; + pci_lookup_method; + pci_lookup_name; + pci_read_block; + pci_read_byte; + pci_read_long; + pci_read_word; + pci_scan_bus; + pci_set_name_list_path; + pci_set_param; + pci_setup_cache; + pci_walk_params; + pci_write_block; + pci_write_byte; + pci_write_long; + pci_write_word; + local: *; +}; + +LIBPCI_3.1 { + global: + pci_fill_info; + pci_find_cap; + pci_read_vpd; +}; + +LIBPCI_3.2 { + global: + pci_fill_info; +}; + +LIBPCI_3.3 { + global: + pci_fill_info; + pci_filter_init; + pci_filter_match; + pci_filter_parse_id; + pci_filter_parse_slot; +}; + +LIBPCI_3.4 { + global: + pci_fill_info; +}; + +LIBPCI_3.5 { + global: + pci_init; + pci_fill_info; +}; + +LIBPCI_3.6 { + global: + pci_get_string_property; +}; + +LIBPCI_3.7 { + global: + pci_find_cap_nr; +}; + +LIBPCI_3.8 { + global: + pci_fill_info; + pci_filter_init; + pci_filter_match; + pci_filter_parse_id; + pci_filter_parse_slot; +}; diff --git a/lib/mmio-ports.c b/lib/mmio-ports.c new file mode 100644 index 0000000..cac8a7e --- /dev/null +++ b/lib/mmio-ports.c @@ -0,0 +1,432 @@ +/* + * The PCI Library -- Direct Configuration access via memory mapped ports + * + * Copyright (c) 2022 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "internal.h" +#include "physmem.h" +#include "physmem-access.h" + +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +struct mmio_cache { + u64 addr_page; + u64 data_page; + void *addr_map; + void *data_map; +}; + +struct mmio_access { + struct mmio_cache *cache; + struct physmem *physmem; + long pagesize; +}; + +static void +munmap_regs(struct pci_access *a) +{ + struct mmio_access *macc = a->backend_data; + struct mmio_cache *cache = macc->cache; + struct physmem *physmem = macc->physmem; + long pagesize = macc->pagesize; + + if (!cache) + return; + + physmem_unmap(physmem, cache->addr_map, pagesize); + if (cache->addr_page != cache->data_page) + physmem_unmap(physmem, cache->data_map, pagesize); + + pci_mfree(macc->cache); + macc->cache = NULL; +} + +static int +mmap_regs(struct pci_access *a, u64 addr_reg, u64 data_reg, int data_off, volatile void **addr, volatile void **data) +{ + struct mmio_access *macc = a->backend_data; + struct mmio_cache *cache = macc->cache; + struct physmem *physmem = macc->physmem; + long pagesize = macc->pagesize; + u64 addr_page = addr_reg & ~(pagesize-1); + u64 data_page = data_reg & ~(pagesize-1); + void *addr_map = (void *)-1; + void *data_map = (void *)-1; + + if (cache && cache->addr_page == addr_page) + addr_map = cache->addr_map; + + if (cache && cache->data_page == data_page) + data_map = cache->data_map; + + if (addr_map == (void *)-1) + addr_map = physmem_map(physmem, addr_page, pagesize, 1); + + if (addr_map == (void *)-1) + return 0; + + if (data_map == (void *)-1) + { + if (data_page == addr_page) + data_map = addr_map; + else + data_map = physmem_map(physmem, data_page, pagesize, 1); + } + + if (data_map == (void *)-1) + { + if (!cache || cache->addr_map != addr_map) + physmem_unmap(physmem, addr_map, pagesize); + return 0; + } + + if (cache && cache->addr_page != addr_page) + physmem_unmap(physmem, cache->addr_map, pagesize); + + if (cache && cache->data_page != data_page && cache->data_page != cache->addr_page) + physmem_unmap(physmem, cache->data_map, pagesize); + + if (!cache) + cache = macc->cache = pci_malloc(a, sizeof(*cache)); + + cache->addr_page = addr_page; + cache->data_page = data_page; + cache->addr_map = addr_map; + cache->data_map = data_map; + + *addr = (unsigned char *)addr_map + (addr_reg & (pagesize-1)); + *data = (unsigned char *)data_map + (data_reg & (pagesize-1)) + data_off; + return 1; +} + +static int +validate_addrs(const char *addrs) +{ + const char *sep, *next; + u64 num; + char *endptr; + + if (!*addrs) + return 0; + + while (1) + { + next = strchr(addrs, ','); + if (!next) + next = addrs + strlen(addrs); + + sep = strchr(addrs, '/'); + if (!sep) + return 0; + + if (!isxdigit(*addrs) || !isxdigit(*(sep+1))) + return 0; + + errno = 0; + num = strtoull(addrs, &endptr, 16); + if (errno || endptr != sep || (num & 3)) + return 0; + + errno = 0; + num = strtoull(sep+1, &endptr, 16); + if (errno || endptr != next || (num & 3)) + return 0; + + if (!*next) + return 1; + + addrs = next + 1; + } +} + +static int +get_domain_count(const char *addrs) +{ + int count = 1; + while (addrs = strchr(addrs, ',')) + { + addrs++; + count++; + } + return count; +} + +static int +get_domain_addr(const char *addrs, int domain, u64 *addr_reg, u64 *data_reg) +{ + char *endptr; + + while (domain-- > 0) + { + addrs = strchr(addrs, ','); + if (!addrs) + return 0; + addrs++; + } + + *addr_reg = strtoull(addrs, &endptr, 16); + *data_reg = strtoull(endptr+1, NULL, 16); + + return 1; +} + +static void +conf1_config(struct pci_access *a) +{ + physmem_init_config(a); + pci_define_param(a, "mmio-conf1.addrs", "", "Physical addresses of memory mapped Intel conf1 interface"); /* format: 0xaddr1/0xdata1,0xaddr2/0xdata2,... */ +} + +static void +conf1_ext_config(struct pci_access *a) +{ + physmem_init_config(a); + pci_define_param(a, "mmio-conf1-ext.addrs", "", "Physical addresses of memory mapped Intel conf1 extended interface"); /* format: 0xaddr1/0xdata1,0xaddr2/0xdata2,... */ +} + +static int +detect(struct pci_access *a, char *addrs_param_name) +{ + char *addrs = pci_get_param(a, addrs_param_name); + + if (!*addrs) + { + a->debug("%s was not specified", addrs_param_name); + return 0; + } + + if (!validate_addrs(addrs)) + { + a->debug("%s has invalid address format %s", addrs_param_name, addrs); + return 0; + } + + if (physmem_access(a, 1)) + { + a->debug("cannot access physical memory: %s", strerror(errno)); + return 0; + } + + a->debug("using with %s", addrs); + return 1; +} + +static int +conf1_detect(struct pci_access *a) +{ + return detect(a, "mmio-conf1.addrs"); +} + +static int +conf1_ext_detect(struct pci_access *a) +{ + return detect(a, "mmio-conf1-ext.addrs"); +} + +static char* +get_addrs_param_name(struct pci_access *a) +{ + if (a->methods->config == conf1_ext_config) + return "mmio-conf1-ext.addrs"; + else + return "mmio-conf1.addrs"; +} + +static void +conf1_init(struct pci_access *a) +{ + char *addrs_param_name = get_addrs_param_name(a); + char *addrs = pci_get_param(a, addrs_param_name); + struct mmio_access *macc; + struct physmem *physmem; + long pagesize; + + if (!*addrs) + a->error("Option %s was not specified.", addrs_param_name); + + if (!validate_addrs(addrs)) + a->error("Option %s has invalid address format \"%s\".", addrs_param_name, addrs); + + physmem = physmem_open(a, 1); + if (!physmem) + a->error("Cannot open physcal memory: %s.", strerror(errno)); + + pagesize = physmem_get_pagesize(physmem); + if (pagesize <= 0) + a->error("Cannot get page size: %s.", strerror(errno)); + + macc = pci_malloc(a, sizeof(*macc)); + macc->cache = NULL; + macc->physmem = physmem; + macc->pagesize = pagesize; + a->backend_data = macc; +} + +static void +conf1_cleanup(struct pci_access *a) +{ + struct mmio_access *macc = a->backend_data; + + munmap_regs(a); + physmem_close(macc->physmem); + pci_mfree(macc); +} + +static void +conf1_scan(struct pci_access *a) +{ + char *addrs_param_name = get_addrs_param_name(a); + char *addrs = pci_get_param(a, addrs_param_name); + int domain_count = get_domain_count(addrs); + int domain; + + for (domain = 0; domain < domain_count; domain++) + pci_generic_scan_domain(a, domain); +} + +static int +conf1_ext_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + char *addrs_param_name = get_addrs_param_name(d->access); + char *addrs = pci_get_param(d->access, addrs_param_name); + volatile void *addr, *data; + u64 addr_reg, data_reg; + + if (pos >= 4096) + return 0; + + if (len != 1 && len != 2 && len != 4) + return pci_generic_block_read(d, pos, buf, len); + + if (!get_domain_addr(addrs, d->domain, &addr_reg, &data_reg)) + return 0; + + if (!mmap_regs(d->access, addr_reg, data_reg, pos&3, &addr, &data)) + return 0; + + physmem_writel(0x80000000 | ((pos & 0xf00) << 16) | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos & 0xfc), addr); + physmem_readl(addr); /* write barrier for address */ + + switch (len) + { + case 1: + buf[0] = physmem_readb(data); + break; + case 2: + ((u16 *) buf)[0] = physmem_readw(data); + break; + case 4: + ((u32 *) buf)[0] = physmem_readl(data); + break; + } + + return 1; +} + +static int +conf1_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + if (pos >= 256) + return 0; + + return conf1_ext_read(d, pos, buf, len); +} + +static int +conf1_ext_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + char *addrs_param_name = get_addrs_param_name(d->access); + char *addrs = pci_get_param(d->access, addrs_param_name); + volatile void *addr, *data; + u64 addr_reg, data_reg; + + if (pos >= 4096) + return 0; + + if (len != 1 && len != 2 && len != 4) + return pci_generic_block_write(d, pos, buf, len); + + if (!get_domain_addr(addrs, d->domain, &addr_reg, &data_reg)) + return 0; + + if (!mmap_regs(d->access, addr_reg, data_reg, pos&3, &addr, &data)) + return 0; + + physmem_writel(0x80000000 | ((pos & 0xf00) << 16) | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos & 0xfc), addr); + physmem_readl(addr); /* write barrier for address */ + + switch (len) + { + case 1: + physmem_writeb(buf[0], data); + break; + case 2: + physmem_writew(((u16 *) buf)[0], data); + break; + case 4: + physmem_writel(((u32 *) buf)[0], data); + break; + } + + /* + * write barrier for data + * Note that we cannot read from data port because it may have side effect. + * Instead we read from address port (which should not have side effect) to + * create a barrier between two conf1_write() calls. But this does not have + * to be 100% correct as it does not ensure barrier on data port itself. + * Correct way is to issue CPU instruction for full hw sync barrier but gcc + * does not provide any (builtin) function yet. + */ + physmem_readl(addr); + + return 1; +} + +static int +conf1_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + if (pos >= 256) + return 0; + + return conf1_ext_write(d, pos, buf, len); +} + +struct pci_methods pm_mmio_conf1 = { + "mmio-conf1", + "Raw memory mapped I/O port access using Intel conf1 interface", + conf1_config, + conf1_detect, + conf1_init, + conf1_cleanup, + conf1_scan, + pci_generic_fill_info, + conf1_read, + conf1_write, + NULL, /* read_vpd */ + NULL, /* init_dev */ + NULL /* cleanup_dev */ +}; + +struct pci_methods pm_mmio_conf1_ext = { + "mmio-conf1-ext", + "Raw memory mapped I/O port access using Intel conf1 extended interface", + conf1_ext_config, + conf1_ext_detect, + conf1_init, + conf1_cleanup, + conf1_scan, + pci_generic_fill_info, + conf1_ext_read, + conf1_ext_write, + NULL, /* read_vpd */ + NULL, /* init_dev */ + NULL /* cleanup_dev */ +}; diff --git a/lib/names-cache.c b/lib/names-cache.c new file mode 100644 index 0000000..16e9e9a --- /dev/null +++ b/lib/names-cache.c @@ -0,0 +1,266 @@ +/* + * The PCI Library -- ID to Name Cache + * + * Copyright (c) 2008--2009 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "internal.h" +#include "names.h" + +#ifdef PCI_USE_DNS + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <pwd.h> +#include <unistd.h> + +static const char cache_version[] = "#PCI-CACHE-1.0"; + +static char *get_cache_name(struct pci_access *a) +{ + if (!a->id_cache_name) + { + char *name = pci_get_param(a, "net.cache_name"); + if (!name || !name[0]) + return NULL; + + if (strncmp(name, "~/", 2)) + a->id_cache_name = pci_strdup(a, name); + else + { + uid_t uid = getuid(); + struct passwd *pw = getpwuid(uid); + if (!pw) + return name; + + a->id_cache_name = pci_malloc(a, strlen(pw->pw_dir) + strlen(name+1) + 1); + sprintf(a->id_cache_name, "%s%s", pw->pw_dir, name+1); + } + } + + return a->id_cache_name; +} + +static void create_parent_dirs(struct pci_access *a, char *name) +{ + // Assumes that we have a private copy of the name we can modify + + char *p = name + strlen(name); + while (p > name && *p != '/') + p--; + if (p == name) + return; + + while (p > name) + { + // We stand at a slash. Check if the current prefix exists. + *p = 0; + struct stat st; + int res = stat(name, &st); + *p = '/'; + if (res >= 0) + break; + + // Does not exist yet, move up one directory + p--; + while (p > name && *p != '/') + p--; + } + + // We now stand at the end of the longest existing prefix. + // Create all directories to the right of it. + for (;;) + { + p++; + while (*p && *p != '/') + p++; + if (!*p) + break; + + *p = 0; + int res = mkdir(name, 0777); + if (res < 0) + { + a->warning("Cannot create directory %s: %s", name, strerror(errno)); + *p = '/'; + break; + } + *p = '/'; + } +} + +int +pci_id_cache_load(struct pci_access *a, int flags) +{ + char *name; + char line[MAX_LINE]; + FILE *f; + int lino; + + if (a->id_cache_status > 0) + return 0; + a->id_cache_status = 1; + + name = get_cache_name(a); + if (!name) + return 0; + a->debug("Using cache %s\n", name); + + if (flags & PCI_LOOKUP_REFRESH_CACHE) + { + a->debug("Not loading cache, will refresh everything\n"); + a->id_cache_status = 2; + return 0; + } + + f = fopen(name, "rb"); + if (!f) + { + a->debug("Cache file does not exist\n"); + return 0; + } + /* FIXME: Compare timestamp with the pci.ids file? */ + + lino = 0; + while (fgets(line, sizeof(line), f)) + { + char *p = strchr(line, '\n'); + lino++; + if (p) + { + *p = 0; + if (lino == 1) + { + if (strcmp(line, cache_version)) + { + a->debug("Unrecognized cache version %s, ignoring\n", line); + break; + } + continue; + } + else + { + int cat, id1, id2, id3, id4, cnt; + if (sscanf(line, "%d%x%x%x%x%n", &cat, &id1, &id2, &id3, &id4, &cnt) >= 5) + { + p = line + cnt; + while (*p && *p == ' ') + p++; + pci_id_insert(a, cat, id1, id2, id3, id4, p, SRC_CACHE); + continue; + } + } + } + a->warning("Malformed cache file %s (line %d), ignoring", name, lino); + break; + } + + if (ferror(f)) + a->warning("Error while reading %s", name); + fclose(f); + return 1; +} + +void +pci_id_cache_flush(struct pci_access *a) +{ + int orig_status = a->id_cache_status; + FILE *f; + unsigned int h; + struct id_entry *e, *e2; + char hostname[256], *tmpname, *name; + int this_pid; + + a->id_cache_status = 0; + if (orig_status < 2) + return; + name = get_cache_name(a); + if (!name) + return; + + create_parent_dirs(a, name); + + this_pid = getpid(); + if (gethostname(hostname, sizeof(hostname)) < 0) + hostname[0] = 0; + else + hostname[sizeof(hostname)-1] = 0; + tmpname = pci_malloc(a, strlen(name) + strlen(hostname) + 64); + sprintf(tmpname, "%s.tmp-%s-%d", name, hostname, this_pid); + + f = fopen(tmpname, "wb"); + if (!f) + { + a->warning("Cannot write to %s: %s", name, strerror(errno)); + pci_mfree(tmpname); + return; + } + a->debug("Writing cache to %s\n", name); + fprintf(f, "%s\n", cache_version); + + for (h=0; h<HASH_SIZE; h++) + for (e=a->id_hash[h]; e; e=e->next) + if (e->src == SRC_CACHE || e->src == SRC_NET) + { + /* Negative entries are not written */ + if (!e->name[0]) + continue; + + /* Verify that every entry is written at most once */ + for (e2=a->id_hash[h]; e2 != e; e2=e2->next) + if ((e2->src == SRC_CACHE || e2->src == SRC_NET) && + e2->cat == e->cat && + e2->id12 == e->id12 && e2->id34 == e->id34) + break; + if (e2 == e) + fprintf(f, "%d %x %x %x %x %s\n", + e->cat, + pair_first(e->id12), pair_second(e->id12), + pair_first(e->id34), pair_second(e->id34), + e->name); + } + + fflush(f); + if (ferror(f)) + a->warning("Error writing %s", name); + fclose(f); + + if (rename(tmpname, name) < 0) + { + a->warning("Cannot rename %s to %s: %s", tmpname, name, strerror(errno)); + unlink(tmpname); + } + pci_mfree(tmpname); +} + +#else + +int pci_id_cache_load(struct pci_access *a UNUSED, int flags UNUSED) +{ + a->id_cache_status = 1; + return 0; +} + +void pci_id_cache_flush(struct pci_access *a) +{ + a->id_cache_status = 0; + pci_mfree(a->id_cache_name); + a->id_cache_name = NULL; +} + +#endif + +void +pci_id_cache_dirty(struct pci_access *a) +{ + if (a->id_cache_status >= 1) + a->id_cache_status = 2; +} diff --git a/lib/names-hash.c b/lib/names-hash.c new file mode 100644 index 0000000..8c75676 --- /dev/null +++ b/lib/names-hash.c @@ -0,0 +1,130 @@ +/* + * The PCI Library -- ID to Name Hash + * + * Copyright (c) 1997--2008 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <string.h> + +#include "internal.h" +#include "names.h" + +struct id_bucket { + struct id_bucket *next; + unsigned int full; +}; + +#ifdef __GNUC__ +#define BUCKET_ALIGNMENT __alignof__(struct id_bucket) +#else +union id_align { + struct id_bucket *next; + unsigned int full; +}; +#define BUCKET_ALIGNMENT sizeof(union id_align) +#endif +#define BUCKET_ALIGN(n) ((n)+BUCKET_ALIGNMENT-(n)%BUCKET_ALIGNMENT) + +static void *id_alloc(struct pci_access *a, unsigned int size) +{ + struct id_bucket *buck = a->current_id_bucket; + unsigned int pos; + + if (!a->id_hash) + { + a->id_hash = pci_malloc(a, sizeof(struct id_entry *) * HASH_SIZE); + memset(a->id_hash, 0, sizeof(struct id_entry *) * HASH_SIZE); + } + + if (!buck || buck->full + size > BUCKET_SIZE) + { + buck = pci_malloc(a, BUCKET_SIZE); + buck->next = a->current_id_bucket; + a->current_id_bucket = buck; + buck->full = BUCKET_ALIGN(sizeof(struct id_bucket)); + } + pos = buck->full; + buck->full = BUCKET_ALIGN(buck->full + size); + return (byte *)buck + pos; +} + +static inline unsigned int id_hash(int cat, u32 id12, u32 id34) +{ + unsigned int h; + + h = id12 ^ (id34 << 3) ^ (cat << 5); + return h % HASH_SIZE; +} + +int +pci_id_insert(struct pci_access *a, int cat, int id1, int id2, int id3, int id4, char *text, enum id_entry_src src) +{ + u32 id12 = id_pair(id1, id2); + u32 id34 = id_pair(id3, id4); + unsigned int h = id_hash(cat, id12, id34); + struct id_entry *n = a->id_hash ? a->id_hash[h] : NULL; + int len = strlen(text); + + while (n && (n->id12 != id12 || n->id34 != id34 || n->cat != cat)) + n = n->next; + if (n) + return 1; + n = id_alloc(a, sizeof(struct id_entry) + len); + n->id12 = id12; + n->id34 = id34; + n->cat = cat; + n->src = src; + memcpy(n->name, text, len+1); + n->next = a->id_hash[h]; + a->id_hash[h] = n; + return 0; +} + +char +*pci_id_lookup(struct pci_access *a, int flags, int cat, int id1, int id2, int id3, int id4) +{ + struct id_entry *n, *best; + u32 id12 = id_pair(id1, id2); + u32 id34 = id_pair(id3, id4); + + if (a->id_hash) + { + n = a->id_hash[id_hash(cat, id12, id34)]; + best = NULL; + for (; n; n=n->next) + { + if (n->id12 != id12 || n->id34 != id34 || n->cat != cat) + continue; + if (n->src == SRC_LOCAL && (flags & PCI_LOOKUP_SKIP_LOCAL)) + continue; + if (n->src == SRC_NET && !(flags & PCI_LOOKUP_NETWORK)) + continue; + if (n->src == SRC_CACHE && !(flags & PCI_LOOKUP_CACHE)) + continue; + if (n->src == SRC_HWDB && (flags & (PCI_LOOKUP_SKIP_LOCAL | PCI_LOOKUP_NO_HWDB))) + continue; + if (!best || best->src < n->src) + best = n; + } + if (best) + return best->name; + } + return NULL; +} + +void +pci_id_hash_free(struct pci_access *a) +{ + pci_mfree(a->id_hash); + a->id_hash = NULL; + while (a->current_id_bucket) + { + struct id_bucket *buck = a->current_id_bucket; + a->current_id_bucket = buck->next; + pci_mfree(buck); + } +} diff --git a/lib/names-hwdb.c b/lib/names-hwdb.c new file mode 100644 index 0000000..71e7229 --- /dev/null +++ b/lib/names-hwdb.c @@ -0,0 +1,118 @@ +/* + * The PCI Library -- Looking up Names via UDEV and HWDB + * + * Copyright (c) 2013--2014 Tom Gundersen <teg@jklm.no> + * Copyright (c) 2014 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <string.h> + +#include "internal.h" +#include "names.h" + +#ifdef PCI_HAVE_HWDB + +#include <libudev.h> +#include <stdio.h> +#include <stdlib.h> + +char * +pci_id_hwdb_lookup(struct pci_access *a, int cat, int id1, int id2, int id3, int id4) +{ + char modalias[64]; + const char *key = NULL; + + const char *disabled = pci_get_param(a, "hwdb.disable"); + if (disabled && atoi(disabled)) + return NULL; + + switch (cat) + { + case ID_VENDOR: + sprintf(modalias, "pci:v%08X*", id1); + key = "ID_VENDOR_FROM_DATABASE"; + break; + case ID_DEVICE: + sprintf(modalias, "pci:v%08Xd%08X*", id1, id2); + key = "ID_MODEL_FROM_DATABASE"; + break; + case ID_SUBSYSTEM: + sprintf(modalias, "pci:v%08Xd%08Xsv%08Xsd%08X*", id1, id2, id3, id4); + key = "ID_MODEL_FROM_DATABASE"; + break; + case ID_GEN_SUBSYSTEM: + sprintf(modalias, "pci:v*d*sv%08Xsd%08X*", id1, id2); + key = "ID_MODEL_FROM_DATABASE"; + break; + case ID_CLASS: + sprintf(modalias, "pci:v*d*sv*sd*bc%02X*", id1); + key = "ID_PCI_CLASS_FROM_DATABASE"; + break; + case ID_SUBCLASS: + sprintf(modalias, "pci:v*d*sv*sd*bc%02Xsc%02X*", id1, id2); + key = "ID_PCI_SUBCLASS_FROM_DATABASE"; + break; + case ID_PROGIF: + sprintf(modalias, "pci:v*d*sv*sd*bc%02Xsc%02Xi%02X*", id1, id2, id3); + key = "ID_PCI_INTERFACE_FROM_DATABASE"; + break; + } + + if (key) + { + if (!a->id_udev_hwdb) + { + a->debug("Initializing UDEV HWDB\n"); + a->id_udev = udev_new(); + a->id_udev_hwdb = udev_hwdb_new(a->id_udev); + } + + struct udev_list_entry *entry; + udev_list_entry_foreach(entry, udev_hwdb_get_properties_list_entry(a->id_udev_hwdb, modalias, 0)) + { + const char *entry_name = udev_list_entry_get_name(entry); + if (entry_name && !strcmp(entry_name, key)) + { + const char *entry_value = udev_list_entry_get_value(entry); + if (entry_value) + return pci_strdup(a, entry_value); + } + } + } + + return NULL; +} + +void +pci_id_hwdb_free(struct pci_access *a) +{ + if (a->id_udev_hwdb) + { + udev_hwdb_unref(a->id_udev_hwdb); + a->id_udev_hwdb = NULL; + } + if (a->id_udev) + { + udev_unref(a->id_udev); + a->id_udev = NULL; + } +} + +#else + +char * +pci_id_hwdb_lookup(struct pci_access *a UNUSED, int cat UNUSED, int id1 UNUSED, int id2 UNUSED, int id3 UNUSED, int id4 UNUSED) +{ + return NULL; +} + +void +pci_id_hwdb_free(struct pci_access *a UNUSED) +{ +} + +#endif diff --git a/lib/names-net.c b/lib/names-net.c new file mode 100644 index 0000000..14141f4 --- /dev/null +++ b/lib/names-net.c @@ -0,0 +1,252 @@ +/* + * The PCI Library -- Resolving ID's via DNS + * + * Copyright (c) 2007--2008 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "internal.h" +#include "names.h" + +#ifdef PCI_USE_DNS + +/* + * Our definition of BYTE_ORDER confuses arpa/nameser_compat.h on + * Solaris so we must undef it before including arpa/nameser.h. + */ +#ifdef PCI_OS_SUNOS +#undef BYTE_ORDER +#endif + +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <resolv.h> +#include <netdb.h> + +/* + * Unfortunately, there are no portable functions for DNS RR parsing, + * so we will do the bit shuffling with our own bare hands. + */ + +#define GET16(x) do { if (p+2 > end) goto err; x = (p[0] << 8) | p[1]; p += 2; } while (0) +#define GET32(x) do { if (p+4 > end) goto err; x = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; p += 4; } while (0) + +enum dns_section { + DNS_SEC_QUESTION, + DNS_SEC_ANSWER, + DNS_SEC_AUTHORITY, + DNS_SEC_ADDITIONAL, + DNS_NUM_SECTIONS +}; + +struct dns_state { + u16 counts[DNS_NUM_SECTIONS]; + byte *sections[DNS_NUM_SECTIONS+1]; + byte *sec_ptr, *sec_end; + + /* Result of dns_parse_rr(): */ + u16 rr_type; + u16 rr_class; + u32 rr_ttl; + u16 rr_len; + byte *rr_data; +}; + +static byte * +dns_skip_name(byte *p, byte *end) +{ + while (p < end) + { + unsigned int x = *p++; + if (!x) + return p; + switch (x & 0xc0) + { + case 0: /* Uncompressed: x = length */ + p += x; + break; + case 0xc0: /* Indirection: 1 byte more for offset */ + p++; + return (p < end) ? p : NULL; + default: /* RFU */ + return NULL; + } + } + return NULL; +} + +static int +dns_parse_packet(struct dns_state *s, byte *p, unsigned int plen) +{ + byte *end = p + plen; + unsigned int i, j, len; + unsigned int UNUSED x; + +#if 0 + /* Dump the packet */ + for (i=0; i<plen; i++) + { + if (!(i%16)) printf("%04x:", i); + printf(" %02x", p[i]); + if ((i%16)==15 || i==plen-1) putchar('\n'); + } +#endif + + GET32(x); /* ID and flags are ignored */ + for (i=0; i<DNS_NUM_SECTIONS; i++) + GET16(s->counts[i]); + for (i=0; i<DNS_NUM_SECTIONS; i++) + { + s->sections[i] = p; + for (j=0; j < s->counts[i]; j++) + { + p = dns_skip_name(p, end); /* Name */ + if (!p) + goto err; + GET32(x); /* Type and class */ + if (i != DNS_SEC_QUESTION) + { + GET32(x); /* TTL */ + GET16(len); /* Length of data */ + p += len; + if (p > end) + goto err; + } + } + } + s->sections[i] = p; + return 0; + +err: + return -1; +} + +static void +dns_init_section(struct dns_state *s, int i) +{ + s->sec_ptr = s->sections[i]; + s->sec_end = s->sections[i+1]; +} + +static int +dns_parse_rr(struct dns_state *s) +{ + byte *p = s->sec_ptr; + byte *end = s->sec_end; + + if (p == end) + return 0; + p = dns_skip_name(p, end); + if (!p) + goto err; + GET16(s->rr_type); + GET16(s->rr_class); + GET32(s->rr_ttl); + GET16(s->rr_len); + s->rr_data = p; + s->sec_ptr = p + s->rr_len; + return 1; + +err: + return -1; +} + +char +*pci_id_net_lookup(struct pci_access *a, int cat, int id1, int id2, int id3, int id4) +{ + static int resolver_inited; + char name[256], dnsname[256], txt[256], *domain; + byte answer[4096]; + const byte *data; + int res, j, dlen; + struct dns_state ds; + + domain = pci_get_param(a, "net.domain"); + if (!domain || !domain[0]) + return NULL; + + switch (cat) + { + case ID_VENDOR: + sprintf(name, "%04x", id1); + break; + case ID_DEVICE: + sprintf(name, "%04x.%04x", id2, id1); + break; + case ID_SUBSYSTEM: + sprintf(name, "%04x.%04x.%04x.%04x", id4, id3, id2, id1); + break; + case ID_GEN_SUBSYSTEM: + sprintf(name, "%04x.%04x.s", id2, id1); + break; + case ID_CLASS: + sprintf(name, "%02x.c", id1); + break; + case ID_SUBCLASS: + sprintf(name, "%02x.%02x.c", id2, id1); + break; + case ID_PROGIF: + sprintf(name, "%02x.%02x.%02x.c", id3, id2, id1); + break; + default: + return NULL; + } + sprintf(dnsname, "%.100s.%.100s", name, domain); + + a->debug("Resolving %s\n", dnsname); + if (!resolver_inited) + { + resolver_inited = 1; + res_init(); + } + res = res_query(dnsname, ns_c_in, ns_t_txt, answer, sizeof(answer)); + if (res < 0) + { + a->debug("\tfailed, h_errno=%d\n", h_errno); + return NULL; + } + if (dns_parse_packet(&ds, answer, res) < 0) + { + a->debug("\tMalformed DNS packet received\n"); + return NULL; + } + dns_init_section(&ds, DNS_SEC_ANSWER); + while (dns_parse_rr(&ds) > 0) + { + if (ds.rr_class != ns_c_in || ds.rr_type != ns_t_txt) + { + a->debug("\tUnexpected RR in answer: class %d, type %d\n", ds.rr_class, ds.rr_type); + continue; + } + data = ds.rr_data; + dlen = ds.rr_len; + j = 0; + while (j < dlen && j+1+data[j] <= dlen) + { + memcpy(txt, &data[j+1], data[j]); + txt[data[j]] = 0; + j += 1+data[j]; + a->debug("\t\"%s\"\n", txt); + if (txt[0] == 'i' && txt[1] == '=') + return strdup(txt+2); + } + } + + return NULL; +} + +#else + +char *pci_id_net_lookup(struct pci_access *a UNUSED, int cat UNUSED, int id1 UNUSED, int id2 UNUSED, int id3 UNUSED, int id4 UNUSED) +{ + return NULL; +} + +#endif diff --git a/lib/names-parse.c b/lib/names-parse.c new file mode 100644 index 0000000..1f8925a --- /dev/null +++ b/lib/names-parse.c @@ -0,0 +1,253 @@ +/* + * The PCI Library -- Parsing of the ID list + * + * Copyright (c) 1997--2008 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "internal.h" +#include "names.h" + +#ifdef PCI_COMPRESSED_IDS +#include <zlib.h> +typedef gzFile pci_file; +#define pci_gets(f, l, s) gzgets(f, l, s) +#define pci_eof(f) gzeof(f) + +static pci_file pci_open(struct pci_access *a) +{ + pci_file result; + size_t len; + char *new_name; + + result = gzopen(a->id_file_name, "rb"); + if (result) + return result; + len = strlen(a->id_file_name); + if (len < 3 || memcmp(a->id_file_name + len - 3, ".gz", 3) != 0) + return result; + new_name = malloc(len - 2); + memcpy(new_name, a->id_file_name, len - 3); + new_name[len - 3] = 0; + pci_set_name_list_path(a, new_name, 1); + return gzopen(a->id_file_name, "rb"); +} + +#define pci_close(f) gzclose(f) +#define PCI_ERROR(f, err) \ + if (!err) { \ + int errnum = 0; \ + gzerror(f, &errnum); \ + if (errnum >= 0) err = NULL; \ + else if (errnum == Z_ERRNO) err = "I/O error"; \ + else err = zError(errnum); \ + } +#else +typedef FILE * pci_file; +#define pci_gets(f, l, s) fgets(l, s, f) +#define pci_eof(f) feof(f) +#define pci_open(a) fopen(a->id_file_name, "r") +#define pci_close(f) fclose(f) +#define PCI_ERROR(f, err) if (!err && ferror(f)) err = "I/O error"; +#endif + +static int id_hex(char *p, int cnt) +{ + int x = 0; + while (cnt--) + { + x <<= 4; + if (*p >= '0' && *p <= '9') + x += (*p - '0'); + else if (*p >= 'a' && *p <= 'f') + x += (*p - 'a' + 10); + else if (*p >= 'A' && *p <= 'F') + x += (*p - 'A' + 10); + else + return -1; + p++; + } + return x; +} + +static inline int id_white_p(int c) +{ + return (c == ' ') || (c == '\t'); +} + + +static const char *id_parse_list(struct pci_access *a, pci_file f, int *lino) +{ + char line[MAX_LINE]; + char *p; + int id1=0, id2=0, id3=0, id4=0; + int cat = -1; + int nest; + static const char parse_error[] = "Parse error"; + + *lino = 0; + while (pci_gets(f, line, sizeof(line))) + { + (*lino)++; + p = line; + while (*p && *p != '\n' && *p != '\r') + p++; + if (!*p && !pci_eof(f)) + return "Line too long"; + *p = 0; + if (p > line && (p[-1] == ' ' || p[-1] == '\t')) + *--p = 0; + + p = line; + while (id_white_p(*p)) + p++; + if (!*p || *p == '#') + continue; + + p = line; + while (*p == '\t') + p++; + nest = p - line; + + if (!nest) /* Top-level entries */ + { + if (p[0] == 'C' && p[1] == ' ') /* Class block */ + { + if ((id1 = id_hex(p+2, 2)) < 0 || !id_white_p(p[4])) + return parse_error; + cat = ID_CLASS; + p += 5; + } + else if (p[0] == 'S' && p[1] == ' ') + { /* Generic subsystem block */ + if ((id1 = id_hex(p+2, 4)) < 0 || p[6]) + return parse_error; + if (!pci_id_lookup(a, 0, ID_VENDOR, id1, 0, 0, 0)) + return "Vendor does not exist"; + cat = ID_GEN_SUBSYSTEM; + continue; + } + else if (p[0] >= 'A' && p[0] <= 'Z' && p[1] == ' ') + { /* Unrecognized block (RFU) */ + cat = ID_UNKNOWN; + continue; + } + else /* Vendor ID */ + { + if ((id1 = id_hex(p, 4)) < 0 || !id_white_p(p[4])) + return parse_error; + cat = ID_VENDOR; + p += 5; + } + id2 = id3 = id4 = 0; + } + else if (cat == ID_UNKNOWN) /* Nested entries in RFU blocks are skipped */ + continue; + else if (nest == 1) /* Nesting level 1 */ + switch (cat) + { + case ID_VENDOR: + case ID_DEVICE: + case ID_SUBSYSTEM: + if ((id2 = id_hex(p, 4)) < 0 || !id_white_p(p[4])) + return parse_error; + p += 5; + cat = ID_DEVICE; + id3 = id4 = 0; + break; + case ID_GEN_SUBSYSTEM: + if ((id2 = id_hex(p, 4)) < 0 || !id_white_p(p[4])) + return parse_error; + p += 5; + id3 = id4 = 0; + break; + case ID_CLASS: + case ID_SUBCLASS: + case ID_PROGIF: + if ((id2 = id_hex(p, 2)) < 0 || !id_white_p(p[2])) + return parse_error; + p += 3; + cat = ID_SUBCLASS; + id3 = id4 = 0; + break; + default: + return parse_error; + } + else if (nest == 2) /* Nesting level 2 */ + switch (cat) + { + case ID_DEVICE: + case ID_SUBSYSTEM: + if ((id3 = id_hex(p, 4)) < 0 || !id_white_p(p[4]) || (id4 = id_hex(p+5, 4)) < 0 || !id_white_p(p[9])) + return parse_error; + p += 10; + cat = ID_SUBSYSTEM; + break; + case ID_CLASS: + case ID_SUBCLASS: + case ID_PROGIF: + if ((id3 = id_hex(p, 2)) < 0 || !id_white_p(p[2])) + return parse_error; + p += 3; + cat = ID_PROGIF; + id4 = 0; + break; + default: + return parse_error; + } + else /* Nesting level 3 or more */ + return parse_error; + while (id_white_p(*p)) + p++; + if (!*p) + return parse_error; + if (pci_id_insert(a, cat, id1, id2, id3, id4, p, SRC_LOCAL)) + return "Duplicate entry"; + } + return NULL; +} + +int +pci_load_name_list(struct pci_access *a) +{ + pci_file f; + int lino; + const char *err; + + pci_free_name_list(a); + a->id_load_attempted = 1; + if (!(f = pci_open(a))) + return 0; + err = id_parse_list(a, f, &lino); + PCI_ERROR(f, err); + pci_close(f); + if (err) + a->error("%s at %s, line %d\n", err, a->id_file_name, lino); + return 1; +} + +void +pci_free_name_list(struct pci_access *a) +{ + pci_id_cache_flush(a); + pci_id_hash_free(a); + pci_id_hwdb_free(a); + a->id_load_attempted = 0; +} + +void +pci_set_name_list_path(struct pci_access *a, char *name, int to_be_freed) +{ + if (a->free_id_name) + free(a->id_file_name); + a->id_file_name = name; + a->free_id_name = to_be_freed; +} diff --git a/lib/names.c b/lib/names.c new file mode 100644 index 0000000..a287cb0 --- /dev/null +++ b/lib/names.c @@ -0,0 +1,229 @@ +/* + * The PCI Library -- ID to Name Translation + * + * Copyright (c) 1997--2014 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> + +#include "internal.h" +#include "names.h" + +static char *id_lookup(struct pci_access *a, int flags, int cat, int id1, int id2, int id3, int id4) +{ + char *name; + int tried_hwdb = 0; + + while (!(name = pci_id_lookup(a, flags, cat, id1, id2, id3, id4))) + { + if ((flags & PCI_LOOKUP_CACHE) && !a->id_cache_status) + { + if (pci_id_cache_load(a, flags)) + continue; + } + if (!tried_hwdb && !(flags & (PCI_LOOKUP_SKIP_LOCAL | PCI_LOOKUP_NO_HWDB))) + { + tried_hwdb = 1; + if (name = pci_id_hwdb_lookup(a, cat, id1, id2, id3, id4)) + { + pci_id_insert(a, cat, id1, id2, id3, id4, name, SRC_HWDB); + pci_mfree(name); + continue; + } + } + if (flags & PCI_LOOKUP_NETWORK) + { + if (name = pci_id_net_lookup(a, cat, id1, id2, id3, id4)) + { + pci_id_insert(a, cat, id1, id2, id3, id4, name, SRC_NET); + pci_mfree(name); + pci_id_cache_dirty(a); + } + else + pci_id_insert(a, cat, id1, id2, id3, id4, "", SRC_NET); + /* We want to iterate the lookup to get the allocated ID entry from the hash */ + continue; + } + return NULL; + } + return (name[0] ? name : NULL); +} + +static char * +id_lookup_subsys(struct pci_access *a, int flags, int iv, int id, int isv, int isd) +{ + char *d = NULL; + if (iv > 0 && id > 0) /* Per-device lookup */ + d = id_lookup(a, flags, ID_SUBSYSTEM, iv, id, isv, isd); + if (!d) /* Generic lookup */ + d = id_lookup(a, flags, ID_GEN_SUBSYSTEM, isv, isd, 0, 0); + if (!d && iv == isv && id == isd) /* Check for subsystem == device */ + d = id_lookup(a, flags, ID_DEVICE, iv, id, 0, 0); + return d; +} + +static char * +format_name(char *buf, int size, int flags, char *name, char *num, char *unknown) +{ + int res; + if ((flags & PCI_LOOKUP_NO_NUMBERS) && !name) + return NULL; + else if (flags & PCI_LOOKUP_NUMERIC) + res = snprintf(buf, size, "%s", num); + else if (!name) + res = snprintf(buf, size, ((flags & PCI_LOOKUP_MIXED) ? "%s [%s]" : "%s %s"), unknown, num); + else if (!(flags & PCI_LOOKUP_MIXED)) + res = snprintf(buf, size, "%s", name); + else + res = snprintf(buf, size, "%s [%s]", name, num); + if (res >= size && size >= 4) + buf[size-2] = buf[size-3] = buf[size-4] = '.'; + else if (res < 0 || res >= size) + return "<pci_lookup_name: buffer too small>"; + return buf; +} + +static char * +format_name_pair(char *buf, int size, int flags, char *v, char *d, char *num) +{ + int res; + if ((flags & PCI_LOOKUP_NO_NUMBERS) && (!v || !d)) + return NULL; + if (flags & PCI_LOOKUP_NUMERIC) + res = snprintf(buf, size, "%s", num); + else if (flags & PCI_LOOKUP_MIXED) + { + if (v && d) + res = snprintf(buf, size, "%s %s [%s]", v, d, num); + else if (!v) + res = snprintf(buf, size, "Device [%s]", num); + else /* v && !d */ + res = snprintf(buf, size, "%s Device [%s]", v, num); + } + else + { + if (v && d) + res = snprintf(buf, size, "%s %s", v, d); + else if (!v) + res = snprintf(buf, size, "Device %s", num); + else /* v && !d */ + res = snprintf(buf, size, "%s Device %s", v, num+5); + } + if (res >= size && size >= 4) + buf[size-2] = buf[size-3] = buf[size-4] = '.'; + else if (res < 0 || res >= size) + return "<pci_lookup_name: buffer too small>"; + return buf; +} + +char * +pci_lookup_name(struct pci_access *a, char *buf, int size, int flags, ...) +{ + va_list args; + char *v, *d, *cls, *pif; + int iv, id, isv, isd, icls, ipif; + char numbuf[16], pifbuf[32]; + + va_start(args, flags); + + flags |= a->id_lookup_mode; + if (!(flags & PCI_LOOKUP_NO_NUMBERS)) + { + if (a->numeric_ids > 1) + flags |= PCI_LOOKUP_MIXED; + else if (a->numeric_ids) + flags |= PCI_LOOKUP_NUMERIC; + } + if (flags & PCI_LOOKUP_MIXED) + flags &= ~PCI_LOOKUP_NUMERIC; + + if (!a->id_load_attempted && !(flags & (PCI_LOOKUP_NUMERIC | PCI_LOOKUP_SKIP_LOCAL))) + pci_load_name_list(a); + + switch (flags & 0xffff) + { + case PCI_LOOKUP_VENDOR: + iv = va_arg(args, int); + sprintf(numbuf, "%04x", iv); + va_end(args); + return format_name(buf, size, flags, id_lookup(a, flags, ID_VENDOR, iv, 0, 0, 0), numbuf, "Vendor"); + case PCI_LOOKUP_DEVICE: + iv = va_arg(args, int); + id = va_arg(args, int); + sprintf(numbuf, "%04x", id); + va_end(args); + return format_name(buf, size, flags, id_lookup(a, flags, ID_DEVICE, iv, id, 0, 0), numbuf, "Device"); + case PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE: + iv = va_arg(args, int); + id = va_arg(args, int); + sprintf(numbuf, "%04x:%04x", iv, id); + v = id_lookup(a, flags, ID_VENDOR, iv, 0, 0, 0); + d = id_lookup(a, flags, ID_DEVICE, iv, id, 0, 0); + va_end(args); + return format_name_pair(buf, size, flags, v, d, numbuf); + case PCI_LOOKUP_SUBSYSTEM | PCI_LOOKUP_VENDOR: + isv = va_arg(args, int); + sprintf(numbuf, "%04x", isv); + v = id_lookup(a, flags, ID_VENDOR, isv, 0, 0, 0); + va_end(args); + return format_name(buf, size, flags, v, numbuf, "Unknown vendor"); + case PCI_LOOKUP_SUBSYSTEM | PCI_LOOKUP_DEVICE: + iv = va_arg(args, int); + id = va_arg(args, int); + isv = va_arg(args, int); + isd = va_arg(args, int); + sprintf(numbuf, "%04x", isd); + va_end(args); + return format_name(buf, size, flags, id_lookup_subsys(a, flags, iv, id, isv, isd), numbuf, "Device"); + case PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE | PCI_LOOKUP_SUBSYSTEM: + iv = va_arg(args, int); + id = va_arg(args, int); + isv = va_arg(args, int); + isd = va_arg(args, int); + v = id_lookup(a, flags, ID_VENDOR, isv, 0, 0, 0); + d = id_lookup_subsys(a, flags, iv, id, isv, isd); + sprintf(numbuf, "%04x:%04x", isv, isd); + va_end(args); + return format_name_pair(buf, size, flags, v, d, numbuf); + case PCI_LOOKUP_CLASS: + icls = va_arg(args, int); + sprintf(numbuf, "%04x", icls); + cls = id_lookup(a, flags, ID_SUBCLASS, icls >> 8, icls & 0xff, 0, 0); + if (!cls && (cls = id_lookup(a, flags, ID_CLASS, icls >> 8, 0, 0, 0))) + { + if (!(flags & PCI_LOOKUP_NUMERIC)) /* Include full class number */ + flags |= PCI_LOOKUP_MIXED; + } + va_end(args); + return format_name(buf, size, flags, cls, numbuf, "Class"); + case PCI_LOOKUP_PROGIF: + icls = va_arg(args, int); + ipif = va_arg(args, int); + sprintf(numbuf, "%02x", ipif); + pif = id_lookup(a, flags, ID_PROGIF, icls >> 8, icls & 0xff, ipif, 0); + if (!pif && icls == 0x0101 && !(ipif & 0x70)) + { + /* IDE controllers have complex prog-if semantics */ + sprintf(pifbuf, "%s%s%s%s%s", + (ipif & 0x80) ? " Master" : "", + (ipif & 0x08) ? " SecP" : "", + (ipif & 0x04) ? " SecO" : "", + (ipif & 0x02) ? " PriP" : "", + (ipif & 0x01) ? " PriO" : ""); + pif = pifbuf; + if (*pif) + pif++; + } + va_end(args); + return format_name(buf, size, flags, pif, numbuf, "ProgIf"); + default: + va_end(args); + return "<pci_lookup_name: invalid request>"; + } +} diff --git a/lib/names.h b/lib/names.h new file mode 100644 index 0000000..b34e3e6 --- /dev/null +++ b/lib/names.h @@ -0,0 +1,77 @@ +/* + * The PCI Library -- ID to Name Translation + * + * Copyright (c) 1997--2014 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define MAX_LINE 1024 + +/* names-hash.c */ + +struct id_entry { + struct id_entry *next; + u32 id12, id34; + byte cat; + byte src; + char name[1]; +}; + +enum id_entry_type { + ID_UNKNOWN, + ID_VENDOR, + ID_DEVICE, + ID_SUBSYSTEM, + ID_GEN_SUBSYSTEM, + ID_CLASS, + ID_SUBCLASS, + ID_PROGIF +}; + +enum id_entry_src { + SRC_UNKNOWN, + SRC_CACHE, + SRC_NET, + SRC_HWDB, + SRC_LOCAL, +}; + +#define BUCKET_SIZE 8192 +#define HASH_SIZE 4099 + +static inline u32 id_pair(unsigned int x, unsigned int y) +{ + return ((x << 16) | y); +} + +static inline unsigned int pair_first(unsigned int x) +{ + return (x >> 16) & 0xffff; +} + +static inline unsigned int pair_second(unsigned int x) +{ + return x & 0xffff; +} + +int pci_id_insert(struct pci_access *a, int cat, int id1, int id2, int id3, int id4, char *text, enum id_entry_src src); +char *pci_id_lookup(struct pci_access *a, int flags, int cat, int id1, int id2, int id3, int id4); + +/* names-cache.c */ + +int pci_id_cache_load(struct pci_access *a, int flags); +void pci_id_cache_dirty(struct pci_access *a); +void pci_id_cache_flush(struct pci_access *a); +void pci_id_hash_free(struct pci_access *a); + +/* names-dns.c */ + +char *pci_id_net_lookup(struct pci_access *a, int cat, int id1, int id2, int id3, int id4); + +/* names-hwdb.c */ + +char *pci_id_hwdb_lookup(struct pci_access *a, int cat, int id1, int id2, int id3, int id4); +void pci_id_hwdb_free(struct pci_access *a); diff --git a/lib/nbsd-libpci.c b/lib/nbsd-libpci.c new file mode 100644 index 0000000..0f9f27b --- /dev/null +++ b/lib/nbsd-libpci.c @@ -0,0 +1,159 @@ +/* + * The PCI Library -- NetBSD libpci access + * (based on FreeBSD /dev/pci access) + * + * Copyright (c) 1999 Jari Kirma <kirma@cs.hut.fi> + * Copyright (c) 2002 Quentin Garnier <cube@cubidou.net> + * Copyright (c) 2002 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Read functionality of this driver is briefly tested, and seems + * to supply basic information correctly, but I promise no more. + */ + +#include <fcntl.h> +#include <string.h> +#include <unistd.h> + +#include <pci.h> + +#include "internal.h" + +static void +nbsd_config(struct pci_access *a) +{ + pci_define_param(a, "nbsd.path", PCI_PATH_NBSD_DEVICE, "Path to the NetBSD PCI device"); +} + +static int +nbsd_detect(struct pci_access *a) +{ + char *name = pci_get_param(a, "nbsd.path"); + + if (access(name, R_OK)) + { + a->warning("Cannot open %s", name); + return 0; + } + + if (!access(name, W_OK)) + a->writeable = O_RDWR; + a->debug("...using %s", name); + return 1; +} + +static void +nbsd_init(struct pci_access *a) +{ + char *name = pci_get_param(a, "nbsd.path"); + int mode = a->writeable ? O_RDWR : O_RDONLY; + + a->fd = open(name, mode, 0); + if (a->fd < 0) + a->error("nbsd_init: %s open failed", name); +} + +static void +nbsd_cleanup(struct pci_access *a) +{ + close(a->fd); +} + +static int +nbsd_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + pcireg_t val; + int shift; + + if (!(len == 1 || len == 2 || len == 4)) + return pci_generic_block_read(d, pos, buf, len); + + if (d->domain || pos >= 4096) + return 0; + + shift = 8*(pos % 4); + pos &= ~3; + + if (pcibus_conf_read(d->access->fd, d->bus, d->dev, d->func, pos, &val) < 0) + d->access->error("nbsd_read: pci_bus_conf_read() failed"); + + switch (len) + { + case 1: + *buf = val >> shift; + break; + case 2: + *(u16*)buf = cpu_to_le16(val >> shift); + break; + case 4: + *(u32*)buf = cpu_to_le32(val); + break; + } + return 1; +} + +static int +nbsd_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + pcireg_t val = 0; + int shift; + + if (!(len == 1 || len == 2 || len == 4)) + return pci_generic_block_write(d, pos, buf, len); + + if (d->domain || pos >= 256) + return 0; + + /* + * BEWARE: NetBSD seems to support only 32-bit access, so we have + * to emulate byte and word writes by read-modify-write, possibly + * causing troubles. + */ + + shift = 8*(pos % 4); + pos &= ~3; + if (len != 4) + { + if (pcibus_conf_read(d->access->fd, d->bus, d->dev, d->func, pos, &val) < 0) + d->access->error("nbsd_write: pci_bus_conf_read() failed"); + } + + switch (len) + { + case 1: + val = (val & ~(0xff << shift)) | (buf[0] << shift); + break; + case 2: + val = (val & ~(0xffff << shift)) | (le16_to_cpu(*(u16*)buf) << shift); + break; + case 4: + val = le32_to_cpu(*(u32*)buf); + break; + } + + if (pcibus_conf_write(d->access->fd, d->bus, d->dev, d->func, pos, val) < 0) + d->access->error("nbsd_write: pci_bus_conf_write() failed"); + + return 1; +} + +struct pci_methods pm_nbsd_libpci = { + "nbsd-libpci", + "NetBSD libpci", + nbsd_config, + nbsd_detect, + nbsd_init, + nbsd_cleanup, + pci_generic_scan, + pci_generic_fill_info, + nbsd_read, + nbsd_write, + NULL, /* read_vpd */ + NULL, /* dev_init */ + NULL /* dev_cleanup */ +}; diff --git a/lib/obsd-device.c b/lib/obsd-device.c new file mode 100644 index 0000000..e2e7652 --- /dev/null +++ b/lib/obsd-device.c @@ -0,0 +1,154 @@ +/* + * The PCI Library -- OpenBSD /dev/pci access + * + * Adapted from fbsd-device.c by Matthieu Herrb <matthieu.herrb@laas.fr>, 2006 + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <sys/endian.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/pciio.h> +#include "internal.h" + +static void +obsd_config(struct pci_access *a) +{ + pci_define_param(a, "obsd.path", PCI_PATH_OBSD_DEVICE, "Path to the OpenBSD PCI device"); +} + +static int +obsd_detect(struct pci_access *a) +{ + char *name = pci_get_param(a, "obsd.path"); + + if (access(name, R_OK)) + { + a->warning("Cannot open %s", name); + return 0; + } + a->debug("...using %s", name); + return 1; +} + +static void +obsd_init(struct pci_access *a) +{ + char *name = pci_get_param(a, "obsd.path"); + + a->fd = open(name, O_RDWR, 0); + if (a->fd < 0) + a->error("obsd_init: %s open failed", name); +} + +static void +obsd_cleanup(struct pci_access *a) +{ + close(a->fd); +} + +static int +obsd_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + struct pci_io pi; + union { + u_int32_t u32; + u_int16_t u16[2]; + u_int8_t u8[4]; + } u; + + if (!(len == 1 || len == 2 || len == 4)) + return pci_generic_block_read(d, pos, buf, len); + + if (d->domain || pos >= 256) + return 0; + + pi.pi_sel.pc_bus = d->bus; + pi.pi_sel.pc_dev = d->dev; + pi.pi_sel.pc_func = d->func; + + pi.pi_reg = pos - (pos % 4); + pi.pi_width = 4; + + if (ioctl(d->access->fd, PCIOCREAD, &pi) < 0) { + if (errno == ENXIO) + pi.pi_data = 0xffffffff; + else + d->access->error("obsd_read: ioctl(PCIOCREAD) failed"); + } + u.u32 = pi.pi_data; + + switch (len) + { + case 1: + buf[0] = (u8) u.u8[pos % 4]; + break; + case 2: + ((u16 *) buf)[0] = letoh16(u.u16[(pos % 4) / 2]); + break; + case 4: + ((u32 *) buf)[0] = (u32) letoh32(pi.pi_data); + break; + } + return 1; +} + +static int +obsd_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + struct pci_io pi; + + if (!(len == 1 || len == 2 || len == 4)) + return pci_generic_block_write(d, pos, buf, len); + + if (d->domain || pos >= 256) + return 0; + + pi.pi_sel.pc_bus = d->bus; + pi.pi_sel.pc_dev = d->dev; + pi.pi_sel.pc_func = d->func; + + pi.pi_reg = pos; + pi.pi_width = len; + + switch (len) + { + case 1: + pi.pi_data = buf[0]; + break; + case 2: + pi.pi_data = ((u16 *) buf)[0]; + break; + case 4: + pi.pi_data = ((u32 *) buf)[0]; + break; + } + + if (ioctl(d->access->fd, PCIOCWRITE, &pi) < 0) + d->access->error("obsd_write: ioctl(PCIOCWRITE) failed"); + + return 1; +} + +struct pci_methods pm_obsd_device = { + "obsd-device", + "/dev/pci on OpenBSD", + obsd_config, + obsd_detect, + obsd_init, + obsd_cleanup, + pci_generic_scan, + pci_generic_fill_info, + obsd_read, + obsd_write, + NULL, /* read_vpd */ + NULL, /* dev_init */ + NULL /* dev_cleanup */ +}; diff --git a/lib/params.c b/lib/params.c new file mode 100644 index 0000000..9b4c2e2 --- /dev/null +++ b/lib/params.c @@ -0,0 +1,104 @@ +/* + * The PCI Library -- Parameters + * + * Copyright (c) 2008--2023 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "internal.h" + +char * +pci_get_param(struct pci_access *acc, char *param) +{ + struct pci_param *p; + + for (p=acc->params; p; p=p->next) + if (!strcmp(p->param, param)) + return p->value; + return NULL; +} + +struct pci_param * +pci_define_param(struct pci_access *acc, char *param, char *value, char *help) +{ + struct pci_param *p, **pp; + + for (pp=&acc->params; p = *pp; pp=&p->next) + { + int cmp = strcmp(p->param, param); + if (!cmp) + { + if (strcmp(p->value, value) || strcmp(p->help, help)) + acc->error("Parameter %s re-defined differently", param); + return p; + } + if (cmp > 0) + break; + } + + p = pci_malloc(acc, sizeof(*p)); + p->next = *pp; + *pp = p; + p->param = param; + p->value = value; + p->value_malloced = 0; + p->help = help; + return p; +} + +int +pci_set_param_internal(struct pci_access *acc, char *param, char *value, int copy) +{ + struct pci_param *p; + + for (p=acc->params; p; p=p->next) + if (!strcmp(p->param, param)) + { + if (p->value_malloced) + pci_mfree(p->value); + p->value_malloced = copy; + if (copy) + p->value = pci_strdup(acc, value); + else + p->value = value; + return 0; + } + return -1; +} + +int +pci_set_param(struct pci_access *acc, char *param, char *value) +{ + return pci_set_param_internal(acc, param, value, 1); +} + +void +pci_free_params(struct pci_access *acc) +{ + struct pci_param *p; + + while (p = acc->params) + { + acc->params = p->next; + if (p->value_malloced) + pci_mfree(p->value); + pci_mfree(p); + } +} + +struct pci_param * +pci_walk_params(struct pci_access *acc, struct pci_param *prev) +{ + /* So far, the params form a simple linked list, but this can change in the future */ + if (!prev) + return acc->params; + else + return prev->next; +} diff --git a/lib/pci.h b/lib/pci.h new file mode 100644 index 0000000..ae1333f --- /dev/null +++ b/lib/pci.h @@ -0,0 +1,312 @@ +/* + * The PCI Library + * + * Copyright (c) 1997--2024 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef _PCI_LIB_H +#define _PCI_LIB_H + +#ifndef PCI_CONFIG_H +#include "config.h" +#endif + +#include "header.h" +#include "types.h" + +#define PCI_LIB_VERSION 0x030b01 + +#ifndef PCI_ABI +#define PCI_ABI +#endif + +/* + * PCI Access Structure + */ + +struct pci_methods; + +enum pci_access_type { + /* Known access methods, remember to update init.c as well */ + PCI_ACCESS_AUTO, /* Autodetection */ + PCI_ACCESS_SYS_BUS_PCI, /* Linux /sys/bus/pci */ + PCI_ACCESS_PROC_BUS_PCI, /* Linux /proc/bus/pci */ + PCI_ACCESS_I386_TYPE1, /* i386 ports, type 1 */ + PCI_ACCESS_I386_TYPE2, /* i386 ports, type 2 */ + PCI_ACCESS_FBSD_DEVICE, /* FreeBSD /dev/pci */ + PCI_ACCESS_AIX_DEVICE, /* /dev/pci0, /dev/bus0, etc. */ + PCI_ACCESS_NBSD_LIBPCI, /* NetBSD libpci */ + PCI_ACCESS_OBSD_DEVICE, /* OpenBSD /dev/pci */ + PCI_ACCESS_DUMP, /* Dump file */ + PCI_ACCESS_DARWIN, /* Darwin */ + PCI_ACCESS_SYLIXOS_DEVICE, /* SylixOS pci */ + PCI_ACCESS_HURD, /* GNU/Hurd */ + PCI_ACCESS_WIN32_CFGMGR32, /* Win32 cfgmgr32.dll */ + PCI_ACCESS_WIN32_KLDBG, /* Win32 kldbgdrv.sys */ + PCI_ACCESS_WIN32_SYSDBG, /* Win32 NT SysDbg */ + PCI_ACCESS_MMIO_TYPE1, /* MMIO ports, type 1 */ + PCI_ACCESS_MMIO_TYPE1_EXT, /* MMIO ports, type 1 extended */ + PCI_ACCESS_ECAM, /* PCIe ECAM via /dev/mem */ + PCI_ACCESS_AOS_EXPANSION, /* AmigaOS Expansion library */ + PCI_ACCESS_MAX +}; + +struct pci_access { + /* Options you can change: */ + unsigned int method; /* Access method */ + int writeable; /* Open in read/write mode */ + int buscentric; /* Bus-centric view of the world */ + + char *id_file_name; /* Name of ID list file (use pci_set_name_list_path()) */ + int free_id_name; /* Set if id_file_name is malloced */ + int numeric_ids; /* Enforce PCI_LOOKUP_NUMERIC (>1 => PCI_LOOKUP_MIXED) */ + + unsigned int id_lookup_mode; /* pci_lookup_mode flags which are set automatically */ + /* Default: PCI_LOOKUP_CACHE */ + + int debugging; /* Turn on debugging messages */ + + /* Functions you can override: */ + void (*error)(char *msg, ...) PCI_PRINTF(1,2) PCI_NONRET; /* Write error message and quit */ + void (*warning)(char *msg, ...) PCI_PRINTF(1,2); /* Write a warning message */ + void (*debug)(char *msg, ...) PCI_PRINTF(1,2); /* Write a debugging message */ + + struct pci_dev *devices; /* Devices found on this bus */ + + /* Fields used internally: */ + struct pci_methods *methods; + struct pci_param *params; + struct id_entry **id_hash; /* names.c */ + struct id_bucket *current_id_bucket; + int id_load_attempted; + int id_cache_status; /* 0=not read, 1=read, 2=dirty */ + char *id_cache_name; + struct udev *id_udev; /* names-hwdb.c */ + struct udev_hwdb *id_udev_hwdb; + int fd; /* proc/sys: fd for config space */ + int fd_rw; /* proc/sys: fd opened read-write */ + int fd_vpd; /* sys: fd for VPD */ + struct pci_dev *cached_dev; /* proc/sys: device the fds are for */ + void *backend_data; /* Private data of the back end */ +}; + +/* Initialize PCI access */ +struct pci_access *pci_alloc(void) PCI_ABI; +void pci_init(struct pci_access *) PCI_ABI; +void pci_cleanup(struct pci_access *) PCI_ABI; + +/* Scanning of devices */ +void pci_scan_bus(struct pci_access *acc) PCI_ABI; +struct pci_dev *pci_get_dev(struct pci_access *acc, int domain, int bus, int dev, int func) PCI_ABI; /* Raw access to specified device */ +void pci_free_dev(struct pci_dev *) PCI_ABI; + +/* Names of access methods */ +int pci_lookup_method(char *name) PCI_ABI; /* Returns -1 if not found */ +char *pci_get_method_name(int index) PCI_ABI; /* Returns "" if unavailable, NULL if index out of range */ + +/* + * Named parameters + */ + +struct pci_param { + struct pci_param *next; /* Please use pci_walk_params() for traversing the list */ + char *param; /* Name of the parameter */ + char *value; /* Value of the parameter */ + int value_malloced; /* used internally */ + char *help; /* Explanation of the parameter */ +}; + +char *pci_get_param(struct pci_access *acc, char *param) PCI_ABI; +int pci_set_param(struct pci_access *acc, char *param, char *value) PCI_ABI; /* 0 on success, -1 if no such parameter */ +/* To traverse the list, call pci_walk_params repeatedly, first with prev=NULL, and do not modify the parameters during traversal. */ +struct pci_param *pci_walk_params(struct pci_access *acc, struct pci_param *prev) PCI_ABI; + +/* + * Devices + */ + +struct pci_dev { + struct pci_dev *next; /* Next device in the chain */ + u16 domain_16; /* 16-bit version of the PCI domain for backward compatibility */ + /* 0xffff if the real domain doesn't fit in 16 bits */ + u8 bus, dev, func; /* Bus inside domain, device and function */ + + /* These fields are set by pci_fill_info() */ + unsigned int known_fields; /* Set of info fields already known (see pci_fill_info()) */ + u16 vendor_id, device_id; /* Identity of the device */ + u16 device_class; /* PCI device class */ + int irq; /* IRQ number */ + pciaddr_t base_addr[6]; /* Base addresses including flags in lower bits */ + pciaddr_t size[6]; /* Region sizes */ + pciaddr_t rom_base_addr; /* Expansion ROM base address */ + pciaddr_t rom_size; /* Expansion ROM size */ + struct pci_cap *first_cap; /* List of capabilities */ + char *phy_slot; /* Physical slot */ + char *module_alias; /* Linux kernel module alias */ + char *label; /* Device name as exported by BIOS */ + int numa_node; /* NUMA node */ + pciaddr_t flags[6]; /* PCI_IORESOURCE_* flags for regions */ + pciaddr_t rom_flags; /* PCI_IORESOURCE_* flags for expansion ROM */ + int domain; /* PCI domain (host bridge) */ + pciaddr_t bridge_base_addr[4]; /* Bridge base addresses (without flags) */ + pciaddr_t bridge_size[4]; /* Bridge sizes */ + pciaddr_t bridge_flags[4]; /* PCI_IORESOURCE_* flags for bridge addresses */ + u8 prog_if, rev_id; /* Programming interface for device_class and revision id */ + u16 subsys_vendor_id, subsys_id; /* Subsystem vendor id and subsystem id */ + struct pci_dev *parent; /* Parent device, does not have to be always accessible */ + int no_config_access; /* No access to config space for this device */ + + /* Fields used internally */ + struct pci_access *access; + struct pci_methods *methods; + u8 *cache; /* Cached config registers */ + int cache_len; + int hdrtype; /* Cached low 7 bits of header type, -1 if unknown */ + void *backend_data; /* Private data for of the back end */ + struct pci_property *properties; /* A linked list of extra properties */ + struct pci_cap *last_cap; /* Last capability in the list */ +}; + +#define PCI_ADDR_IO_MASK (~(pciaddr_t) 0x3) +#define PCI_ADDR_MEM_MASK (~(pciaddr_t) 0xf) +#define PCI_ADDR_FLAG_MASK 0xf + +/* Access to configuration space */ +u8 pci_read_byte(struct pci_dev *, int pos) PCI_ABI; +u16 pci_read_word(struct pci_dev *, int pos) PCI_ABI; +u32 pci_read_long(struct pci_dev *, int pos) PCI_ABI; +int pci_read_vpd(struct pci_dev *d, int pos, u8 *buf, int len) PCI_ABI; +int pci_write_byte(struct pci_dev *, int pos, u8 data) PCI_ABI; +int pci_write_word(struct pci_dev *, int pos, u16 data) PCI_ABI; +int pci_write_long(struct pci_dev *, int pos, u32 data) PCI_ABI; + +/* Configuration space as a sequence of bytes (little-endian) */ +int pci_read_block(struct pci_dev *, int pos, u8 *buf, int len) PCI_ABI; +int pci_write_block(struct pci_dev *, int pos, u8 *buf, int len) PCI_ABI; + +/* + * Most device properties take some effort to obtain, so libpci does not + * initialize them during default bus scan. Instead, you have to call + * pci_fill_info() with the proper PCI_FILL_xxx constants OR'ed together. + * + * Some properties are stored directly in the pci_dev structure. + * The remaining ones can be accessed through pci_get_string_property(). + * + * pci_fill_info() returns the current value of pci_dev->known_fields. + * This is a bit mask of all fields, which were already obtained during + * the lifetime of the device. This includes fields which are not supported + * by the particular device -- in that case, the field is left at its default + * value, which is 0 for integer fields and NULL for pointers. On the other + * hand, we never consider known fields unsupported by the current back-end; + * such fields always contain the default value. + * + * XXX: flags and the result should be unsigned, but we do not want to break the ABI. + */ + +int pci_fill_info(struct pci_dev *, int flags) PCI_ABI; +char *pci_get_string_property(struct pci_dev *d, u32 prop) PCI_ABI; + +#define PCI_FILL_IDENT 0x0001 +#define PCI_FILL_IRQ 0x0002 +#define PCI_FILL_BASES 0x0004 +#define PCI_FILL_ROM_BASE 0x0008 +#define PCI_FILL_SIZES 0x0010 +#define PCI_FILL_CLASS 0x0020 +#define PCI_FILL_CAPS 0x0040 +#define PCI_FILL_EXT_CAPS 0x0080 +#define PCI_FILL_PHYS_SLOT 0x0100 +#define PCI_FILL_MODULE_ALIAS 0x0200 +#define PCI_FILL_LABEL 0x0400 +#define PCI_FILL_NUMA_NODE 0x0800 +#define PCI_FILL_IO_FLAGS 0x1000 +#define PCI_FILL_DT_NODE 0x2000 /* Device tree node */ +#define PCI_FILL_IOMMU_GROUP 0x4000 +#define PCI_FILL_BRIDGE_BASES 0x8000 +#define PCI_FILL_RESCAN 0x00010000 +#define PCI_FILL_CLASS_EXT 0x00020000 /* prog_if and rev_id */ +#define PCI_FILL_SUBSYS 0x00040000 /* subsys_vendor_id and subsys_id */ +#define PCI_FILL_PARENT 0x00080000 +#define PCI_FILL_DRIVER 0x00100000 /* OS driver currently in use (string property) */ + +void pci_setup_cache(struct pci_dev *, u8 *cache, int len) PCI_ABI; + +/* + * Capabilities + */ + +struct pci_cap { + struct pci_cap *next; + u16 id; /* PCI_CAP_ID_xxx */ + u16 type; /* PCI_CAP_xxx */ + unsigned int addr; /* Position in the config space */ +}; + +#define PCI_CAP_NORMAL 1 /* Traditional PCI capabilities */ +#define PCI_CAP_EXTENDED 2 /* PCIe extended capabilities */ + +struct pci_cap *pci_find_cap(struct pci_dev *, unsigned int id, unsigned int type) PCI_ABI; +struct pci_cap *pci_find_cap_nr(struct pci_dev *, unsigned int id, unsigned int type, + unsigned int *cap_number) PCI_ABI; + +/* + * Filters + */ + +struct pci_filter { + int domain, bus, slot, func; /* -1 = ANY */ + int vendor, device; + int device_class; + unsigned int device_class_mask; /* Which bits of the device_class are compared, default=all */ + int prog_if; + int rfu[1]; +}; + +void pci_filter_init(struct pci_access *, struct pci_filter *) PCI_ABI; +char *pci_filter_parse_slot(struct pci_filter *, char *) PCI_ABI; +char *pci_filter_parse_id(struct pci_filter *, char *) PCI_ABI; +int pci_filter_match(struct pci_filter *, struct pci_dev *) PCI_ABI; + +/* + * Conversion of PCI ID's to names (according to the pci.ids file) + * + * Call pci_lookup_name() to identify different types of ID's: + * + * VENDOR (vendorID) -> vendor + * DEVICE (vendorID, deviceID) -> device + * VENDOR | DEVICE (vendorID, deviceID) -> combined vendor and device + * SUBSYSTEM | VENDOR (subvendorID) -> subsystem vendor + * SUBSYSTEM | DEVICE (vendorID, deviceID, subvendorID, subdevID) -> subsystem device + * SUBSYSTEM | VENDOR | DEVICE (vendorID, deviceID, subvendorID, subdevID) -> combined subsystem v+d + * SUBSYSTEM | ... (-1, -1, subvendorID, subdevID) -> generic subsystem + * CLASS (classID) -> class + * PROGIF (classID, progif) -> programming interface + */ + +char *pci_lookup_name(struct pci_access *a, char *buf, int size, int flags, ...) PCI_ABI; + +int pci_load_name_list(struct pci_access *a) PCI_ABI; /* Called automatically by pci_lookup_*() when needed; returns success */ +void pci_free_name_list(struct pci_access *a) PCI_ABI; /* Called automatically by pci_cleanup() */ +void pci_set_name_list_path(struct pci_access *a, char *name, int to_be_freed) PCI_ABI; +void pci_id_cache_flush(struct pci_access *a) PCI_ABI; + +enum pci_lookup_mode { + PCI_LOOKUP_VENDOR = 1, /* Vendor name (args: vendorID) */ + PCI_LOOKUP_DEVICE = 2, /* Device name (args: vendorID, deviceID) */ + PCI_LOOKUP_CLASS = 4, /* Device class (args: classID) */ + PCI_LOOKUP_SUBSYSTEM = 8, + PCI_LOOKUP_PROGIF = 16, /* Programming interface (args: classID, prog_if) */ + PCI_LOOKUP_NUMERIC = 0x10000, /* Want only formatted numbers; default if access->numeric_ids is set */ + PCI_LOOKUP_NO_NUMBERS = 0x20000, /* Return NULL if not found in the database; default is to print numerically */ + PCI_LOOKUP_MIXED = 0x40000, /* Include both numbers and names */ + PCI_LOOKUP_NETWORK = 0x80000, /* Try to resolve unknown ID's by DNS */ + PCI_LOOKUP_SKIP_LOCAL = 0x100000, /* Do not consult local database */ + PCI_LOOKUP_CACHE = 0x200000, /* Consult the local cache before using DNS */ + PCI_LOOKUP_REFRESH_CACHE = 0x400000, /* Forget all previously cached entries, but still allow updating the cache */ + PCI_LOOKUP_NO_HWDB = 0x800000, /* Do not ask udev's hwdb */ +}; + +#endif diff --git a/lib/physmem-access.h b/lib/physmem-access.h new file mode 100644 index 0000000..a4e9744 --- /dev/null +++ b/lib/physmem-access.h @@ -0,0 +1,52 @@ +/* + * The PCI Library -- Compiler-specific wrappers for memory mapped I/O + * + * Copyright (c) 2023 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * FIXME + * Unfortunately gcc does not provide architecture independent way to read from + * or write to memory mapped I/O. The best approximation is to use volatile and + * for the write operation follow it by the read operation from the same address. + */ + +static inline void +physmem_writeb(unsigned char value, volatile void *ptr) +{ + *(volatile unsigned char *)ptr = value; +} + +static inline void +physmem_writew(unsigned short value, volatile void *ptr) +{ + *(volatile unsigned short *)ptr = value; +} + +static inline void +physmem_writel(u32 value, volatile void *ptr) +{ + *(volatile u32 *)ptr = value; +} + +static inline unsigned char +physmem_readb(volatile void *ptr) +{ + return *(volatile unsigned char *)ptr; +} + +static inline unsigned short +physmem_readw(volatile void *ptr) +{ + return *(volatile unsigned short *)ptr; +} + +static inline u32 +physmem_readl(volatile void *ptr) +{ + return *(volatile u32 *)ptr; +} diff --git a/lib/physmem-posix.c b/lib/physmem-posix.c new file mode 100644 index 0000000..7cd7e99 --- /dev/null +++ b/lib/physmem-posix.c @@ -0,0 +1,95 @@ +/* + * The PCI Library -- Physical memory mapping for POSIX systems + * + * Copyright (c) 2023 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Tell 32-bit platforms that we are interested in 64-bit variant of off_t type + * as 32-bit variant of off_t type is signed and so it cannot represent all + * possible 32-bit offsets. It is required because off_t type is used by mmap(). + */ +#define _FILE_OFFSET_BITS 64 + +#include "internal.h" +#include "physmem.h" + +#include <limits.h> +#include <errno.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> + +#ifndef OFF_MAX +#define OFF_MAX ((((off_t)1 << (sizeof(off_t) * CHAR_BIT - 2)) - 1) * 2 + 1) +#endif + +struct physmem { + int fd; +}; + +void +physmem_init_config(struct pci_access *a) +{ + pci_define_param(a, "devmem.path", PCI_PATH_DEVMEM_DEVICE, "Path to the /dev/mem device"); +} + +int +physmem_access(struct pci_access *a, int w) +{ + const char *devmem = pci_get_param(a, "devmem.path"); + a->debug("checking access permission of physical memory device %s for %s mode...", devmem, w ? "read/write" : "read-only"); + return access(devmem, R_OK | (w ? W_OK : 0)); +} + +struct physmem * +physmem_open(struct pci_access *a, int w) +{ + const char *devmem = pci_get_param(a, "devmem.path"); + struct physmem *physmem = pci_malloc(a, sizeof(struct physmem)); + + a->debug("trying to open physical memory device %s in %s mode...", devmem, w ? "read/write" : "read-only"); + physmem->fd = open(devmem, (w ? O_RDWR : O_RDONLY) | O_DSYNC); /* O_DSYNC bypass CPU cache for mmap() on Linux */ + if (physmem->fd < 0) + { + pci_mfree(physmem); + return NULL; + } + + return physmem; +} + +void +physmem_close(struct physmem *physmem) +{ + close(physmem->fd); + pci_mfree(physmem); +} + +long +physmem_get_pagesize(struct physmem *physmem UNUSED) +{ + return sysconf(_SC_PAGESIZE); +} + +void * +physmem_map(struct physmem *physmem, u64 addr, size_t length, int w) +{ + if (addr > OFF_MAX) + { + errno = EOVERFLOW; + return (void *)-1; + } + return mmap(NULL, length, PROT_READ | (w ? PROT_WRITE : 0), MAP_SHARED, physmem->fd, addr); +} + +int +physmem_unmap(struct physmem *physmem UNUSED, void *ptr, size_t length) +{ + return munmap(ptr, length); +} diff --git a/lib/physmem.h b/lib/physmem.h new file mode 100644 index 0000000..46ee021 --- /dev/null +++ b/lib/physmem.h @@ -0,0 +1,19 @@ +/* + * The PCI Library -- Physical memory mapping API + * + * Copyright (c) 2023 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +struct physmem; + +void physmem_init_config(struct pci_access *a); +int physmem_access(struct pci_access *a, int w); +struct physmem *physmem_open(struct pci_access *a, int w); +void physmem_close(struct physmem *physmem); +long physmem_get_pagesize(struct physmem *physmem); +void *physmem_map(struct physmem *physmem, u64 addr, size_t length, int w); +int physmem_unmap(struct physmem *physmem, void *ptr, size_t length); diff --git a/lib/proc.c b/lib/proc.c new file mode 100644 index 0000000..429cea6 --- /dev/null +++ b/lib/proc.c @@ -0,0 +1,230 @@ +/* + * The PCI Library -- Configuration Access via /proc/bus/pci + * + * Copyright (c) 1997--2023 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> + +#include "internal.h" + +static void +proc_config(struct pci_access *a) +{ + pci_define_param(a, "proc.path", PCI_PATH_PROC_BUS_PCI, "Path to the procfs bus tree"); +} + +static int +proc_detect(struct pci_access *a) +{ + char *name = pci_get_param(a, "proc.path"); + + if (access(name, R_OK)) + { + a->warning("Cannot open %s", name); + return 0; + } + a->debug("...using %s", name); + return 1; +} + +static void +proc_init(struct pci_access *a) +{ + a->fd = -1; +} + +static void +proc_cleanup(struct pci_access *a) +{ + if (a->fd >= 0) + { + close(a->fd); + a->fd = -1; + } +} + +static void +proc_scan(struct pci_access *a) +{ + FILE *f; + char buf[512]; + + if (snprintf(buf, sizeof(buf), "%s/devices", pci_get_param(a, "proc.path")) == sizeof(buf)) + a->error("File name too long"); + f = fopen(buf, "r"); + if (!f) + a->error("Cannot open %s", buf); + while (fgets(buf, sizeof(buf)-1, f)) + { + struct pci_dev *d = pci_alloc_dev(a); + unsigned int dfn, vend, cnt, known; + char *driver; + int offset; + +#define F " " PCIADDR_T_FMT + cnt = sscanf(buf, "%x %x %x" F F F F F F F F F F F F F F "%n", + &dfn, + &vend, + &d->irq, + &d->base_addr[0], + &d->base_addr[1], + &d->base_addr[2], + &d->base_addr[3], + &d->base_addr[4], + &d->base_addr[5], + &d->rom_base_addr, + &d->size[0], + &d->size[1], + &d->size[2], + &d->size[3], + &d->size[4], + &d->size[5], + &d->rom_size, + &offset); +#undef F + if (cnt != 9 && cnt != 10 && cnt != 17) + a->error("proc: parse error (read only %d items)", cnt); + d->bus = dfn >> 8U; + d->dev = PCI_SLOT(dfn & 0xff); + d->func = PCI_FUNC(dfn & 0xff); + d->vendor_id = vend >> 16U; + d->device_id = vend & 0xffff; + known = 0; + if (!a->buscentric) + { + known |= PCI_FILL_IDENT | PCI_FILL_IRQ | PCI_FILL_BASES; + if (cnt >= 10) + known |= PCI_FILL_ROM_BASE; + if (cnt >= 17) + known |= PCI_FILL_SIZES; + } + if (cnt >= 17) + { + while (buf[offset] && isspace(buf[offset])) + ++offset; + driver = &buf[offset]; + while (buf[offset] && !isspace(buf[offset])) + ++offset; + buf[offset] = '\0'; + if (driver[0]) + { + pci_set_property(d, PCI_FILL_DRIVER, driver); + known |= PCI_FILL_DRIVER; + } + } + d->known_fields = known; + pci_link_dev(a, d); + } + fclose(f); +} + +static int +proc_setup(struct pci_dev *d, int rw) +{ + struct pci_access *a = d->access; + + if (a->cached_dev != d || a->fd_rw < rw) + { + char buf[1024]; + int e; + if (a->fd >= 0) + close(a->fd); + e = snprintf(buf, sizeof(buf), "%s/%02x/%02x.%d", + pci_get_param(a, "proc.path"), + 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); + if (a->fd < 0) + { + e = snprintf(buf, sizeof(buf), "%s/%04x:%02x/%02x.%d", + pci_get_param(a, "proc.path"), + d->domain, d->bus, d->dev, d->func); + if (e < 0 || e >= (int) sizeof(buf)) + a->error("File name too long"); + a->fd = open(buf, a->fd_rw ? O_RDWR : O_RDONLY); + } + if (a->fd < 0) + a->warning("Cannot open %s", buf); + a->cached_dev = d; + } + return a->fd; +} + +static int +proc_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + int fd = proc_setup(d, 0); + int res; + + if (fd < 0) + return 0; + res = pread(fd, buf, len, pos); + if (res < 0) + { + d->access->warning("proc_read: read failed: %s", strerror(errno)); + return 0; + } + else if (res != len) + return 0; + return 1; +} + +static int +proc_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + int fd = proc_setup(d, 1); + int res; + + if (fd < 0) + return 0; + res = pwrite(fd, buf, len, pos); + if (res < 0) + { + d->access->warning("proc_write: write failed: %s", strerror(errno)); + return 0; + } + else if (res != len) + { + d->access->warning("proc_write: tried to write %d bytes at %d, but only %d succeeded", len, pos, res); + return 0; + } + return 1; +} + +static void +proc_cleanup_dev(struct pci_dev *d) +{ + if (d->access->cached_dev == d) + d->access->cached_dev = NULL; +} + +struct pci_methods pm_linux_proc = { + "linux-proc", + "The proc file system on Linux", + proc_config, + proc_detect, + proc_init, + proc_cleanup, + proc_scan, + pci_generic_fill_info, + proc_read, + proc_write, + NULL, /* read_vpd */ + NULL, /* init_dev */ + proc_cleanup_dev +}; diff --git a/lib/sylixos-device.c b/lib/sylixos-device.c new file mode 100644 index 0000000..170ae02 --- /dev/null +++ b/lib/sylixos-device.c @@ -0,0 +1,160 @@ +/* + * The PCI Library -- Direct Configuration access via SylixOS Ports + * + * Copyright (c) 2018 YuJian.Gong <gongyujian@acoinfo.com> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define _GNU_SOURCE +#define __SYLIXOS_KERNEL +#define __SYLIXOS_PCI_DRV +#include <SylixOS.h> +#include <stdlib.h> +#include <string.h> + +#include "internal.h" + +static void +sylixos_scan(struct pci_access *a) +{ + u8 busmap[256]; + int bus; + + memset(busmap, 0, sizeof(busmap)); + + for (bus = 0; bus < PCI_MAX_BUS; bus++) + if (!busmap[bus]) + pci_generic_scan_bus(a, busmap, 0, bus); +} + +static void +sylixos_config(struct pci_access *a) +{ + pci_define_param(a, "sylixos.path", PCI_PATH_SYLIXOS_DEVICE, "Path to the SylixOS PCI device"); +} + +static int +sylixos_detect(struct pci_access *a) +{ + char *name = pci_get_param(a, "sylixos.path"); + + if (access(name, R_OK)) + { + a->warning("Cannot open %s", name); + return 0; + } + + a->debug("...using %s", name); + return 1; +} + +static void +sylixos_init(struct pci_access *a UNUSED) +{ +} + +static void +sylixos_cleanup(struct pci_access *a UNUSED) +{ +} + +static int +sylixos_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + int ret = -1; + u8 data_byte = -1; + u16 data_word = -1; + u32 data_dword = -1; + + if (!(len == 1 || len == 2 || len == 4)) + return pci_generic_block_read(d, pos, buf, len); + + if (pos >= 256) + return 0; + + switch (len) + { + case 1: + ret = pciConfigInByte(d->bus, d->dev, d->func, pos, &data_byte); + if (ret != ERROR_NONE) + return 0; + buf[0] = (u8)data_byte; + break; + + case 2: + ret = pciConfigInWord(d->bus, d->dev, d->func, pos, &data_word); + if (ret != ERROR_NONE) + return 0; + ((u16 *) buf)[0] = cpu_to_le16(data_word); + break; + + case 4: + ret = pciConfigInDword(d->bus, d->dev, d->func, pos, &data_dword); + if (ret != ERROR_NONE) + return 0; + ((u32 *) buf)[0] = cpu_to_le32(data_dword); + break; + } + + return 1; +} + +static int +sylixos_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + int ret = PX_ERROR; + u8 data_byte; + u16 data_word; + u32 data_dword; + + if (!(len == 1 || len == 2 || len == 4)) + return pci_generic_block_write(d, pos, buf, len); + + if (pos >= 256) + return 0; + + switch (len) + { + case 1: + data_byte = buf[0]; + ret = pciConfigOutByte(d->bus, d->dev, d->func, pos, data_byte); + if (ret != ERROR_NONE) + return 0; + break; + + case 2: + data_word = le16_to_cpu(((u16 *) buf)[0]); + ret = pciConfigOutWord(d->bus, d->dev, d->func, pos, data_word); + if (ret != ERROR_NONE) + return 0; + break; + + case 4: + data_dword = le32_to_cpu(((u32 *) buf)[0]); + ret = pciConfigOutDword(d->bus, d->dev, d->func, pos, data_dword); + if (ret != ERROR_NONE) + return 0; + break; + } + + return 1; +} + +struct pci_methods pm_sylixos_device = { + "sylixos-device", + "SylixOS /proc/pci device", + sylixos_config, + sylixos_detect, + sylixos_init, + sylixos_cleanup, + sylixos_scan, + pci_generic_fill_info, + sylixos_read, + sylixos_write, + NULL, // no read_vpd + NULL, // no init_dev + NULL, // no cleanup_dev +}; diff --git a/lib/sysdep.h b/lib/sysdep.h new file mode 100644 index 0000000..40e1407 --- /dev/null +++ b/lib/sysdep.h @@ -0,0 +1,129 @@ +/* + * The PCI Library -- System-Dependent Stuff + * + * Copyright (c) 1997--2020 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifdef __GNUC__ +#define UNUSED __attribute__((unused)) +#define NONRET __attribute__((noreturn)) +#define FORMAT_CHECK(x,y,z) __attribute__((format(x,y,z))) +#else +#define UNUSED +#define NONRET +#define FORMAT_CHECK(x,y,z) +#define inline +#endif + +typedef u8 byte; +typedef u16 word; + +#ifdef PCI_OS_WINDOWS +#define strcasecmp _strcmpi +#define strncasecmp _strnicmp +#if defined(_MSC_VER) && _MSC_VER < 1800 +#if _MSC_VER < 1300 +#define strtoull strtoul +#else +#define strtoull _strtoui64 +#endif +#endif +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#endif +#endif + +#ifdef PCI_HAVE_LINUX_BYTEORDER_H + +#include <asm/byteorder.h> +#define cpu_to_le16 __cpu_to_le16 +#define cpu_to_le32 __cpu_to_le32 +#define le16_to_cpu __le16_to_cpu +#define le32_to_cpu __le32_to_cpu + +#else + +#ifdef PCI_OS_LINUX +#include <endian.h> +#define BYTE_ORDER __BYTE_ORDER +#define BIG_ENDIAN __BIG_ENDIAN +#endif + +#ifdef PCI_OS_SUNOS +#include <sys/byteorder.h> +#if defined(__i386) && defined(LITTLE_ENDIAN) +# define BYTE_ORDER LITTLE_ENDIAN +#elif defined(__sparc) && defined(BIG_ENDIAN) +# define BYTE_ORDER BIG_ENDIAN +#else +#define BIG_ENDIAN 4321 +#endif +#ifndef BYTE_ORDER +#ifdef _LITTLE_ENDIAN +#define BYTE_ORDER 1234 +#else +#define BYTE_ORDER 4321 +#endif +#endif /* BYTE_ORDER */ +#endif /* PCI_OS_SUNOS */ + +#ifdef PCI_OS_WINDOWS +#ifdef __MINGW32__ + #include <sys/param.h> +#else + #include <io.h> + #define BIG_ENDIAN 4321 + #define LITTLE_ENDIAN 1234 + #define BYTE_ORDER LITTLE_ENDIAN +#endif +#endif + +#ifdef PCI_OS_SYLIXOS +#include <endian.h> +#endif + +#ifdef PCI_OS_DJGPP + #define BIG_ENDIAN 4321 + #define LITTLE_ENDIAN 1234 + #define BYTE_ORDER LITTLE_ENDIAN +#endif + +#ifdef PCI_OS_AMIGAOS + #include <machine/endian.h> +#endif + +#if !defined(BYTE_ORDER) +#error "BYTE_ORDER not defined for your platform" +#endif + +#if BYTE_ORDER == BIG_ENDIAN +#define cpu_to_le16 swab16 +#define cpu_to_le32 swab32 +#define le16_to_cpu swab16 +#define le32_to_cpu swab32 + +static inline word swab16(word w) +{ + return (w << 8) | ((w >> 8) & 0xff); +} + +static inline u32 swab32(u32 w) +{ + return ((w & 0xff000000) >> 24) | + ((w & 0x00ff0000) >> 8) | + ((w & 0x0000ff00) << 8) | + ((w & 0x000000ff) << 24); +} +#else +#define cpu_to_le16(x) (x) +#define cpu_to_le32(x) (x) +#define le16_to_cpu(x) (x) +#define le32_to_cpu(x) (x) +#endif + +#endif diff --git a/lib/sysfs.c b/lib/sysfs.c new file mode 100644 index 0000000..cd2379e --- /dev/null +++ b/lib/sysfs.c @@ -0,0 +1,609 @@ +/* + * The PCI Library -- Configuration Access via /sys/bus/pci + * + * Copyright (c) 2003 Matthew Wilcox <matthew@wil.cx> + * Copyright (c) 1997--2023 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <unistd.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <sys/types.h> + +#include "internal.h" + +static void +sysfs_config(struct pci_access *a) +{ + pci_define_param(a, "sysfs.path", PCI_PATH_SYS_BUS_PCI, "Path to the sysfs device tree"); +} + +static inline char * +sysfs_name(struct pci_access *a) +{ + return pci_get_param(a, "sysfs.path"); +} + +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; + a->fd_vpd = -1; +} + +static void +sysfs_flush_cache(struct pci_access *a) +{ + if (a->fd >= 0) + { + close(a->fd); + a->fd = -1; + } + if (a->fd_vpd >= 0) + { + close(a->fd_vpd); + a->fd_vpd = -1; + } + a->cached_dev = NULL; +} + +static void +sysfs_cleanup(struct pci_access *a) +{ + sysfs_flush_cache(a); +} + +#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"); +} + +#define OBJBUFSIZE 1024 + +static int +sysfs_get_string(struct pci_dev *d, char *object, char *buf, int mandatory) +{ + struct pci_access *a = d->access; + int fd, n; + char namebuf[OBJNAMELEN]; + void (*warn)(char *msg, ...) = (mandatory ? a->error : a->warning); + + sysfs_obj_name(d, object, namebuf); + fd = open(namebuf, O_RDONLY); + if (fd < 0) + { + if (mandatory || errno != ENOENT) + warn("Cannot open %s: %s", namebuf, strerror(errno)); + return 0; + } + n = read(fd, buf, OBJBUFSIZE); + close(fd); + if (n < 0) + { + warn("Error reading %s: %s", namebuf, strerror(errno)); + return 0; + } + if (n >= OBJBUFSIZE) + { + warn("Value in %s too long", namebuf); + return 0; + } + buf[n] = 0; + return 1; +} + +static char * +sysfs_deref_link(struct pci_dev *d, char *link_name) +{ + char path[2*OBJNAMELEN], rel_path[OBJNAMELEN]; + + sysfs_obj_name(d, link_name, path); + memset(rel_path, 0, sizeof(rel_path)); + + if (readlink(path, rel_path, sizeof(rel_path)) < 0) + return NULL; + + sysfs_obj_name(d, "", path); + strcat(path, rel_path); + + // Returns a pointer to malloc'ed memory + return realpath(path, NULL); +} + +static int +sysfs_get_value(struct pci_dev *d, char *object, int mandatory) +{ + char buf[OBJBUFSIZE]; + + if (sysfs_get_string(d, object, buf, mandatory)) + return strtol(buf, NULL, 0); + else + return -1; +} + +static void +sysfs_get_resources(struct pci_dev *d) +{ + struct pci_access *a = d->access; + char namebuf[OBJNAMELEN], buf[256]; + struct { pciaddr_t flags, base_addr, size; } lines[10]; + int have_bar_bases, have_rom_base, have_bridge_bases; + FILE *file; + int i; + + have_bar_bases = have_rom_base = have_bridge_bases = 0; + 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 < 7+6+4+1; i++) + { + unsigned long long start, end, size, flags; + if (!fgets(buf, sizeof(buf), file)) + break; + if (sscanf(buf, "%llx %llx %llx", &start, &end, &flags) != 3) + a->error("Syntax error in %s", namebuf); + if (end > start) + size = end - start + 1; + else + size = 0; + if (i < 6) + { + d->flags[i] = flags; + flags &= PCI_ADDR_FLAG_MASK; + d->base_addr[i] = start | flags; + d->size[i] = size; + have_bar_bases = 1; + } + else if (i == 6) + { + d->rom_flags = flags; + flags &= PCI_ADDR_FLAG_MASK; + d->rom_base_addr = start | flags; + d->rom_size = size; + have_rom_base = 1; + } + else if (i < 7+6+4) + { + /* + * If kernel was compiled without CONFIG_PCI_IOV option then after + * the ROM line for configured bridge device (that which had set + * subordinary bus number to non-zero value) are four additional lines + * which describe resources behind bridge. For PCI-to-PCI bridges they + * are: IO, MEM, PREFMEM and empty. For CardBus bridges they are: IO0, + * IO1, MEM0 and MEM1. For unconfigured bridges and other devices + * there is no additional line after the ROM line. If kernel was + * compiled with CONFIG_PCI_IOV option then after the ROM line and + * before the first bridge resource line are six additional lines + * which describe IOV resources. Read all remaining lines in resource + * file and based on the number of remaining lines (0, 4, 6, 10) parse + * resources behind bridge. + */ + lines[i-7].flags = flags; + lines[i-7].base_addr = start; + lines[i-7].size = size; + } + } + if (i == 7+4 || i == 7+6+4) + { + int offset = (i == 7+6+4) ? 6 : 0; + for (i = 0; i < 4; i++) + { + d->bridge_flags[i] = lines[offset+i].flags; + d->bridge_base_addr[i] = lines[offset+i].base_addr; + d->bridge_size[i] = lines[offset+i].size; + } + have_bridge_bases = 1; + } + fclose(file); + if (!have_bar_bases) + clear_fill(d, PCI_FILL_BASES | PCI_FILL_SIZES | PCI_FILL_IO_FLAGS); + if (!have_rom_base) + clear_fill(d, PCI_FILL_ROM_BASE); + if (!have_bridge_bases) + clear_fill(d, PCI_FILL_BRIDGE_BASES); +} + +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); + + /* Ensure kernel provided domain that fits in a signed integer */ + if (dom > 0x7fffffff) + a->error("sysfs_scan: Invalid domain %x", dom); + + d->domain = dom; + d->bus = bus; + d->dev = dev; + d->func = func; + pci_link_dev(a, d); + } + closedir(dir); +} + +static void +sysfs_fill_slots(struct pci_access *a) +{ + char dirname[1024]; + DIR *dir; + struct dirent *entry; + int n; + + n = snprintf(dirname, sizeof(dirname), "%s/slots", sysfs_name(a)); + if (n < 0 || n >= (int) sizeof(dirname)) + a->error("Directory name too long"); + dir = opendir(dirname); + if (!dir) + return; + + while (entry = readdir(dir)) + { + char namebuf[OBJNAMELEN], buf[16]; + FILE *file; + unsigned int dom, bus, dev; + int res = 0; + struct pci_dev *d; + + /* ".", ".." or a special non-device perhaps */ + if (entry->d_name[0] == '.') + continue; + + n = snprintf(namebuf, OBJNAMELEN, "%s/%s/%s", dirname, entry->d_name, "address"); + if (n < 0 || n >= OBJNAMELEN) + a->error("File name too long"); + file = fopen(namebuf, "r"); + /* + * Old versions of Linux had a fakephp which didn't have an 'address' + * file. There's no useful information to be gleaned from these + * devices, pretend they're not there. + */ + if (!file) + continue; + + if (!fgets(buf, sizeof(buf), file) || (res = sscanf(buf, "%x:%x:%x", &dom, &bus, &dev)) < 3) + { + /* + * In some cases, the slot is not tied to a specific device before + * a card gets inserted. This happens for example on IBM pSeries + * and we need not warn about it. + */ + if (res != 2) + a->warning("sysfs_fill_slots: Couldn't parse entry address %s", buf); + } + else + { + for (d = a->devices; d; d = d->next) + if (dom == (unsigned)d->domain && bus == d->bus && dev == d->dev && !d->phy_slot) + d->phy_slot = pci_set_property(d, PCI_FILL_PHYS_SLOT, entry->d_name); + } + fclose(file); + } + closedir(dir); +} + +static void +sysfs_fill_info(struct pci_dev *d, unsigned int flags) +{ + int value, want_class, want_class_ext; + + if (!d->access->buscentric) + { + /* + * These fields can be read from the config registers, but we want to show + * the kernel's view, which has regions and IRQs remapped and other fields + * (most importantly classes) possibly fixed if the device is known broken. + */ + if (want_fill(d, flags, PCI_FILL_IDENT)) + { + d->vendor_id = sysfs_get_value(d, "vendor", 1); + d->device_id = sysfs_get_value(d, "device", 1); + } + want_class = want_fill(d, flags, PCI_FILL_CLASS); + want_class_ext = want_fill(d, flags, PCI_FILL_CLASS_EXT); + if (want_class || want_class_ext) + { + value = sysfs_get_value(d, "class", 1); + if (want_class) + d->device_class = value >> 8; + if (want_class_ext) + { + d->prog_if = value & 0xff; + value = sysfs_get_value(d, "revision", 0); + if (value < 0) + value = pci_read_byte(d, PCI_REVISION_ID); + if (value >= 0) + d->rev_id = value; + } + } + if (want_fill(d, flags, PCI_FILL_SUBSYS)) + { + value = sysfs_get_value(d, "subsystem_vendor", 0); + if (value >= 0) + { + d->subsys_vendor_id = value; + value = sysfs_get_value(d, "subsystem_device", 0); + if (value >= 0) + d->subsys_id = value; + } + else + clear_fill(d, PCI_FILL_SUBSYS); + } + if (want_fill(d, flags, PCI_FILL_IRQ)) + d->irq = sysfs_get_value(d, "irq", 1); + if (want_fill(d, flags, PCI_FILL_BASES | PCI_FILL_ROM_BASE | PCI_FILL_SIZES | PCI_FILL_IO_FLAGS | PCI_FILL_BRIDGE_BASES)) + sysfs_get_resources(d); + if (want_fill(d, flags, PCI_FILL_PARENT)) + { + unsigned int domain, bus, dev, func; + char *path_abs, *path_canon, *name; + char path_rel[OBJNAMELEN]; + struct pci_dev *parent; + + /* Construct sysfs path for parent device */ + sysfs_obj_name(d, "..", path_rel); + path_abs = realpath(path_rel, NULL); + name = path_abs ? strrchr(path_abs, '/') : NULL; + name = name ? name+1 : name; + parent = NULL; + + if (name && sscanf(name, "%x:%x:%x.%d", &domain, &bus, &dev, &func) == 4 && domain <= 0x7fffffff) + for (parent = d->access->devices; parent; parent = parent->next) + if (parent->domain == (int)domain && parent->bus == bus && parent->dev == dev && parent->func == func) + break; + + if (parent) + { + /* Check if parsed BDF address from parent sysfs device is really expected PCI device */ + sysfs_obj_name(parent, ".", path_rel); + path_canon = realpath(path_rel, NULL); + if (!path_canon || strcmp(path_canon, path_abs) != 0) + parent = NULL; + + if (path_canon) + free(path_canon); + } + + if (parent) + d->parent = parent; + else + clear_fill(d, PCI_FILL_PARENT); + + if (path_abs) + free(path_abs); + } + } + + if (want_fill(d, flags, PCI_FILL_PHYS_SLOT)) + { + struct pci_dev *pd; + sysfs_fill_slots(d->access); + for (pd = d->access->devices; pd; pd = pd->next) + pd->known_fields |= PCI_FILL_PHYS_SLOT; + } + + if (want_fill(d, flags, PCI_FILL_MODULE_ALIAS)) + { + char buf[OBJBUFSIZE]; + if (sysfs_get_string(d, "modalias", buf, 0)) + d->module_alias = pci_set_property(d, PCI_FILL_MODULE_ALIAS, buf); + } + + if (want_fill(d, flags, PCI_FILL_LABEL)) + { + char buf[OBJBUFSIZE]; + if (sysfs_get_string(d, "label", buf, 0)) + d->label = pci_set_property(d, PCI_FILL_LABEL, buf); + } + + if (want_fill(d, flags, PCI_FILL_NUMA_NODE)) + d->numa_node = sysfs_get_value(d, "numa_node", 0); + + if (want_fill(d, flags, PCI_FILL_IOMMU_GROUP)) + { + char *group_link = sysfs_deref_link(d, "iommu_group"); + if (group_link) + { + pci_set_property(d, PCI_FILL_IOMMU_GROUP, basename(group_link)); + free(group_link); + } + } + + if (want_fill(d, flags, PCI_FILL_DT_NODE)) + { + char *node = sysfs_deref_link(d, "of_node"); + if (node) + { + pci_set_property(d, PCI_FILL_DT_NODE, node); + free(node); + } + } + + if (want_fill(d, flags, PCI_FILL_DRIVER)) + { + char *driver_path = sysfs_deref_link(d, "driver"); + if (driver_path) + { + char *driver = strrchr(driver_path, '/'); + driver = driver ? driver+1 : driver_path; + pci_set_property(d, PCI_FILL_DRIVER, driver); + free(driver_path); + } + else + clear_fill(d, PCI_FILL_DRIVER); + } + + pci_generic_fill_info(d, flags); +} + +/* Intent of the sysfs_setup() caller */ +enum + { + SETUP_READ_CONFIG = 0, + SETUP_WRITE_CONFIG = 1, + SETUP_READ_VPD = 2 + }; + +static int +sysfs_setup(struct pci_dev *d, int intent) +{ + struct pci_access *a = d->access; + char namebuf[OBJNAMELEN]; + + if (a->cached_dev != d || (intent == SETUP_WRITE_CONFIG && !a->fd_rw)) + { + sysfs_flush_cache(a); + a->cached_dev = d; + } + + if (intent == SETUP_READ_VPD) + { + if (a->fd_vpd < 0) + { + sysfs_obj_name(d, "vpd", namebuf); + a->fd_vpd = open(namebuf, O_RDONLY); + /* No warning on error; vpd may be absent or accessible only to root */ + } + return a->fd_vpd; + } + + if (a->fd < 0) + { + sysfs_obj_name(d, "config", namebuf); + a->fd_rw = a->writeable || intent == SETUP_WRITE_CONFIG; + a->fd = open(namebuf, a->fd_rw ? O_RDWR : O_RDONLY); + if (a->fd < 0) + a->warning("Cannot open %s", namebuf); + } + return a->fd; +} + +static int sysfs_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + int fd = sysfs_setup(d, SETUP_READ_CONFIG); + int res; + + if (fd < 0) + return 0; + res = pread(fd, buf, len, pos); + if (res < 0) + { + d->access->warning("sysfs_read: read failed: %s", strerror(errno)); + return 0; + } + else if (res != len) + return 0; + return 1; +} + +static int sysfs_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + int fd = sysfs_setup(d, SETUP_WRITE_CONFIG); + int res; + + if (fd < 0) + return 0; + res = pwrite(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 only %d succeeded", len, pos, res); + return 0; + } + return 1; +} + +static int sysfs_read_vpd(struct pci_dev *d, int pos, byte *buf, int len) +{ + int fd = sysfs_setup(d, SETUP_READ_VPD); + int res; + + if (fd < 0) + return 0; + res = pread(fd, buf, len, pos); + if (res < 0) + { + d->access->warning("sysfs_read_vpd: read failed: %s", strerror(errno)); + return 0; + } + else if (res != len) + return 0; + return 1; +} + +static void sysfs_cleanup_dev(struct pci_dev *d) +{ + struct pci_access *a = d->access; + + if (a->cached_dev == d) + sysfs_flush_cache(a); +} + +struct pci_methods pm_linux_sysfs = { + "linux-sysfs", + "The sys filesystem on Linux", + sysfs_config, + sysfs_detect, + sysfs_init, + sysfs_cleanup, + sysfs_scan, + sysfs_fill_info, + sysfs_read, + sysfs_write, + sysfs_read_vpd, + NULL, /* init_dev */ + sysfs_cleanup_dev +}; diff --git a/lib/types.h b/lib/types.h new file mode 100644 index 0000000..260c981 --- /dev/null +++ b/lib/types.h @@ -0,0 +1,67 @@ +/* + * The PCI Library -- Types and Format Strings + * + * Copyright (c) 1997--2022 Martin Mares <mj@ucw.cz> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/types.h> +#include <stddef.h> + +#ifndef PCI_HAVE_Uxx_TYPES + +#ifdef PCI_OS_WINDOWS +/* On Windows compilers, use <windows.h> */ +#include <windows.h> +typedef BYTE u8; +typedef WORD u16; +typedef DWORD u32; +typedef unsigned __int64 u64; +#define PCI_U64_FMT_X "I64x" +#define PCI_U64_FMT_U "I64u" + +#else +/* Use standard types in C99 and newer */ +#include <stdint.h> +#include <inttypes.h> +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +#define PCI_U64_FMT_X PRIx64 +#define PCI_U64_FMT_U PRIu64 +#endif + +#endif /* PCI_HAVE_Uxx_TYPES */ + +#ifdef PCI_HAVE_64BIT_ADDRESS +typedef u64 pciaddr_t; +#define PCIADDR_T_FMT "%08" PCI_U64_FMT_X +#define PCIADDR_PORT_FMT "%04" PCI_U64_FMT_X +#else +typedef u32 pciaddr_t; +#define PCIADDR_T_FMT "%08x" +#define PCIADDR_PORT_FMT "%04x" +#endif + +#ifdef PCI_ARCH_SPARC64 +/* On sparc64 Linux the kernel reports remapped port addresses and IRQ numbers */ +#undef PCIADDR_PORT_FMT +#define PCIADDR_PORT_FMT PCIADDR_T_FMT +#define PCIIRQ_FMT "%08x" +#else +#define PCIIRQ_FMT "%d" +#endif + +#if defined(__GNUC__) && __GNUC__ > 2 +#define PCI_PRINTF(x,y) __attribute__((format(printf, x, y))) +#define PCI_NONRET __attribute((noreturn)) +#define PCI_PACKED __attribute((packed)) +#else +#define PCI_PRINTF(x,y) +#define PCI_NONRET +#define PCI_PACKED +#endif diff --git a/lib/ver2def.pl b/lib/ver2def.pl new file mode 100755 index 0000000..18231bc --- /dev/null +++ b/lib/ver2def.pl @@ -0,0 +1,47 @@ +#!/usr/bin/perl +use strict; +use warnings; +die "Usage: $0 script.ver dllname build.def import.def\n" if @ARGV != 4; +my ($verfile, $dllname, $builddef, $importdef) = @ARGV; +open my $verfh, '<', $verfile or die "Cannot open input file $verfile: $!\n"; +my $input = join '', <$verfh>; +close $verfh; +my @syms; +my (%cnt, %last, %ords); +$input =~ s/\/\*.*?\*\///sg; # Remove C comments +while ($input =~ m/(\S+)\s*\{((?:[^\{\}]|\{(?2)\})+)\}\s*;/sg) { # Split {...} + my ($ver, $block) = ($1, $2); + while ($block =~ s/(\S+)\s*:((?:[^\{\}:]|\{(?2)\})+)$//sg) { # Split section: + my ($section, $syms) = ($1, $2); + next if $section ne 'global'; + $syms =~ s/\s+//g; + foreach (split /;\s*/, $syms) { # Split symbols + $cnt{$_}++; + $last{$_} = $ver; + push @syms, [$_, $ver]; + } + } +} +open my $importfh, '>', $importdef or die "Cannot open output file $importdef: $!\n"; +open my $buildfh, '>', $builddef or die "Cannot open output file $builddef: $!\n"; +print $importfh "LIBRARY \"$dllname\"\n"; +print $importfh "EXPORTS\n"; +print $buildfh "EXPORTS\n"; +my $ord = 1; +foreach (@syms) { + my ($sym, $ver) = @{$_}; + print $importfh "\"$sym\@$ver\" \@$ord\n"; + if ($last{$sym} ne $ver) { + print $buildfh "\"$sym\@$ver\" \@$ord\n"; + } else { + $ords{$sym} = $ord; + print $buildfh "\"$sym\@$ver\" = " . (($cnt{$sym} > 1) ? "\"$sym\@\@$ver\"" : $sym) . " \@$ord\n" + } + $ord++; +} +# GNU dlltool has broken calculation of ordinals for aliased symbols, so specify ordinals explicitly +# GNU LD prior 2.21 has broken handling of symbols with dot character +# Operator == for defining symbol alias is supported since GNU dlltool 2.21 +print $importfh "$_ \@$ords{$_} == \"$_\@$last{$_}\"\n" foreach sort keys %last; +close $importfh; +close $buildfh; diff --git a/lib/win32-cfgmgr32.c b/lib/win32-cfgmgr32.c new file mode 100644 index 0000000..dbddc54 --- /dev/null +++ b/lib/win32-cfgmgr32.c @@ -0,0 +1,1765 @@ +/* + * The PCI Library -- List PCI devices on Win32 via Configuration Manager + * + * Copyright (c) 2021 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <windows.h> +#include <cfgmgr32.h> + +#include <ctype.h> /* for isxdigit() */ +#include <stdio.h> /* for sprintf() */ +#include <string.h> /* for strlen(), strchr(), strncmp() */ +#include <wchar.h> /* for wcslen(), wcscpy() */ + +#include "internal.h" +#include "win32-helpers.h" + +/* Unfortunately MinGW32 toolchain does not provide these cfgmgr32 constants. */ + +#ifndef RegDisposition_OpenExisting +#define RegDisposition_OpenExisting 0x00000001 +#endif + +#ifndef CM_REGISTRY_SOFTWARE +#define CM_REGISTRY_SOFTWARE 0x00000001 +#endif + +#ifndef CM_DRP_HARDWAREID +#define CM_DRP_HARDWAREID 0x00000002 +#endif +#ifndef CM_DRP_SERVICE +#define CM_DRP_SERVICE 0x00000005 +#endif +#ifndef CM_DRP_BUSNUMBER +#define CM_DRP_BUSNUMBER 0x00000016 +#endif +#ifndef CM_DRP_ADDRESS +#define CM_DRP_ADDRESS 0x0000001D +#endif + +#ifndef CR_INVALID_CONFLICT_LIST +#define CR_INVALID_CONFLICT_LIST 0x00000039 +#endif +#ifndef CR_INVALID_INDEX +#define CR_INVALID_INDEX 0x0000003A +#endif +#ifndef CR_INVALID_STRUCTURE_SIZE +#define CR_INVALID_STRUCTURE_SIZE 0x0000003B +#endif + +#ifndef fIOD_10_BIT_DECODE +#define fIOD_10_BIT_DECODE 0x0004 +#endif +#ifndef fIOD_12_BIT_DECODE +#define fIOD_12_BIT_DECODE 0x0008 +#endif +#ifndef fIOD_16_BIT_DECODE +#define fIOD_16_BIT_DECODE 0x0010 +#endif +#ifndef fIOD_POSITIVE_DECODE +#define fIOD_POSITIVE_DECODE 0x0020 +#endif +#ifndef fIOD_PASSIVE_DECODE +#define fIOD_PASSIVE_DECODE 0x0040 +#endif +#ifndef fIOD_WINDOW_DECODE +#define fIOD_WINDOW_DECODE 0x0080 +#endif +#ifndef fIOD_PORT_BAR +#define fIOD_PORT_BAR 0x0100 +#endif + +#ifndef fMD_WINDOW_DECODE +#define fMD_WINDOW_DECODE 0x0040 +#endif +#ifndef fMD_MEMORY_BAR +#define fMD_MEMORY_BAR 0x0080 +#endif + +#ifndef mMD_Prefetchable +#define mMD_Prefetchable fMD_Prefetchable +#endif + +/* + * Unfortunately MinGW32 toolchain does not provide import library for these + * cfgmgr32.dll functions. So resolve pointers to these functions at runtime. + * MinGW-w64 toolchain provides them also in 32-bit mode. + */ + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + +#ifdef CM_Get_DevNode_Registry_PropertyA +#undef CM_Get_DevNode_Registry_PropertyA +#endif +static CONFIGRET (WINAPI *MyCM_Get_DevNode_Registry_PropertyA)(DEVINST dnDevInst, ULONG ulProperty, PULONG pulRegDataType, PVOID Buffer, PULONG pulLength, ULONG ulFlags); +#define CM_Get_DevNode_Registry_PropertyA MyCM_Get_DevNode_Registry_PropertyA + +#ifdef CM_Get_DevNode_Registry_PropertyW +#undef CM_Get_DevNode_Registry_PropertyW +#endif +static CONFIGRET (WINAPI *MyCM_Get_DevNode_Registry_PropertyW)(DEVINST dnDevInst, ULONG ulProperty, PULONG pulRegDataType, PVOID Buffer, PULONG pulLength, ULONG ulFlags); +#define CM_Get_DevNode_Registry_PropertyW MyCM_Get_DevNode_Registry_PropertyW + +#ifndef CM_Open_DevNode_Key +#undef CM_Open_DevNode_Key +#endif +static CONFIGRET (WINAPI *MyCM_Open_DevNode_Key)(DEVINST dnDevNode, REGSAM samDesired, ULONG ulHardwareProfile, REGDISPOSITION Disposition, PHKEY phkDevice, ULONG ulFlags); +#define CM_Open_DevNode_Key MyCM_Open_DevNode_Key + +static BOOL +resolve_cfgmgr32_functions(void) +{ + HMODULE cfgmgr32; + + if (CM_Get_DevNode_Registry_PropertyA && CM_Get_DevNode_Registry_PropertyW && CM_Open_DevNode_Key) + return TRUE; + + cfgmgr32 = GetModuleHandleA("cfgmgr32.dll"); + if (!cfgmgr32) + return FALSE; + + CM_Get_DevNode_Registry_PropertyA = (void *)GetProcAddress(cfgmgr32, "CM_Get_DevNode_Registry_PropertyA"); + CM_Get_DevNode_Registry_PropertyW = (void *)GetProcAddress(cfgmgr32, "CM_Get_DevNode_Registry_PropertyW"); + CM_Open_DevNode_Key = (void *)GetProcAddress(cfgmgr32, "CM_Open_DevNode_Key"); + if (!CM_Get_DevNode_Registry_PropertyA || !CM_Get_DevNode_Registry_PropertyW || !CM_Open_DevNode_Key) + return FALSE; + + return TRUE; +} + +#endif + +/* + * cfgmgr32.dll uses custom non-Win32 error numbers which are unsupported by + * Win32 APIs like GetLastError() and FormatMessage() functions. + * + * Windows 7 introduced new cfgmgr32.dll function CM_MapCrToWin32Err() for + * translating mapping CR_* errors to Win32 errors but most error codes are + * not mapped. So this function is unusable. + * + * Error strings for CR_* errors are defined in cmapi.rc file which is + * statically linked into some system libraries (e.g. filemgmt.dll, + * acledit.dll, netui0.dll or netui2.dll) but due to static linking it is + * not possible to access these error strings easily at runtime. + * + * So define own function for translating CR_* errors directly to strings. + */ +static const char * +cr_strerror(CONFIGRET cr_error_id) +{ + static char unknown_error[sizeof("Unknown CR error XXXXXXXXXX")]; + static const char *cr_errors[] = { + "The operation completed successfully", + "CR_DEFAULT", + "Not enough memory is available to process this command", + "A required pointer parameter is invalid", + "The ulFlags parameter specified is invalid for this operation", + "The device instance handle parameter is not valid", + "The supplied resource descriptor parameter is invalid", + "The supplied logical configuration parameter is invalid", + "CR_INVALID_ARBITRATOR", + "CR_INVALID_NODELIST", + "CR_DEVNODE_HAS_REQS/CR_DEVINST_HAS_REQS", + "The RESOURCEID parameter does not contain a valid RESOURCEID", + "CR_DLVXD_NOT_FOUND", + "The specified device instance handle does not correspond to a present device", + "There are no more logical configurations available", + "There are no more resource descriptions available", + "This device instance already exists", + "The supplied range list parameter is invalid", + "CR_INVALID_RANGE", + "A general internal error occurred", + "CR_NO_SUCH_LOGICAL_DEV", + "The device is disabled for this configuration", + "CR_NOT_SYSTEM_VM", + "A service or application refused to allow removal of this device", + "CR_APM_VETOED", + "CR_INVALID_LOAD_TYPE", + "An output parameter was too small to hold all the data available", + "CR_NO_ARBITRATOR", + "CR_NO_REGISTRY_HANDLE", + "A required entry in the registry is missing or an attempt to write to the registry failed", + "The specified Device ID is not a valid Device ID", + "One or more parameters were invalid", + "CR_INVALID_API", + "CR_DEVLOADER_NOT_READY", + "CR_NEED_RESTART", + "There are no more hardware profiles available", + "CR_DEVICE_NOT_THERE", + "The specified value does not exist in the registry", + "CR_WRONG_TYPE", + "The specified priority is invalid for this operation", + "This device cannot be disabled", + "CR_FREE_RESOURCES", + "CR_QUERY_VETOED", + "CR_CANT_SHARE_IRQ", + "CR_NO_DEPENDENT", + "CR_SAME_RESOURCES", + "The specified key does not exist in the registry", + "The specified machine name does not meet the UNC naming conventions", + "A general remote communication error occurred", + "The machine selected for remote communication is not available at this time", + "The Plug and Play service or another required service is not available", + "Access denied", + "This routine is not implemented in this version of the operating system", + "The specified property type is invalid for this operation", + "Device interface is active", + "No such device interface", + "Invalid reference string", + "Invalid conflict list", + "Invalid index", + "Invalid structure size" + }; + if (cr_error_id <= 0 || cr_error_id >= sizeof(cr_errors)/sizeof(*cr_errors)) + { + sprintf(unknown_error, "Unknown CR error %lu", cr_error_id); + return unknown_error; + } + return cr_errors[cr_error_id]; +} + +static int +fmt_validate(const char *s, int len, const char *fmt) +{ + int i; + + for (i = 0; i < len; i++) + if (!fmt[i] || (fmt[i] == '#' ? !isxdigit(s[i]) : fmt[i] != s[i])) + return 0; + + return 1; +} + +static int +seq_xdigit_validate(const char *s, int mult, int min) +{ + int i, len; + + len = strlen(s); + if (len < min*mult || len % mult) + return 0; + + for (i = 0; i < len; i++) + if (!isxdigit(s[i])) + return 0; + + return 1; +} + +static LPWSTR +get_device_service_name(struct pci_access *a, DEVINST devinst, DEVINSTID_A devinst_id, BOOL *supported) +{ + ULONG reg_type, reg_size, reg_len; + LPWSTR service_name; + CONFIGRET cr; + + /* + * All data are stored as 7-bit ASCII strings in system but service name is + * exception. It can contain UNICODE. Moreover it is passed to other Win32 API + * functions and therefore it cannot be converted to 8-bit ANSI string without + * data loss. So use wide function CM_Get_DevNode_Registry_PropertyW() in this + * case and deal with all wchar_t problems... + */ + + reg_size = 0; + cr = CM_Get_DevNode_Registry_PropertyW(devinst, CM_DRP_SERVICE, ®_type, NULL, ®_size, 0); + if (cr == CR_CALL_NOT_IMPLEMENTED) + { + *supported = FALSE; + return NULL; + } + else if (cr == CR_NO_SUCH_VALUE) + { + *supported = TRUE; + return NULL; + } + else if (cr != CR_SUCCESS && + cr != CR_BUFFER_SMALL) + { + a->warning("Cannot retrieve service name for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + *supported = TRUE; + return NULL; + } + else if (reg_type != REG_SZ) + { + a->warning("Cannot retrieve service name for PCI device %s: Service name is stored as unknown type 0x%lx.", devinst_id, reg_type); + *supported = TRUE; + return NULL; + } + +retry: + /* + * Returned size is on older Windows versions without nul-term char. + * So explicitly increase size and fill nul-term byte. + */ + reg_size += sizeof(service_name[0]); + service_name = pci_malloc(a, reg_size); + reg_len = reg_size; + cr = CM_Get_DevNode_Registry_PropertyW(devinst, CM_DRP_SERVICE, ®_type, service_name, ®_len, 0); + service_name[reg_size/sizeof(service_name[0]) - 1] = 0; + if (reg_len > reg_size) + { + pci_mfree(service_name); + reg_size = reg_len; + goto retry; + } + else if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve service name for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + pci_mfree(service_name); + *supported = TRUE; + return NULL; + } + else if (reg_type != REG_SZ) + { + a->warning("Cannot retrieve service name for PCI device %s: Service name is stored as unknown type 0x%lx.", devinst_id, reg_type); + pci_mfree(service_name); + *supported = TRUE; + return NULL; + } + + + return service_name; +} + +static char* +get_driver_path_for_service(struct pci_access *a, LPCWSTR service_name, SC_HANDLE manager) +{ + UINT (WINAPI *get_system_root_path)(LPWSTR buffer, UINT size) = NULL; + DWORD service_config_size, service_config_len; + LPQUERY_SERVICE_CONFIGW service_config = NULL; + LPWSTR service_image_path = NULL; + SERVICE_STATUS service_status; + SC_HANDLE service = NULL; + char *driver_path = NULL; + int trim_system32 = 0; + UINT systemroot_len; + int driver_path_len; + UINT system32_len; + HMODULE kernel32; + WCHAR *trim_ptr; + DWORD error; + + service = OpenServiceW(manager, service_name, SERVICE_QUERY_CONFIG | SERVICE_QUERY_STATUS); + if (!service) + { + error = GetLastError(); + if (error != ERROR_SERVICE_DOES_NOT_EXIST) + a->warning("Cannot open service %ls with query rights: %s.", service_name, win32_strerror(error)); + goto out; + } + + if (!QueryServiceStatus(service, &service_status)) + { + error = GetLastError(); + a->warning("Cannot query status of service %ls: %s.", service_name, win32_strerror(error)); + goto out; + } + + if (service_status.dwCurrentState == SERVICE_STOPPED) + goto out; + + if (service_status.dwServiceType != SERVICE_KERNEL_DRIVER) + goto out; + + if (!QueryServiceConfigW(service, NULL, 0, &service_config_size)) + { + error = GetLastError(); + if (error != ERROR_INSUFFICIENT_BUFFER) + { + a->warning("Cannot query config of service %ls: %s.", service_name, win32_strerror(error)); + goto out; + } + } + +retry_service_config: + service_config = pci_malloc(a, service_config_size); + if (!QueryServiceConfigW(service, service_config, service_config_size, &service_config_len)) + { + error = GetLastError(); + if (error == ERROR_INSUFFICIENT_BUFFER) + { + pci_mfree(service_config); + service_config_size = service_config_len; + goto retry_service_config; + } + a->warning("Cannot query config of service %ls: %s.", service_name, win32_strerror(error)); + goto out; + } + + if (service_config->dwServiceType != SERVICE_KERNEL_DRIVER) + goto out; + + /* + * Despite QueryServiceConfig() is Win32 API, it returns lpBinaryPathName + * (ImagePath registry) in NT format. Unfortunately there is no Win32 + * function for converting NT paths to Win32 paths. So do it manually and + * convert this NT format to human-readable Win32 path format. + */ + + /* + * GetSystemWindowsDirectoryW() returns path to NT SystemRoot namespace. + * Alternativelly path to NT SystemRoot namespace can be constructed by + * GetSystemDirectoryW() by trimming "\\system32" from the end of path. + * GetSystemWindowsDirectoryW() is not provided in old Windows versions, + * so use GetProcAddress() for compatibility with all Windows versions. + */ + kernel32 = GetModuleHandleW(L"kernel32.dll"); + if (kernel32) + get_system_root_path = (void *)GetProcAddress(kernel32, "GetSystemWindowsDirectoryW"); + else + { + get_system_root_path = &GetSystemDirectoryW; + trim_system32 = 1; + } + + if (!service_config->lpBinaryPathName || !service_config->lpBinaryPathName[0]) + { + /* No ImagePath is specified, NT kernel assumes implicit kernel driver path by service name, which is relative to "\\system32\\drivers". */ + /* GetSystemDirectoryW() returns path to "\\system32" directory on all Windows versions. */ + system32_len = GetSystemDirectoryW(NULL, 0); /* Returns number of WCHARs plus 1 for nul-term. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * (system32_len + sizeof("\\drivers\\")-1 + wcslen(service_name) + sizeof(".sys")-1)); + system32_len = GetSystemDirectoryW(service_image_path, system32_len); /* Now it returns number of WCHARs without nul-term. */ + if (system32_len && service_image_path[system32_len-1] != L'\\') + service_image_path[system32_len++] = L'\\'; + wcscpy(service_image_path + system32_len, L"drivers\\"); + wcscpy(service_image_path + system32_len + sizeof("drivers\\")-1, service_name); + wcscpy(service_image_path + system32_len + sizeof("drivers\\")-1 + wcslen(service_name), L".sys"); + } + else if (wcsncmp(service_config->lpBinaryPathName, L"\\SystemRoot\\", sizeof("\\SystemRoot\\")-1) == 0) + { + /* ImagePath is in NT SystemRoot namespace, convert to Win32 path via GetSystemWindowsDirectoryW()/GetSystemDirectoryW(). */ + systemroot_len = get_system_root_path(NULL, 0); /* Returns number of WCHARs plus 1 for nul-term. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * (systemroot_len + wcslen(service_config->lpBinaryPathName) - (sizeof("\\SystemRoot")-1))); + systemroot_len = get_system_root_path(service_image_path, systemroot_len); /* Now it returns number of WCHARs without nul-term. */ + if (trim_system32 && systemroot_len && (trim_ptr = wcsrchr(service_image_path, L'\\')) != NULL) + systemroot_len = trim_ptr - service_image_path; + if (systemroot_len && service_image_path[systemroot_len-1] != L'\\') + service_image_path[systemroot_len++] = L'\\'; + wcscpy(service_image_path + systemroot_len, service_config->lpBinaryPathName + sizeof("\\SystemRoot\\")-1); + } + else if (wcsncmp(service_config->lpBinaryPathName, L"\\??\\UNC\\", sizeof("\\??\\UNC\\")-1) == 0 || + wcsncmp(service_config->lpBinaryPathName, L"\\??\\\\UNC\\", sizeof("\\??\\\\UNC\\")-1) == 0) + { + /* ImagePath is in NT UNC namespace, convert to Win32 UNC path via "\\\\" prefix. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * (sizeof("\\\\") + wcslen(service_config->lpBinaryPathName) - (sizeof("\\??\\UNC\\")-1))); + /* Namespace separator may be single or double backslash. */ + driver_path_len = sizeof("\\??\\")-1; + if (service_config->lpBinaryPathName[driver_path_len] == L'\\') + driver_path_len++; + driver_path_len += sizeof("UNC\\")-1; + wcscpy(service_image_path, L"\\\\"); + wcscpy(service_image_path + sizeof("\\\\")-1, service_config->lpBinaryPathName + driver_path_len); + } + else if (wcsncmp(service_config->lpBinaryPathName, L"\\??\\", sizeof("\\??\\")-1) == 0) + { + /* ImagePath is in NT Global?? namespace, root of the Win32 file namespace, so just remove "\\??\\" prefix to get Win32 path. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * (wcslen(service_config->lpBinaryPathName) - (sizeof("\\??\\")-1))); + /* Namespace separator may be single or double backslash. */ + driver_path_len = sizeof("\\??\\")-1; + if (service_config->lpBinaryPathName[driver_path_len] == L'\\') + driver_path_len++; + wcscpy(service_image_path, service_config->lpBinaryPathName + driver_path_len); + } + else if (service_config->lpBinaryPathName[0] != L'\\') + { + /* ImagePath is relative to the NT SystemRoot namespace, convert to Win32 path via GetSystemWindowsDirectoryW()/GetSystemDirectoryW(). */ + systemroot_len = get_system_root_path(NULL, 0); /* Returns number of WCHARs plus 1 for nul-term. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * (systemroot_len + sizeof("\\")-1 + wcslen(service_config->lpBinaryPathName))); + systemroot_len = get_system_root_path(service_image_path, systemroot_len); /* Now it returns number of WCHARs without nul-term. */ + if (trim_system32 && systemroot_len && (trim_ptr = wcsrchr(service_image_path, L'\\')) != NULL) + systemroot_len = trim_ptr - service_image_path; + if (systemroot_len && service_image_path[systemroot_len-1] != L'\\') + service_image_path[systemroot_len++] = L'\\'; + wcscpy(service_image_path + systemroot_len, service_config->lpBinaryPathName); + } + else + { + /* ImagePath is in some unhandled NT namespace, copy it as is. It cannot be used in Win32 API but libpci user can be still interested in it. */ + service_image_path = pci_malloc(a, sizeof(WCHAR) * wcslen(service_config->lpBinaryPathName)); + wcscpy(service_image_path, service_config->lpBinaryPathName); + } + + /* Calculate len of buffer needed for conversion from LPWSTR to char*. */ + driver_path_len = WideCharToMultiByte(CP_ACP, 0, service_image_path, -1, NULL, 0, NULL, NULL); + if (driver_path_len <= 0) + { + error = GetLastError(); + a->warning("Cannot convert kernel driver path from wide string to 8-bit string: %s.", win32_strerror(error)); + goto out; + } + + driver_path = pci_malloc(a, driver_path_len); + driver_path_len = WideCharToMultiByte(CP_ACP, 0, service_image_path, -1, driver_path, driver_path_len, NULL, NULL); + if (driver_path_len <= 0) + { + error = GetLastError(); + a->warning("Cannot convert kernel driver path from wide string to 8-bit string: %s.", win32_strerror(error)); + pci_mfree(driver_path); + driver_path = NULL; + goto out; + } + +out: + if (service_image_path) + pci_mfree(service_image_path); + if (service_config) + pci_mfree(service_config); + if (service) + CloseServiceHandle(service); + return driver_path; +} + +static HKEY +get_device_driver_devreg(struct pci_access *a, DEVINST devinst, DEVINSTID_A devinst_id) +{ + CONFIGRET cr; + HKEY key; + + cr = CM_Open_DevNode_Key(devinst, KEY_READ, 0, RegDisposition_OpenExisting, &key, CM_REGISTRY_SOFTWARE); + if (cr != CR_SUCCESS) + { + if (cr != CR_NO_SUCH_VALUE) + a->warning("Cannot retrieve driver key for device %s: %s.", devinst_id, cr_strerror(cr)); + return NULL; + } + + return key; +} + +static char* +read_reg_key_string_value(struct pci_access *a, HKEY key, const char *name, DWORD *unkn_reg_type) +{ + DWORD reg_type, reg_size, reg_len; + char *value; + LONG error; + + reg_size = 0; + error = RegQueryValueExA(key, name, NULL, ®_type, NULL, ®_size); + if (error != ERROR_SUCCESS && + error != ERROR_MORE_DATA) + { + SetLastError(error); + return NULL; + } + else if (reg_type != REG_SZ) + { + SetLastError(0); + *unkn_reg_type = reg_type; + return NULL; + } + +retry: + value = pci_malloc(a, reg_size + 1); + reg_len = reg_size; + error = RegQueryValueExA(key, name, NULL, ®_type, (PBYTE)value, ®_len); + if (error != ERROR_SUCCESS) + { + pci_mfree(value); + if (error == ERROR_MORE_DATA) + { + reg_size = reg_len; + goto retry; + } + SetLastError(error); + return NULL; + } + else if (reg_type != REG_SZ) + { + pci_mfree(value); + SetLastError(0); + *unkn_reg_type = reg_type; + return NULL; + } + value[reg_len] = '\0'; + + return value; +} + +static int +driver_cmp(const char *driver, const char *match) +{ + int len = strlen(driver); + if (driver[0] == '*') + driver++; + if (len >= 4 && strcasecmp(driver + len - 4, ".vxd") == 0) + len -= 4; + return strncasecmp(driver, match, len); +} + +static char* +get_driver_path_for_regkey(struct pci_access *a, DEVINSTID_A devinst_id, HKEY key) +{ + char *driver_list, *driver, *driver_next; + char *subdriver, *subname; + char *driver_ptr; + char *driver_path; + DWORD unkn_reg_type; + UINT systemdir_len; + HKEY subkey; + LONG error; + BOOL vmm32; + BOOL noext; + int len; + + driver_list = read_reg_key_string_value(a, key, "DevLoader", &unkn_reg_type); + if (!driver_list) + { + error = GetLastError(); + if (error == 0) + a->warning("Cannot read driver DevLoader key for PCI device %s: DevLoader key is stored as unknown type 0x%lx.", devinst_id, unkn_reg_type); + else if (error != ERROR_FILE_NOT_FOUND) + a->warning("Cannot read driver DevLoader key for PCI device %s: %s.", devinst_id, win32_strerror(error)); + return NULL; + } + + subdriver = NULL; + driver = driver_list; + while (*driver) + { + driver_next = strchr(driver, ','); + if (driver_next) + *(driver_next++) = '\0'; + + if (driver_cmp(driver, "ios") == 0 || + driver_cmp(driver, "vcomm") == 0) + subname = "PortDriver"; + else if (driver_cmp(driver, "ntkern") == 0) + subname = "NTMPDriver"; + else if (driver_cmp(driver, "ndis") == 0) + subname = "DeviceVxDs"; + else if (driver_cmp(driver, "vdd") == 0) + subname = "minivdd"; + else + subname = NULL; + + subkey = key; + if (subname && strcmp(subname, "minivdd") == 0) + { + error = RegOpenKeyA(key, "Default", &subkey); + if (error != ERROR_SUCCESS) + { + a->warning("Cannot open driver subkey Default for PCI device %s: %s.", devinst_id, win32_strerror(error)); + subkey = NULL; + } + } + + if (!subname) + break; + + if (subkey) + { +retry_subname: + subdriver = read_reg_key_string_value(a, subkey, subname, &unkn_reg_type); + if (!subdriver) + { + error = GetLastError(); + if (error == 0) + a->warning("Cannot read driver %s key for PCI device %s: %s key is stored as unknown type 0x%lx.", subname, devinst_id, subname, unkn_reg_type); + else if (error != ERROR_FILE_NOT_FOUND) + a->warning("Cannot read driver %s key for PCI device %s: %s.", subname, devinst_id, win32_strerror(error)); + else if (strcmp(subname, "minivdd") == 0) + { + subname = "drv"; + goto retry_subname; + } + else if (strcmp(subname, "drv") == 0) + { + subname = "vdd"; + goto retry_subname; + } + } + + if (subkey != key) + RegCloseKey(subkey); + + if (subdriver) + { + char *endptr = strchr(subdriver, ','); + if (endptr) + *endptr = '\0'; + break; + } + } + + driver = driver_next; + } + + if (subdriver && subdriver[0]) + driver_ptr = subdriver; + else if (driver[0]) + driver_ptr = driver; + else + driver_ptr = NULL; + + if (driver_ptr && driver_ptr[0] == '*') + { + vmm32 = TRUE; + driver_ptr++; + } + else + vmm32 = FALSE; + + if (!driver_ptr[0]) + driver_ptr = NULL; + + len = driver_ptr ? strlen(driver_ptr) : 0; + noext = driver_ptr && (len < 4 || driver_ptr[len-4] != '.'); + + if (!driver_ptr) + driver_path = NULL; + else + { + if (tolower(driver_ptr[0]) >= 'a' && tolower(driver_ptr[0]) <= 'z' && driver_ptr[1] == ':') + { + /* Driver is already with absolute path. */ + driver_path = pci_strdup(a, driver_ptr); + } + else if (driver_cmp(driver, "ntkern") == 0 && subdriver) + { + /* Driver is relative to system32\drivers\ directory which is relative to windows directory. */ + systemdir_len = GetWindowsDirectoryA(NULL, 0); + driver_path = pci_malloc(a, systemdir_len + 1 + sizeof("system32\\drivers\\")-1 + strlen(driver_ptr) + 4 + 1); + systemdir_len = GetWindowsDirectoryA(driver_path, systemdir_len + 1); + if (systemdir_len && driver_path[systemdir_len - 1] != '\\') + driver_path[systemdir_len++] = '\\'; + sprintf(driver_path + systemdir_len, "system32\\drivers\\%s%s", driver_ptr, noext ? ".sys" : ""); + } + else if (vmm32) + { + /* Driver is packed in vmm32.vxd which is stored in system directory. */ + systemdir_len = GetSystemDirectoryA(NULL, 0); + driver_path = pci_malloc(a, systemdir_len + 1 + sizeof("vmm32.vxd ()")-1 + strlen(driver_ptr) + 4 + 1); + systemdir_len = GetSystemDirectoryA(driver_path, systemdir_len + 1); + if (systemdir_len && driver_path[systemdir_len - 1] != '\\') + driver_path[systemdir_len++] = '\\'; + sprintf(driver_path + systemdir_len, "vmm32.vxd (%s%s)", driver_ptr, noext ? ".vxd" : ""); + } + else + { + /* Otherwise driver is relative to system directory. */ + systemdir_len = GetSystemDirectoryA(NULL, 0); + driver_path = pci_malloc(a, systemdir_len + 1 + strlen(driver_ptr) + 4 + 1); + systemdir_len = GetSystemDirectoryA(driver_path, systemdir_len + 1); + if (systemdir_len && driver_path[systemdir_len - 1] != '\\') + driver_path[systemdir_len++] = '\\'; + sprintf(driver_path + systemdir_len, "%s%s", driver_ptr, noext ? ".vxd" : ""); + } + } + + if (subdriver) + pci_mfree(subdriver); + pci_mfree(driver_list); + return driver_path; +} + +static char * +get_device_driver_path(struct pci_dev *d, SC_HANDLE manager, BOOL manager_supported) +{ + struct pci_access *a = d->access; + BOOL service_supported = TRUE; + DEVINSTID_A devinst_id = NULL; + LPWSTR service_name = NULL; + ULONG devinst_id_len = 0; + char *driver_path = NULL; + DEVINST devinst = (DEVINST)d->backend_data; + ULONG problem = 0; + ULONG status = 0; + HKEY key = NULL; + + if (CM_Get_DevNode_Status(&status, &problem, devinst, 0) != CR_SUCCESS || !(status & DN_DRIVER_LOADED)) + return NULL; + + if (CM_Get_Device_ID_Size(&devinst_id_len, devinst, 0) == CR_SUCCESS) + { + devinst_id = pci_malloc(a, devinst_id_len + 1); + if (CM_Get_Device_IDA(devinst, devinst_id, devinst_id_len + 1, 0) != CR_SUCCESS) + { + pci_mfree(devinst_id); + devinst_id = pci_strdup(a, "UNKNOWN"); + } + } + else + devinst_id = pci_strdup(a, "UNKNOWN"); + + service_name = get_device_service_name(d->access, devinst, devinst_id, &service_supported); + if ((!service_name || !manager) && service_supported && manager_supported) + goto out; + else if (service_name && manager) + { + driver_path = get_driver_path_for_service(d->access, service_name, manager); + goto out; + } + + key = get_device_driver_devreg(d->access, devinst, devinst_id); + if (key) + { + driver_path = get_driver_path_for_regkey(d->access, devinst_id, key); + goto out; + } + +out: + if (key) + RegCloseKey(key); + if (service_name) + pci_mfree(service_name); + pci_mfree(devinst_id); + return driver_path; +} + +static void +fill_drivers(struct pci_access *a) +{ + BOOL manager_supported; + SC_HANDLE manager; + struct pci_dev *d; + char *driver; + DWORD error; + + /* ERROR_CALL_NOT_IMPLEMENTED is returned on systems without Service Manager support. */ + manager_supported = TRUE; + manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT); + if (!manager) + { + error = GetLastError(); + if (error != ERROR_CALL_NOT_IMPLEMENTED) + a->warning("Cannot open Service Manager with connect right: %s.", win32_strerror(error)); + else + manager_supported = FALSE; + } + + for (d = a->devices; d; d = d->next) + { + driver = get_device_driver_path(d, manager, manager_supported); + if (driver) + { + pci_set_property(d, PCI_FILL_DRIVER, driver); + pci_mfree(driver); + } + d->known_fields |= PCI_FILL_DRIVER; + } + + if (manager) + CloseServiceHandle(manager); +} + +static const char * +res_id_to_str(RESOURCEID res_id) +{ + static char hex_res_id[sizeof("0xffffffff")]; + + if (res_id == ResType_IO) + return "IO"; + else if (res_id == ResType_Mem) + return "MEM"; + else if (res_id == ResType_IRQ) + return "IRQ"; + + sprintf(hex_res_id, "0x%lx", res_id); + return hex_res_id; +} + +static void +fill_resources(struct pci_dev *d, DEVINST devinst, DEVINSTID_A devinst_id) +{ + struct pci_access *a = d->access; + + CONFIGRET cr; + + LOG_CONF config; + ULONG problem; + ULONG status; + + RES_DES prev_res_des; + RES_DES res_des; + RESOURCEID res_id; + DWORD bar_res_count; + + BOOL is_bar_res; + BOOL non_nt_system; + + int last_irq = -1; + int last_shared_irq = -1; + + cr = CM_Get_DevNode_Status(&status, &problem, devinst, 0); + if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve status of PCI device %s: %s.", devinst_id, cr_strerror(cr)); + return; + } + + cr = CR_NO_MORE_LOG_CONF; + + /* + * If the device is running then retrieve allocated configuration by PnP + * manager which is currently in use by a device. + */ + if (!(status & DN_HAS_PROBLEM)) + cr = CM_Get_First_Log_Conf(&config, devinst, ALLOC_LOG_CONF); + + /* + * If the device is not running or it does not have allocated configuration by + * PnP manager then retrieve forced configuration which prevents PnP manager + * from assigning resources. + */ + if (cr == CR_NO_MORE_LOG_CONF) + cr = CM_Get_First_Log_Conf(&config, devinst, FORCED_LOG_CONF); + + /* + * If the device does not have neither allocated configuration by PnP manager + * nor forced configuration and it is not disabled in the BIOS then retrieve + * boot configuration supplied by the BIOS. + */ + if (cr == CR_NO_MORE_LOG_CONF && + (!(status & DN_HAS_PROBLEM) || problem != CM_PROB_HARDWARE_DISABLED)) + cr = CM_Get_First_Log_Conf(&config, devinst, BOOT_LOG_CONF); + + if (cr != CR_SUCCESS) + { + /* + * Note: Starting with Windows 8, CM_Get_First_Log_Conf returns + * CR_CALL_NOT_IMPLEMENTED when used in a Wow64 scenario. + * To request information about the hardware resources on a local machine + * it is necessary implement an architecture-native version of the + * application using the hardware resource APIs. For example: An AMD64 + * application for AMD64 systems. + */ + if (cr == CR_CALL_NOT_IMPLEMENTED && win32_is_32bit_on_win8_64bit_system()) + { + static BOOL warn_once = FALSE; + if (!warn_once) + { + warn_once = TRUE; + a->warning("Cannot retrieve resources of PCI devices from 32-bit application on 64-bit system."); + } + } + else if (cr != CR_NO_MORE_LOG_CONF) + a->warning("Cannot retrieve resources of PCI device %s: %s.", devinst_id, cr_strerror(cr)); + return; + } + + bar_res_count = 0; + non_nt_system = win32_is_non_nt_system(); + + is_bar_res = TRUE; + if (non_nt_system) + { + BOOL has_child; + DEVINST child; + ULONG child_name_len; + PSTR child_name; + BOOL is_bridge; + + if (CM_Get_Child(&child, devinst, 0) != CR_SUCCESS) + has_child = FALSE; + else if (CM_Get_Device_ID_Size(&child_name_len, child, 0) != CR_SUCCESS) + has_child = FALSE; + else + { + child_name_len++; + child_name = pci_malloc(a, child_name_len); + if (CM_Get_Device_IDA(child, child_name, child_name_len, 0) != CR_SUCCESS) + has_child = FALSE; + else if (strncmp(child_name, "PCI\\", 4) != 0) + has_child = FALSE; + else + has_child = TRUE; + pci_mfree(child_name); + } + + if (has_child || d->device_class == PCI_CLASS_BRIDGE_PCI || d->device_class == PCI_CLASS_BRIDGE_CARDBUS) + is_bridge = TRUE; + else + is_bridge = FALSE; + + if (is_bridge) + is_bar_res = FALSE; + } + + prev_res_des = (RES_DES)config; + while ((cr = CM_Get_Next_Res_Des(&res_des, prev_res_des, ResType_All, &res_id, 0)) == CR_SUCCESS) + { + pciaddr_t start, end, size, flags; + ULONG res_des_data_size; + PBYTE res_des_data; + + if (prev_res_des != config) + CM_Free_Res_Des_Handle(prev_res_des); + + prev_res_des = res_des; + + /* Skip other resources early */ + if (res_id != ResType_IO && res_id != ResType_Mem && res_id != ResType_IRQ) + continue; + + cr = CM_Get_Res_Des_Data_Size(&res_des_data_size, res_des, 0); + if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve %s resource data of PCI device %s: %s.", res_id_to_str(res_id), devinst_id, cr_strerror(cr)); + continue; + } + + if (!res_des_data_size) + { + a->warning("Cannot retrieve %s resource data of PCI device %s: %s.", res_id_to_str(res_id), devinst_id, "Empty data"); + continue; + } + + res_des_data = pci_malloc(a, res_des_data_size); + cr = CM_Get_Res_Des_Data(res_des, res_des_data, res_des_data_size, 0); + if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve %s resource data of PCI device %s: %s.", res_id_to_str(res_id), devinst_id, cr_strerror(cr)); + pci_mfree(res_des_data); + continue; + } + + /* + * There can be more resources with the same id. In this case we are + * interested in the last one in the list as at the beginning of the list + * can be some virtual resources (which are not set in PCI config space). + */ + + if (res_id == ResType_IO) + { + PIO_RESOURCE io_data = (PIO_RESOURCE)res_des_data; + + start = io_data->IO_Header.IOD_Alloc_Base; + end = io_data->IO_Header.IOD_Alloc_End; + size = (end > start) ? (end - start + 1) : 0; + flags = PCI_IORESOURCE_IO; + + /* + * If neither 10-bit, 12-bit, nor 16-bit support is presented then + * expects that this is 32-bit I/O resource. If resource does not fit + * into 16-bit space then it must be 32-bit. If PCI I/O resource is + * not 32-bit then it is 16-bit. + */ + if (end <= 0xffff && (io_data->IO_Header.IOD_DesFlags & (fIOD_10_BIT_DECODE|fIOD_12_BIT_DECODE|fIOD_16_BIT_DECODE))) + flags |= PCI_IORESOURCE_IO_16BIT_ADDR; + + /* + * 16/32-bit non-NT systems do not support these two flags. + * Most NT-based Windows versions support only the fIOD_WINDOW_DECODE + * flag and put all BAR resources before window resources in this + * resource list. So use this fIOD_WINDOW_DECODE flag as separator + * between IO/MEM windows and IO/MEM BARs of PCI Bridges. + */ + if (io_data->IO_Header.IOD_DesFlags & fIOD_WINDOW_DECODE) + is_bar_res = FALSE; + else if (io_data->IO_Header.IOD_DesFlags & fIOD_PORT_BAR) + is_bar_res = TRUE; + + if (is_bar_res && bar_res_count < 6) + { + d->flags[bar_res_count] = flags; + d->base_addr[bar_res_count] = start; + d->size[bar_res_count] = size; + bar_res_count++; + } + else if (!is_bar_res) + { + d->bridge_flags[0] = flags; + d->bridge_base_addr[0] = start; + d->bridge_size[0] = size; + d->known_fields |= PCI_FILL_BRIDGE_BASES; + } + } + else if (res_id == ResType_Mem) + { + PMEM_RESOURCE mem_data = (PMEM_RESOURCE)res_des_data; + + start = mem_data->MEM_Header.MD_Alloc_Base; + end = mem_data->MEM_Header.MD_Alloc_End; + size = (end > start) ? (end - start + 1) : 0; + flags = PCI_IORESOURCE_MEM; + + /* + * If fMD_PrefetchAllowed flag is set then this is + * PCI Prefetchable Memory resource. + */ + if ((mem_data->MEM_Header.MD_Flags & mMD_Prefetchable) == fMD_PrefetchAllowed) + flags |= PCI_IORESOURCE_PREFETCH; + + /* If resource does not fit into 32-bit space then it must be 64-bit. */ + if (is_bar_res && end > 0xffffffff) + flags |= PCI_IORESOURCE_MEM_64; + + /* + * These two flags (fMD_WINDOW_DECODE and fMD_MEMORY_BAR) are + * unsupported on most Windows versions, so distinguish between + * window and BAR based on previous resource type. + */ + if (mem_data->MEM_Header.MD_Flags & fMD_WINDOW_DECODE) + is_bar_res = FALSE; + else if (mem_data->MEM_Header.MD_Flags & fMD_MEMORY_BAR) + is_bar_res = TRUE; + + /* 64-bit BAR resource must be at even position. */ + if (is_bar_res && (flags & PCI_IORESOURCE_MEM_64) && bar_res_count % 2) + bar_res_count++; + + if (is_bar_res && bar_res_count < 6) + { + d->flags[bar_res_count] = flags; + d->base_addr[bar_res_count] = start; + d->size[bar_res_count] = size; + bar_res_count++; + /* 64-bit BAR resource occupies two slots. */ + if (flags & PCI_IORESOURCE_MEM_64) + bar_res_count++; + } + else if (!is_bar_res && !(flags & PCI_IORESOURCE_PREFETCH)) + { + d->bridge_flags[1] = flags; + d->bridge_base_addr[1] = start; + d->bridge_size[1] = size; + d->known_fields |= PCI_FILL_BRIDGE_BASES; + } + else if (!is_bar_res && (flags & PCI_IORESOURCE_PREFETCH)) + { + d->bridge_flags[2] = flags; + d->bridge_base_addr[2] = start; + d->bridge_size[2] = size; + d->known_fields |= PCI_FILL_BRIDGE_BASES; + } + } + else if (res_id == ResType_IRQ) + { + PIRQ_RESOURCE irq_data = (PIRQ_RESOURCE)res_des_data; + + /* + * libpci's d->irq should be set to the non-MSI PCI IRQ and therefore + * it should be level IRQ which may be shared with other PCI devices + * and drivers in the system. As always we want to retrieve the last + * IRQ number from the resource list. + * + * On 16/32-bit non-NT systems is fIRQD_Level set to 2 but on NT + * systems to 0. Moreover it looks like that different PCI drivers + * on both NT and non-NT systems set bits 0 and 1 to wrong values + * and so reported value in this list may be incorrect. + * + * Therefore take the last level-shared IRQ number from the resource + * list and if there is none of this type then take the last IRQ + * number from the list. + */ + last_irq = irq_data->IRQ_Header.IRQD_Alloc_Num; + if ((irq_data->IRQ_Header.IRQD_Flags & (mIRQD_Share|mIRQD_Edge_Level)) == (fIRQD_Share|fIRQD_Level)) + last_shared_irq = irq_data->IRQ_Header.IRQD_Alloc_Num; + + /* + * IRQ resource on 16/32-bit non-NT systems is separator between + * IO/MEM windows and IO/MEM BARs of PCI Bridges. After the IRQ + * resource are IO/MEM BAR resources. + */ + if (!is_bar_res && non_nt_system) + is_bar_res = TRUE; + } + + pci_mfree(res_des_data); + } + if (cr != CR_NO_MORE_RES_DES) + a->warning("Cannot retrieve resources of PCI device %s: %s.", devinst_id, cr_strerror(cr)); + + if (prev_res_des != config) + CM_Free_Res_Des_Handle(prev_res_des); + + CM_Free_Log_Conf_Handle(config); + + /* Set the last IRQ from the resource list to pci_dev. */ + if (last_shared_irq >= 0) + d->irq = last_shared_irq; + else if (last_irq >= 0) + d->irq = last_irq; + if (last_shared_irq >= 0 || last_irq >= 0) + d->known_fields |= PCI_FILL_IRQ; + + if (bar_res_count > 0) + d->known_fields |= PCI_FILL_BASES | PCI_FILL_SIZES | PCI_FILL_IO_FLAGS; +} + +static BOOL +get_device_location(struct pci_access *a, DEVINST devinst, DEVINSTID_A devinst_id, unsigned int *domain, unsigned int *bus, unsigned int *dev, unsigned int *func) +{ + ULONG reg_type, reg_len; + CONFIGRET cr; + BOOL have_bus, have_devfunc; + DWORD drp_bus_num, drp_address; + + *domain = 0; + have_bus = FALSE; + have_devfunc = FALSE; + + /* + * DRP_BUSNUMBER consists of PCI domain number in high 24 bits + * and PCI bus number in low 8 bits. + */ + reg_len = sizeof(drp_bus_num); + cr = CM_Get_DevNode_Registry_PropertyA(devinst, CM_DRP_BUSNUMBER, ®_type, &drp_bus_num, ®_len, 0); + if (cr == CR_SUCCESS && reg_type == REG_DWORD && reg_len == sizeof(drp_bus_num)) + { + *domain = drp_bus_num >> 8; + *bus = drp_bus_num & 0xff; + have_bus = TRUE; + } + + /* + * DRP_ADDRESS consists of PCI device number in high 16 bits + * and PCI function number in low 16 bits. + */ + reg_len = sizeof(drp_address); + cr = CM_Get_DevNode_Registry_PropertyA(devinst, CM_DRP_ADDRESS, ®_type, &drp_address, ®_len, 0); + if (cr == CR_SUCCESS && reg_type == REG_DWORD && reg_len == sizeof(drp_address)) + { + *dev = drp_address >> 16; + *func = drp_address & 0xffff; + have_devfunc = TRUE; + } + + /* + * Device Instance Id for PCI devices is of format: + * "<enumerator>\\<device_id>\\<instance_id>" + * where: + * "<enumerator>" is "PCI" + * "<device_id>" is "VEN_####&DEV_####&SUBSYS_########&REV_##" + * and "<instance_id>" for PCI devices is at least in one of following format: + * "BUS_##&DEV_##&FUNC_##" + * "##.." (sequence of devfn hex bytes, where bytes represents tree path to the root) + * "#..&#..&#..&#.." (four hex numbers separated by "&"; meaning is unknown yet) + * + * First two formats are used only on systems without support for multiple + * domains. The second format uses intel-conf encoding of device and function + * number: Low 3 bits is function number and high 5 bits is device number. + * Bus number is not really encoded in second format! + * + * The third format is used on systems with support for multiple domains but + * format is variable length and currently its meaning is unknown. Apparently + * it looks like that DRP_BUSNUMBER and DRP_ADDRESS registry properties are + * supported on these systems. + * + * If DRP_BUSNUMBER or DRP_ADDRESS failed then try to parse PCI bus, device + * and function numbers from Instance Id part. + */ + if (!have_bus || !have_devfunc) + { + const char *device_id0 = strchr(devinst_id, '\\'); + const char *instance_id0 = device_id0 ? strchr(device_id0 + 1, '\\') : NULL; + const char *instance_id = instance_id0 ? instance_id0 + 1 : NULL; + unsigned int devfn; + + if (instance_id) + { + if (fmt_validate(instance_id, strlen(instance_id), "BUS_##&DEV_##&FUNC_##") && + sscanf(instance_id, "BUS_%x&DEV_%x&FUNC_%x", bus, dev, func) == 3) + { + have_bus = TRUE; + have_devfunc = TRUE; + } + else if (seq_xdigit_validate(instance_id, 2, 2) && + sscanf(instance_id, "%2x", &devfn) == 1) + { + *dev = devfn >> 3; + *func = devfn & 0x7; + have_devfunc = TRUE; + } + } + } + + /* + * Virtual IRQ holder devices do not have assigned any bus/dev/func number and + * have "IRQHOLDER" in their Device Id part. So skip them. + */ + if (!have_bus && !have_devfunc && strncmp(devinst_id, "PCI\\IRQHOLDER\\", 14) == 0) + return FALSE; + + /* + * When some numbers cannot be retrieved via cfgmgr32 then set them to zeros + * to have structure initialized. It makes sense to report via libpci also + * such "incomplete" device as cfgmgr32 can provide additional information + * like device/vendor ids or assigned resources. + */ + if (!have_bus && !have_devfunc) + { + *bus = *dev = *func = 0; + a->warning("Cannot retrieve bus, device and function numbers for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + } + else if (!have_bus) + { + *bus = 0; + a->warning("Cannot retrieve bus number for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + } + else if (!have_devfunc) + { + *dev = *func = 0; + a->warning("Cannot retrieve device and function numbers for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + } + + return TRUE; +} + +static void +fill_data_from_string(struct pci_dev *d, const char *str) +{ + BOOL have_device_id; + BOOL have_vendor_id; + BOOL have_prog_if; + BOOL have_rev_id; + const char *endptr, *endptr2; + unsigned int hex; + int len; + + have_device_id = have_vendor_id = (d->known_fields & PCI_FILL_IDENT); + have_prog_if = have_rev_id = (d->known_fields & PCI_FILL_CLASS_EXT); + + while (1) + { + endptr = strchr(str, '&'); + endptr2 = strchr(str, '\\'); + if (endptr2 && (!endptr || endptr > endptr2)) + endptr = endptr2; + len = endptr ? endptr-str : (int)strlen(str); + + if (!have_vendor_id && + fmt_validate(str, len, "VEN_####") && + sscanf(str, "VEN_%x", &hex) == 1) + { + d->vendor_id = hex; + have_vendor_id = TRUE; + } + else if (!have_device_id && + fmt_validate(str, len, "DEV_####") && + sscanf(str, "DEV_%x", &hex) == 1) + { + d->device_id = hex; + have_device_id = TRUE; + } + else if (!(d->known_fields & PCI_FILL_SUBSYS) && + fmt_validate(str, len, "SUBSYS_########") && + sscanf(str, "SUBSYS_%x", &hex) == 1) + { + d->subsys_vendor_id = hex & 0xffff; + d->subsys_id = hex >> 16; + d->known_fields |= PCI_FILL_SUBSYS; + } + else if (!have_rev_id && + fmt_validate(str, len, "REV_##") && + sscanf(str, "REV_%x", &hex) == 1) + { + d->rev_id = hex; + have_rev_id = TRUE; + } + else if (!((d->known_fields & PCI_FILL_CLASS) && have_prog_if) && + (fmt_validate(str, len, "CC_####") || fmt_validate(str, len, "CC_######")) && + sscanf(str, "CC_%x", &hex) == 1) + { + if (len == 9) + { + if (!have_prog_if) + { + d->prog_if = hex & 0xff; + have_prog_if = TRUE; + } + hex >>= 8; + } + if (!(d->known_fields & PCI_FILL_CLASS)) + { + d->device_class = hex; + d->known_fields |= PCI_FILL_CLASS; + } + } + + if (!endptr || endptr == endptr2) + break; + + str = endptr + 1; + } + + if ((have_device_id || d->device_id) && (have_vendor_id || d->vendor_id)) + d->known_fields |= PCI_FILL_IDENT; + + if ((have_prog_if || d->prog_if) && (have_rev_id || d->rev_id)) + d->known_fields |= PCI_FILL_CLASS_EXT; +} + +static void +fill_data_from_devinst_id(struct pci_dev *d, DEVINSTID_A devinst_id) +{ + const char *device_id; + + device_id = strchr(devinst_id, '\\'); + if (!device_id) + return; + device_id++; + + /* + * Device Id part of Device Instance Id is in format: + * "VEN_####&DEV_####&SUBSYS_########&REV_##" + */ + fill_data_from_string(d, device_id); +} + +static void +fill_data_from_hardware_ids(struct pci_dev *d, DEVINST devinst, DEVINSTID_A devinst_id) +{ + ULONG reg_type, reg_size, reg_len; + struct pci_access *a = d->access; + char *hardware_ids = NULL; + const char *str; + CONFIGRET cr; + + reg_size = 0; + cr = CM_Get_DevNode_Registry_PropertyA(devinst, CM_DRP_HARDWAREID, ®_type, NULL, ®_size, 0); + if (cr != CR_SUCCESS && cr != CR_BUFFER_SMALL) + { + a->warning("Cannot retrieve hardware ids for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + return; + } + else if (reg_type != REG_MULTI_SZ && reg_type != REG_SZ) /* Older Windows versions return REG_SZ and new versions REG_MULTI_SZ. */ + { + a->warning("Cannot retrieve hardware ids for PCI device %s: Hardware ids are stored as unknown type 0x%lx.", devinst_id, reg_type); + return; + } + +retry: + /* + * Returned size is on older Windows versions without nul-term char. + * So explicitly increase size and fill nul-term byte. + */ + reg_size++; + hardware_ids = pci_malloc(a, reg_size); + reg_len = reg_size; + cr = CM_Get_DevNode_Registry_PropertyA(devinst, CM_DRP_HARDWAREID, ®_type, hardware_ids, ®_len, 0); + hardware_ids[reg_size - 1] = 0; + if (reg_len > reg_size) + { + pci_mfree(hardware_ids); + reg_size = reg_len; + goto retry; + } + else if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve hardware ids for PCI device %s: %s.", devinst_id, cr_strerror(cr)); + pci_mfree(hardware_ids); + return; + } + else if (reg_type != REG_MULTI_SZ && reg_type != REG_SZ) /* Older Windows versions return REG_SZ and new versions REG_MULTI_SZ. */ + { + a->warning("Cannot retrieve hardware ids for PCI device %s: Hardware ids are stored as unknown type 0x%lx.", devinst_id, reg_type); + pci_mfree(hardware_ids); + return; + } + + /* + * Hardware ids is nul-separated nul-term string list where each string has + * one of the following format: + * "PCI\\VEN_####&DEV_####&SUBSYS_########&REV_##" + * "PCI\\VEN_####&DEV_####&SUBSYS_########" + * "PCI\\VEN_####&DEV_####&REV_##&CC_####" + * "PCI\\VEN_####&DEV_####&CC_######" + * "PCI\\VEN_####&DEV_####&CC_####" + * "PCI\\VEN_####&DEV_####&REV_##" + * "PCI\\VEN_####&DEV_####" + */ + for (str = hardware_ids; *str != '\0'; str += strlen(str) + 1) + { + if (strncmp(str, "PCI\\", 4) != 0) + continue; + str += 4; + fill_data_from_string(d, str); + } + + pci_mfree(hardware_ids); +} + +static void +scan_devinst_id(struct pci_access *a, DEVINSTID_A devinst_id) +{ + unsigned int domain, bus, dev, func; + struct pci_dev *d; + DEVINST devinst; + CONFIGRET cr; + + cr = CM_Locate_DevNodeA(&devinst, devinst_id, CM_LOCATE_DEVNODE_NORMAL); + if (cr != CR_SUCCESS) + { + /* Do not show warning when device is not present (= does not match NORMAL flag). */ + if (cr != CR_NO_SUCH_DEVNODE) + a->warning("Cannot retrieve handle for device %s: %s.", devinst_id, cr_strerror(cr)); + return; + } + + /* get_device_location() returns FALSE if devinst is not real PCI device. */ + if (!get_device_location(a, devinst, devinst_id, &domain, &bus, &dev, &func)) + return; + + d = pci_get_dev(a, domain, bus, dev, func); + pci_link_dev(a, d); + if (!d->access->backend_data) + d->no_config_access = 1; + d->backend_data = (void *)devinst; + + /* Parse device id part of devinst id and fill details into pci_dev. */ + if (!a->buscentric) + fill_data_from_devinst_id(d, devinst_id); + + /* Retrieve hardware ids of devinst, parse them and fill details into pci_dev. */ + if (!a->buscentric) + fill_data_from_hardware_ids(d, devinst, devinst_id); + + if (!a->buscentric) + fill_resources(d, devinst, devinst_id); + + /* + * Set parent field to cfgmgr32 parent devinst handle and backend_data field to current + * devinst handle. At later stage in win32_cfgmgr32_scan() when all pci_dev + * devices are linked, change every devinst handle by pci_dev. + */ + if (!a->buscentric) + { + DEVINST parent_devinst; + if (CM_Get_Parent(&parent_devinst, devinst, 0) != CR_SUCCESS) + { + parent_devinst = 0; + a->warning("Cannot retrieve parent handle for device %s: %s.", devinst_id, cr_strerror(cr)); + } + d->parent = (void *)parent_devinst; + } +} + +static void +win32_cfgmgr32_scan(struct pci_access *a) +{ + ULONG devinst_id_list_size; + PCHAR devinst_id_list; + DEVINSTID_A devinst_id; + struct pci_dev *d; + CONFIGRET cr; + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + if (!resolve_cfgmgr32_functions()) + { + a->warning("Required cfgmgr32.dll functions are unavailable."); + return; + } +#endif + + /* + * Explicitly initialize size to zero as wine cfgmgr32 implementation does not + * support this API but returns CR_SUCCESS without touching size argument. + */ + devinst_id_list_size = 0; + cr = CM_Get_Device_ID_List_SizeA(&devinst_id_list_size, "PCI", CM_GETIDLIST_FILTER_ENUMERATOR); + if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve list of PCI devices: %s.", cr_strerror(cr)); + return; + } + else if (devinst_id_list_size <= 1) + { + a->warning("Cannot retrieve list of PCI devices: No device was found."); + return; + } + + devinst_id_list = pci_malloc(a, devinst_id_list_size); + cr = CM_Get_Device_ID_ListA("PCI", devinst_id_list, devinst_id_list_size, CM_GETIDLIST_FILTER_ENUMERATOR); + if (cr != CR_SUCCESS) + { + a->warning("Cannot retrieve list of PCI devices: %s.", cr_strerror(cr)); + pci_mfree(devinst_id_list); + return; + } + + /* Register pci_dev for each cfgmgr32 devinst handle. */ + for (devinst_id = devinst_id_list; *devinst_id; devinst_id += strlen(devinst_id) + 1) + scan_devinst_id(a, devinst_id); + + /* Fill all drivers. */ + if (!a->buscentric) + fill_drivers(a); + + /* Switch parent fields from cfgmgr32 devinst handle to pci_dev. */ + if (!a->buscentric) + { + struct pci_dev *d1, *d2; + for (d1 = a->devices; d1; d1 = d1->next) + { + for (d2 = a->devices; d2; d2 = d2->next) + if ((DEVINST)d1->parent == (DEVINST)d2->backend_data) + break; + d1->parent = d2; + if (d1->parent) + d1->known_fields |= PCI_FILL_PARENT; + } + } + + /* devinst stored in ->backend_data is not needed anymore, clear it. */ + for (d = a->devices; d; d = d->next) + d->backend_data = NULL; + + pci_mfree(devinst_id_list); +} + +static void +win32_cfgmgr32_config(struct pci_access *a) +{ + pci_define_param(a, "win32.cfgmethod", "auto", "PCI config space access method"); +} + +static int +win32_cfgmgr32_detect(struct pci_access *a) +{ + ULONG devinst_id_list_size; + CONFIGRET cr; + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + if (!resolve_cfgmgr32_functions()) + { + a->debug("Required cfgmgr32.dll functions are unavailable."); + return 0; + } +#endif + + /* + * Explicitly initialize size to zero as wine cfgmgr32 implementation does not + * support this API but returns CR_SUCCESS without touching size argument. + */ + devinst_id_list_size = 0; + cr = CM_Get_Device_ID_List_SizeA(&devinst_id_list_size, "PCI", CM_GETIDLIST_FILTER_ENUMERATOR); + if (cr != CR_SUCCESS) + { + a->debug("CM_Get_Device_ID_List_SizeA(\"PCI\"): %s.", cr_strerror(cr)); + return 0; + } + else if (devinst_id_list_size <= 1) + { + a->debug("CM_Get_Device_ID_List_SizeA(\"PCI\"): No device was found."); + return 0; + } + + return 1; +} + +static void +win32_cfgmgr32_fill_info(struct pci_dev *d, unsigned int flags) +{ + /* + * All available flags were filled by win32_cfgmgr32_scan(). + * Filling more flags is possible only from config space. + */ + if (!d->access->backend_data) + return; + + pci_generic_fill_info(d, flags); +} + +static int +win32_cfgmgr32_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + struct pci_access *a = d->access; + struct pci_access *acfg = a->backend_data; + struct pci_dev *dcfg = d->backend_data; + + if (!acfg) + return pci_emulated_read(d, pos, buf, len); + + if (!dcfg) + d->backend_data = dcfg = pci_get_dev(acfg, d->domain, d->bus, d->dev, d->func); + + return pci_read_block(dcfg, pos, buf, len); +} + +static int +win32_cfgmgr32_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + struct pci_access *a = d->access; + struct pci_access *acfg = a->backend_data; + struct pci_dev *dcfg = d->backend_data; + + if (!acfg) + return 0; + + if (!dcfg) + d->backend_data = dcfg = pci_get_dev(acfg, d->domain, d->bus, d->dev, d->func); + + return pci_write_block(dcfg, pos, buf, len); +} + +static void +win32_cfgmgr32_cleanup_dev(struct pci_dev *d) +{ + struct pci_dev *dcfg = d->backend_data; + + if (dcfg) + pci_free_dev(dcfg); +} + +static void +win32_cfgmgr32_init(struct pci_access *a) +{ + char *cfgmethod = pci_get_param(a, "win32.cfgmethod"); + struct pci_access *acfg; + + if (strcmp(cfgmethod, "") == 0 || + strcmp(cfgmethod, "auto") == 0) + { + acfg = pci_clone_access(a); + acfg->method = PCI_ACCESS_AUTO; + } + else if (strcmp(cfgmethod, "none") == 0 || + strcmp(cfgmethod, "win32-cfgmgr32") == 0) + { + if (a->writeable) + a->error("Write access requested but option win32.cfgmethod was not set."); + return; + } + else + { + int m = pci_lookup_method(cfgmethod); + if (m < 0) + a->error("Option win32.cfgmethod is set to an unknown access method \"%s\".", cfgmethod); + acfg = pci_clone_access(a); + acfg->method = m; + } + + a->debug("Loading config space access method...\n"); + if (!pci_init_internal(acfg, PCI_ACCESS_WIN32_CFGMGR32)) + { + pci_cleanup(acfg); + a->debug("Cannot find any working config space access method.\n"); + if (a->writeable) + a->error("Write access requested but no usable access method found."); + return; + } + + a->backend_data = acfg; +} + +static void +win32_cfgmgr32_cleanup(struct pci_access *a) +{ + struct pci_access *acfg = a->backend_data; + + if (acfg) + pci_cleanup(acfg); +} + +struct pci_methods pm_win32_cfgmgr32 = { + "win32-cfgmgr32", + "Win32 device listing via Configuration Manager", + win32_cfgmgr32_config, + win32_cfgmgr32_detect, + win32_cfgmgr32_init, + win32_cfgmgr32_cleanup, + win32_cfgmgr32_scan, + win32_cfgmgr32_fill_info, + win32_cfgmgr32_read, + win32_cfgmgr32_write, + NULL, /* read_vpd */ + NULL, /* init_dev */ + win32_cfgmgr32_cleanup_dev, +}; diff --git a/lib/win32-helpers.c b/lib/win32-helpers.c new file mode 100644 index 0000000..d370d5c --- /dev/null +++ b/lib/win32-helpers.c @@ -0,0 +1,1387 @@ +/* + * The PCI Library -- Win32 helper functions + * + * Copyright (c) 2023 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <windows.h> + +#include <stdio.h> /* for sprintf() */ + +#include "win32-helpers.h" + +/* Unfortunately i586-mingw32msvc toolchain does not provide this constant. */ +#ifndef PROCESS_QUERY_LIMITED_INFORMATION +#define PROCESS_QUERY_LIMITED_INFORMATION 0x1000 +#endif + +/* Unfortunately some toolchains do not provide this constant. */ +#ifndef SE_IMPERSONATE_NAME +#define SE_IMPERSONATE_NAME TEXT("SeImpersonatePrivilege") +#endif + +/* Unfortunately some toolchains do not provide these constants. */ +#ifndef SE_DACL_AUTO_INHERIT_REQ +#define SE_DACL_AUTO_INHERIT_REQ 0x0100 +#endif +#ifndef SE_SACL_AUTO_INHERIT_REQ +#define SE_SACL_AUTO_INHERIT_REQ 0x0200 +#endif +#ifndef SE_DACL_AUTO_INHERITED +#define SE_DACL_AUTO_INHERITED 0x0400 +#endif +#ifndef SE_SACL_AUTO_INHERITED +#define SE_SACL_AUTO_INHERITED 0x0800 +#endif + +/* + * These psapi functions are available in kernel32.dll library with K32 prefix + * on Windows 7 and higher systems. On older Windows systems these functions are + * available in psapi.dll libary without K32 prefix. So resolve pointers to + * these functions dynamically at runtime from the available system library. + * Function GetProcessImageFileNameW() is not available on Windows 2000 and + * older systems. + */ +typedef BOOL (WINAPI *EnumProcessesProt)(DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded); +typedef DWORD (WINAPI *GetProcessImageFileNameWProt)(HANDLE hProcess, LPWSTR lpImageFileName, DWORD nSize); +typedef DWORD (WINAPI *GetModuleFileNameExWProt)(HANDLE hProcess, HMODULE hModule, LPWSTR lpImageFileName, DWORD nSize); + +/* + * These aclapi function is available in advapi.dll library on Windows 2000 + * and higher systems. + */ +typedef BOOL (WINAPI *SetSecurityDescriptorControlProt)(PSECURITY_DESCRIPTOR pSecurityDescriptor, SECURITY_DESCRIPTOR_CONTROL ControlBitsOfInterest, SECURITY_DESCRIPTOR_CONTROL ControlBitsToSet); + +/* + * This errhandlingapi function is available in kernel32.dll library on + * Windows 7 and higher systems. + */ +typedef BOOL (WINAPI *SetThreadErrorModeProt)(DWORD dwNewMode, LPDWORD lpOldMode); + + +static DWORD +format_message_from_system(DWORD win32_error_id, DWORD lang_id, LPSTR buffer, DWORD size) +{ + return FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, win32_error_id, lang_id, buffer, size, NULL); +} + +const char * +win32_strerror(DWORD win32_error_id) +{ + /* + * Use static buffer which is large enough. + * Hopefully no Win32 API error message string is longer than 4 kB. + */ + static char buffer[4096]; + DWORD len; + + /* + * If it is possible show error messages in US English language. + * International Windows editions do not have to provide error + * messages in English language, so fallback to the language + * which system provides (neutral). + */ + len = format_message_from_system(win32_error_id, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), buffer, sizeof(buffer)); + if (!len) + len = format_message_from_system(win32_error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer)); + + /* FormatMessage() automatically appends ".\r\n" to the error message. */ + if (len && buffer[len-1] == '\n') + buffer[--len] = '\0'; + if (len && buffer[len-1] == '\r') + buffer[--len] = '\0'; + if (len && buffer[len-1] == '.') + buffer[--len] = '\0'; + + if (!len) + sprintf(buffer, "Unknown Win32 error %lu", win32_error_id); + + return buffer; +} + +BOOL +win32_is_non_nt_system(void) +{ + OSVERSIONINFOA version; + version.dwOSVersionInfoSize = sizeof(version); + return GetVersionExA(&version) && version.dwPlatformId < VER_PLATFORM_WIN32_NT; +} + +BOOL +win32_is_32bit_on_64bit_system(void) +{ + BOOL (WINAPI *MyIsWow64Process)(HANDLE, PBOOL); + HMODULE kernel32; + BOOL is_wow64; + + /* + * Check for 64-bit system via IsWow64Process() function exported + * from 32-bit kernel32.dll library available on the 64-bit systems. + * Resolve pointer to this function at runtime as this code path is + * primary running on 32-bit systems where are not available 64-bit + * functions. + */ + + kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (!kernel32) + return FALSE; + + MyIsWow64Process = (void *)GetProcAddress(kernel32, "IsWow64Process"); + if (!MyIsWow64Process) + return FALSE; + + if (!MyIsWow64Process(GetCurrentProcess(), &is_wow64)) + return FALSE; + + return is_wow64; +} + +BOOL +win32_is_32bit_on_win8_64bit_system(void) +{ +#ifdef _WIN64 + return FALSE; +#else + OSVERSIONINFOA version; + + /* Check for Windows 8 (NT 6.2). */ + version.dwOSVersionInfoSize = sizeof(version); + if (!GetVersionExA(&version) || + version.dwPlatformId != VER_PLATFORM_WIN32_NT || + version.dwMajorVersion < 6 || + (version.dwMajorVersion == 6 && version.dwMinorVersion < 2)) + return FALSE; + + return win32_is_32bit_on_64bit_system(); +#endif +} + +/* + * Change error mode of the current thread. If it is not possible then change + * error mode of the whole process. Always returns previous error mode. + */ +UINT +win32_change_error_mode(UINT new_mode) +{ + SetThreadErrorModeProt MySetThreadErrorMode = NULL; + HMODULE kernel32; + DWORD old_mode; + + /* + * Function SetThreadErrorMode() was introduced in Windows 7, so use + * GetProcAddress() for compatibility with older systems. + */ + kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (kernel32) + MySetThreadErrorMode = (SetThreadErrorModeProt)(LPVOID)GetProcAddress(kernel32, "SetThreadErrorMode"); + + if (MySetThreadErrorMode && + MySetThreadErrorMode(new_mode, &old_mode)) + return old_mode; + + /* + * Fallback to function SetErrorMode() which modifies error mode of the + * whole process and returns old mode. + */ + return SetErrorMode(new_mode); +} + +/* + * Check if the current thread has particular privilege in current active access + * token. Case when it not possible to determinate it (e.g. current thread does + * not have permission to open its own current active access token) is evaluated + * as thread does not have that privilege. + */ +BOOL +win32_have_privilege(LUID luid_privilege) +{ + PRIVILEGE_SET priv; + HANDLE token; + BOOL ret; + + /* + * If the current thread does not have active access token then thread + * uses primary process access token for all permission checks. + */ + if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) && + (GetLastError() != ERROR_NO_TOKEN || + !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))) + return FALSE; + + priv.PrivilegeCount = 1; + priv.Control = PRIVILEGE_SET_ALL_NECESSARY; + priv.Privilege[0].Luid = luid_privilege; + priv.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!PrivilegeCheck(token, &priv, &ret)) + return FALSE; + + return ret; +} + +/* + * Enable or disable particular privilege in specified access token. + * + * Note that it is not possible to disable privilege in access token with + * SE_PRIVILEGE_ENABLED_BY_DEFAULT attribute. This function does not check + * this case and incorrectly returns no error even when disabling failed. + * Rationale for this decision: Simplification of this function as WinAPI + * call AdjustTokenPrivileges() does not signal error in this case too. + */ +static BOOL +set_privilege(HANDLE token, LUID luid_privilege, BOOL enable) +{ + TOKEN_PRIVILEGES token_privileges; + + token_privileges.PrivilegeCount = 1; + token_privileges.Privileges[0].Luid = luid_privilege; + token_privileges.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; + + /* + * WinAPI function AdjustTokenPrivileges() success also when not all + * privileges were enabled. It is always required to check for failure + * via GetLastError() call. AdjustTokenPrivileges() always sets error + * also when it success, as opposite to other WinAPI functions. + */ + if (!AdjustTokenPrivileges(token, FALSE, &token_privileges, sizeof(token_privileges), NULL, NULL) || + GetLastError() != ERROR_SUCCESS) + return FALSE; + + return TRUE; +} + +/* + * Change access token for the current thread to new specified access token. + * Previously active access token is stored in old_token variable and can be + * used for reverting to this access token. It is set to NULL if the current + * thread previously used primary process access token. + */ +BOOL +win32_change_token(HANDLE new_token, HANDLE *old_token) +{ + HANDLE token; + + if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token)) + { + if (GetLastError() != ERROR_NO_TOKEN) + return FALSE; + token = NULL; + } + + if (!ImpersonateLoggedOnUser(new_token)) + { + if (token) + CloseHandle(token); + return FALSE; + } + + *old_token = token; + return TRUE; +} + +/* + * Change access token for the current thread to the primary process access + * token. This function fails also when the current thread already uses primary + * process access token. + */ +static BOOL +change_token_to_primary(HANDLE *old_token) +{ + HANDLE token; + + if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token)) + return FALSE; + + RevertToSelf(); + + *old_token = token; + return TRUE; +} + +/* + * Revert to the specified access token for the current thread. When access + * token is specified as NULL then revert to the primary process access token. + * Use to revert after win32_change_token() or change_token_to_primary() call. + */ +VOID +win32_revert_to_token(HANDLE token) +{ + /* + * If SetThreadToken() call fails then there is no option to revert to + * the specified previous thread access token. So in this case revert to + * the primary process access token. + */ + if (!token || !SetThreadToken(NULL, token)) + RevertToSelf(); + if (token) + CloseHandle(token); +} + +/* + * Enable particular privilege for the current thread. And set method how to + * revert this privilege (if to revert whole token or only privilege). + */ +BOOL +win32_enable_privilege(LUID luid_privilege, HANDLE *revert_token, BOOL *revert_only_privilege) +{ + HANDLE thread_token; + HANDLE new_token; + + if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &thread_token)) + { + if (set_privilege(thread_token, luid_privilege, TRUE)) + { + /* + * Indicate that correct revert method is just to + * disable privilege in access token. + */ + if (revert_token && revert_only_privilege) + { + *revert_token = thread_token; + *revert_only_privilege = TRUE; + } + else + { + CloseHandle(thread_token); + } + return TRUE; + } + CloseHandle(thread_token); + /* + * If enabling privilege failed then try to enable it via + * primary process access token. + */ + } + + /* + * If the current thread has already active thread access token then + * open it with just impersonate right as it would be used only for + * future revert. + */ + if (revert_token && revert_only_privilege) + { + if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &thread_token)) + { + if (GetLastError() != ERROR_NO_TOKEN) + return FALSE; + thread_token = NULL; + } + + /* + * If current thread has no access token (and uses primary + * process access token) or it does not have permission to + * adjust privileges or it does not have specified privilege + * then create a copy of the primary process access token, + * assign it for the current thread (= impersonate self) + * and then try adjusting privilege again. + */ + if (!ImpersonateSelf(SecurityImpersonation)) + { + if (thread_token) + CloseHandle(thread_token); + return FALSE; + } + } + + if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &new_token)) + { + /* thread_token is set only when we were asked for revert method. */ + if (revert_token && revert_only_privilege) + win32_revert_to_token(thread_token); + return FALSE; + } + + if (!set_privilege(new_token, luid_privilege, TRUE)) + { + CloseHandle(new_token); + /* thread_token is set only when we were asked for revert method. */ + if (revert_token && revert_only_privilege) + win32_revert_to_token(thread_token); + return FALSE; + } + + /* + * Indicate that correct revert method is to change to the previous + * access token. Either to the primary process access token or to the + * previous thread access token. + */ + if (revert_token && revert_only_privilege) + { + *revert_token = thread_token; + *revert_only_privilege = FALSE; + } + return TRUE; +} + +/* + * Revert particular privilege for the current thread was previously enabled by + * win32_enable_privilege() call. Either disable privilege in specified access token + * or revert to previous access token. + */ +VOID +win32_revert_privilege(LUID luid_privilege, HANDLE revert_token, BOOL revert_only_privilege) +{ + if (revert_only_privilege) + { + set_privilege(revert_token, luid_privilege, FALSE); + CloseHandle(revert_token); + } + else + { + win32_revert_to_token(revert_token); + } +} + +/* + * Return owner of the access token used by the current thread. Buffer for + * returned owner needs to be released by LocalFree() call. + */ +static TOKEN_OWNER * +get_current_token_owner(VOID) +{ + HANDLE token; + DWORD length; + TOKEN_OWNER *owner; + + /* + * If the current thread does not have active access token then thread + * uses primary process access token for all permission checks. + */ + if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) && + (GetLastError() != ERROR_NO_TOKEN || + !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))) + return NULL; + + if (!GetTokenInformation(token, TokenOwner, NULL, 0, &length) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + CloseHandle(token); + return NULL; + } + +retry: + owner = (TOKEN_OWNER *)LocalAlloc(LPTR, length); + if (!owner) + { + CloseHandle(token); + return NULL; + } + + if (!GetTokenInformation(token, TokenOwner, owner, length, &length)) + { + /* + * Length of token owner (SID) buffer between two get calls may + * changes (e.g. by another thread of process), so retry. + */ + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + LocalFree(owner); + goto retry; + } + LocalFree(owner); + CloseHandle(token); + return NULL; + } + + CloseHandle(token); + return owner; +} + +/* + * Create a new security descriptor in absolute form from relative form. + * Newly created security descriptor in absolute form is stored in linear buffer. + */ +static PSECURITY_DESCRIPTOR +create_relsd_from_abssd(PSECURITY_DESCRIPTOR rel_security_descriptor) +{ + PBYTE abs_security_descriptor_buffer; + DWORD abs_security_descriptor_size=0, abs_dacl_size=0, abs_sacl_size=0, abs_owner_size=0, abs_primary_group_size=0; + + if (!MakeAbsoluteSD(rel_security_descriptor, + NULL, &abs_security_descriptor_size, + NULL, &abs_dacl_size, + NULL, &abs_sacl_size, + NULL, &abs_owner_size, + NULL, &abs_primary_group_size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return NULL; + + abs_security_descriptor_buffer = (PBYTE)LocalAlloc(LPTR, abs_security_descriptor_size+abs_dacl_size+abs_sacl_size+abs_owner_size+abs_primary_group_size); + if (!abs_security_descriptor_buffer) + return NULL; + + if (!MakeAbsoluteSD(rel_security_descriptor, + (PSECURITY_DESCRIPTOR)abs_security_descriptor_buffer, &abs_security_descriptor_size, + (PACL)(abs_security_descriptor_buffer+abs_security_descriptor_size), &abs_dacl_size, + (PACL)(abs_security_descriptor_buffer+abs_security_descriptor_size+abs_dacl_size), &abs_sacl_size, + (PSID)(abs_security_descriptor_buffer+abs_security_descriptor_size+abs_dacl_size+abs_sacl_size), &abs_owner_size, + (PSID)(abs_security_descriptor_buffer+abs_security_descriptor_size+abs_dacl_size+abs_sacl_size+abs_owner_size), &abs_primary_group_size)) + return NULL; + + return (PSECURITY_DESCRIPTOR)abs_security_descriptor_buffer; +} + +/* + * Prepare security descriptor obtained by GetKernelObjectSecurity() so it can be + * passed to SetKernelObjectSecurity() as identity operation. It modifies control + * flags of security descriptor, which is needed for Windows 2000 and new. + */ +static BOOL +prepare_security_descriptor_for_set_operation(PSECURITY_DESCRIPTOR security_descriptor) +{ + SetSecurityDescriptorControlProt MySetSecurityDescriptorControl; + SECURITY_DESCRIPTOR_CONTROL bits_mask; + SECURITY_DESCRIPTOR_CONTROL bits_set; + SECURITY_DESCRIPTOR_CONTROL control; + OSVERSIONINFO version; + HMODULE advapi32; + DWORD revision; + + /* + * SE_DACL_AUTO_INHERITED and SE_SACL_AUTO_INHERITED are flags introduced in + * Windows 2000 to control client-side automatic inheritance (client - user + * process - is responsible for propagating inherited ACEs to subobjects). + * To prevent applications which do not understand client-side automatic + * inheritance (applications created prior Windows 2000 or which use low + * level API like SetKernelObjectSecurity()) to unintentionally set those + * SE_DACL_AUTO_INHERITED and SE_SACL_AUTO_INHERITED control flags when + * coping them from other security descriptor. + * + * As we are not modifying existing ACEs, we are compatible with Windows 2000 + * client-side automatic inheritance model and therefore prepare security + * descriptor for SetKernelObjectSecurity() to not clear existing automatic + * inheritance control flags. + * + * Control flags SE_DACL_AUTO_INHERITED and SE_SACL_AUTO_INHERITED are set + * into security object only when they are set together with set-only flags + * SE_DACL_AUTO_INHERIT_REQ and SE_SACL_AUTO_INHERIT_REQ. Those flags are + * never received by GetKernelObjectSecurity() and are just commands for + * SetKernelObjectSecurity() how to interpret SE_DACL_AUTO_INHERITED and + * SE_SACL_AUTO_INHERITED flags. + * + * Function symbol SetSecurityDescriptorControl is not available in the + * older versions of advapi32.dll library, so resolve it at runtime. + */ + + version.dwOSVersionInfoSize = sizeof(version); + if (!GetVersionEx(&version) || + version.dwPlatformId != VER_PLATFORM_WIN32_NT || + version.dwMajorVersion < 5) + return TRUE; + + if (!GetSecurityDescriptorControl(security_descriptor, &control, &revision)) + return FALSE; + + bits_mask = 0; + bits_set = 0; + + if (control & SE_DACL_AUTO_INHERITED) + { + bits_mask |= SE_DACL_AUTO_INHERIT_REQ; + bits_set |= SE_DACL_AUTO_INHERIT_REQ; + } + + if (control & SE_SACL_AUTO_INHERITED) + { + bits_mask |= SE_SACL_AUTO_INHERIT_REQ; + bits_set |= SE_SACL_AUTO_INHERIT_REQ; + } + + if (!bits_mask) + return TRUE; + + advapi32 = GetModuleHandle(TEXT("advapi32.dll")); + if (!advapi32) + return FALSE; + + MySetSecurityDescriptorControl = (SetSecurityDescriptorControlProt)(LPVOID)GetProcAddress(advapi32, "SetSecurityDescriptorControl"); + if (!MySetSecurityDescriptorControl) + return FALSE; + + if (!MySetSecurityDescriptorControl(security_descriptor, bits_mask, bits_set)) + return FALSE; + + return TRUE; +} + +/* + * Grant particular permissions in the primary access token of the specified + * process for the owner of current thread token and set old DACL of the + * process access token for reverting permissions. Security descriptor is + * just memory buffer for old DACL. + */ +static BOOL +grant_process_token_dacl_permissions(HANDLE process, DWORD permissions, HANDLE *token, PSECURITY_DESCRIPTOR *old_security_descriptor) +{ + TOKEN_OWNER *owner; + PACL old_dacl; + BOOL old_dacl_present; + BOOL old_dacl_defaulted; + PACL new_dacl; + WORD new_dacl_size; + PSECURITY_DESCRIPTOR new_security_descriptor; + DWORD length; + + owner = get_current_token_owner(); + if (!owner) + return FALSE; + + /* + * READ_CONTROL is required for GetSecurityInfo(DACL_SECURITY_INFORMATION) + * and WRITE_DAC is required for SetSecurityInfo(DACL_SECURITY_INFORMATION). + */ + if (!OpenProcessToken(process, READ_CONTROL | WRITE_DAC, token)) + { + LocalFree(owner); + return FALSE; + } + + if (!GetKernelObjectSecurity(*token, DACL_SECURITY_INFORMATION, NULL, 0, &length) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + +retry: + *old_security_descriptor = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, length); + if (!*old_security_descriptor) + { + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + + if (!GetKernelObjectSecurity(*token, DACL_SECURITY_INFORMATION, *old_security_descriptor, length, &length)) + { + /* + * Length of the security descriptor between two get calls + * may changes (e.g. by another thread of process), so retry. + */ + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + LocalFree(*old_security_descriptor); + goto retry; + } + LocalFree(*old_security_descriptor); + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + + if (!prepare_security_descriptor_for_set_operation(*old_security_descriptor)) + { + LocalFree(*old_security_descriptor); + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + + /* Retrieve the current DACL from security descriptor including present and defaulted properties. */ + if (!GetSecurityDescriptorDacl(*old_security_descriptor, &old_dacl_present, &old_dacl, &old_dacl_defaulted)) + { + LocalFree(*old_security_descriptor); + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + + /* + * If DACL is not present then system grants full access to everyone. It this + * case do not modify DACL as it just adds one ACL allow rule for us, which + * automatically disallow access to anybody else which had access before. + */ + if (!old_dacl_present || !old_dacl) + { + LocalFree(*old_security_descriptor); + LocalFree(owner); + *old_security_descriptor = NULL; + return TRUE; + } + + /* Create new DACL which would be copy of the current old one. */ + new_dacl_size = old_dacl->AclSize + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(owner->Owner) - sizeof(DWORD); + new_dacl = (PACL)LocalAlloc(LPTR, new_dacl_size); + if (!new_dacl) + { + LocalFree(*old_security_descriptor); + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + + /* + * Initialize new DACL structure to the same format as was the old one. + * Set new explicit access for the owner of the current thread access + * token with non-inherited granting access to specified permissions. + * This permission is added in the first ACE, so has the highest priority. + */ + if (!InitializeAcl(new_dacl, new_dacl_size, old_dacl->AclRevision) || + !AddAccessAllowedAce(new_dacl, ACL_REVISION2, permissions, owner->Owner)) + { + LocalFree(new_dacl); + LocalFree(*old_security_descriptor); + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + + /* + * Now (after setting our new permissions) append all ACE entries from the + * old DACL to the new DACL, which preserve all other existing permissions. + */ + if (old_dacl->AceCount > 0) + { + WORD ace_index; + LPVOID ace; + + for (ace_index = 0; ace_index < old_dacl->AceCount; ace_index++) + { + if (!GetAce(old_dacl, ace_index, &ace) || + !AddAce(new_dacl, old_dacl->AclRevision, MAXDWORD, ace, ((PACE_HEADER)ace)->AceSize)) + { + LocalFree(new_dacl); + LocalFree(*old_security_descriptor); + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + } + } + + /* + * Create copy of the old security descriptor, so we can modify its DACL. + * Function SetSecurityDescriptorDacl() works only with security descriptors + * in absolute format. So use our helper function create_relsd_from_abssd() + * for converting security descriptor from relative format (which is returned + * by GetKernelObjectSecurity() function) to the absolute format. + */ + new_security_descriptor = create_relsd_from_abssd(*old_security_descriptor); + if (!new_security_descriptor) + { + LocalFree(new_dacl); + LocalFree(*old_security_descriptor); + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + + /* + * In the new security descriptor replace old DACL by the new DACL (which has + * new permissions) and then set this new security descriptor to the token, + * so token would have new access permissions. + */ + if (!SetSecurityDescriptorDacl(new_security_descriptor, TRUE, new_dacl, FALSE) || + !SetKernelObjectSecurity(*token, DACL_SECURITY_INFORMATION, new_security_descriptor)) + { + LocalFree(new_security_descriptor); + LocalFree(new_dacl); + LocalFree(*old_security_descriptor); + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + + LocalFree(new_security_descriptor); + LocalFree(new_dacl); + LocalFree(owner); + return TRUE; +} + +/* + * Revert particular granted permissions in specified access token done by + * grant_process_token_dacl_permissions() call. + */ +static VOID +revert_token_dacl_permissions(HANDLE token, PSECURITY_DESCRIPTOR old_security_descriptor) +{ + SetKernelObjectSecurity(token, DACL_SECURITY_INFORMATION, old_security_descriptor); + LocalFree(old_security_descriptor); + CloseHandle(token); +} + +/* + * Open process handle specified by the process id with the query right and + * optionally also with vm read right. + */ +static HANDLE +open_process_for_query(DWORD pid, BOOL with_vm_read) +{ + BOOL revert_only_privilege; + LUID luid_debug_privilege; + OSVERSIONINFO version; + DWORD process_right; + HANDLE revert_token; + HANDLE process; + + /* + * Some processes on Windows Vista and higher systems can be opened only + * with PROCESS_QUERY_LIMITED_INFORMATION right. This right is enough + * for accessing primary process token. But this right is not supported + * on older pre-Vista systems. When the current thread on these older + * systems does not have Debug privilege then OpenProcess() fails with + * ERROR_ACCESS_DENIED. If the current thread has Debug privilege then + * OpenProcess() success and returns handle to requested process. + * Problem is that this handle does not have PROCESS_QUERY_INFORMATION + * right and so cannot be used for accessing primary process token + * on those older systems. Moreover it has zero rights and therefore + * such handle is fully useless. So never try to use open process with + * PROCESS_QUERY_LIMITED_INFORMATION right on older systems than + * Windows Vista (NT 6.0). + */ + version.dwOSVersionInfoSize = sizeof(version); + if (GetVersionEx(&version) && + version.dwPlatformId == VER_PLATFORM_WIN32_NT && + version.dwMajorVersion >= 6) + process_right = PROCESS_QUERY_LIMITED_INFORMATION; + else + process_right = PROCESS_QUERY_INFORMATION; + + if (with_vm_read) + process_right |= PROCESS_VM_READ; + + process = OpenProcess(process_right, FALSE, pid); + if (process) + return process; + + /* + * It is possible to open only processes to which owner of the current + * thread access token has permissions. For opening other processing it + * is required to have Debug privilege enabled. By default local + * administrators have this privilege, but it is disabled. So try to + * enable it and then try to open process again. + */ + + if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid_debug_privilege)) + return NULL; + + if (!win32_enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege)) + return NULL; + + process = OpenProcess(process_right, FALSE, pid); + + win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); + + return process; +} + +/* + * Check if process image path name (wide string) matches exe file name + * (7-bit ASCII string). Do case-insensitive string comparison. Process + * image path name can be in any namespace format (DOS, Win32, UNC, ...). + */ +static BOOL +check_process_name(LPCWSTR path, DWORD path_length, LPCSTR exe_file) +{ + DWORD exe_file_length; + WCHAR c1; + UCHAR c2; + DWORD i; + + exe_file_length = 0; + while (exe_file[exe_file_length] != '\0') + exe_file_length++; + + /* Path must have backslash before exe file name. */ + if (exe_file_length >= path_length || + path[path_length-exe_file_length-1] != L'\\') + return FALSE; + + for (i = 0; i < exe_file_length; i++) + { + c1 = path[path_length-exe_file_length+i]; + c2 = exe_file[i]; + /* + * Input string for comparison is 7-bit ASCII and file name part + * of path must not contain backslash as it is path separator. + */ + if (c1 >= 0x80 || c2 >= 0x80 || c1 == L'\\') + return FALSE; + if (c1 >= L'a' && c1 <= L'z') + c1 -= L'a' - L'A'; + if (c2 >= 'a' && c2 <= 'z') + c2 -= 'a' - 'A'; + if (c1 != c2) + return FALSE; + } + + return TRUE; +} + +/* Open process handle with the query right specified by process exe file. */ +HANDLE +win32_find_and_open_process_for_query(LPCSTR exe_file) +{ + GetProcessImageFileNameWProt MyGetProcessImageFileNameW; + GetModuleFileNameExWProt MyGetModuleFileNameExW; + EnumProcessesProt MyEnumProcesses; + HMODULE kernel32, psapi; + UINT prev_error_mode; + DWORD partial_retry; + BOOL found_process; + DWORD size, length; + DWORD *processes; + HANDLE process; + LPWSTR path; + DWORD error; + DWORD count; + DWORD i; + + psapi = NULL; + kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (!kernel32) + return NULL; + + /* + * On Windows 7 and higher systems these functions are available in + * kernel32.dll library with K32 prefix. + */ + MyGetModuleFileNameExW = NULL; + MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(kernel32, "K32GetProcessImageFileNameW"); + MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(kernel32, "K32EnumProcesses"); + if (!MyGetProcessImageFileNameW || !MyEnumProcesses) + { + /* + * On older NT-based systems these functions are available in + * psapi.dll library without K32 prefix. + */ + prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS); + psapi = LoadLibrary(TEXT("psapi.dll")); + win32_change_error_mode(prev_error_mode); + + if (!psapi) + return NULL; + + /* + * Function GetProcessImageFileNameW() is available in + * Windows XP and higher systems. On older versions is + * available function GetModuleFileNameExW(). + */ + MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(psapi, "GetProcessImageFileNameW"); + MyGetModuleFileNameExW = (GetModuleFileNameExWProt)(LPVOID)GetProcAddress(psapi, "GetModuleFileNameExW"); + MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(psapi, "EnumProcesses"); + if ((!MyGetProcessImageFileNameW && !MyGetModuleFileNameExW) || !MyEnumProcesses) + { + FreeLibrary(psapi); + return NULL; + } + } + + /* Make initial buffer size for 1024 processes. */ + size = 1024 * sizeof(*processes); + +retry: + processes = (DWORD *)LocalAlloc(LPTR, size); + if (!processes) + { + if (psapi) + FreeLibrary(psapi); + return NULL; + } + + if (!MyEnumProcesses(processes, size, &length)) + { + LocalFree(processes); + if (psapi) + FreeLibrary(psapi); + return NULL; + } + else if (size == length) + { + /* + * There is no indication given when the buffer is too small to + * store all process identifiers. Therefore if returned length + * is same as buffer size there can be more processes. Call + * again with larger buffer. + */ + LocalFree(processes); + size *= 2; + goto retry; + } + + process = NULL; + count = length / sizeof(*processes); + + for (i = 0; i < count; i++) + { + /* Skip System Idle Process. */ + if (processes[i] == 0) + continue; + + /* + * Function GetModuleFileNameExW() requires additional + * PROCESS_VM_READ right as opposite to function + * GetProcessImageFileNameW() which does not need it. + */ + process = open_process_for_query(processes[i], MyGetProcessImageFileNameW ? FALSE : TRUE); + if (!process) + continue; + + /* + * Set initial buffer size to 256 (wide) characters. + * Final path length on the modern NT-based systems can be also larger. + */ + size = 256; + found_process = FALSE; + partial_retry = 0; + +retry_path: + path = (LPWSTR)LocalAlloc(LPTR, size * sizeof(*path)); + if (!path) + goto end_path; + + if (MyGetProcessImageFileNameW) + length = MyGetProcessImageFileNameW(process, path, size); + else + length = MyGetModuleFileNameExW(process, NULL, path, size); + + error = GetLastError(); + + /* + * GetModuleFileNameEx() returns zero and signal error ERROR_PARTIAL_COPY + * when remote process is in the middle of updating its module table. + * Sleep 10 ms and try again, max 10 attempts. + */ + if (!MyGetProcessImageFileNameW) + { + if (length == 0 && error == ERROR_PARTIAL_COPY && partial_retry++ < 10) + { + Sleep(10); + goto retry_path; + } + partial_retry = 0; + } + + /* + * When buffer is too small then function GetModuleFileNameEx() returns + * its size argument on older systems (Windows XP) or its size minus + * argument one on new systems (Windows 10) without signalling any error. + * Function GetProcessImageFileNameW() on the other hand returns zero + * value and signals error ERROR_INSUFFICIENT_BUFFER. So in all these + * cases call function again with larger buffer. + */ + + if (MyGetProcessImageFileNameW && length == 0 && error != ERROR_INSUFFICIENT_BUFFER) + goto end_path; + + if ((MyGetProcessImageFileNameW && length == 0) || + (!MyGetProcessImageFileNameW && (length == size || length == size-1))) + { + LocalFree(path); + size *= 2; + goto retry_path; + } + + if (length && check_process_name(path, length, exe_file)) + found_process = TRUE; + +end_path: + if (path) + { + LocalFree(path); + path = NULL; + } + + if (found_process) + break; + + CloseHandle(process); + process = NULL; + } + + LocalFree(processes); + + if (psapi) + FreeLibrary(psapi); + + return process; +} + +/* + * Try to open primary access token of the particular process with specified + * rights. Before opening access token try to adjust DACL permissions of the + * primary process access token, so following open does not fail on error + * related to no open permissions. Revert DACL permissions after open attempt. + * As following steps are not atomic, try to execute them more times in case + * of possible race conditions caused by other threads or processes. + */ +static HANDLE +try_grant_permissions_and_open_process_token(HANDLE process, DWORD rights) +{ + PSECURITY_DESCRIPTOR old_security_descriptor; + HANDLE grant_token; + HANDLE token; + DWORD retry; + DWORD error; + + /* + * This code is not atomic. Between grant and open calls can other + * thread or process change or revert permissions. So try to execute + * it more times. + */ + for (retry = 0; retry < 10; retry++) + { + if (!grant_process_token_dacl_permissions(process, rights, &grant_token, &old_security_descriptor)) + return NULL; + if (!OpenProcessToken(process, rights, &token)) + { + token = NULL; + error = GetLastError(); + } + if (old_security_descriptor) + revert_token_dacl_permissions(grant_token, old_security_descriptor); + if (token) + return token; + else if (error != ERROR_ACCESS_DENIED) + return NULL; + } + + return NULL; +} + +/* + * Open primary access token of particular process handle with specified rights. + * If permissions for specified rights are missing then try to grant them. + */ +HANDLE +win32_open_process_token_with_rights(HANDLE process, DWORD rights) +{ + HANDLE old_token; + HANDLE token; + + /* First try to open primary access token of process handle directly. */ + if (OpenProcessToken(process, rights, &token)) + return token; + + /* + * If opening failed then it means that owner of the current thread + * access token does not have permission for it. Try it again with + * primary process access token. + */ + if (change_token_to_primary(&old_token)) + { + if (!OpenProcessToken(process, rights, &token)) + token = NULL; + win32_revert_to_token(old_token); + if (token) + return token; + } + + /* + * If opening is still failing then try to grant specified permissions + * for the current thread and try to open it again. + */ + token = try_grant_permissions_and_open_process_token(process, rights); + if (token) + return token; + + /* + * And if it is still failing then try it again with granting + * permissions for the primary process token of the current process. + */ + if (change_token_to_primary(&old_token)) + { + token = try_grant_permissions_and_open_process_token(process, rights); + win32_revert_to_token(old_token); + if (token) + return token; + } + + /* + * TODO: Sorry, no other option for now... + * It could be possible to use Take Ownership Name privilege to + * temporary change token owner of specified process to the owner of + * the current thread token, grant permissions for current thread in + * that process token, change ownership back to original one, open + * that process token and revert granted permissions. But this is + * not implemented yet. + */ + return NULL; +} + +/* + * Call supplied function with its argument and if it fails with + * ERROR_PRIVILEGE_NOT_HELD then try to enable Tcb privilege and + * call function with its argument again. + */ +BOOL +win32_call_func_with_tcb_privilege(BOOL (*function)(LPVOID), LPVOID argument) +{ + LUID luid_tcb_privilege; + LUID luid_impersonate_privilege; + + HANDLE revert_token_tcb_privilege; + BOOL revert_only_tcb_privilege; + + HANDLE revert_token_impersonate_privilege; + BOOL revert_only_impersonate_privilege; + + BOOL impersonate_privilege_enabled; + + BOOL revert_to_old_token; + HANDLE old_token; + + HANDLE lsass_process; + HANDLE lsass_token; + + BOOL ret; + + impersonate_privilege_enabled = FALSE; + revert_to_old_token = FALSE; + lsass_token = NULL; + old_token = NULL; + + /* Call supplied function. */ + ret = function(argument); + if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD) + goto ret; + + /* + * If function call failed with ERROR_PRIVILEGE_NOT_HELD + * error then it means that the current thread token does not have + * Tcb privilege enabled. Try to enable it. + */ + + if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &luid_tcb_privilege)) + goto err_privilege_not_held; + + /* + * If the current thread has already Tcb privilege enabled then there + * is some additional unhanded restriction. + */ + if (win32_have_privilege(luid_tcb_privilege)) + goto err_privilege_not_held; + + /* Try to enable Tcb privilege and try function call again. */ + if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) + { + ret = function(argument); + win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); + goto ret; + } + + /* + * If enabling of Tcb privilege failed then it means that current thread + * does not have this privilege. But current process may have it. So try it + * again with primary process access token. + */ + + /* + * If system supports Impersonate privilege (Windows 2000 SP4 or higher) then + * all future actions in this function require this Impersonate privilege. + * So try to enable it in case it is currently disabled. + */ + if (LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid_impersonate_privilege) && + !win32_have_privilege(luid_impersonate_privilege)) + { + /* + * If current thread does not have Impersonate privilege enabled + * then first try to enable it just for the current thread. If + * it is not possible to enable it just for the current thread + * then try it to enable globally for whole process (which + * affects all process threads). Both actions will be reverted + * at the end of this function. + */ + if (win32_enable_privilege(luid_impersonate_privilege, &revert_token_impersonate_privilege, &revert_only_impersonate_privilege)) + { + impersonate_privilege_enabled = TRUE; + } + else if (win32_enable_privilege(luid_impersonate_privilege, NULL, NULL)) + { + impersonate_privilege_enabled = TRUE; + revert_token_impersonate_privilege = NULL; + revert_only_impersonate_privilege = TRUE; + } + else + { + goto err_privilege_not_held; + } + + /* + * Now when Impersonate privilege is enabled, try to enable Tcb + * privilege again. Enabling other privileges for the current + * thread requires Impersonate privilege, so enabling Tcb again + * could now pass. + */ + if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) + { + ret = function(argument); + win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); + goto ret; + } + } + + /* + * If enabling Tcb privilege failed then it means that the current + * thread access token does not have this privilege or does not + * have permission to adjust privileges. + * + * Try to use more privileged token from Local Security Authority + * Subsystem Service process (lsass.exe) which has Tcb privilege. + * Retrieving this more privileged token is possible for local + * administrators (unless it was disabled by local administrators). + */ + + lsass_process = win32_find_and_open_process_for_query("lsass.exe"); + if (!lsass_process) + goto err_privilege_not_held; + + /* + * Open primary lsass.exe process access token with query and duplicate + * rights. Just these two rights are required for impersonating other + * primary process token (impersonate right is really not required!). + */ + lsass_token = win32_open_process_token_with_rights(lsass_process, TOKEN_QUERY | TOKEN_DUPLICATE); + + CloseHandle(lsass_process); + + if (!lsass_token) + goto err_privilege_not_held; + + /* + * After successful open of the primary lsass.exe process access token, + * assign its copy for the current thread. + */ + if (!win32_change_token(lsass_token, &old_token)) + goto err_privilege_not_held; + + revert_to_old_token = TRUE; + + ret = function(argument); + if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD) + goto ret; + + /* + * Now current thread is not using primary process token anymore + * but is using custom access token. There is no need to revert + * enabled Tcb privilege as the whole custom access token would + * be reverted. So there is no need to setup revert method for + * enabling privilege. + */ + if (win32_have_privilege(luid_tcb_privilege) || + !win32_enable_privilege(luid_tcb_privilege, NULL, NULL)) + goto err_privilege_not_held; + + ret = function(argument); + goto ret; + +err_privilege_not_held: + SetLastError(ERROR_PRIVILEGE_NOT_HELD); + ret = FALSE; + goto ret; + +ret: + if (revert_to_old_token) + win32_revert_to_token(old_token); + + if (impersonate_privilege_enabled) + win32_revert_privilege(luid_impersonate_privilege, revert_token_impersonate_privilege, revert_only_impersonate_privilege); + + if (lsass_token) + CloseHandle(lsass_token); + + return ret; +} diff --git a/lib/win32-helpers.h b/lib/win32-helpers.h new file mode 100644 index 0000000..c415439 --- /dev/null +++ b/lib/win32-helpers.h @@ -0,0 +1,13 @@ +const char *win32_strerror(DWORD win32_error_id); +BOOL win32_is_non_nt_system(void); +BOOL win32_is_32bit_on_64bit_system(void); +BOOL win32_is_32bit_on_win8_64bit_system(void); +UINT win32_change_error_mode(UINT new_mode); +BOOL win32_have_privilege(LUID luid_privilege); +BOOL win32_enable_privilege(LUID luid_privilege, HANDLE *revert_token, BOOL *revert_only_privilege); +VOID win32_revert_privilege(LUID luid_privilege, HANDLE revert_token, BOOL revert_only_privilege); +BOOL win32_change_token(HANDLE new_token, HANDLE *old_token); +VOID win32_revert_to_token(HANDLE token); +HANDLE win32_find_and_open_process_for_query(LPCSTR exe_file); +HANDLE win32_open_process_token_with_rights(HANDLE process, DWORD rights); +BOOL win32_call_func_with_tcb_privilege(BOOL (*function)(LPVOID), LPVOID argument); diff --git a/lib/win32-kldbg.c b/lib/win32-kldbg.c new file mode 100644 index 0000000..33f5751 --- /dev/null +++ b/lib/win32-kldbg.c @@ -0,0 +1,731 @@ +/* + * The PCI Library -- PCI config space access using Kernel Local Debugging Driver + * + * Copyright (c) 2022 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <windows.h> +#include <winioctl.h> + +#include <stdio.h> /* for sprintf() */ +#include <string.h> /* for memset() and memcpy() */ + +#include "internal.h" +#include "win32-helpers.h" + +#ifndef ERROR_NOT_FOUND +#define ERROR_NOT_FOUND 1168 +#endif + +#ifndef LOAD_LIBRARY_AS_IMAGE_RESOURCE +#define LOAD_LIBRARY_AS_IMAGE_RESOURCE 0x20 +#endif +#ifndef LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE +#define LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE 0x40 +#endif + +#ifndef IOCTL_KLDBG +#define IOCTL_KLDBG CTL_CODE(FILE_DEVICE_UNKNOWN, 0x1, METHOD_NEITHER, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#endif + +#ifndef BUS_DATA_TYPE +#define BUS_DATA_TYPE LONG +#endif +#ifndef PCIConfiguration +#define PCIConfiguration (BUS_DATA_TYPE)4 +#endif + +#ifndef SYSDBG_COMMAND +#define SYSDBG_COMMAND ULONG +#endif +#ifndef SysDbgReadBusData +#define SysDbgReadBusData (SYSDBG_COMMAND)18 +#endif +#ifndef SysDbgWriteBusData +#define SysDbgWriteBusData (SYSDBG_COMMAND)19 +#endif + +#ifndef SYSDBG_BUS_DATA +typedef struct _SYSDBG_BUS_DATA { + ULONG Address; + PVOID Buffer; + ULONG Request; + BUS_DATA_TYPE BusDataType; + ULONG BusNumber; + ULONG SlotNumber; +} SYSDBG_BUS_DATA, *PSYSDBG_BUS_DATA; +#define SYSDBG_BUS_DATA SYSDBG_BUS_DATA +#endif + +#ifndef PCI_SEGMENT_BUS_NUMBER +typedef struct _PCI_SEGMENT_BUS_NUMBER { + union { + struct { + ULONG BusNumber:8; + ULONG SegmentNumber:16; + ULONG Reserved:8; + } bits; + ULONG AsULONG; + } u; +} PCI_SEGMENT_BUS_NUMBER, *PPCI_SEGMENT_BUS_NUMBER; +#define PCI_SEGMENT_BUS_NUMBER PCI_SEGMENT_BUS_NUMBER +#endif + +#ifndef PCI_SLOT_NUMBER +typedef struct _PCI_SLOT_NUMBER { + union { + struct { + ULONG DeviceNumber:5; + ULONG FunctionNumber:3; + ULONG Reserved:24; + } bits; + ULONG AsULONG; + } u; +} PCI_SLOT_NUMBER, *PPCI_SLOT_NUMBER; +#define PCI_SLOT_NUMBER PCI_SLOT_NUMBER +#endif + +#ifndef KLDBG +typedef struct _KLDBG { + SYSDBG_COMMAND Command; + PVOID Buffer; + DWORD BufferLength; +} KLDBG, *PKLDBG; +#define KLDBG KLDBG +#endif + +static BOOL debug_privilege_enabled; +static LUID luid_debug_privilege; +static BOOL revert_only_privilege; +static HANDLE revert_token; + +static HANDLE kldbg_dev = INVALID_HANDLE_VALUE; + +static BOOL +win32_kldbg_pci_bus_data(BOOL WriteBusData, USHORT SegmentNumber, BYTE BusNumber, BYTE DeviceNumber, BYTE FunctionNumber, USHORT Address, PVOID Buffer, ULONG BufferSize, LPDWORD Length); + +static WORD +win32_get_current_process_machine(void) +{ + IMAGE_DOS_HEADER *dos_header; + IMAGE_NT_HEADERS *nt_header; + + dos_header = (IMAGE_DOS_HEADER *)GetModuleHandle(NULL); + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) + return IMAGE_FILE_MACHINE_UNKNOWN; + + nt_header = (IMAGE_NT_HEADERS *)((BYTE *)dos_header + dos_header->e_lfanew); + if (nt_header->Signature != IMAGE_NT_SIGNATURE) + return IMAGE_FILE_MACHINE_UNKNOWN; + + return nt_header->FileHeader.Machine; +} + +static BOOL +win32_check_driver(BYTE *driver_data) +{ + IMAGE_DOS_HEADER *dos_header; + IMAGE_NT_HEADERS *nt_headers; + WORD current_machine; + + current_machine = win32_get_current_process_machine(); + if (current_machine == IMAGE_FILE_MACHINE_UNKNOWN) + return FALSE; + + dos_header = (IMAGE_DOS_HEADER *)driver_data; + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) + return FALSE; + + nt_headers = (IMAGE_NT_HEADERS *)((BYTE *)dos_header + dos_header->e_lfanew); + if (nt_headers->Signature != IMAGE_NT_SIGNATURE) + return FALSE; + + if (nt_headers->FileHeader.Machine != current_machine) + return FALSE; + + if (!(nt_headers->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE)) + return FALSE; + +#ifndef _WIN64 + if (!(nt_headers->FileHeader.Characteristics & IMAGE_FILE_32BIT_MACHINE)) + return FALSE; +#endif + + /* IMAGE_NT_OPTIONAL_HDR_MAGIC is alias for the header magic used on the target compiler architecture. */ + if (nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) + return FALSE; + + if (nt_headers->OptionalHeader.Subsystem != IMAGE_SUBSYSTEM_NATIVE) + return FALSE; + + return TRUE; +} + +static int +win32_kldbg_unpack_driver(struct pci_access *a, LPTSTR driver_path) +{ + BOOL use_kd_exe = FALSE; + HMODULE exe_with_driver = NULL; + HRSRC driver_resource_info = NULL; + HGLOBAL driver_resource = NULL; + BYTE *driver_data = NULL; + DWORD driver_size = 0; + HANDLE driver_handle = INVALID_HANDLE_VALUE; + DWORD written = 0; + DWORD error = 0; + int ret = 0; + + /* Try to find and open windbg.exe or kd.exe file in PATH. */ + exe_with_driver = LoadLibraryEx(TEXT("windbg.exe"), NULL, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); + if (!exe_with_driver) + { + use_kd_exe = TRUE; + exe_with_driver = LoadLibraryEx(TEXT("kd.exe"), NULL, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); + } + if (!exe_with_driver) + { + error = GetLastError(); + if (error == ERROR_FILE_NOT_FOUND || + error == ERROR_MOD_NOT_FOUND) + a->debug("Cannot find windbg.exe or kd.exe file in PATH"); + else + a->debug("Cannot load %s file: %s.", use_kd_exe ? "kd.exe" : "windbg.exe", win32_strerror(error)); + goto out; + } + + /* kldbgdrv.sys is embedded in windbg.exe/kd.exe as a resource with name id 0x7777 and type id 0x4444. */ + driver_resource_info = FindResource(exe_with_driver, MAKEINTRESOURCE(0x7777), MAKEINTRESOURCE(0x4444)); + if (!driver_resource_info) + { + a->debug("Cannot find kldbgdrv.sys resource in %s file: %s.", use_kd_exe ? "kd.exe" : "windbg.exe", win32_strerror(GetLastError())); + goto out; + } + + driver_resource = LoadResource(exe_with_driver, driver_resource_info); + if (!driver_resource) + { + a->debug("Cannot load kldbgdrv.sys resource from %s file: %s.", use_kd_exe ? "kd.exe" : "windbg.exe", win32_strerror(GetLastError())); + goto out; + } + + driver_size = SizeofResource(exe_with_driver, driver_resource_info); + if (!driver_size) + { + a->debug("Cannot determinate size of kldbgdrv.sys resource from %s file: %s.", use_kd_exe ? "kd.exe" : "windbg.exe", win32_strerror(GetLastError())); + goto out; + } + + driver_data = LockResource(driver_resource); + if (!driver_data) + { + a->debug("Cannot load kldbgdrv.sys resouce data from %s file: %s.", use_kd_exe ? "kd.exe" : "windbg.exe", win32_strerror(GetLastError())); + goto out; + } + + if (!win32_check_driver(driver_data)) + { + a->debug("Cannot use kldbgdrv.sys driver from %s file: Driver is from different architecture.", use_kd_exe ? "kd.exe" : "windbg.exe"); + goto out; + } + + driver_handle = CreateFile(driver_path, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + if (driver_handle == INVALID_HANDLE_VALUE) + { + error = GetLastError(); + if (error != ERROR_FILE_EXISTS) + { + a->debug("Cannot create kldbgdrv.sys driver file in system32 directory: %s.", win32_strerror(error)); + goto out; + } + /* If driver file in system32 directory already exists then treat it as successfull unpack. */ + ret = 1; + goto out; + } + + if (!WriteFile(driver_handle, driver_data, driver_size, &written, NULL) || + written != driver_size) + { + a->debug("Cannot store kldbgdrv.sys driver file to system32 directory: %s.", win32_strerror(GetLastError())); + /* On error, delete file from system32 directory to allow another unpack attempt. */ + CloseHandle(driver_handle); + driver_handle = INVALID_HANDLE_VALUE; + DeleteFile(driver_path); + goto out; + } + + a->debug("Driver kldbgdrv.sys was successfully unpacked from %s and stored in system32 directory...", use_kd_exe ? "kd.exe" : "windbg.exe"); + ret = 1; + +out: + if (driver_handle != INVALID_HANDLE_VALUE) + CloseHandle(driver_handle); + + if (driver_resource) + FreeResource(driver_resource); + + if (exe_with_driver) + FreeLibrary(exe_with_driver); + + return ret; +} + +static int +win32_kldbg_register_driver(struct pci_access *a, SC_HANDLE manager, SC_HANDLE *service) +{ + UINT system32_len; + LPTSTR driver_path; + HANDLE driver_handle; + + /* + * COM library dbgeng.dll unpacks kldbg driver to file "\\system32\\kldbgdrv.sys" + * and register this driver with service name kldbgdrv. Implement same behavior. + * GetSystemDirectory() returns path to "\\system32" directory on all Windows versions. + */ + + system32_len = GetSystemDirectory(NULL, 0); /* Returns number of TCHARs plus 1 for nul-term. */ + if (!system32_len) + system32_len = sizeof("C:\\Windows\\System32"); + + driver_path = pci_malloc(a, (system32_len + sizeof("\\kldbgdrv.sys")-1) * sizeof(TCHAR)); + + system32_len = GetSystemDirectory(driver_path, system32_len); /* Now it returns number of TCHARs without nul-term. */ + if (!system32_len) + { + system32_len = sizeof("C:\\Windows\\System32")-1; + memcpy(driver_path, TEXT("C:\\Windows\\System32"), system32_len); + } + + /* GetSystemDirectory returns path without backslash unless the system directory is the root directory. */ + if (driver_path[system32_len-1] != '\\') + driver_path[system32_len++] = '\\'; + + memcpy(driver_path + system32_len, TEXT("kldbgdrv.sys"), sizeof(TEXT("kldbgdrv.sys"))); + + driver_handle = CreateFile(driver_path, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (driver_handle != INVALID_HANDLE_VALUE) + CloseHandle(driver_handle); + else if (GetLastError() == ERROR_FILE_NOT_FOUND) + { + a->debug("Driver kldbgdrv.sys is missing, trying to unpack it from windbg.exe or kd.exe..."); + if (!win32_kldbg_unpack_driver(a, driver_path)) + { + pci_mfree(driver_path); + return 0; + } + } + + *service = CreateService(manager, TEXT("kldbgdrv"), TEXT("kldbgdrv"), SERVICE_START, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, driver_path, NULL, NULL, NULL, NULL, NULL); + if (!*service) + { + if (GetLastError() != ERROR_SERVICE_EXISTS) + { + a->debug("Cannot create kldbgdrv service: %s.", win32_strerror(GetLastError())); + pci_mfree(driver_path); + return 0; + } + + *service = OpenService(manager, TEXT("kldbgdrv"), SERVICE_START); + if (!*service) + { + a->debug("Cannot open kldbgdrv service: %s.", win32_strerror(GetLastError())); + pci_mfree(driver_path); + return 0; + } + } + + a->debug("Service kldbgdrv was successfully registered..."); + pci_mfree(driver_path); + return 1; +} + +static int +win32_kldbg_start_driver(struct pci_access *a) +{ + SC_HANDLE manager = NULL; + SC_HANDLE service = NULL; + DWORD error = 0; + int ret = 0; + + manager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE); + if (!manager) + manager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + if (!manager) + { + a->debug("Cannot open Service Manager: %s.", win32_strerror(GetLastError())); + return 0; + } + + service = OpenService(manager, TEXT("kldbgdrv"), SERVICE_START); + if (!service) + { + error = GetLastError(); + if (error != ERROR_SERVICE_DOES_NOT_EXIST) + { + a->debug("Cannot open kldbgdrv service: %s.", win32_strerror(error)); + goto out; + } + + a->debug("Kernel Local Debugging Driver (kldbgdrv.sys) is not registered, trying to register it..."); + + if (win32_is_32bit_on_64bit_system()) + { + /* TODO */ + a->debug("Registering driver from 32-bit process on 64-bit system is not implemented yet."); + goto out; + } + + if (!win32_kldbg_register_driver(a, manager, &service)) + goto out; + } + + if (!StartService(service, 0, NULL)) + { + error = GetLastError(); + if (error != ERROR_SERVICE_ALREADY_RUNNING) + { + a->debug("Cannot start kldbgdrv service: %s.", win32_strerror(error)); + goto out; + } + } + + a->debug("Service kldbgdrv successfully started..."); + ret = 1; + +out: + if (service) + CloseServiceHandle(service); + + if (manager) + CloseServiceHandle(manager); + + return ret; +} + +static int +win32_kldbg_setup(struct pci_access *a) +{ + OSVERSIONINFO version; + DWORD ret_len; + DWORD error; + DWORD id; + + if (kldbg_dev != INVALID_HANDLE_VALUE) + return 1; + + /* Check for Windows Vista (NT 6.0). */ + version.dwOSVersionInfoSize = sizeof(version); + if (!GetVersionEx(&version) || + version.dwPlatformId != VER_PLATFORM_WIN32_NT || + version.dwMajorVersion < 6) + { + a->debug("Accessing PCI config space via Kernel Local Debugging Driver requires Windows Vista or higher version."); + return 0; + } + + kldbg_dev = CreateFile(TEXT("\\\\.\\kldbgdrv"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (kldbg_dev == INVALID_HANDLE_VALUE) + { + error = GetLastError(); + if (error != ERROR_FILE_NOT_FOUND) + { + a->debug("Cannot open \"\\\\.\\kldbgdrv\" device: %s.", win32_strerror(error)); + return 0; + } + + a->debug("Kernel Local Debugging Driver (kldbgdrv.sys) is not running, trying to start it..."); + + if (!win32_kldbg_start_driver(a)) + return 0; + + kldbg_dev = CreateFile(TEXT("\\\\.\\kldbgdrv"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (kldbg_dev == INVALID_HANDLE_VALUE) + { + error = GetLastError(); + a->debug("Cannot open \"\\\\.\\kldbgdrv\" device: %s.", win32_strerror(error)); + return 0; + } + } + + /* + * Try to read PCI id register from PCI device 0000:00:00.0. + * If this device does not exist and kldbg API is working then + * kldbg returns success with read value 0xffffffff. + */ + if (win32_kldbg_pci_bus_data(FALSE, 0, 0, 0, 0, 0, &id, sizeof(id), &ret_len) && ret_len == sizeof(id)) + return 1; + + error = GetLastError(); + + a->debug("Cannot read PCI config space via Kernel Local Debugging Driver: %s.", win32_strerror(error)); + + if (error != ERROR_ACCESS_DENIED) + { + CloseHandle(kldbg_dev); + kldbg_dev = INVALID_HANDLE_VALUE; + return 0; + } + + a->debug("..Trying again with Debug privilege..."); + + if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid_debug_privilege)) + { + a->debug("Debug privilege is not supported."); + CloseHandle(kldbg_dev); + kldbg_dev = INVALID_HANDLE_VALUE; + return 0; + } + + if (!win32_enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege)) + { + a->debug("Process does not have right to enable Debug privilege."); + CloseHandle(kldbg_dev); + kldbg_dev = INVALID_HANDLE_VALUE; + return 0; + } + + if (win32_kldbg_pci_bus_data(FALSE, 0, 0, 0, 0, 0, &id, sizeof(id), &ret_len) && ret_len == sizeof(id)) + { + a->debug("Succeeded."); + debug_privilege_enabled = TRUE; + return 1; + } + + error = GetLastError(); + + a->debug("Cannot read PCI config space via Kernel Local Debugging Driver: %s.", win32_strerror(error)); + + CloseHandle(kldbg_dev); + kldbg_dev = INVALID_HANDLE_VALUE; + + win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); + revert_token = NULL; + revert_only_privilege = FALSE; + return 0; +} + +static int +win32_kldbg_detect(struct pci_access *a) +{ + if (!win32_kldbg_setup(a)) + return 0; + + return 1; +} + +static void +win32_kldbg_init(struct pci_access *a) +{ + if (!win32_kldbg_setup(a)) + { + a->debug("\n"); + a->error("PCI config space via Kernel Local Debugging Driver cannot be accessed."); + } +} + +static void +win32_kldbg_cleanup(struct pci_access *a UNUSED) +{ + if (kldbg_dev == INVALID_HANDLE_VALUE) + return; + + CloseHandle(kldbg_dev); + kldbg_dev = INVALID_HANDLE_VALUE; + + if (debug_privilege_enabled) + { + win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); + revert_token = NULL; + revert_only_privilege = FALSE; + debug_privilege_enabled = FALSE; + } +} + +struct acpi_mcfg { + char signature[4]; + u32 length; + u8 revision; + u8 checksum; + char oem_id[6]; + char oem_table_id[8]; + u32 oem_revision; + char asl_compiler_id[4]; + u32 asl_compiler_revision; + u64 reserved; + struct { + u64 address; + u16 pci_segment; + u8 start_bus_number; + u8 end_bus_number; + u32 reserved; + } allocations[0]; +} PCI_PACKED; + +static void +win32_kldbg_scan(struct pci_access *a) +{ + /* + * There is no kldbg API to retrieve list of PCI segments. WinDBG pci plugin + * kext.dll loads debug symbols from pci.pdb file for kernel module pci.sys. + * Then it reads kernel memory which belongs to PciSegmentList local variable + * which is the first entry of struct _PCI_SEGMENT linked list. And then it + * iterates all entries in linked list and reads SegmentNumber for each entry. + * + * This is extremly ugly hack and does not work on systems without installed + * kernel debug symbol files. + * + * Do something less ugly. Retrieve ACPI MCFG table via GetSystemFirmwareTable + * and parse all PCI segment numbers from it. ACPI MCFG table contains PCIe + * ECAM definitions, so all PCI segment numbers. + */ + + UINT (*WINAPI MyGetSystemFirmwareTable)(DWORD FirmwareTableProviderSignature, DWORD FirmwareTableID, PVOID pFirmwareTableBuffer, DWORD BufferSize); + int i, allocations_count; + struct acpi_mcfg *mcfg; + HMODULE kernel32; + byte *segments; + DWORD error; + DWORD size; + + /* Always scan PCI segment 0. */ + pci_generic_scan_domain(a, 0); + + kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (!kernel32) + return; + + /* Function GetSystemFirmwareTable() is available since Windows Vista. */ + MyGetSystemFirmwareTable = (void *)GetProcAddress(kernel32, "GetSystemFirmwareTable"); + if (!MyGetSystemFirmwareTable) + return; + + /* 0x41435049 = 'ACPI', 0x4746434D = 'MCFG' */ + size = MyGetSystemFirmwareTable(0x41435049, 0x4746434D, NULL, 0); + if (size == 0) + { + error = GetLastError(); + if (error == ERROR_INVALID_FUNCTION) /* ACPI is not present, so only PCI segment 0 is available. */ + return; + else if (error == ERROR_NOT_FOUND) /* MCFG table is not present, so only PCI segment 0 is available. */ + return; + a->debug("Cannot retrieve ACPI MCFG table: %s.\n", win32_strerror(error)); + return; + } + + mcfg = pci_malloc(a, size); + + if (MyGetSystemFirmwareTable(0x41435049, 0x4746434D, mcfg, size) != size) + { + error = GetLastError(); + a->debug("Cannot retrieve ACPI MCFG table: %s.\n", win32_strerror(error)); + pci_mfree(mcfg); + return; + } + + if (size < sizeof(*mcfg) || size < mcfg->length) + { + a->debug("ACPI MCFG table is broken.\n"); + pci_mfree(mcfg); + return; + } + + segments = pci_malloc(a, 0xFFFF/8); + memset(segments, 0, 0xFFFF/8); + + /* Scan all MCFG allocations and set available PCI segments into bit field. */ + allocations_count = (mcfg->length - ((unsigned char *)&mcfg->allocations - (unsigned char *)mcfg)) / sizeof(mcfg->allocations[0]); + for (i = 0; i < allocations_count; i++) + segments[mcfg->allocations[i].pci_segment / 8] |= 1 << (mcfg->allocations[i].pci_segment % 8); + + /* Skip PCI segment 0 which was already scanned. */ + for (i = 1; i < 0xFFFF; i++) + if (segments[i / 8] & (1 << (i % 8))) + pci_generic_scan_domain(a, i); + + pci_mfree(segments); + pci_mfree(mcfg); +} + +static BOOL +win32_kldbg_pci_bus_data(BOOL WriteBusData, USHORT SegmentNumber, BYTE BusNumber, BYTE DeviceNumber, BYTE FunctionNumber, USHORT Address, PVOID Buffer, ULONG BufferSize, LPDWORD Length) +{ + KLDBG kldbg_cmd; + SYSDBG_BUS_DATA sysdbg_cmd; + PCI_SLOT_NUMBER pci_slot; + PCI_SEGMENT_BUS_NUMBER pci_seg_bus; + + memset(&pci_slot, 0, sizeof(pci_slot)); + memset(&sysdbg_cmd, 0, sizeof(sysdbg_cmd)); + memset(&pci_seg_bus, 0, sizeof(pci_seg_bus)); + + sysdbg_cmd.Address = Address; + sysdbg_cmd.Buffer = Buffer; + sysdbg_cmd.Request = BufferSize; + sysdbg_cmd.BusDataType = PCIConfiguration; + pci_seg_bus.u.bits.BusNumber = BusNumber; + pci_seg_bus.u.bits.SegmentNumber = SegmentNumber; + sysdbg_cmd.BusNumber = pci_seg_bus.u.AsULONG; + pci_slot.u.bits.DeviceNumber = DeviceNumber; + pci_slot.u.bits.FunctionNumber = FunctionNumber; + sysdbg_cmd.SlotNumber = pci_slot.u.AsULONG; + + kldbg_cmd.Command = WriteBusData ? SysDbgWriteBusData : SysDbgReadBusData; + kldbg_cmd.Buffer = &sysdbg_cmd; + kldbg_cmd.BufferLength = sizeof(sysdbg_cmd); + + *Length = 0; + return DeviceIoControl(kldbg_dev, IOCTL_KLDBG, &kldbg_cmd, sizeof(kldbg_cmd), &sysdbg_cmd, sizeof(sysdbg_cmd), Length, NULL); +} + +static int +win32_kldbg_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + DWORD ret_len; + + if ((unsigned int)d->domain > 0xffff) + return 0; + + if (!win32_kldbg_pci_bus_data(FALSE, d->domain, d->bus, d->dev, d->func, pos, buf, len, &ret_len)) + return 0; + + if (ret_len != (unsigned int)len) + return 0; + + return 1; +} + +static int +win32_kldbg_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + DWORD ret_len; + + if ((unsigned int)d->domain > 0xffff) + return 0; + + if (!win32_kldbg_pci_bus_data(TRUE, d->domain, d->bus, d->dev, d->func, pos, buf, len, &ret_len)) + return 0; + + if (ret_len != (unsigned int)len) + return 0; + + return 1; +} + +struct pci_methods pm_win32_kldbg = { + "win32-kldbg", + "Win32 PCI config space access using Kernel Local Debugging Driver", + NULL, /* config */ + win32_kldbg_detect, + win32_kldbg_init, + win32_kldbg_cleanup, + win32_kldbg_scan, + pci_generic_fill_info, + win32_kldbg_read, + win32_kldbg_write, + NULL, /* read_vpd */ + NULL, /* init_dev */ + NULL /* cleanup_dev */ +}; diff --git a/lib/win32-sysdbg.c b/lib/win32-sysdbg.c new file mode 100644 index 0000000..99ce607 --- /dev/null +++ b/lib/win32-sysdbg.c @@ -0,0 +1,306 @@ +/* + * The PCI Library -- PCI config space access using NT SysDbg interface + * + * Copyright (c) 2022 Pali Rohár <pali@kernel.org> + * + * Can be freely distributed and used under the terms of the GNU GPL v2+. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <windows.h> + +#include "internal.h" +#include "win32-helpers.h" + +#ifndef NTSTATUS +#define NTSTATUS LONG +#endif +#ifndef STATUS_UNSUCCESSFUL +#define STATUS_UNSUCCESSFUL (NTSTATUS)0xC0000001 +#endif +#ifndef STATUS_NOT_IMPLEMENTED +#define STATUS_NOT_IMPLEMENTED (NTSTATUS)0xC0000002 +#endif +#ifndef STATUS_INVALID_INFO_CLASS +#define STATUS_INVALID_INFO_CLASS (NTSTATUS)0xC0000003 +#endif +#ifndef STATUS_ACCESS_DENIED +#define STATUS_ACCESS_DENIED (NTSTATUS)0xC0000022 +#endif +#ifndef STATUS_DEBUGGER_INACTIVE +#define STATUS_DEBUGGER_INACTIVE (NTSTATUS)0xC0000354 +#endif + +#ifndef BUS_DATA_TYPE +#define BUS_DATA_TYPE LONG +#endif +#ifndef PCIConfiguration +#define PCIConfiguration (BUS_DATA_TYPE)4 +#endif + +#ifndef SYSDBG_COMMAND +#define SYSDBG_COMMAND ULONG +#endif +#ifndef SysDbgReadBusData +#define SysDbgReadBusData (SYSDBG_COMMAND)18 +#endif +#ifndef SysDbgWriteBusData +#define SysDbgWriteBusData (SYSDBG_COMMAND)19 +#endif + +#ifndef SYSDBG_BUS_DATA +typedef struct _SYSDBG_BUS_DATA { + ULONG Address; + PVOID Buffer; + ULONG Request; + BUS_DATA_TYPE BusDataType; + ULONG BusNumber; + ULONG SlotNumber; +} SYSDBG_BUS_DATA, *PSYSDBG_BUS_DATA; +#define SYSDBG_BUS_DATA SYSDBG_BUS_DATA +#endif + +#ifndef PCI_SLOT_NUMBER +typedef struct _PCI_SLOT_NUMBER { + union { + struct { + ULONG DeviceNumber:5; + ULONG FunctionNumber:3; + ULONG Reserved:24; + } bits; + ULONG AsULONG; + } u; +} PCI_SLOT_NUMBER, *PPCI_SLOT_NUMBER; +#define PCI_SLOT_NUMBER PCI_SLOT_NUMBER +#endif + +#ifdef NtSystemDebugControl +#undef NtSystemDebugControl +#endif +static NTSTATUS (NTAPI *MyNtSystemDebugControl)(SYSDBG_COMMAND Command, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength, PULONG ReturnLength); +#define NtSystemDebugControl MyNtSystemDebugControl + +static BOOL debug_privilege_enabled; +static LUID luid_debug_privilege; +static BOOL revert_only_privilege; +static HANDLE revert_token; +static HMODULE ntdll; + +static int win32_sysdbg_initialized; + +static NTSTATUS +win32_sysdbg_pci_bus_data(BOOL WriteBusData, BYTE BusNumber, BYTE DeviceNumber, BYTE FunctionNumber, BYTE Address, PVOID Buffer, BYTE BufferSize, PULONG Length) +{ + SYSDBG_BUS_DATA sysdbg_cmd; + PCI_SLOT_NUMBER pci_slot; + + if (!NtSystemDebugControl) + return STATUS_NOT_IMPLEMENTED; + + memset(&pci_slot, 0, sizeof(pci_slot)); + memset(&sysdbg_cmd, 0, sizeof(sysdbg_cmd)); + + sysdbg_cmd.Address = Address; + sysdbg_cmd.Buffer = Buffer; + sysdbg_cmd.Request = BufferSize; + sysdbg_cmd.BusDataType = PCIConfiguration; + sysdbg_cmd.BusNumber = BusNumber; + pci_slot.u.bits.DeviceNumber = DeviceNumber; + pci_slot.u.bits.FunctionNumber = FunctionNumber; + sysdbg_cmd.SlotNumber = pci_slot.u.AsULONG; + + *Length = 0; + return NtSystemDebugControl(WriteBusData ? SysDbgWriteBusData : SysDbgReadBusData, &sysdbg_cmd, sizeof(sysdbg_cmd), NULL, 0, Length); +} + +static int +win32_sysdbg_setup(struct pci_access *a) +{ + UINT prev_error_mode; + NTSTATUS status; + ULONG ret_len; + DWORD id; + + if (win32_sysdbg_initialized) + return 1; + + prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS); + ntdll = LoadLibrary(TEXT("ntdll.dll")); + win32_change_error_mode(prev_error_mode); + if (!ntdll) + { + a->debug("Cannot open ntdll.dll library."); + return 0; + } + + NtSystemDebugControl = (LPVOID)GetProcAddress(ntdll, "NtSystemDebugControl"); + if (!NtSystemDebugControl) + { + a->debug("Function NtSystemDebugControl() is not supported."); + FreeLibrary(ntdll); + ntdll = NULL; + return 0; + } + + /* + * Try to read PCI id register from PCI device 00:00.0. + * If this device does not exist and NT SysDbg API is working then + * NT SysDbg returns STATUS_UNSUCCESSFUL. + */ + status = win32_sysdbg_pci_bus_data(FALSE, 0, 0, 0, 0, &id, sizeof(id), &ret_len); + if ((status >= 0 && ret_len == sizeof(id)) || status == STATUS_UNSUCCESSFUL) + { + win32_sysdbg_initialized = 1; + return 1; + } + else if (status != STATUS_ACCESS_DENIED) + { + if (status == STATUS_NOT_IMPLEMENTED || status == STATUS_INVALID_INFO_CLASS) + a->debug("NT SysDbg is not supported."); + else if (status == STATUS_DEBUGGER_INACTIVE) + a->debug("NT SysDbg is disabled."); + else + a->debug("NT SysDbg returned error 0x%lx.", status); + FreeLibrary(ntdll); + ntdll = NULL; + NtSystemDebugControl = NULL; + return 0; + } + + a->debug("NT SysDbg returned Access Denied, trying again with Debug privilege..."); + + if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid_debug_privilege)) + { + a->debug("Debug privilege is not supported."); + FreeLibrary(ntdll); + ntdll = NULL; + NtSystemDebugControl = NULL; + return 0; + } + + if (!win32_enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege)) + { + a->debug("Cannot enable Debug privilege."); + FreeLibrary(ntdll); + ntdll = NULL; + NtSystemDebugControl = NULL; + return 0; + } + + status = win32_sysdbg_pci_bus_data(FALSE, 0, 0, 0, 0, &id, sizeof(id), &ret_len); + if ((status >= 0 && ret_len == sizeof(id)) || status == STATUS_UNSUCCESSFUL) + { + a->debug("Succeeded."); + debug_privilege_enabled = TRUE; + win32_sysdbg_initialized = 1; + return 1; + } + + win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); + revert_token = NULL; + revert_only_privilege = FALSE; + + FreeLibrary(ntdll); + ntdll = NULL; + NtSystemDebugControl = NULL; + + if (status == STATUS_NOT_IMPLEMENTED || status == STATUS_INVALID_INFO_CLASS) + a->debug("NT SysDbg is not supported."); + else if (status == STATUS_DEBUGGER_INACTIVE) + a->debug("NT SysDbg is disabled."); + else if (status == STATUS_ACCESS_DENIED) + a->debug("NT SysDbg returned Access Denied."); + else + a->debug("NT SysDbg returned error 0x%lx.", status); + + return 0; +} + +static int +win32_sysdbg_detect(struct pci_access *a) +{ + if (!win32_sysdbg_setup(a)) + return 0; + + return 1; +} + +static void +win32_sysdbg_init(struct pci_access *a) +{ + if (!win32_sysdbg_setup(a)) + { + a->debug("\n"); + a->error("NT SysDbg PCI Bus Data interface cannot be accessed."); + } +} + +static void +win32_sysdbg_cleanup(struct pci_access *a UNUSED) +{ + if (!win32_sysdbg_initialized) + return; + + if (debug_privilege_enabled) + { + win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); + revert_token = NULL; + revert_only_privilege = FALSE; + debug_privilege_enabled = FALSE; + } + + FreeLibrary(ntdll); + ntdll = NULL; + NtSystemDebugControl = NULL; + + win32_sysdbg_initialized = 0; +} + +static int +win32_sysdbg_read(struct pci_dev *d, int pos, byte *buf, int len) +{ + NTSTATUS status; + ULONG ret_len; + + if ((unsigned int)d->domain > 0 || (unsigned int)pos > 255 || (unsigned int)(pos+len) > 256) + return 0; + + status = win32_sysdbg_pci_bus_data(FALSE, d->bus, d->dev, d->func, pos, buf, len, &ret_len); + if (status < 0 || ret_len != (unsigned int)len) + return 0; + + return 1; +} + +static int +win32_sysdbg_write(struct pci_dev *d, int pos, byte *buf, int len) +{ + NTSTATUS status; + ULONG ret_len; + + if ((unsigned int)d->domain > 0 || (unsigned int)pos > 255 || (unsigned int)(pos+len) > 256) + return 0; + + status = win32_sysdbg_pci_bus_data(TRUE, d->bus, d->dev, d->func, pos, buf, len, &ret_len); + if (status < 0 || ret_len != (unsigned int)len) + return 0; + + return 1; +} + +struct pci_methods pm_win32_sysdbg = { + "win32-sysdbg", + "Win32 PCI config space access using NT SysDbg Bus Data interface", + NULL, /* config */ + win32_sysdbg_detect, + win32_sysdbg_init, + win32_sysdbg_cleanup, + pci_generic_scan, + pci_generic_fill_info, + win32_sysdbg_read, + win32_sysdbg_write, + NULL, /* read_vpd */ + NULL, /* init_dev */ + NULL /* cleanup_dev */ +}; diff --git a/lib/winrsrc.rc.in b/lib/winrsrc.rc.in new file mode 100644 index 0000000..e061bff --- /dev/null +++ b/lib/winrsrc.rc.in @@ -0,0 +1,39 @@ +#include <windows.h> +VS_VERSION_INFO VERSIONINFO +FILEVERSION @PCILIB_VERSION_WINRC@ +PRODUCTVERSION @PCILIB_VERSION_WINRC@ +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#if @DEBUG_BUILD@ +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0 +#endif +FILEOS VOS_NT_WINDOWS32 +#if @LIBRARY_BUILD@ +FILETYPE VFT_DLL +#else +FILETYPE VFT_APP +#endif +FILESUBTYPE 0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + /* + * GNU windres seems that converts 7-bit ASCII strings to UTF-16, + * so specify UNICODE/UTF-16 encoding (0x04B0) for these strings. + */ + BLOCK "040904B0" /* Default U.S. English language, UNICODE/UTF-16 codepage */ + BEGIN + VALUE "FileDescription", "@DESCRIPTION@" + VALUE "FileVersion", "@PCILIB_VERSION@" + VALUE "InternalName", "@FILENAME@" + VALUE "OriginalFilename", "@FILENAME@" + VALUE "ProductName", "pciutils" + VALUE "ProductVersion", "@PCILIB_VERSION@" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 0x04B0 /* Default U.S. English language, UNICODE/UTF-16 codepage */ + END +END |