Patching the kernel callout code

If a device for which you are writing a kernel callout can appear in different locations on different boards, a patch routine is required to add the addresses of the registers to the kernel callout code.

There are two reasons why kernel callouts may not know what addresses the registers occupy:

The kernel callout code is part of the startup library, and has, therefore, been designed to be flexible; it doesn't hard-code the register addresses, but instead assumes that the addresses are patched in.
These addresses come from the board-specific code; that is, the addresses are found in code in a board's directory, not in the startup library code, which is designed to be as board-independent as possible.
Address conversion
A physical to virtual address conversion is required, so the kernel callout can’t know the virtual address where the register is mapped in until after the kernel has copied the kernel callouts into system memory.
Note: If the only registers kernel callout code accesses are CPU registers, patching isn't needed.

Patcher routines

When it starts up, the procnto kernel and process manager runs any patchers that are present (see procnto* in the Utilities Reference). The third argument of the CALLOUT_START macro can be either 0 (nothing to do), or the address of a patcher routine.

Patcher routines have the following prototype:

void patcher( paddr_t paddr, 
	paddr_t vaddr, 
	unsigned rtn_offset,
	unsigned rw_offset,
	void *data,
	struct callout_rtn *src );

This routine is invoked immediately after the kernel callout has been copied to its final resting place. Its arguments are as follows:

The physical address of the start of the system page.
The virtual address of the system page that allows read/write access (usable only by the kernel).
The offset from the beginning of the system page to the start of the kernel callout's code.
The offset from the start of the system page to a location of read/write storage, which can be shared by all kernel callouts that have the same value in their CALLOUT_START macro's second argument. See Allocating read/write storage below in this chapter.
A pointer to an arbitrary data registered by callout_register_data().
A pointer to the callout_rtn structure that's being copied into place.

Patcher routines don't have to be written in assembly. They are usually written in assembly, however, so that they can be kept in the same source file as the code that they patch.

If you arrange the first instructions in a group of related kernel callouts the same way (e.g. debug_char_*(), poll_key_*(), break_detect_*()), you can use the same patcher routine for these callouts.

Example patcher routine

Here's an example of a patcher routine for an x86 processor. We assume that the display_char_8250() routine has been copied to its permanent location in memory before we invoke our patcher.

The patch_debug_8250() modifies the constants in the first two instructions to the I/O port location and register spacing required for the board:

    movl    0x4(%esp),%eax              // get paddr of routine
    addl    0xc(%esp),%eax              // ...
    movl    0x14(%esp),%edx         	// get base info

    movl    DDI_BASE(%edx),%ecx         // patch code with real serial port
    movl    %ecx,0x1(%eax)
    movl    DDI_SHIFT(%edx),%ecx        // patch code with register shift
    movl    $REG_LS,%edx
    shll    %cl,%edx
    movl    %edx,0x6(%eax)

CALLOUT_START(display_char_8250, 0, patch_debug_8250)
    movl      $0x12345678,%edx          // get serial port base (patched)
    movl      $0x12345678,%ecx          // get serial port shift (patched)