OS/2 Device Driver Frequently Asked Questions

From EDM2
Jump to: navigation, search

By Tim Snape

This article contains a selection of frequently asked questions and is intended to be used by anyone who is writing or planning to write OS/2 device drivers. The full file with some code example can be downloaded at hobbes.

Contents

Support Categories

Situations when it is (un)necessary to write an OS/2 device driver

Is a driver necessary to use IRQs

Question: I have a requirement to intercept hardware interrupts from an adapter. Do I need to write a device driver.

Answer: Yes, a device driver is absolutely the only way to interact with hardware generated interrupts.

Is a driver necessary to access I/O ports

Question: I have a requirement to read and write to I/O ports. Is it necessary for me to write an OS/2 driver.

Answer: No, it is not.

Under OS/2 there are four levels of privilege (referred to as rings)

  • Ring 3 - Least privilege, this is where applications normally reside
  • Ring 2 - Next highest privilege. Code which is running at Ring 2 may access I/O ports under OS/2. For this reason this privilege is often referred to as I/O privilege level or IOPL.
  • Ring 1 - Is not used under OS/2
  • Ring 0 - Is the highest privilege level and is used by device drivers & the operating system.

In order to access an I/O port (and also to disable/enable the interrupt flag) a program MUST be running at ring 2. The way this is done is to specify in the programs definition file that a particular segment will have IOPL (IO privilege).

Next comes the clever bit. The compiler/ling will generate an executable that contains a special (IOPL) code segment. When the program is loaded, the loader will see that there is an IOPL segment & it will create a special mechanism that will allow that segment to run at ring 2.

For the technically knowledgable the loader creates a ring 2 call gate.

When the IOPL segment (containing the I/O access code) is called, privilege is changed to ring 2, and I/O access can be performed.

N.B. There is an overhead in this transition so if you plan to implement a polling loop on an I/O port (or something equally horrible) then be warned.

For some example code that accesses I/O ports go to the file download area.

Is a driver necessary to access memory on an adapter card

Question: I have a requirement to read and write to specific areas in memory in the range D000:0 to D800:0. This is to interface with a special function option card.

There is no time criticality or interrupts to deal with, so writing an OS/2 device driver does seem rather over-the-top.

In DOS, no problem. With MS-Windows, there are Global Selectors pre-defined ( _D000 etc. ).

How can this be done using OS/2.

Answer: At some point you will HAVE to use a driver to access the RAM directly. However it is not necessary to write a driver from scratch. Instead use one of OS/2's drivers to do the job for you.

Failing that, there are a number of drivers written by third parties that will provide you with this function.

I have placed some code in the anonymous ftp area that can be used to access memory directly. Download the Technical Developers Toolkit. The interesting file in the .zip is parallel.c, this contains code that reads the Bios data area used to hold the parallel port addresses.

Accessing Video Ram

Question: I need to access the memory mapped video ram do I need a driver

Answer: There is already support for obtaining addressability in the way you require. I suspect you don't need a device driver. You can access the frame buffer directly, use memory-mapped and port IO from the app level.

A note on the physical memory address space. Your graphics adapter memory can live anywhere above the end of physical memory (RAM) up to the 4Gb limit (0FFFFFFFFh). To see the memory, try as Tim suggested, using the KDB to dump physical memory, %%address.

There is an IOCTl supported by the SCREEN$ device driver for this purpose. You'll need to know the physical address and the aperture size, ie. how much memory the adapter has. See the include file bsedev.h and the definitions for SCREENDD_GETLINEARACCESS. The IOCTl will create and return a linear address which maps to the physical for you to use.

The flags field in the call is critical and one reason I asked what you're writing. They allow you to choose where in OS/2s linear address space the mapping occurs 1) in the current process address space 2) in the shared arena 3) in global space. BTW the flags map directly to those for VMAlloc except the ATTACH bit which causes a DevHlp_ProcessToGlobal.

Application Issues

Accessing IO ports at application level

Question: I have a requirement to access I/O ports from a 32 bit application. I know all about IOPL privilege BUT the only way to create a ring 2 IOPL segment seems to be to use a 16 bit DLL, is this right ?

Answer: Unfortunately yes, there is a more complete explanation of IOPL and accessing I/O ports in the Is a driver necessary section, so I will not repeat it here.

Basically though ring 2 IOPL segments can only be 16 bit, there are however several approaches to solving your problem.

  • Use another driver, one that already exists. The TESTCFG driver contains code that allows you to access I/O port addresses higher than hex 100.
  • Package up your 16 bit IOPL segment into a DLL and call that from your 32 bit code segment.
  • Write a driver from scratch and get the driver to perform the I/O on behalf of the application.
  • Write a driver from scratch and get the driver to set the IOPL bits in the EFLAGS register to 3. This will allow the calling thread to perform I/O at ring 3.

The last (rather neat) suggestion was supplied by Holger Veit. It should be noted that patching the EFLAGS register will only allow the calling thread to access I/O ports.

Read buffers greater than 64K

Question: When performing Read/Write operations can I use data buffers larger than 64K.

Answer: No you cannot. The Read/Write device driver commands have a length field which is a 16 bit word, this implies that the largest I/O operation is 64K.

If you really want to perform I/O on regions greater than 64K, then you should use an IOCTL command to pass in the address of a 32 bit memory item.

Compiling and building your OS/2 device driver

Using the Microsoft C compiler

Question: Should I use the Microsoft C6 compiler & if yes, where can I buy it.

Answer: Back in the old days when IBM & Microsoft were talking to each other, all device drivers were written using Microsoft C compilers. Usually Version 6.00a.

Unhappily following the rift between the two companies Microsoft withdrew support for the C6 product & brought out new versions. These versions were incompatible with OS/2.

Many people today still use the C6 compiler, many examples exist based on the C6 compiler & many companies still recommend using the C6 compiler - but you cannot buy it.

So if you want an easy life use the C6 compiler, how you acquire the tool is the real question. But that cannot be answered here.

If you elect to purchase an alternative C compiler to generate your driver look at the other QA's in this category.

Using the Borland C compiler

Question: How do I use the Borland compiler

Answer: You should refer to Borlands web site for support questions.

Using the Watcom C compiler

Question: How do I use the Watcom compiler version 10.5

Answer: Watcom is a recommended supplier of 'C' compilers for OS/2 device driver developers. IBM development are now widely using the product. The Watcom 'C' compiler comes with sample OS/2 device drivers plus a number of useful utilities. For detailed support information on using the Watcom product you should refer to Watcoms web site.

When using Watcom for the first time you may experience compatibility problems linking to libraries compiled using other products. The following comments assume you are using the dhcalls.lib helper library found in the Devcon DDK

  1. Instead of _acrtused, the Watcom C runtime library looks for cstart_. This should be declared as public in the .asm file.
  2. Watcom provides a special pragma, cdecl, for specifying the calling convention used by MSC. They also provide a _Cdecl keyword, as in 'int _Cdecl main(...);'.
  3. The Watcom naming convention is to append an underscore to function & variable names. Under Microsoft 'C' the convention is to prefix the names with underscore :
 Watcom function name  = name_
 MSC     function name = _name

Therefore all references to Watcom 'C' names from assembler code should assume a trailing underscore. All references to Microsoft 'C' names from assembler code should assume leading underscores. The reverse should be assumed when referencing assembler names from Watcom or MS 'C' code.

  1. The compiler flag -Zu must be used.
  2. The "_Seg16" keyword is not legal when compiling with WCC(Watcom 16bit), therefore in all declarations "_Seg16" must be replaced with "_far". Because of this some of the toolkit headers like OS2DEF.H must be changed.
  3. The "PASCAL" keyword must be changed to "pascal".
  4. There is a library of device driver helper functions, that is callable from Watcom 'C'. This library can be found on the DDK BBS.
  5. The symbol files generated by Watcoms linker are incompatible with the Kernel debugger. If you use Watcom .sym files and the kernel debugger then the system will crash at boot time, with the error message :
 Internal symbol error: SegDefLinkUp

There is a utility for translating Watcom .sym files to the correct format. This utility is called WAT2MAP.EXE and can be found on the DUDE BBS.

  1. The Watcom 'C' compiler is more rigorous in its compliance with the ANSI 'C' standard. This may cause minor problems when porting from other 'C' compilers.

There was an article on porting OS/2 device drivers to Watcom 'C' in the Devcon 7 newsletter.

Please note that according to Watcom technical support, there are some problems with using NMAKE with the Watcom compilers/linkers.

Using the CSet compiler

Question: How do I use the CSet compiler

Answer: You should not. CSet generates 32-bit code this is incompatible with the the 16 bit architecture of OS/2's device drivers.

Will IBM fix CSet so that it can generate OS/2 device drivers

Question: Will IBM fix CSet so that it can generate OS/2 device drivers

Answer: No, the stated policy on CSet is that it will NOT and NEVER will generate 16 bit code. The reason IBM's CSet people give for this drastic decision, is that there already exist many, many 16 bit 'C' compiler products.

In fact this will all change when OS/2 for Power hits the streets. OS/2 for Power uses a 32 bit driver model so it will be possible to use CSet for drivers in the future.

Using the CSet compiler to generate 32 bit driver code

Question: How do I use the CSet compiler to generate 32 bit code for linking into a 16 bit device driver.

Answer: This can be done. The security kernel consists of 16 bit assembler code which is linked with 32 bit code emitted by CSet.

There is an example device driver that does this however it is not for the faint hearted. In order to get the code to work you must first supply a (complete) replacement run-time library. The body of the driver is pure 16 bit assembler and the CSet code is bolted onto this.

The way this code is implemented is very specific to the security kernel, however the message is it can be done

Using Steve Mastriannis example 'C' code.

Question: I am about to write a device driver for a simple network now. Before starting this I am reading the book "Writing OS/2 2.1 device drivers in C" by Steven J. Mastrianni. In this book they write about a C callable 2.0 Devhlp library and a toolkit. About this I have a few questions. I don't have this C callable Devhlp library so I will have to call this function from an assembler routine. My problem is that I can not find any "prototype" of this function anywhere. I suppose these are different for all Devhlp functions. Where do I find this?

Answer: Steve supplies these helper libraries as seperate products. I think the retail price is 129 USD.

There are a number of alternative helper libraries. These can be found in the Devcon DDK product.

It is unfortunate that IBM was not able to standardise on a single library of helper functions. The result is everyone tends to create their own.

Problem using Microsoft library

Question: In S.J.Mastrianni's book (second edition page 498 3rd line from the end and in manyother places) he uses the library "llibcep.lib" from the Microsoft Compiler LIB directory. I did a full instalation of the compiler (Version 6.0A) and I can not find the librery.

Answer: The files installed & their names will vary depending on the installation options you typed in when you ran the MS setup program.

The name

llibcep
  l = represents large memory model
  s = represents small memory model
  e = represents floating point emulation
  7 = represents floating point in 8087 hardware
  p = represents protected mode
  r = represents real mode

One of the installation options asks "use default naming convention" in which case your llibcep.lib file is renamed llibce.lib

To fix it either rename or copy the file to llibcep or modify your link parameters so it uses the "correct" default name of llibce.lib.

Where are the 16 bit DOS library functions

Question: I wrote a driver under OS/2 1.3, but now under Warp the linker cannot find the DOS library functions, DosOpen, DosRead etc., where are these functions ?

Answer: These functions used to be in the file DOSCALLS.LIB, this file is no longer shipped with the Warp Toolkit, instead you should use the library OS2286.LIB.

Alternatively you could copy your version of DOSCALLS.LIB from the earlier release to your current development system.

Initialising the driver for the first time

When you reboot, the device driver will not install

Question: The driver has been compiled & linked with no apparent problems but at initialisation time the kernel reports that the driver is in an incorrect format.

Answer:There are a number of rules for the layout of OS/2 device drivers:

  • The first segment MUST be a data segment.
  • The data segment MUST contain a header structure at the very beginning.
  • The header structure must be in the correct format.

Data items quite often get placed in front of the device driver header structure, there is a tool called DHDR.EXE that will dump the contents of a device driver files header.

At initialisation time the device driver is responsible for telling the kernel the length of the drivers code & data segments. The kernel then uses the information to limit the amount of storage allocated for the drivers code & data.

If you forget to set these fields up then by the values used will be invalid and your driver will not have very long to live.

Similiarly if the value supplied by the driver is too short, then the moment you pass control into this non-existent code space the driver will crash.

There are a number of ways of identifying this problem and removing it.

  • You can examine the .MAP file, if a code function occurs after the initialisation code then you will probably want to move code around.
  • Place special markers at the end of the data & code segments. In the data segments the marker could be a text string e.g. char *eodata = "END OF DATA"; In the code segment the marker could be a function (not a static) with a special name.
  • The simplest approach is to say (at init time) that the size of the code & data space is the size of the currently loaded segments (LSL assembler instruction). This way you are guaranteed that all the code will be in the correct place.

In the bad old days of DOS & OS/2 1.x memory for device drivers was at a premium, with OS/2 2.x & Warp this is no longer the case. Make life easy for yourself and do not worry about saving that last 100 bytes of code.

How should a driver output strings to the display at boot time

Question:What is the best way to output a string to the screen from the DD INIT routine and INIT_COMPLETE routinE?

In INIT I call DOSPUTMESSAGE. Is this function still available for INIT_COMPLETE - or does that depend on INIT segment of my code?

Answer: DosPutMessage is fine for INIT time. You could also do a DosWrite to the stdout device (File handle 1). Similiarly you can read keystrokes using a DosRead on the stdin device (File handle 0).

There is no way for a driver at INIT_COMPLETE time to output a message.

The only solution I can suggest is to fire up an application in startup.cmd or run from config.sys. And get that application to talk to the driver & report whether the INIT_COMPLETE was successful or not.

The only reason for the INIT_COMPLETE is to set up links between drivers (IDC) and you do it after all the INITs so as to remove any dependencies on the order of DEVICE='s in the config.sys. There should be no need to output strings at INIT_COMPLETE time.

How should a base driver output strings to the display at boot time

Question: What is the best way to output a string to the screen from a base device driver at INIT time.

Answer: This is a little tricky.

DosPutMessage is fine for INIT time. However it is not available to Base Device drivers. Instead there is a special device helper that saves all the base device driver messages. At the end of base device driver initialisation all the saved messages are output to the display.

#include  "dhcalls.h"

USHORT APIENTRY DevHelp_Save_Message( NPBYTE MsgTable );

typedef struct _MSGTABLE {

  USHORT   MsgId;                       /* Message Id #                  */
  USHORT   cMsgStrings;                 /* # of (%) substitution strings */
  PSZ      MsgString[1];               /* Substitution string pointers  */
} MSGTABLE;

typedef MSGTABLE *NPMSGTABLE;

As you can see from the example, the save message helper requires you to use message ids.

How can you tell the type of the PC's bus at INIT time

Question: I want my device driver (PDD) to determine if it runs in an EISA PC environment at INIT time. Is there any support by the operating system to do this ?

Answer: There are some IOCTL calls a driver can make to talk to the TESTCFG driver and ask what type of bus the system is using.

The file bus.zip in the download area contains the source code you need to query the bus type, ISA, MCA or EISA.

Function call hangs on return

Question: At INIT Time I use DosPutMessage() to print some messages on the Screen. If I call DosPutMessage() directly from my INIT-function, it works fine. But if I put DosPutMessage() in a separate function and call this function from my INIT function, the driver hangs upon return.

Why? Is this a limitation or a bug?

Answer: A bug.

This is a fairly common problem. If code (any code) is crashing on a return instruction the most likely cause is because the stack frame (which has the return address) is in some way damaged.

I would guess that you have declared the function incorrectly either

declared as pascal & called as C

or

declared as C called as pascal

If there is only one input parameter then both variants can be called ok, & will work, but both will crash when you return. Of course there could be another explanation, you have an errant pointer which is corrupting the stack:

function ()
{
   char arr[10], *p;

       p = &arr[0];
       *--p = 0;       // the return will "probably" crash
}

Differences between OS/2 2.0 & 2.1 at initialisation time

Question: I recently moved from OS/2 2.0 to version 2.1 and I noticed that my driver was being entered with some new strategy commands. Specifically, at boot time immediately after the INIT call, I get called again with function 1F. What is this function ?

I also noticed that my CS selector changed between the INIT function and the 1F function (from 0AEB to 0AE8). What is going on ?

Answer: The 1f function is new, its purpose is to tell all device drivers that all device drivers are now installed. It is referred to as INIT_COMPLETE.

The INIT_COMPLETE command is passed into drivers at initialisation time after all device drivers have been initialised. The context is strategy time and the drivers privilege level is 0.

The operation you should perform at INIT_COMPLETE is to make all your IDC attachments (see attachdd helper service).

OLD WAY

1) Drivers install at ring 3 with iopl set to 3

2) Applications open and start using drivers

NEW WAY

1) Drivers install at ring 3 with iopl set to 3

2) Drivers can continue to initialise and communicate with other drivers, at ring 0 with iopl set to 2

3) Applications open and start using drivers

As regards your second question :

I also noticed that my CS selector changed between the INIT function and the 1F function (from 0AEB to 0AE8). What is going on ?

That is quite normal, the selectors are constructed so that the low order two bits contains the "Requestors Privilege Level" or RPL

At Init  time the RPL is 3 - 0AE8 orred 3 => 0AEB.
At 1F   time the RPL is 0 - 0AE8 or    0 => 0AE8.

Error code SYS1201 at Initialisation time

Question: I recently assembled a pysical DD with MS MASM 6.0. When I install it on my CONFIG.SYS file by typing: DEVICE=C:\........\XXXXX.SYS

I have 2 situation on 2 different configuration. On a PS/2 P70 all run OK. On a PS/2 95-V01 I have an error code SYS1201. Why?

