PHOB: PCI Helper to Observe Boards

The phob tool is a kernel module that can be used to access registers in arbitrary PCI devices from user space. The device also offers a very simplified mechanism to define interrupt handlers.


There may be memory leakages somewhere. I didn't yet audit all the code.

There's no support for do_gettimeofday() in the interrupt handler. I'll add it pretty soon, though

No support for ISA. It's pretty straightforward to add it, though, and it will happen soon as well.

Not ported/tested to other platforms than the PC.

Authors and License

Phob is by Alessandro Rubini <>, licensed according to the GNU GPL. It has been sponsored by and


Phob currently runs only under Linux version 2.2 and 2.4. It's unlikely it will be ported back to 2.0, but I may need that some time in the future. The current version has been written in a hurry without any attention to cross-platform portability. In particular, several places assume little-endian data storage. I didn't even mark those parts with FIXME's, as I had not time to think about that; but I'll deal with that, eventually. Porting to other operating system kernels is not planned at all.

Compiling and Installing

To compile, just run "make". If your kernel headers are not in /usr/src/linux/include/linux and /usr/src/linux/include/asm, please specificy an include directory or the kernel directory in the command line of "make". This is what I do:

make KERNELDIR=/usr/src/linux-2.2

You can also use INCLUDEDIR (set, for example, to /usr/src/linux-2.2/include), but support for INCLUDEDIR is only there for backward compatibility and will be removed in later versions.

You can also set KERNELDIR or INCLUDEDIR in the environment.

Although the tool is meant for prototyping, "make install" should do something sensible, but I never tried it and I wouldn't suggest to install anyways.

Loading and Unloading

To load the module, run the script phob_load, providing the parameters "vendor_id" and "device_id", like in the following example:

phob_load vendor_id=0x11AA device_id=0x5555

The driver will hook to any device with the specified vendor and device ID, up to at most 16 devices.

Device Nodes

/dev/phob0      The cfg PCI space of the first device (256 bytes)
/dev/phob0a     The first memory or I/O region
/dev/phob0b     Second memory or I/O region
/dev/phob0c     Third region
/dev/phob0d     Fourth region
/dev/phob0e     Fifth region
/dev/phob0f     Sixth region
/dev/phob0i     Interrupt management data (NOT YET IMPLEMENTED)
/dev/phob0m     One page of memory, mmap()pable from user space.
/proc/phob0     Textual information related to the device

The same entry points are repeated as "phob1", "phob2" etc in order to access additional device boards. The "phob_load" script only creates entry point for four devices, hack it and phob_unload to support more (the module can drive up to 16 devices).

Entry points


The open() system call will succeed or fail according to whether the hardaware device is there and the requested memory region or interrupt handlare is available. If no device is there, ENODEV is reported; if the specific feature being accessed is not available, ENOSUPP is returned.


Reading /proc/phob0 returns textual information as tagged lines. It's mainly intended for humans issuing "cat" and scripts issuing "grep".

Reading either of the a-f and m devices returns binary data. Access to the I/O or memory regions is performed 32 bits at a time wherever possible. The physical address of the page of memory (/dev/phob0m) is available by grepping in /proc/phob0

Reading /dev/phob0 returns the PCI configuration space as binary data just like the memory regions.

Reading the 'i' device returns data according to how interrupt management has been designed. See below under interrupt management.


Writing to /proc/phob0 is not permitted. Writing to /dev/phob0 and either of the a-f and m devices is a valid way to set registers to arbitrary binary values. The physical address of the page of memory (/dev/phob0m) is available by grepping in /proc/phob0 or by running "./phob_control getaddress".

When writing to the first 64 bytes of PCI space, data being written is ignored to prevent trashing the device while allowing "cat /dev/zero" > /dev/phob0".

Wirting to the "i" device is not allowed.


The ioctl device method is implemented in the same way for every entry point: the system call can be used to read or write arbitrary data from/to any I/O region or from/to PCI space. The system call is implemented to ease myself in writing the user space tools described below.

Available commands are:

PHOB_IOCUNUSE (no third argument)

        Decrease the  usage count  of the module  to one.  This allows
        unloading the module after an  Oops happened, and it is mainly
        an aid for me while hunting bugs.

PHOB_IOCREAD (phob_io_struct * as third argument)

        Read 1, 2, 4 bytes  from either PCI configuration space, a PCI
        region or  the memory page. See "phob_io.c"  for details about
        structure fields.

PHOB_IOCWRITE (phob_io_struct * as third argument)

        Write 1, 2,  4 bytes to either PCI  configuration space, a PCI
        region or  the memory page. See "phob_io.c"  for details about
        structure fields.

PHOB_IOCGADDRESS (unsigned long * as third argument)

        Return to user  space the physical address of  the memory page
        being used by the "m" entry point for this device. This is how
        "phob_control getaddress" works.


        Allows user space to register an irq handler (see below).  Use
        ./phob_irq as a frontend to this (see below, too).


        Irq  handling  can be  enabled  or  disabled  at runtime.  The
        command gets  either one or  zero as third argument  to enable
        and disable irq reporting.


