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