Answer: When a driver is initialised it has the option of installing or not installing, the 1201 error means it is not installing, this could be for anyone of a million reasons - ALL UNDER THE CONTROL OF THE DRIVER -

So basically your driver has given up, without really telling you why.

It could be it could not acquire an interrupt because it was not available " " " " " " " an memory segment " " " " " " " a GDT selector " " " " " " " a timer

 anything in fact.

Two suggestions if the driver is not going to install call a standard routine with a message to display. The following is what I use.

#include 

UINT abend (PREQPACKET rp, char *message);

static char     ActMessage[] = "Hit enter key to continue.\r\n";
static char     FailMessage[]  = " driver failed to install.\r\n";
static char     CrLf[]= "\r\n";

UINT abend (PREQPACKET rp, char *message)
{
   ULONG length;
   UCHAR ch;

   DosPutMessage(1, strlen(FailMessage), FailMessage);
   DosPutMessage(1, strlen (message), message);
   DosPutMessage(1, 2, CrLf);
   DosPutMessage(1, strlen(ActMessage), ActMessage);

   DosRead ((SHANDLE)0, (FARPOINTER)&ch, (USHORT)1, (FARPOINTER)&length);

   rp->s.InitExit.finalCS = 0;
   rp->s.InitExit.finalDS = 0;

   return RPDONE;
}

But you are desperate, so put an int 3 instruction in your drivers initialisation routine, reassemble and install it on the problem machine, using the kdb you should then be able to follow through the code to the point where the driver is setting :

   rp->s.InitExit.finalCS = 0;
   rp->s.InitExit.finalDS = 0;
       which will cause the 1201 result.

If you apply (or already have) the change and you are still experiencing the same problem then there is an even simpler explanation. You are trying to install the same device driver twice.

This will work under DOS. Under OS/2 you cannot install multiple device drivers with the same name (and attribute). And anyway, why would you want to ?

How can you tell which version of OS/2 you are using at INIT time

Question: Is there a way to know during init time if a devide driver is running under OS/2 1.3 or OS/2 2.0. What sort of call,function or instruction can i use to find out ?

Answer: You can use the DosGetInfoSeg API at INIT time. This API provides the addresses of two data structures:

  1. The Global info seg
  2. The Local info seg

The addresses of these structures does not change so you only need to raise the query once. Once you have the address of these structures you can query their contents, at any time during the device drivers life. I've enclosed the code fragment I use to display the version number at Initialisation time.

   DosGetInfoSeg (&g, &l);
   Ginfo = MAKEP(g, 0);
   Linfo = MAKEP(l, 0);

//          Output OS/2 version number
   DosPutMessage(1, strlen(InitMessage1), InitMessage1);

   ch = Ginfo->uchMajorVersion;
   out_dec (ch);
   DosPutMessage(1, 1, ".");
   ch = Ginfo->uchMinorVersion;
   out_dec (ch);

   DosPutMessage(1, strlen(VersMessage2), VersMessage2);
   out_dec (ch);
   DosPutMessage(1, 1, &ch);

   DosPutMessage(1, 2, CrLf);

Calling API's at INIT time

Question: I have seen an example device driver that invokes a DosExecPgm during initialisation. Is this possible ?

Answer: Yes it is, so long as the file system is ready (so you can read the DLL's), you can call any and all the .DLLs on the computer, HOWEVER the the sub-systems required to support/cooperate with those dlls, may not be ready to go at init time . This means that the dll call may appear to work at init time but it may not work in the next release of the OS.

The ONLY APIs documented as supported at init time are - DosBeep & all the file handling APIs. Unless there is a very good reason, I would suggest complying with the documentation.

SYS1719 error after loading a PDD

Question: What are the posible reasons for SYS1719 error after loading of PDD?

Answer: The output from "help sys1719" states:

SYS1719: The file "***" specified in the *** command on line *** of the CONFIG.SYS file does not contain a valid device driver or file system driver. Line *** is ignored.

EXPLANATION: The file specified does not contain a valid device driver or file system driver, or contains a valid DOS device driver when a DOS session was not started.

ACTION: Perform one of the following actions, then restart the system:

  1. Edit the CONFIG.SYS file to correct or remove the incorrect command.
  2. Edit the CONFIG.SYS file to remove the PROTECTONLY=YES command.
  3. Install the correct device driver or file system driver in the specified file.
  4. Install all dynamic link libraries required by the specified device driver.

There are a number of reasons your driver may be considered invalid, if you have a map file for it, check:

  • That the driver contains a data & a code segment IN THAT PHYSICAL ORDER
  • The very first data item in the data segment is the drivers header.
  • Quite often when people are building drivers, the device header (WHICH MUST BE AT OFFSET ZERO) is placed incorrectly in the data segment.
  • If you have the utility exehdr then you can try examining the driver with that, ie EXEHDR MOUSE.SYS:
Microsoft (R) EXE File Header Utility  Version 2.01
Copyright (C) Microsoft Corp 1985-1990.  All rights reserved.

Library:                  MOUSE
Description:              mouse.DLL
Data:                     SHARED
Initialization:           Global
Initial CS:IP:            seg   0 offset 0000
Initial SS:SP:            seg   0 offset 0000
DGROUP:                   seg   1
PROTMODE

no. type address  file  mem   flags
  1 DATA 00000110 001f1 01430 SHARED, PRELOAD
  2 CODE 00000320 01c9a 01c9a PRELOAD
  3 CODE 00001ff0 01ac3 01ac4 PRELOAD, IOPL, (movable)

Where in memory, is a device driver loaded

Question: If I got the previous conversation right in this area, then device driver will be loaded with its first DataSeg and CodeSeg below the 1 MB border. Is this correct ?

Answer: Yes, it is correct for OS/2 1.x. No, it is wrong for OS/2 2.x

Under OS/2 1.x, it was a problem too, as all those OS/2 drivers sucked up the DOSboxes memory. Under 2.X however the physical ram for the dd's code & data is coming from the TOP of memory not the bottom.

Making messages to display at Initialisation time

Question: I have heard of a program called MKMSGF which can be used to generate some sort of generic message information which allows a driver to support foreign languages very simply. It seems to have some relationship with a DLL call LANMSGDD.

Do you know where I might find out detailed information about this, so I can look at include such support in my drivers.

Answer: The MKMSGF & the MSGBIND utilities allow you to bind text messages into an application AFTER it has been compiled & linked. All OS/2's error and warning messages work this way.

The intention is that a developer can produce some code which is then given to the distributors. The distributors can then apply all the text messages to the app. The benefit is that the distributor can use different message files for different countries and thus (hopefully) "internationalise" the application, to suit the end user.

There is info (and examples) on how to use these utilities in the toolkit tools reference online help.

The rules for using messages bound in this way from within a device driver; you can either use the DosGet/Put/Ins message APIs or alternatively there is a helper service (0x3d). In the 2.0 doc Physical Device Driver Ref, this helper is called SAVE_MESSAGE 17-26. This is a typo its real name is DispMsg - which is why it appears between DevDone & DynamicAPI. Messages can only be displayed in this way by device drivers at initialisation time.

How can I query the OS/2 language version at initialisation time

Question: I want my driver query the language version of the system so I can output messages in the correct language.

Answer: There are several ways to do this:

  • Open the keyboard driver (DosOpen ("KBD$", ...)) and pass in an IOCtl request to query the current codepage.
  • Use the DevHelp_GetDOSVar to read the Codepage field from the Global Info Seg.
  • Parse the config.sys file to read and interpret the CODEPAGE= line.

But really all the above solutions are wrong. The correct approach would be to use Message files. You should create one message file for each language you intend to support, then when the driver is installed you should let the user decide which language variant should be installed.

Can I use an IDC link at Initialisation time

Question: I have created an IDC link to another driver. I used the AttachDD helper service to create the link at initialisation time. When I tried to call the other driver using the IDC link the system hung. Why?

Answer: The IDC link information is for a Ring 0 call, into the Attached driver. At Init time your driver is running at Ring 3. It is not possible to call from Ring 3 to Ring 0 in this way. Give up it cannot be done.

Instead, you should delay ATTACHing to other drivers until all the drivers have been installed. You do this by adding support for the INIT_COMPLETE command. Once your driver receives the INIT_COMPLETE, you know that all drivers will be installed. You should now establish the IDC link using the AttachDD helper service. Once this is done you can start using the IDC interface, immediately.

Start of operations

How should an application handle a proprietary device driver error code

Question: I am trying to return a proprietary return code to a READ strategy command, however what ever value I return is interpreted by the system causing the device driver error message window to be displayed. The value I am returning is:

RPERR | RPDEV | RPDONE | my-8-bit-code

I understood that specifying RPDEV meant that OS/2 would not try to interpret the error. Am I wrong? Am I restricted to a particualar range for my-8-bit-code?

Answer: There are two things I can suggest you do:

  • When you open the driver, use the bit flag to tell the kernel to return error codes to the application (I cannot remember which bit it is, but its in the manual)
  • Use the DosError API to selectively enable/disable the error notification popup.

How should a driver return a proprietary error code

Question: My driver needs to return a special error code. The list of standard error codes does not include the error I want to return. How should my driver return its special error code.

Answer: Set the following bits in the Request packets status word.

0x8000  -   RPERR - Signal an error
0x4000  -   RPDEV - Signal a device specific error code
0x0100  -   RPDONE - Signal the operation is complete
0x00XX  -   The drivers special error code

The RPDEV setting is the important one as it informs the system that this is a device specific error code, and NOT one of the standard ones.

The driver crashes the first time you call it

Question: The driver has installed correctly, everything is looking good, but the first time you call the driver it crashes with strange register values. You may have looked at the driver using the kernel debugger but the driver is still crashing in a very strange way, and for no apparent reason. Answer

Answer: At initialisation time the device driver is responsible for telling the kernel the length of the drivers code & data segments. The kernel then uses the information to limit the amount of storage allocated for the drivers code & data.

If the value supplied by the driver is too short, then the moment you pass control into this non-existent code space the driver will crash.

There are a number of ways of identifying this problem and removing it.

  1. The simplest approach is to say (at init) time that the size of the code & data space is the size of the currently loaded segments (LSL assembler instruction). This way you are guaranteed that all the code will be in the correct place.
  2. You can examine the .MAP file, if a code function occurs after the initialisation code then you will probably want to move code around.
  3. Place special markers at the end of the data & code segments. In the data segments the marker could be a text string e.g. char *eodata = "END OF DATA"; In the code segment the marker could be a function (not a static) with a special name.

How can a driver tell which session context it is being called in

Question: I need to be able to tell what type of application is invoking the driver. Windows, OS/2 I would also like to know what kind of session the application is running under - Full Screen, Windowed etc.

The sgCurrent field in the global info seg seems to give me the information I want, at least in my initial testing. I've found that it takes a value of 1 when a windowed app has focus (i.e. PM apps, seamless WinOS/2 apps) and different values if a full-screen DOS or OS/2 session has focus. My method then simply becomes:

if (sgCurrent == 1)
   Let video be shown
else
   Don't let video be shown

My worry is that I can't find the values of sgCurrent documented anywhere, so I'm a bit uneasy about assuming I've done the right thing. Do you know of any documentation of this field?

Answer: The sgCurrent field contains the number of the current Screen Group. Different types f screen group use specific values.

The PM session is always 1, OS/2 full screen sessions start at 4, and full screen DOS/Win OS/2 sessions begin at 16.

In addition there are pop_up & hard error sessions, I guess there numbers will be 2 & 3 respectively (or vice versa).

I haven't seen these values documented anywhere. So be warned they may change.

There is a TypeProcess field in the local info seg, this describes the type of the application:

0 Full Screen protect-mode session
1 Requires real mode
2 VIO windable protect-mode session
3 Presentation Manager protect-mode session
4 Detached protected-mode process

This is from the PDD reference manual under GetDOSVar.

Since you are looking to detect screen switching, look at the cat 5 ioctls, you should see some cat 5's coming through to all drivers when screen switching. From memory I think they are query & set active font. You will need to experiment but I believe they may be useful.

How should a driver use the busy flag

Question: I developed a simple device driver for an in-house device that performs synchronous comms using the DevIOCtl call. This works fine in either interrupt or DMA modes but I found a problem when I had two applications hammering the device. Eventually one applications communications threads just seizes up.

I traced this problem to the fact that the application with focus was getting all the communications time and as a result the device driver evetually returned a timeout on a RAM semaphore to the other thread. This was done using the DevBusy value as in:

   MOV     AX, DevBusy OR ReqError
   MOV     [(Req ptr ES:BX).ReqStatus], AX

This results in the DosDevIOCtl Call never returning to the caller. A change in code to

   MOV     AX, Done OR ReqError
   MOV     [(Req ptr ES:BX).ReqStatus], AX

Returns OK.

Now the questions. Is there a problem in the way I am returning the DevBusy flag?

Answer: Yes,

The DevBusy flag only has significance for the status & test for removable media commands. The way you are using it in the ioctl command it will have no affect at all (its being ignored). The reason you have a problem is that you are omitting the Done bit. The way it works is this:

Any driver returns any command with the Done bit NOT set

The kernel will block that thread (lets call it thread A)

.... time passes
.... more time passes

A driver invokes the DevDone helper service with the es:bx register pair set to the address of thread A's request packet

The kernel will unblock thread A, and control will be passed back to thread A

The way this mechanism is intended to function:

Thread A does something but the operation needs to wait for an external event (an interrupt perhaps) before it is finally complete.

Thread A saves the address of its request packet somewhere

Thread A exits with the Done bit clear

The kernel will block thread A

.... Time passes

The interrupt occurs, does what it needs to do, and flags the blocked thread (Thread A) that the operation is complete using the DevDone helper

The interrupt irets

Thread A completes

What is the Yield flag for and why should I check it ?

Question: How am I meant to use the yield flag, should I test it before issueing a yield. What should I test it for.

Answer: You do not need to check the yield flag.

To understand the yield helper imagine you are back at college sharing a flat with several other students.

You are on the phone to your girlfriend, meanwhile your flat mates also want to use the phone. The device driver ref states that you should yield the phone to your flat mates every 3 milliseconds. Now you could simply yield every 3 ms or in a better organised flat you would ask your flat mates every 3 millis if any one else wants to use the phone, if noone does, then carry on talking to your girlfriend.

The bottom line is that it is more efficient to:

  1. GetDOSVar the address of the yield flag
  2. do your thing for 3 millis
  3. Check the yield flag
  4. If the flag is clear goto 2)
  5. Yield
  6. Go to 2)

This is because when you yield (or TCyield), your process is descheduled, the kernel then schedules the next highest priority task. If your task is the next highest priority task it will be scheduled again. Which is an inefficient way of doing things.

When to use the DONE bit

Question: When is there a need to return from the strategy function without setting the DONE bit? (There must be because otherwise the bit would be useless).

Answer: The DONE bit provides a simple & easy (to implement) way to manage asynchronous events. The following pseudo code illustrates the two options :

1) Using the DONE bit 2) Using BLOCK/UNBLOCK helpers.

Using the DONE bit

 strat rtn
       do something
       set up an int handler
       put the request packet into a data structure somewhere
       return with done bit not set
               the kernel will now block the calling thread

 time passes ....

 more time passes ....

 int handler
       get the previously saved request packet
       do what ever you have to do (the request packet contains info
       on the calling threads read/write buffer etc.)
       invoke devdone helper using the es:bx address of the request packet
       ret
               the kernel will now UNblock the calling thread

Using the BLOCK/UNLOCK helpers

 strat rtn
       do something
       set up an int handler
       put the request packet into a data structure somewhere
       invoke the block helper on the calling thread using es:bx address of
            the request packet as the block id
               ... the thread is now blocked until int handler unblocks it
       ...
       YOU CAN NOW DO SOMETHING HERE
       ...
       return with done bit set

 time passes ....

 more time passes ....
 int handler
       get the previously saved request packet
       do what ever you have to do (the request packet contains info
       on the calling threads read/write buffer etc.)
       invoke unblock helper using the es:bx address of the request packet
       ret
               the kernel will now UNblock the calling thread

As you can see the only functional difference between the two solutions is that if YOU do the block/unblock you can do some processing at the end of the strat routine. If you do not need to do processing at the end of the strat routine then exiting with done bit clear is the simpler solution.

When does the system change the running thread

Question: I am confused about blocking & yields. If a device driver never issues these calls, who does and when. How does this affect the thread that is currently scheduled and descheduled.

Answer: The way it works is this.

The current thread is only descheduled under certain circumstances, these are called scheduling opportunities and here they are:

  1. In response to the Yield helper
  2. In response to the Block helper
  3. Ring 0 to Ring 2 & 3 transitions

Notice I have not included exiting drivers with done status bit not set, because all rets from your driver (see 3) above and next para) are scheduling opportunities including the not done exit.

The ring 0 to ring 2 & 3 transition occurs after every entry to your driver including interrupts & traps, BUT if you get an interrupt when you are already at ring 0, no descheduling opportunity will take place.

The overhead of blocking high priority threads

Question: I have a number of questions.

Q1. Does blocking a high-priority thread impose more load on the system than blocking a low-priority thread?

Q2. When a thread is run by a device driver, does the priority of the thread determine how quickly that thread is scheduled?

Q3. The NETBIOS command completion is scheduled using semaphores - when a semaphore is cleared, does the priority of the thread waiting on the semaphore determine how quickly that thread is scheduled?

Q4. Perhaps I should change the priority of the rx_thread dynamically,depending on the level of the rx q, ie decrease its priority as the queue fills up?

Answer: Yes, yes, yes, Perhaps.

