"Patching" the callout code

You may need to write a callout that deals with a device that may appear in different locations on different boards. You can do this by "patching" the callout code as it is copied to its final position. The third parameter of the CALLOUT_START macro is either a zero or the address of a patcher() routine. This routine has 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 callout has been copied to its final resting place. The parameters are as follows:

Physical address of the start of the system page.
Virtual address of the system page that allows read/write access (usable only by the kernel).
Offset from the beginning of the system page to the start of the callout's code.
See the section on "Getting some R/W storage" below.
A pointer to arbitrary data registered by callout_register_data() (see above).
A pointer to the callout_rtn structure that's being copied into place.
Note: The data and src arguments were added in the QNX Neutrino Core OS 6.3.2. Earlier patcher functions can ignore them.

Here's an example of a patcher routine for an x86 processor:

    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)

After the display_char_8250() routine has been copied, the patch_debug_8250() routine is invoked, where it modifies the constants in the first two instructions to the appropriate I/O port location and register spacing for the particular board. The patcher routines don't have to be written in assembler, but they typically are to keep them in the same source file as the code they're patching. By arranging the first instructions in a group of related callouts all the same (e.g. debug_char_*(), poll_key_*(), break_detect_*()), the same patcher routine can be used for all of them.