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:
Understanding the UNIX boot timeline and how callbacks are affected (Section 4.1)
Why to use callbacks in your kernel module (Section 4.2)
Understanding dispatch points along the UNIX boot timeline (Section 4.3)
How to implement callbacks in your kernel module (Section 4.4)
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.
While the system is booting, 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 that your module
performs that requires the allocation of virtual memory must happen at or
after this dispatch point.
After single-user mode is reached, 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.
Figure 4-1
shows the boot timeline
and the associated dispatch points.
Figure 4-1: Dispatch Points Along the Boot Timeline
The arrows along the timeline depict the dispatch points.
The routines
that are shown in the example can be called at any time at or after 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 that ends 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
Figure 4-1
shows, the call to initialize a kernel module (well before
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.
This behavior is particular to dispatch points up to and including
CFG_PT_ENTER_SUSER
.
All higher numbered dispatch points represent
run-time (as opposed to boot-time) events.
Their associated callback routines
are invoked only whent the event reoccurs (or reoccurs).
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. During system boot-up (prior to single-user mode), the dispatch points occur in a strict chronological order.
CFG_PT_HAL_INIT
CFG_PT_UNIXTBL_AVAIL
CFG_PT_VM_AVAIL
CFG_PT_LOCK_AVAIL
CFG_PT_PRECONFIG
CFG_PT_TOPOLOGY_CONF
CFG_PT_PLATFORM_CONF
CFG_PT_POSTCONFIG
CFG_PT_CLU_CONF
CFG_PT_GLROOTFS_AVAIL
CFG_PT_ROOTFS_AVAIL
CFG_PT_ENTER_SUSER
CFG_PT_ROOTFS_WR
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.
Figure 4-2
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 that are 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 that is 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 of the minimum and maximum range.
A kernel module calls the
unregister_callback
routine
to undo the registration of 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
.
Some callbacks may never be unregistered.
4.4.1.2 Writing the Callback Routine
When a callback occurs, the kernel executes the callback routine that
you specified in the call to
register_callback
.
The callback
routine does all the callback processing and implements whatever action that
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 for 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 requested 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, we recommend that you do not register a large number of callback entries.
4.4.3 Nesting Callbacks and Unregistering 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.
Doing so 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 that are 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 that are 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
that is 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 that is 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 that is triggered in user mode with a value of 35600,
the following command triggers callbacks that are registered for this dispatch
point:
sysconfig -r generic user_cfg_pt=35600
To trigger the callback, you execute the 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.