Time critical threads will be serviced 8 milliseconds after an unblock. Normal priority threads take 32 milliseconds. Basically the timer resolution is increased by a factor of four when you block a Time Critical thread. Its a bit like saying, "this is a really important job, respond immediately it unblocks". This will impose some overhead on the CPU, but not much.

As far as dynamically changing priority I would not bother. If you are really concerned, invest time in benchmarking and measure EXACTLY what the overhead is. Then worry about optimising. Optimisation without some metrics is rarely useful and usually counter productive - but thats just my opinion

Using Request Packets

PUSH request packet will not work

Question: I am using the PushRequestPacket helper at strategy time, yet whatever I do it always returns error.

Answer: No, it does not.

The real gotcha with these push/pull helpers is that push, DOES NOT set/clear the carry flag on completion, so do not test it to see if it works. I suspect you ARE using the push (correctly) at strategy time (where its meant to be used) but testing the result of the operation which YOU SHOULD NOT DO.

The upsetting thing about push/pull request packet helpers is that they are inconsistent. They are the only helpers that do not return an error code and they should be regarded as special cases.

Pushing request packets at interrupt time

Question: I want to use the PushRequestPacket helper at interrupt time, yet it does not seem to be supported. Why is this & how do I get round it.

Answer: The question is "why do you want to push request packets in an interrupt". For arguments sake we will assume you have a good reason .

It was probably felt by the original designers that there was no reason for developers to want to push request packets so they made the arbitrary decision not to support it. Think of it as a design limitation (not a bug).

To code round the problem, simply write your own function. Appending a request packet onto the end of a Request Packet Queue is trivial to implement.

What is the length of a request packet

Question: Different driver commands use different size request packets. When I use the DevHelp_AllocReqPacket call, what size request packet does it create.

Answer: All request packets are allocated from the same pool of memory and they are all (currently) 32 bytes long.

The data areas which are unused at the end of the request packet can be used by the driver to pass parameters into interrupt routines. The elegance of this technique is that it simplifies writing re-entrant drivers. This is because all request packet specific values can be stored in the request packet.

Do I need to Lock the memory for a request packet

Question: I am using the push & pull request packet helpers to pass request packets into an interrupt routine. Do I need to lock the request packets in memory to prevent them being swapped out.

Answer: No, all request packets are allocated from the same pool of memory and this pool will always be resident at the same physical location.

Distinguishing request packets from multiple file handles

Question: I am writing a driver that may be called multiple times by multiple threads and processes. How should the driver keep track of each opened file handle.

Answer: The Open, Close, Read & Write request packets all contain a field identifying the unique file handle. This field is called the System File Number or SFN.

union
{
   struct
   {                                 /*  READ, WRITE, WRITE_VERIFY */
        UCHAR      media;            /* media descriptor     */
        PHYSADDR   buffer;           /* transfer address     */
        USHORT     count;            /* bytes/sectors        */
        ULONG      startsector;      /* starting sector#     */
        USHORT     rwsfn;            /* system file number   */
   } ReadWrite;                      /* available: 6 bytes   */
   struct
   {                                 /* IOCTL */
        UCHAR      category;         /* category code        */
        UCHAR      function;         /* function code        */
        FARPOINTER parameters;       /* ¶meters          */
        FARPOINTER buffer;           /* &buffer              */
        USHORT     iosfn;            /* system file number   */
   } IOCtl;                          /* available: 7 bytes   */
   struct
   {                                 /* OPEN/CLOSE */
        USHORT     ocsfn;            /* system file number   */
   } OpenClose;                      /* available: 17 bytes   */
};

A collection of commonly asked questions about the IOCTL interface

Why does my driver receive a PRT_ACTIVEFONT IOCTL request

Question: It appears that the device driver always receives the PRT_ACTIVATEFONT IOCTL when the application does an OPEN, even when the device header says it doesn't support the OPEN command. Am I doing something wrong, or is this just a fact of life?

Answer: Fact of life, just return an unrecognised command error code 0x8103 & it will go away.

How should I open a block device

Question: I want to perform some low level IOCTL commands on the floppy drive. What file name should I use to open this type of driver?

Answer: "A:" et.seq

My comms software works on some machines but not others

Question: When communicating to a "black box" over COM1 on an IBM Mod 77, 90, 95 (tried them all !!), I can DosWrite a few bytes --> finito. DosRead-ing somethin with another thread doesn't work at all.

SAME Program, SAME black box, SAME Cables 'n'stuff on a IBM Thinkpad, IBM ValuePoint (tried them all again...) --> works SUPER fine.

Seems like some handshake problem...

I want the program to work on ANY machine, so what is different on IBM Mod 77, 90, 95 COM ?

Answer: Use the IOCTLs to set the handshaking.

 ASYNC_SETDCBINFO
 flags1 = 0x08;
 flags2 = 0x40;
 ASYNC_SETMODEMCTRL
 mask_on = 0x02;
 mask_off = 0xFF;

By default, DTR is initially set on MCA PW>driver opens but NOT on ISA driver opens...

How do OS/2 drivers work with DOS apps that are raising IOCTL requests

Question: I have a DOS app that uses IOCTL requests to communicate with a DOS driver.

What will happen if I try & run this application and driver under OS/2

- a DOS application opens a character device driver and gets a handle to it - then it fills in the following values to the registers

   ah    = 44h
   al    = 03h (send control string)
   bx    = handle
   cx    = number of bytes to transfer
   ds:dx = segment:offset of data buffer

and issues an Int 21h

- a DOS device driver will receive these values via the DOS kernel thru the device driver function 0Ch

If this DOS application now runs in a DOS box and instead of the DOS driver there is an OS/2 PDD, what kind of request packet will an OS/2 PDD receive?

Answer: A generic IOCTL function 10h. The only difference is that you can also use:

si:di = segment:offset of parameter buffer

The addresses passed thru to the driver will be virtual addresses (not physical).

Your parameter usage tho' is wrong/inappropriate for the GIOCTL command

ah = 44h       - correct
al = 03        - ?  I use 0c, I cannot remember why tho' I guess 3 is OK
bx = handle    - correct
ch = category
cl = function
ds:dx = segment:offset of data buffer

It is important to set up cx, as OS/2 pdds get lots of ioctls from lots of places (some pretty strange). As a convention category & function codes are used to identify the ioctl command.

In general you should find that ALL your old DOS apps which are using standard DOS ioctls will still ALL work. Internally what will be happening is those IOCTLs will be translated by their respective drivers.

If you are using software using non-standard (hardware specific) IOCTL's these cannot be translated, & you will need to provide a device driver that will recognise them.

Can a device driver report back length information in an IOCTL request

Question: DosDevIOCtl2() takes the address of the data and parameter buffer sizes, which implies that the values can be changed on return - does the PDD have a way to change these values,

Answer: There is nothing in my docs about dd's being able to change the length fields. My suspicion is that it is a design flaw in the API function.

In the past I have found dd data structure that 'could' be changed, only to find in subsequent releases that they could not. So my advise is to assume that the length fields are read only (but I may be wrong).

Using OS/2 IOCTLs from DOS & Windows apps

Question: I have a Win OS/2 app that can talk directly to the PDD by opening the device name.

eg, in "C"          dev = open( "DEVNAME" );
then          bytecount = read( dev, ... )
and           bytecount = write( dev, ... );

You can also do ioctl( dev, ... ) but I hav'nt found a way to control the category, and in general IOCTL's don't seem very satisfactory.

Answer:

DOS apps (& windows is still a Dos app) have access to OS/2 drivers (when running under OS/2). In order to invoke an IOCTL call to an OS/2 driver your DOS app should raise an int21h call. I use an assembler routine to do this:

       TITLE   devioctl.asm
       NAME    devioctl

DEVIOCTL_TEXT           SEGMENT
       ASSUME          CS: DEVIOCTL_TEXT

       PUBLIC          _devioctl
_devioctl       PROC FAR
       push    bp
       mov     bp,sp

;       OFFSETS OF INPUT PARAMETERS RELATIVE TO BP
;
;        buffer = 6     - Data buffer containing parameters
;        funct  = 10    - Function being performed
;        class  = 12    - Category of device
;        hand   = 14    - File handle of IOCTL device

       push    ds

       mov     cx,WORD PTR [bp+10]     ;funct
       mov     ax,WORD PTR [bp+12]     ;class
       mov     ch, al
       mov     bx,WORD PTR [bp+14]     ;hand
       mov     dx,WORD PTR [bp+6]      ;buffer lo address
       mov     ax,WORD PTR [bp+8]      ;buffer hi address
       mov     ds,ax
       mov     ax, 0440ch

       int     021h

       pop     ds
       mov     sp,bp
       pop     bp
       ret

_devioctl       ENDP

DEVIOCTL_TEXT        ENDS
END

I've extracted some info from Ralph Browns interrupt list that documents the int21 function 44 :

-----D-21440C-----------------------------
INT 21 - DOS 3.2+ - IOCTL - GENERIC CHARACTER DEVICE REQUEST
        AX = 440Ch
        BX = device handle
        CH = category code
           00h unknown (DOS 3.3+)
           01h COMn: (DOS 3.3+)
           03h CON (DOS 3.3+)
           05h LPTn:
           9Eh Media Access Control driver (STARLITE)
           00h-7Fh reserved for Microsoft
           80h-FFh reserved for OEM/user-defined
        CL = function
           00h MAC driver Bind (STARLITE)
           45h set iteration (retry) count
           4Ah select code page
           4Ch start code-page preparation
           4Dh end code-page preparation
           5Fh set display information (DOS 4+)
           65h get iteration (retry) count
           6Ah query selected code page
           6Bh query prepare list
           7Fh get display information (DOS 4+)
        DS:DX -> parameter block (see below)
        SI = parameter to pass to driver (European MS-DOS 4.0, OS/2 comp box)
        DI = parameter to pass to driver (European MS-DOS 4.0, OS/2 comp box)
Return: CF set on error
           AX = error code (see AH=59h)
        CF clear if successful
           DS:DX -> iteration count if CL=65h
           SI = returned value (European MS-DOS 4.0, OS/2 comp box)
           DI = returned value (European MS-DOS 4.0, OS/2 comp box)
Notes:  bit assignments for function code in CL:
           bit 7: set to ignore if unsupported, clear to return error
           bit 6: set if passed to driver, clear if intercepted by DOS
           bit 5: set if queries data from device, clear if sends command
           bits 4-0: subfunction
        DR-DOS 3.41 and 5.0 return error code 16h on CL=45h,65h if the device
         does not support a retry counter
SeeAlso: AX=440Dh,INT 2F/AX=0802h,INT 2F/AX=122Bh,INT 2F/AX=14FFh
SeeAlso: INT 2F/AX=1A01h

Defining and Using Semaphores

How to use a semaphore at interrupt time

Question: I have a question about using semaphores to make a physical device driver communicate with an application. The application process creates an event semaphore and passes the semaphore handle to the device driver in a generic IOCtl call.

The dd uses PostEventSem when it has something for the application, and the application, which is hanging on a DosWaitEventSem, takes over. Everything is working just fine.

BUT when I'm using PostEventSem at interrupt time OS/2 gets very angry. I don't like it, but it's ok 'cause PostEventSem cannot be used at interrupt time.

Back in message #218 you said it is possible go get an event sem to work at interrupt time. How is this done ?

Otherwise, is it possible to create a second thread inside the DD which could post the semaphores, and then control this thread via system semaphores at interrupt time. If yes - how ?

Answer: Yes it is possible to get an event sem to work at interrupt time and you do do it by creating a second thread.

  1. Create a context hook, using the AllocateCtxHook helper, you can do this at strategy time or init, but I'd suggest do it as part of the initialisation process. The AllocateCtxHook allows you to specify the address of your ctx hook handling function. When you do this you place the 16 bit offset address of your hook handler into the 32 bit eax register. It is important that the high order address bits are zeroised (I guess this is for future compatibility with the 32 bit).
  2. To deinstall a driver with an allocated ctx hook use the FreeCtxHook helper
  3. Place your event sema4 stuff in the ctx hook function. NB Hook functions MUST save & restore all registers on entry & exit.
  4. To pass control into your strategy time hook handler from an interrupt time context, simply use the ArmCtxHook helper. And exit the interrupt handler.

Effectively what is happening when you arm a hook is that you are creating a mega high priority (strategy time) thread. At the VERY NEXT scheduling opportunity the ctx hook will be called and simultaneously disarmed. When control passes into the hook function you can then do the things you wanted to do within the interrupt handler but with the limitations of a strategy time context.

The DS SS & CS registers will all be set as they would in strategy time for the driver.

What types of semaphore work with device drivers

Question: I am passing semaphore handles to my driver as parameters in IOCTL packets. What are the different types of semaphore that will work inside a device driver.

Answer: System semaphores (named semaphores) can be used at strategy time AND interrupt time. RAM Semaphores can only be used at strategy time. Event semaphores behave like RAM semaphores. And finally, you cannot use Mutex semaphores in drivers.


How should a device driver create a semaphore

Question: I would like to use a semaphore in my driver to communicate with an application. I have looked in the device driver references but I cannot find out how to do this.

Answer: Device Drivers cannot create semaphores. Instead you should create the semaphores inside an app. The app should then pass the semaphore handle into the device driver for registration.

What does semhandle do ?

Question: I have examined what happens when you use the semhandle helper. It does not seem to do anything? Do I need to call it.

Answer: Yes ! Semhandle has two important functions:

  1. Registering/deregistering access. Each semaphore has an in use count field. This is used to indicate how many, processes & threads are using this semaphore. When the in use count drops to zero. Then the semaphore will be deleted.
  2. Converting the handle to system format. Named semaphore handle need to be converted to a different address format. RAM semaphores do not.

How do I create a system semaphore

Question: I would like to create a system semaphore to pass into a device driver. I have studied the manuals but I cannot find anything on system semaphores. How should I create a system semaphore.

Answer: System (named) semaphores are 16 bit objects. They are created by the 16 bit Dos16CreateSem API. Once created a system semaphore can be passed into a device driver for registration.

Restrictions on semaphore usage

Question: Is there any restrictions to use semaphore between 32 bit application and OS2 driver?

Answer: Yes, 32 bit OS/2 APIs refers to MUTEX & EVENT sema4's, EVENT sema4s can be used within drivers BUT you must use the special event sema4 helpers. Of course event sema4's are not supported in versions of OS/2 before 2.0

Mutex seamphores are not supported by device drivers.

16 bit named and RAM sema4's can be used within drivers

Alternatively you could do ALL the work yourself with the BLOCK & RUN helpers

BLOCK stops
RUN   unstops.

You should understand that all the higher level synchronisation services, Timers, Sleeps, Semaphores are implemented using the lowly Block & Run primitives.

How the stack works

Who owns the stack

Question: Who owns the stack of the driver at DosDevIOCTL time?

Answer: The driver does. The system supports 4 stacks, one for each privilege level (ring)

RING                      STACKSIZE
ring3 application       - as requested by app
ring2 iopl              - 512 bytes
ring1 not used          -
ring0 drivers/kernel    - about 1800Hex bytes

When control passes into the driver it has its own private stack. The only thing that can grab the stack is an interrupt, so you must always be wary of interrupts eating all of your ring 0 stack. There is a helper service for specifing/limiting max number of ints coming in on your stack.

Stack based RAM semaphores seem to cause problems

Question: When I declared a structure local to (int main) which contained a semaphore handle and passed it via reference to secondary function which opened/created the semaphore and passed it to the driver I received an error 87 invalid parameter from the DosDevIOCTl. When I declared the struct as static ie not stack based the code worked correctly. The code was sent to the compiler writers who could find nothing wrong with the 'c' source and hinted that it was either in the driver or os2.

Answer: I guess you were using a RAM sema4. I did not know of the above problem. DosDevioctls are definitely allowed to pass paramaters on the stack. The only thing that could have caused the error result is your driver. So to investigate further follow the ioctl into the driver and find out exactly which helper is failing on you.

I do think your suggestion that a RAM sem should not be on the stack is probably correct. But there are only two ways to find out rtfm and try it out with kdb, I dont remember reading in the manual so ...

Creating a larger stack

Question: Can you tell me a way to get a bigger stack during initialization time of my selfwritten Physical Device Driver (PDD) ? I can't find any information in the books.

Answer: Why do you need a larger stack, creating a larger stack is likely to be technical nightmare, I'd suggest that there may be an easier way to crack this particular nut.

The only way would be to create your own stack segment in the driver at run-time. You would have to be very careful to ensure that the original stack is preserved & restored on entry & exit to your driver and you can expect some problems with some of the devhelp calls.

Similiarly you would need to disable all interrupts as if one occurred on your "fabricated" stack, it would almost certainly crash the system.

Basically the bottom line is you cannot do it - but if you try it, it "might" work.

My advise is "Do not waste your time, trying to get it to work"

Using DMA

Locking DMA addresses

Question: If I allocate a buffer in the driver's one and only data segment, then I can be sure the buffer is always in memory. To make sure it doesn't move during the DMA initialisation and execution, I can use DevHlp to lock it. Question is: I understand that the DMA controller can only address the bottom 1 Meg of memory,so how do I make sure my buffer is locked down there? I believe there is also a restriction that limits DMA to 64k boundaries, so I must make sure the lock keeps the buffer in a single 64k segment.

Answer: DMA allows you to define a memory transfer operation, the addresses supplied MUST be physical addresses. If it is physical that also means it cannot be moved, locked down, etc. It is physically wired up to a physical RAM chip.

The address limitation of DMA are limited to the addressing capabilities of the RAM you are talking to. The DMA controller chip can address up to 16MB.

One memory transfer can move up to 64KB of ram, some of the DMA channels (Channels 5, 6, & 7) can transfer up to 64 K WORDS, The only alignment (128 KB) issue I'm aware of is that word transfers have to align on word boundaries.

Aligning DMA buffers on 64K boundaries

