/* * DOS devices * * Copyright 1999 Ove K�ven * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include <stdlib.h> #include <string.h> #include "wine/winbase16.h" #include "dosexe.h" #include "wine/debug.h" #include "pshpack1.h" /* Warning: need to return LOL ptr w/ offset 0 (&ptr_first_DPB) to programs ! */ typedef struct _DOS_LISTOFLISTS { WORD CX_Int21_5e01; /* -24d contents of CX from INT 21/AX=5E01h */ WORD LRU_count_FCB_cache; /* -22d */ WORD LRU_count_FCB_open; /* -20d */ DWORD OEM_func_handler; /* -18d OEM function of INT 21/AH=F8h */ WORD INT21_offset; /* -14d offset in DOS CS of code to return from INT 21 call */ WORD sharing_retry_count; /* -12d */ WORD sharing_retry_delay; /* -10d */ DWORD ptr_disk_buf; /* -8d ptr to current disk buf */ WORD offs_unread_CON; /* -4d pointer in DOS data segment of unread CON input */ WORD seg_first_MCB; /* -2d */ DWORD ptr_first_DPB; /* 00 */ DWORD ptr_first_SysFileTable; /* 04 */ DWORD ptr_clock_dev_hdr; /* 08 */ DWORD ptr_CON_dev_hdr; /* 0C */ WORD max_byte_per_sec; /* 10 maximum bytes per sector of any block device */ DWORD ptr_disk_buf_info; /* 12 */ DWORD ptr_array_CDS; /* 16 current directory structure */ DWORD ptr_sys_FCB; /* 1A */ WORD nr_protect_FCB; /* 1E */ BYTE nr_block_dev; /* 20 */ BYTE nr_avail_drive_letters; /* 21 */ DOS_DEVICE_HEADER NUL_dev; /* 22 */ BYTE nr_drives_JOINed; /* 34 */ WORD ptr_spec_prg_names; /* 35 */ DWORD ptr_SETVER_prg_list; /* 37 */ WORD DOS_HIGH_A20_func_offs;/* 3B */ WORD PSP_last_exec; /* 3D if DOS in HMA: PSP of program executed last; if DOS low: 0000h */ WORD BUFFERS_val; /* 3F */ WORD BUFFERS_nr_lookahead; /* 41 */ BYTE boot_drive; /* 43 */ BYTE flag_DWORD_moves; /* 44 01h for 386+, 00h otherwise */ WORD size_extended_mem; /* 45 size of extended mem in KB */ SEGPTR wine_rm_lol; /* -- wine: Real mode pointer to LOL */ SEGPTR wine_pm_lol; /* -- wine: Protected mode pointer to LOL */ } DOS_LISTOFLISTS; #include "poppack.h" #define CON_BUFFER 128 enum strategy { SYSTEM_STRATEGY_NUL, SYSTEM_STRATEGY_CON, NB_SYSTEM_STRATEGIES }; static void *strategy_data[NB_SYSTEM_STRATEGIES]; #define LJMP 0xea /* prototypes */ static void WINAPI nul_strategy(CONTEXT86*ctx); static void WINAPI nul_interrupt(CONTEXT86*ctx); static void WINAPI con_strategy(CONTEXT86*ctx); static void WINAPI con_interrupt(CONTEXT86*ctx); /* devices */ static WINEDEV devs[] = { { "NUL ", ATTR_CHAR|ATTR_NUL|ATTR_DEVICE, nul_strategy, nul_interrupt }, { "CON ", ATTR_CHAR|ATTR_STDIN|ATTR_STDOUT|ATTR_FASTCON|ATTR_NOTEOF|ATTR_DEVICE, con_strategy, con_interrupt } }; #define NR_DEVS (sizeof(devs)/sizeof(WINEDEV)) /* DOS data segment */ typedef struct { DOS_LISTOFLISTS lol; DOS_DEVICE_HEADER dev[NR_DEVS-1]; DOS_DEVICE_HEADER *last_dev; /* ptr to last registered device driver */ WINEDEV_THUNK thunk[NR_DEVS]; REQ_IO req; BYTE buffer[CON_BUFFER]; } DOS_DATASEG; #define DOS_DATASEG_OFF(xxx) FIELD_OFFSET(DOS_DATASEG, xxx) DWORD DOS_LOLSeg; static struct _DOS_LISTOFLISTS * DOSMEM_LOL(void) { return PTR_REAL_TO_LIN(HIWORD(DOS_LOLSeg),0); } /* the device implementations */ static void do_lret(CONTEXT86*ctx) { WORD *stack = CTX_SEG_OFF_TO_LIN(ctx, ctx->SegSs, ctx->Esp); ctx->Eip = *(stack++); ctx->SegCs = *(stack++); ctx->Esp += 2*sizeof(WORD); } static void do_strategy(CONTEXT86*ctx, int id, int extra) { REQUEST_HEADER *hdr = CTX_SEG_OFF_TO_LIN(ctx, ctx->SegEs, ctx->Ebx); void **hdr_ptr = strategy_data[id]; if (!hdr_ptr) { hdr_ptr = calloc(1,sizeof(void *)+extra); strategy_data[id] = hdr_ptr; } *hdr_ptr = hdr; do_lret(ctx); } static REQUEST_HEADER * get_hdr(int id, void**extra) { void **hdr_ptr = strategy_data[id]; if (extra) *extra = hdr_ptr ? (void*)(hdr_ptr+1) : (void *)NULL; return hdr_ptr ? *hdr_ptr : (void *)NULL; } static void WINAPI nul_strategy(CONTEXT86*ctx) { do_strategy(ctx, SYSTEM_STRATEGY_NUL, 0); } static void WINAPI nul_interrupt(CONTEXT86*ctx) { REQUEST_HEADER *hdr = get_hdr(SYSTEM_STRATEGY_NUL, NULL); /* eat everything and recycle nothing */ switch (hdr->command) { case CMD_INPUT: ((REQ_IO*)hdr)->count = 0; hdr->status = STAT_DONE; break; case CMD_SAFEINPUT: hdr->status = STAT_DONE|STAT_BUSY; break; default: hdr->status = STAT_DONE; } do_lret(ctx); } static void WINAPI con_strategy(CONTEXT86*ctx) { do_strategy(ctx, SYSTEM_STRATEGY_CON, sizeof(int)); } static void WINAPI con_interrupt(CONTEXT86*ctx) { int *scan; REQUEST_HEADER *hdr = get_hdr(SYSTEM_STRATEGY_CON,(void **)&scan); BIOSDATA *bios = DOSVM_BiosData(); WORD CurOfs = bios->NextKbdCharPtr; DOS_LISTOFLISTS *lol = DOSMEM_LOL(); DOS_DATASEG *dataseg = (DOS_DATASEG *)lol; BYTE *linebuffer = dataseg->buffer; BYTE *curbuffer = (lol->offs_unread_CON) ? (((BYTE*)dataseg) + lol->offs_unread_CON) : (BYTE*)NULL; DOS_DEVICE_HEADER *con = dataseg->dev; DWORD w; switch (hdr->command) { case CMD_INPUT: { REQ_IO *io = (REQ_IO *)hdr; WORD count = io->count, len = 0; BYTE *buffer = CTX_SEG_OFF_TO_LIN(ctx, SELECTOROF(io->buffer), (DWORD)OFFSETOF(io->buffer)); hdr->status = STAT_BUSY; /* first, check whether we already have data in line buffer */ if (curbuffer) { /* yep, copy as much as we can */ BYTE data = 0; while ((len<count) && (data != '\r')) { data = *curbuffer++; buffer[len++] = data; } if (data == '\r') { /* line buffer emptied */ lol->offs_unread_CON = 0; curbuffer = NULL; /* if we're not in raw mode, call it a day */ if (!(con->attr & ATTR_RAW)) { hdr->status = STAT_DONE; io->count = len; break; } } else { /* still some data left */ lol->offs_unread_CON = curbuffer - (BYTE*)lol; /* but buffer was filled, we're done */ hdr->status = STAT_DONE; io->count = len; break; } } /* if we're in raw mode, we just need to fill the buffer */ if (con->attr & ATTR_RAW) { while (len<count) { WORD data; /* do we have a waiting scancode? */ if (*scan) { /* yes, store scancode in buffer */ buffer[len++] = *scan; *scan = 0; if (len==count) break; } /* check for new keyboard input */ while (CurOfs == bios->FirstKbdCharPtr) { /* no input available yet, so wait... */ DOSVM_Wait( ctx ); } /* read from keyboard queue (call int16?) */ data = ((WORD*)bios)[CurOfs]; CurOfs += 2; if (CurOfs >= bios->KbdBufferEnd) CurOfs = bios->KbdBufferStart; bios->NextKbdCharPtr = CurOfs; /* if it's an extended key, save scancode */ if (LOBYTE(data) == 0) *scan = HIBYTE(data); /* store ASCII char in buffer */ buffer[len++] = LOBYTE(data); } } else { /* we're not in raw mode, so we need to do line input... */ while (TRUE) { WORD data; /* check for new keyboard input */ while (CurOfs == bios->FirstKbdCharPtr) { /* no input available yet, so wait... */ DOSVM_Wait( ctx ); } /* read from keyboard queue (call int16?) */ data = ((WORD*)bios)[CurOfs]; CurOfs += 2; if (CurOfs >= bios->KbdBufferEnd) CurOfs = bios->KbdBufferStart; bios->NextKbdCharPtr = CurOfs; if (LOBYTE(data) == '\r') { /* it's the return key, we're done */ linebuffer[len++] = LOBYTE(data); break; } else if (LOBYTE(data) >= ' ') { /* a character */ if ((len+1)<CON_BUFFER) { linebuffer[len] = LOBYTE(data); WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), &linebuffer[len++], 1, &w, NULL); } /* else beep, but I don't like noise */ } else switch (LOBYTE(data)) { case '\b': if (len>0) { len--; WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "\b \b", 3, &w, NULL); } break; } } if (len > count) { /* save rest of line for later */ lol->offs_unread_CON = linebuffer - (BYTE*)lol + count; len = count; } memcpy(buffer, linebuffer, len); } hdr->status = STAT_DONE; io->count = len; } break; case CMD_SAFEINPUT: if (curbuffer) { /* some line input waiting */ hdr->status = STAT_DONE; ((REQ_SAFEINPUT*)hdr)->data = *curbuffer; } else if (con->attr & ATTR_RAW) { if (CurOfs == bios->FirstKbdCharPtr) { /* no input */ hdr->status = STAT_DONE|STAT_BUSY; } else { /* some keyboard input waiting */ hdr->status = STAT_DONE; ((REQ_SAFEINPUT*)hdr)->data = ((BYTE*)bios)[CurOfs]; } } else { /* no line input */ hdr->status = STAT_DONE|STAT_BUSY; } break; case CMD_INSTATUS: if (curbuffer) { /* we have data */ hdr->status = STAT_DONE; } else if (con->attr & ATTR_RAW) { if (CurOfs == bios->FirstKbdCharPtr) { /* no input */ hdr->status = STAT_DONE|STAT_BUSY; } else { /* some keyboard input waiting */ hdr->status = STAT_DONE; } } else { /* no line input */ hdr->status = STAT_DONE|STAT_BUSY; } break; case CMD_INFLUSH: /* flush line and keyboard queue */ lol->offs_unread_CON = 0; bios->NextKbdCharPtr = bios->FirstKbdCharPtr; break; case CMD_OUTPUT: case CMD_SAFEOUTPUT: { REQ_IO *io = (REQ_IO *)hdr; BYTE *buffer = CTX_SEG_OFF_TO_LIN(ctx, SELECTOROF(io->buffer), (DWORD)OFFSETOF(io->buffer)); DWORD result = 0; WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buffer, io->count, &result, NULL); io->count = result; hdr->status = STAT_DONE; } break; default: hdr->status = STAT_DONE; } do_lret(ctx); } static void InitListOfLists(DOS_LISTOFLISTS *DOS_LOL) { /* Output of DOS 6.22: 0133:0020 6A 13-33 01 CC 00 33 01 59 00 j.3...3.Y. 0133:0030 70 00 00 00 72 02 00 02-6D 00 33 01 00 00 2E 05 p...r...m.3..... 0133:0040 00 00 FC 04 00 00 03 08-92 21 11 E0 04 80 C6 0D .........!...... 0133:0050 CC 0D 4E 55 4C 20 20 20-20 20 00 00 00 00 00 00 ..NUL ...... 0133:0060 00 4B BA C1 06 14 00 00-00 03 01 00 04 70 CE FF .K...........p.. 0133:0070 FF 00 00 00 00 00 00 00-00 01 00 00 0D 05 00 00 ................ 0133:0080 00 FF FF 00 00 00 00 FE-00 00 F8 03 FF 9F 70 02 ..............p. 0133:0090 D0 44 C8 FD D4 44 C8 FD-D4 44 C8 FD D0 44 C8 FD .D...D...D...D.. 0133:00A0 D0 44 C8 FD D0 44 .D...D */ DOS_LOL->CX_Int21_5e01 = 0x0; DOS_LOL->LRU_count_FCB_cache = 0x0; DOS_LOL->LRU_count_FCB_open = 0x0; DOS_LOL->OEM_func_handler = -1; /* not available */ DOS_LOL->INT21_offset = 0x0; DOS_LOL->sharing_retry_count = 3; DOS_LOL->sharing_retry_delay = 1; DOS_LOL->ptr_disk_buf = 0x0; DOS_LOL->offs_unread_CON = 0x0; DOS_LOL->seg_first_MCB = 0x0; DOS_LOL->ptr_first_DPB = 0x0; DOS_LOL->ptr_first_SysFileTable = 0x0; DOS_LOL->ptr_clock_dev_hdr = 0x0; DOS_LOL->ptr_CON_dev_hdr = 0x0; DOS_LOL->max_byte_per_sec = 512; DOS_LOL->ptr_disk_buf_info = 0x0; DOS_LOL->ptr_array_CDS = 0x0; DOS_LOL->ptr_sys_FCB = 0x0; DOS_LOL->nr_protect_FCB = 0x0; DOS_LOL->nr_block_dev = 0x0; DOS_LOL->nr_avail_drive_letters = 26; /* A - Z */ DOS_LOL->nr_drives_JOINed = 0x0; DOS_LOL->ptr_spec_prg_names = 0x0; DOS_LOL->ptr_SETVER_prg_list = 0x0; /* no SETVER list */ DOS_LOL->DOS_HIGH_A20_func_offs = 0x0; DOS_LOL->PSP_last_exec = 0x0; DOS_LOL->BUFFERS_val = 99; /* maximum: 99 */ DOS_LOL->BUFFERS_nr_lookahead = 8; /* maximum: 8 */ DOS_LOL->boot_drive = 3; /* C: */ DOS_LOL->flag_DWORD_moves = 0x01; /* i386+ */ DOS_LOL->size_extended_mem = 0xf000; /* very high value */ } void DOSDEV_SetupDevice(const WINEDEV * devinfo, WORD seg, WORD off_dev, WORD off_thunk) { DOS_DEVICE_HEADER *dev = PTR_REAL_TO_LIN(seg, off_dev); WINEDEV_THUNK *thunk = PTR_REAL_TO_LIN(seg, off_thunk); DOS_DATASEG *dataseg = (DOS_DATASEG*)DOSMEM_LOL(); dev->attr = devinfo->attr; dev->strategy = off_thunk + FIELD_OFFSET(WINEDEV_THUNK, ljmp1); dev->interrupt = off_thunk + FIELD_OFFSET(WINEDEV_THUNK, ljmp2); memcpy(dev->name, devinfo->name, 8); thunk->ljmp1 = LJMP; thunk->strategy = DPMI_AllocInternalRMCB(devinfo->strategy); thunk->ljmp2 = LJMP; thunk->interrupt = DPMI_AllocInternalRMCB(devinfo->interrupt); dev->next_dev = NONEXT; if (dataseg->last_dev) dataseg->last_dev->next_dev = MAKESEGPTR(seg, off_dev); dataseg->last_dev = dev; } void DOSDEV_InstallDOSDevices(void) { DOS_DATASEG *dataseg; WORD seg; WORD selector; unsigned int n; /* allocate DOS data segment or something */ dataseg = DOSVM_AllocDataUMB( sizeof(DOS_DATASEG), &seg, &selector ); DOS_LOLSeg = MAKESEGPTR( seg, 0 ); DOSMEM_LOL()->wine_rm_lol = MAKESEGPTR( seg, FIELD_OFFSET(DOS_LISTOFLISTS, ptr_first_DPB) ); DOSMEM_LOL()->wine_pm_lol = MAKESEGPTR( selector, FIELD_OFFSET(DOS_LISTOFLISTS, ptr_first_DPB) ); /* initialize the magnificent List Of Lists */ InitListOfLists(&dataseg->lol); /* Set up first device (NUL) */ dataseg->last_dev = NULL; DOSDEV_SetupDevice( &devs[0], seg, DOS_DATASEG_OFF(lol.NUL_dev), DOS_DATASEG_OFF(thunk[0]) ); /* Set up the remaining devices */ for (n = 1; n < NR_DEVS; n++) DOSDEV_SetupDevice( &devs[n], seg, DOS_DATASEG_OFF(dev[n-1]), DOS_DATASEG_OFF(thunk[n]) ); /* CON is device 1 */ dataseg->lol.ptr_CON_dev_hdr = MAKESEGPTR(seg, DOS_DATASEG_OFF(dev[0])); } DWORD DOSDEV_Console(void) { return DOSMEM_LOL()->ptr_CON_dev_hdr; } DWORD DOSDEV_FindCharDevice(char*name) { SEGPTR cur_ptr = MAKESEGPTR(HIWORD(DOS_LOLSeg), FIELD_OFFSET(DOS_LISTOFLISTS,NUL_dev)); DOS_DEVICE_HEADER *cur = PTR_REAL_TO_LIN(SELECTOROF(cur_ptr),OFFSETOF(cur_ptr)); char dname[8]; int cnt; /* get first 8 characters */ /* if less than 8 characters, pad with spaces */ for (cnt=0; name[cnt] && cnt<8; cnt++) dname[cnt]=name[cnt]; while(cnt<8) dname[cnt++] = ' '; /* search for char devices with the right name */ while (cur && ((!(cur->attr & ATTR_CHAR)) || memcmp(cur->name,dname,8))) { cur_ptr = cur->next_dev; if (cur_ptr == NONEXT) cur=NULL; else cur = PTR_REAL_TO_LIN(SELECTOROF(cur_ptr),OFFSETOF(cur_ptr)); } return cur_ptr; } static void DOSDEV_DoReq(void*req, DWORD dev) { REQUEST_HEADER *hdr = (REQUEST_HEADER *)req; DOS_DEVICE_HEADER *dhdr; CONTEXT86 ctx; char *phdr; dhdr = PTR_REAL_TO_LIN(SELECTOROF(dev),OFFSETOF(dev)); phdr = ((char*)DOSMEM_LOL()) + DOS_DATASEG_OFF(req); /* copy request to request scratch area */ memcpy(phdr, req, hdr->size); /* prepare to call device driver */ memset(&ctx, 0, sizeof(ctx)); ctx.EFlags |= V86_FLAG; /* ES:BX points to request for strategy routine */ ctx.SegEs = HIWORD(DOS_LOLSeg); ctx.Ebx = DOS_DATASEG_OFF(req); /* call strategy routine */ ctx.SegCs = SELECTOROF(dev); ctx.Eip = dhdr->strategy; DPMI_CallRMProc(&ctx, 0, 0, 0); /* call interrupt routine */ ctx.SegCs = SELECTOROF(dev); ctx.Eip = dhdr->interrupt; DPMI_CallRMProc(&ctx, 0, 0, 0); /* completed, copy request back */ memcpy(req, phdr, hdr->size); if (hdr->status & STAT_ERROR) { switch (hdr->status & STAT_MASK) { case 0x0F: /* invalid disk change */ /* this error seems to fit the bill */ SetLastError(ERROR_NOT_SAME_DEVICE); break; default: SetLastError((hdr->status & STAT_MASK) + 0x13); break; } } } static int DOSDEV_IO(unsigned cmd, DWORD dev, DWORD buf, int buflen) { REQ_IO req; req.hdr.size=sizeof(req); req.hdr.unit=0; /* not dealing with block devices yet */ req.hdr.command=cmd; req.hdr.status=STAT_BUSY; req.media=0; /* not dealing with block devices yet */ req.buffer=buf; req.count=buflen; req.sector=0; /* block devices */ req.volume=0; /* block devices */ DOSDEV_DoReq(&req, dev); return req.count; } int DOSDEV_Peek(DWORD dev, BYTE*data) { REQ_SAFEINPUT req; req.hdr.size=sizeof(req); req.hdr.unit=0; /* not dealing with block devices yet */ req.hdr.command=CMD_SAFEINPUT; req.hdr.status=STAT_BUSY; req.data=0; DOSDEV_DoReq(&req, dev); if (req.hdr.status & STAT_BUSY) return 0; *data = req.data; return 1; } int DOSDEV_Read(DWORD dev, DWORD buf, int buflen) { return DOSDEV_IO(CMD_INPUT, dev, buf, buflen); } int DOSDEV_Write(DWORD dev, DWORD buf, int buflen, int verify) { return DOSDEV_IO(verify?CMD_SAFEOUTPUT:CMD_OUTPUT, dev, buf, buflen); } int DOSDEV_IoctlRead(DWORD dev, DWORD buf, int buflen) { return DOSDEV_IO(CMD_INIOCTL, dev, buf, buflen); } int DOSDEV_IoctlWrite(DWORD dev, DWORD buf, int buflen) { return DOSDEV_IO(CMD_OUTIOCTL, dev, buf, buflen); } void DOSDEV_SetSharingRetry(WORD delay, WORD count) { DOSMEM_LOL()->sharing_retry_delay = delay; if (count) DOSMEM_LOL()->sharing_retry_count = count; } SEGPTR DOSDEV_GetLOL(BOOL v86) { if (v86) return DOSMEM_LOL()->wine_rm_lol; else return DOSMEM_LOL()->wine_pm_lol; }