Rebooting OS/2

From EDM2
Jump to: navigation, search

Written by Roman Stangl

Using SETBOOT.EXE

OS/2 may be rebooted by using the SETBOOT utility, that is shipped with OS/2. However when adding a reboot feature in your application you may want to avoid the effort starting another session, as you can easily do the same yourself.

Using the DOS.SYS device driver

You can easily implement the way SETBOOT works, you just need to open the device DOS$ and make an IOCTL-call, however this call is undocumented. Both, the following code using DOS$ and SETBOOT, require that the device driver DOS.SYS is included in your CONFIG.SYS (as you can only get access to the reboot-vector from ring-3 by a device driver, and DOS.SYS provides that functionality).

If you have not installed the device driver DOS.SYS (which for example the IBM EWS BOOTOS2 package doesn't), add it to your CONFIG.SYS (where d: is your boot drive):

  • For OS/2 2.11 and before add the line DEVICE=d:\OS2\DOS.SYS.
  • For OS/2 Warp add the line DEVICE=d:\OS2\BOOT\DOS.SYS.

Now you should be able to add a reboot function by a few additions to your code. To access the device driver DOS.SYS you have to open the device DOS$ as shown bellow. You want to check the return code, as you should inform the user or write a log, if the device couldn't be opened.

 apiretDosDD=DosOpen("\\DEV\\DOS$", &hfileDosDD, &ulAction, 0,
     FILE_NORMAL, OPEN_ACTION_OPEN_IF_EXISTS, OPEN_SHARE_DENYNONE |
     OPEN_ACCESS_READWRITE, 0); if(apiretDosDD!=NO_ERROR)
     ...

Once you have opened the DOS$ device, you might inform the user that a ShutDown is in progress and finally shut down the file system by calling the API DosShutdown(). DosShutdown() will not stop processes from running, but file system calls will no longer work (therefore we had to open the device DOS$ before, as DosOpen is a file system call). Not having tested it, I would say threads making file system calls will either simply block or fail. Bellow listing shows the call to DosShutdown(), though its quite well documented in the online reference.

/* Shutdown the system anyway */
DosShutdown(0);

Having shut down OS/2, you might again inform the user what's going on. You can do this for example by updating a text in a message box, just ensure that this text is already loaded into memory (and hope that is hasn't been paged out of memory, but this is very very unlikely, I personally have never experienced such a problem).

You jump to the reboot vector by calling a function of the DOS$ device:

 if(NO_ERROR!=DosDevIOCtl(hfileDosDD,
                          IOCTL_DOS,
                          DOS_REBOOT,
                          NULL,
                          0,
                          NULL,
                          NULL,
                          0,
                          NULL)) ...

Last but not least, Listing bellow shows the parameters for the undocumented interface of the DOS$ device to call the reboot vector,

 #define IOCTL_DOS                   0x00D5
 #define DOS_REBOOT                  0x00AB

In case something didn't work you also want to close the DOS$ device, and inform the user.

Using IOPL segments

Every workstation can also be rebooted by programming the keyboard controller and for at least MicroChannel PC's also by using the system control port A. The programming requires I/O and therefore must be done from a ring-2 IOPL segment (unless you are using the advanced methods to access I/Os, that is device drivers, call gates or ring-3 IOPL segments which have been described in other issues of EDM/2. I strongly discourage using this approach, because for me it didn't work reliably (e.g. ThinkPads seem to ignore this completely, most likely due to some power management hardware, I know that the code is processed for sure because I traced processing to the I/O instructions with the kernel debugger) and IOPL segments are non portable 16 bit code that is likely to be unsupported in the future.

Anyway, implementing it I found the solution of some interesting problems, so I've included it here too.

Let's start by a short look into listing bellow which shows the assembler code to reboot a workstation by trying to force activation of the reset line:

  1. First, reset is tried via the system control port A. This will likely work only on MicroChannel machines, but on such machines it would be the fastest method, much faster then using the keyboard controller. This is an example of superior technology introduced with the MicroChannel architecture, which unfortunately has been killed by IBM's inability to market such advantages.
  2. If reset via the system control port A didn't work, system reset is tried via the keyboard controller. One doesn't know if the first method worked, but if it worked execution is stopped by the hardware reset anyway.