Question: I believe there is also a restriction that limits DMA to 64k boundaries, so I must make sure the lock keeps the buffer in a single 64k segment - How

Answer: There is no documented way to do this. A recommended solution is to allocate a buffer twice the size that you actually need and then use the part of the buffer than does not cross a 64K boundary.

Alternatively split it into 2 dma requests. (One of) the recommended way of doing this is:

  1. Stuff your initial request packet into a queue, with the length field set to only copy WITHIN the first 64K
  2. create a new request packet (AllocReqPacket) and set it up for the tail end of the transfer. Stuff that into the queue.
  3. Repeat 2) as many times as is necessary
  4. Start first transfer
  5. Interrupt handler receives completion interrupt
  6. Start next transfer from within isr using RP queue.

The only thing to worry about is running out of request packets. I've done tests and after allocating 87 or so the system keels over. So be very careful to:

  • Not alloc too many RP's
  • To free them up when they've done their job.

Accessing RAM for DMA

Question: I am developing a driver for an A/D card, which does DMA transfers. The problem I have is with the read request where the DMA transfer is to be performed. As you may know, the physical buffer for the DMA transfer must:-

  • Reside below address 0x1000000 (24 bit address space).
  • As i understand it, be entirely within a single physical (64K) page i.e the buffer must not cross a 0xXX0000 boundary. This is a consequence of the 8237A only having a 16 bit address register, the DMA page select being a separate 8 bit register.

The question is whether the physical address parameter passed in the read request packet satisfies these conditions, and if not, what is the recommended way to obtain a buffer that does.

Answer: I am not sure, the decisions being made as to what does & does not go above the 16MB line seem to be a little arbitrary. I'd recommend using the compatibility strip to indicate to the kernel that your driver does not support addressing above 16MB (its bit 1 set to zero). That way you can use the read/write physical buffer directly, as opposed to creating a working < 16MB buffer, to copy your read/write data into prior to the dma.

Dma channels 5->7 support 16 bit data transfers, so theoretically you can set up a 128KB transfer, but I'd suggest it'll be a lot easier just sticking to 64KB.

Sharing DMA completion interrupt

Question: The other question I have concerns the DMA completion interrupt. How is this shared between devices using the various DMA channels. This is particularly of concern because reading the 8237A status word clears the terminal count status bits (Intel, Microsystem Components Handbook Vol 1). If one cannot access the interrupt then one has to monitor the terminal count using a timer, which is somewhat inelegant.

Answer: If you mask out the dma controller so only your device has access to it. ie as per Mastrianni, when the dma interrupt goes off, you will know it can only be for your channel.

DMA and SCSI devices

Question: Does anyone have any advice to offer on the recommended method for DMA transfer from a SCSI device? I would particularly like advice of buffer allocation and locking using virtual memory and 32 bit support functions. Does anyone have a library of these Device Driver support services? Any advice is gratefully recieved.

Answer: The easy way to write a SCSI driver is to use the Advanced Scsi Programming Interface (ASPI driver). (or maybe its the Adaptec Scsi ...) anyway it is a driver produced by adaptec that you talk to. It does all the hard SCSI work for you.

There are two ways you can use it:

APP <------------> ASPI driver

This has limitations as it does not support transfer of data, but it can be used to send simple commands like eject page, query ...

The real way to use ASPI is

APP <------------> Your driver <------IDC link-------> ASPI driver

It is actually very simple to do, Adaptec have example drivers on their BBS and specs for the ASPI interface.

Defining and Using memory

Accessing memory on adapters

Question: I have a serial interface, with dual ported RAM, which needs to be mapped into the LDTs.

When I map this interface under 1MB, using the DevHlp routines PhysToUVirt and UnLockSeg, everything is O.K. When I try to map it at 15MB (in a computer which has only 8MB) it fails. Do I have to do something other then using the above DevHlp routines in order to map the interface?

Answer: The phys2uvirt is failing because the ram is outside the system memory area.

There are two quick ways of solving this problem, if the memory region is > 64KB and you will NOT be using OS/2 1.X, you should consider using the VMAlloc helper.

Alternatively you could use the GDT helpers to create a ring 0 selector:

AllocGDTSelector
Phys2GDTSelector

Does a device driver have its own memory space

Question: I do not understand where a device driver gets its memory from and how it should share it with other processes, especially at interrupt time.

Answer: At initialisation time the driver will tell the operating syustem how much memory it requires for its data and code. The kernel will then reserve that amount of storage for the driver and the drivers image will then be copied into this space.

In the strategy routine, the driver is running in the memory context of the application that made the device driver call, so it shares the same LDT, which gives it access to the applications memory space. It can access the memory as 16 bit memory via the LDT, or it can access the memory space using a 32 bit flat selector.

The CS,SS and DS of the driver are all GDT selectors, so they are accessible to the driver both at kernal AND interrupt time. The driver loads an application LDT selector into its ES (for example) in the strategy routine, so it can access the application memory.

So, in answer to your questions, the driver does NOT have a different memory space. Instead the driver has access to the calling applications address space, as well as it's own.

At interrupt time, the memory space will be effectively random, whatever was running when the interrupt happened.

Since you loose addressability of the process space at interrupt time, there needs to be a way for passing data into the interrupt handlers.

There are two ways to do this:

  1. Place the data into the device drivers data segment. This will be accessible at interrupt time
  2. Place the data into a request packet. Enqueued request packets are accessible at interrupt time, so you can use request packets as storage buckets for small amounts of data.

The beauty of using the request packets to hold data is that it makes writing re-entrant code very simple.

Can a driver access an applications variables at interrupt time

Question: I know that at interrupt time a driver does not have access to a processes address space. So how should a driver pass data captured at interrupt time into a processes address space. Should it use temporary buffers?

Answer: A driver does not have access to a processes data areas at interrupt time and using a temporary buffer would be slow and make writing (re-entrant) drivers very difficult.

To solve the problem we translate the addreses of application variables into a format that is accessible at interrupt & strategy time. This format is the physical address.

A physical address describes where an object is, it is physically fixed and is valid in all contexts. The only problem is the situation where address space may be relocated by the operating system. To prevent this from happening you should first lock the memory in place.

Which LDT is used at interrupt time

Question: Which LDT is used at interrupt-time? (One at random?) I hope you understand my question! The answer must be simple but I can't figure it out!

Answer: It is not simple at all, elegant yes, simple no. The answer is "one at random". The interrupt handler will be using the stack of the currently running process, which could be anything.

Should I Lock memory before VerifyAccess or after?

Question: I am puzzled why the documentation says lock before doing a verifyaccess, shouldn't you check an address is valid before you lock it?

Answer: Common sense would suggest checking before using. In fact you can verify first if you like, providing you lock immediately afterwards. The difference (& the reason you lock first) can be seen when there is a problem.

If you try and lock a memory region you do not have access to, then the lock will fail and the device driver should flag the problem to the calling application.

If you try and verifyaccess to a memory region you do not have access to, then the calling process will Trap D.

Both are correct, locking first is more correct.

Preventing memory conflicts

Question: I need to interface a frame grabber to my software and I am unsure about the PC's / OS/2's allocation scheme for memory between 640k and 1Mb. I have already written a simple driver for a different frame grabber that sits at 15Mb and occupies 512k of memory address space.

The new frame grabber I want to use occupies a 64k page between 640k and 1Mb, Can you tell me how to use a suitable 64k window, and how to ensure that I do not have any address conflicts? (I want the physical memory to be available via a selector/handle in my OS/2 program).

Answer: PhysToGDTSelector is probably the easiest way to get a application usable selector. You would use a device driver and an IOCTL command to pass the selector value to the app.

There is no way of guaranteeing against address conflicts. Most people use jumpers to select the physical address decode, or at the very least a socketed PAL. A nice way I saw to do it last week was to use an I/O address to initialise the memory map address, but then you have to worry about the I/O address decode. Of course with EISA PCI & MCA bus interface chips the address decode is configured in at installation time, so its not a problem, but for ISA bus machines it is always a pain.

There is an architectural feature new to Warp called "resource management". This allows each and every device driver to "inform" the operating system of its resource requirements, I/O, IRQ, DMA & RAM. Using this mechanism you can:

1) Query to see if a resource is available 2) Assign a resource to your driver

Of course this presupposes that a "resource UNfriendly" driver has not tried to grab the resource before your driver.

Using addresses supplied with READ/WRITE command

Question: I have problems in using the physical address that is in the request packet when I am in the strategy routines for read and write. I try to use these addresses in repetitive string move functions(rep insb/outsb), loading them into es:di and ds:si respectively. My problem is that either there is a general protection fault TRAP or there is a page fault TRAP. My questions are therefore:

Is it possible to use these physical addresses (how do i use them in the strategy routines (r/w)) or do I have to convert the addresses with the device help physToVirt ?

Answer: It is important for anybody who is writing a device driver for the first time to be aware of the fact that all the helpers are very very simple - except those that deal with memory management. The Memory management helpers are virtually impossible for mere mortals to understand, but if you can understand WHY they are difficult to use then that is 90% of the problem solved.

PhysToVirt:

Your application wants to read a file (a device driver) so it raises an API identifying the file the number of bytes to read and a buffer into which to read the data.

The kernel receives this request. The kernel is a very clever creature and so checks that the apps buffer address is a valid one. If it is invalid then the kernel will return control immediately to the app with an error result.

Now this valid buffer address is a pointing into the applications address space, the kernel could pass this address straight thru to the driver at this point in which case I would not be writing this now and your driver would already be working - but it does not !!! Instead it converts the apps virtual address into a physical address and locks the memory so that it does not get swapped out to disk or relocated. Why does it do this, well ...

Your drivers strategy routine is called at task time, in other words it is running in the same context as the application so it could use the address space of the app without any problem, but what if the driver could not perform the read operation immediately but instead had to wait for some hardware to complete some task, in which case you would probably set up an interrupt service routine to be called when the hardware was done and get the read buffer updated in the ISR. In which case the ISR would be invoked at interrupt time AND WOULD NOT BE IN THE SAME CONTEXT AS THE APPLICATION and thus the applications address space would no longer be accessible.

To summarise you can view the physical address as being one that is usable by any task or process at any time. It is always there address 2 million is always address 2 million (pretend paging does not exist) and so the physical address is the esperanto of addressing.

Thats the why, now the fun starts.

Converting from Phys to Virt is very easy you just use the PhysToVirt helper (often referred to as P2V), well it is not quite that simple. The following describes what USED to happen under OS/2 1.x:

TASK TIME

1) A protected mode app issues a read

2) The driver receives a physical address (address 2 million) which it saves somewhere (or just queues the request packet)

3) The driver sets up an ISR and blocks waiting for the ISR to do its thing.

Meanwhile some dumb user comes along and hits the ALT ESC keys putting OS/2 into the Dos box

INTERRUPT TIME

4) The interrupt occurs control is passed into the ISR (in real mode)

5) The ISR gets the address and invokes the P2V, but hang on the Physical address was address 2 million - how can you access location 2 million when in dos box - the answer is you cannot. AND THIS IS THE PROBLEM. What happens next ...

The P2V helper should really be called the PhysToUsable helper, in protected mode it generates a protected mode virtual address, in Dos box it also generates a usable address - but the way it does it is OEM dependent.

In dos box a P2V to address below 1 Meg ALWAYS generate segment addresses

In dos box a P2V to address above 1 Meg will:

1) On slow 286s use an undocumented instruction loadall to trick the CPU into addressing memory above 1 Meg (this is how extended memory drivers access high memory under DOS on 286s). The downside of this instruction is that you cannot save and restore the registers holding the address, this means interrupts MUST be disabled and a whole host of other restrictions apply.

2) On fast 286s PS/2s and all processors above 286, the helper simply changes into protected mode. The implication of this is that all the segment addresses that the ISR is using become invalid (excepting DS, CS and the address of the request packet).

Fortunately, with all versions of OS/2 after 2.0 it is much simpler. The driver will never be called in real mode, it will always be protected mode. This means we can ignore DOS box issues.

Ok so get back to your question:

Is it possible to use these physical addresses (how do i use them in the strategy routines (r/w)) or do I have to convert the addresses with the device help physToVirt?

In the strat routine there is no problem

a) PhysToVirt

b) Use the address

c) UnPhysToVirt

Think of the UnP2V as the cleanup routine for P2V. It is good practice to always call it.

Problem allocating 64K selectors

Question: At initialisation time of my PDD, I use AllocGDTSelector to receive a selector: After, I have some problems with the DevHlp PHYS_TO_GDT_SEL fonction: I try to map a given selector to a physical address:

       mov eax, D0000
       mov ecx, size
       mov si, selector
       mov dh, 1  (data writeable, Ring 3)
       mov dl, DEV_PHYS_TO_GDT_SEL
       call  DevHlp

When size = 32K: OK ! My card can access the address D0000:

       dw selector:0  = dw %%D0000

When size = 0 ( 64K ) : all seems good ('C' is Clear, AX = 0) but

       dw selector:0 != dw %%D0000     !!!!

If I try to convert with VIRT TO PHYS the address given by the selector, it's wrong! Do you see something wrong in my code?

Answer: No nothing wrong in your code, and yes your quite right it is not mapping properly. I tried a few different values myself and found that it works correctly for all values except 0. There is an old maxim that programs should do nothing well, and unfortunately the kernel in this case is doing nothing badly.

I tried using ffff and that gives a 64K-1 selector correctly, I tried a 10000 32 bit selector and that also gave a good ffff selctor, so if you are happy with either a 64K (or more) 32 bit segment or alternatively a 16 bit segment that is fffe (or less) bytes long then thats the way to go.

What appears to be happening is that the page mapping for the 64K selector is mapping thru to physical addresses incorrectly. It seems to be mapping thru to the physical address %%fffff000. Which is most odd!

In the end I managed to get a 64K 16 bit selector & this is how I did it:

AllocGDTSelector    as before

PhysToGDTSel        Length  10000 (not 0, 0 does the same as for a 16 bit)
                    Address d0000
                    Type    80    (ring 3, 32 bit)

This gave a 32 bit selector mapping onto ffff bytes To turn it into a 16 bit selector do the same PhysToGDTSel again, but this time ask for a zero length segment and a 16 bit selector!

PhysToGDTSel        Length  0
                    Address d0000
                    Type    1    (ring 3, 16 bit)

It would appear that the first PhysToGDTSel sets up the mapping mechanism correctly so that when you repeat but this time ask for a 16 bit segment it gives you the same page map as the one acquired for the 32 bit segment.

BE WARNED THIS CODE WORKS IN THE SAME WAY A TV DOES WHEN YOU KICK IT ! So I cannot guarantee this will work in the same way in the future. But it does work now.

What is the TLB

Question: I read in a book that when OS/2 changes context, the page manager switches the per-process page tables and flushes the TLB; What is the TLB?

Answer: The TLB is the Transaction Lookaside Buffer, you do not need to understand what it is, what it does or why it does it, so you can skip to the next "chapter" if you like. Still here goes - you know what a linear address is and what a physical address is, the question is how does the CPU translate from one to the other.

The information mapping the physical & linear addreses is held in page tables (memory) so there should be a large overhead in performing the transformation - and indeed there is a large overhead. The TLB contains the last n (128 I think) page table addresses that have been accessed and the physical addresses they map onto. So the TLB "caches" linear to physical address transforms.

Self modifying code

Question: My device driver needs to perform a bitblt operation. To get the best performance I need to be able to create the code dynamically (self modifying code segment). How should I do this.

Answer: You cannot create a writeable code segment. The Intel chip set does not support such a data structure. What you must do is create a data segment that maps onto EXACTLY the same address region as your code segment. Once you have done this, you can modify the code segments contents, by writing to the data segment.

I have enclosed the driver code I use to perform this operation. There is a little known API that you could use to achieve the same effect at application level : DosCSAlias ().

// Create ring 3 modifiable data/code segment of any ring
// This function can be used for creating a self modified code area. The
// application for it is when copying chunks of memory from one place to
// another. ie video capture.  The 3 reasons for wanting to do it this way
// are performance, performance & performance.
// Code that is generated at run-time to support bit blatting type functions
// can be optimised optimally.

UINT rw_seg (ULONG size, SEL *seg1, SEL *seg2, USHORT flag)
{
 ULONG phys;
 SEL sa[2];

   if (AllocGDTSelector (2, (char far *)sa))
            return FALSE;

   if (AllocPhys (size, 0, (PPHYSADDR)&phys))
       return FALSE;

                               // ring 3 read/write data segment
   if (PhysToGDTSel (size, (ULONG)phys, sa[0], 1))
       return FALSE;

                              // The interesting segment
   if (PhysToGDTSel (size, (ULONG)phys, sa[1], flag))
       return FALSE;

   *seg1 = sa[0];
   *seg2 = sa[1];

   return TRUE;
}

32 Bit issues compiling/linking/running

Incorporating 386 code

Question: OS/2 Device Drivers are 16 bit. But I want to use some 386 32 bit instructions, how do I do this?

The driver is currently being assembled using Borland's Turbo Assembler Version 3.0 (Dos Based) and the driver is linked with the OS2 linker link. Should I be using the link386 version of the linker because the code contains .386P sections. I have tried this and the driver fails to load correctly, if this is required then please could you advise on any suitable switches or options (.def files) required ?

Answer: There are four different solutions to your question:

1.- Force the operations to be 32 bit using prefix bytes:

           db      66h
           pop     cx          ; = pop ecx

There are actually two prefix byte values you use to do 32 bit. 066h is the force 32 bit operand size, 067h is the force 32 bit address size. But you really should not need to do it this way.

           db      66h
           mov     ax, [si]    ; = mov eax, [si]
           db      67h
           mov     ax, [esi]   ; = mov ax, [esi]
           dw      6667h
           mov     ax, [esi]   ; = mov eax, [esi]

