Navigation
Highlights

Release 0.0.5

The latest release, alpha testing, unstable. See "downloads".

2005-06-01

Links
Documentation

Drivers Specification

FreeDOS-32 is based on a small kernel providing some basic functionality that can be extended by drivers: a driver is an object file that can be dynamically linked with the kernel either at boot time or at run time (in this sense, a driver is not different from a Linux module). This document describes the loadable drivers interface. Everything else (the system structure, the kernel, and so on) is described into separate documents: see Document Organization in the System Specification.

Drivers format and environment

For implementing drivers, we chose the ELF and COFF object formats because they permit to easily perform dynamic linking: hence, a driver can be statically linked with the kernel at compile time, or dynamically linked at boot or run time, through a kernel component called Dynalink.

In order to compile a driver, just proceed as if it were part of the kernel, but link it using the -r option, as in the following example:

ld <regular link options> <object files> -r -o <module file name>

Drivers can use kernel services via function call, provided that the kernel version is compatible with the function prototypes expected by drivers. The kernel contains a table of all the exported symbols, associating each symbol with the correct pointer. When the kernel loads a driver, Dynalink uses this table to resolve all the external references contained in the driver. When Dynalink can not resolve a symbol, the dynamic linking is aborted and the module loading fails.

The driver entry point is a function with its name ending with the string "_init". After having loaded and dynamically linked the driver, the kernel searches for this function and calls it. Hence, each driver can contain only one symbol containing the string "_init", that will be the entry point of the driver. In this way, it is possible to assign different names to the entry points of different drivers, permitting to link them also statically without having to use things like the Linux "#ifdef __MODULE__".

A driver can also export symbols, adding them to the kernel symbol table, or "hook" some system symbol, modifying the pointer associated to it in the symbols table. See the CMOS Clock driver for an example of a driver that modifies the kernel symbols table.

Typically, the initialization function of a module can:

  1. register new devices
  2. modify the system symbols table
  3. register interrupt handlers

Drivers interface

Drivers services can be called in different ways. The main goal of this document is to provide simple but expandible mechanisms to call driver services.

Direct function call

A driver may registers a new call in the kernel symbols table, and this function can be called from processes and other drivers loaded and linked with the kernel after that driver. Due to the dynamic linking, function calls will be very efficient near calls. In order to do that, callers must know the function prototype. Some kind of version control and/or mangling should be ensured to avoid problems due to inconsistent prototypes.

Request functions

Drivers may use a uniform interface based on a single variadic function, called the driver request function.

int request (int command, ...)
Description

The kernel, processes or other modules can call the request function to have the operation specified by command performed by a dvirer. Additional parameters may follow, depending on the operation to be performed. These additional parameters usually include a pointer to the opaque private data of a device, if a driver can manage multiple devices. This acts like the this implied parameter in C++.

The request function may block the caller until the operation is executed or may return immediatly queueing the function. Some synchronization mechanisms such as callback functions or event/signal notification should be provided in order to do that.

Return value

The return value may change depending on the service requested to the driver, but a negative value always indicate an error condition, using the FreeDOS-32 error codes. On success, drivers should return zero if no particular return value is needed.

Remarks

Please note that request above is a placeholder for the real name of the request function. You can use any name you like for it, as it is the address of this function that is registered to the FD32 kernel.

Structures of operations

The request function (see above) is a very simple extendible interface, but since it has to use a switch (or similar conditional constructs) to check for the operation to perform, it has some performance issues.

For common operations a faster interface is a structure of operations. A structure of operations is a structure containing function pointers (but it may contains other fields, too), each of them performing a common operation directly. Structure of operations are used by the Linux kernel too, an example is the struct file_operations, containing functions to open, close, read, write files and similar. This approach resembles object oriented programming as you can think the operations of a structure as the methods of a virtual class.

Using structures of operations

A user (the kernel, processes or other drivers) gets a pointer to a structure of operations of a driver as shown below. Several types of structures of operations exist. The type is a means to provide an extendible interface, that is creating new structure formats when needed, keeping backward compatibility. For example, most drivers are likely to implement character devices: such drivers are likely to expose a struct file_operations, whose type is OT_FILE_OPERATIONS. File operations can be enhanced adding support for asynchronous input/output: a new structure type can be created for this, while modules not supporting that new format can continue to expose the old format.

Once got the pointer to the structure of operations, a user calls its methods normally to perform the operations needed. Each of the function pointers of a structure of operations shall contain the address of a valid function, i.e. it may not be a null pointer. If a function is not supported, a dummy function returning -ENOTSUP shall be provided. This is in order to avoid the overhead of checking for a null function pointer before calling a method. Of course methods may fail returning an error, and in such case the caller must take the appropriate measures.

Getting a pointer to a structure of operations increases the reference counter of the module providing the operations, that is the number of users using the facilities of that module. If the reference counter of a module is greater than zero, the module can not be unloaded. When a user has done using the facilities of a module, it has to release the structure of operations in order to decrease the reference counter of the module.

Getting structures of operations

If a driver exposes its functionality through structures of operations, you can get them using its request function (see above).

int request(REQ_GET_OPERATIONS, int type, void **operations)
Description

If a module exposes the specified type of operations, this function gets a pointer to that structure in operations. This is done by the REQ_GET_OPERATIONS command of the request function. The caller may pass a null pointer as operations argument: this can be used to check if the specified type of operations is available.

Return value

If the specified type of operations is available, a pointer to that structure is stored in operations and the request function returns zero.

If the type is not available, or the request function does not support the REQ_GET_OPERATIONS command, the request function returns -ENOTSUP and the content of operations is undefined.

Releasing structures of operations

As said above, a user that does not need the facilities of a module any longer must release the structure of operations it got in order to decrsease the reference counter of the module providing those operations. This is done by calling the request function with the REQ_RELEASE command.

int request(REQ_RELEASE)

Decreases the reference counter of a module. Mah!

Defined types of structures of operations

The following table is the complete list of defined types of structure of operations, showing the mnemonic constants (and their value) that you can use when getting a structure of operations through the request function. In order to ensure that each type value is unique, all defined types of operations must be included in this list.

In any case, developers should use mnemonic names instead of the integer values. Mnemonic constants for operations types begin with a capital OT_ and are followed by the name of the structure of operations in capital letters (for example, the type of a struct file_operations is OT_FILE_OPERATIONS).

Mnemonic Value Description
OT_FILE_OPERATIONS 1 character I/O on file
OT_BLOCK_OPERATIONS 2 I/O on block devices
OT_NLS_OPERATIONS 3 national code pages services