Note: Though OS/2 code is presented here, it's quite easy to assemble a DOS version, by changing Assembler switches.

 ;********************************************************************
 ;*                              PC2.c
 ;*         Copyright (C) by Stangl Roman, 1993, 1994, 1995
 ;* This Code may be freely distributed, provided the Copyright isn't
 ;* removed, under the conditions indicated in the documentation.
 ;*
 ;* Reboot.asm   IOPL segment to reboot OS/2 by trying to hardware
 ;*              reset the system by system control port A and by
 ;*              programming a keyboard controller reset.
 ;*              Unfortunately this does not work always, e.g. doesn't
 ;*              work on a IBM ThinkPad 701, doesn't work reliably on
 ;*              a PS/2 77i but perfectly works on a IBM PC 750. I
 ;*              think this is due to a hardware incompatibility to
 ;*              support features as APM (Advanced Power Management)
 ;*
 ;* Use the function prototype:
 ;*
 ;* extern USHORT _Far16 _Pascal RebootSystem(USHORT usPerformReset);
 ;*
 ;*
 ;* Add to your module definition file:
 ;*
 ;* SEGMENTS
 ;*             CODEIOPL CLASS 'CODE' IOPL PRELOAD
 ;*
 ;* EXPORTS
 ;*             RebootSystem=REBOOTSYSTEM   1
 ;*
 ;********************************************************************

 CODEIOPL        SEGMENT PARA PUBLIC USE16 'CODE'
                 ASSUME  CS:CODEIOPL, DS:NOTHING

         db      "@(#) $Header: Reboot.asm Version 2.00 05,1995 $ (LBL)"

 ; MACRO to let the processor loop after IN, OUT instructions
 IOWAIT  MACRO
     Local   IOW_Loop
         JMP     $+2
         PUSH    CX
         MOV     CX,010h
 IOW_Loop:
         LOOP    IOW_Loop
         POP     CX
 ENDM

   ; System control port A (see PS/2 technical references)
 CONTROLPORTA            EQU 092h
   ; AND mask to toggle reset bit (bit 0) of control port A off
 RESETOFF                EQU 0FEh
   ; OR mask to toggle reset bit (bit 0) of control port A on
 RESETON                 EQU 001h

   ; Keyboard controller status port (reading)
 KBDSTATUS               EQU 064h
   ; Input buffer full
 INPUTBUFFERFULL         EQU 002h
   ; Keyboard command pulse output port
 CMDPULSEOUTPUTPORT      EQU 0FEh

 Stackframe      struc      ; Structure for arguments passed on stack
                 dw      0h       ;   BP+00 : Saved BP
                 dw      0h       ;   BP+02 : IP of return address
                 dw      0h       ;   BP+04 : CS of return address
         arg1    dw      0h       ;   BP+06 : First parameter passed
         arg2    dw      0h       ;   BP+08 : Second parameter passed
 Stackframe      ends

 ;*-----------------------------------------------------------------*\
 ;* This function programs the system control port A and the keyboard
 ;* controller to reset the system via activation the reset line (both
 ;* ways are used to increase the probability it works). This function
 ;* must be in an segment having IOPL privileges because the control
 ;* port A and the keyboard controller is programmed via I/O
 ;* instructions.
 ;*-----------------------------------------------------------------*/

 PUBLIC  REBOOTSYSTEM     ; USHORT RebootSystem(USHORT argument 1)
 REBOOTSYSTEM    PROC    FAR
         PUSH    BP
         MOV     BP, SP
         MOV     AX, Stackframe.arg1[BP] ; Access argument 1
         XOR     AX, 0FFFFh              ; Return failure
         CMP     AX, 0FFFFh
           ; ? If argument 1 is FALSE return with failure
           ;   to allow caller to call our code for diagnostics
         JE      RS_Return


         IN      AL, CONTROLPORTA      ; Get system control port A
         IOWAIT
         MOV     CX, 01000h              ; Retry counter
 RS_SystemReset:
         AND     AL, RESETOFF  ; Toggle reset bit 0 off, on and off
         OUT     CONTROLPORTA, AL
         IOWAIT
         OR      AL, RESETON
         OUT     CONTROLPORTA, AL
         IOWAIT
         AND     AL, RESETOFF
         OUT     CONTROLPORTA, AL
         IOWAIT
         LOOP    RS_SystemReset          ; Try a few times

 RS_WaitForKbd:
         MOV     CX, 01000h              ; Retry counter
         IN      AL, KBDSTATUS           ; Get keyboard status
         IOWAIT
         TEST    AL, INPUTBUFFERFULL
           ; ? If keyboard buffer is empty, reset system
         JZ      RS_PulseReset
         IOWAIT
         LOOP    RS_WaitForKbd
         MOV     AX, 0FFFFh              ; Return failure
         JMP     RS_Return
 RS_PulseReset:
         MOV     AL, CMDPULSEOUTPUTPORT  ; Pulse system reset port
         OUT     KBDSTATUS, AL
         IOWAIT
         MOV     AX, 0h                  ; Return success
         JMP     RS_WaitForKbd

 RS_Return:
         POP     BP
         RET     2             ; Clear stack from parameters passed
 REBOOTSYSTEM    ENDP

 CODEIOPL        ENDS
    END

You may note that an argument is passed to this ring-2 function call. This will be explained in listing bellow in more detail.

Many articles have already covered calling ring-2 code from ring-3, so listing is only included for completeness.

 SEGMENTS
             CODEIOPL CLASS 'CODE' IOPL PRELOAD

 EXPORTS
             RebootSystem=REBOOTSYSTEM   1

The most interesting thing is shown in listing bellow. You will surely notice, that RebootSystem() is called twice, once with the parameter FALSE and once with TRUE. RebootSystem(FALSE) calls the code in the IOPL segment without actually doing I/O programming to reset your workstation, however calling code in the IOPL segment ensures that the code is in memory, that is not paged out into the swapper or not loaded at all.

If we didn't do this, DosShutdown() would flush and close the file system, and if the IOPL segment wasn't in memory before calling DosShutdown() it would never be paged in or loaded. Of course, as only device drivers can lock memory, there is the possibility, that the IOPL segment gets paged out between the calls to RebootSystem(FALSE) and DosShutdown() but I would expect this to be very unlikely.

Finally, RebootSystem(TRUE) is called to perform the hardware reset.

 /* Call function RebootSystem() from our IOPL segment
    to ensure that all necessary code gets loaded now,
    because after DosShutdown(), no disk access is allowed
    anymore. Leaving out this call prevents the reboot
    from working (above explanation is what I assume,
    I don't know it exactly) */
 RebootSystem(FALSE);
 DosShutdown(0);
 RebootSystem(TRUE);

I've excluded the code that informed the user on a dialog about the progress, and some DosSleep calls that gave the user some time to read messages. You may argue that DosSleep in a dialog procedure blocks PM (because blocking the message queue) and you are right, but once you have called DosShutdown() user input doen't make much sense anyway.

Credits

The undocumented DOS$ call has been published in the OS/2 development fora on the IBM PC conference disk and the InterNet NetNews. IPLing a PC through the system control port A and the keyboard controller has been published in verious IBM technical references.