2.- Using processor overrides. These may or may not be supported by the assembler yopu are using.

           .386P
           mov    eax,cr0
           push   eax
           .286P

3.- Change the segment type. When you declare the egment declare the entire segment to be 386.

           .386
           public  Procedure_name
           assume  CS: _TEXT
   _TEXT   segment word use16 public 'CODE'
   Procedure_name proc near

The trick to get this to work is to declare the usage type of the segment. In the example above:

   _TEXT   segment word use16 public 'CODE'
                        -----

It is the use16 directive that is the key. If you do not supply a usage statement then by default a .386p segment will be generated that is 32 bits.

4.- Define a 32 bit segment in your driver. There are two rules:

  1. The 32 bit segment must be the last one in the linkage/segment and therefore load order. So all the 16 bit segments come first.
  2. The 32 bit segment must have it's IOPL bit set (using the definition linkage file). This is obviously unnecesary as this is a device driver segment after all and therefore should not need IOPL, it seems to be used as a flag to the loader - to inform it that this is a 32 bit segment.

Practicalities mean defining the segment ordering and IOPL privilege in the .def file used to link the driver.

The information for the last technique was supplied by Dale Whitfield.

I want to write a 32 bit device driver

Question: I have been searching on the EMEA DAP for an example 32-bit device driver, something to give me a quick start in writing device drivers in 32-bit mode. I have searched the DEV file areas, but came up with nothing!

Are most of the device drivers still 16-Bit? Do you have an example of a (void) 32-bit device driver?

Answer: The current DD model is 16 bit only, there are some exceptions, namely virtual device drivers & display device drivers which both support the 32 bit memory model.

It is possible to incorporate 32 bit code inside a 16 bit driver, the only restriction is that it will not be in a 32 bit code segment. 32 bit data segments are fairly easy to use but you will need to "allocate" the 32 bit data space using one of the VM helpers.

This is all set to change with OS/2 for PowerPC which I will support a 32 bit model, the information I have, suggests that the IBMers will be keen to migrate the OS/2 for PowerPC DD architecture back down into OS/2 (for Intel).

Anyway for the time being you will need to stick to boring old 16:16 if this is unacceptable please let me know & we can try & figure out a way round your problem.

What are the different types of memory address

Question: I get very confused by the different types of addressing used under OS/2.

Answer: There are 4 types of address:

  DOS addresses  - segment address
  PHYSICAL addresses - address 5 million maps onto ram chip address 5 million
  LINEAR address - goes thru page tables to generate physical addresses
       under 1.X linear addreses are always the same as physical addresses
       (and this is the main reason for the confusion) under 2.0 they are
       different.
  VIRTUAL addresses - addresses constructed uses a descriptor table

To access the different address types using kdb

  DOS addresses       &seg:offset
  PHYSICAL addresses  %%physaddr
  LINEAR address      %linaddr
  VIRTUAL addresses   #sel:offset

Using VMAlloc - correctly

Question: We have an application which uses an X.25 adapter which appears as a 1Meg (or 4Meg) window in Ram. The application uses a physical device to obtain a linear address to access the memory and to lock the linear memory. The combination of driver and application works with no problems under 2.1 under heavy loads (it stays up for periods of over 10 days for example). Under 3.0 however it fails after a few hours when the paging load is increased and page swapping occurs heavily. We can bring it down more quickly as we increase the load.

The fault manifests itself by a 4k page of a large linear array (which should contain linear pointers or nulls) becoming corrupt. The application updates this array infrequently and to eliminate app problems we set the access bits to read only normally and only turn on write access when it is being updated. After the update and as we access the entries we validate all the pointers to check for corruptions. The application stops when it detects an invalid pointer and the corrupt region is normally <=4k approx.

It looks as if under some circumstances the physical memory reserved for the adapter has been re-allocated by VMM for use by application pages even though we have mapped the area and locked it down. We obtain access to the memory and lock it down as described below.

We first use DevHelp_VMAlloc to obtain a linear address for the physical address for the adapter. We use a flags 0x30 i.e. get a linear address useable in the process address range (the application directly accesses the adapter memory). If I attempt to map it with the request that it is to be fixed in memory the request is refused (ERROR_INVALID_PARAMETER).

We then issue a VMLock for the virtual address (and the memory size) asking for a long term lock (ActionFlags = 0x10). We dont set bit 3 since the processor (i.e. the application) writes to the memory normally. The memory area for the adapter is either 1Meg (or 4Meg) but we dont ask for physically contiguous (it is contiguous due to the mapping).

Are there any known problems in this area or is there anything else we should be doing to prevent the problem.

Answer: The ERROR_INVALID_PARAMETER result from VMAlloc is because the VMAlloc (EDI) parameter is actually a pointer to an address where you should place the physical address. In other words you have missed out a level of indirection !

I'd suggest different flags on the VMAlloc: 0x410 is what I use to get linear addressability to video memory. It was originally created (bit 10) for this very reason. This is the same for any memory on an adapter mapped into the CPU space and no part of system RAM.

The locking is also unnecessary for the following reason. When the memory manager sees this VMAlloc with the flags to map to non-system physical memory it creates a so-called 'uvirt' mapping. This isn't swappable or pageable or any of the other things that system RAM is subject to, its a direct mapping of physical to linear.

BTW, the screen-group switch stuff is probably unnecessary, I've never used it. Your driver ultimately is managing the memory.

The flags 0x410 will create a mapping in the global arena, to get a mapping in a process (private) address space set bit 5, ie. 0x430.

This stuff is nasty to get your head around sometimes, and poorly and misleadingly documented anyway. I think, mainly because not that many people know how it works and those that do don't work there anymore or are too busy to pass it on :-) I was luck enough to get first-hand tuition...

The credit for this VMAlloc info should go to Dale Whitfield & Philip Mulrane. Thanks.

Using VMAlloc - incorrectly

Question: In an earlier message you suggested setting the VMAlloc flags to 0x410 or 0x430. I'd agree completely with your comments re the lower 8 bits being set to 0x10, however I am puzzled by the bit 10 setting. I've included the very latest docs on the VMAlloc helper and you will see that nowhere is bit 10 described. Indeed the earlier refs state that all other bits MUST be set to zeros.

Answer: You don't believe what they put in the manuals do you? :-)

Well the developer for this piece of code was one Mike Kogan who was one of the few to understand (and therefore code) the virtual memory management (VMM) stuff. He left, wrote the book (with Deitel), did his own thing and I think Glenn Brew is the only person left who has a good grip on it now. But he's alone, more or less, in a hectic environment, so things like this become less important.

Digression aside, bit 10 was first put in to allow access to the video frame buffer, we're talking about pre 2.0, post 1.3, so this is CGA/EGA/VGA and the 0xA0000 region. But the code is general enough to allow a mapping to be created to physical memory in much the same way as the 'phys to virt' and its variants. Those used some funky CPU LOADALL mechanisms and bypassed the virtual memory stuff anyway (hence the old restrictions about not loading es, ds after the phystovirt until you were done, 286 stuff). VMAlloc with bit 10 set achieves the same result, but in 32-bit land. From what I understand, it doesn't use page-directory and page-table entries in the same way as virtual memory does, but the mapping is done at a lower more direct level. You can see the reasons for its existence, the VMM doesn't worry about it, doesn't care, just leaves it alone, its not system RAM after all.

I have seen the bit documented, but can't recall where, may have been the code itself, I had to resort to that once or twice! You're correct in the quoted PDD ref, its not mentioned and it does state to set all bits to zero. But then again, what are 'reserved' bits for?...:-) This is about as much as I can recall off the top of my head, but would refer you to a piece of code close to my heart - that for SCREENDD. Check out ddkx86\src\dev\screendd\svgarout.asm for an implementation and use of the above flags.

I need to do a quick test to jog my memory, but I think that one of the mappings creates a linear address in the shared arena, not global. If not there is a combination which does it. This has advantages in that you can control which process 'sees' the address by selectively 'attaching' the mapping to the process via VMGlobalToProcess. Again, the PDD ref doesn't document the bit used 0x10 to do the 'attach' - don't shoot me, I'm just the messenger here.....just check out my code in svgarout.asm, it works!

Sorry I can't enlighten you anymore as to the VMM workings in terms of how page dirs/tables are setup when using bit 10, if at all (I suspect more towards the latter). I didn't need to know and didn't have the time, at the time.

The credit for this VMAlloc info should go to Dale Whitfield.

Memory above 16MB

Question: I want to use memory above 16MB. I tried h/w forum and searched on '16m' in message title. There's a lot - and it seems that no-one is really SURE what OS/2 actually does with memory above 16M. It seems to depend on which device drivers are loaded and whether they say they can use mem above 16M or not. If NOT then the high memory becomes part of the swap file.

OS20MEMU is smart enough to do this - on the machine I'm evaluating it shows a swap file size of 34Mb but in fact only 18Mb of it is on the C drive - the rest is in memory.

Answer: I have just read an article in the MCA developers newsletters which stated that memory is ignored AFTER a memory hole, so if you have:

16MB populated
16KB unpopulated
16MB populated

It will only support the first contiguous bank of memory , to quote directly current operating systems will only use memory above 16MB if memory is contiguous across the 16MB boundary. Due to DMA address limitations, most operating systems only use memory above 16MB as VDISK or other type of temporary storage.

Using data areas larger than 64KB

Question: A ring 3 32-bit app has a buffer "UCHAR buffer[4*1024*1024];" and it sends the 32-bit near address "buffer" to a PDD in a DosIOCtl. The PDD must convert this 32-bit linear address to be able to use it (even for task time use). The helpers available are LinToGDTSelector (max size 64KB!) and LinToPageList (max size 64KB!). VMLock which can lock a linear address generates an array of PageList_s structs. (The PageListToGDTSelector has a max size of 64KB!) I don't understand how this buffer is going to be handled by the PDD. It seems that the PDD can't use buffers allocated by an app which are larger than 64KB.

Answer: But it can. Your 32 bit address is a GDT_selector:32bit_offset address where the GDT selector is fixed giving you the "flat" 32 bit offset (only) address. Because you are passing this address into the driver using an IOCTL packet, the address is being copied across as an item of data. Your driver could access the address directly at task time using normal 32 bit addressing operands.

If you wanted to access the data area at interrupt time you would need to first map the address to a physical location using the VirtToPhys helper and save the result. In the interrupt you would/could use the VMAlloc helper to gain accessibility to the memory region.

Copying using 32 bit memory transfers

Question: I need to transfer a block of data. The processor is a 386, so (if possible) I'd like to transfer the data using 32 bit instructions. How can I do this in a 16 bit device driver.

Answer:It is actually very easy.

.386p
       shr     ecx, 2          ; but it is a dword copy so divide by 4
       and     di, 0fffch      ; mask out low order 2 bits
       and     si, 0fffch
       rep     movs dword ptr es:[edi], dword ptr ds:[esi]
.286p

The above code assumes:

  • The ecx register contains the number of bytes to transfer.
  • es:edi register pair are initialised to the 32 bit linear destination address.
  • ds:esi register pair are initialised to the 32 bit linear source address.
  • The memory being copied is double word aligned.

The first instruction is dividing the contents of ecx by 4. ecx is used to indicate the number of memory transfers, since the transfers are 4 bytes at a time and ecx is initialised to the number of bytes to transfer, ecx divided by 4, is the number of 4 byte transfers.

The next instructions are ensuring that the source and destination offsets are correctly aligned.

Finally the transfer.

The code sequence is delimited by .386 ... .286p. These allow you to use 386 32 bit mnemonics.

I/O access in 32 bit code - a problem

Question: I am accessing an I/O port. I have noticed some strange behaviour when I put my I/O instructions in an assembler module supporting 386 instructions.

i.e. If I use a .386p directive (instead of .286p)

Answer: In a .386 segment, out dx, ax is equivalent to out edx, eax. Although you might disagree, this is actually good news as it allows you to use 4 byte port access. If you really only want 2 byte or 1 byte port access then you could code your I/O like this:

   .286p
       out dx, ax
   .386p

How time works

Timer resolution set to 32Hz or 128Hz

Question: Setting various int 3 inside clock$ I discovered that OS/2 sometimes changes from 32hz->128hz and back. Why? (This happens EVERY time i start my editor: Brief!!)

Answer: The timer resolution is defined in the Global Infoseg. This is always set to 32Hz. This may change with futureversions.

Now to answer your qustion; the OS/2 kernel gives a special boost to blocked time critical threads. Guaranteeing that on unblock they will be rescheduled after only 7 or 8 milliseconds. This is why you are suddenly seeing the timer speed up by a factor of 4.

What you are observing, is Brief, setting a thread to time critical and then blocking on it. This would be sensible behaviour for any app that wanted to provide a highly responsive user interface.

Using timers at initialisation time and IDC links

Question: If I set a timer at initialisation time, what happens if the timer goes off and I use an IDC link?

Answer: It will crash & burn.

Instead, you should delay ATTACHing to other drivers until all the drivers have been installed. You do this by adding support for the INIT_COMPLETE command. Once your driver receives the INIT_COMPLETE, you know that all drivers will be installed. You should now establish the IDC link using the AttachDD helper service. Once this is done you can start using the IDC interface.

Reading the time at interrupt time

Question: I am using the GetDOSVar helper to supply the address of the Global Info Seg. I am then reading the current time field to find out the system time. The problem I have is that GetDOSVar is not available in interrupts. So how should I query the time in an interrupt.

Answer: GetDOSVar can be used to get the address of the Global Info Seg. That address never changes. So you only need to use GetDOSVar once, store the address somewhere in the data segment. From then onwards, you never need to call GetDOSVar again.

It is also faster!

Changing the tick count

Question: I want to be able to dynamically change the tick count, using the DevHelp_TickCount helper service. Should I reset the previous timer handler or can I simply forget it?

Answer: The documentation (PDD Reference) states that "Multiple tickcount requests can be issued for a given timer handler, but only the last tickcount setting will be in effect". The documentation is wrong.

If the previous timer was created using a DevHelp_TickCount, then the helper service will figure out that the new tickcount value is for that earlier registration, in which case there is no problem, and the tickcount value will be changed as you'd expect.

If the previous timer was created using a DevHelp_StartTimer, then a call to DevHelp_TickCount will create a second timer handler, leaving the first one still running. This has been a feature of OS/2 since version 1.0. I am unaware of any plans to change it.

So if you plan to change tickcounts, do not use the DevHelp_StartTimer service.

How should I implement a 5 millisecond delay

Question: My adapter require a small delay between accesses. The duration of the delay is between 1 & 10 milliseconds. How should I implement this?

Answer: Tricky one. The correct answer is you should architect your hardware so that it will tell you when the jobs done, but failing that here are a few thoughts (many people will disagree with these thoughts):

  • Use the hardware timer chip, this will be accessible to your driver and their should not be a problem using it.
  • Write a software delay loop, I'd advise caution here & assume the loop was running on a v. fast machine for 1 milli. OK so it will take 100millis or so on a 286 but its simple... If in the loop you get the code to poll an I/O port on the ISA bus (it does not matter which one). It will give you a delay with some consistency between machines. The reason for this is because the ISA bus operates at a fixed (well almost fixed) speed of between 8 & 10MHz.
  • What I (actually) do in this situation is, (and I assume you need this 1 milli for a handshake) is to hard code a software delay loop, but make the number of iterations a config.sys timeout parameter - and supply a suitably slow default.
  • Use a timer. If this is a normal thread of execution then the timer may take much longer to trigger than you expect. This is because timer resolution is 32 milliseconds between ticks. For TC threads the situation is a little better with a timer resolution of about 8 milliseconds.
  • Use hardware. Designing & using a hardware timer, counter board is very easy. Timing resolutions of 20-60 micro seconds are easily achievable. The cost of such a board is likely to be less than 50 US Dollars.
  • There is a file in the Devcon DDK iodelay.inc. This uses a clever trick to implement a high precision delay loop. At boot up time the system determines very accurately the speed of the CPU. A variable is set to indicate this. Iodelay.inc contains some code that uses this variable to implement a polling loop with a precision of 500 nanoseconds.

Whatever happens you are going to have to think about this one, as there is no good answer. Your objective though should be to avoid polling loops by whatever means possible, sadly this is not always achievable. Whatever you do, make sure your driver never sits in a forever loop waiting for an I/O value that will never be there. If this happens then you will have crashed the system.

Using interrupts

Timings for interrupt handlers

