Patchwork [v3,1/5] I/O Hook: core functions and Register Override

login
register
mail settings
Submitter Wang, Rui Y
Date April 15, 2014, 1:48 p.m.
Message ID <1397569691-24691-2-git-send-email-rui.y.wang@intel.com>
Download mbox | patch
Permalink /patch/339285/
State Not Applicable
Headers show

Comments

Wang, Rui Y - April 15, 2014, 1:48 p.m.
This is the 3rd version of I/O Hook, a patch set aimed at intercepting
h/w access by the OS. Some examples of how it can be used:
        1) To emulate h/w events (e.g. hotplug)
        2) To inject h/w errors to the kernel
        3) To trace h/w access by the OS for performance tuning or debugging
Details of the examples can be found in Documentation/PCI/iohook.txt.

A set of user space tools using I/O Hook for various use cases will be hosted
on https://github.com/iohook. It initially contains inject-aer which takes the
trace event output of /sys/kernel/debug/tracing/events/ras/aer_event/ and
regenerate the same PCIE AER event so that PCIE AER can be easily tested.

This patch provides a hook in the core h/w access functions in the kernel.
It also introduces Register Override, which is a set of bits defined in RAM
to override the real value of a h/w register. With the hook in place, access
to h/w registers can be redirected to Register Overrides with user-defined
values, so that h/w states can be emulated easily.

A Register Override can be defined in whatever bit-width, identified by its
address, bitmask, initial value and attributs like read-only, read-write,
write-clear, etc., similar to how a hardware register behaves when accessed.

Jump Label is used, so when the hook is disabled (by default), this adds
only a NOP to the core functions, with zero performance penalty.

This patch is the first step towards the goal of emulating h/w events.

Signed-off-by: Rui Wang <rui.y.wang@intel.com>
---
 arch/Kconfig                      |   10 +
 arch/x86/boot/compressed/Makefile |    1 +
 arch/x86/include/asm/io.h         |   57 +++++-
 arch/x86/vdso/Makefile            |    2 +
 drivers/misc/Makefile             |    1 +
 drivers/misc/iohook/Makefile      |    1 +
 drivers/misc/iohook/iohook.c      |  392 +++++++++++++++++++++++++++++++++++++
 drivers/misc/iohook/iohook.h      |    6 +
 drivers/pci/access.c              |   66 ++++++
 include/linux/reg_ovrd.h          |   54 +++++
 10 files changed, 588 insertions(+), 2 deletions(-)
 create mode 100644 drivers/misc/iohook/Makefile
 create mode 100644 drivers/misc/iohook/iohook.c
 create mode 100644 drivers/misc/iohook/iohook.h
 create mode 100644 include/linux/reg_ovrd.h

Patch

diff --git a/arch/Kconfig b/arch/Kconfig
index 97ff872..55b224a 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -46,6 +46,16 @@  config KPROBES
 	  for kernel debugging, non-intrusive instrumentation and testing.
 	  If in doubt, say "N".
 
+config IO_HOOK
+	bool "Method for emulating hardware events"
+	default n
+	depends on PCI
+	help
+	  I/O Hook is a mechanism to intercept i/o register access functions
+	  in the kernel. By overriding h/w register bits with user-defined
+	  bits in RAM called Register Override, it is possible to emulate
+	  h/w states without modifying the driver specific to that hardware.
+
 config JUMP_LABEL
        bool "Optimize very unlikely/likely branches"
        depends on HAVE_ARCH_JUMP_LABEL
diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile
index 0fcd913..ea53270 100644
--- a/arch/x86/boot/compressed/Makefile
+++ b/arch/x86/boot/compressed/Makefile
@@ -10,6 +10,7 @@  targets := vmlinux vmlinux.bin vmlinux.bin.gz vmlinux.bin.bz2 vmlinux.bin.lzma \
 KBUILD_CFLAGS := -m$(BITS) -D__KERNEL__ $(LINUX_INCLUDE) -O2
 KBUILD_CFLAGS += -fno-strict-aliasing -fPIC
 KBUILD_CFLAGS += -DDISABLE_BRANCH_PROFILING