The mmap system call is only implemented on the "m" device node. It makes available to user space the single page of RAM allocated to the device at load time. There is currently no support to mmap PCI memory regions.

Interrupt management

The user can define an own interrupt handler by specifying a sequence of actions to perform. The sequence of actions is interpreted at interrupt time. Alternatively, the user can load a client module that registers a C function that implements the interrupt handler.

C code for interrupt handling is NOT YET IMPLEMENTED.

The interpreter used in interrupt management runs a virtual machine with one 32-bit accumulator and 15 32-bit additional registers. The following actions are implemented:

        read8   <region> <u32-or-reg>  region is a char, address is 32-bit
        read16  <region> <u32-or-reg>     region p is PCI configuration
        read32  <region> <u32-or-reg>     regions 0-5 are the device regions
        write8  <region> <u32-or-reg>     region m is the mmappable page
        write16 <region> <u32-or-reg>
        write32 <region> <u32-or-reg>

        mempage                        store the mem. address in acc.

        or      <u32-or-reg>           change the u32-or-reg in the acc.
        and     <u32-or-reg>
        xor     <u32-or-reg>
        add     <u32-or-reg>
        sub     <u32-or-reg>

        store   <register>             store acc. to register 1-15
        load    <register>             load acc. from register 1-15

        printk  <string>               printk() the string (with acc.)
        printf  <string>               sprintf()  to /dev/phob?i

        jz      <offset>               jump relative if acc. is 0
        jnz     <offset>               jump relative if acc. is not 0
        jmp     <offset>               jump always

        ret                            end of interrupt handler

Please note that "irqsample.txt" shows how to use the commands.

Note that printf is NOT YET IMPLEMENTED (it currently is the same as printk).

The ``printk'' and ``printf'' commands receive as argument a string, which may include at most one printf argument, of type integer ("%i" "%x" "%08x" etc). The argument passed is the accumulator. If more than one argument is specified please don't tell me what happens. A string is enclosed in double-quotes, any double-quote prefixed with a backslash is ignored (but the backslash is not trimmed and no other escape sequence is interpreted). The string gets a trailing newline automatically appended.

Registers are specified by a letter 'R' followed by a number in the range 0-15. Non-register arguments are specified in decimal, hex, or octal with the usual C-language syntax. Whenever a new procedure is loaded, the state machine is reset (all registers are cleared).

For jump instructions, the offset is counted from the next instruction. Therefore, "jmp 0" is a no-operation, and "jmp -1" is an infinite loop. If you jump too far, the procedure will return (this can be used to save intruction to force a return on a condition),

Any line whose first non-blank character is either '#' or '!' is ignored as a comment.

I/O operation that act out of bound have undefined results. For your convenience, though, operations that read/write in the memory area have the offset checked and invalid bits are masked out (so you can use an ever-increasing counter to implement a circular buffer with no checks on overflow).

If you need other operators (like shift operators), please tell me about your needs (or contribute a patch).

User space tools

The following user space programs are distributed with the phob driver in order to simplify use of the underlying hardware. All of them receive the device name as either first or last argument on the command line (the first or last argument is used as device name if it begins with a '/'). If no device name is specified on cmdline, they use the environment variable PHOBDEVICE or default to use /dev/phob0.


A toolt o invoke ioctl commands on the phob device. Invoke with no arguments to get help.


Read and write registers in the device. The first or last argument is the device being used (default is /dev/phob0). Other arguments are either read or write commands, of this form:

                write command. Either write 1, 2, 4 bytes, according
                to the length of the datum being written.

            Examples:  ``p:200=ffff'' (write one word to PCI space)
                       ``0:131=4f''   (write one byte to 1st region)
                       ``3:100=00000000'' (write one dword to 3rd)

                read command. It reads 1, 2, 4 bytes according to the
                alignment of the address specified, or can be specified
                with the -%x suffix (which can be only 1, 2, 4).

            Examples:  ``p:0''   (read 4 bytes from address 0 of pci cfg)
                       ``p:0-2'' (read 2 bytes)
                       ``0:120'' (read 4 bytes from 0x120 in region 0)
                       ``0:121'' (read 1 byte from 0x121 in region 0)
                       ``0:121-2'' (invalid due to misalignment)


The tool defines the interrupt handler for a phob device, possibly overriding any previous handler. The program receives at most two non-option arguments: the phob device name (defaults to "/dev/phob0") and the interrupt handler code (defaults to stdin). It accepts the following options:

        -n      parse the program but not install it in the driver.
        -d      parse and install, but keep the handler disabled.
        -v      print machine code and disassembled instructions.

If phob_irq receives an empty input file, it will uninstall the interrupt handler (and disables interrupt reporting. Note that interrupt reporting can be enabled or disabled at any time using phob_control.

An example interrupt handler that uses most of the supported features, is part of the package as ``irqsample.txt''. The sample file ``irqsample.c'' (NOT YET IMPLEMENTED), on the other hand, is an example of C-language interrupt handler that is compiled by Makefile and can be loaded via insmod. You can look at the source itself for usage details.

Final Remarks

Enjoy, and feel free to send me comments.