Question: Are there any figures for delays in interrupt processing in OS/2 1.3. I need to know roughly how long it takes for an interrupt handler to get control after an interrupt has occured, how long the PhysToVirt, UnPhysToVirt (or whatever it's called!) and EOI DevHlp services help?

Answer: There are no official figures that I have seen. The system however guarantees a maximum interrupt disable time of 400 microseconds and that time critical threads are dispatched within 6 milliseconds of becoming ready to run.

This does not mean a badly behaved device driver cannot disable interrupts and hog the cpu for as long as it wants.

Measurements I have made with some diagnostic hardware indicate that the majority of IRQ's take 30 to 50 microseconds. After a soak test of 24 hours continuously hitting IRQ the slowest was 110 microseconds.

The performance of the PhysToVirt and UPV helpers will depend on the address locations being PtoVed or UPtoVed, whether there is a mode switch, and also of course on the CPU.

Interrupts and Race Conditions

Question: In the strategy section parts for RPWRITE and RPREAD I do the Block call immediately after the interrupt request.

So:

       setup hardware
       setirq
       while (not_done)
          block

Nevertheless it can happen that the interrupt occurs before the Block is reached. Under stress conditions the probability of this behaviour is great. In the description of the DevHlp_Block I read about disabling interrupts. How can I disable interrupts?

Answer: The assembler instructions cli & sti disable interrupts, the block helper automatically re-enables interrupts.

       setup hardware
       _asm { cli }
       setirq
       while (not_done)
       {
           block
           _asm { cli }
       }
       _asm { sti }

Is EOI necessary

Question: What are the criteria for the need of EOI?

Answer: Any and all irqs should/must have an EOI. Its trivial to use as the only parameter is the irq number, as a general rule I'd code it up this way.

       EOI(irqnum)
       do the interrupt stuff
       enable interrupts as soon as you are ready to rx more irqs.

The problem/danger is if you get nested interrupts you may run out of stack space. You should try and EOI as soon as possible, and enable irqs when you are ready (or your stack is).

There is a companion helper (more complex) that allows you to automate this process RegisterStackUsage. Tells the systems interrupt handler what each irqs stack requirements are and then the systems interrupt handler will manage all the disabling enabling stuff, YOU MUST STILL DO AN EOI.

Hooking interrupt vectors at device driver level

Question: You write with big letters that hooking a interrupt is a BAD BAD idea. But if I really wanted to do it, what problems am I facing. How do I do it?

Answer: As I said, in the past people have succeeded in doing it. BUT it is documented as being Verboten, so I would not offer it as a solution to a user problem. Developers ok (for debugging purposes), users no.

In order to hook any of the interrupt vectors you will need to patch the contents of the Interrupt Descriptor Table (IDT). If you do this you will be sailing pretty close to the wind. So to be formal I'd say "your on your own if you do it", informally "tell me if it works - & good luck" . So how do you do it?

1) Read the IDTR register to get the size & length of the IDT segment 2) Translate the IDT physical address to a selector address 3) Using the selector address patch entry 8.

  1. 2) is quite tricky. You can cheat, what you'll find is that the IDT can be addressed directly via one of the existing GDT entries (under 2.1 GA I think its selector 20). So you only need to worry about step 3). I'd recommend using the kernel debugger to manually patch the values in, then you'll be able to see quickly whether it will work or not.

Interrupts at Initialisation time

Question: I have associated an IRQ with an interrupt handler at INIT time.

What happens if the interrupt handler is called while I am still in the INIT phase? Or are interrupt handlers disabled till the INIT functions returns?

Answer: Interrupts are not disabled at init time, so it is quite possible for a hardware interrupt to be raised and cause control to pass into an interrupt service routine (ISR).

  • The ISR will receive control
  • The stack will be the INIT time stack
  • The ring level will be 0
  • The iopl level will be 3 (but you can ignore that)
  • It will be in protected mode
  • The state of the interrupt flag will vary depending on whether it is a shared irq or not
  • All interrupt helper services will be available
  • Control will pass into your ISR with cs:ip set to its entry point & ds set to the drivers data segment. This means that all variables & code will be accessible.

How are hardware interrupts prioritised

Question: Under DOS IRQ 0 has the highest priority, is this still the case for OS/2?

Answer: In PDDBASE.INF (OS/2 V2.1 Physical Device Driver Reference), under Physical Parallel Port Device Driver/subsection Parallel Port IRQ Performance:- The Programmable Interrupt Controller (PIC) has been rotated under OS/2 2.1, giving IRQ3 (serial port) the highest priority in the system.

So, the priority is 3,4,5,6,7,0,1,8,9(2),10,11,12,13,14,15.

Unless you can find out otherwise I would work on the basis of IRQ3 (COM2) being the highest.

Intercepting NMIs

Question: I would like to hook the non maskable interrupt, is this possible?

Answer: No. The NMI interrupt is raised on exception conditions:

  1. Memory parity errors
  2. Coprocessor errors

There is no documented mechanism to intercept these interrupts. There is however an undocumented way

DevHelp_Kernel_Exit

DevHelp - Kernel Exit   0x6f

AX  0           Remove
    0x1000      Add
    0x2000      Query

CX  0           NMI
    1           SFF (System Fatal Fault)
BX:SI -- Sel:Offset of code
DS    -- DS of code

Return in EAX

You return Carry flag as done (you handled it) for NMI There is NO recovery for SFF condition.

Set & Clear interrupt instruction is not immediate

Question: I expected the clear and set interrupt assembly instructions to have an immediate effect, but it sort of looks like there is a delay of several instructions. Is this possible, or am I just seeing things wrongly?

Answer: It depends on how you are observing it. The cli instruction takes effect immediately, but the instruction pipeline WILL cause the cli to appear to be read first, then execute about 6 instructions later. To overcome that try:

   jmp next        ; the jump will flush the pipeline (on 386's)
next:
   cli

On the other hand, if you were observing this in a DOS session, then the cli & sti instructions are virtualised. So yes there would be a delay.

Can an OS/2 driver communicate with a Windows app at interrupt time

Question: I've got a PDD servicing interrupts from a card. The PDD lets apps know of these events by running a blocked application thread when it has something to notify.

If I want a Win OS/2 app to use this PDD, is there a clean way of getting notifications from the PDD to the Win OS/2 app ? Obviously I can't go blocking the Win OS/2 thread ...

I'd like to avoid polling if at all possible, but I can't think of a way to make this event-driven. Is there a way you can think of ?

Answer: The correct but complex way would be to write a Virtual Device Driver (VDD). It is simple for an OS/2 Physical Device Driver (PDD) to communicate with a VDD & vice versa.

An incorrect but simple and effective way would be for the Windows app to pass a shared buffer into the PDD using an IOCTL packet. The Windows app can then poll the shared memory waiting on a response from the PDD.

I know this is dirty and I hate polling too, but you can waste more time trying to work out something better than you will ever recover runing the improved program.

To create the shared memory for the windows app, declare a block of memory in your driver and create a GDT entry which points to it, which you pass to DOS by an IOCTL, or just a read( device, buffer, 2 ).

Even better, junk that grotty old Windows code and redo it in shiny new WARP. You know it makes sense !

Printing debug messages at interrupt time

Question: How can I do to print mesages at task or interrupt time? printf is ok at init time but isn't not right after? is there there a device helper function to do that?

Answer: No there are not. The normal way to print messages (for debugging) is to send them down the serial port to a remote (debug) terminal. If you want to print to the screen you can just write to b800:0 (text mode) or a000:0 in graphics mode but that is going to confuse other apps. No, I think what you need is some simple IPC so that your driver can communicate with a normal app and get the normal app to do the display There are several ways to do this using:

  1. semaphores
  2. monitors
  3. block/run
  4. the not done bit

The easiest is 4). I will call your display daemon 'Daemon'

Daemons_main
{
       DosOpen ("DRIVER", ......
       while (1)
       {
               DosDevIoctl (I am a display daemon);
               printf (data read);
       }
}

The driver

Ioctl_cmd
       Is that "the I am a display daemon" function
           yes
               save es:bx to daemon_esbx
               return with done bit NOT set  // This will cause the
                                             // daemons calling thread to
                                             // block
       .....

Later on at interrupt time or task time when you want to display a message from your driver

       get daemon_esbx into es:bx
       put data to print into es:bx data area // This data will be
                                             // returned to the daemon
                                             // in its ioctl packet
       use the dev_done helper and es:bx     // This will unblock the
                                             // daemons blocked thread
       ret

There are many variations on this theme, but the beauty of the above approach is that it is fairly simple and the display daemon can be a pm or a normal app, or simply write to file - whatever you like.

Sharing Interrupts

Question: I'm involved in the development of a driver. I have a question which I hope you can help me with.

If we consider the interrupt servicing part of the driver, then under what circumstances will I get another interrupt on the same irq. Is it after EOI or is it after STI or both.

Answer: Both (98% true).

There are quite complex rules for interrupt sharing under OS/2. If a driver is to support irq sharing then the rules are:

  1. The device MUST not raise irqs at power up (spurious irqs)
  2. You must be able to identify the device generating the interrupt
  3. You must be able to tell the device to deactivate the irq (on mca & level driven h/w)
  4. You must deactivate the irq (on mca & level driven) before issuing the EOI

The important rule is 2). You must be able to tell which device generated the irq.

Popular opinion is that irq sharing will not work on ISA bus machines, well actually it will, BUT apparently (I say apparently because I do not understand why) there is a danger of hardware damage if you try and use shared irqs on an ISA bus.

Now to answer your question re EOI & STI

STI tells the CPU (286/386/486 or in your case 485) to vector to the isr in response to the INT pin being asserted.

EOI tells the interrupt controller chip (8259) that the current (in-service) irq has been serviced and it can service the next (waiting for service) highest priority irq.

So to summarise STI tells the CPU it can start doing more irqs, EOI tells the interrupt queueing/controlling chip that you are ready for the next irq.

As a general guideline do your EOI as fast as possible & do the STI last of all (unless you want nested interrupts).

A couple of gotchas for shared irqs:

  1. The interrupt flag is not set, unlike normal irqs (so do a CLI) that is why I said in answer to your q. above "Both (98% true)" if you do not do a cli on a shared irq & do an EOI & it is not mca then you can get another irq on the irq line immediately.
  2. If you miss an irq which is shared & it is a level irq (rather than edge) then the interrupt dispatcher will disable that irq line.

I would also suggest you use the RegisterStackUsage helper, allow nested irqs on your shared irq line & do the EOI & STI ASAP. The isr's themselves tho' should not take longer than a millisec or two, preferably less.

The meaning of Trap 8

Question: What exactly does " Trap 0008 at #0220:383d mean? I should mention that we are on an AT bus machine, 486 50 Mhz 16 Mb RAM.

Answer: Deep doo-doo. Trap 8 is the double exception fault & occurs when a trap occurs in a trap, you should not see it - something is badly wrong. The address 220:383d is somewhere inside the kernel & does not mean anything to me. I'll need more info to help with this one.

One possible and common cause is that you have run out of stack space. I would advise using the RegisterStackUsage helper. This will prevent the irqs nesting too deeply. If indeed that is the problem.

PC interrupts - edge or level triggered

Question: Do you now if the PC has edge triggered interrupt handling or level triggered interrupts and is there any way to change this. We've had an simular problem in our target systems and solved it by changing from edge triggered to level triggered.

Answer: PCAT uses edge triggered (makes it sensitive to noise and difficult to share) PS/2 uses level triggered (makes it easy to share but naughty devices can lock up the interrupt line which is then masked out by the kernel)

f There is no standard way of changing the interrupt type that I know of (other than going to PCs'r'Us ).

Can I use the DOS & BIOS interrupt calls

Question: I am porting some DOS code to an OS/2 device driver, I need to call some of the BIOS interrupts, how do I do this?

Answer: You cannot, an OS/2 device driver cannot call BIOS functions. You could use a cooperating Virtual Device Driver. Pass parameters to and from the VDD and get the VDD to invoke the calls.

BIOS functions may offer support for protected mode invocation but this is specific to the BIOS function. An example of this is Advanced Power Management which uses an IDC link to provide the address of a BIOS entry point that can be invoked by other OS/2 device drivers.

Shutting down OS/2 gracefully

Is it possible to prevent the user from rebooting the system

Question: Is there a device driver around that assists one to trap and intercept in OS/2 1.x or 2.x

Answer: No, there is not. The problem is that the Ctrl Alt Del (C-A-D) processing is done by the keyboard driver. The keyboard driver when it detects C-A-D invokes the Shutdown code using a Shutdown event device helper service call. There is an IOCTL command you can use with the keyboard driver to tell it to ignore shutdown requests BUT it also disables CTRL/ALT ESC.

