TOC PREV NEXT INDEX

Writing Device Drivers for LynxOS


Installation and Debugging

This chapter discusses the two methods of device driver installation in LynxOS: static and dynamic.

Static Versus Dynamic Installation

This section provides a comparison the two methods of device driver installation to assist the developer in choosing the type of installation to suit specific requirements.

Static Installation

With this method, the driver object code is incorporated into the image of the kernel. The driver object code is linked with the kernel routines and is installed during system start-up. A driver installed in this manner can be removed; however its text and data segments remain within the body of the kernel.

The advantages of static installation are:

Dynamic Installation

This method allows the installation of a driver after the operating system is booted. The driver object code is attached to the end of the kernel image and the operating system dynamically adds this driver to its internal structure. A driver installed in this fashion can also be removed dynamically.

The advantages of dynamic installation are as follows:

Static Installation Procedure

The code organization for static installation is shown in the table below.

Code Organization for Static Installation  
Directory
File
Description
/
lynx.os
LynxOS kernel
/sys/lib
libdrivers.a
Drivers object code library
libdevices.a
Device information declarations
/sys/dheaders
devinfo.h
Device information definition
/sys/devices
devinfo.c
Device configuration file
Makefile
Instructions for making devlib.a
/sys/drivers/drvr
driver source
The source code for driver drvr to be installed
/sys/lynx.os
CONFIG.TBL
Master device and driver configuration file.
Makefile
Instructions for making /lynx.os
/etc
nodetab
Device nodes
/sys/cfg
driv.cfg
Configuration file for driv driver and its devices

The following steps describe how to implement a static installation:

  1. Create a device information definition and declaration. Place the device information definition file devinfo.h in the directory /sys/dheaders along with the existing header files for other drivers in the system.

  2. Make sure that the device information declaration file devinfo.c is in the /sys/devices directory and has the following lines in the file in addition to the declaration.

#include "../dheaders/devinfo.h"

This ensures the presence of the device information definition.

  1. Compile the devinfo.c file and update the /sys/lib/libdevices.a library file to include devinfo.o. This may also be automated by adding devinfo.c to the Makefile. For example:

DEVICE_FILES=atcinfo.x dtinfo.x flopinfo.x devinfo.x
  1. To update /sys/lib/libdevices.a, enter:

make install

Driver Source Code

Assuming the new driver is called driver, the following steps must be followed for driver code installation.

  1. Make a new directory driver under /sys/drivers and place the code of the device driver there.

  2. Create a Makefile to compile the device driver.

  3. Update the library file /sys/lib/libdrivers.a with the driver object file using the command:

make install

Device and Driver Configuration File

The device and driver configuration file should be created with the appropriate entry points, major device declarations, and minor device declarations. The system configuration file is CONFIG.TBL in the /sys/lynx.os directory.

The CONFIG.TBL file is used with the config utility to produce driver and device configuration tables for LynxOS. Drivers, major devices, and minor devices are listed in this configuration file. Each time the system is rebuilt, config reads CONFIG.TBL and produces a new set of tables and a corresponding nodetab file for use with the mknod utility.

Configuration File: CONFIG.TBL

The parsing of the configuration files in LynxOS follows these rules:

The special characters in the configuration file are

Special Characters  
Character
Description
#
Indicates a comment in the configuration file. The rest of the line is ignored when this is the first character in any line.
\
The continuation character to continue a line even within a comment
:
If the : is the first character in the line, it is ignored.
I:filename
Indicates that the contents of the file filename should replace the declaration.

The format of a device driver entry with its major and minor device declarations should look like this:

# Character device
C:driver name:driveropen:driverclose: \
    :driverread:driverwrite: \
    :driverselect:driverioctl: \
    :driverinstall:driveruninstall
D:some driver:devinfo::
N:minor_device1:minor_number
N:minor_device2:minor_number

# Block device
B:driver name:driveropen:driverclose: \
    :driverstrategy:: \
    :driverselect:driverioctl: \
    :driverinstall:driveruninstall
D:some driver:devinfo::
N:minor_device1:minor_number
N:minor_device2:minor_number