+KBUILD_CFLAGS += -DNO_IO_HOOK
 cflags-$(CONFIG_X86_32) := -march=i386
 cflags-$(CONFIG_X86_64) := -mcmodel=small
 KBUILD_CFLAGS += $(cflags-y)
diff --git a/arch/x86/include/asm/io.h b/arch/x86/include/asm/io.h
index b8237d8..e3db0ab 100644
--- a/arch/x86/include/asm/io.h
+++ b/arch/x86/include/asm/io.h
@@ -41,14 +41,41 @@ 
 #include <asm/page.h>
 #include <asm/early_ioremap.h>
 
+#if !defined(NO_IO_HOOK) && defined(CONFIG_IO_HOOK)
+#include <linux/jump_label.h>
+#include <linux/reg_ovrd.h>
+
+#define mem_read_ovrd(type, addr) \
+{ \
+	type val;\
+	if (static_key_false(&ovrdhw_enabled)			\
+		&& !read_ovrd_common(OVRD_SPACE_MEM, (u64)addr,	\
+			sizeof(type), &val, NULL))		\
+		return val;					\
+}
+
+#define mem_write_ovrd(type, addr, val)	\
+{ \
+	if (static_key_false(&ovrdhw_enabled)			\
+		&& !write_ovrd_common(OVRD_SPACE_MEM, (u64)addr,\
+			sizeof(type), &val, NULL))		\
+		return;							\
+}
+#else /* CONFIG_IO_HOOK */
+#define mem_read_ovrd(type, addr)
+#define mem_write_ovrd(type, addr, val)
+#endif /* CONFIG_IO_HOOK */
+
 #define build_mmio_read(name, size, type, reg, barrier) \
 static inline type name(const volatile void __iomem *addr) \
