4    Dispatch Point Callbacks

This chapter describes callbacks in relation to dispatch points along the boot timeline and the rules for implementing them in your kernel module. Kernel modules may contain one or more callback routines, which perform different tasks at different dispatch points. The kernel interacts with the callback routines to perform these tasks at the appropriate time.

This chapter contains the following information:

4.1    Understanding the UNIX Boot Timeline

To understand why callbacks are needed and how to implement them, you need to understand some details of the UNIX boot timeline.

The boot timeline represents all code that executes while the system boots. Key to the boot process are dispatch points that indicate certain functions can be done. In kernel mode, dispatch points occur in a specifically ordered manner (see Section 4.3). For example, the kernel-mode dispatch point CFG_PT_VM_AVAIL indicates the point where virtual memory can be allocated. Any activity your module performs that requires the allocation of virtual memory must happen at or after this dispatch point. In user mode, the dispatch points are more loosely ordered.

Callbacks are the mechanism for ensuring that the code in your module executes at the right point along the boot timeline. Section 4.4 describes ways that you can code your callback routine and, consequently, register the callback in your kernel module.

shows the boot timeline and kernel-mode dispatch points.

Figure 4-1:  Dispatch Points Along the Boot Timeline

The arrows along the timeline depict the dispatch points. Note that the routines shown in the example can be called at any time once the dispatch point is reached, but not before.

4.2    Why Use Callbacks?

Many kernel modules are dynamic modules--that is, they are dynamically loaded into memory as needed. Other kernel modules are statically loaded as part of /vmunix early in the boot timeline. For a kernel module to be a single binary image, it must be able to load statically as part of /vmunix or load dynamically as needed.

As explained in Chapter 2, when a module is loaded into memory, the only routine in the module that is known to the operating system is the configure routine. The module framework has access to the configure routine because of the predetermined name of the routine--that is, the module framework knows to look for a routine name ending with _configure. The framework calls the configure routine at initialization so that the kernel module can register its other routines with the rest of the operating system.

When static kernel modules are called to initialize themselves, they cannot allocate memory, initialize locks, or call any routine that is not yet available on the boot timeline. For example, as shows, the call to initialize a kernel module (CFG_PT_VM_AVAIL) occurs early in the boot timeline, while the dispatch point for locking (CFG_PT_LOCK_AVAIL) occurs later. To avoid the problem of calling routines that are not yet available, the kernel module can register a callback routine that will be called later in the boot timeline. When that routine is called, it will perform the required initialization correctly because the routines it requires will be available.

Callbacks, then, are the mechanism for implementing kernel modules as single binary images. Statically loaded kernel modules register callbacks that the module framework can execute at a later time. For a static configuration, callbacks are registered to execute at dispatch points along the boot timeline.

For example, the device switch subsystem is statically configured. It registers a callback routine to initialize the in-memory copy of the database after virtual memory is available (at the dispatch point called CFG_PT_VM_AVAIL). It registers another callback routine to update the on-disk database files, if necessary. This callback occurs after the root file system becomes writable (at dispatch point CFG_PT_ROOTFS_WR) because the subsystem's files reside on the root file system.

For a dynamically loaded module, callback routines that register with the dispatch points along the boot timeline are called directly from the register_callback routine because the dispatch point has already occurred.

Kernel modules call the register_callback routine to register their own callback routine. The kernel calls this routine when the specified dispatch point occurs.

4.3    Dispatch Points on the Boot Timeline

This section presents a list of dispatch points as they occur on the boot timeline. In kernel mode (prior to single-user mode), the dispatch points occur in a strict chronological order.

CFG_PT_HAL_INIT
Description: Hardware architecture layer is initialized.

CFG_PT_VM_AVAIL
Description: Virtual memory is available.
Common routines available: Device switch routines.

CFG_PT_LOCK_AVAIL
Description: Locking is available.
Common routines available: Routines that handle hardware registration.

CFG_PT_TOPOLOGY_CONF
Description: The topology configuration point. The operating system can create threads, timeouts begin working, kernel event management is available, the system begins incrementing time.

CFG_PT_POSTCONFIG
Description: Postscan the hardware. Tasks that require completion of hardware configuration can be performed at this dispatch point. Hardware events are posted.

CFG_PT_GLROOTFS_AVAIL
Description: Global root file system has been mounted.

CFG_PT_ROOTFS_AVAIL
Description: Root file system has been mounted read-only. Tasks that require completion of the root file system mount operation can be performed at this dispatch point. Dynamic device registration can occur.

CFG_PT_ENTER_SUSER
Description: Enter single-user mode.

4.4    Implementing Callbacks in Your Kernel Module

This section describes how you code callbacks in your kernel module.

4.4.1    Coding Callbacks

To implement callbacks in your kernel module, you must:

Section 4.4.1.1 describes the first step in this process, registering your callback routine. It defines the parameters that are passed to the callback subsystem when you register callbacks. Section 4.4.1.2 describes how to write a callback routine in your kernel module that receives information from the callback subsystem prior to performing some task. shows how the kernel module uses the kernel's callback subsystem.

