$Id: design.xml,v 1.7 2005/06/17 01:55:50 martins Exp $
Copyright © 2004, 2005 IBM Corporation
Table of Contents
The Linux lsvpd package is a partial reimplementation, for Linux®, of some AIX® hardware inventory and configuration commands. The focus is on providing some of the same serviceability features when running Linux on IBM® pSeries® hardware as is provided by AIX. The main function is to list Vital Product Data (VPD) about hardware components, including systems, adapters and devices, in a variety of ways.
This documentation contains design and implementation notes for the Linux lsvpd package. This documentation is aimed at developers who are attempting to update the package. Installation notes are in a different document.
Commands in the package belong to one of two categories: data collection (or scanning) and querying.
Information about hardware is collected and stored in a
database under
/var/lib/lsvpd/db
(formerly -DATE-TIME
/var/lib/lsvpd/device-tree
,
with some supplementary information stored in other
subdirectories of /var/lib/lsvpd
). There
is generally a symbolic link
/var/lib/lsvpd/db
pointing the the most
recent database - so we will use this name to refer to the
database in this document. Currently there is only a single
data collection command called
update-lsvpd-db, which is invoked at boot
time and can also be run manually at any time.
update-lsvpd-db's main job is to construct
linux,vpd
directories, containing VPD,
for use by the querying commands.
It is assumed that under Linux 2.4 there will be no hotplug events that the lsvpd package needs to know about. Under Linux 2.6 this will change, so finer grain data collection will need to be implemented.
The query commands find VPD and other information in the database and present it in a variety of formats. These commands include the following:
The database under /var/lib/lsvpd/db
has
two main subdirectories, bus
andlinux
, as well as two auxiliary
subdirectories state
and
tmp
. The bus
directory contains a bus view of the system's hardware, while
the linux
directory contains the operating
system's view of adapters, devices and miscellaneous useful
information. Devices and adapters are cross linked between the
two directories. The state
subdirectory is
used to store sequence numbers and locks. Supplementary
information that used to be stored in (other) subdirectories of
/var/lib/lsvpd
is now stored in relevant
directories of the adapters and devices.
On machines with an Open Firmware device-tree, such as pSeries,
the bus
directory contains a
device-tree
subdirectory, which is a copy
of /proc/device-tree
augmented with
linux,vpd
nodes (and cross-links).
On other machines with sysfs, the
bus
directory is populated with
information from sysfs and then augmented
as above.
The following observations and requirements influenced the choice of bash as the primary implementation language:
Helper utilities that can't be implemented in
bash, and are not otherwise available, are
implemented in C. This allows small, efficient executables to
be produced, which is appropriate for certain early-boot
environments, such as in an initramfs
.
At this point in time, much of the functionality is being migrated to larger C programs. This is to allow lsvpd to realistically cope with hotplug. Eventually the whole package will be rewritten in C.
The main commands in the lsvpd package, written in bash, are written in a modular manner. This allows their complexity to be more easily managed and allows features to be added depending on (possibly architecture-dependent) operating system features and availability of other commands or packages.
There are several subdirectories of modules:
common.d
scan.d
query.d
lsvpd.d
,
lscfg.d
,
lsmcode.d
common-post.d
At the simplest level, modules work by redefining shell functions that are defined in previously loaded modules. Default modules contain stubs or versions of functions with minimal functionality. Modules that are loaded later may then define more sophisticated versions of those functions, depending on the available functionality.
The C reimplementation uses a similar system for deciding
whether to use functions from a particular module at
run-time. Each module uses features of
src/init.h
to define an
init
function that overrides relevant
function pointers, perhaps if an optional condition is met.
The INIT
macro is used to make the
init
function available early in the
initialisation phase. The inline function
call_inits
, which is called from
main
, calls the
init
function from each module in the
order the modules were passed to the linker
(ld). In this way, the logic detailing
the relative priorities of the modules is needed in only one
place: the Makefile
.
To avoid a plethora of case statements, a function multiplexing scheme has been implemented. A multiplexed function is declared like this:
make_multiplexed add_device
Initially, this causes any calls to this function, such as
add_device scsi 0:0:0:1
to fail silently, since the add_device
function has been declared but not defined.
Subsequent modules can then define individual implementation
functions that are called according to the first argument
that is passed to add_device
. For
example, if an implementation called
add_device_scsi
is defined, it will be
called when the first argument is scsi
,
as in the above example. If the first argument does not
coincide with a defined implementation, then the
implementation add_device_DEFAULT
will
be called, if it is defined. Otherwise silent failure
occurs. This allows modules to incrementally introduce
functionality for different device types.
Note that if a module wishes to replace a number of
implementations with a ..._DEFAULT
implementation, it will also need to use
set -f to remove unwanted
implementations. Alternatively, if a module is able to
provide all implementations using a single function, then it
can simply define a new add_device
function and completely override the multiplexing.
The C implementation uses a similar scheme for function
multiplexing. One of the main differences between this
implementation and the bash one is that
all functions require a type
argument,
since C is strongly typed (at least compare to
bash).
This has currently been partially implemented for devices. The current components of the implementation work like this:
src/device.h
contains a
typedef
for each function type.
src/device.h
contains a member in
struct device_functions
for each
function. This structure also has a member that
indicates the type of functions in the structure
(default, SCSI, IDE).
src/device.h
contains a declaration
for each top-level function.
src/device.c
contains a definition
for each top-level function, using the macros
DEVICE_MULTIPLEXED_VOID
and
DEVICE_MULTIPLEXED_RET
.
device_functions
structures (with a
relevant type flag) and register them using
device_type_register
. Possibly
previously defined functions can be undefined by
assigning the macro DEVICE_UNDEF
to
a function member.