-{ type ret; asm volatile("mov" size " %1,%0":reg (ret) \
+{ type ret; mem_read_ovrd(type, addr);	\
+asm volatile("mov" size " %1,%0" : reg(ret) \
 :"m" (*(volatile type __force *)addr) barrier); return ret; }
 
 #define build_mmio_write(name, size, type, reg, barrier) \
 static inline void name(type val, volatile void __iomem *addr) \
-{ asm volatile("mov" size " %0,%1": :reg (val), \
+{ mem_write_ovrd(type, addr, val);	\
+asm volatile("mov" size " %0,%1" : : reg(val), \
 "m" (*(volatile type __force *)addr) barrier); }
 
 build_mmio_read(readb, "b", unsigned char, "=q", :"memory")
@@ -266,9 +293,34 @@  static inline void slow_down_io(void)
 
 #endif
 
+#if !defined(NO_IO_HOOK) && defined(CONFIG_IO_HOOK)
+
+#define io_write_ovrd(type, value, port)				\
+{									\
+	if (static_key_false(&ovrdhw_enabled)				\
+		&& !write_ovrd_common(OVRD_SPACE_IO, (u64)port,		\
+			sizeof(type), &value, NULL))			\
+		return;							\
+}
+
+#define io_read_ovrd(type, port)					\
+{									\
+	type val;							\
+	if (static_key_false(&ovrdhw_enabled)				\
+		&& !read_ovrd_common(OVRD_SPACE_IO, (u64)port,		\
+			sizeof(type), &val, NULL))			\
+		return val;						\
+}
+
+#else
+#define io_write_ovrd(type, value, port)
+#define io_read_ovrd(type, port)
+#endif
+
 #define BUILDIO(bwl, bw, type)						\
 static inline void out##bwl(unsigned type value, int port)		\
 {									\
+	io_write_ovrd(type, value, port);				\
 	asm volatile("out" #bwl " %" #bw "0, %w1"			\
 		     : : "a"(value), "Nd"(port));			\
 }									\
@@ -276,6 +328,7 @@  static inline void out##bwl(unsigned type value, int port)		\
 static inline unsigned type in##bwl(int port)				\
 {									\
 	unsigned type value;						\
+	io_read_ovrd(type, port);					\
 	asm volatile("in" #bwl " %w1, %" #bw "0"			\
 		     : "=a"(value) : "Nd"(port));			\
 	return value;							\
diff --git a/arch/x86/vdso/Makefile b/arch/x86/vdso/Makefile
index c580d12..521405d 100644
--- a/arch/x86/vdso/Makefile
+++ b/arch/x86/vdso/Makefile
@@ -4,6 +4,8 @@ 
 
 KBUILD_CFLAGS += $(DISABLE_LTO)
 
+KBUILD_CFLAGS += -DNO_IO_HOOK
+
 VDSO64-$(CONFIG_X86_64)		:= y
 VDSOX32-$(CONFIG_X86_X32_ABI)	:= y
 VDSO32-$(CONFIG_X86_32)		:= y
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 7eb4b69..baaa135 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -49,6 +49,7 @@  obj-y				+= carma/
 obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o
 obj-$(CONFIG_ALTERA_STAPL)	+=altera-stapl/
 obj-$(CONFIG_INTEL_MEI)		+= mei/
+obj-$(CONFIG_IO_HOOK)		+= iohook/
 obj-$(CONFIG_VMWARE_VMCI)	+= vmw_vmci/
 obj-$(CONFIG_LATTICE_ECP3_CONFIG)	+= lattice-ecp3-config.o
 obj-$(CONFIG_SRAM)		+= sram.o
diff --git a/drivers/misc/iohook/Makefile b/drivers/misc/iohook/Makefile
new file mode 100644
index 0000000..80e2a7d
--- /dev/null
+++ b/drivers/misc/iohook/Makefile
@@ -0,0 +1 @@ 
+obj-$(CONFIG_IO_HOOK) += iohook.o
diff --git a/drivers/misc/iohook/iohook.c b/drivers/misc/iohook/iohook.c
new file mode 100644
index 0000000..e6a626f
--- /dev/null
+++ b/drivers/misc/iohook/iohook.c
@@ -0,0 +1,392 @@ 
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/reg_ovrd.h>
+#include <linux/pci.h>
+#include "iohook.h"
+
+static DEFINE_RAW_SPINLOCK(io_hook_lock);
+
+LIST_HEAD(ovrd_io_reg_map);
+LIST_HEAD(ovrd_mem_reg_map);
+LIST_HEAD(ovrd_pci_conf_reg_map);
+
+struct static_key ovrdhw_enabled = STATIC_KEY_INIT_FALSE;
+EXPORT_SYMBOL(ovrdhw_enabled);
+
+/* len should only be 1, 2, 4, 8 */
+static int mem_read(u64 address, int len, void *data)
+{
+	int ret = 0;
+
+	switch (len) {
+	case 1:
+		*(u8 *)data = *(u8 *)address;
+		break;
+	case 2:
+		*(u16 *)data = *(u16 *)address;
+		break;
+	case 4:
+		*(u32 *)data = *(u32 *)address;
+		break;
+	case 8:
+		*(u64 *)data = *(u64 *)address;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+
+}
+
+static int mem_write(u64 address, int len, void *value)
+{
+	int ret = 0;
+
+	switch (len) {
+	case 1:
+		*(u8 *)address = *(u8 *)value;
+		break;
+	case 2:
+		*(u16 *)address = *(u16 *)value;
+		break;
+	case 4:
+		*(u32 *)address = *(u32 *)value;
+		break;
+	case 8:
+		*(u64 *)address = *(u64 *)value;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+
+}
+
+#ifdef CONFIG_X86
+
+/* len should only be 1, 2, 4 */
+static int io_read(u64 address, int len, void *data)
+{
+	int ret = 0;
+	u16 port;
+	u8 bvalue;
+	u16 wvalue;
+	u32 lvalue;
+
+
+	port = (u16)address;
+
+	switch (len) {
+	case 1:
+		asm volatile ("inb %w1, %b0" : "=a"(bvalue) : "Nd"(port));
+		*(u8 *)data = bvalue;
+		break;
+	case 2:
+		asm volatile ("inw %w1, %w0" : "=a"(wvalue) : "Nd"(port));
+		*(u16 *)data = wvalue;
+		break;
+	case 4:
+		asm volatile ("inl %w1, %0" : "=a"(lvalue) : "Nd"(port));
+		*(u32 *)data = lvalue;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+
+}
+
+static int io_write(u64 address, int len, void *data)
+{
+	int ret = 0;
+	u8 bvalue;
+	u16 wvalue, port;
+	u32 lvalue;
+
+	port = (u16)address;
+
+	switch (len) {
+	case 1:
+		bvalue = *(u8 *)data;
+		asm volatile ("outb %b0, %w1" : : "a"(bvalue), "Nd"(port));
+		break;
+	case 2:
+		wvalue = *(u16 *)data;
+		asm volatile ("outw %w0, %w1" : : "a"(wvalue), "Nd"(port));
+		break;
+	case 4:
+		lvalue = *(u32 *)data;
+		asm volatile ("outl %0, %w1" : : "a"(lvalue), "Nd"(port));
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+
+}
+
+#else
+
+static int io_read(u64 address, int len, void *data)
+{
+	return -EINVAL;
+}
+
+static int io_write(u64 address, int len, void *data)
+{
+	return -EINVAL;
+}
+
+#endif /* CONFIG_X86 */
+
+static u64 v2p(u64 vaddr)
+{
+	return slow_virt_to_phys((void *)vaddr);
+}
+
+/* shift left if i>=0, otherwise shift right */
+#define BYTE_SHIFT(value, i) \
+	((i) >= 0 ? (value) << (i)*8 : (value) >> (-i)*8)
+
+int read_ovrd_common(int spaceid, u64 address, int len, void *value, void *bus)
+{
+	struct list_head *ovrd_list;
+	struct reg_ovrd *ovrd_reg;
+	struct pci_bus	*pcib;
+	unsigned long lock_flags = 0, flags = 0;
+	u64 faddress, vaddr = 0;
+	u64 data, bit_mask, attrib, val;
+	unsigned int devfn = 0, pos = 0;
+	int i, flength, res, ret;
+
+	ret = -EINVAL;
+
+	if (spaceid == OVRD_SPACE_MEM) {
+		/* in the case of memory, 'address' is virtual */
+		vaddr = address;
+		address = v2p(address);
+		ovrd_list = &ovrd_mem_reg_map;
+	} else if (spaceid == OVRD_SPACE_IO) {
+		ovrd_list = &ovrd_io_reg_map;
+	} else if (spaceid == OVRD_SPACE_PCICONF) {
+		devfn = PCI_DECODE_DEVFN(address);
+		pos = PCI_DECODE_POS(address);
+		ovrd_list = &ovrd_pci_conf_reg_map;
+	} else {
+		return ret;
+	}
+
+	raw_spin_lock_irqsave(&io_hook_lock, lock_flags);
+	list_for_each_entry(ovrd_reg, ovrd_list, node) {
+
+		faddress = ovrd_reg->address;
+		flength = ovrd_reg->length;
+		val = ovrd_reg->val;
+		bit_mask = ovrd_reg->bit_mask;
+		attrib = ovrd_reg->attrib;
+
+		if (address >= faddress + flength ||
+			address + len <= faddress) {
+			/* no overlap, skip */
+			continue;
+		}
+
+		raw_spin_unlock_irqrestore(&io_hook_lock,
+			lock_flags);
+
+		/* at least one byte falls into the overridden range */
+		data = 0;
+		ret = 0;
+		if (!(address >= faddress && address+len <= faddress+flength &&
+			bit_mask == (u64)((1<<flength*8) - 1))) {
+			/* partially overridden. Read from HW for real bits */
+
+			if (spaceid == OVRD_SPACE_MEM) {
+				res = mem_read(vaddr, len, &data);
+			} else if (spaceid == OVRD_SPACE_IO) {
+				res = io_read(address, len, &data);
+			} else if (spaceid == OVRD_SPACE_PCICONF) {
+				raw_spin_lock_irqsave(&pci_lock, flags);
+				pcib = (struct pci_bus *)bus;
+				res = pcib->ops->read(pcib, devfn, pos, len,
+							(u32 *)&data);
+				raw_spin_unlock_irqrestore(&pci_lock, flags);
+			} else
+				goto out;
+
+			if (res) {
+				/* failed to read from HW, clear the result */
+				data = 0;
+			}
+		}
+
+		for (i = 0; i < len; i++) {
+			if (address+i >= faddress &&
+				address+i < faddress+flength) {
+				int j, k;
+
+				j = address + i - faddress;
+				k = faddress - address;
+				if (flength <= 8) {
+					/* <= 8 bytes, use bit_mask */
+					u64 byte_mask;
+
+					byte_mask =
+						bit_mask & BYTE_SHIFT(0xff, j);
+					data &= ~BYTE_SHIFT(byte_mask, k);
+					data |= BYTE_SHIFT(val & byte_mask, k);
+					if (attrib == OVRD_RC)
+						ovrd_reg->val &= ~byte_mask;
+
+				} else {
+					/* If flength is > 8, this is
+					 * used to override a consecutive
+					 * range of readonly identical
+					 * bytes.
+					 */
+					data |= (val & 0xff) << i*8;
+				}
+			}
+		}
+
+		switch (len) {
+		case 1:
+			*(u8 *)value = (u8)data;
+			break;
+		case 2:
+			*(u16 *)value = (u16)data;
+			break;
+		case 4:
+			*(u32 *)value = (u32)data;
+			break;
+		case 8:
+			*(u64 *)value = data;
+			break;
+		default:
+			ret = -EINVAL;
+			goto out;
+		}
+
+		raw_spin_lock_irqsave(&io_hook_lock,
+			lock_flags);
+	}
+
+	raw_spin_unlock_irqrestore(&io_hook_lock, lock_flags);
+out:
+	return ret;
+}
+EXPORT_SYMBOL(read_ovrd_common);
+
+int write_ovrd_common(int spaceid, u64 address, int len, void *data, void *bus)
+{
+	struct list_head *ovrd_list;
+	struct reg_ovrd *ovrd_reg;
+	struct pci_bus	*pcib;
+	unsigned long lock_flags = 0, flags = 0;
+	u64 faddress, vaddr = 0;
+	u64  bit_mask, val, attrib;
+	unsigned int devfn = 0, pos = 0;
+	int i, flength, res, ret;
+	u64 value;
+
+	ret = -EINVAL;
+
+	if (spaceid == OVRD_SPACE_MEM) {
+		/* in the case of memory, 'address' is virtual */
+		vaddr = address;
+		address = v2p(address);
+		ovrd_list = &ovrd_mem_reg_map;
+	} else if (spaceid == OVRD_SPACE_IO) {
+		ovrd_list = &ovrd_io_reg_map;
+	} else if (spaceid == OVRD_SPACE_PCICONF) {
+		devfn = PCI_DECODE_DEVFN(address);
+		pos = PCI_DECODE_POS(address);
+		ovrd_list = &ovrd_pci_conf_reg_map;
+	} else {
+		return ret;
+	}
+
+	raw_spin_lock_irqsave(&io_hook_lock, lock_flags);
+	list_for_each_entry(ovrd_reg, ovrd_list, node) {
+
+		faddress = ovrd_reg->address;
+		flength = ovrd_reg->length;
+		val = ovrd_reg->val;
+		bit_mask = ovrd_reg->bit_mask;
+		attrib = ovrd_reg->attrib;
+		value = *(u64 *)data;
+
+		if (address >= faddress + flength ||
+			address + len <= faddress) {
+			/* no overlap, skip */
+			continue;
+		}
+
+		ret = 0;
+
+		if (!(address >= faddress && address+len <= faddress+flength &&
+			bit_mask == (u64)((1<<flength*8) - 1))) {
+			/* partially overridden. write to HW for real bits */
+			if (spaceid == OVRD_SPACE_MEM) {
+				res = mem_write(vaddr, len, data);
+			} else if (spaceid == OVRD_SPACE_IO) {
+				res = io_write(address, len, data);
+			} else if (spaceid == OVRD_SPACE_PCICONF) {
+				raw_spin_unlock_irqrestore(&io_hook_lock,
+					lock_flags);
+				raw_spin_lock_irqsave(&pci_lock, flags);
+				pcib = (struct pci_bus *)bus;
+				pcib->ops->write(pcib, devfn, pos, len,
+							(u32)value);
+				raw_spin_unlock_irqrestore(&pci_lock, flags);
+				raw_spin_lock_irqsave(&io_hook_lock,
+					lock_flags);
+			} else
+				break;
+		}
+
+		for (i = 0; i < len; i++) {
+			if (address+i >= faddress &&
+				address+i < faddress+flength) {
+				int j, k;
+
+				j = address + i - faddress;
+				k = faddress - address;
+				if (flength <= 8) {
+					/* <= 8 bytes, use bit_mask */
+					u64 byte_mask;
+
+					byte_mask =
+						bit_mask & BYTE_SHIFT(0xff, j);
+					if (attrib == OVRD_RW) {
+						ovrd_reg->val &= ~byte_mask;
+						ovrd_reg->val |=
+							BYTE_SHIFT(value, k)
+								& byte_mask;
+					} else if (attrib == OVRD_WC) {
+						ovrd_reg->val &=
+							~(BYTE_SHIFT(value, k)
+							& byte_mask);
+					}
+
+				}
+				/* if flength > 8, must be OVRD_RO */
+			}
+		}
+
+	}
+
+	raw_spin_unlock_irqrestore(&io_hook_lock, lock_flags);
+
+	return ret;
+}
+EXPORT_SYMBOL(write_ovrd_common);
diff --git a/drivers/misc/iohook/iohook.h b/drivers/misc/iohook/iohook.h
new file mode 100644
index 0000000..46c97be
--- /dev/null
+++ b/drivers/misc/iohook/iohook.h
@@ -0,0 +1,6 @@ 
+#ifndef _IOHOOK_H
+#define _IOHOOK_H
+
+extern raw_spinlock_t pci_lock;
+
+#endif
diff --git a/drivers/pci/access.c b/drivers/pci/access.c
index 7f8b78c..c1e0a2b 100644
--- a/drivers/pci/access.c
+++ b/drivers/pci/access.c
@@ -15,6 +15,68 @@ 
 
 DEFINE_RAW_SPINLOCK(pci_lock);
 
+#ifdef CONFIG_IO_HOOK
+#include <linux/reg_ovrd.h>
+#include <linux/jump_label.h>
+
+int pci_bus_read_config_ovrd(struct pci_bus *bus, unsigned int devfn,
+		int pos, int len, void *value)
+{
+	u64 address;
+	int ret;
+
+	address = PCI_ENCODE_ADDR(pci_domain_nr(bus), bus->number, devfn, pos);
+
+	ret = read_ovrd_common(OVRD_SPACE_PCICONF, address, len,
+			value, (void *)bus);
+	if (!ret)
+		pr_info("read from %x:%x+%x-%x, ret=%x, val=0x%x\n",
+			bus->number, devfn, pos, len, ret, *(u32 *)value);
+	return ret;
+
+}
+
+int pci_bus_write_config_ovrd(struct pci_bus *bus, unsigned int devfn,
+		int pos, int len, u32 value)
+{
+	u64 address;
+	int ret;
+
+	address = PCI_ENCODE_ADDR(pci_domain_nr(bus), bus->number, devfn, pos);
+	ret = write_ovrd_common(OVRD_SPACE_PCICONF, address, len,
+			&value, (void *)bus);
+	if (!ret)
+		pr_info("write to %x:%x+%x-%x, ret=0x%x, val=0x%x\n",
+			bus->number, devfn, pos, len, ret, value);
+	return ret;
+
+
+}
+
+
+#define pci_read_ovrd(bus, devfn, pos, len, value)			\
+{ \
+	if (static_key_false(&ovrdhw_enabled)			\
+		&& !pci_bus_read_config_ovrd(bus, devfn, pos,	\
+		len, value))						\
+		return 0; \
+}
+
+#define pci_write_ovrd(bus, devfn, pos, len, value)			\
+{ \
+	if (static_key_false(&ovrdhw_enabled)			\
+		&& !pci_bus_write_config_ovrd(bus, devfn, pos,	\
+		len, value))						\
+		return 0; \
+}
+
+#else
+
+#define pci_read_ovrd(bus, devfn, pos, len, value)
+#define pci_write_ovrd(bus, devfn, pos, len, value)
+
+#endif	/* CONFIG_IO_HOOK */
+
 /*
  *  Wrappers for all PCI configuration access functions.  They just check
  *  alignment, do locking and call the low-level functions pointed to
@@ -33,6 +95,7 @@  int pci_bus_read_config_##size \
 	unsigned long flags;						\
 	u32 data = 0;							\
 	if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;	\
+	pci_read_ovrd(bus, devfn, pos, len, value);			\
 	raw_spin_lock_irqsave(&pci_lock, flags);			\
 	res = bus->ops->read(bus, devfn, pos, len, &data);		\
 	*value = (type)data;						\
@@ -47,6 +110,7 @@  int pci_bus_write_config_##size \
 	int res;							\
 	unsigned long flags;						\
 	if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;	\
+	pci_write_ovrd(bus, devfn, pos, len, value)			\
 	raw_spin_lock_irqsave(&pci_lock, flags);			\
 	res = bus->ops->write(bus, devfn, pos, len, value);		\
 	raw_spin_unlock_irqrestore(&pci_lock, flags);		\
@@ -152,6 +216,7 @@  int pci_user_read_config_##size						\
 	u32 data = -1;							\
 	if (PCI_##size##_BAD)						\
 		return -EINVAL;						\
+	pci_read_ovrd(dev->bus, dev->devfn, pos, sizeof(type), (void *)val);\
 	raw_spin_lock_irq(&pci_lock);				\
 	if (unlikely(dev->block_cfg_access))				\
 		pci_wait_cfg(dev);					\
@@ -173,6 +238,7 @@  int pci_user_write_config_##size					\
 	int ret = -EIO;							\
 	if (PCI_##size##_BAD)						\
 		return -EINVAL;						\
+	pci_write_ovrd(dev->bus, dev->devfn, pos, sizeof(type), val);\
 	raw_spin_lock_irq(&pci_lock);				\
 	if (unlikely(dev->block_cfg_access))				\
 		pci_wait_cfg(dev);					\
diff --git a/include/linux/reg_ovrd.h b/include/linux/reg_ovrd.h
new file mode 100644
index 0000000..2707d6c
--- /dev/null
+++ b/include/linux/reg_ovrd.h
@@ -0,0 +1,54 @@ 
+#ifndef __REG_OVRD_H__
+#define __REG_OVRD_H__
+
+#include <linux/types.h>
+#include <linux/spinlock_types.h>
+
+enum ovrd_attrib {
+	OVRD_RW,	/* readwrite */
+	OVRD_RO,	/* readonly */
+	OVRD_RC,	/* read clear */
+	OVRD_WC,	/* write clear */
+	OVRD_TC,	/* trace only */
+};
+
+/*
+ * address - Starting phys address of the h/w register
+ * length - # of bytes to be overridden
+ * val	-  When length <= 8, use (val & bit_mask) as the overridden value.
+	   When length > 8, we're overriding a range of bytes to a single
+	   readonly value. So attrib must be OVRD_RO, and (val & 0xff)
+	   is the contiguous readonly value.
+ * bit_mask - used when length <= 8 to indicate which bits are being overridden.
+	      unused when length > 8
+ * attrib -   when length <=8, is the common attribute of the overridden
+	      bits matching bit_mask. When length > 8, must be OVRD_RO
+ */
+struct reg_ovrd {
+	struct list_head node;
+	u64 address;
+	u64  val;
+	u64  bit_mask;
+	u32 length;
+	u8  attrib;
+};
+
+/* address space id */
+#define	OVRD_SPACE_IO		0
+#define	OVRD_SPACE_MEM		1
+#define	OVRD_SPACE_PCICONF	2
+
+#define	PCI_ENCODE_ADDR(domain, bus, devfn, pos)	\
+		(((u64)(domain))<<32|(bus)<<20|(devfn)<<12|(pos))
+#define PCI_DECODE_POS(x)	((u16)((x) & ((1 << 12) - 1)))
+#define PCI_DECODE_DEVFN(x)	((u8)(((x) >> 12) & 0xff))
+#define PCI_DECODE_BUSN(x)	((u8)(((x) >> 20) & 0xff))
+#define PCI_DECODE_DOMAIN(x)	((u32)((x) >> 32))
+
+extern int read_ovrd_common(int spaceid, u64 address, int len, void *value,
+		void *bus);
+extern int write_ovrd_common(int spaceid, u64 address, int len, void *data,
+		void *bus);
+extern struct static_key ovrdhw_enabled;
+
+#endif			/* __REG_OVRD_H__ */