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:
The UNIX boot timeline and how callbacks are affected
Why you would use callbacks in your kernel module
Dispatch points along the UNIX boot timeline
How to implement callbacks in your kernel module
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
CFG_PT_VM_AVAIL
CFG_PT_LOCK_AVAIL
CFG_PT_TOPOLOGY_CONF
CFG_PT_POSTCONFIG
CFG_PT_GLROOTFS_AVAIL
CFG_PT_ROOTFS_AVAIL
CFG_PT_ENTER_SUSER
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:
Call the
register_callback
routine
Write a callback routine in your kernel module that will be passed parameters from the kernel's callback subsystem
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
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:
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.
The
register_callback
routine saves information
about the callback and proceeds to the next step in the callback sequence.
(This is the normal operation.)
The routine
dispatch_callback
calls the
kernel module callback routine
abc_dowork
at the appropriate
dispatch point.
The kernel module callback routine executes.
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
The
func
parameter is the name of the callback
routine that you want called at a particular dispatch point.
The
point
parameter is the value of the
dispatch point at which you want your callback routine called (for example,
CFG_PT_VM_AVAIL
).
The
order
parameter is used to order multiple
callback requests registered for the same dispatch point.
A request with
a smaller order value is executed before a request with a larger value.
A
kernel module may use this to coordinate among other modules.
The order constant
most useful to kernel module writers is
CFG_ORD_DONTCARE
.
This constant registers the callback with no specific order priority.
If you are a device driver writer, consider using one of the following order constants:
CFG_ORD_NOMINAL
--Registers the callback with
lowest order priority.CFG_ORD_MAXIMUM
--Registers the callback with
the highest order priority.
The
arg
parameter is used by the kernel
module to communicate information to the callback routine.
Pass the integer
0L
to indicate that you do not want to pass an argument.
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:
The
point
parameter is the value associated
with the dispatch point.
The value from the same parameter in the corresponding
call to
register_callback
is passed.
The
order
parameter specifies the order
in which the callback routine is being called.
The value from the same parameter
in the corresponding call to
register_callback
is passed.
The
arg
parameter specifies the argument
that the kernel module asked to pass to the callback routine.
The value from
the same parameter in the corresponding call to
register_callback
is passed.
The
arg2
parameter is an additional value
supplied by the callback dispatcher.
It is used to communicate point-specific
information to the callback routine.
For many dispatch points, this parameter
is not used.
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:
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).
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.