The entry points should appear in the same order as they are shown here. If a particular entry point is not implemented, the field is left out, but the delimiter should still be in place.

If above declarations are in a file driver.cfg, the entry

I:driver.cfg

should be inserted into the CONFIG.TBL file.

Rebuilding the Kernel

To rebuild the LynxOS kernel, type the following commands:

cd /sys/lynx.os
make install

For the applications programs to use a device, a node must be created in the file system with mknod. This can be done automatically by using the nodetab file created by config.

When the system is rebooted to use the newly-created operating system, the reboot command should be given the N flag:

reboot -aN

The N flag instructs init to run mknod and create all the nodes mentioned in the new nodetab.

Dynamic Installation Procedure

Dynamic installation requires a single driver object file, and a pointer to the entry points must be declared. The location of the driver source code is irrelevant in dynamic installation. The installation of the dynamically-loaded device driver need not be done manually. A shell script can be written or a C program can be used to install the device driver after system startup.

Driver Source Code

To install a device driver dynamically the entry points must be declared in a structure defined in dldd.h. The variable should be named entry_points and for a block composite driver, block_entry_points is also required.

The format of the dldd structure is illustrated below:

#include <dldd.h>
static struct dldd entry_points = { open, close, read
    write, select, ioctl, install, uninstall, 0}

For block composite drivers, the block driver entry points are specified as:

static struct dldd block_entry_points =
 { b_open, b_close, b_strategy, ionull, ionull, b_ioctl,    b_install,
   b_uninstall, 0}

The include file dldd.h must be included in the driver source code and the declaration must contain the entry points in the same order as they appear above. If a particular entry point is not present in a driver, the field in the dldd structure should refer to the external function ionull, which is a kernel function that simply returns OK. The last field in the dldd structure was used for STREAMS drivers, which are no longer supported by LynxOS. STREAMS functionality can be replicated with mmap(). See the mmap() man page for details.

Note: On the PowerPC platform, the dldd structure should not be declared static.

The following example shows the null device driver that will be installed dynamically.

/* -------------- NULLDRVR.C ------------------*/

#include <conf.h>
#include <kernel.h>
#include <file.h>
#include <dldd.h>

extern int ionull ();
}
nullread(s, f, buff, count)
char *s;
struct file *f;
char *buff;
register int count;
{
    return 0;
}
nullwrite(s, f, buff, count)
char *s;
struct file *f;
char *buff;
register int count;
{
    return (count);
}
nullioctl()
{
    pseterr (EINVAL);
    return (SYSERR);
}
nullselect()
{
    return (SYSERR);
}
int nullinstall()
{
    return (0);
}
int nulluninstall()
{
    return (OK);
}
static struct dldd entry_points = {
  ionull,
  ionull,
  nullread,
  nullwrite,
  nullselect,
  nullioctl,
  nullinstall,
  nulluninstall,
  (kaddr_t *) 0
};

Note that calls to a driver entry point replaced by ionull still succeed. If a driver does not support certain functionality, then it must include an entry point that explicitly returns SYSERR, as in the case of the ioctl() and select() entry point functions in the above example. This causes calls to these entry points from an application task to fail with an error.

Note: To dynamically install the null driver on the PowerPC platform, omit the keyword static from the struct dldd declaration.

Driver Installation

In this release of LynxOS, follow these recommendations for compiling and installing dynamic device drivers on a specific LynxOS platform.

x86

LynxOS supports dynamic driver installation with the GNU C compiler. To compile a dynamically-loadable device driver, the command to be used depends on whether you are compiling on a Lynx OSa native development system, or on a cross development host:

On a LynxOS native development system:

/usr/i386-coff-lynxos/usr/bin/gcc -c -o driver.obj\  driver.c -I/sys/include/kernel\
 -I/sys/include/family/x86  -D__LYNXOS

On a LynxOS cross development host:

$(ENV_PREFIX)/cdk/platform-coff-x86/usr/bin/gcc -c -o\  driver.obj driver.c -I/sys/include/kernel\
 -I/sys/include/family/x86 -D__LYNXOS

where platform is

sunos For SunOS targets
win32 For Windows targets
hpux For HP/UX targets
linux For Linux targets

