Jump to content

Rebooting OS/2: Difference between revisions

From EDM2
No edit summary
Ak120 (talk | contribs)
 
(7 intermediate revisions by the same user not shown)
Line 1: Line 1:
Written by [[Roman Stangl]]
''Written by [[Roman Stangl]]''


=== Using SETBOOT.EXE ===
== 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 of starting another session, as you can easily do the same yourself.


OS/2 may be rebooted by using the '''SETBOOT''' utility, that is shipped
== Using the DOS.SYS device driver ==
with OS/2.  However when adding a reboot feature in your application you
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).
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.


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 <tt>DEVICE=d:\OS2\DOS.SYS<(/tt>.
*For OS/2 Warp, add the line <tt>DEVICE=d:\OS2\BOOT\DOS.SYS</tt>.
Now you should be able to add a reboot function by a few additions to your code. To access the device driver <tt>DOS.SYS</tt> you have to open the device '''DOS$''' as shown below. 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,
   apiretDosDD=DosOpen("\\DEV\\DOS$", &hfileDosDD, &ulAction, 0,
       FILE_NORMAL, OPEN_ACTION_OPEN_IF_EXISTS, OPEN_SHARE_DENYNONE |
       FILE_NORMAL, OPEN_ACTION_OPEN_IF_EXISTS, OPEN_SHARE_DENYNONE |
       OPEN_ACCESS_READWRITE, 0); if(apiretDosDD!=NO_ERROR)
       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. Below listing shows the call to '''DosShutdown()''', though it's quite well documented in the online reference.
 
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 */
  /* Shutdown the system anyway */
  DosShutdown(0);
  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 unlikely, I personally have never experienced such a problem).


Having shut down OS/2, you might again inform the user what's going on.
You jump to the reboot vector by calling a function of the '''DOS$''' device:
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,
   if(NO_ERROR!=DosDevIOCtl(hfileDosDD,
                           IOCTL_DOS,
                           IOCTL_DOS,
Line 67: Line 30:
                           0,
                           0,
                           NULL)) ...
                           NULL)) ...
 
Last but not least, the Listing below shows the parameters for the undocumented interface of the '''DOS$''' device to call the reboot vector,
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 IOCTL_DOS                  0x00D5
   #define DOS_REBOOT                  0x00AB
   #define DOS_REBOOT                  0x00AB
