LWN.net Logo

I/O hook documentation

Emulating h/w events via iohook
=================================

1. Introduction
2. How to use it
3. Use cases

1. Introduction
---------------------
IO 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.

iohook is a driver that exports sysfs interfaces used to talk to the IO Hook
in the kernel in order to emulate h/w events. Here's how it works:

The sysfs interfaces can be used to add/delete Register Overrides with user-
defined values. The user can also specify which IRQ  to  be  triggered  via
Inter-Processor Interrupt (IPI)  while  the  h/w registers are being over-
ridden. When  the irq  handler  is  triggered  by  the IPI it looks for the
registers specific to some h/w events. As long as the Register Overrides are
setup correctly, the irq  handler  will believe that the h/w is in a state
corresponding to a predefined interrupt, thus process the event.

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

A Register Override may not use every bit in a byte. Its bitmask identifies
which bits are used (overridden). The unused bits are accessed on the h/w
and combined with the overridden bits to form the final result. The reason
to support this combination is that many h/w events are controlled by only
a few bits. For example the ACPI GPEx_STS and GPEx_EN are encoded such that
each bit represents a different General Purpose Event. The user is supposed
to fully understand the side-effect, if any, of reading adjacent bits when
he or she adds a Register Override not in the entirety of a byte, a word, a
dword, or a qword.

iohook can be typically used to generate ACPI events, PCI interrupts, PCIe
AER injection etc., and can thus be used to help test RAS features like
the hotplug of CPU/MEM/IOH on machines that are not capable of generating
these events.

2. How to use it
-------------------

2.1 Compile the kernel

First compile the kernel with CONFIG_IO_HOOK and CONFIG_IO_HOOK_DRV.
Depending on your configuration, if iohook is compiled as a module, then
you'll need to load the driver first.

2.2 Sysfs interface

After the iohook driver is loaded a directory subtree is created under
/sys/kernel/debug/iohook

bash# modprobe iohook
bash# cd /sys/kernel/debug/iohook
bash# ls
io  irq  mem  pciconf  trigger

Each file is used to manage a type of resource.
'io' is used to add/show Register Overrides in IO space.
'mem' is used to add/show Register Overrides in memory space.
'pciconf' is used to add/show Register Overrides in pci config space.
'irq' is used to set the desired IRQ to be triggered via IPI.
'trigger' is used to turn on/off the IO Hook.

2.3 Add Register Overrides

A Register Override can be specified on the command line with the following
syntax (all numbers are in hex without space between each element)

for a Register Override in IO or memory space, it's specified as:

	address-length[value/mask]attribute

for a Register Override in PCI config space, it's specified as:

	domain|bus:dev.func+offset-length[value/mask]attribute

where
	address - the 64bit address of the h/w register to be overridden

	length - the number of bytes affected. Affected here means that
		 at least one bit in that byte is overridden. For 'length'
		 less than 8, the overridden bits are determined by the
		 corresponding bits set in 'mask'. Other bits are unaffected
		 and accessed on the h/w. For 'length' >= 8 then 'mask' is
		 ignored and the entire range of bytes are overridden to be
		 a single value specified by the first byte of 'value'. This
		 can be used, for example, to set the entire PCI Config
		 space of a device to 0xff.

	value -  the user-defined value to replace the content of the
		 corresponding h/w register. for 'length' < 8, only the bits
		 masked by 'mask' are used.

	mask -   is the bit-mask specifying the bits to be overridden when
		 'length' < 8.

	attribute - used to specify the attribute of the overridden bits.
		 It can be ro, rw, wc, rc to mean read-only, read-write,
		 write-clear, and read-clear respectively.

	domain - pci domain number
	bus    - pci bus number
	dev    - pci device number
	func   - pci function number
	offset - used to specify the offset of the affected bytes in the
		 PCI config space.

Multiple registers can be specified on one line with each separated by at
least one space.  For example, to override two registers in IO space at port
0x420 and port 0x428, with the former in write-clear mode and the latter in
read-only mode:

bash# cd /sys/kernel/debug/iohook
bash# echo "420-1[04/04]wc 428-1[04/04]ro" > io
The syntax is "address-length[value/mask]attribute".
Since only one bit is overridden (mask is 0x04), the affected byte is 1.
So 'length' is 1.

As another example, to add two Register Overrides in the PCI config space of
device 00:05.0 at offsets 0x130 and 0x134 respectively:

bash# echo "0000|00:05.0+130-1[01/01]wc 0000|00:05.0+134-2[0500/ffff]ro">pciconf
The syntax is "domain|bus:dev.func+offset-length[value/mask]attribute"
The first register overrides only bit0 and the second register overrides the
first 2 bytes (mask == 0xffff), with an initial value of 0x0500.

Register Overrides are disabled when added. They can be enabled by using the
'trigger' file. See below.

2.4 Add IRQ and enable the Register Overrides

To specify an IRQ to be triggered via IPI, just echo the IRQ number in decimal
to the 'irq' file. For example:

bash# cd /sys/kernel/debug/iohook
bash# echo 9 > irq
This specifies that IRQ9 be triggered after the Register Overrides are enabled.

To enable the Register Overrides in the kernel:

bash# echo 1 > trigger

This immediately enables all the Register Overrides and if an IRQ number was
specified, generate the IPI.

To disable the Register Overrides in the kernel:

bash echo 0 > trigger
This immediately disables all Register Overrides. The kernel starts to see
real h/w registers again. This does not delete the Register Overrides. They
can be re-enabled again by echo 1 > trigger.

3. Use cases
-----------------

3.1 Generate ACPI Events

