Changed global DOS_LOL into function DOSMEM_LOL() in order to operate
[wine] / msdos / devices.c
1 /*
2  * DOS devices
3  *
4  * Copyright 1999 Ove Kåven
5  */
6
7 #include <stdlib.h>
8 #include <string.h>
9 #include "wine/winbase16.h"
10 #include "msdos.h"
11 #include "miscemu.h"
12 #include "dosexe.h"
13 #include "debug.h"
14
15 #include "pshpack1.h"
16
17 typedef struct {
18   DOS_DEVICE_HEADER hdr;
19   BYTE ljmp1;
20   RMCBPROC strategy;
21   BYTE ljmp2;
22   RMCBPROC interrupt;
23 } WINEDEV;
24
25 typedef struct {
26   BYTE size; /* length of header + data */
27   BYTE unit; /* unit (block devices only) */
28   BYTE command;
29   WORD status;
30   BYTE reserved[8];
31 } REQUEST_HEADER;
32
33 typedef struct {
34   REQUEST_HEADER hdr;
35   BYTE media; /* media descriptor from BPB */
36   SEGPTR buffer;
37   WORD count; /* byte/sector count */
38   WORD sector; /* starting sector (block devices) */
39   DWORD volume; /* volume ID (block devices) */
40 } REQ_IO;
41
42 typedef struct {
43   REQUEST_HEADER hdr;
44   BYTE data;
45 } REQ_SAFEINPUT;
46
47 #include "poppack.h"
48
49 #define REQ_SCRATCH sizeof(REQ_IO)
50
51 #define SYSTEM_STRATEGY_NUL 0x0100
52 #define SYSTEM_STRATEGY_CON 0x0101
53
54 DWORD DOS_LOLSeg;
55
56 #define NONEXT ((DWORD)-1)
57
58 #define ATTR_STDIN     0x0001
59 #define ATTR_STDOUT    0x0002
60 #define ATTR_NUL       0x0004
61 #define ATTR_CLOCK     0x0008
62 #define ATTR_FASTCON   0x0010
63 #define ATTR_RAW       0x0020
64 #define ATTR_NOTEOF    0x0040
65 #define ATTR_DEVICE    0x0080
66 #define ATTR_REMOVABLE 0x0800
67 #define ATTR_NONIBM    0x2000 /* block devices */
68 #define ATTR_UNTILBUSY 0x2000 /* char devices */
69 #define ATTR_IOCTL     0x4000
70 #define ATTR_CHAR      0x8000
71
72 #define CMD_INIT       0
73 #define CMD_MEDIACHECK 1 /* block devices */
74 #define CMD_BUILDBPB   2 /* block devices */
75 #define CMD_INIOCTL    3
76 #define CMD_INPUT      4 /* read data */
77 #define CMD_SAFEINPUT  5 /* "non-destructive input no wait", char devices */
78 #define CMD_INSTATUS   6 /* char devices */
79 #define CMD_INFLUSH    7 /* char devices */
80 #define CMD_OUTPUT     8 /* write data */
81 #define CMD_SAFEOUTPUT 9 /* write data with verify */
82 #define CMD_OUTSTATUS 10 /* char devices */
83 #define CMD_OUTFLUSH  11 /* char devices */
84 #define CMD_OUTIOCTL  12
85 #define CMD_DEVOPEN   13
86 #define CMD_DEVCLOSE  14
87 #define CMD_REMOVABLE 15 /* block devices */
88 #define CMD_UNTILBUSY 16 /* output until busy */
89
90 #define STAT_MASK  0x00FF
91 #define STAT_DONE  0x0100
92 #define STAT_BUSY  0x0200
93 #define STAT_ERROR 0x8000
94
95 #define LJMP 0xea
96
97 #define DEV0_OFS (sizeof(DOS_LISTOFLISTS) - sizeof(DOS_DEVICE_HEADER))
98 #define DEV1_OFS (DEV0_OFS + sizeof(WINEDEV))
99 #define ALLDEV_OFS (DEV1_OFS + sizeof(devs))
100 #define ALL_OFS (ALLDEV_OFS + REQ_SCRATCH)
101
102 /* prototypes */
103 static void WINAPI nul_strategy(CONTEXT*ctx);
104 static void WINAPI nul_interrupt(CONTEXT*ctx);
105 static void WINAPI con_strategy(CONTEXT*ctx);
106 static void WINAPI con_interrupt(CONTEXT*ctx);
107
108 /* the device headers */
109 #define STRATEGY_OFS sizeof(DOS_DEVICE_HEADER)
110 #define INTERRUPT_OFS STRATEGY_OFS+5
111
112 static DOS_DEVICE_HEADER dev_nul_hdr={
113  NONEXT,
114  ATTR_CHAR|ATTR_NUL|ATTR_DEVICE,
115  STRATEGY_OFS,INTERRUPT_OFS,
116  "NUL     "
117 };
118
119 static WINEDEV devs[]={
120 {
121  {NONEXT,
122   ATTR_CHAR|ATTR_STDIN|ATTR_STDOUT|ATTR_FASTCON|ATTR_NOTEOF|ATTR_DEVICE,
123   STRATEGY_OFS,INTERRUPT_OFS,
124   "CON     "},
125  LJMP,con_strategy,
126  LJMP,con_interrupt}
127 };
128 #define nr_devs (sizeof(devs)/sizeof(WINEDEV))
129
130 /* the device implementations */
131 static void do_lret(CONTEXT*ctx)
132 {
133   WORD *stack = CTX_SEG_OFF_TO_LIN(ctx, SS_reg(ctx), ESP_reg(ctx));
134
135   IP_reg(ctx) = *(stack++);
136   CS_reg(ctx) = *(stack++);
137   SP_reg(ctx) += 2*sizeof(WORD);
138 }
139
140 static void do_strategy(CONTEXT*ctx, int id, int extra)
141 {
142   REQUEST_HEADER *hdr = CTX_SEG_OFF_TO_LIN(ctx, ES_reg(ctx), EBX_reg(ctx));
143   void **hdr_ptr = DOSVM_GetSystemData(id);
144
145   if (!hdr_ptr) {
146     hdr_ptr = calloc(1,sizeof(void *)+extra);
147     DOSVM_SetSystemData(id, hdr_ptr);
148   }
149
150   *hdr_ptr = hdr;
151   do_lret(ctx);
152 }
153
154 static REQUEST_HEADER * get_hdr(int id, void**extra)
155 {
156   void **hdr_ptr = DOSVM_GetSystemData(id);
157   if (extra)
158     *extra = hdr_ptr ? (void*)(hdr_ptr+1) : (void *)NULL;
159   return hdr_ptr ? *hdr_ptr : (void *)NULL;
160 }
161
162 static void WINAPI nul_strategy(CONTEXT*ctx)
163 {
164   do_strategy(ctx, SYSTEM_STRATEGY_NUL, 0);
165 }
166
167 static void WINAPI nul_interrupt(CONTEXT*ctx)
168 {
169   REQUEST_HEADER *hdr = get_hdr(SYSTEM_STRATEGY_NUL, NULL);
170   /* eat everything and recycle nothing */
171   switch (hdr->command) {
172   case CMD_INPUT:
173     ((REQ_IO*)hdr)->count = 0;
174     hdr->status = STAT_DONE;
175     break;
176   case CMD_SAFEINPUT:
177     hdr->status = STAT_DONE|STAT_BUSY;
178     break;
179   default:
180     hdr->status = STAT_DONE;
181   }
182   do_lret(ctx);
183 }
184
185 #define CON_BUFFER 128
186
187 static void WINAPI con_strategy(CONTEXT*ctx)
188 {
189   do_strategy(ctx, SYSTEM_STRATEGY_CON, sizeof(int));
190 }
191
192 static void WINAPI con_interrupt(CONTEXT*ctx)
193 {
194   int *scan;
195   REQUEST_HEADER *hdr = get_hdr(SYSTEM_STRATEGY_CON,(void **)&scan);
196   BIOSDATA *bios = DOSMEM_BiosData();
197   WORD CurOfs = bios->NextKbdCharPtr;
198   DOS_LISTOFLISTS *lol = DOSMEM_LOL();
199   BYTE *linebuffer = ((BYTE*)lol) + ALL_OFS;
200   BYTE *curbuffer = (lol->offs_unread_CON) ?
201     (((BYTE*)lol) + lol->offs_unread_CON) : (BYTE*)NULL;
202   DOS_DEVICE_HEADER *con = (DOS_DEVICE_HEADER*)(((BYTE*)lol) + DEV1_OFS);
203   LPDOSTASK lpDosTask = MZ_Current();
204
205   switch (hdr->command) {
206   case CMD_INPUT:
207     {
208       REQ_IO *io = (REQ_IO *)hdr;
209       WORD count = io->count, len = 0;
210       BYTE *buffer = CTX_SEG_OFF_TO_LIN(ctx,
211                                         SELECTOROF(io->buffer),
212                                         (DWORD)OFFSETOF(io->buffer));
213
214       hdr->status = STAT_BUSY;
215       /* first, check whether we already have data in line buffer */
216       if (curbuffer) {
217         /* yep, copy as much as we can */
218         BYTE data = 0;
219         while ((len<count) && (data != '\r')) {
220           data = *curbuffer++;
221           buffer[len++] = data;
222         }
223         if (data == '\r') {
224           /* line buffer emptied */
225           lol->offs_unread_CON = 0;
226           curbuffer = NULL;
227           /* if we're not in raw mode, call it a day*/
228           if (!(con->attr & ATTR_RAW)) {
229             hdr->status = STAT_DONE;
230             io->count = len;
231             break;
232           }
233         } else {
234           /* still some data left */
235           lol->offs_unread_CON = curbuffer - (BYTE*)lol;
236           /* but buffer was filled, we're done */
237           hdr->status = STAT_DONE;
238           io->count = len;
239           break;
240         }
241       }
242
243       /* if we're in raw mode, we just need to fill the buffer */
244       if (con->attr & ATTR_RAW) {
245         while (len<count) {
246           WORD data;
247
248           /* do we have a waiting scancode? */
249           if (*scan) {
250             /* yes, store scancode in buffer */
251             buffer[len++] = *scan;
252             *scan = 0;
253             if (len==count) break;
254           }
255
256           /* check for new keyboard input */
257           while (CurOfs == bios->FirstKbdCharPtr) {
258             /* no input available yet, so wait... */
259             DOSVM_Wait( -1, 0 );
260           }
261           /* read from keyboard queue (call int16?) */
262           data = ((WORD*)bios)[CurOfs];
263           CurOfs += 2;
264           if (CurOfs >= bios->KbdBufferEnd) CurOfs = bios->KbdBufferStart;
265           bios->NextKbdCharPtr = CurOfs;
266           /* if it's an extended key, save scancode */
267           if (LOBYTE(data) == 0) *scan = HIBYTE(data);
268           /* store ASCII char in buffer */
269           buffer[len++] = LOBYTE(data);
270         }
271       } else {
272         /* we're not in raw mode, so we need to do line input... */
273         while (TRUE) {
274           WORD data;
275           /* check for new keyboard input */
276           while (CurOfs == bios->FirstKbdCharPtr) {
277             /* no input available yet, so wait... */
278             DOSVM_Wait( -1, 0 );
279           }
280           /* read from keyboard queue (call int16?) */
281           data = ((WORD*)bios)[CurOfs];
282           CurOfs += 2;
283           if (CurOfs >= bios->KbdBufferEnd) CurOfs = bios->KbdBufferStart;
284           bios->NextKbdCharPtr = CurOfs;
285
286           if (LOBYTE(data) == '\r') {
287             /* it's the return key, we're done */
288             linebuffer[len++] = LOBYTE(data);
289             break;
290           }
291           else if (LOBYTE(data) >= ' ') {
292             /* a character */
293             if ((len+1)<CON_BUFFER) {
294               linebuffer[len] = LOBYTE(data);
295               WriteFile(lpDosTask->hConOutput, &linebuffer[len++], 1, NULL, NULL);
296             }
297             /* else beep, but I don't like noise */
298           }
299           else switch (LOBYTE(data)) {
300           case '\b':
301             if (len>0) {
302               len--;
303               WriteFile(lpDosTask->hConOutput, "\b \b", 3, NULL, NULL);
304             }
305             break;
306           }
307         }
308         if (len > count) {
309           /* save rest of line for later */
310           lol->offs_unread_CON = linebuffer - (BYTE*)lol + count;
311           len = count;
312         }
313         memcpy(buffer, linebuffer, len);
314       }
315       hdr->status = STAT_DONE;
316       io->count = len;
317     }
318     break;
319   case CMD_SAFEINPUT:
320     if (curbuffer) {
321       /* some line input waiting */
322       hdr->status = STAT_DONE;
323       ((REQ_SAFEINPUT*)hdr)->data = *curbuffer;
324     }
325     else if (con->attr & ATTR_RAW) {
326       if (CurOfs == bios->FirstKbdCharPtr) {
327         /* no input */
328         hdr->status = STAT_DONE|STAT_BUSY;
329       } else {
330         /* some keyboard input waiting */
331         hdr->status = STAT_DONE;
332         ((REQ_SAFEINPUT*)hdr)->data = ((BYTE*)bios)[CurOfs];
333       }
334     } else {
335       /* no line input */
336       hdr->status = STAT_DONE|STAT_BUSY;
337     }
338     break;
339   case CMD_INSTATUS:
340     if (curbuffer) {
341       /* we have data */
342       hdr->status = STAT_DONE;
343     }
344     else if (con->attr & ATTR_RAW) {
345       if (CurOfs == bios->FirstKbdCharPtr) {
346         /* no input */
347         hdr->status = STAT_DONE|STAT_BUSY;
348       } else {
349         /* some keyboard input waiting */
350         hdr->status = STAT_DONE;
351       }
352     } else {
353       /* no line input */
354       hdr->status = STAT_DONE|STAT_BUSY;
355     }
356
357     break;
358   case CMD_INFLUSH:
359     /* flush line and keyboard queue */
360     lol->offs_unread_CON = 0;
361     bios->NextKbdCharPtr = bios->FirstKbdCharPtr;
362     break;
363   case CMD_OUTPUT:
364   case CMD_SAFEOUTPUT:
365     {
366       REQ_IO *io = (REQ_IO *)hdr;
367       BYTE *buffer = CTX_SEG_OFF_TO_LIN(ctx,
368                                         SELECTOROF(io->buffer),
369                                         (DWORD)OFFSETOF(io->buffer));
370       DWORD result = 0;
371       WriteFile(lpDosTask->hConOutput, buffer, io->count, &result, NULL);
372       io->count = result;
373       hdr->status = STAT_DONE;
374     }
375     break;
376   default:
377     hdr->status = STAT_DONE;
378   }
379   do_lret(ctx);
380 }
381
382 static void InitListOfLists(DOS_LISTOFLISTS *DOS_LOL)
383 {
384 /*
385 Output of DOS 6.22:
386
387 0133:0020                    6A 13-33 01 CC 00 33 01 59 00         j.3...3.Y.
388 0133:0030  70 00 00 00 72 02 00 02-6D 00 33 01 00 00 2E 05   p...r...m.3.....
389 0133:0040  00 00 FC 04 00 00 03 08-92 21 11 E0 04 80 C6 0D   .........!......
390 0133:0050  CC 0D 4E 55 4C 20 20 20-20 20 00 00 00 00 00 00   ..NUL     ......
391 0133:0060  00 4B BA C1 06 14 00 00-00 03 01 00 04 70 CE FF   .K...........p..
392 0133:0070  FF 00 00 00 00 00 00 00-00 01 00 00 0D 05 00 00   ................
393 0133:0080  00 FF FF 00 00 00 00 FE-00 00 F8 03 FF 9F 70 02   ..............p.
394 0133:0090  D0 44 C8 FD D4 44 C8 FD-D4 44 C8 FD D0 44 C8 FD   .D...D...D...D..
395 0133:00A0  D0 44 C8 FD D0 44                                 .D...D
396 */
397   DOS_LOL->CX_Int21_5e01                = 0x0;
398   DOS_LOL->LRU_count_FCB_cache  = 0x0;
399   DOS_LOL->LRU_count_FCB_open           = 0x0;
400   DOS_LOL->OEM_func_handler             = -1; /* not available */
401   DOS_LOL->INT21_offset         = 0x0;
402   DOS_LOL->sharing_retry_count  = 3;
403   DOS_LOL->sharing_retry_delay  = 1;
404   DOS_LOL->ptr_disk_buf         = 0x0;
405   DOS_LOL->offs_unread_CON              = 0x0;
406   DOS_LOL->seg_first_MCB                = 0x0;
407   DOS_LOL->ptr_first_DPB                = 0x0;
408   DOS_LOL->ptr_first_SysFileTable       = 0x0;
409   DOS_LOL->ptr_clock_dev_hdr            = 0x0;
410   DOS_LOL->ptr_CON_dev_hdr              = 0x0;
411   DOS_LOL->max_byte_per_sec             = 512;
412   DOS_LOL->ptr_disk_buf_info            = 0x0;
413   DOS_LOL->ptr_array_CDS                = 0x0;
414   DOS_LOL->ptr_sys_FCB          = 0x0;
415   DOS_LOL->nr_protect_FCB               = 0x0;
416   DOS_LOL->nr_block_dev         = 0x0;
417   DOS_LOL->nr_avail_drive_letters       = 26; /* A - Z */
418   DOS_LOL->nr_drives_JOINed             = 0x0;
419   DOS_LOL->ptr_spec_prg_names           = 0x0;
420   DOS_LOL->ptr_SETVER_prg_list  = 0x0; /* no SETVER list */
421   DOS_LOL->DOS_HIGH_A20_func_offs       = 0x0;
422   DOS_LOL->PSP_last_exec                = 0x0;
423   DOS_LOL->BUFFERS_val          = 99; /* maximum: 99 */
424   DOS_LOL->BUFFERS_nr_lookahead = 8; /* maximum: 8 */
425   DOS_LOL->boot_drive                   = 3; /* C: */
426   DOS_LOL->flag_DWORD_moves             = 0x01; /* i386+ */
427   DOS_LOL->size_extended_mem            = 0xf000; /* very high value */
428 }
429
430 void DOSDEV_InstallDOSDevices(void)
431 {
432   WINEDEV *dev;
433   DOS_DEVICE_HEADER *pdev;
434   UINT16 seg;
435   int n;
436   WORD ofs = DEV0_OFS;
437   DOS_LISTOFLISTS *DOS_LOL;
438
439   /* allocate DOS data segment or something */
440   DOS_LOLSeg = GlobalDOSAlloc16(ALL_OFS+CON_BUFFER);
441   seg = HIWORD(DOS_LOLSeg);
442   DOS_LOL = PTR_SEG_OFF_TO_LIN(LOWORD(DOS_LOLSeg), 0);
443
444   /* initialize the magnificent List Of Lists */
445   InitListOfLists(DOS_LOL);
446
447   /* copy first device (NUL) */
448   pdev = &(DOS_LOL->NUL_dev);
449   memcpy(pdev,&dev_nul_hdr,sizeof(DOS_DEVICE_HEADER));
450   pdev->strategy += ofs;
451   pdev->interrupt += ofs;
452   /* set up dev so we can copy over the rest */
453   dev = (WINEDEV*)(((char*)DOS_LOL)+ofs);
454   dev[0].ljmp1 = LJMP;
455   dev[0].strategy = (RMCBPROC)DPMI_AllocInternalRMCB(nul_strategy);
456   dev[0].ljmp2 = LJMP;
457   dev[0].interrupt = (RMCBPROC)DPMI_AllocInternalRMCB(nul_interrupt);
458
459   dev++;
460   ofs += sizeof(WINEDEV);
461
462   /* first of remaining devices is CON */
463   DOS_LOL->ptr_CON_dev_hdr = PTR_SEG_OFF_TO_SEGPTR(seg, ofs);
464
465   /* copy remaining devices */
466   memcpy(dev,&devs,sizeof(devs));
467   for (n=0; n<nr_devs; n++) {
468     pdev->next_dev = PTR_SEG_OFF_TO_SEGPTR(seg, ofs);
469     dev[n].hdr.strategy += ofs;
470     dev[n].hdr.interrupt += ofs;
471     dev[n].strategy = (RMCBPROC)DPMI_AllocInternalRMCB(dev[n].strategy);
472     dev[n].interrupt = (RMCBPROC)DPMI_AllocInternalRMCB(dev[n].interrupt);
473     ofs += sizeof(WINEDEV);
474     pdev = &(dev[n].hdr);
475   }
476 }
477
478 DWORD DOSDEV_Console(void)
479 {
480   return DOSMEM_LOL()->ptr_CON_dev_hdr;
481 }
482
483 DWORD DOSDEV_FindCharDevice(char*name)
484 {
485   SEGPTR cur_ptr = PTR_SEG_OFF_TO_SEGPTR(HIWORD(DOS_LOLSeg),
486                                          FIELD_OFFSET(DOS_LISTOFLISTS,NUL_dev));
487   DOS_DEVICE_HEADER *cur = DOSMEM_MapRealToLinear(cur_ptr);
488   char dname[8];
489   int cnt;
490
491   /* get first 8 characters */
492   strncpy(dname,name,8);
493   /* if less than 8 characters, pad with spaces */
494   for (cnt=0; cnt<8; cnt++)
495     if (!dname[cnt]) dname[cnt]=' ';
496
497   /* search for char devices with the right name */
498   while (cur &&
499          ((!(cur->attr & ATTR_CHAR)) ||
500           memcmp(cur->name,dname,8))) {
501     cur_ptr = cur->next_dev;
502     if (cur_ptr == NONEXT) cur=NULL;
503     else cur = DOSMEM_MapRealToLinear(cur_ptr);
504   }
505   return cur_ptr;
506 }
507
508 static void DOSDEV_DoReq(void*req, DWORD dev)
509 {
510   REQUEST_HEADER *hdr = (REQUEST_HEADER *)req;
511   DOS_DEVICE_HEADER *dhdr;
512   CONTEXT ctx;
513   char *phdr;
514
515   dhdr = DOSMEM_MapRealToLinear(dev);
516   phdr = ((char*)DOSMEM_LOL()) + ALLDEV_OFS;
517
518   /* copy request to request scratch area */
519   memcpy(phdr, req, hdr->size);
520
521   /* prepare to call device driver */
522   memset(&ctx, 0, sizeof(ctx));
523
524   /* ES:BX points to request for strategy routine */
525   ES_reg(&ctx) = HIWORD(DOS_LOLSeg);
526   BX_reg(&ctx) = ALLDEV_OFS;
527
528   /* call strategy routine */
529   CS_reg(&ctx) = SELECTOROF(dev);
530   IP_reg(&ctx) = dhdr->strategy;
531   DPMI_CallRMProc(&ctx, 0, 0, 0);
532
533   /* call interrupt routine */
534   CS_reg(&ctx) = SELECTOROF(dev);
535   IP_reg(&ctx) = dhdr->interrupt;
536   DPMI_CallRMProc(&ctx, 0, 0, 0);
537
538   /* completed, copy request back */
539   memcpy(req, phdr, hdr->size);
540
541   if (hdr->status & STAT_ERROR) {
542     switch (hdr->status & STAT_MASK) {
543     case 0x0F: /* invalid disk change */
544       /* this error seems to fit the bill */
545       SetLastError(ER_NotSameDevice);
546       break;
547     default:
548       SetLastError((hdr->status & STAT_MASK) + 0x13);
549       break;
550     }
551   }
552 }
553
554 static int DOSDEV_IO(unsigned cmd, DWORD dev, DWORD buf, int buflen)
555 {
556   REQ_IO req;
557
558   req.hdr.size=sizeof(req);
559   req.hdr.unit=0; /* not dealing with block devices yet */
560   req.hdr.command=cmd;
561   req.hdr.status=STAT_BUSY;
562   req.media=0; /* not dealing with block devices yet */
563   req.buffer=buf;
564   req.count=buflen;
565   req.sector=0; /* block devices */
566   req.volume=0; /* block devices */
567
568   DOSDEV_DoReq(&req, dev);
569
570   return req.count;
571 }
572
573 int DOSDEV_Peek(DWORD dev, BYTE*data)
574 {
575   REQ_SAFEINPUT req;
576
577   req.hdr.size=sizeof(req);
578   req.hdr.unit=0; /* not dealing with block devices yet */
579   req.hdr.command=CMD_SAFEINPUT;
580   req.hdr.status=STAT_BUSY;
581   req.data=0;
582
583   DOSDEV_DoReq(&req, dev);
584
585   if (req.hdr.status & STAT_BUSY) return 0;
586
587   *data = req.data;
588   return 1;
589 }
590
591 int DOSDEV_Read(DWORD dev, DWORD buf, int buflen)
592 {
593   return DOSDEV_IO(CMD_INPUT, dev, buf, buflen);
594 }
595
596 int DOSDEV_Write(DWORD dev, DWORD buf, int buflen, int verify)
597 {
598   return DOSDEV_IO(verify?CMD_SAFEOUTPUT:CMD_OUTPUT, dev, buf, buflen);
599 }
600
601 int DOSDEV_IoctlRead(DWORD dev, DWORD buf, int buflen)
602 {
603   return DOSDEV_IO(CMD_INIOCTL, dev, buf, buflen);
604 }
605
606 int DOSDEV_IoctlWrite(DWORD dev, DWORD buf, int buflen)
607 {
608   return DOSDEV_IO(CMD_OUTIOCTL, dev, buf, buflen);
609 }