Note: The current release of LynxOS does not support a.out format dynamic drivers.

PowerPC

LynxOS for PowerPC supports dynamic driver installation with the GNU C compiler but requires a special import file for the linker. The import file contains a list of driver service calls used by the device driver. An example is shown in the steps below.

  1. Compile the driver with GNU C. The command for compiling the driver depends on whether the target is a native target, or if it is an AIX, SunOS, Windows, HP/UX, or Linux cross target.

A) For LynxOS native development systems:
/usr/ppc-xcoff-lynxos/usr/bin/gcc -c -o driver.o\  driver.c -I/sys/include/kernel\
 -I/sys/include/family/ppc -D__LYNXOS

B) On cross development systems:
$(ENV_PREFIX)/cdk/platform-xcoff-ppc/usr/bin/gcc\
 -c -o driver.o driver.c -I/sys/include/kernel\
 -I/sys/include/family/ppc -D__LYNXOS
where platform is
sunos For SunOS targets
win32 For Windows targets
hpux For HP/UX targets
linux For Linux targets
  1. Create an import file. For example, the file driver.import contains:

sysbrk
iointset
fclear
iointclr
get1page
free1page
  1. Now create a dynamically-loadable object module using ld:

- On a LynxOS native development system:
ld bM:SRE bimport:driver.import o driver.obj\  driver.o
- On a cross development host:
$(ENV_PREFIX)/cdk/platform-xcoff-ppc/usr/bin/ld\
 -bM:SRE -bimport:driver.import -o driver.obj\  driver.o
The option BM:SRE tells the linker this is a shared reusable module. The option bimport tells the linker the name of the import file. platform is the same as described in Step 1.

This will create an object module that has a loader section for dynamic linking and stub functions for kernel callbacks.

All Platforms

Once a dynamic driver object module has been created, this object module can now be dynamically installed.

For character device drivers, enter:

drinstall -c driver.obj

For block device drivers, enter:

drinstall -b driver.obj

If successful, drinstall or dr_install returns the unique driverid that is defined internally by the LynxOS kernel. For the block composite driver, the driverid returned will be a logical OR of the character driverid in the lower 16 bits and the block driverid in the upper 16 bits.

It is also possible to use a program to install a driver by using the system call drinstall().

For a character device driver, use:

dr_install("./driver.obj", CHARDRIVER);

For a block device driver, use:

dr_install("./driver.obj", BLOCKDRIVER);

Device Information Definition and Declaration

The device information definition is created the same way as in the static installation. To create a device information declaration a program has to be written to instantiate the device information definition.

Assuming the device information definition appears as:

struct my_device_info {
int address;
int interrupt_vector;
};

/* myprogram.c */

struct my_device_info devinfo = { 0xd000, 4 };

main()
{
write(1, &devinfo, sizeof(struct \
my_device_info));
}

This program can be compiled and executed while redirecting the output to a file. When compiling the program, use the default ELF-based compiler.

On a LynxOS native development system:

gcc -o myprogram myprogram.c

On a cross development host:

$(ENV_PREFIX)/bin/gcc -o myprogram myprogram.c

Then run the program on the target computer. Redirect standard output to a file.

./myprogram > mydevice_info

Device Installation

The installation of the device should be done after the installation of the driver. The two ways of installing devices are either through the devinstall utility program or cdv_install and bdv_install system calls. For example:

devinstall -c -d driver_id mydevice_info
devinstall -b -e raw_driver_id -d block_driver_id \ mydevice_info

The driver_id is the identification number returned by the drinstall command or system call. This installs the appropriate device with the corresponding driver and assigns a major device number to it (in this case, we assume this is major_no).

Node Creation