A typical use case is to generate ACPI events. Suppose we want to test IOH
hotplug on a machine whose BIOS doesn't support it. We can override its DSDT
and add a GPE to notify the OS the hot-add/removal of the IOH device. We can
then use iohook to trigger the imaginary GPE and the OS will have to process
the hotplug event. (For detailed instructions on how to override DSDT see
Documentation/acpi/initrd_table_override.txt.) The following is an example:

We first extract the DSDT
bash # cat /sys/firmware/acpi/tables/DSDT > DSDT
bash # iasl -d DSDT
Now we have disassembled the DSDT into DSDT.dsl. We vi DSDT.dsl and notice
that there's a IOH device named \_SB.IOH1. Since GPEs are named _Lxx with xx
being the GPE numbers, we notice that there's no _L02 in DSDT.dsl, so we can
use this spare GPE to notify the OS the hotplug of \_SB.IOH1. We add a new
method in DSDT.dsl:

    Method (_L02, 0, NotSerialized) // _Lxx: Level-Triggered GPE
    {
	Notify (\_SB.IOH1, 0x0)     // 0x0: hot-add event
    }

ACPI uses a single interrupt (SCI) to dispatch all GPEs. The SCI IRQ number
can be found from:

bash # grep acpi /proc/interrupts | awk '{ print $1; }'
9:

which means SCI is IRQ9. What we need to do is to generate IRQ9 via IPI and
let the SCI interrupt handler call _L02. Each _Lxx has a status bit in
GPEx_STS to reflect if it is asserted and a controlling bit in GPEx_EN to
reflect if it is enabled. The SCI interrupt handler reads GPEx_STS and
GPEx_EN to decide whether to call a _Lxx. a _Lxx is called if it is both
enabled and asserted. We can use Register Overrides to override the bits
controlling _L02 in GPEx_STS and GPEx_EN so that the SCI handler will believe
that _L02 is asserted and enabled, and will call the ACPI method that we
added.

_L02's controlling bits are in GPE0_STS and GPE0_EN, whose addresses can be
found from FACP as follows.

bash # cat /sys/firmware/acpi/tables/FACP > FACP
bash # iasl -d FACP
[root@TXT acpi]# grep GPE FACP.dsl
[050h 080  4]           GPE0 Block Address : 00000420
[054h 084  4]           GPE1 Block Address : 00000000
[05Ch 092  1]            GPE0 Block Length : 10

So according to FACP, GPE0 block is at port 0x420; GPE0 block length is 0x10.
GPE0_STS/GPE0_EN each occupies half the block length, with GPE0_STS at 0x420
and GPE0_EN at 0x428. _L02 is controlled by bit2 of each of them. We need to
override bit2 of both IO port 0x420 and IO port 0x428.

Before adding the Register Override we need to replace the DSDT provided by
BIOS with our modified DSDT.dsl, by following initrd_table_override.txt.
Once the new DSDT is injected into initrd we reboot the system and add the
Register Override as follows.

bash # cd /sys/kernel/debug/iohook/
bash # echo "420-1[4/4]wc 428-1[4/4]ro" > io
bash # echo 9 > irq
bash # echo 1 > trigger

The last command immediately triggers the _L02 method that we provided and
Linux sees a hot-add ACPI event for the IOH device.


3.2 Generate PCIe Native Hotplug

The pciehp driver allocates an irq to handle each native PCIe hotplug slot.
The driver prints the status of each hotplug slot when debugging is enabled
so we can see which irq it's using.

bash # modprobe pciehp pciehp_debug=1

dmesg shows:

pciehp 0000:00:1c.0:pcie04: Hotplug Controller:
pciehp 0000:00:1c.0:pcie04:   Seg/Bus/Dev/Func/IRQ : 0000:00:1c.0 IRQ 70

So we pick this hotplug slot at 00:1c.0 which uses IRQ70. Its PCIe Slot
Status Register is at offset 0x5a of its pci config space. We can easily
inject a Hot-Add (Presence Detect) event into the system by adding a
Register Override to override the register at offset 0x5a in the PCI config
space of 00:1c.0

bash # cd /sys/kernel/debug/iohook/
bash # echo "00|00:1c.0+5a-1[48/ff]wc" > pciconf
bash # echo 70 > irq
bash # echo 1 > trigger

dmesg shows:

pciehp 0000:00:1c.0:pcie04: Card present on Slot(1)
pciehp 0000:00:1c.0:pcie04: Device 0000:0f:00.0 already exists at 0000:0f:00,
cannot hot-add
pciehp 0000:00:1c.0:pcie04: Cannot add device at 0000:0f:00

We can then inject an Attention Button Pressed event:

bash # echo "00|00:1c.0+5a-1[41/ff]wc" > pciconf
bash # echo 70 > irq
bash # echo 1 > trigger

dmesg shows:

pciehp 0000:00:1c.0:pcie04: Button pressed on Slot(1)
pciehp 0000:00:1c.0:pcie04: PCI slot #1 - powering off due to button press

3.3 PCIe AER error injection

The aerdrv driver allocates a few irqs to handle AER. Each of them handles a
PCIe root port device. In an example aerdrv uses irq66 to handle AER on root
port 00:05.0. As seen in lspci, the AER capability is at offset 0x100 of its
pci config space. So the Root Error Status Register is at offset 0x130, and
the Error Source Identification Register is at offset 0x134. We can inject a
Correctable Error with a source id to identify its child at 05:00.0.

bash # modprobe iohook
bash # cd /sys/kernel/debug/iohook/
bash # echo "00|00:05.0+130-1[01/01]wc 00|00:05.0+134-2[0500/ffff]ro" > pciconf
bash # echo 66 > irq
bash # echo 1 > trigger

dmesg shows:
pcieport 0000:00:05.0: AER: Corrected error received: id=0500

(Log in to post comments)

Copyright © 2013, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds