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.
- Design
- 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.
- Address conversion
- A physical-to-virtual address conversion is required, which means the kernel callout can’t know the virtual address where the register is mapped in until after the kernel has copied the callouts into system memory.
Patcher routines
When it starts up, the procnto kernel and process manager runs any patchers that are present. The third argument of the CALLOUT_START macro can be either 0 (nothing to do), or the address of a patcher routine.
void patcher( paddr_t paddr,
paddr_t vaddr,
unsigned rtn_offset,
unsigned rw_offset,
void *data,
struct callout_rtn *src );
- paddr
- The physical address of the start of the system page.
- vaddr
- The virtual address of the system page that allows read/write access (usable only by the kernel).
- rtn_offset
- The offset from the start of the system page to the start of the kernel callout's code.
- rw_offset
- 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
in this chapter). - data
- A pointer to arbitrary data registered by callout_register_data().
- src
- 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 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 callouts in 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.
patch_debug_8250:
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)
ret
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)
....
CALLOUT_END(display_char_8250)