In case something didn't work, you also want to close the '''DOS$''' device, and inform the user.


In case something didn't work you also want to close the '''DOS$'''
== Using IOPL segments ==
device, and inform the user.
Every workstation can also be rebooted by programming the keyboard controller and for at least Microchannel PCs 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.


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


Every workstation can also be rebooted by programming the keyboard
Let's start by a short look into the listing below, which shows the assembler code to reboot a workstation by trying to force activation of the reset line:
controller and for at least MicroChannel PC's also by using the system
#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 than 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.
control port A.  The programming requires I/O and therefore must be done
#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.
from a ring-2 IOPL segment (unless you are using the advanced methods to
;Note:Though OS/2 code is presented here, it's quite easy to assemble a DOS version, by changing Assembler switches.
access I/Os, that is device drivers, call gates or ring-3 IOPL segments
<code>
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.
;*                              PC2.c
ThinkPads seem to ignore this completely, most likely due to some power
;*        Copyright (C) by Stangl Roman, 1993, 1994, 1995
management hardware, I know that the code is processed for sure because I
;* This Code may be freely distributed, provided the Copyright isn't
traced processing to the I/O instructions with the kernel debugger) and
;* removed, under the conditions indicated in the documentation.
IOPL segments are non portable 16 bit code that is likely to be
;*
unsupported in the future.
;* Reboot.asm  IOPL segment to reboot OS/2 by trying to hardware
 
;*              reset the system by system control port A and by
Anyway, implementing it I found the solution of some interesting
;*              programming a keyboard controller reset.
problems, so I've included it here too.
;*              Unfortunately this does not work always, e.g. doesn't
 
;*              work on a IBM ThinkPad 701, doesn't work reliably on
Let's start by a short look into listing bellow which shows the assembler
;*              a PS/2 77i but perfectly works on a IBM PC 750. I
code to reboot a workstation by trying to force activation of the reset
;*              think this is due to a hardware incompatibility to
line:
;*              support features as APM (Advanced Power Management)
 
;*
1. First, reset is tried via the system control port A. This will likely
;* Use the function prototype:
work only on MicroChannel machines, but on such machines it would be the
;*
fastest method, much faster then using the keyboard controller. This is
;* extern USHORT _Far16 _Pascal RebootSystem(USHORT usPerformReset);
an example of superior technology introduced with the MicroChannel
;*
architecture, which unfortunately has been killed by IBM's inability to
;*
market such advantages.
;* Add to your module definition file:
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
;* SEGMENTS
worked, but if it worked execution is stopped by the hardware reset
;*            CODEIOPL CLASS 'CODE' IOPL PRELOAD
anyway.
;*
 
;* EXPORTS
'''Note:''' Though OS/2 code is presented here, it's quite easy to
;*            RebootSystem=REBOOTSYSTEM  1
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'
CODEIOPL        SEGMENT PARA PUBLIC USE16 'CODE'
                  ASSUME  CS:CODEIOPL, DS:NOTHING
                ASSUME  CS:CODEIOPL, DS:NOTHING
   
   
          db      "@(#) $Header: Reboot.asm Version 2.00 05,1995 $ (LBL)"
        db      "@(#) $Header: Reboot.asm Version 2.00 05,1995 $ (LBL)"
   
   
  ; MACRO to let the processor loop after IN, OUT instructions
; MACRO to let the processor loop after IN, OUT instructions
  IOWAIT  MACRO
IOWAIT  MACRO
      Local  IOW_Loop
    Local  IOW_Loop
          JMP    $+2
        JMP    $+2
          PUSH    CX
        PUSH    CX
          MOV    CX,010h
        MOV    CX,010h
  IOW_Loop:
IOW_Loop:
          LOOP    IOW_Loop
        LOOP    IOW_Loop
          POP    CX
        POP    CX
  ENDM
ENDM
   
   
    ; System control port A (see PS/2 technical references)
  ; System control port A (see PS/2 technical references)
  CONTROLPORTA            EQU 092h
CONTROLPORTA            EQU 092h
    ; AND mask to toggle reset bit (bit 0) of control port A off
  ; AND mask to toggle reset bit (bit 0) of control port A off
  RESETOFF                EQU 0FEh
RESETOFF                EQU 0FEh
    ; OR mask to toggle reset bit (bit 0) of control port A on
  ; OR mask to toggle reset bit (bit 0) of control port A on
  RESETON                EQU 001h
RESETON                EQU 001h
   
   
    ; Keyboard controller status port (reading)
  ; Keyboard controller status port (reading)
  KBDSTATUS              EQU 064h
KBDSTATUS              EQU 064h
    ; Input buffer full
  ; Input buffer full
  INPUTBUFFERFULL        EQU 002h
INPUTBUFFERFULL        EQU 002h
    ; Keyboard command pulse output port
  ; Keyboard command pulse output port
  CMDPULSEOUTPUTPORT      EQU 0FEh
CMDPULSEOUTPUTPORT      EQU 0FEh
   
   
  Stackframe      struc      ; Structure for arguments passed on stack
Stackframe      struc      ; Structure for arguments passed on stack
                  dw      0h      ;  BP+00 : Saved BP
                dw      0h      ;  BP+00 : Saved BP
                  dw      0h      ;  BP+02 : IP of return address
                dw      0h      ;  BP+02 : IP of return address
                  dw      0h      ;  BP+04 : CS of return address
                dw      0h      ;  BP+04 : CS of return address
          arg1    dw      0h      ;  BP+06 : First parameter passed
        arg1    dw      0h      ;  BP+06 : First parameter passed
          arg2    dw      0h      ;  BP+08 : Second parameter passed
        arg2    dw      0h      ;  BP+08 : Second parameter passed
  Stackframe      ends
Stackframe      ends
   
   
  ;*-----------------------------------------------------------------*\
;*-----------------------------------------------------------------*\
  ;* This function programs the system control port A and the keyboard
;* This function programs the system control port A and the keyboard
  ;* controller to reset the system via activation the reset line (both
;* controller to reset the system via activation the reset line (both
  ;* ways are used to increase the probability it works). This function
;* ways are used to increase the probability it works). This function
  ;* must be in an segment having IOPL privileges because the control
;* must be in an segment having IOPL privileges because the control
  ;* port A and the keyboard controller is programmed via I/O
;* port A and the keyboard controller is programmed via I/O
  ;* instructions.
;* instructions.
  ;*-----------------------------------------------------------------*/
;*-----------------------------------------------------------------*/
   
   
  PUBLIC  REBOOTSYSTEM    ; USHORT RebootSystem(USHORT argument 1)
PUBLIC  REBOOTSYSTEM    ; USHORT RebootSystem(USHORT argument 1)
  REBOOTSYSTEM    PROC    FAR
REBOOTSYSTEM    PROC    FAR
          PUSH    BP
        PUSH    BP
          MOV    BP, SP
        MOV    BP, SP
          MOV    AX, Stackframe.arg1[BP] ; Access argument 1
        MOV    AX, Stackframe.arg1[BP] ; Access argument 1
          XOR    AX, 0FFFFh              ; Return failure
        XOR    AX, 0FFFFh              ; Return failure
          CMP    AX, 0FFFFh
        CMP    AX, 0FFFFh
            ; ? If argument 1 is FALSE return with failure
          ; ? If argument 1 is FALSE return with failure
            ;  to allow caller to call our code for diagnostics
          ;  to allow caller to call our code for diagnostics
           JE     RS_Return
        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
          IN      AL, CONTROLPORTA      ; Get system control port A
    END
          IOWAIT
</code>
          MOV    CX, 01000h              ; Retry counter
You may note that an argument is passed to this ring-2 function call. This will be explained in the listing below in more detail.
  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.


Many articles have already covered calling ring-2 code from ring-3, so the listing is only included for completeness.
   SEGMENTS
   SEGMENTS
               CODEIOPL CLASS 'CODE' IOPL PRELOAD
               CODEIOPL CLASS 'CODE' IOPL PRELOAD
   EXPORTS
   EXPORTS
               RebootSystem=REBOOTSYSTEM  1
               RebootSystem=REBOOTSYSTEM  1
The most interesting thing is shown in the listing below. You will surely notice, that '''RebootSystem()''' is called twice, once with the parameter '''FALSE''' and once with '''TRUE'''.


The most interesting thing is shown in listing bellow.  You will surely
'''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.
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
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.
reset.