Figure 4-2:  Using the Kernel Callback Subsystem

  1. Some routine, typically the configure routine, calls register_callback because it needs the kernel module callback routine (abc_dowork in the example) called at some later point. When you call register_callback to register your callback routine, you pass several parameters: the dispatch point, the priority, the address of the callback routine, and an argument to be passed to the callback routine.

    When register_callback is called, it does either step 2 or step 3:

  2. The register_callback routine calls abc_dowork directly if the kernel dispatch point is on the boot timeline and it has already occurred. This completes the callback sequence.

  3. The register_callback routine saves information about the callback and proceeds to the next step in the callback sequence. (This is the normal operation.)

  4. The routine dispatch_callback calls the kernel module callback routine abc_dowork at the appropriate dispatch point.

  5. The kernel module callback routine executes.

4.4.1.1    Calling the register_callback Routine

The register_callback routine enables your kernel module to execute its callback routine by storing callback information until the correct dispatch point. The register_callback routine has the following format:

int register_callback(void  (*func)(), int point, int order, ulong arg);
 

where

When you call register_callback to register your callback routine, the information you pass says, in effect, "At this dispatch point, with this priority, call the kernel module callback routine with this argument." Normally, the callback will occur later than the register_callback call. There is one exception: if the callback being registered is for a dispatch point along the boot timeline that has already passed, the callback occurs immediately.

Upon successful completion, the register_callback routine returns the status value ESUCCESS. Otherwise, it returns one of the following error status values:

ENOMEM--The system limit on the maximum number of registered callbacks was exceeded. You can correct this error by increasing the value of the max_callbacks attribute in the cm subsystem and then rebooting the system. (See System Configuration and Tuning for details.)
EINVAL--The value that you passed as the point argument is outside the minimum and maximum range.

A kernel module calls the unregister_callback routine to deregister a callback. It has the following format:

int unregister_callback(void  (*func)(), int point, int order, ulong arg);
 

where the parameters are identical to those used by register_callback. Note that some callbacks may never be unregistered.

4.4.1.2    Writing the Callback Routine

When a callback occurs, the kernel executes the callback routine you specified in the call to register_callback. The callback routine does all the callback processing and implements whatever action you require when the callback occurs. The callback routine is most often written as part of your kernel module. It can be statically linked to the kernel as part of /vmunix or dynamically loaded at run time. The requirement is that it exists in the kernel prior to when the callback occurs.

The callback routine that you write in your kernel module is passed the dispatch point, order, and argument parameters when it is called.

A kernel module callback routine must conform to the following format:

void xx_callback(int point, int order, ulong arg, ulong arg2);
 

where the parameters are defined as follows:

4.4.2    Registering Callbacks

To code callbacks in your kernel module, register all the callbacks in your configure routine. The following pseudocode fragment for abc_configure.mod registers two callbacks from within the configure routine:


.
.
.
abc_configure (opcode, ...){ switch (opcode) { case CFG_OP_CONFIGURE: register_callback (abc_vm, CFG_PT_VM_AVAIL, CFG_ORD_DONTCARE, arg1) . register_callback (abc_post, CFG_PT_POST_CONFIG, CFG_ORD_DONTCARE, arg2)
.
.
.
} } abc_vm (int point, int priority, int arg){
.
.
.
} abc_post (int point, int priority, int arg){
.
.
.
}    

Note

Because there are a limited number of callbacks that you can use, registering a large number of callback entries is not recommended.

4.4.3    Nesting Callbacks and Deregistering Callbacks

A kernel module can register multiple callbacks, possibly at different callback points, by calling register_callback() many times. Callbacks may not, however, be nested--calling register_callback() from within a callback routine is illegal.

To enable deregistration, call unregister_callback() from within a callback routine. This allows a callback to unregister itself or other callbacks.

4.4.4    Defining New Dispatch Points in your Kernel Module

You can write a kernel module that uses the predefined dispatch points (see Section 4.3), or you can write a module that defines and uses new ones. The following steps describe how to define a new kernel dispatch point:

  1. Choose and reserve a unique number for the new dispatch point.

    The valid range for developer-defined dispatch points is listed in the /usr/include/sys/sysconfig.h file, along with the values for the system-defined dispatch points.

    Values for developer-defined run-time dispatch points triggered within the kernel must be within the range of these values: CFG_PT_RUNTIME_KERN_MIN_EXT (20000) to CFG_PT_RUNTIME_KERN_MAX_EXT (29999).

    Values for developer-defined run-time dispatch points triggered outside the kernel (user mode) must be within the following range: CFG_PT_RUNTIME_USER_MIN_EXT (30000) to CFG_PT_RUNTIME_USER_MAX_EXT (39999).

  2. Trigger the callback.

    All kernel callbacks triggered within the kernel are activated by the dispatch_callback() routine, which has the following format:

    dispatch_callback (CFG_PT_MYPOINT, arg2)
    

    where CFG_PT_MYPOINT is the unique value for the dispatch point you define and arg2 communicates point-specific information to the callback routine. Thus, when you define a dispatch point triggered from the kernel, you need to insert the dispatch_callback() call at the appropriate place within your kernel module.

    In contrast, when you define a dispatch point triggered from user space, you do not need to supply the dispatch_callback() call in the kernel module. A callback triggered from user mode is accomplished by setting the value of the user_cfg_pt attribute in the generic subsystem to the value of the dispatch point. For example, if you define a dispatch point triggered in user mode with a value of 35600, the following command triggers callbacks registered for this dispatch point:

    sysconfig -r generic user_cfg_pt=35600
    

    To trigger the callback, you would execute the above command from within a script or from the user prompt. Alternately, you could call the cfg_subsys_reconfig(3) routine from within a program to achieve the same result.