1 /*
2  * COPYRIGHT:       GPLv2+ - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/dos/dos32krnl/device.c
5  * PURPOSE:         DOS Device Support
6  * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "ntvdm.h"
12 
13 #define NDEBUG
14 #include <debug.h>
15 
16 #include "emulator.h"
17 #include "cpu/bop.h"
18 #include "device.h"
19 
20 #include "dos.h"
21 #include "dos/dem.h"
22 #include "memory.h"
23 
24 /* PRIVATE VARIABLES **********************************************************/
25 
26 static const BYTE StrategyRoutine[] = {
27     LOBYTE(EMULATOR_BOP),
28     HIBYTE(EMULATOR_BOP),
29     BOP_DOS,
30     BOP_DRV_STRATEGY,
31     0xCB // retf
32 };
33 
34 static const BYTE InterruptRoutine[] = {
35     LOBYTE(EMULATOR_BOP),
36     HIBYTE(EMULATOR_BOP),
37     BOP_DOS,
38     BOP_DRV_INTERRUPT,
39     0xCB // retf
40 };
41 
42 C_ASSERT((sizeof(StrategyRoutine) + sizeof(InterruptRoutine)) == DEVICE_CODE_SIZE);
43 
44 static LIST_ENTRY DeviceList = { &DeviceList, &DeviceList };
45 static PDOS_REQUEST_HEADER DeviceRequest;
46 
47 /* PRIVATE FUNCTIONS **********************************************************/
48 
49 static VOID DosCallDriver(DWORD Driver, PDOS_REQUEST_HEADER Request)
50 {
51     PDOS_DRIVER DriverBlock = (PDOS_DRIVER)FAR_POINTER(Driver);
52     WORD AX = getAX();
53     WORD CX = getCX();
54     WORD DX = getDX();
55     WORD BX = getBX();
56     WORD BP = getBP();
57     WORD SI = getSI();
58     WORD DI = getDI();
59     WORD DS = getDS();
60     WORD ES = getES();
61 
62     /* Set ES:BX to the location of the request */
63     setES(DOS_DATA_SEGMENT);
64     setBX(DOS_DATA_OFFSET(Sda.Request));
65 
66     /* Copy the request structure to ES:BX */
67     RtlMoveMemory(&Sda->Request, Request, Request->RequestLength);
68 
69     /* Call the strategy routine, and then the interrupt routine */
70     RunCallback16(&DosContext, MAKELONG(DriverBlock->StrategyRoutine , HIWORD(Driver)));
71     RunCallback16(&DosContext, MAKELONG(DriverBlock->InterruptRoutine, HIWORD(Driver)));
72 
73     /* Get the request structure from ES:BX */
74     RtlMoveMemory(Request, &Sda->Request, Request->RequestLength);
75 
76     /* Restore the registers */
77     setAX(AX);
78     setCX(CX);
79     setDX(DX);
80     setBX(BX);
81     setBP(BP);
82     setSI(SI);
83     setDI(DI);
84     setDS(DS);
85     setES(ES);
86 }
87 
88 static inline WORD NTAPI DosDriverReadInternal(PDOS_DEVICE_NODE DeviceNode,
89                                                DWORD Buffer,
90                                                PWORD Length,
91                                                BOOLEAN IoControl)
92 {
93     DOS_RW_REQUEST Request;
94 
95     Request.Header.RequestLength = IoControl ? sizeof(DOS_IOCTL_RW_REQUEST)
96                                              : sizeof(DOS_RW_REQUEST);
97     Request.Header.CommandCode = IoControl ? DOS_DEVCMD_IOCTL_READ : DOS_DEVCMD_READ;
98     Request.BufferPointer = Buffer;
99     Request.Length = *Length;
100 
101     DosCallDriver(DeviceNode->Driver, &Request.Header);
102 
103     *Length = Request.Length;
104     return Request.Header.Status;
105 }
106 
107 static inline WORD NTAPI DosDriverWriteInternal(PDOS_DEVICE_NODE DeviceNode,
108                                                 DWORD Buffer,
109                                                 PWORD Length,
110                                                 BOOLEAN IoControl)
111 {
112     DOS_RW_REQUEST Request;
113 
114     Request.Header.RequestLength = IoControl ? sizeof(DOS_IOCTL_RW_REQUEST)
115                                              : sizeof(DOS_RW_REQUEST);
116     Request.Header.CommandCode = IoControl ? DOS_DEVCMD_IOCTL_WRITE : DOS_DEVCMD_WRITE;
117     Request.BufferPointer = Buffer;
118     Request.Length = *Length;
119 
120     DosCallDriver(DeviceNode->Driver, &Request.Header);
121 
122     *Length = Request.Length;
123     return Request.Header.Status;
124 }
125 
126 static inline WORD NTAPI DosDriverGenericRequest(PDOS_DEVICE_NODE DeviceNode,
127                                                  BYTE CommandCode)
128 {
129     DOS_REQUEST_HEADER Request;
130 
131     Request.RequestLength = sizeof(DOS_REQUEST_HEADER);
132     Request.CommandCode = CommandCode;
133 
134     DosCallDriver(DeviceNode->Driver, &Request);
135 
136     return Request.Status;
137 }
138 
139 static WORD NTAPI DosDriverDispatchIoctlRead(PDOS_DEVICE_NODE DeviceNode,
140                                              DWORD Buffer,
141                                              PWORD Length)
142 {
143     return DosDriverReadInternal(DeviceNode, Buffer, Length, TRUE);
144 }
145 
146 static WORD NTAPI DosDriverDispatchRead(PDOS_DEVICE_NODE DeviceNode,
147                                         DWORD Buffer,
148                                         PWORD Length)
149 {
150     return DosDriverReadInternal(DeviceNode, Buffer, Length, FALSE);
151 }
152 
153 static WORD NTAPI DosDriverDispatchPeek(PDOS_DEVICE_NODE DeviceNode,
154                                         PBYTE Character)
155 {
156     DOS_PEEK_REQUEST Request;
157 
158     Request.Header.RequestLength = sizeof(DOS_PEEK_REQUEST);
159     Request.Header.CommandCode = DOS_DEVCMD_PEEK;
160 
161     DosCallDriver(DeviceNode->Driver, &Request.Header);
162 
163     *Character = Request.Character;
164     return Request.Header.Status;
165 }
166 
167 static WORD NTAPI DosDriverDispatchInputStatus(PDOS_DEVICE_NODE DeviceNode)
168 {
169     return DosDriverGenericRequest(DeviceNode, DOS_DEVCMD_INSTAT);
170 }
171 
172 static WORD NTAPI DosDriverDispatchFlushInput(PDOS_DEVICE_NODE DeviceNode)
173 {
174     return DosDriverGenericRequest(DeviceNode, DOS_DEVCMD_FLUSH_INPUT);
175 }
176 
177 static WORD NTAPI DosDriverDispatchIoctlWrite(PDOS_DEVICE_NODE DeviceNode,
178                                               DWORD Buffer,
179                                               PWORD Length)
180 {
181     return DosDriverWriteInternal(DeviceNode, Buffer, Length, TRUE);
182 }
183 
184 static WORD NTAPI DosDriverDispatchWrite(PDOS_DEVICE_NODE DeviceNode,
185                                          DWORD Buffer,
186                                          PWORD Length)
187 {
188     return DosDriverWriteInternal(DeviceNode, Buffer, Length, FALSE);
189 }
190 
191 static WORD NTAPI DosDriverDispatchOutputStatus(PDOS_DEVICE_NODE DeviceNode)
192 {
193     return DosDriverGenericRequest(DeviceNode, DOS_DEVCMD_OUTSTAT);
194 }
195 
196 static WORD NTAPI DosDriverDispatchFlushOutput(PDOS_DEVICE_NODE DeviceNode)
197 {
198     return DosDriverGenericRequest(DeviceNode, DOS_DEVCMD_FLUSH_OUTPUT);
199 }
200 
201 static WORD NTAPI DosDriverDispatchOpen(PDOS_DEVICE_NODE DeviceNode)
202 {
203     return DosDriverGenericRequest(DeviceNode, DOS_DEVCMD_OPEN);
204 }
205 
206 static WORD NTAPI DosDriverDispatchClose(PDOS_DEVICE_NODE DeviceNode)
207 {
208     return DosDriverGenericRequest(DeviceNode, DOS_DEVCMD_CLOSE);
209 }
210 
211 static WORD NTAPI DosDriverDispatchOutputUntilBusy(PDOS_DEVICE_NODE DeviceNode,
212                                                    DWORD Buffer,
213                                                    PWORD Length)
214 {
215     DOS_OUTPUT_BUSY_REQUEST Request;
216 
217     Request.Header.RequestLength = sizeof(DOS_OUTPUT_BUSY_REQUEST);
218     Request.Header.CommandCode = DOS_DEVCMD_OUTPUT_BUSY;
219     Request.BufferPointer = Buffer;
220     Request.Length = *Length;
221 
222     DosCallDriver(DeviceNode->Driver, &Request.Header);
223 
224     *Length = Request.Length;
225     return Request.Header.Status;
226 }
227 
228 static VOID DosAddDriver(DWORD Driver)
229 {
230     PDOS_DRIVER LastDriver = &SysVars->NullDevice;
231 
232     /* Find the last driver in the list */
233     while (LOWORD(LastDriver->Link) != 0xFFFF)
234     {
235         LastDriver = (PDOS_DRIVER)FAR_POINTER(LastDriver->Link);
236     }
237 
238     /* Add the new driver to the list */
239     LastDriver->Link = Driver;
240     LastDriver = (PDOS_DRIVER)FAR_POINTER(Driver);
241 
242     if (LastDriver->DeviceAttributes & DOS_DEVATTR_CLOCK)
243     {
244         /* Update the active CLOCK driver */
245         SysVars->ActiveClock = Driver;
246     }
247 
248     if (LastDriver->DeviceAttributes
249         & (DOS_DEVATTR_STDIN | DOS_DEVATTR_STDOUT | DOS_DEVATTR_CON))
250     {
251         /* Update the active CON driver */
252         SysVars->ActiveCon = Driver;
253     }
254 }
255 
256 static VOID DosRemoveDriver(DWORD Driver)
257 {
258     DWORD CurrentDriver = MAKELONG(DOS_DATA_OFFSET(SysVars.NullDevice), DOS_DATA_SEGMENT);
259 
260     while (LOWORD(CurrentDriver) != 0xFFFF)
261     {
262         PDOS_DRIVER DriverHeader = (PDOS_DRIVER)FAR_POINTER(CurrentDriver);
263 
264         if (DriverHeader->Link == Driver)
265         {
266             /* Remove it from the list */
267             DriverHeader->Link = ((PDOS_DRIVER)FAR_POINTER(DriverHeader->Link))->Link;
268             return;
269         }
270 
271         CurrentDriver = DriverHeader->Link;
272     }
273 }
274 
275 static PDOS_DEVICE_NODE DosCreateDeviceNode(DWORD Driver)
276 {
277     BYTE i;
278     PDOS_DRIVER DriverHeader = (PDOS_DRIVER)FAR_POINTER(Driver);
279     PDOS_DEVICE_NODE Node = RtlAllocateHeap(RtlGetProcessHeap(),
280                                             HEAP_ZERO_MEMORY,
281                                             sizeof(*Node));
282     if (Node == NULL) return NULL;
283 
284     Node->Driver = Driver;
285     Node->DeviceAttributes = DriverHeader->DeviceAttributes;
286 
287     /* Initialize the name string */
288     Node->Name.Buffer = Node->NameBuffer;
289     Node->Name.MaximumLength = MAX_DEVICE_NAME;
290 
291     for (i = 0; i < MAX_DEVICE_NAME; i++)
292     {
293         if (DriverHeader->DeviceName[i] == ' ') break;
294         Node->Name.Buffer[i] = DriverHeader->DeviceName[i];
295     }
296 
297     Node->Name.Length = i;
298 
299     InsertTailList(&DeviceList, &Node->Entry);
300     return Node;
301 }
302 
303 /* PUBLIC FUNCTIONS ***********************************************************/
304 
305 PDOS_DEVICE_NODE DosGetDriverNode(DWORD Driver)
306 {
307     PLIST_ENTRY i;
308     PDOS_DEVICE_NODE Node;
309 
310     for (i = DeviceList.Flink; i != &DeviceList; i = i->Flink)
311     {
312         Node = CONTAINING_RECORD(i, DOS_DEVICE_NODE, Entry);
313         if (Node->Driver == Driver) break;
314     }
315 
316     if (i == &DeviceList)
317     {
318         DPRINT1("The driver at %04X:%04X has no associated device node. "
319                 "Installing automagically.\n",
320                 HIWORD(Driver),
321                 LOWORD(Driver));
322 
323         /* Create the device node */
324         Node = DosCreateDeviceNode(Driver);
325         Node->IoctlReadRoutine = DosDriverDispatchIoctlRead;
326         Node->ReadRoutine = DosDriverDispatchRead;
327         Node->PeekRoutine = DosDriverDispatchPeek;
328         Node->InputStatusRoutine = DosDriverDispatchInputStatus;
329         Node->FlushInputRoutine = DosDriverDispatchFlushInput;
330         Node->IoctlWriteRoutine = DosDriverDispatchIoctlWrite;
331         Node->WriteRoutine = DosDriverDispatchWrite;
332         Node->OutputStatusRoutine = DosDriverDispatchOutputStatus;
333         Node->FlushOutputRoutine = DosDriverDispatchFlushOutput;
334         Node->OpenRoutine = DosDriverDispatchOpen;
335         Node->CloseRoutine = DosDriverDispatchClose;
336         Node->OutputUntilBusyRoutine = DosDriverDispatchOutputUntilBusy;
337     }
338 
339     return Node;
340 }
341 
342 PDOS_DEVICE_NODE DosGetDevice(LPCSTR DeviceName)
343 {
344     DWORD CurrentDriver = MAKELONG(DOS_DATA_OFFSET(SysVars.NullDevice), DOS_DATA_SEGMENT);
345     ANSI_STRING DeviceNameString;
346 
347     RtlInitAnsiString(&DeviceNameString, DeviceName);
348 
349     while (LOWORD(CurrentDriver) != 0xFFFF)
350     {
351         PDOS_DEVICE_NODE Node = DosGetDriverNode(CurrentDriver);
352         PDOS_DRIVER DriverHeader = (PDOS_DRIVER)FAR_POINTER(CurrentDriver);
353 
354         if (RtlEqualString(&Node->Name, &DeviceNameString, TRUE)) return Node;
355         CurrentDriver = DriverHeader->Link;
356     }
357 
358     return NULL;
359 }
360 
361 PDOS_DEVICE_NODE DosCreateDeviceEx(WORD Attributes, PCHAR DeviceName, WORD PrivateDataSize)
362 {
363     BYTE i;
364     WORD Segment;
365     PDOS_DRIVER DriverHeader;
366     PDOS_DEVICE_NODE Node;
367 
368     /* Make sure this is a character device */
369     if (!(Attributes & DOS_DEVATTR_CHARACTER))
370     {
371         DPRINT1("ERROR: Block devices are not supported.\n");
372         return NULL;
373     }
374 
375     /* Create a driver header for this device */
376     Segment = DosAllocateMemory(sizeof(DOS_DRIVER) + DEVICE_CODE_SIZE + PrivateDataSize, NULL);
377     if (Segment == 0) return NULL;
378 
379     /* Fill the header with data */
380     DriverHeader = SEG_OFF_TO_PTR(Segment, 0);
381     DriverHeader->Link = MAXDWORD;
382     DriverHeader->DeviceAttributes = Attributes;
383     DriverHeader->StrategyRoutine = sizeof(DOS_DRIVER);
384     DriverHeader->InterruptRoutine = sizeof(DOS_DRIVER) + sizeof(StrategyRoutine);
385 
386     RtlFillMemory(DriverHeader->DeviceName, MAX_DEVICE_NAME, ' ');
387     for (i = 0; i < MAX_DEVICE_NAME; i++)
388     {
389         if (DeviceName[i] == '\0' || DeviceName[i] == ' ') break;
390         DriverHeader->DeviceName[i] = DeviceName[i];
391     }
392 
393     /* Write the routines */
394     RtlMoveMemory(SEG_OFF_TO_PTR(Segment, DriverHeader->StrategyRoutine),
395                   StrategyRoutine,
396                   sizeof(StrategyRoutine));
397     RtlMoveMemory(SEG_OFF_TO_PTR(Segment, DriverHeader->InterruptRoutine),
398                   InterruptRoutine,
399                   sizeof(InterruptRoutine));
400 
401     /* Create the node */
402     Node = DosCreateDeviceNode(MAKELONG(0, Segment));
403     if (Node == NULL)
404     {
405         DosFreeMemory(Segment);
406         return NULL;
407     }
408 
409     DosAddDriver(Node->Driver);
410     return Node;
411 }
412 
413 PDOS_DEVICE_NODE DosCreateDevice(WORD Attributes, PCHAR DeviceName)
414 {
415     /* Call the extended API */
416     return DosCreateDeviceEx(Attributes, DeviceName, 0);
417 }
418 
419 VOID DosDeleteDevice(PDOS_DEVICE_NODE DeviceNode)
420 {
421     DosRemoveDriver(DeviceNode->Driver);
422 
423     ASSERT(LOWORD(DeviceNode->Driver) == 0);
424     DosFreeMemory(HIWORD(DeviceNode->Driver));
425 
426     RemoveEntryList(&DeviceNode->Entry);
427     RtlFreeHeap(RtlGetProcessHeap(), 0, DeviceNode);
428 }
429 
430 VOID DeviceStrategyBop(VOID)
431 {
432     /* Save ES:BX */
433     DeviceRequest = (PDOS_REQUEST_HEADER)SEG_OFF_TO_PTR(getES(), getBX());
434 }
435 
436 VOID DeviceInterruptBop(VOID)
437 {
438     PLIST_ENTRY i;
439     PDOS_DEVICE_NODE Node;
440     DWORD DriverAddress = (getCS() << 4) + getIP() - sizeof(DOS_DRIVER) - 9;
441 
442     /* Get the device node for this driver */
443     for (i = DeviceList.Flink; i != &DeviceList; i = i->Flink)
444     {
445         Node = CONTAINING_RECORD(i, DOS_DEVICE_NODE, Entry);
446         if (TO_LINEAR(HIWORD(Node->Driver), LOWORD(Node->Driver)) == DriverAddress) break;
447     }
448 
449     if (i == &DeviceList)
450     {
451         DPRINT1("Device interrupt BOP from an unknown location.\n");
452         return;
453     }
454 
455     switch (DeviceRequest->CommandCode)
456     {
457         case DOS_DEVCMD_IOCTL_READ:
458         {
459             PDOS_IOCTL_RW_REQUEST Request = (PDOS_IOCTL_RW_REQUEST)DeviceRequest;
460 
461             DeviceRequest->Status = Node->IoctlReadRoutine(
462                 Node,
463                 Request->BufferPointer,
464                 &Request->Length
465             );
466 
467             break;
468         }
469 
470         case DOS_DEVCMD_READ:
471         {
472             PDOS_RW_REQUEST Request = (PDOS_RW_REQUEST)DeviceRequest;
473 
474             DeviceRequest->Status = Node->ReadRoutine(
475                 Node,
476                 Request->BufferPointer,
477                 &Request->Length
478             );
479 
480             break;
481         }
482 
483         case DOS_DEVCMD_PEEK:
484         {
485             PDOS_PEEK_REQUEST Request = (PDOS_PEEK_REQUEST)DeviceRequest;
486             DeviceRequest->Status = Node->PeekRoutine(Node, &Request->Character);
487             break;
488         }
489 
490         case DOS_DEVCMD_INSTAT:
491         {
492             DeviceRequest->Status = Node->InputStatusRoutine(Node);
493             break;
494         }
495 
496         case DOS_DEVCMD_FLUSH_INPUT:
497         {
498             DeviceRequest->Status = Node->FlushInputRoutine(Node);
499             break;
500         }
501 
502         case DOS_DEVCMD_IOCTL_WRITE:
503         {
504             PDOS_IOCTL_RW_REQUEST Request = (PDOS_IOCTL_RW_REQUEST)DeviceRequest;
505 
506             DeviceRequest->Status = Node->IoctlWriteRoutine(
507                 Node,
508                 Request->BufferPointer,
509                 &Request->Length
510             );
511 
512             break;
513         }
514 
515         case DOS_DEVCMD_WRITE:
516         {
517             PDOS_RW_REQUEST Request = (PDOS_RW_REQUEST)DeviceRequest;
518 
519             DeviceRequest->Status = Node->WriteRoutine(Node,
520                 Request->BufferPointer,
521                 &Request->Length
522             );
523 
524             break;
525         }
526 
527         case DOS_DEVCMD_OUTSTAT:
528         {
529             DeviceRequest->Status = Node->OutputStatusRoutine(Node);
530             break;
531         }
532 
533         case DOS_DEVCMD_FLUSH_OUTPUT:
534         {
535             DeviceRequest->Status = Node->FlushOutputRoutine(Node);
536             break;
537         }
538 
539         case DOS_DEVCMD_OPEN:
540         {
541             DeviceRequest->Status = Node->OpenRoutine(Node);
542             break;
543         }
544 
545         case DOS_DEVCMD_CLOSE:
546         {
547             DeviceRequest->Status = Node->CloseRoutine(Node);
548             break;
549         }
550 
551         case DOS_DEVCMD_OUTPUT_BUSY:
552         {
553             PDOS_OUTPUT_BUSY_REQUEST Request = (PDOS_OUTPUT_BUSY_REQUEST)DeviceRequest;
554 
555             DeviceRequest->Status = Node->OutputUntilBusyRoutine(
556                 Node,
557                 Request->BufferPointer,
558                 &Request->Length
559             );
560 
561             break;
562         }
563 
564         default:
565         {
566             DPRINT1("Unknown device command code: %u\n", DeviceRequest->CommandCode);
567         }
568     }
569 }
570 
571 DWORD DosLoadDriver(LPCSTR DriverFile)
572 {
573     DWORD Result = ERROR_SUCCESS;
574     HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
575     LPBYTE Address = NULL;
576     DWORD Driver;
577     PDOS_DRIVER DriverHeader;
578     WORD Segment = 0;
579     DWORD FileSize;
580     DWORD DriversLoaded = 0;
581     DOS_INIT_REQUEST Request;
582     PDOS_DEVICE_NODE DeviceNode;
583 
584     /* Open a handle to the driver file */
585     FileHandle = CreateFileA(DriverFile,
586                              GENERIC_READ,
587                              FILE_SHARE_READ,
588                              NULL,
589                              OPEN_EXISTING,
590                              FILE_ATTRIBUTE_NORMAL,
591                              NULL);
592     if (FileHandle == INVALID_HANDLE_VALUE)
593     {
594         Result = GetLastError();
595         goto Cleanup;
596     }
597 
598     /* Get the file size */
599     FileSize = GetFileSize(FileHandle, NULL);
600 
601     /* Allocate DOS memory for the driver */
602     Segment = DosAllocateMemory(FileSize >> 4, NULL);
603     if (Segment == 0)
604     {
605         Result = Sda->LastErrorCode;
606         goto Cleanup;
607     }
608 
609     /* Create a mapping object for the file */
610     FileMapping = CreateFileMapping(FileHandle,
611                                     NULL,
612                                     PAGE_READONLY,
613                                     0,
614                                     0,
615                                     NULL);
616     if (FileMapping == NULL)
617     {
618         Result = GetLastError();
619         goto Cleanup;
620     }
621 
622     /* Map the file into memory */
623     Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
624     if (Address == NULL)
625     {
626         Result = GetLastError();
627         goto Cleanup;
628     }
629 
630     /* Copy the entire file to the DOS memory */
631     Driver = MAKELONG(0, Segment);
632     DriverHeader = (PDOS_DRIVER)FAR_POINTER(Driver);
633     RtlCopyMemory(DriverHeader, Address, FileSize);
634 
635     /* Loop through all the drivers in this file */
636     while (TRUE)
637     {
638         if (!(DriverHeader->DeviceAttributes & DOS_DEVATTR_CHARACTER))
639         {
640             DPRINT1("Error loading driver at %04X:%04X: "
641                     "Block device drivers are not supported.\n",
642                     HIWORD(Driver),
643                     LOWORD(Driver));
644             goto Next;
645         }
646 
647         /* Send the driver an init request */
648         RtlZeroMemory(&Request, sizeof(Request));
649         Request.Header.RequestLength = sizeof(DOS_INIT_REQUEST);
650         Request.Header.CommandCode = DOS_DEVCMD_INIT;
651         // TODO: Set Request.DeviceString to the appropriate line in CONFIG.NT!
652         DosCallDriver(Driver, &Request.Header);
653 
654         if (Request.Header.Status & DOS_DEVSTAT_ERROR)
655         {
656             DPRINT1("Error loading driver at %04X:%04X: "
657                     "Initialization routine returned error %u.\n",
658                     HIWORD(Driver),
659                     LOWORD(Driver),
660                     Request.Header.Status & 0x7F);
661             goto Next;
662         }
663 
664         /* Create the device node */
665         DeviceNode = DosCreateDeviceNode(Driver);
666         DeviceNode->IoctlReadRoutine = DosDriverDispatchIoctlRead;
667         DeviceNode->ReadRoutine = DosDriverDispatchRead;
668         DeviceNode->PeekRoutine = DosDriverDispatchPeek;
669         DeviceNode->InputStatusRoutine = DosDriverDispatchInputStatus;
670         DeviceNode->FlushInputRoutine = DosDriverDispatchFlushInput;
671         DeviceNode->IoctlWriteRoutine = DosDriverDispatchIoctlWrite;
672         DeviceNode->WriteRoutine = DosDriverDispatchWrite;
673         DeviceNode->OutputStatusRoutine = DosDriverDispatchOutputStatus;
674         DeviceNode->FlushOutputRoutine = DosDriverDispatchFlushOutput;
675         DeviceNode->OpenRoutine = DosDriverDispatchOpen;
676         DeviceNode->CloseRoutine = DosDriverDispatchClose;
677         DeviceNode->OutputUntilBusyRoutine = DosDriverDispatchOutputUntilBusy;
678 
679         DosAddDriver(Driver);
680         DriversLoaded++;
681 
682 Next:
683         if (LOWORD(DriverHeader->Link) == 0xFFFF) break;
684         Driver = DriverHeader->Link;
685         DriverHeader = (PDOS_DRIVER)FAR_POINTER(Driver);
686     }
687 
688     DPRINT1("%u drivers loaded from %s.\n", DriversLoaded, DriverFile);
689 
690 Cleanup:
691     if (Result != ERROR_SUCCESS)
692     {
693         /* It was not successful, cleanup the DOS memory */
694         if (Segment) DosFreeMemory(Segment);
695     }
696 
697     /* Unmap the file */
698     if (Address != NULL) UnmapViewOfFile(Address);
699 
700     /* Close the file mapping object */
701     if (FileMapping != NULL) CloseHandle(FileMapping);
702 
703     /* Close the file handle */
704     if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
705 
706     return Result;
707 }
708 
709 /* EOF */
710