Finally, '''RebootSystem(TRUE)''' is called to perform the hardware reset.
   /* Call function RebootSystem() from our IOPL segment
   /* Call function RebootSystem() from our IOPL segment
     to ensure that all necessary code gets loaded now,
     to ensure that all necessary code gets loaded now,
     because after DosShutdown(), no disk access is allowed
     because after DosShutdown(), no disk access is allowed
     anymore. Leaving out this call prevents the reboot
     any more. Leaving out this call prevents the reboot
     from working (above explanation is what I assume,
     from working (above explanation is what I assume,
     I don't know it exactly) */
     I don't know it exactly) */
Line 284: Line 194:
   DosShutdown(0);
   DosShutdown(0);
   RebootSystem(TRUE);
   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 of blocking the message queue) and you are right, but once you have called '''DosShutdown()''' user input doesn't make much sense anyway.


I've excluded the code that informed the user on a dialog about the
== Credits ==
progress, and some '''DosSleep''' calls that gave the user some time to
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 various IBM technical references.
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.


[[Category:Languages Articles]]
[[Category:Languages Articles]]

Latest revision as of 20:52, 22 August 2022

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 of 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<(/tt>.
  • 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 below. 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. Below listing shows the call to DosShutdown(), though it's 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 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, the Listing below 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 PCs 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 the listing below, 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 than 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 the listing below in more detail.

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

 SEGMENTS
             CODEIOPL CLASS 'CODE' IOPL PRELOAD
 EXPORTS
             RebootSystem=REBOOTSYSTEM   1

The most interesting thing is shown in the listing below. 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
    any more. 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 of blocking the message queue) and you are right, but once you have called DosShutdown() user input doesn'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 various IBM technical references.