This chapter provides a tutorial to LCP programming, and includes the following topics:
Section 4.2, talks about starting up a control program.
Section 4.3, describes the LCP subroutine libraries.
Section 4.4, discusses layout of sample source code.
Section 4.5, presents tables of OpenVault tokens for an LCP.
A library control program (LCP) translates between the OpenVault ALI and the actual device control interface for its library, and between device responses and ALI/R. The LCP does what is necessary to affect the required ALI semantics. It keeps the MLM server's cache (persistent store) up to date regarding LCP configuration, library configuration, and ready state information. To do this, the LCP sends config and ready commands when it detects changes in state, on a best-effort basis.
Currently, the library configuration and state is moved in one direction only, from an LCP to the MLM server persistent store. The MLM server uses this information to assist with library and drive selection for cartridge and volume mounts. In future revisions of OpenVault, the LCP might recover some state from the persistent store, so that state and configuration information can flow in both directions. However, the LCP and library are always considered the authoritative source for information about the LCP or its library.
In sample implementations, LCP configuration is stored in a configuration file that is local to each LCP. See Section 4.2.1, for more information.
Each LCP must initialize itself in order to contact the MLM server.
Removable media libraries may be connected to multiple hosts and thus have multiple control paths. There may be one LCP associated with each control path. Only one LCP at a time can be active for any library; the MLM server arbitrates which LCP is active.
For example, an LCP could be on an inactive library connection. The LCP boot sequence must not interfere with another LCP with an active connection. The MLM server is the arbitrator of control for multiconnected libraries and drives. An LCP should not assume that it controls a library until the MLM server says so.
Each LCP should have a configuration file containing at least the following information:
Address of the controlling MLM server: This allows the LCP to initiate contact with the controlling MLM server. It is the name of the system, or its numeric IP address. The MLM server is usually available at well-known port number on that system, by default 44444.
OpenVault name for the managed library: The MLM server uses this name as an identifier for this physical library. This is the name of the device that it is managing, not the name of the particular instance of LCP. All names must be unique within an OpenVault domain so that the server can detect multiconnected libraries (multiple LCPs controlling the same library).
LCP instance name: The instance name is arbitrary, but is required for cases where there are multiconnected libraries.
Control path to the library: This path show an LCP talks to the hardware (for example, /dev/scsi/sc0d2l0). This information is not visible to the MLM server. Some library implementations are not controlled in this fashion, but all LCP implementations need something equivalent.
OpenVault name for the drives contained in this library: The MLM server uses this information to determine relationships between libraries and drives (between LCPs and DCPs). The “contained in” relationship is helpful when deciding into which drives a cartridge can be placed, based on which library contains the cartridge. Each library has some method for addressing each drive inside that library. The LCP's name-to-drive address mapping takes the form: the OpenVault drive named dlt1 corresponds to library drive 1, while the drive named dlt2 corresponds to library drive 2.
For easy editing, LCP configuration files should be composed of readable ASCII text.
The LCP boot sequence is composed of the following steps:
When an LCP boots or re-boots, it does the following:
Allocates internal data structures.
Refrains from talking to the library.
The LCP boots into activate disable state, and must wait for the MLM server to tell it when to talk to the library. If the library is dual-ported with another LCP actively controlling it, that session should not be interrupted! The MLM server issues an activate enable command when conditions permit your LCP to control the library.
If the library is single-ported, activate enable is issued almost immediately.
Reads its configuration file.
Establishes a session with the MLM server.
The LCP sends the hello message upon opening the connection. In this example, name is the OpenVault name for the library, and inst is the LCP instance name. If connection fails, retry every two minutes. The LCP blocks until it receives a welcome command telling it which language version to use during this session.
hello language["ALI"] version["1.0"] client["name"] instance["inst"]; |
When the MLM server is first contacted by an LCP, it does the following:
Integrates the library into its list of managed devices.
Thre MLM server checks for other LCPs managing that physical library. If this LCP is the first, OpenVault allows the LCP to proceed. This sequencing implies that LCPs are given control of their associated library on a first-come-first served basis.
Eventually issues an activate enable command to the LCP.
When the MLM server says to activate enable, the LCP does the following:
Replies to the MLM server with a ready no command.
The LCP informs the MLM server that it has started to come up, but is not yet ready to accept cartridge movement commands.
Talks to the library to determine:
That the library is supported by this LCP (“ATL-2640” is supported).
Whether or not the library supports PCLs (barcodes), true or false.
List of supported cartridge form factors (“DLT”); may be compiled into the LCP
Total number of slots for each formFactor
Total number of used slots for each formFactor
Import/export port configuration
Slotmap (all the barcodes and occupancy info for the library)
Any other information that may be relevant to library or LCP operation
Collects any state or configuration information from the MLM server.
The LCP can store state or configuration information in the OpenVault persistent store.
Pushs all the slotmap and drive information up into the MLM server.
The LCP owns the slotmap and therefore needs to update the MLM server's copy of the slotmap whenever required. The LCP needs to tell the MLM Core when it is ready to accept cartridge movement commands.
Sends a ready command to the MLM server.
The LCP is now ready to accept cartridge movement commands.
Responds success to the original activate enable command.
This is defined to be the last step as a convenience to the MLM server, so that the server can block until it receives a response from its activate enable command rather than continually polling for arrival of the ready command.
After the MLM receives slotmap and drive information from the LCP, the server does the following:
Crosschecks the list of drives.
The MLM server crosschecks the list of contained drive names with the list of drives controlled by known DCPs. Not all DCPs may have checked in before the LCP does. The MLM server keeps a list of known DCPs that have not yet checked in so that it can flag them as possible hardware failures.
Crosschecks the list of PCLs (barcodes).
The MLM server crosschecks the LCP's list of PCLs against the previously known contents of the library, looking for new or missing cartridges. A message is sent to the system administrator and/or a logfile if any changes are detected.
Stores all the slot and drive information in persistent store.
The MLM server stores all the information that the LCP provided in its database. That information is the basis for choosing drives and cartridges on behalf of CAPI or AAPI clients.
When the MLM server gets a successful response to activate enable, it sends the LCP its message logging level and marks the library as being available for cartridge mounts.
The library is ready to accept cartridge mount, unmount, and movement requests. This implies that cartridges in that library are no longer filtered out of the list of candidates for mount operations because they are not accessible to OpenVault.
When an LCP receives an activate enable command from the MLM server, and the LCP is in ready lost state, it performs these steps:
Accesses its library to acquire or verify device-specific configuration and state.
For example, an LCP may consult its library to determine the following:
Library supported by this LCP (For instance, “ATL-2640” is supported.)
Whether the library is in a usable state by this LCP
Whether the library supports verification of PCLs (barcode reader)
Supported cartridge form factors (for instance, “DLT”)
Total number of slots for each formFactor
Total number of free slots for each formFactor
Import and export port configuration
Element maps (slot, drive, bay, port)
Pushes configuration information to the MLM server.
For example, configuration information includes: free slots, element maps, and performance information. The LCP is responsible for updating the MLM server's copy of element maps whenever it detects a change in map information.
Transitions to ready state, and pushes this new state to the MLM server.
While in ready lost state, the LCP should service the activate command, and any ALI commands in the session that do not require device access. The LCP should return a ready error (ALI_E_READY) and resend ready lost state for other ALI commands.
The infrastructure developer's kit includes a framework for writing an LCP that helps ease the development, porting, and maintenance effort for new devices. The framework provides general processing of ALI and ALI/R commands, thus freeing the developer to focus on the idiosyncrasies of a particular device, and on developing suitable support for a new removable media library.
This section describes the general source tree layout.
OpenVault clients and servers communicate with a custom interprocess communication (IPC) layer. LCP modules that deal directly with ALI and ALI/R must include the following header file, and be loaded with the following C library:
ovsrc/include/ov_lib.h | |
C data structures, macros, and subroutine prototypes for IPC | |
ovsrc/src.LGPL/comm/libov_comm.a | |
C library containing IPC subroutines |
The framework includes language parsers and generators. LCP modules using these facilities must include the following header files, and be linked with these C libraries:
ovsrc/src.LGPL/include/ali.h | |
Supported ALI and ALI/R language version, ALI standard errors, and C data structures for ALI and ALI/R command representation | |
ovsrc/src.LGPL/include/lcp.h | |
Parser and generator subroutine prototypes | |
ovsrc/src.LGPL/include/hello.h | |
C data structures for HELLO and WELCOME command representation | |
ovsrc/src.LGPL/hellor/libov_hello.a | |
C library that contains HELLO parser-generator subroutines | |
ovsrc/src.LGPL/ali/libov_lcp.a | |
C library that contains ALI parser-generator subroutines |
The LCP(3) man page documents the ALI and ALI/R lexical library routines that you employ when writing a LCP. Table 4-1 offers a summary of these routines.
Table 4-1. ALI and ALI/R Lexical Library Routines
Purpose of Activity | LCP Function | Short Description |
---|---|---|
To initiate session with MLM server | Begins session with a specific MLM server, including HELLO version negotiation. | |
To parse ALI command from MLM server | Parses an ALI command and returns an ALI command structure. | |
To acknowledge ALI command | Informs MLM server that the LCP received an ALI command. | |
To send ALI/R command to MLM server | ALIR_alloc_cmd() ALIR_alloc_ready() ALIR_alloc_message() ALIR_alloc_slotinfo() ALIR_alloc_bayinfo() | Allocates ALIR command structure. Allocates ALIR command for ready command. Allocates ALIR command for ALIR message. Inserts slot map info for config command. Inserts bay map info for config command. Inserts drive map info for config command. |
To send final response for ALI command to MLM server | ALIR_alloc_response() ALI_alloc_string() ALIR_send() | Allocates ALIR response structure. Allocates string for response, error, data results. Transmits ALIR command to MLM server. Deallocates ALIR command structure. |
To free ALI command | Deallocates ALI command structure. |
The infrastructure developer's kit includes common utility code for writing an LCP. To use this code, include the following header files, and read the following C module:
ovsrc/src.GPL/server/include/queue.h | |
Generic queue and linked list implementation | |
ovsrc/src.GPL/include/cctxt.h | |
Generic command queuing mechanism | |
ovsrc/src.GPL/include/maps.h | |
Generic element map representation | |
ovsrc/src.GPL/include/lcp_lib.h | |
Generic representation of LCP and library state, generic representation of an attribute, common LCP fixed and programmable entry points, and common LCP utility subroutine prototypes | |
ovsrc/src.BSD/lcp/common/util.c | |
LCP common fixed-entry points and utility subroutines |
Much of an LCP's representation of LCP and library state can be represented generically. However, the LCP developer needs a way to customize this representation for a particular library and implementation.
The LCP framework provides a private data area and programmable LCP entry points as a means for the developer to customize the LCP's representation of LCP and library state. The private data area allows the developer to maintain additional information about the LCP and library; the programmable entry points allow the developer to customize actions associated with ALI command dispatch, deactivation (transition to ready lost state), graceful shutdown, and ALI/R command task ID generation. This arrangement allows the shared framework to invoke these entry points as appropriate.
Example 4-1 shows the framework's generic representation for a library.
Example 4-1. Generic Library Representation
Here is the framework's generic representation for a library:
struct libinfo { /* elements from LCP config file. */ char *client; /* MLM name of this library. */ char *instance; /* Client instance. */ char *mlmhost; /* MLM host. */ int mlmport; /* MLM port. */ int pollinterval; /* seconds between library polls */ char *addr; /* library control address. */ /* elements initiated by LCP. */ char *type; /* Type of library. */ enum ALIR_msg_severity loglevel; /* Log level for LCP messages */ enum ALIR_ready_type readystatus; /* ready, not r_, disconnected */ int supportPCLs; /* 1 if barcode scanner, or 0 */ char *vendor; /* Library vendor name. */ queue_t ALI_cmd_queue; /* ALI command queue. */ queue_t ALIR_cmd_queue; /* ALIR command queue. */ int waiting_for_ack; /* 1 if waiting for ack, or 0 */ char *taskid_for_ack; /* TaskID of last ALIR command */ void(*lcp_deactivate)(struct libinfo *libi); /* deactivate */ void(*lcp_exit)(struct libinfo *libi, int abnormal); /* shutdown */ void(*lcp_dispatch)(struct libinfo *libi, struct ALI_command *cmd); char *(*lcp_taskid)(struct libinfo *libi); /* taskid generation */ /* element map info, shared by do- and control-layers */ element_map_t slotmap; /* Slot map */ element_map_t drivemap; /* Drive map */ element_map_t portmap; /* Port map */ element_map_t baymap; /* Bay map */ void *private; /* LCP private libary info */ }; |
An LCP that makes use of this developer framework must call the lcp_init subroutine, shown in Example 4-2, to initialize the generic and private data areas for LCP and library information, and set the programmable LCP entry points:
Example 4-2. lcp_init Subroutine
void lcp_init(struct libinfo *libi, void lcp_init_private(), void lcp_deactivate(), void lcp_exit(), void lcp_dispatch(), char *lcp_taskid(), void slot_private(), void drive_private(), void bay_private(), void port_private()); |
This entry point is called one time only from lcp_init(); so the libinfo structure does not store it. Required entry point for LCP private data area allocation and initialization:
void lcp_init_private(struct libinfo *libi); |
Remaining entry points are stored in the libinfo structure. Required entry point for LCP private actions to activate disable:
void lcp_deactivate(struct libinfo *libi); |
Required entry point for LCP private actions to shut down gracefully and exit:
void lcp_exit(struct libinfo *libi, int abnormal); |
Required entry point for ALI command dispatch from the command state machine:
void lcp_dispatch(struct libinfo *libi, struct ALI_command *cmd); |
Required entry point for LCP to generate a task ID for ALI/R commands:
void char *lcp_taskid(struct libinfo *libi); |
Optional entry points for element map allocation and initialization (may be NULL):
void slot_private(queue_t *q, int initflag); void drive_private(queue_t *q, int initflag); void bay_private(queue_t *q, int initflag); void port_private(queue_t *q, int initflag); |
Much of the information that an LCP needs to maintain about library elements, including slots, drives, bays, and ports, may be generically represented. However, LCP developers must be able to customize element information that the LCP maintains.
For example, typical information that an LCP needs to maintain about a slot includes the slotID, the device-specific address for this slot, the name of the bay in which this slot is located, whether the slot is accessible and occupied, the PCL of the cartridge that is currently occupying this slot (if any), and the name of the drive where the cartridge that was last in this slot is currently mounted (if any).
For typical slot information, the framework provides an extension to the common information by means of an LCP private data area and programmable entry points for allocating and deallocating this data area.
An example of how an LCP might use its private slot data area is for multi-sided media, where the library can mount the cartridge either “side A” up, or “side B” up. In addition to the typical slot information, an LCP for such a library would probably maintain the current orientation of a cartridge in its private data area for that slot.
The element map header file, maps.h, is separated from the LCP common header file, lcp_lib.h, so that the generic element map representation and subroutines may be used separately from the generic library piece. In the sample implementations, this permits the ALI semantic layer and the control layer modules for an LCP to share the element map representation, without both having to include the generic library piece. The control layer needs the generic element map piece, but not the generic library piece.
Example 4-3 illustrates the common representations for slot, drive, bay, and port:
Example 4-3. Common Slot, Drive, Bay, and Port Representations
typedef struct slot { char *name; /* Slot id. */ char *addr; /* Hardware address. */ char *bayid; /* Name of bay where slot resides */ char *shape; /* Of cartridges fitting this slot */ int access; /* T/F: is slot accessible? */ int occupied; /* T/F: is slot occupied? */ char *PCL; /* Label of cartridge in slot, if any */ char *driveid; /* Drive with slot's cartridge, if any */ void *private; /* LCP private data area. */ queue_t queue; /* To next/prev slots. */ } slot_t; typedef struct drive { char *name; /* Name of drive. */ char *addr; /* Hardware address. */ char *bayid; /* Name of bay where drive resides */ char *shape; /* Of cartridges that fit in this drive */ int access; /* T/F: is drive accessible? */ int occupied; /* T/F: is drive occupied? */ char *PCL; /* Label of cartridge in drive, if any */ char *slotid; /* Slot from where cartridge mounted */ void *private; /* LCP private data area */ queue_t queue; /* To next/prev drives */ } drive_t; typedef struct bay { char *name; /* Name of bay */ char *addr; /* Hardware address */ int access; /* T/F: is bay accessible? */ void *private; /* LCP private data area */ queue_t queue; /* To next/prev bays */ } bay_t; typedef struct port { char *name; /* Name of port */ char *addr; /* Hardware address */ char *bayid; /* Name of bay where port resides */ int access; /* T/F: is port accessable? */ element_map_t slots; /* Separately addressable slots in port */ void *private; /* LCP private data area */ queue_t queue; /* To next/prev ports */ } port_t; |
The element map header file is separated from generic library representation, to allow element maps to be shared between potentially different layers of an LCP, for instance between the ALI semantic layer and the device access layer. In sample implementations, the device layer fills in some of this information and the ALI semantic layer fills in the rest, then passes element maps to the MLM server with an ALI/R config command.
The following convenience routines are provided in module ovsrc/include/util.c to handle LCP element maps. See the ovsrc/include/maps.h file for subroutine prototypes.
void map_init() | |
Initializes element map of a given type. | |
void map_free() | |
Frees an element map. | |
void map_move() | |
Swaps two element maps. | |
slot_t *slotmap_add() | |
Adds an entry to the slot map. | |
void slotmap_del() | |
Deletes a slot map entry. | |
slot_t *slotmap_find_name() | |
Finds the entry for a given name in the slot map. | |
slot_t *slotmap_find_addr() | |
Finds the entry for a given address in the slot map. | |
slot_t *slotmap_find_PCL() | |
Finds the entry for a given PCL in the slot map. | |
slot_t *slotmap_find_empty() | |
Finds an empty slot, if one exists. | |
int slotmap_compare() | |
Compares two slot map entries for equivalence. | |
void slotmap_mount() | |
Updates slot information after a mount. | |
void slotmap_unmount() | |
Updates slot information after an unmount. | |
void slotmap_move() | |
Updates slot information after a move. | |
void slotmap_inject() | |
Updates slot information after an inject. | |
void slotmap_eject() | |
Updates slot information after an eject. | |
drive_t *drivemap_add() | |
Adds an entry to the drive map. | |
void drivemap_del() | |
Removes an entry from the drive map. | |
drive_t *drivemap_find_name() | |
Finds entry for a given drive name in the drive map. | |
drive_t *drivemap_find_addr() | |
Finds entry for a given drive address in the drive map. | |
int drivemap_compare() | |
Compares two drive map entries for equivalence. | |
void drivemap_inject() | |
Updates drive information after an inject. | |
bay_t *baymap_add() | |
Adds an entry to the bay map. | |
void baymap_del() | |
Removes an entry from the bay map. | |
bay_t *baymap_find_name() | |
Finds entry for a given bay name in the bay map. | |
bay_t *baymap_find_addr() | |
Finds entry for a given bay address in the bay map. | |
int baymap_compare() | |
Compares two bay map entries for equivalence. | |
port_t *portmap_add() | |
Adds an entry to the port map. | |
void portmap_del() | |
Removes an entry from the port map. | |
port_t *portmap_find_name() | |
Finds entry for a given port name in the port map. | |
port_t *portmap_find_addr() | |
Finds entry for a given port address in the port map. | |
int portmap_compare() | |
Compares two port map entries for equivalence. |
This section summarizes convenience routines available in module ovsrc/include/util.c, grouped by purpose:
The following functions are provided for ALI command queuing and the state machine:
The following functions are provided for ALI/R command queuing and MLM server acknowledgment processing:
The following function is provided for LCP ready state processing:
The following functions are provided for handling ALI error responses:
The following functions are provided for mandatory attribute and show processing:
The following functions are provided for debugging:
The EXABYTE 210/220/440/480 libraries are SCSI-2 medium changers. The EXABYTE 210 is a model with 10 slots and one or two EXABYTE 8505XL drives. It is comparatively simple to operate--the LCP source code for this autochanger is less than 4000 lines long. The EXABYTE 220 is similar but has 20 slots. The EXABYTE 440 has 40 slots and up to four drives. The EXABYTE 480 is similar but has 80 slots. (In EXABYTE model numbers, the first digit describes the maximum number of drives, while the remaining digits describe the number of available slots.)
The EXABYTE 210 LCP may be used in conjunction with the EXABYTE 8505XL DCP.
Calls to the pass-through SCSI driver are made with the IRIX C library for generic SCSI operations; see the dslib(3X) man page. Direct SCSI access is by means of this device special file:
/dev/scsi/scCdUlL |
In this filename, C is the SCSI controller number, U is the unit number, and L is the logical unit number (lun) for accessing library control. This information may be determined on IRIX systems by using the hinv command.
This section describes the LCP source and run-time configuration modules.
Example 4-4 shows the ovsrc/clients/lcp/EXABYTE-210/config file, which describes both the library and MLM server.
Example 4-4. ovsrc/clients/lcp/EXABYTE-210/config File
The ovsrc/clients/lcp/EXABYTE-210/config.c module parses this file and fills in library information in both the LCP generic and private data areas.
localhost # MLM server host name 739 # MLM server TCP socket wilma # OpenVault name for library host-bedrock # LCP instance name /dev/scsi/sc0d510 # SCSI drive control access path 60 # Library polling interval fred:82 # Map OpenVault drive name to library address barney:83 # Do likewise for second drive |
The ovsrc/clients/lcp/EXABYTE-210/control.h header file contains the device access layer device representation, and declares subroutine entry points for the ALI semantics layer to access the device. The ovsrc/clients/lcp/EXABYTE-210/control.c module implements these subroutines.
This layer, named after its many functions starting with “do,” is where the LCP interprets ALI commands. The programmer customizes this layer, based on the generic library methods that are provided as part of the LCP developer framework.
The ovsrc/clients/lcp/EXABYTE-210/main.h header file contains the LCP private data area of a generic library representation, and associated macros and subroutine prototypes, including four programmable LCP entry points used by the framework, and semantic support routines. The ovsrc/clients/lcp/EXABYTE-210/main.c module is where ALI semantic handling routines are implemented, and where ALI commands are dispatched to the appropriate semantic handling routine. For example, the ALI mount command would be dispatched to the do_mount() function.
The EXABYTE 210 LCP does not require custom element maps, because the developer framework provides an adequate generic representation. Other LCPs may require customization. Programmers can customize element map representation by creating the ovsrc/src.BSD/lcp/NAME/maps_private.h and ovsrc/src.BSD/lcp/NAME/maps_private.c files, where NAME represents the LCP name.
There is potential for a single, shared SCSI-2 media changer LCP. An additional device module would be required only for vendor-dependent processing, or for possible departures from the standard. The infrastructure developer's kit was developed on the IRIX operating system, and has been ported to an increasing list of operating system platforms.
Certain media libraries may perform parallel (instead of serial) execution of commands, and complex (not simple) mappings of ALI to underlying library control. Some libraries, such as SCSI-2 media changers, execute device control commands in a blocking or serial fashion. For most of these devices, there is a one-to-one mapping between an ALI command and the underlying SCSI-2 request. The LCP implementation for such a device may be trivial. For this sort of device, the LCP implementor may simply implement all ALI commands in a serial fashion. No extension of the framework is needed.
Other libraries, such as the StorageTek ACSLS and the IBM 3494, provide some parallelism in control command execution. Optimal use of these devices requires some extra work on the part of the LCP developer to extend the framework. These controllers tend to be more complex than SCSI-2, and require one ALI command to be mapped to potentially multiple underlying control requests. This requires a command execution state machine. Also, developers must understand command dependencies and how the underlying library or controller executes commands, to ensure proper sequencing.
This section documents the predefined strings that are relevant to LCP development.
The ALI interface lets the LCP describe to the MLM server what shapes of cartridges it can accept, and what capabilities it can offer with cartridges of that shape. Table 4-2 shows the tokens used for the currently existing cartridge shapes. Cartridge form factors are also called slot type names.
Table 4-2. Predefined Cartridge Form Factor Tokens
Token | Description or Usage |
---|---|
8mm | Any generic 8-mm shell |
3480 | For example: IBM 3480/3490/3495, STK 4480/4490, and so forth |
DLT | Digital linear tape (Quantum) |
DAT | 4 mm digital audio tape (DDS1 and DDS2) |
D2-S | Small DST cartridges (25 GB capacity) |
D2-M | Medium DST cartridges (75 GB capacity) |
D2-L | Large DST cartridges (165 GB capacity) |
DTF | 20 GB cartridges from Sony |
Table 4-3 shows one attribute used in an LCP, where it is used, and what it means.
Table 4-3. Predefined Attribute Name Tokens (LCP)
Attribute Name | Where Used | Possible Values | Required? | Description |
---|---|---|---|---|
ALI config command, perf clause | Numeric, in seconds | Yes | The approximate time it takes for the library to move a cartridge from its home location to a drive, or back, not including drive load/unload time. |