I needed to implement a DOS device driver for a project I'm working on. While searching for information, all the examples I found were developed in assembler. Although I have done things in assembler before, I find it much more satisfying to program in C and much, much easier to maintain, so I ended up figuring out a way to do it in C, using the Open Watcom toolkit.
Since I haven't seen anything like it out there, I'm sharing it in case anyone else finds it useful. Please open an issue if you encounter any bug or have some feedback.
All my code is under the GNU General Public License. The device.h header is from the FreeDOS kernel, with some minor modifications, and is also GPLd. See the LICENSE.txt file for details.
This is not a tutorial on writing DOS device drivers. There are excellent sources of information on the subject, both online and in print, so Google is your friend.
THIS PROGRAM IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK ASTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION
- "Undocumented DOS 2nd ed." by Andrew Schulman et al.
- "Advanced MSDOS Programming", 2nd ed. by Ray Duncan
- The FreeDOS kernel
- Ralf Brown's Interrupt List
- Jiří Malák, of Open Watcom v2 fame, for insights on the code and proper use of the OW toolchain features.
-
Edit the cstrtsys.asm file and define the device name and attributes. Examples:
For a char device:
DEVICE_NAME equ 'MYDEVIC$' DEVICE_ATTR equ ATTR_CHAR
For a block device supporting generic IOCTL and 32-bit sectors:
DEVICE_NAME equ 0 ; First byte is number of units DEVICE_ATTR equ ATTR_EXCALLS or ATTR_QRYIOCTL or ATTR_GENIOCTL or ATTR_HUGE
-
Implement the functions that your device is supporting in template.c and update the
dispatchTable
accordingly. Return value is a word (uint16_t
) with the status code in the higher byte and the error code in the lower one.The available stack space for device drivers is very scarce. Restrict the use of automatic variables to simple data types. If you want to declare variables inside the scope of a function, make them
static
Alternatively, uncomment
!define USE_INTERNAL_STACK
in the makefile. This will make the driver switch to an internal stack on entry and give enough room for using automatic variables. The default internal stack size is 300 bytes. Modify theSTACK_SIZE
definition in the template.h file or add-DSTACK_SIZE=<size>
toCFLAGS
NOTE: If your driver implements just a couple of functions, probably some if / else or a switch statement would be a better option than a dispatch table.
-
Place everything your driver must execute during initialization inside the
DeviceInit()
function in devinit.c. If an error should cause abort, indicate so by telling the kernel to free all the driver space:fpRequest->r_endaddr = MK_FP( getCS(), 0 );
Any code and data in devinit.c will be discarded after driver initialization.
-
Build using
wmake