Rebooting OS/2: Difference between revisions
No edit summary |
|||
(8 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
Written by [[Roman Stangl]] | ''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). | |||
You can easily implement the way '''SETBOOT''' works, you just need to | |||
open the device '''DOS$''' and make an IOCTL | |||
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 <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()'''. | |||
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. | |||
call to '''DosShutdown()''', though | |||
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). | |||
You jump to the reboot vector by calling a function of the '''DOS$''' device: | |||
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 | |||
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. | |||
== 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. | |||
Every workstation can also be rebooted by programming the keyboard | |||
controller and for at least | |||
control port A. | |||
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. | |||
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. | |||
'''Note: | 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: | ||
assemble a DOS version, by changing Assembler switches. | #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. | ||
#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. | |||
<code> | |||
;******************************************************************** | |||
;* 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 | |||
</code> | |||
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. | |||
You may note that an argument is passed to this ring-2 function call. | |||
This will be explained in listing | |||
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'''. | |||
'''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. | |||
'''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 | 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. | ||
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 | /* 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 | ||
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. | |||
== 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. | |||
[[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:
- 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.
- 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.