diff options
-rw-r--r-- | ChangeLog | 27 | ||||
-rw-r--r-- | Makefile | 17 | ||||
-rw-r--r-- | lib/Makefile | 11 | ||||
-rwxr-xr-x | lib/configure | 11 | ||||
-rw-r--r-- | lib/header.h | 80 | ||||
-rw-r--r-- | lib/i386-io-linux.h | 48 | ||||
-rw-r--r-- | lib/i386-io-windows.h | 3 | ||||
-rw-r--r-- | lib/i386-ports.c | 20 | ||||
-rw-r--r-- | lib/init.c | 6 | ||||
-rw-r--r-- | lib/internal.h | 2 | ||||
-rw-r--r-- | lib/pci.h | 1 | ||||
-rw-r--r-- | lib/types.h | 2 | ||||
-rw-r--r-- | lib/win32-cfgmgr32.c | 18 | ||||
-rw-r--r-- | lib/win32-kldbg.c | 796 | ||||
-rw-r--r-- | lib/winrsrc.rc.in (renamed from lib/dllrsrc.rc.in) | 12 | ||||
-rw-r--r-- | ls-ecaps.c | 227 | ||||
-rw-r--r-- | pcilib.man | 25 | ||||
-rw-r--r-- | tests/cap-dvsec-cxl | 258 |
18 files changed, 1527 insertions, 37 deletions
@@ -1,3 +1,30 @@ +2022-11-18 Martin Mares <mj@ucw.cz> + + * XXX: To be released as 3.9.0. + + * We decode Compute Express Link (CXL) capabilities. + + * The tree mode of lspci is now compatible with filtering options. + + * When setpci is used with a named register, it checks whether + the register is present in the particular header type. + + * Linux: The intel-conf[12] back-ends prefer to use ioperm() instead + of iopl() to gain access to I/O ports. + + * Windows: We have two new back-ends thanks to Pali Rohár. + One uses the NT SysDbg interface, the other uses kldbgdrv.sys + (which is a part of the Microsoft WinDbg tool). + + * Windows: We support building libpci as a DLL. Also, Windows + binaries now include meta-data with version. + + * Hurd: The Hurd back-end works again. + + * mmio-ports: Added a new back-end implementing the intel-conf1 + interface over MMIO. This is useful on some ARM machines, but it + requires manual configuration of the MMIO addresses. + 2022-04-18 Martin Mares <mj@ucw.cz> * Released as 3.8.0. @@ -112,6 +112,21 @@ example.o: example.c $(PCIINC) %$(EXEEXT): %.o $(CC) $(LDFLAGS) $(TARGET_ARCH) $^ $(LDLIBS) -o $@ +ifdef PCI_OS_WINDOWS +comma := , +%-rsrc.rc: lib/winrsrc.rc.in + sed <$< >$@ -e 's,@PCILIB_VERSION@,$(PCILIB_VERSION),' \ + -e 's,@PCILIB_VERSION_WINRC@,$(subst .,\$(comma),$(PCILIB_VERSION).0),' \ + -e 's,@FILENAME@,$(subst -rsrc.rc,$(EXEEXT),$@),' \ + -e 's,@DESCRIPTION@,$(subst -rsrc.rc,,$@),' \ + -e 's,@LIBRARY_BUILD@,0,' \ + -e 's,@DEBUG_BUILD@,$(if $(findstring -g,$(CFLAGS)),1,0),' +%-rsrc.o: %-rsrc.rc + $(WINDRES) --input=$< --output=$@ --input-format=rc --output-format=coff +lspci$(EXEEXT): lspci-rsrc.o +setpci$(EXEEXT): setpci-rsrc.o +endif + %.8 %.7 %.5: %.man M=`echo $(DATE) | sed 's/-01-/-January-/;s/-02-/-February-/;s/-03-/-March-/;s/-04-/-April-/;s/-05-/-May-/;s/-06-/-June-/;s/-07-/-July-/;s/-08-/-August-/;s/-09-/-September-/;s/-10-/-October-/;s/-11-/-November-/;s/-12-/-December-/;s/\(.*\)-\(.*\)-\(.*\)/\3 \2 \1/'` ; sed <$< >$@ "s/@TODAY@/$$M/;s/@VERSION@/pciutils-$(VERSION)/;s#@IDSDIR@#$(IDSDIR)#;s#@PCI_IDS@#$(PCI_IDS)#" @@ -125,7 +140,7 @@ TAGS: clean: rm -f `find . -name "*~" -o -name "*.[oa]" -o -name "\#*\#" -o -name TAGS -o -name core -o -name "*.orig"` - rm -f update-pciids lspci$(EXEEXT) setpci$(EXEEXT) example$(EXEEXT) lib/config.* *.[578] pci.ids.gz lib/*.pc lib/*.so lib/*.so.* lib/*.dll lib/*.def lib/dllrsrc.rc tags + rm -f update-pciids lspci$(EXEEXT) setpci$(EXEEXT) example$(EXEEXT) lib/config.* *.[578] pci.ids.gz lib/*.pc lib/*.so lib/*.so.* lib/*.dll lib/*.def lib/dllrsrc.rc *-rsrc.rc tags rm -rf maint/dist distclean: clean diff --git a/lib/Makefile b/lib/Makefile index a119bdf..43829ef 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -63,6 +63,10 @@ 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 @@ -83,10 +87,12 @@ $(PCIIMPDEF): libpci.ver ver2def.pl $(PCIIMPLIB): $(PCIIMPDEF) $(DLLTOOL) --input-def $< --output-lib $@ comma := , -dllrsrc.rc: dllrsrc.rc.in +dllrsrc.rc: winrsrc.rc.in sed <$< >$@ -e 's,@PCILIB_VERSION@,$(PCILIB_VERSION),' \ -e 's,@PCILIB_VERSION_WINRC@,$(subst .,\$(comma),$(PCILIB_VERSION).0),' \ - -e 's,@PCILIB@,$(PCILIB),' \ + -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 @@ -133,6 +139,7 @@ filter.o: filter.c $(INCL) nbsd-libpci.o: nbsd-libpci.c $(INCL) hurd.o: hurd.c $(INCL) win32-cfgmgr32.o: win32-cfgmgr32.c $(INCL) +win32-kldbg.o: win32-kldbg.c $(INCL) win32-sysdbg.o: win32-sysdbg.c $(INCL) # MinGW32 toolchain has some required Win32 header files in /ddk subdirectory. diff --git a/lib/configure b/lib/configure index 7676c0b..57b064b 100755 --- a/lib/configure +++ b/lib/configure @@ -118,8 +118,6 @@ case $sys in darwin*) echo_n " darwin" echo >>$c '#define PCI_HAVE_PM_DARWIN_DEVICE' - echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' - echo >>$c '#define PCI_PATH_DEVMEM_DEVICE "/dev/mem"' echo >>$m 'WITH_LIBS+=-lresolv -framework CoreFoundation -framework IOKit' echo >>$c '#define PCI_HAVE_64BIT_ADDRESS' LIBRESOLV= @@ -129,8 +127,6 @@ case $sys in aix) echo_n " aix-device" echo >>$c '#define PCI_HAVE_PM_AIX_DEVICE' - echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' - echo >>$c '#define PCI_PATH_DEVMEM_DEVICE "/dev/mem"' echo >>$m 'CFLAGS=-g' echo >>$m 'INSTALL=installbsd' echo >>$m 'DIRINSTALL=mkdir -p' @@ -150,8 +146,6 @@ case $sys in echo_n " hurd i386-ports" echo >>$c '#define PCI_HAVE_PM_HURD_CONF' echo >>$c '#define PCI_HAVE_PM_INTEL_CONF' - echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' - echo >>$c '#define PCI_PATH_DEVMEM_DEVICE "/dev/mem"' ;; djgpp) echo_n " i386-ports" @@ -159,9 +153,10 @@ case $sys in EXEEXT=.exe ;; cygwin|windows) - echo_n " win32-cfgmgr32 win32-sysdbg" + 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. @@ -188,8 +183,6 @@ case $sys in echo >>$c '#define PCI_HAVE_PM_INTEL_CONF' ;; esac - echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' - echo >>$c '#define PCI_PATH_DEVMEM_DEVICE "/dev/misc/mem"' echo >>$c '#define PCI_HAVE_STDINT_H' ;; sylixos) diff --git a/lib/header.h b/lib/header.h index e74f6d8..63ee03c 100644 --- a/lib/header.h +++ b/lib/header.h @@ -1129,6 +1129,86 @@ /* 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 */ + +/* 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 */ diff --git a/lib/i386-io-linux.h b/lib/i386-io-linux.h index 731e8e3..a2fd69e 100644 --- a/lib/i386-io-linux.h +++ b/lib/i386-io-linux.h @@ -7,17 +7,61 @@ */ #include <sys/io.h> +#include <errno.h> + +static int ioperm_enabled; +static int iopl_enabled; static int intel_setup_io(struct pci_access *a UNUSED) { - return (iopl(3) < 0) ? 0 : 1; + 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) { - iopl(0); + 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) diff --git a/lib/i386-io-windows.h b/lib/i386-io-windows.h index 1509d7d..6bb578c 100644 --- a/lib/i386-io-windows.h +++ b/lib/i386-io-windows.h @@ -1343,7 +1343,8 @@ intel_setup_io(struct pci_access *a) /* On NT-based systems issue ProcessUserModeIOPL syscall which changes IOPL to 3. */ if (!SetProcessUserModeIOPL()) { - a->warning("NT ProcessUserModeIOPL call failed with error: %lu.", (unsigned long int)GetLastError()); + DWORD error = GetLastError(); + a->debug("NT ProcessUserModeIOPL call failed: %s.", error == ERROR_INVALID_FUNCTION ? "Not Implemented" : error == ERROR_PRIVILEGE_NOT_HELD ? "Access Denied" : "Operation Failed"); return 0; } diff --git a/lib/i386-ports.c b/lib/i386-ports.c index 2e64fe4..0ca87dd 100644 --- a/lib/i386-ports.c +++ b/lib/i386-ports.c @@ -136,6 +136,9 @@ conf1_read(struct pci_dev *d, int pos, byte *buf, int len) 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(); outl(0x80000000 | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos&~3), 0xcf8); @@ -150,8 +153,6 @@ conf1_read(struct pci_dev *d, int pos, byte *buf, int len) case 4: ((u32 *) buf)[0] = cpu_to_le32(inl(addr)); break; - default: - res = pci_generic_block_read(d, pos, buf, len); } intel_io_unlock(); @@ -167,6 +168,9 @@ conf1_write(struct pci_dev *d, int pos, byte *buf, int len) 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(); outl(0x80000000 | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos&~3), 0xcf8); @@ -181,8 +185,6 @@ conf1_write(struct pci_dev *d, int pos, byte *buf, int len) case 4: outl(le32_to_cpu(((u32 *) buf)[0]), addr); break; - default: - res = pci_generic_block_write(d, pos, buf, len); } intel_io_unlock(); return res; @@ -228,6 +230,9 @@ conf2_read(struct pci_dev *d, int pos, byte *buf, int len) /* 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(); outb((d->func << 1) | 0xf0, 0xcf8); outb(d->bus, 0xcfa); @@ -242,8 +247,6 @@ conf2_read(struct pci_dev *d, int pos, byte *buf, int len) case 4: ((u32 *) buf)[0] = cpu_to_le32(inl(addr)); break; - default: - res = pci_generic_block_read(d, pos, buf, len); } outb(0, 0xcf8); intel_io_unlock(); @@ -263,6 +266,9 @@ conf2_write(struct pci_dev *d, int pos, byte *buf, int len) /* 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(); outb((d->func << 1) | 0xf0, 0xcf8); outb(d->bus, 0xcfa); @@ -277,8 +283,6 @@ conf2_write(struct pci_dev *d, int pos, byte *buf, int len) case 4: outl(le32_to_cpu(* (u32 *) buf), addr); break; - default: - res = pci_generic_block_write(d, pos, buf, len); } outb(0, 0xcf8); @@ -81,6 +81,11 @@ static struct pci_methods *pci_methods[PCI_ACCESS_MAX] = { #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 @@ -106,6 +111,7 @@ static int probe_sequence[] = { PCI_ACCESS_SYLIXOS_DEVICE, PCI_ACCESS_HURD, PCI_ACCESS_WIN32_CFGMGR32, + PCI_ACCESS_WIN32_KLDBG, PCI_ACCESS_WIN32_SYSDBG, // Low-level methods poking the hardware directly PCI_ACCESS_I386_TYPE1, diff --git a/lib/internal.h b/lib/internal.h index 6bfcde7..81316dc 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -135,4 +135,4 @@ 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_win32_cfgmgr32, pm_win32_sysdbg; + pm_win32_cfgmgr32, pm_win32_kldbg, pm_win32_sysdbg; @@ -44,6 +44,7 @@ enum pci_access_type { 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_MAX diff --git a/lib/types.h b/lib/types.h index 243997f..ab53f7c 100644 --- a/lib/types.h +++ b/lib/types.h @@ -75,7 +75,9 @@ typedef u32 pciaddr_t; #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/win32-cfgmgr32.c b/lib/win32-cfgmgr32.c index 004f95f..a3404d1 100644 --- a/lib/win32-cfgmgr32.c +++ b/lib/win32-cfgmgr32.c @@ -126,7 +126,7 @@ resolve_cfgmgr32_functions(void) * cfgmgr32.dll uses custom non-Win32 error numbers which are unsupported by * Win32 APIs like GetLastError() and FormatMessage() functions. * - * Windows 7 instroduced new cfgmgr32.dll function CM_MapCrToWin32Err() for + * 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. * @@ -498,18 +498,28 @@ retry_service_config: 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) + 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 + sizeof("\\??\\UNC\\")-1); + 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))); - wcscpy(service_image_path, 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'\\') { diff --git a/lib/win32-kldbg.c b/lib/win32-kldbg.c new file mode 100644 index 0000000..3890b6b --- /dev/null +++ b/lib/win32-kldbg.c @@ -0,0 +1,796 @@ +/* + * 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. + */ + +#include <windows.h> +#include <winioctl.h> + +#include <stdio.h> /* for sprintf() */ +#include <string.h> /* for memset() and memcpy() */ + +#include "internal.h" +#include "i386-io-windows.h" + +#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 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; + + len = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, win32_error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), NULL); + + /* 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; +} + +static BOOL +win32_is_32bit_on_64bit_system(void) +{ + BOOL (WINAPI *MyIsWow64Process)(HANDLE, PBOOL); + HMODULE kernel32; + BOOL is_wow64; + + 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; +} + +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, void *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 (WINAPI *get_system_root_path)(void *buffer, UINT size) = NULL; + UINT systemroot_len; + void *driver_path; + HANDLE driver_handle; + HMODULE kernel32; + + /* + * COM library dbgeng.dll unpacks kldbg driver to file \\system32\\kldbgdrv.sys + * and register this driver with service name kldbgdrv. Implement same behavior. + */ + + /* + * Old Windows versions return path to NT SystemRoot namespace via + * GetWindowsDirectory() function. New Windows versions via + * GetSystemWindowsDirectory(). GetSystemWindowsDirectory() is not + * provided in old Windows versions, so use GetProcAddress() for + * compatibility with all Windows versions. + */ + + kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (kernel32) + get_system_root_path = (void *)GetProcAddress(kernel32, "GetSystemWindowsDirectory" +#ifdef UNICODE + "W" +#else + "A" +#endif + ); + + if (!get_system_root_path) + get_system_root_path = (void *)&GetWindowsDirectory; + + systemroot_len = get_system_root_path(NULL, 0); + if (!systemroot_len) + systemroot_len = sizeof(TEXT("C:\\Windows\\")); + + driver_path = pci_malloc(a, systemroot_len + sizeof(TEXT("\\system32\\kldbgdrv.sys"))); + + systemroot_len = get_system_root_path(driver_path, systemroot_len + sizeof(TEXT(""))); + if (!systemroot_len) + { + systemroot_len = sizeof(TEXT("C:\\Windows\\")); + memcpy(driver_path, TEXT("C:\\Windows\\"), systemroot_len); + } + + if (((char *)driver_path)[systemroot_len-sizeof(TEXT(""))+1] != '\\') + { + ((char *)driver_path)[systemroot_len-sizeof(TEXT(""))+1] = '\\'; + systemroot_len += sizeof(TEXT("")); + } + + memcpy((char *)driver_path + systemroot_len, TEXT("system32\\kldbgdrv.sys"), sizeof(TEXT("system32\\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 (!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; + + 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) + { + 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/dllrsrc.rc.in b/lib/winrsrc.rc.in index 519772b..e061bff 100644 --- a/lib/dllrsrc.rc.in +++ b/lib/winrsrc.rc.in @@ -9,7 +9,11 @@ FILEFLAGS VS_FF_DEBUG FILEFLAGS 0 #endif FILEOS VOS_NT_WINDOWS32 +#if @LIBRARY_BUILD@ FILETYPE VFT_DLL +#else +FILETYPE VFT_APP +#endif FILESUBTYPE 0 BEGIN BLOCK "StringFileInfo" @@ -20,16 +24,16 @@ BEGIN */ BLOCK "040904B0" /* Default U.S. English language, UNICODE/UTF-16 codepage */ BEGIN - VALUE "FileDescription", "libpci" + VALUE "FileDescription", "@DESCRIPTION@" VALUE "FileVersion", "@PCILIB_VERSION@" - VALUE "InternalName", "@PCILIB@" - VALUE "OriginalFilename", "@PCILIB@" + VALUE "InternalName", "@FILENAME@" + VALUE "OriginalFilename", "@FILENAME@" VALUE "ProductName", "pciutils" VALUE "ProductVersion", "@PCILIB_VERSION@" END END BLOCK "VarFileInfo" BEGIN - VALUE "Translation", 0x0409, 0x004B0 /* Default U.S. English language, UNICODE/UTF-16 codepage */ + VALUE "Translation", 0x0409, 0x04B0 /* Default U.S. English language, UNICODE/UTF-16 codepage */ END END @@ -839,6 +839,223 @@ dvsec_cxl_register_locator(struct device *d, int where, int len) } static void +dvsec_cxl_gpf_device(struct device *d, int where) +{ + u32 l; + u16 w, duration; + u8 time_base, time_scale; + + w = get_conf_word(d, where + PCI_CXL_GPF_DEV_PHASE2_DUR); + time_base = BITS(w, 0, 4); + time_scale = BITS(w, 8, 4); + + switch (time_scale) + { + case PCI_CXL_GPF_DEV_100US: + case PCI_CXL_GPF_DEV_100MS: + duration = time_base * 100; + break; + case PCI_CXL_GPF_DEV_10US: + case PCI_CXL_GPF_DEV_10MS: + case PCI_CXL_GPF_DEV_10S: + duration = time_base * 10; + break; + case PCI_CXL_GPF_DEV_1US: + case PCI_CXL_GPF_DEV_1MS: + case PCI_CXL_GPF_DEV_1S: + duration = time_base; + break; + default: + /* Reserved */ + printf("\t\tReserved time scale encoding %x\n", time_scale); + duration = time_base; + } + + printf("\t\tGPF Phase 2 Duration: %u%s\n", duration, + (time_scale < PCI_CXL_GPF_DEV_1MS) ? "us": + (time_scale < PCI_CXL_GPF_DEV_1S) ? "ms" : + (time_scale == PCI_CXL_GPF_DEV_1S) ? "s" : "<?>"); + + l = get_conf_long(d, where + PCI_CXL_GPF_DEV_PHASE2_POW); + printf("\t\tGPF Phase 2 Power: %umW\n", (unsigned int)l); +} + +static void +dvsec_cxl_gpf_port(struct device *d, int where) +{ + u16 w, timeout; + u8 time_base, time_scale; + + w = get_conf_word(d, where + PCI_CXL_GPF_PORT_PHASE1_CTRL); + time_base = BITS(w, 0, 4); + time_scale = BITS(w, 8, 4); + + switch (time_scale) + { + case PCI_CXL_GPF_PORT_100US: + case PCI_CXL_GPF_PORT_100MS: + timeout = time_base * 100; + break; + case PCI_CXL_GPF_PORT_10US: + case PCI_CXL_GPF_PORT_10MS: + case PCI_CXL_GPF_PORT_10S: + timeout = time_base * 10; + break; + case PCI_CXL_GPF_PORT_1US: + case PCI_CXL_GPF_PORT_1MS: + case PCI_CXL_GPF_PORT_1S: + timeout = time_base; + break; + default: + /* Reserved */ + printf("\t\tReserved time scale encoding %x\n", time_scale); + timeout = time_base; + } + + printf("\t\tGPF Phase 1 Timeout: %d%s\n", timeout, + (time_scale < PCI_CXL_GPF_PORT_1MS) ? "us": + (time_scale < PCI_CXL_GPF_PORT_1S) ? "ms" : + (time_scale == PCI_CXL_GPF_PORT_1S) ? "s" : "<?>"); + + w = get_conf_word(d, where + PCI_CXL_GPF_PORT_PHASE2_CTRL); + time_base = BITS(w, 0, 4); + time_scale = BITS(w, 8, 4); + + switch (time_scale) + { + case PCI_CXL_GPF_PORT_100US: + case PCI_CXL_GPF_PORT_100MS: + timeout = time_base * 100; + break; + case PCI_CXL_GPF_PORT_10US: + case PCI_CXL_GPF_PORT_10MS: + case PCI_CXL_GPF_PORT_10S: + timeout = time_base * 10; + break; + case PCI_CXL_GPF_PORT_1US: + case PCI_CXL_GPF_PORT_1MS: + case PCI_CXL_GPF_PORT_1S: + timeout = time_base; + break; + default: + /* Reserved */ + printf("\t\tReserved time scale encoding %x\n", time_scale); + timeout = time_base; + } + + printf("\t\tGPF Phase 2 Timeout: %d%s\n", timeout, + (time_scale < PCI_CXL_GPF_PORT_1MS) ? "us": + (time_scale < PCI_CXL_GPF_PORT_1S) ? "ms" : + (time_scale == PCI_CXL_GPF_PORT_1S) ? "s" : "<?>"); +} + +static void +dvsec_cxl_flex_bus(struct device *d, int where, int rev) +{ + u16 w; + u32 l, data; + + if (rev < 1) + { + printf("\t\tRevision %d not supported\n", rev); + return; + } + + w = get_conf_word(d, where + PCI_CXL_FB_PORT_CAP); + printf("\t\tFBCap:\tCache%c IO%c Mem%c 68BFlit%c MltLogDev%c", + FLAG(w, PCI_CXL_FB_CAP_CACHE), FLAG(w, PCI_CXL_FB_CAP_IO), + FLAG(w, PCI_CXL_FB_CAP_MEM), FLAG(w, PCI_CXL_FB_CAP_68B_FLIT), + FLAG(w, PCI_CXL_FB_CAP_MULT_LOG_DEV)); + + if (rev > 1) + printf(" 256BFlit%c PBRFlit%c", + FLAG(w, PCI_CXL_FB_CAP_256B_FLIT), FLAG(w, PCI_CXL_FB_CAP_PBR_FLIT)); + + w = get_conf_word(d, where + PCI_CXL_FB_PORT_CTRL); + printf("\n\t\tFBCtl:\tCache%c IO%c Mem%c SynHdrByp%c DrftBuf%c 68BFlit%c MltLogDev%c RCD%c Retimer1%c Retimer2%c", + FLAG(w, PCI_CXL_FB_CTRL_CACHE), FLAG(w, PCI_CXL_FB_CTRL_IO), + FLAG(w, PCI_CXL_FB_CTRL_MEM), FLAG(w, PCI_CXL_FB_CTRL_SYNC_HDR_BYP), + FLAG(w, PCI_CXL_FB_CTRL_DRFT_BUF), FLAG(w, PCI_CXL_FB_CTRL_68B_FLIT), + FLAG(w, PCI_CXL_FB_CTRL_MULT_LOG_DEV), FLAG(w, PCI_CXL_FB_CTRL_RCD), + FLAG(w, PCI_CXL_FB_CTRL_RETIMER1), FLAG(w, PCI_CXL_FB_CTRL_RETIMER2)); + + if (rev > 1) + printf(" 256BFlit%c PBRFlit%c", + FLAG(w, PCI_CXL_FB_CTRL_256B_FLIT), FLAG(w, PCI_CXL_FB_CTRL_PBR_FLIT)); + + w = get_conf_word(d, where + PCI_CXL_FB_PORT_STATUS); + printf("\n\t\tFBSta:\tCache%c IO%c Mem%c SynHdrByp%c DrftBuf%c 68BFlit%c MltLogDev%c", + FLAG(w, PCI_CXL_FB_STAT_CACHE), FLAG(w, PCI_CXL_FB_STAT_IO), + FLAG(w, PCI_CXL_FB_STAT_MEM), FLAG(w, PCI_CXL_FB_STAT_SYNC_HDR_BYP), + FLAG(w, PCI_CXL_FB_STAT_DRFT_BUF), FLAG(w, PCI_CXL_FB_STAT_68B_FLIT), + FLAG(w, PCI_CXL_FB_STAT_MULT_LOG_DEV)); + + if (rev > 1) + printf(" 256BFlit%c PBRFlit%c", + FLAG(w, PCI_CXL_FB_STAT_256B_FLIT), FLAG(w, PCI_CXL_FB_STAT_PBR_FLIT)); + + l = get_conf_long(d, where + PCI_CXL_FB_MOD_TS_DATA); + data = BITS(l, 0, 24); + printf("\n\t\tFBModTS:\tReceived FB Data: %06x\n", (unsigned int)data); + + if (rev > 1) + { + u8 nop; + + l = get_conf_long(d, where + PCI_CXL_FB_PORT_CAP2); + printf("\t\tFBCap2:\tNOPHint%c\n", FLAG(l, PCI_CXL_FB_CAP2_NOP_HINT)); + + l = get_conf_long(d, where + PCI_CXL_FB_PORT_CTRL2); + printf("\t\tFBCtl2:\tNOPHint%c\n", FLAG(l, PCI_CXL_FB_CTRL2_NOP_HINT)); + + l = get_conf_long(d, where + PCI_CXL_FB_PORT_STATUS2); + nop = BITS(l, 0, 2); + printf("\t\tFBSta2:\tNOPHintInfo: %x\n", nop); + } +} + +static void +dvsec_cxl_mld(struct device *d, int where) +{ + u16 w; + + w = get_conf_word(d, where + PCI_CXL_MLD_NUM_LD); + + /* Encodings greater than 16 are reserved */ + if (w && w <= PCI_CXL_MLD_MAX_LD) + printf("\t\tNumLogDevs: %d\n", w); +} + +static void +dvsec_cxl_function_map(struct device *d, int where) +{ + + printf("\t\tFuncMap 0: %08x\n", + (unsigned int)(get_conf_word(d, where + PCI_CXL_FUN_MAP_REG_0))); + + printf("\t\tFuncMap 1: %08x\n", + (unsigned int)(get_conf_word(d, where + PCI_CXL_FUN_MAP_REG_1))); + + printf("\t\tFuncMap 2: %08x\n", + (unsigned int)(get_conf_word(d, where + PCI_CXL_FUN_MAP_REG_2))); + + printf("\t\tFuncMap 3: %08x\n", + (unsigned int)(get_conf_word(d, where + PCI_CXL_FUN_MAP_REG_3))); + + printf("\t\tFuncMap 4: %08x\n", + (unsigned int)(get_conf_word(d, where + PCI_CXL_FUN_MAP_REG_4))); + + printf("\t\tFuncMap 5: %08x\n", + (unsigned int)(get_conf_word(d, where + PCI_CXL_FUN_MAP_REG_5))); + + printf("\t\tFuncMap 6: %08x\n", + (unsigned int)(get_conf_word(d, where + PCI_CXL_FUN_MAP_REG_6))); + + printf("\t\tFuncMap 7: %08x\n", + (unsigned int)(get_conf_word(d, where + PCI_CXL_FUN_MAP_REG_7))); +} + +static void cap_dvsec_cxl(struct device *d, int id, int rev, int where, int len) { printf(": CXL\n"); @@ -854,25 +1071,25 @@ cap_dvsec_cxl(struct device *d, int id, int rev, int where, int len) dvsec_cxl_device(d, rev, where, len); break; case 2: - printf("\t\tNon-CXL Function Map DVSEC\n"); + dvsec_cxl_function_map(d, where); break; case 3: dvsec_cxl_port(d, where, len); break; case 4: - printf("\t\tGPF DVSEC for Port\n"); + dvsec_cxl_gpf_port(d, where); break; case 5: - printf("\t\tGPF DVSEC for Device\n"); + dvsec_cxl_gpf_device(d, where); break; case 7: - printf("\t\tPCIe DVSEC Flex Bus Port\n"); + dvsec_cxl_flex_bus(d, where, rev); break; case 8: dvsec_cxl_register_locator(d, where, len); break; case 9: - printf("\t\tMLD DVSEC\n"); + dvsec_cxl_mld(d, where); break; default: printf("\t\tUnknown ID %04x\n", id); @@ -90,6 +90,31 @@ systems. Process needs to have Debug privilege, which local Administrators have by default. Not available on 64-bit systems and neither on recent 32-bit systems. Only devices from the first domain are accessible and only first 256 bytes of the PCI configuration space is accessible via this method. +.TP +.B win32-kldbg +Access to the PCI configuration space via Kernel Local Debugging Driver +kldbgdrv.sys. This driver is not part of the Windows system but is part of +the Microsoft WinDbg tool. It is required to have kldbgdrv.sys driver installed +in the system32 directory or to have windbg.exe or kd.exe binary in PATH. +kldbgdrv.sys driver has some restrictions. Process needs to have Debug privilege +and Windows system has to be booted with Debugging option. Debugging option can +be enabled by calling (takes effect after next boot): +.B bcdedit /debug on +.IP +Download links for WinDbg 6.12.2.633 standalone installer from Microsoft Windows +SDK for Windows 7 and .NET Framework 4: +.br +amd64: https://download.microsoft.com/download/A/6/A/A6AC035D-DA3F-4F0C-ADA4-37C8E5D34E3D/setup/WinSDKDebuggingTools_amd64/dbg_amd64.msi +.br +ia64: https://download.microsoft.com/download/A/6/A/A6AC035D-DA3F-4F0C-ADA4-37C8E5D34E3D/setup/WinSDKDebuggingTools_ia64/dbg_ia64.msi +.br +x86: https://download.microsoft.com/download/A/6/A/A6AC035D-DA3F-4F0C-ADA4-37C8E5D34E3D/setup/WinSDKDebuggingTools/dbg_x86.msi +.IP +Archived download links of previous WinDbg versions: +.br +https://web.archive.org/web/20110221133326/https://www.microsoft.com/whdc/devtools/debugging/installx86.mspx +.br +https://web.archive.org/web/20110214012715/https://www.microsoft.com/whdc/devtools/debugging/install64bit.mspx .SH PARAMETERS diff --git a/tests/cap-dvsec-cxl b/tests/cap-dvsec-cxl index a24e3fb..c5fa9e7 100644 --- a/tests/cap-dvsec-cxl +++ b/tests/cap-dvsec-cxl @@ -351,3 +351,261 @@ fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +7f:00.0 CXL: Xilinx Corporation Device c084 (rev 70) (prog-if 10 [CXL Memory Device (CXL 2.x)]) +00: ee 10 84 c0 02 00 10 00 70 10 02 05 10 00 00 00 +10: 0c 00 00 b0 80 03 00 00 0c 00 10 b0 80 03 00 00 +20: 00 00 00 00 00 00 00 00 00 00 00 00 ee 10 84 c0 +30: 00 00 00 00 80 00 00 00 00 00 00 00 05 01 00 00 +40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +80: 10 e0 92 00 21 80 2c 11 30 29 00 00 00 00 00 00 +90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +a0: 00 00 00 00 10 00 11 00 00 00 00 00 00 00 00 00 +b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +e0: 05 f8 88 00 00 00 00 00 00 00 00 00 00 00 00 00 +f0: 00 00 00 00 00 00 00 00 01 00 03 00 08 00 00 00 +100: 0b 00 81 12 56 15 81 00 00 00 00 00 00 00 00 00 +110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +120: 00 00 00 00 00 00 00 00 0e 00 01 1e 00 00 00 00 +130: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +160: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +190: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +1a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +1b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +1c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +1d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +1e0: 25 00 01 20 01 00 00 80 01 00 00 80 00 00 00 00 +1f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +200: 01 00 02 45 00 00 00 00 00 00 40 00 10 20 46 00 +210: 00 00 00 00 00 60 00 00 00 00 00 00 00 00 00 00 +220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +290: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +2a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +2b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +2c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +2d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +2e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +2f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +310: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +320: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +330: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +340: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +350: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +360: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +370: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +390: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +3a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +3b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +3c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +3d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +3e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +3f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +410: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +420: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +430: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +440: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +450: 2e 00 01 50 03 00 00 00 00 00 00 00 02 00 00 00 +460: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +470: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +490: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +4a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +4b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +4c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +4d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +4e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +4f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +500: 23 00 01 54 98 1e 81 03 00 00 1e 40 06 00 00 00 +510: 00 00 00 80 00 00 00 00 04 00 00 00 03 00 00 00 +520: 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 +530: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +540: 23 00 01 56 98 1e 41 01 07 00 26 00 26 00 06 00 +550: 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +560: 23 00 01 59 98 1e 40 02 08 00 00 00 00 01 00 00 +570: 00 00 00 00 00 03 01 00 00 00 00 00 00 00 00 00 +580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +590: 23 00 01 00 98 1e 00 01 05 00 03 02 00 00 00 00 +5a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +5b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +5c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +5d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +5e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +5f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +660: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +670: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +690: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +6a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +6b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +6c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +6d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +6e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +6f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +710: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +720: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +730: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +740: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +750: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +760: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +770: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +790: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +7a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +7b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +7c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +7d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +7e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +7f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +820: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +830: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +840: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +850: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +860: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +870: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +890: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +8a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +8b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +8c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +8d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +8e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +8f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +910: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +920: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +930: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +940: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +950: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +960: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +970: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +990: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +9a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +9b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +9c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +9d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +9e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +9f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +a10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +a20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +a30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +a40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +a50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +a60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +a70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +a80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +a90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +aa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ab0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ac0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ad0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ae0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +af0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +b10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +b20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +b30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +b40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +b50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +b60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +b70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +b80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +b90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ba0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +bb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +bc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +bd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +be0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +bf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +c10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +c20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +c30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +c40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +c50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +c60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +c70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +c90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ca0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +cb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +cc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +cd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ce0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +cf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +d10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +d20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +d30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +d40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +d50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +d60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +d70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +d90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +da0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +db0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +dc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +dd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +de0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +df0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +e10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +e20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +e30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +e40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +e50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +e60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +e70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +e90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ea0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +eb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ec0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ed0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ee0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ef0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +f10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +f20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +f30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +f40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +f50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +f60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +f70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +f90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +fa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |