OS/2 initialisation - making it work for you

From EDM2
Jump to: navigation, search

By Roger Orr

One of the biggest disappointments I first had with OS/2 was the number of times I needed to reboot my machine. In particular, I didn't like having to reboot TWICE very often - first to boot DOS off a flopppy so I could update a DLL or check the disk and the second time to restart OS/2 to use the new environment.

There are four common reasons why I have to reboot from floppy (or at least with a changed CONFIG.SYS)

1) to run CHKDSK

This is a fairly common problem with developers! When OS/2 stops with one of those wonderfully unhelpful messages ("the system is stopped") or more often when PM just hangs up on you forcing a reboot, then any open files are often not tidied up properly. I am very often caught with compilations in progress or log files being written to, and so have to run CHKDSK. Unfortunately... you need exclusive use of the disk to do a good job and OS/2 tends to have lots of files assigned. My system is set up with several logical drives, and drive C: is basically read-only (OS/2, C compiler, utility programs, archives) and I try to keep it that way to eliminate disk problems with it. However I do use other drives (for example for the swap file) and so cannot CHKDSK them once OS/2 is running.

2) to update DLLs

As described by Adrian Thomson in the last issue of Pointers you can write DLLs which OS/2 treats as though they are part of the operating system. This is fine...until you wish to update one such DLL with a new version - you can't update the file while OS/2 has the DLL loaded. Even worse, if you have a bug in the DLL then PM may never initialise.

3) to save/restore PM configurations

PM saves its configuration in files (OS2.INI for OS/2 1.1, OS2.INI and OS2SYS.INI for OS/2 1.2) which remain assigned while PM is running. I feel much happier with a backup copy of these important files, and sometimes need to replace the current copy with a backup.

4) to remove bad device drivers

It is very hard to write device drivers without sometimes putting in a little bug causing the driver to abort during initialisation or shortly after! Once this occurs you can't continue, often you must reboot from diskette and remove the device driver before you can load OS/2 again.

In this article I will describe and discuss a simple device driver which I find very useful to have on my machine to cater for these instances. This device driver allows you to enter an OS/2 full screen session BEFORE the system is initialised.

