Linux lsvpd design and implementation notes

Martin Schwenke

$Id: design.xml,v 1.7 2005/06/17 01:55:50 martins Exp $


Table of Contents

1. Introduction
2. Overall architecture
2.1. Data collection
2.2. Querying
3. Database structure
4. Implementation languages
4.1. bash
4.2. C
5. Modular design
5.1. bash
5.1.1. C
5.2. Multiplexed functions
5.2.1. bash
5.2.2. C
6. Legal Statement

1. Introduction

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.

2. Overall architecture

Commands in the package belong to one of two categories: data collection (or scanning) and querying.

2.1. Data collection

Information about hardware is collected and stored in a database under /var/lib/lsvpd/db-DATE-TIME (formerly /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.

2.2. Querying

The query commands find VPD and other information in the database and present it in a variety of formats. These commands include the following:

lsvpd
Lists VPD. The textual output format is both machine and human readable. This command is used by higher-level system management tools.
lscfg
Lists (hardware) configuration. The textual output format is designed for human consumption. The simplest type of output simply lists a subset of hardware components, and this output can be augmented with VPD. There is also a facility for outputting a platform-specific section.
lsmcode
Lists microcode and firmware levels. Can produce either a tabular, machine-readable format, or a human-oriented format. Doesn't have interactive menus like the AIX version.
lsvio
Lists virtual I/O adapters and devices and their physical locations.

3. Database structure

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.

4. Implementation languages

4.1. bash

The following observations and requirements influenced the choice of bash as the primary implementation language:

  • A lot of the work done by the lsvpd package is text processing. The shell, along with external commands, is very good at processing text.
  • lsvpd's hardware inventory database might be useful early in the boot sequence as part of a persistent device naming system or similar. The only scripting language that is generally available on a Linux root filesystem is the shell.
  • /bin/bash is the usual Linux shell, and contains useful features, such as built-in arithmetic operations and a certain amount of string handling. It also has many commonly used commands built-in.

4.2. C

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.

5. Modular design

5.1. bash

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
Contains modules available to all commands.
scan.d
Contains modules used during data collection, currently just the update-lsvpd-db command.
query.d
Contains modules used by all of the query commands.
lsvpd.d, lscfg.d, lsmcode.d
Each of these directories contains modules used by the associated query command.
common-post.d
Contains modules available to all commands, but is loaded after other modules.

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.

5.1.1. C

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.

5.2. Multiplexed functions

5.2.1. bash

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.

5.2.2. C

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.
  • Modules assign functions into one or more 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.

6. Legal Statement

  • This work represents the view of the author and does not necessarily represent the view of IBM.
  • Linux is a registered trademark of Linus Torvalds.
  • IBM, AIX and pSeries are trademarks or registered trademarks of International Business Machines Corporation in the United States and/or other countries.
  • Other company, product, and service names may be trademarks or service marks of others.