Unlike the static installation, there is no feature to automatically generate the nodes under dynamic installation. This should be done manually using the mknod command. (See the LynxOS User's Guide.)

By convention, the node is typically created in the /dev directory. The creation of the nodes allows application programs to access the driver by opening and closing the file that has been associated with the driver through the mknod command.

mknod /dev/device c major_no minor_no

The major_no is the number assigned to the device after a devinstall command. This can be obtained by using the devices command. The minor_no is the minor device number, which can be specified by the user in the range of 0255. The c indicating a character device could also be a b to indicate a block device.

Device and Driver Uninstallation

Dynamically loaded device drivers can be uninstalled when they are no longer needed in the system. This can help in removing unwanted code in physical memory when it is no longer relevant. Removal is performed with the drinstall command. However, the device attached to the driver has to be uninstalled before uninstalling the driver. Removing the device is accomplished with the devinstall command.

For character devices:

devinstall -u -c device_id

For block devices:

devinstall -u -b device_id

After the device is uninstalled the driver can be uninstalled using the command:

drinstall -u driver_id

Common Error Messages During Dynamic Installation

The following list describes some common error messages that may be encountered during dynamic installation of a device driver. In this case, the LynxOS kernel assists in debugging efforts by printing help messages to the system console.

This is usually seen when a drinstall command is executed. It indicates that a symbol in the device driver has not been resolved with the kernel symbols. Make sure that there are no symbols that cannot be resolved by the kernel and that the structure dldd has been declared inside the driver.

This error message is seen when attempting to uninstall the driver before uninstalling the device. The correct order is to uninstall the device before uninstalling the driver.

Note: A driver cannot be dynamically installed on a kernel that has been stripped.

Debugging

This section describes some of the techniques and mechanisms available to assist with the debugging process.

Communicating with the Device Driver

Because device drivers are not attached to a particular control terminal, ordinary printf() statements do not work. LynxOS provides the device driver service routines kkprintf() and cprintf() to assist in debugging. Both have the same syntax as the printf() system call.

The kkprintf() routine always outputs to the debug terminal. The kkprintf() routine can be used in interrupt routines, however, with caution. If the operating system configuration includes a device that uses the hardware used by kkprintf(), programs that access the device may hang the system.

The device driver support routine cprintf() prints to the current console. Unlike kkprintf(), cprintf() cannot be used in an interrupt routine or where interrupts or preemption are disabled.

Following are some tips for using kkprintf() and cprintf().

Simple Kernel Debugger (SKDB)

The Simple Kernel Debugger can also be used to debug device drivers. It allows breakpoints to be set and can display stack trace and register information. However, since the symbol table of a dynamic device driver is not added to the symbol table of the kernel, SKDB may not be useful for debugging dynamic device drivers. A serial connection to a second machine running kermit to capture debugging output from SKDB is also possible. See the LynxOS Total/db Guide for more information on SKDB.

Handling Bus Errors

A bus error occurs when access to an invalid address is made. The function recoset() can be used to change the default system behavior when a bus error occurs. By default, the bus fault handler calls panic(), which displays a message that a serious problem has occurred and attempts to shut down the system.

Before attempting a process that may cause a bus error, use recoset(). If a bus error occurs, program execution continues as if returning from recoset() with a non-zero return value. A branch to the code to handle the error condition can then be made. Restore the default bus error handler with noreco().

For example:

...
p = <some device address>
if (!recoset()) /* setup recoset() and establish branch back to point */
{
  x = *p /* possible bus error, if device not present */
}
else
{
/* code to handle bus error, if it occurs */
}
noreco();
...

In the example, if a bus error occurs at x = *p, program execution changes with a branch back to if (!recoset()). At this point the if condition is evaluated as if recoset() returned a negative value, and program flow continues with the code to handle the error.

Note: The install() entry point function is protected from crashing if a bus error occurs and pointers passed to the read() and write() entry points are validated by the OS so bus errors will not occur. Pointers passed to the ioctl() entry point function must be checked with rbounds() or wbounds().

Probing for Devices

It is very common for the device driver to test for the presence of a device during the install() entry point. For this reason, LynxOS handles bus errors during execution of the install routine, thus relieving the driver of this responsibility. If a bus error occurs, the kernel does not return to the install routine from the bus error handler. The error is taken to mean that the device is not present and user tasks will not be permitted to open it.

Additional Notes



LynuxWorks, Inc.
855 Branham Lane East
San Jose, CA 95138
http://www.lynuxworks.com
1.800.255.5969
TOC PREV NEXT INDEX