This means that:

  1. only the C: drive is in use so I can CHKDSK the others (even the swapper hasn't started at this point)
  2. Only the base OS/2 is running so new copies of 'system' DLLs and detached programs can be copied into the right places
  3. PM is not running so OS2.INI can be copied to and from without problem
  4. Not all device drivers are loaded, so a faulty one can be deleted or updated before it is loaded!

An additional reason for using this device driver is because it can be instructive to examine the system while it initialises. You may be one of those people who need to know a bit more about how OS/2 hangs together, or you may just be curious! If you possess one of the various memory analysis programs for OS/2 it can be instructive to see what gets loaded when, and what state various subsystems are in.

Although I now USE the device driver fairly often for the four reasons listed above, I of course initially WROTE it because I was curious! I had two questions in my mind; the first was 'can I write a device driver entirely in C?' and the second was 'which OS/2 functions are available during system initialisation?'

Overview of the device driver

How does this device driver work? First a brief reminder for the rusty-minded among you of how OS/2 initialises.

OS/2 loading starts with the kernel and the 'base' device drivers (these are the separate device drivers such as DISK01.SYS, etc. under OS/2 1.1 and the single merged device driver such as basedd01.sys under OS/2 1.2). The configuration file CONFIG.SYS is then processed. However this is NOT a one-pass process - for example all the SET commands have been processed before any DEVICE= statements are processed. All the DEVICE= and IFS= statements in the configuration are saved up and done towards the end of the initialisation. At this point the system environment is essentially complete. Each device driver is loaded and initialised in the order it was listed.

Finally the PROTSHELL and RUN statements are executed and the system grinds into life!

Device driver initialisation is performed by OS/2 loading the device driver as a dynamic link library and calling the initial entry point. The thread runs as a thread of the startup process and is like a normal OS/2 thread (with a few differences, of course, such as privilege level!) During initialisation the device driver code has a dual personality - it can do 'device driver' like things AND regular OS/2 like things.

The CDD.SYS device driver uses this feature to perform a simple OS/2 program task - it merely waits up to 5 seconds for a key to be pressed, and if ESCAPE is pressed then it calls DosExecPgm() to start a full screen command prompt. When this command processor finishes (by the user typing EXIT), the device completes its initialisation.

The initialisation process carries on with subsequent device drivers as if nothing has happened, the system manager is started and the rest is history...

NOTE: if you are using an installable file system or RAM disk then you need to ensure that this device driver is in CONFIG.SYS after the ifs=c:\os2\hpfs.ifs and device=c:\os2\vdisk.sys statements. If you don't do this then the HPFS drive and/or the RAM drive will not be accessible!

Detailed comments on the device driver code

The device driver is written entirely in C (I use Microsoft C5.1 or C6.0, but I expect IBM C/2 is OK too), which is easy to do because it isn't "really" a device driver at all! To write a less trivial device driver in C would involve either inline assembler or a subroutine library. Note the various compiler/language 'work arounds' for this sample code.

Firstly C5.1 generates incorrect code for a FAR function with the _saveregs keyword, so I have NEAR function which does the work (C6.0 doesn't need this fix). I wish to save all registers since then I can load ES and BX off the stack without needing to use assembler!

Secondly, a device driver header ought to contain the offset of the strategy routine followed by the offset of the inter-device communication routine. I can't find a way to compile a static structure containing only the OFFSET of a far function, so I merged the strategy and IDC routine addresses into a single field called strategy! This won't cause any problems becuase the bit in the flag word telling OS/2 that IDC is enabled is not set so the selector value won't be treated as an IDC offset!

Lastly I define '_acrtused' to prevent any C runtime functions being called in. You CAN in fact use some of them in C device drivers, but you need to take care during linking to ensure that the device header stays at the front of the data segment and that the DATA segment comes BEFORE the CODE segments. The .DOSSEG assembler directive in some of the C runtime functions prevents them being used as this directive forces code to preceed data! The moral is to examine the MAP file from the linker to check everything looks OK (and, of course, to have a working copy of the CDD device driver in your CONFIG.SYS to allow you to recover from failed device driver initialisation!)

The device driver returns error for all commands other than INIT. An alternative approach would be to uninstall the device driver but doing this gives a nasty error message during system initialisation!

I found by experiment that the KdbPeek() call was required to get the subsequent KbdCharIn() to work properly - presumably the keyboard subsystem isn't quite ready for us yet!

There is a documented list of 'officially' supported APIs available to device driver initialisation code. DosExecPgm() is NOT on the list. However I take the form of the wording ("Those dynamic link functions available to a device driver a INIT-time include...") to mean that IBM will guarantee that, for example, DosOpen() will work but that if DosExecPgm() causes problems then they'll just say "it's not a documented feature". It works fine under 1.1 and 1.2 so I don't expect any problems... but you have been warned!

Creating and installing the CDD.SYS file

The compile command to do the trick is:

cl /W3 cdd.c cdd.def /Fecdd.sys

where the CDD.DEF file contains information for the linker to make a loadable device driver. (See below.) I recommend copying the device driver to a separate directory on drive C: and then you add it to CONFIG.SYS (for example you might copy the CDD.SYS file to c:\drivers and add the line "device=c:\drivers\cdd.sys" to your configuration file. You might want to experiment with the location relative to other device drivers and see how much or the system you want to be running when you start the full screen command prompt.

Conclusion

OS/2 initialisation, like so much of OS/2, benefits from the open design of the operating system. Using simple device drivers like this one enables you to customise the initialisation process to suit your requirements without needing to write very much specialised code.

Module Definition File CDD.DEF for the C Device Driver

LIBRARY CDD             ; Specifies CDD as a dynamic link module

PROTMODE                ; Runs only in protect mode 

SEGMENTS                ; make DATA to precede CODE
      _DATA   CLASS   'DATA'
      CONST   CLASS   'CONST'
      _BSS    CLASS   'BSS'

The source code CDD.C for the C Device Driver

#define         INCL_BASE
#include        <os2.h>

/* forward references */
void far start(void);
void _saveregs near realstart(int i);
void initproc(void);

/* structure of an INIT device packet */
typedef struct _PKT
  {
  char PktLen;       /* Length in bytes of packet          */
  char PktUnit;      /* Subunit number of block device     */
  char PktCmd;       /* Command code                       */
  int PktStatus;     /* Status word                        */
  long PktDOSLink;   /* Reserved                           */
  long PktDevLink;   /* Device multiple-request link       */
  char PktData[1];   /* Data pertaining to specific packet */
  int codesize;      /* code size on INIT return           */
  int datasize;      /* data size on INIT return           */
  } PKT;

/* definitions of device driver return status */
#define done  0x0100              /* Word representing the code for DONE     */
#define error 0x8000              /* error return                            */
#define general_failure 0xc       /* aaargh!                                 */


/* structure of device header */
typedef struct _DEVHDR
  {
  PSZ ptr_to_next;               /* points to next device driver            */
                                 /* set to -1 - OS will fill it in          */
  int attributes;                /* attributes of device driver             */
  PFN strategy;                  /* pointer to strategy routine             */
                                 /* + the ignored SEG part of strategy!     */
  char name[8];                  /* name of the device                      */
  int rsvd[4];                   /* used by OS/2                            */
  } DEVHDR;

/* definitions of some of the bits in the attribute word */
#define  devlev_1       0x0080      /* Bits 7-9 - OS/2 1.x                   */
#define  dev_char_dev   0x8000      /* Bit 15 - Device is a character device */
#define  dev_open       0x0800      /* Bit 11 - Accepts Open/Close/Removable */


/* define this device */
static DEVHDR devhdr =
  {
  (PSZ)-1L,
  devlev_1 | dev_char_dev | dev_open,
  (PFN)start,
  "CDD$    "
  };


/* definitions of the command values used */
#define  INIT             0


/* define _acrtused to prevent the C runtime startup code being linked in */
int _acrtused = 0;


/* device driver entry point */
/* ES:BX points to the device driver request packet */
void far start(void)
  {
  /* call the 'real' entry point */
  realstart(0);
  }


/* the 'real' device driver code - work around for C5.1 bug described in the text */
void _saveregs near realstart(int dummy)
  {
  PKT far *pkt;
  int *regptr;

#define ES -11                    /* offset for ES in saved registers        */
#define BX -5                     /* offset for BX in saved registers        */

  regptr = &dummy;               /* get address of top of saved registers   */

  pkt = MAKEP(regptr[ES], regptr[BX]);

  /* if not init command, return failure! */
  if (pkt->PktCmd != INIT)
     {
     pkt->PktStatus = done + error + general_failure;
     return;
     }

  /* perform the initialisation function */
  initproc();

  /* save code + data size to initialise as a device driver */
  pkt->codesize = (int)initproc;
  pkt->datasize = sizeof(DEVHDR);
  pkt->PktStatus = done;
  }


/* INITIALISATION CODE STARTS HERE */
void initproc(void)
  {
static   KBDKEYINFO kbci;
  RESULTCODES res;
  int i;

  /* initial read to kick things off - seems needed to wake up KBD subsystem */
  KbdPeek(&kbci, 0);

  /* tell the world we're ready */
  VioWrtTTY("Press ESC for command prompt...", 31, 0);

  /* wait up to 5 seconds for a keypress then carry on */
  for (i = 0; i < 10; i++)
     {
     DosSleep(500L);

     /* drop out on key press */
     if ((KbdCharIn(&kbci, IO_NOWAIT, 0) == 0) && (kbci.fbStatus & 0x40))
        break;
     }

  /* check for ESC, which kicks off CMD.EXE */
  if (kbci.chChar == '\033')
     {
     DosExecPgm(NULL, 0, EXEC_SYNC,
             "cmd\0",            /* no arguments                            */
             NULL,               /* inherit the environment                 */
             &res,               /* resultcodes                             */
             "C:\\os2\\cmd.exe");  /* program name                          */
     }
  }

Roger Orr 21-Aug-1990