DosOpen ("KBD$",....
DosDevIOCtl ( Type 4
              Function 0x56
              Last Word field of parm block 0xffff
The -1 toggles C-A-D off and on.

The source code for the keyboard driver is supplied in the Devcon Device Driver Source Kit. It is a relatively simple task to remove the the C-A-D processing from the driver.

Can a device driver detect a shutdown

Question: My device driver needs to perform some shutdown processing when the user reboots the system. How can a driver sense that the system is being shutdown?

Answer: There is a new option in the capabilities strip that allows drivers to inform the system, that they need to know about shutdowns.

When the kernel is shutting down it calls the driver with a special shutdown (0x1C) command. To summarise:

  1. Create type 3 driver by setting the type field in the attribute word to 3
  2. Set bit 1 on in the capabilities strip, to indicate support for DosDevIOCTL2 request packet & and Shutdown support.
  3. Add support for the shutdown command (command code 0x1C). Each driver will be called twice. First at beginning of shutdown with the function code set to 0, then at end of shutdown with the function code set to 1. The function code is stored in the 14th byte of the request packet.

What differences are there between the versions of OS/2

What are the differences between OS/2 1.X drivers and OS/2 2.1

Question: I have written a driver for OS/2 1.3, will it run under OS/2 2.1

Answer: The following items are possible problems that can occur when OS/2 Version 1.3 16-bit physical device drivers are run on OS/2 2.1 or later. Physical device drivers that use DevHlp_PhysToUVirt will notice changes in these areas:

  • Returned offset (in BX) can no longer equal 0.
  • The OS/2 I/O Subsystems and Device Support Device Drivers (erroneously) stated that the result found in BX, on return from the call to PhysToUVirt, would always be 0.
  • In OS/2 2.1, BX is not always equal to 0 on return from PhysToUVirt.
  • Mapped areas that are greater than 60KB might not be fully mapped if their beginning offset is greater than 0. For example, a 64KB object that begins at n bytes offset from a 4KB page boundary results in a segment/descriptor that has a length of 64KB-n.

Physical device drivers that set up their own GDT call gate will not work in OS/2 2.1. The solution is to use the DevHlp_DynamicAPI in order to have the system generate a GDT call gate into the physical device driver.

Physical device drivers that set up GDT descriptors for private use will not work in OS/2 2.1. The solution is to use one of three Device Helper services - PhysToGDTSelector, PhysToUVirt or PhysToVirt. The decision about which service to use must be based on the context in which the memory is to be accessed.

Physical device drivers that need to switch to real mode will fail. OS/2 defines virtual device drivers that coordinate the access that DOS session have to physical devices. Refer to the Virtual Device Driver Reference for further information.

OS/2 physical device drivers, that were written to support a single DOS application, can fail in OS/2 if they start supporting several DOS applications at the same time. This concerns physical device drivers that maintain addresses of items within a DOS program. Because OS/2 2.1 supports the running of multiple DOS sessions, an address within a DOS program might not always refer to the same DOS session.

If a physical device driver supports multiple DOS applications and needs to maintain data relative to each, the device driver must be modified to the VDD/PDD model.

The device helper service RomCritSection is obsolete under OS/2 2.x. The service is still supported for compatibility but it does nothing.

OS/2 2.1 & Warp Incompatibility - PCI

Question: We have experienced a problem with our PCI device drivers running under OS/2 2.1, the software works fine under Warp.

What is happening is that the driver works until the system is rebooted (using Cntrl Alt Del). After that the driver hangs completely, but this is only a problem under OS/2 2.1.

Answer: PCI support was not available for OS/2 2.11 and earlier versions of OS/2. If you are using OS/2 2.11 then you should apply the latest CSDs. For a fix to OS/2 2.1 you should contact OS/2 Tech Support quoting APAR PJ14230.

What is happening is that the reset operation is not being passed through to your PCI cards, the result is that after reboot, the PCI busmaster is merrily accessing memory that it should not be.

OS/2 2.1 & Warp Incompatibility - link386

Question: We are experiencing a problem running on an OS/2 2.1 system, our driver works fine under 2.11 and Warp. The driver was linked with link386, what seems to be happening is that under OS/2 2.1 not all of the drivers code is being loaded into memory.

Answer: This was a bug in OS/2 2.1. What is happening is that only the first 32K bytes of your code segment is being loaded into memory, the rest is ignored.

There is a fix for this in the latest fixpack.

Warp & OS/2 2.1 & Warp Incompatibility - General Rules

Question: We have experienced a problem with our device drivers running under Warp, the software works fine under OS/2 2.1.

Answer: There are very few differences between OS/2 2.1 & Warp (Version 2.3). The main difference being that Warp has more 32 bit components and is meant to be a performance release.

Under OS/2 2.X the unit of memory acquisition is 4K. Under earlier releases it was 64K. If your driver is locking memory using the DevHelp_Lock helper and it is requesting contiguous physical memory & long term lock. This can cause problems under all versions of OS/2 2.X as contiguous memory is rare. The solution is to only ever use short term lock if the duration of the lock is going to be longer than 2 seconds.

Warp provides support for Resource management. This is a facility where drivers can inform the rest of the system about the resources they require. Once a resource is assigned to a driver, that information is available to other drivers. This might cause a problem if your resource unaware driver was using a resource that had already been acquired by another driver.

In addition there are some guidelines for closing processes:

  1. Any outstanding Request Packets for that process are dispatched.
  2. Any process memory allocated should be freed.

These are sensible things to do for all drivers.

Debugging tips, tricks and treats

How to use the System Tracing calls inside a device driver

Question: Also, you have frequently mentioned DosSysTrace. This is a function (one of many in my opinion) that appears in the H files but is not documented anywhere and worse still there are also no examples of it working.

I would like to know exactly how to use DosSysTrace? Is there some kind of help file or something that I am missing?

Answer: There is an OEMI document which I believe is called cptrace.vol, this documents how to use systrace. It is actually very easy once you have done it once, here is the int3 macro I use:

#ifdef INT3_ON
#define INT3(a)     int3 ();
#else
USHORT  APIENTRY    DosSysTrace (USHORT, USHORT, USHORT, PCHAR);
#define INT3(a)     DosSysTrace (254, 75, 0, a)
#endif

The 254 is the major trace code, when you enable/disable tracing the major code is what you use; 254 is a user-definable trace code. The 75 is the length in bytes of the 4th parameter. The maximum length is 75 so thats what I use (you could do a strlen on parameter 4). The 3rd parameter is the minor trace code I use 0 for int3's and other numbers to describe other events. In a physical device driver for instance you could use the command number (init, read, write, etc.) as the minor code. Finally the 4th param is just a data buffer, for passing other params.

Once your driver has output a trace you can use the tracefmt or the trace utility tools to view the contents of the trace buffer.

Unfortunately the document I mentioned above does not include the API documentation, but you should have enough details in this message to proceed.

There is a document in the Devcon DDK, which describes the dynamic trace facility, unfortunately my simple mind has been unable to decipher and understand it.

Kernel Debug will not work

Question: We try to use the kernel debugger for development of an OS/2 PDD. We have the Device Driver Kit and the OS/2 Toolkit 2.1. But we still have difficulties in using the KDB.

The problem is that when booting the machine the KDB startup message will be displayed upon the KDB console. But we get no prompt to enter a command.

Answer: To grab control on the console type Control C (^C), you should see the registers displayed. If you do not then the most likely cause of a problem is your serial cable. It should be a null modem cable 2 & 3 switched + ground

IE:

 2 ----- 3
 3 ----- 2
 7 ----- 7

I always like to check the comms first as comms problems are so common. Boot up dos on your os/2 machine & check that you can get comms working in both directions under dos.

Using kernel debug over a modem

Question: I am debugging an OS/2 system over a 2400 baud modem connected phone line. Is there a way to tell kernel debugger to use 2400 baud at boot up.

Answer: You can place a series of kernel debug commands into a text file called KDB.INI, which should be placed in the root directory. On power up the kernel debugger will execute all the commands in this file.

The command you require is :

.b 2400 

VMLock Device Helper crashing system

Question: I have solved my problem. I was unlocking memory that was not previously locked (actually, a NULL pointer). However, perhaps you can forward this, since such an error should not bring down OS/2.

Answer: The issue here is that, device drivers are not applications. It is the responsibilty of the device driver author to ensure that the dd (& the rest of the system) does not crash in response to invalid/illegal parameters - thats why we get paid so much (NOT).

Sorry about the tone of the last para, but basically I think the development groups would be reluctant to 'add' extra code (& overhead) testing these helper function parameters further. On the other hand the manual does state that there is an ERROR_INVALID_PARAMETER & an ERROR_INVALID_HANDLE return code, so I'd have thought that a NULL lockhandle would be trapped - but was it a NULL lockhandle or a NULL pointer to lockhandle either way it may have been a valid lockhandle & you were just unlucky in that you unlocked something that should not have been unlocked. And anyway was it the unlock that was trashing the system or what you did after with the unlocked memory?

The bottom line for driver writers is code defensively.

Invoking Trace from within a device driver

Question: You mentioned in an earlier message that it was possible to generate trace messages from within a Physical Device Driver, how ? I cannot find it in any of the manuals.

Answer: It is not widely known, but there is a DevHlp_RASTrace. It places data into the systems circular trace buffer, & can be invoked from within drivers. I usually use major code 254 & a variety of minor codes to signify different things.

The code for invoking DevHlp_RASTrace:

   mov ax, major         ; I suggest you use 0xff or 0xfe
   mov bx, length        ; trace buffer length must be < 75
   mov cx, minor         ;
   mov si, offset buffer ; data to be placed in trace buffer @ ds:si
   mov dl, 28h           ; devhlp_RASTrace

Its pretty straightforward to use, but I have only seen one doc on this in the MS SDK for OS/2 1.0 (date 20/5/87) - but it does still work.

To view my trace I normally write a simple app to:

system ("tracefmt > file")
extract my info from file

It is extremely useful as it allows your driver to dump out any information you like, you do not need kernel debug and with a judicious use of switches you can incorporate it into shipping code.

Source level device driver debugging

Question: I'm writting Device Driver for graphics cards based on the S3 Chipset. Now i look for an Source Code Debugger for debugging my display.dll and other 'System' DLL's. Is the Periscope Debugger the right one ?

Similiarly, can I set a Breakpoint on I/O Port access ?

Answer: Yes, & as far as I know it is the only source level kernel debugger (for OS/2) there is.

The answer is yes/no. The periscope product comes in two flavours expensive & cheap. The expensive variant is a hardware debugger and will allow you to bp on an io address (+ loads more things). The cheap variant is software only & will not do an io bp.

I have experiences with an early periscope hardware debugger (it needs an 8086 chip) & it was an excellent product, I have no experiences with the current hw debuggers. I use the current software debugger & it is extremely good value for money.

My suggestions to you are :

  1. Check out the software debugger first - it has EXACTLY the same interface as the hw variant. It costs a 395 US dollars but you'll be able to decide then whether to purchase the hw version (the hw version costs 1000's $ depending on chip).
  2. Contact Brett Salter at Periscope and see what he says re the hw platform you have & the hw debuggers they can supply (& at what cost).

I know of several companies that keep very quiet about using the Periscope product - because it is so good!

Conditional breakpoints in kernel debug

Question: How can I do with kernel Debugger to stop the execution when an expression comes true: For example, I want to have a breakpoint when ES == 500 or a dump at this moment? Is it possible to have a trace, a dump before or after an condition is true ? I have no success with the command "J"...another mistake? thank you.

Answer: J expression command_list

The J command executes the command list if the expression is true. So if you want to dump es:bx only if es == 500:

J ES==500 "dw es:bx; g"

Now this is not much good on its own, the way it is meant to be used is in conjunction with a normal sticky breakpoint:

bp CS:a54 "J es==500 'dw es:bx;g';g

The above sets a bp at cs:a54 and when the bp is hit will dump es:bx only if es == 500. In both cases control will then carry on.

Warning when using the Kernel Debugger

Question: I'm debugging a device driver of mine using the kernel debugger, and a data stream to the serial port, and I get the following message in the middle of my debugging stream at random points

NWD WARNING - a failsafe timeout was detected at cs:eip ... (and some address)

What does this mean?

Answer: When the KDB is running a time-critical code section, specifically in the file system areas, this error can occur. It means that NMI interrupts are occuring at a rate which cannot be handled fast enough.

The usual reason for this problem is that a driver is disabling interrupts for too long.

How should my driver output debugging messages to the debug terminal

Question: I would like to be able to output debug messages to the kernel debuggers debug terminal. Is there a simple way to do this?

Answer:Yes, simply write to the serial port. The following code is what I use. It uses a hardwired COM2: address, but you could easily change this by modifying the COM_PORT equate.

;
;UINT sprint(CHAR *str)
;
                .286p
_TEXT           SEGMENT WORD PUBLIC 'CODE'
                ASSUME CS:_TEXT
                PUBLIC SPRINT 

COM_PORT        equ     02F8h                      ; 2f8 = com2 3f8 = com1

SPRINT          PROC    NEAR
                enter   0,0
                push    si
                mov     si,4[bp]                   ; get src
nextc:          mov     dx,COM_PORT+5              ; get line status
xwait:
                in      al,dx                      ; read status
                test    al,20h                     ; txer holding empty
                jz      xwait                      ; if not
                lodsb                              ; get next byte to xmit
                cmp     al,0                       ;
                je      finnn                      ; if end of string
                mov     dx,COM_PORT                ; data tx reg
                out     dx,al                      ; xmit the char

                                                   ; PCs generally need
                                                   ; a small inter character
                                                   ; delay
                push    cx
                mov     cx, 0100h
slodown:        nop
                loop    slodown
                pop     cx

                jmp     nextc                      ;get next char
finnn:          pop     si
                leave
                ret     2
SPRINT          ENDP
_TEXT           ENDS
                end

A collection of tools & information on tools for device driver authors

Getting access to IBM's OS/2 device driver source code

Question: I would like to write a printer device driver, is it possible to get the source from IBM?

Answer: Yes it is.

The Device Driver Kit from IBMs Devcon group contains the complete source code to most of IBM's OS/2 device drivers. The DDK contains several examples of printer drivers.

I would suggest that this kit is a must have for your project. Without it, you will not be taken seriously.

If you need access to code not in the DDK and if you can demonstrate a business need, then you will find that the IBM support staff will do their best to fulfill your needs.

Debugging at source level

Question: I want to be able to debug my device driver at source level (the way CodeView works). This is important as I do not really understand assembler and anyway why should I have to look at assembly.

Answer: Periscope/32 is the only tool I know of that will allow you to debug an in-situ device driver at source level.

I know of several companies that keep very quiet about using the Periscope product, because it is so good.

SNOOP/2 System debugging tool

Question: I need a tool that will tell me where in memory a device driver has been loaded.

Answer: I have included a tool that should work. Its about 250K, and is likely to be of interest though to those using the kernel debugger. The utility is called SNOOP/2 and can be downloaded from the File area.

This code is unlikely ever to come out of beta as there are a lot of functions I cannot implement under 2.X. The functions that I have implemented are:

  • View installed device drivers
    This is likely to be useful if you are using kernel debug but need to know where in memory your drivers (or any other drivers) are.
  • Disassemble installed drivers in memory
    This is useful as it supports symbols and can be used to view your drivers data seg symbolically. There is an example skeleton driver to experiment with. N.B. Instructions are in the supplied READ.ME file.
  • View GDT contents and edit/disassemble data/code. I have been trying to use this to look into the GA proc 160:xxxxx problem, without any success so far - it crashes ! But it does work for other less "delicate" regions of memory.
  • View GDT Call gates
    If you want to put a breakpoint on DosBeep with kdb you can use this to find the location of DosBeep
  • View IDT interrupt and Trap gates

All the functions work correctly under all versions of OS/2 1.X, including 1.0, 1.1, 1.2 and 1.3.

There are a couple of license docs in the zip, basically this note authorises developers to use this code for driver related work. It is distributed in the spirit of shareware but if you find yourself using the code for real work, then offers of warm alcohol would be looked on favourably .

General useful information and support

What are the copyright issues on Devcon DDK sample code

Question: I've rebuilt COM.SYS from the DDSK CD, and though it is a different size, it seems to work OK - and it's simple to add an IOCTL to get it to do the stuff I need, but how does IBM deal with having its copyrighted code ripped off, hacked up and sold on to our customers? I can put in a message during boot acknowledging the owner's copyright, and add something to the manual, but is this enough?

Answer: In a word yes, I've asked the people in Boca the same question a couple of times and in both cases the response was "no problem". As far as the letter of the law is concerned I'd still be a little nervous tho'. So to cover yourself, I'd suggest reading the copyright notices in the product very carefully.

I have enclosed the copyright notices from the various components in the DDK. The author of this document is not a lawyer and cannot accept any liability for problems arising from the information supplied here. The bottom line is - IBM WANTS YOU TO WRITE DRIVERS - this is why the Devcon product exists. From an uneducated (legally) point of view the license agreements look fair and reasonable and I would agree with the IBMers comments, there 'should' be no problem.

Source code
The source code in the DDK all contains the same or similiar Copyright messages. This message refers you to the License agreement supplied with the DDK.
Some of the DDK source components, contain Copyright notices from organisations other than IBM. You should read these notices carefully. The Intellectual Property rights for these components WILL NOT BE COVERED UNDER THE IBM COPYRIGHT STATEMENTS. An example of one such, is included in the next extract.
;*DDK*************************************************************************/
;
; COPYRIGHT (C) Microsoft Corporation, 1989
; COPYRIGHT    Copyright (C) 1995 IBM Corporation
;
;    The following IBM OS/2 WARP source code is provided to you solely for
;    the purpose of assisting you in your development of OS/2 WARP device
;    drivers. You may use this code in accordance with the IBM License
;    Agreement provided in the IBM Device Driver Source Kit for OS/2. This
;    Copyright statement may not be removed.;
;*****************************************************************************/
License Agreement
This is the IBM license agreement that is referred to in the DDK source code:

COPYRIGHT LICENSE: This publication contains printed sample application programs in source language, which illustrate OS/2 programming techniques. You may copy, modify, and distribute these sample programs in any form without payment to IBM, for the purposes of developing, using, marketing or distributing application programs conforming to the OS/2 application programming interface.

Each copy of any portion of these sample programs or any derivative work, which is distributed to others, must include a copyright notice as follows: "(C) (your company name) (year). All rights reserved."

(C) Copyright International Business Machines Corporation 1994. All rights reserved.

Note to U.S. Government Users - Documentation related to restricted rights - Use, duplication or disclosure is subject to restrictions set forth in GSA ADP Schedule Contract with IBM Corp.

Additional IBM Property Rights
In the DDK there is reference to other components, this agreement is a catch-all and covers all components referred to in the DDK other than the source code.
References in this publication to IBM products, programs, or services do not imply that IBM intends to make these available in all countries in which IBM operates. Any reference to an IBM product, program, or service is not intended to state or imply that only IBM's product, program, or service may be used. Any functionally equivalent product, program, or service that does not infringe on any of IBM's intellectual property rights or other legally protectable rights may be used instead of the IBM product, program, or service. Evaluation and verification of operation in conjunction with other products, programs, or services, except those expressly designated by IBM, are the user's responsibility.
IBM may have patents or pending patent applications covering subject matter in this document. The furnishing of this document does not give you any license to these patents. You can send license inquiries, in writing, to the IBM Director of Commercial Relations, IBM Corporation, Purchase, NY 10577.
Writing OS/2 Device Drivers in 'C' by Steve Mastrianni
This on-line book is covered under a seperate license agreement.
                                    Writing OS/2 2.1 Device Drivers in C
                                    Second Edition
                                    Steven J. Mastrianni

TRADEMARKS AND COPYRIGHTS

Copyright  1993 by Van Nostrand Reinhold

Library of Congress Catalog Card Number 93-2264 ISBN 0-442-01729-4

All rights reserved. No part of this work covered by the copyright hereon may be reproduced or
used in any form or by any means-graphic, electronic, or mechanical, including photocopying,
recording, taping, or information storage and retrieval systems-without written permission of
the publisher.

Van Nostrand Reinhold is an International Thomson Publishing company. ITP logo is a trademark
under license.

Printed in the United States of America

Van Nostrand Reinhold               International Thomson Publishing GmbH
115 Fifth Avenue                    Kunigswinteror Str. 518
New York, NY 10003                  5300 Bonn 3
                                    Germany

International Thomson Publishing    International Thomson Publishing Asia
Berkshire House, 168-173            38 Kim Tian Road, #0105
High Holborn, London WC1V 7AA       Kim Tian Plaza
England                             Singapore 0316


Thomas Nelson Australia             International Thomson Publishing Japan
102 Dodds Street                    Kyowa Building, 3F
South Melbourne 3205                2-2-1 Hirakawacho
Victoria, Australia                 Chiyada-Ku, Tokyo 102
                                    Japan

Nelson Canada
1120 Birchmount Road
Scarborough, Ontario
M1K 5G4, Canada

What are the plans for supporting PCMCIA machines running OS/2

Question: What are the plans for supporting PCMCIA machines running OS/2. As I understand it the O/S has to provide Card and Socket Services. Does such a device driver exist (I have seen a spec for its OS/2 interface, but is there an official spec around ?)

Answer: Yes it is the PCMCIA Card Services Version 2.0 & Socket Services Version 2.0 the complete spec may be acquired from the PCMCIA organisation. The chairman of this outfit is on secondment from IBM so I guess you can assume that OS/2 will be following the standard - closely. PCMCIA Office 1030G East Duane Avenue Sunnyvale CA 94086 Voice (408) 7200107 Fax (408) 7209416 BBS (408) 7209388

The spec costs a couple of hundred quid, but if your writing PCMCIA drivers it is a must have.

All the latest OS/2 information may be found in the device driver developers kit version 1.1. There is some documentation in the IN_OUT.INF (Input Output Technical Reference). This is very overviewy but it does describe the main deviations from the 2.0 standard.

THere is also an example client services driver provided in source. And the info required to use the COM driver interface for communication with PCMCIA serial devices.

So to summarise the available OS/2 info is still slim, but it is possible to get help for (technical) PCMCIA queries.

Is a delay necessary between I/O operations

Question: I looked at some code in ddk\src\dev\mouse\bus.asm which diddles with the interrupt controller, and it calls MyIODelay in between IN and OUT instructions. I'm not clear why these are required. It says something about letting the bus catch up - is this brain dead hardware or what?

Answer: You were right first time - brain dead hardware. "Some" (not all) IO devices cannot do:

  in al,dx
  or al,MASK
  out dx,al

as the CPU is faster than the IO peripherals, received wisdom is that you should put an arbitrary small delay between IO access to the same IO device. And most people use:

  in al,dx
  or al,MASK
     jmp next_instruction
next_instruction:
  out dx,al

The jump op has the additional "benefit" of flushing the CPUs instruction pipeline and thereby slowing down processing even more.

Non-NULL NULL pointer

Question:I have noticed a 32 bit null pointer is getting converted to something else when I call a driver. Is this possible?

Answer: This is a side effect of the thunking layer. The thunking layers job is to convert 32 bit pointers to 16:16 ones. The explanation for this is quite complex but the end result is that

32 bit          16:16 bit
00000000  --->  0003:0000

Unfortunately tests for NULL will no longer worker. A solution is to use a new NULL test macro:

#define ISNULL(p) ((((ULONG(p)&0xFFFCFFFF)==0L)

Querying the type of CPU

Question: How can I get the model of the CPU by program?

Answer: Some of the info (stepping level) is only available when the chip is reset, some '86 series chips from other vendors (Cyrix) have dedicated IO registers that can be queried. The 586 chip set has a new op CPUID which returns all the info you want.

The chip vendors supply routines that can be used to query the type of the chip 8086, 286, 386, 486. These routines work by exploiting differences between the instruction sets. You tend to find examples of these routines on the chip vendors BBS's.

Most of Intel's Processor User Manuals contain example "Feature Determination" code.

Device driver support for OS/2 SMP

Question: I have some questions about OS/2 SMP (Symmetric MultiProcessing). I'm writing physical device drivers (PDD) for OS/2. Is there any operating system support for my device driver (DevHelp or similar) for SPINLOCKS etc.?

Answer: The simplest way to get info on SMP is to purchase the OS/2 for SMP product, the docs come with it. If you want your SMP unaware drivers to run under SMP there are only two rules:

  1. Use the EOI helper and not out 20 to port 20.
  2. Do NOT mask IRQ levels.

There are 4 devhelps for spinlocks, 1 for port access, and 2 for managing IRQ masking. There is also a hardware dependent IDC architecture that allows the hardware vendor to supply additional functionality to your driver.

The device driver should protect access to critical resources using spinlocks. The device driver allocates spinlocks in the Init routine by calling DevHlp_CreateSpinLock. CreateSpinLock returns a handle for use by the device driver. This handle is passed to DevHlp_AcquireSpinLock and DevHlp_ReleaseSpinLock. The spinlock is freed by calling DevHlp_FreeSpinLock. The driver may request any number of spinlocks, as the spinlocks are represented by a very small data structure. Once created, the spinlocks never go away.

As was outlined previously, OS/2 for SMP V2.11 contains a layer of abstraction for any functions that are deemed platform specific. These functions are placed inside the Platform Specific Driver (PSD) and isolate device drivers from the particular hardware platform that they are running on. At boot time, OS/2 determines and loads the appropriate PSD for the MP system hardware it is currently running on.

All device drivers that are MP-safe must use the appropriate kernel services to do hardware specific functions. The kernel will route these requests to the PSD for processing.

Device drivers in OS/2 2.x were written with the concept that only one processor can generate interrupts. But with OS/2 for SMP V2.11 other processors can now generate interrupts, so device drivers should account for re-entrance and parallel execution of task-time and interrupt-time code.

Plug'n'Play with resource manager

Question: We've developed a plug'n'play ISA card and now want to adapt the drivers for this card.

Is there a service in OS/2 (DevHelp etc.) that enables our driver to request the resources assigned to our card from BIOS/OS or CM (Configuration Manager)? If YES where can I get information/specification/examples on it?

Answer: Yes there is, the file that documents it is called RMBASE.INF it is on the DUDE board & the DDK CDROM.

This document describes how a driver should inform the system of the resources it is going to use. It is the responsibility of the driver to behave appropriately if a resource is already in use by another driver (ie display an error message and do not install).

The mechanism is very simple and all the example drivers in the DDK use it.

Documented and undocumented IO addresses

Question: Using a scope we have found that on some systems there are conflicts with our cards IO port address. Our cards I/O address is fixed, so it is very important for us to have a complete list of all port-adresses used by OS/2 particularly the ports higher than 0x400.

Answer: There are a number of video drivers that appear to be using undocumented IO port addresses especially during screen switching. We have no control over these 3rd party products.

Any vendor may use any IO port addreses, except for the ones that are reserved.

PC AT bus machines only decode on the bottom 10 address lines for IO. MCA & EISA decode on 16 address lines. I suspect that what has happened is that code that was intended for MCA has drifted onto ISA bus machines the result is strange IO addreses being generated.

Any software (or hardware) using IO addresses above 0x400 (on ISA bus) is not 'exactly' wrong, but I'd consider it sloppy design, unhappily it does happen, so your design must offer the users the ability to select IO ranges.

This is very easy to do in hardware using either a simple PAL or a TTL comparator wired up to a set of DIP switches.

Generating a Printscreen interrupt

Question: Is it possible to force PrintScreen to capture full screen independent of which window that is active (and without activating the desktop)? Are there any system calls to do this (C or REXX)?

Answer:I do not know of a system call to do it, but there is a print screen event that can be generated by device drivers:

mov ah, 5
xor bx,bx
mov dl, DevHlp_SendEvent
call [dev_help]

Its documented in PDD tech ref page 17-84

How should device drivers read and write to .ini files

Question: Currently the configuration parameters for our driver (VNSNETB2.SYS) are provided on the DEVICE=VNSNETB2.SYS line in CONFIG.SYS. What I would like to do is extract our configuration information from a [VNSNETB] section in PROTOCOL.INI. This would make things consistent for our users, and would make NETBIOS.OS2 report the correct number of Sessions, Commands, and Names available after it has done it's initialisation, as it tries to interprete the PROTOCOL.INI file for our configuration.

Answer: It is possible to read and write any file at initialisation time, but that is the only time. There are no documented mechanisms for drivers to do this at run time.

This can be a real nuisance. The solution I use is borrowed from the TCP/IP SNMP (Simple Network Management Protocol) suite. SNMP is a protocol for managing settings (.ini) for distributed networks. It does this by reading and writing to a collection of settings called MIBs. The primitives used are fetch & store. I.E. Fetch the value of a setting, and store the value of a setting. To reset a router you would set the time_to_reset setting to zero.

To implement this in a driver requires 3 IOCTL commands (actually 2):

  1. Fetch a value for a named setting
  2. Store a value for a named setting
  3. Enumerate the names and values of settings

The third item is not necessary but it makes life a lot easier for settings notebooks.

For example a video capture card will have settings for volume, tuners and many other items. A tool for modifying the .ini for the volume would:

  1. Query & set the volume setting
  2. Enumerate all settings and values
  3. Write all settings and values to the .ini file

In other words the volume control does not need to know anything about the other settings.

Non-error return codes

Question: In my OS/2 device driver the function DosRead() should have various return values. If I return (RPDONE or 1) or (RPDONE or 2) ... then the application gets always the value 5. Does anybody know why these values will always be mapped by the OS/2 kernel to the value 5 ? Only with the return value (RPDONE or 0x0011) the application gets the return value 0x5f.

Answer: The return values are only documented as being meaningful when you are returning errors ie when bit 14 or bit 15 are set. I would recommend not trying to return status codes in the way you describe because you may have problems with future versions of OS/2.

As an alternative, you could provide an IOCTL interface that is called after a DosRead, that however could give problems if the driver is shared. Alternatively dispense with DosRead altogether and just use an IOCTL call to perform the read AND return a status code.

Can I load a driver without rebooting OS/2

Question: I am developing a device driver and everytime I make a change I have to reboot the system. My system is a LAN workstation and the reboot process takes a long time. Is there anyway I can avoid rebooting the system?

Answer:No, there is no way at present to avoid rebooting. There are techniques you can use to reduce the number of reboots and the time it takes to reboot.

If the change you are making to your driver is to fix a small bug, or is only of a very minor nature, you can patch the driver in situ, using the kernel debugger. I know this sounds difficult but it is actually quite good fun and very rewarding when you succeed in pulling it off. The most important machine code instruction for manual patching is 0x90 (No op). Most bugs when you find them can be patched temporarily by removing the offending code, if after overwriting the buggy bit with No Ops the driver works, then you know you have:

  • Identified the problem
  • Found and applied a fix

more importantly you can now patch and compile the source ready for a reboot.

There are several things you can do to reduce the time required for a reboot:

  • Create a second (small) bootable partition and place the driver under development in this partition.
  • Use the FAT file system in this partition. HPFS is far less tolerant of system crashes and it is easier to repair FAT files than HPFS.
  • Modify the PROTSHELL config.sys line to use a text shell. You can use CMD.EXE but that will only give you a single OS/2 session. There are two other minimal shells on the Devcon 8 CD, MSHELL.EXE & TSHELL.EXE.

Can a device driver start a program

Question: I would like my driver to start running a ring 3 application. Is this possible?

Answer: The correct answer is no, however there are a couple of ways a driver can start an application:

  • At initialisation time a driver can use the DosExecPgm call to start a program. The documentation is quite clear that this is not supported but it does work with this version of OS/2 and all earlier releases.
  • Use a cooperating app. Once communications between an app and a driver are working then the driver can request the cooperating app do anything.

Other sources of information and support

Where is the file DEMODB.ASM from the Developers Toolkit for OS/2 2.0

Question: I am looking for the file DEMODB.ASM from the Developers Toolkit for OS/2 2.0. It is a demo of a COM driver and was advertised as being part of the Developers Toolkit.

Can you help me?

Answer: Unfortunately no, that particular file was listed as being present in the toolkit but was in fact missing.

If you are interested in writing a com driver two good starting points are:

  1. The DDK which contains the source code for com drivers
  2. The Mastrianni book "Writing OS/2 Device Drivers in 'C'" which also contains source code examples.

I've never seen the demodb.asm example, but (like you) I do remember looking for it.

Finding OS/2 Device Drivers

Question:I need a device driver for product X, where can I find it?

Answer: Dilbagh has set up an area on an IBM server which has become the de facto world wide repository for all OS/2 device drivers. This can be found at http://www.europe.ibm.com/getdoc/psmemea/progserv/device/ or FTP:

ftp.europe.ibm.com or www.europe.ibm.com or 193.129.186.2
- logon using anonymous, password=
- change to /psmemea/os2drivers/

In either case, please read the README and/or .message files. The file 00index.txt provides a list of all drivers in the repository.

If you cannot find a driver for the product you are interested in then there is a useful list of company links in the area http://www.europe.ibm.com/getdoc/psmemea/progserv/device/helpful.html

Support for the Device Driver Source Kit - The Old Entry

Question: How do I get support for the Device Driver Source Kit?

Answer: The support channel for the Devcon DDK is the DUDE. The form this takes is:

You submit a description of your problem either via a BBS or Internet mail or FTP. A response will be returned.

The DUDE is very good for problems in the IBM code. They cannot provide support for defects in user supplied code (although quite often they will). The DUDE team are good guys.

The following information was supplied by the DUDE group:


Isn't it time your devices got Warp-ed?

After all, it's going to be a Warp-ed world in 1995 and you'll want to stay ahead of your competition. The time is NOW for you to reap the benefits of the exploding acceptance of OS/2 as the 32-bit operating system for personal computers. The opportunity is here and increasing daily. Join the ranks of your colleagues in the industry who have device support for OS/2 and are exploiting this market explosion.

The list of OS/2 compatible graphics accelerators, DASD, SCSI, and multimedia products that exist today is constantly expanding. Are YOU on that list? If not, then NOW is the time to make a change and get your share of the market. And here's how to do it.

The DUDE

DUDE team of the IBM Driver Development Support Center is standing ready to provide you with training and support to help you expedite your OS/2 driver development efforts. Just take a look at how the DUDE team provides one-stop shopping for worldwide device driver development support.

  • The DUDE Team
    • Support for your questions and training needs
    • Voice-Mail-24 hours-407-982-4239
  • The DUDE, a dedicated BBS
    • Latest information focused on driver developer needs
    • Development problem assistance
    • Timely news of interest and Technical Conference bulletins
    • 407-982-3217 - N,8,1 @ 14.4KBPS
  • Education
    • Specialized driver developer workshops
    • Expertise of highly experienced instructors
    • Intensive, hands-on technical sessions
    • Access to IBM Boca Programming Center technical personnel

Now that you know how extensive the on-going technical support is, just look at the sound business reasons why you need to begin your OS/2 driver development efforts right away!

  • Ride the crest of rapidly increasing OS/2 market acceptance.
  • Save significantly on programmer development time and costs.
  • Gain access to the latest levels of code.
  • Give yourself a "Quick Start" on bringing your driver to market.
  • Open your opportunities to new, explosive OS/2 market segments.
  • Your competition is doing it!

OK, now you need to take the first step toward acquiring your market share! First, connect to "The DUDE" at 407-982-3217 and complete the registration form. Downloading the file DDSC_DES.TXT provides the information that will guide you through the array of services available on the DUDE. Within one business day, your access level will be upgraded and you may sign up for one of the Device Driver Developer Workshops held at the IBM Boca Raton facility. To register for any of the workshops, download the registration file, REGISTER.TXT, fill in the blanks, and then upload the completed file to the DUDE. When you upload your form, you will receive a D-MAIL (DUDE-MAIL) confirming your registration in the DDSC Workshop. IT'S THAT EASY!!

And, if you don't have immediate access to the DUDE, call our Voice-Mail number (407-982-4239) and leave a message requesting help. Your call will be returned within two business hours.

Your registration on the DUDE will ensure that the DUDE team can continue to provide you with the on-going support that you may need during your driver development efforts.

Additionally, if you're looking for someone to write device drivers, then look at this bonus offered by the DUDE. You can use DUDE-ADS to help increase your chances of finding the right developer for the job.

Now that you know how to get started, put your plan together, enroll now, and prepare to Warp into the future with OS/2, the award winning 32-bit operating system of choice!

Support for the Device Driver Source Kit - The New Entry

Question: How do I get support for the Device Driver Source Kit?

Answer: The Dude guys now have a web site, it is at The DUDE

The site contains all the latest documentation and tools for OS/2 device driver developers, this is a MUST for your bookmarks.

Device Driver Documentation for developers

Question: What documentation is there for device driver developers?

Answer: The DUDE

This site contains all the latest documentation and tools for OS/2 device driver developers, this includes all the latest references from the Device Driver Toolkit. The Device Driver Toolkit contains all the references you need. Many of these documents can be ordered seperately.

Display Device Driver Reference (S71G-1896)
The Display Device Driver Reference covers the OS/2* display device drivers supplied with this kit.
A hardcopy version of this book is available separately with order form number S71G-1896.
Printer Device Driver Reference (S71G-1895)
The Printer Device Driver Reference covers presentation drivers for OS/2* and how they operate. Also covered are printer and plotter drivers, their interfaces, and the available OS/2 system services provided.
A hardcopy version of this book is available separately with order form number S71G-1895.
Storage Device Driver Reference (S71G-1897)
The Storage Device Driver Reference covers the OS/2* programming interfaces to support original equipment manufacturer (OEM) direct access storage devices (DASD), small computer system interface (SCSI) devices, and compact disc read-only memory (CD-ROM) devices.
A hardcopy version of this book is available separately with order form number S71G-1897.
Input/Output Device Driver Reference (S71G-1898)
The Input/Output Device Driver Reference covers the OS/2* I/O device drivers supplied with this kit.
A hardcopy version of this book is available separately with order form number S71G-1898.
Pen for OS/2 Device Driver Reference (S71G-1899)
The Pen for OS/2 Device Driver Reference covers the OS/2 Pen for OS/2 device driver and its operation, architecture, and interface.
A hardcopy version of this book is available separately with order form number S71G-1899.
MMPM/2 Device Driver Reference (S71G-3678) - Updated
The MMPM/2 Device Driver Reference describes the Multimedia Presentation Manager/2* device driver supplied with this kit.
A hardcopy version of this book is available separately with order form number S71G-3678.
Virtual Device Driver Reference (S10G-6310)
The OS/2 2.1 Virtual Device Driver Reference describes the different types of virtual device drivers, their interfaces, and the kernel services available under OS/2*.
A hardcopy version of this book is available separately with order form number S10G-6310.
Physical Device Driver Reference (S10G-6266)
The OS/2 2.1 Physical Device Driver Reference describes the different types of physical device drivers, their interfaces, and the system services available under OS/2*.
A hardcopy version of this book is available separately with order form number S10G-6266.
Presentation Driver Reference (S10G-6267)
The OS/2 2.1 Presentation Driver Reference describes the different types of presentation drivers, their interfaces, and the system services available under OS/2*.
A hardcopy version of this book is available separately with order form number S10G-6267.
IPF Guide and Reference (G25H-7110)
The Information Presentation Facility Guide and Reference describes the use of IPFC Version 2.0 of the online reference creation tool.
Select the IPFC icon to view this documentation.
A hardcopy version of this book is available separately with order form number S10G-6262.
TRCUST Online Reference (Online Only)
The Dynamic Trace Customizer (TRCUST) Reference describes the installation and use of the TRCUST tool, supplied with the DDK.
Select the Dynamic Trace Customizer object to view this documentation.
OS/2 for SMP V2.11 Reference (Online Only)
The OS/2 for SMP V2.11 Reference online document is a guide for developers who are writing applications and device drivers for OS/2 for Symmetrical Multiprocessing (SMP) V2.11.
To order OS/2 for SMP V2.11, call 1-800-342-6672.
Writing OS/2 2.1 Device Drivers in C
Writing OS/2 2.1 Device Drivers in C is a comprehensive technical reference book written by Steve Mastrianni. This book covers most topics you need to know if you are a device driver developer.
This book is reproduced by permission. No permission is granted for electronic or print versions of this title.
A hardcopy version of this book is available separately.
The Graphics Adapter Device Driver Reference is a preliminary document that describes the new architecture for creating graphics adapter device drivers for multiple operating system services.
It also describes the Enhanced Direct Interface Video Extension (EnDIVE) and the Video Protect-Mode Interface.
OS/2 Hardware compatibility list
The OS/2 Hardware Compatibility List online document is a comprehensive listing of all major manufacturer products that are OS/2 compatible.
For information on having your hardware product included in this list or to order, call the following numbers:
1-800-426-4579 U.S. and Canada
1-708-296-6767 International
1-708-635-3620 Fax

Support for OS/2 Device Driver Developers

Question: How do I get support for developing OS/2 Device Drivers

Answer: There are several support mechanisms, but before you start you should :

  • Purchase the Device Driver Kit from IBM. This contains the source to many of OS/2's device drivers plus it is supported by the The DUDE team.
  • The Devcon DDK contains all the technical documentation and Steve Mastriannis book - you should read Steve's book. It is the very best way to get started.

Once you have completed the above two tasks you are ready and (hopefully) eager to start cutting your first driver and that's when the fun starts.

For support you can now use:

  • The DUDE guys.
  • Steve Mastrianni, Steve hangs out on Compuserve in the OS2DD area. Steve is very very active in helping folk solve their driver writing problems.
  • The EMEA DAP group.