* Fix some cases where NULL was used but 0 was meant (and vice versa).
[dragonfly.git] / usr.bin / doscmd / xms.c
blobaeb5ad33094647536e97652365ef685477f3e2b0
1 /*-
2 * Copyright (c) 1997 Helmut Wirth <hfwirth@ping.at>
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice immediately at the beginning of the file, witout modification,
10 * this list of conditions, and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 * $FreeBSD: src/usr.bin/doscmd/xms.c,v 1.6.2.1 2002/04/25 11:04:51 tg Exp $
29 * $DragonFly: src/usr.bin/doscmd/xms.c,v 1.4 2008/06/05 18:06:33 swildner Exp $
33 * XMS memory manmagement
35 * To emulate DOS extended memory (EMM) we use an implementation of
36 * HIMEM.SYS driver capabitlities, according to the XMS 3.0 Spec.
37 * The actual memory allocated via XMS calls from DOS is allocated
38 * via malloc by the emulator. Maximum memory allocation is configureable.
40 * Credits to:
41 * The original author of this file, some parts are still here
42 * Linux dosemu programmers. I looked into their code.
45 #include <sys/types.h>
46 #include <sys/param.h>
47 #include <sys/mman.h>
48 #include <unistd.h>
50 #include "doscmd.h"
51 #include "xms.h"
54 /* Extended memory handle management */
56 static XMS_handle xms_hand[NUM_HANDLES];
57 int num_free_handle = NUM_HANDLES;
59 /* This is planned to be selectable from .doscmdrc */
60 u_long xms_maxsize = DEFAULT_EMM_SIZE;
61 static u_long xms_free_mem;
62 static u_long xms_used_mem;
63 static u_char vec_grabbed;
65 /* Address entry for zero size allocated handles */
66 #define XMS_NULL_ALLOC 0xffffffff
68 /* High memory area (HMA) management */
69 static u_char HMA_allocated = 0;
70 static short HMA_a20 = -1;
71 static int HMA_fd_off, HMA_fd_on;
73 /* high memory mapfiles */
74 static char memfile[] = "/tmp/doscmd.XXXXXX";
76 /* Upper memory block (UMB) management */
77 UMB_block *UMB_freelist = NULL;
78 UMB_block *UMB_alloclist = NULL;
80 /* Calls to emulator */
81 u_long xms_vector;
82 static u_char xms_trampoline[] = {
83 0xeb, /* JMP 5 */
84 0x03,
85 0x90, /* NOP */
86 0x90, /* NOP */
87 0x90, /* NOP */
88 0xf4, /* HLT */
89 0xcb, /* RETF */
92 /* Local prototypes */
93 static void add_block(UMB_block **listp, UMB_block *blk);
94 static UMB_block *create_block(u_long addr, u_long size);
95 static void disable_a20(void);
96 static void enable_a20(void);
97 static int get_free_handle(void);
98 static void merge_blocks(void);
99 static void xms_entry(regcontext_t *REGS);
101 /* Init the entire module */
102 void
103 xms_init(void)
105 /* Initialize handle table: xms_handle.addr == 0 means free */
106 bzero((void *)xms_hand, sizeof(XMS_handle) * NUM_HANDLES);
107 xms_free_mem = xms_maxsize;
108 xms_used_mem = 0;
109 vec_grabbed = 0;
110 HMA_allocated = 0;
111 /* Initialize UMB blocks */
112 /* 0xD0000 to 0xDffff */
113 add_block(&UMB_freelist, create_block(0xd0000, 64*1024));
114 /*XXX check for EMS emulation, when it is done! */
115 /* 0xE0000 to 0xEffff */
117 /* This is used as window for EMS, will be configurable ! */
118 /* add_block(&UMB_freelist, create_block(0xe0000, 64*1024)); */
120 merge_blocks();
122 xms_vector = insert_generic_trampoline(
123 sizeof(xms_trampoline), xms_trampoline);
124 register_callback(xms_vector + 5, xms_entry, "xms");
128 * UMB management routines: UMBs normally lie between 0xd0000 and
129 * 0xefff0 in VM86 memory space and are accessible for all DOS applictions.
130 * We could enable more space, but with the emulator we do not
131 * need many drivers, so I think 2 * 64kB will suffice. If EMS emulation
132 * exists, a 64kB segment (0xe0000 - 0xeffff for example) is needed for
133 * the EMS mapping, in this case we have 64kB UMB space. This is more than
134 * many PCs are able to do.
135 * This emulation does only the management for the memory, the memory
136 * is present and read/write/excutable for VM86 applications.
139 /* Add a block to a list, maintain ascending start address order */
141 static void
142 add_block(UMB_block **listp, UMB_block *blk)
144 UMB_block *bp, *obp;
146 /* No blocks there, attach the new block to the head */
147 if (*listp == NULL) {
148 *listp = blk;
149 blk->next = NULL;
150 } else {
151 /* Insert at the start */
152 bp = obp = *listp;
153 if (blk->addr < bp->addr) {
154 blk->next = *listp;
155 *listp = blk;
156 return;
158 /* Not at the start, insert into the list */
159 for (; bp != NULL; bp = bp->next) {
160 if (blk->addr > bp->addr) {
161 obp = bp;
162 continue;
163 } else {
164 obp->next = blk;
165 blk->next = bp;
166 return;
169 /* Append to the end of the list */
170 obp->next = blk;
171 blk->next = NULL;
173 return;
176 /* Find a block with address addr in the alloc list */
177 static UMB_block *
178 find_allocated_block(u_long addr)
180 UMB_block *bp;
182 if (UMB_alloclist == NULL)
183 return NULL;
185 for (bp = UMB_alloclist; bp != NULL; bp = bp->next)
186 if (bp->addr == addr)
187 return bp;
188 return NULL;
191 /* Remove a block blk from a list, the block must exist on the list */
192 static void
193 remove_block(UMB_block **listp, UMB_block *blk)
195 UMB_block *bp;
197 if (*listp == NULL)
198 goto faterr;
200 if (*listp == blk) {
201 *listp = (*listp)->next;
202 return;
204 bp = *listp;
205 do {
206 if (bp->next == blk) {
207 bp->next = bp->next->next;
208 return;
210 bp = bp->next;
211 } while(bp != NULL);
212 faterr:
213 fatal("XMS: UMB remove_block did not find block\n");
216 /* Try to merge neighbouring blocks in the free list */
217 static void
218 merge_blocks(void)
220 UMB_block *bp;
221 u_long endaddr;
223 if (UMB_freelist == NULL)
224 return;
225 bp = UMB_freelist;
226 do {
227 endaddr = bp->addr + bp->size;
228 if (bp->next != NULL && endaddr == bp->next->addr) {
229 /* Merge the current and the next block */
230 UMB_block *mergebp = bp->next;
231 bp->size += mergebp->size;
232 bp->next = mergebp->next;
233 free(mergebp);
234 } else {
235 /* Goto next block */
236 bp = bp->next;
238 } while (bp != NULL);
241 /* Try to find a free block of size exactly siz */
242 static UMB_block *
243 find_exact_block(u_long siz)
245 UMB_block *bp;
247 if (UMB_freelist == NULL)
248 return NULL;
250 for (bp = UMB_freelist; bp != NULL; bp = bp->next)
251 if (bp->size == siz)
252 return bp;
253 return NULL;
256 /* Try to find a block with a size bigger than requested. If there is
257 * no such block, return the block with the biggest size. If there is
258 * no free block at all, return NULL
260 static UMB_block *
261 find_block(u_long siz)
263 UMB_block *bp;
264 UMB_block *biggest = NULL;
266 if (UMB_freelist == NULL)
267 return NULL;
269 for (bp = UMB_freelist; bp != NULL; bp = bp->next) {
270 if (bp->size > siz)
271 return bp;
272 if (biggest == NULL) {
273 biggest = bp;
274 continue;
276 if (biggest->size < bp->size)
277 biggest = bp;
279 return biggest;
282 /* Create a block structure, memory is allocated. The structure lives
283 * until the block is merged into another block, then it is freed */
284 static UMB_block *
285 create_block(u_long addr, u_long size)
287 UMB_block *blk;
289 if ((blk = malloc(sizeof(UMB_block))) == NULL)
290 fatal ("XMS: Cannot allocate UMB structure\n");
291 blk->addr = addr;
292 blk->size = size;
293 blk->next = NULL;
294 return blk;
299 * initHMA(): The first 64kB of memory are mapped from 1MB (0x100000)
300 * again to emulate the address wrap around of the 808x. The HMA area
301 * is a cheap trick, usable only with 80386 and higher. The 80[345..]86
302 * does not have this address wrap around. If more than 1MB is installed
303 * the processor can address more than 1MB: load 0xFFFF to the segment
304 * register and using the full offset of 0xffff the resulting highest
305 * address is (0xffff << 4) + 0xffff = 0x10ffef. Nearly 64kB are accessible
306 * from real or VM86 mode. The mmap calls emulate the address wrap by
307 * mapping the lowest 64kB the the first 64kB after the 1MB limit.
308 * In hardware this is achieved by setting and resetting the a20 bit,
309 * an ugly compatibility hack: The hardware simlpy clamps the address 20
310 * line of the processor to low and hence the wrap around is forced.
311 * This is switchable via the BIOS or via HIMEM.SYS and therefore the
312 * first 64kB over 1MB can be enabled or disabled at will by software.
313 * DOS uses this trick to load itself high, if the memory is present.
314 * We emulate this behaviour by mapping and unmapping the HMA area.
315 * (Linux has implemented this feature using shared memory (SHM) calls.)
317 * This routine is called from doscmd.c at startup. A20 is disabled after
318 * startup.
321 void initHMA(void)
323 int mfd;
326 * We need two files, one for the wrap around mapping and one
327 * for the HMA contents
330 mfd = mkstemp(memfile);
332 if (mfd < 0) {
333 fprintf(stderr, "memfile: %s\n", strerror(errno));
334 fprintf(stderr, "High memory will not be mapped\n");
336 /* We need this for XMS services. If it fails, turn HMA off */
337 HMA_a20 = -1;
338 return;
340 unlink(memfile);
341 HMA_fd_off = squirrel_fd(mfd);
343 lseek(HMA_fd_off, 64 * 1024 - 1, 0);
344 write(HMA_fd_off, "", 1);
346 mfd = mkstemp(memfile);
348 if (mfd < 0) {
349 fprintf(stderr, "memfile: %s\n", strerror(errno));
350 fprintf(stderr, "High memory will not be mapped\n");
352 /* We need this for XMS services. If it fails, turn HMA off */
353 HMA_a20 = -1;
354 return;
356 unlink(memfile);
357 HMA_fd_on = squirrel_fd(mfd);
359 lseek(HMA_fd_on, 64 * 1024 - 1, 0);
360 write(HMA_fd_on, "", 1);
362 if (mmap((caddr_t)0x000000, 0x100000,
363 PROT_EXEC | PROT_READ | PROT_WRITE,
364 MAP_ANON | MAP_FIXED | MAP_SHARED,
365 -1, 0) == MAP_FAILED) {
366 perror("Error mapping HMA, HMA disabled: ");
367 HMA_a20 = -1;
368 close(HMA_fd_off);
369 close(HMA_fd_on);
370 return;
372 if (mmap((caddr_t)0x000000, 64 * 1024,
373 PROT_EXEC | PROT_READ | PROT_WRITE,
374 MAP_FILE | MAP_FIXED | MAP_SHARED,
375 HMA_fd_off, 0) == MAP_FAILED) {
376 perror("Error mapping HMA, HMA disabled: ");
377 HMA_a20 = -1;
378 close(HMA_fd_off);
379 close(HMA_fd_on);
380 return;
382 if (mmap((caddr_t)0x100000, 64 * 1024,
383 PROT_EXEC | PROT_READ | PROT_WRITE,
384 MAP_FILE | MAP_FIXED | MAP_SHARED,
385 HMA_fd_off, 0) == MAP_FAILED) {
386 perror("Error mapping HMA, HMA disabled: ");
387 HMA_a20 = -1;
388 close(HMA_fd_off);
389 close(HMA_fd_on);
390 return;
392 HMA_a20 = 0;
396 /* Enable the a20 "address line" by unmapping the 64kB over 1MB */
397 static void enable_a20(void)
399 if (HMA_a20 < 0)
400 return;
402 /* Unmap the wrap around portion (fd = HMA_fd_off) */
403 /* XXX Not sure about this: Should I unmap first, then map new or
404 * does it suffice to map new "over' the existing mapping ? Both
405 * works (define to #if 0 next line and some lines below to try.
407 #if 1
408 if (munmap((caddr_t)0x100000, 64 * 1024) < 0) {
409 fatal("HMA unmapping error: %s\nCannot recover\n", strerror(errno));
411 #endif
412 /* Map memory for the HMA with fd = HMA_fd_on */
413 if (mmap((caddr_t)0x100000, 64 * 1024,
414 PROT_EXEC | PROT_READ | PROT_WRITE,
415 MAP_FILE | MAP_FIXED | MAP_SHARED,
416 HMA_fd_on, 0) == MAP_FAILED) {
417 fatal("HMA mapping error: %s\nCannot recover\n", strerror(errno));
421 /* Disable the a20 "address line" by mapping the 64kB over 1MB again */
422 static void disable_a20(void)
424 if (HMA_a20 < 0)
425 return;
426 #if 1
427 /* Unmap the HMA (fd = HMA_fd_on) */
428 if (munmap((caddr_t)0x100000, 64 * 1024) < 0) {
429 fatal("HMA unmapping error: %s\nCannot recover\n", strerror(errno));
431 #endif
432 /* Remap the wrap around area */
433 if (mmap((caddr_t)0x100000, 64 * 1024,
434 PROT_EXEC | PROT_READ | PROT_WRITE,
435 MAP_FILE | MAP_FIXED | MAP_SHARED,
436 HMA_fd_off, 0) == MAP_FAILED) {
437 fatal("HMA mapping error: %s\nCannot recover\n", strerror(errno));
443 * This handles calls to int15 function 88: BIOS extended memory
444 * request. XMS spec says: "In order to maintain compatibility with existing
445 * device drivers, DOS XMS drivers must not hook INT 15h until the first
446 * non-Version Number call to the control function is made."
449 void
450 get_raw_extmemory_info(regcontext_t *REGS)
452 if (vec_grabbed)
453 R_AX = 0x0;
454 else
455 R_AX = xms_maxsize / 1024;
456 return;
460 /* Handle management routine: Find next free handle */
462 static int
463 get_free_handle(void)
465 int i;
466 /* Linear search, there are only a few handles */
467 for (i = 0; i < NUM_HANDLES; i++) {
468 if (xms_hand[i].addr == 0)
469 return i + 1;
471 return 0;
475 /* Installation check */
477 int2f_43(regcontext_t *REGS)
480 switch (R_AL) {
481 case 0x00: /* installation check */
482 R_AL = 0x80;
483 break;
485 case 0x10: /* get handler address */
486 PUTVEC(R_ES, R_BX, xms_vector);
487 break;
489 default:
490 return (0);
492 return (1);
495 /* Main call entry point for the XMS handler from DOS */
496 static void
497 xms_entry(regcontext_t *REGS)
500 if (R_AH != 0)
501 vec_grabbed = 1;
503 /* If the HMA feature is disabled these calls are "not managed" */
504 if (HMA_a20 < 0) {
505 if (R_AH == XMS_ALLOCATE_HIGH_MEMORY || R_AH == XMS_FREE_HIGH_MEMORY ||
506 R_AH == XMS_GLOBAL_ENABLE_A20 || R_AH == XMS_GLOBAL_DISABLE_A20 ||
507 R_AH == XMS_LOCAL_ENABLE_A20 || R_AH == XMS_LOCAL_DISABLE_A20 ||
508 R_AH == XMS_QUERY_A20) {
509 R_AX = 0x0;
510 R_BL = XMS_HMA_NOT_MANAGED;
511 return;
515 switch (R_AH) {
516 case XMS_GET_VERSION:
517 debug(D_XMS, "XMS: Get Version\n");
518 R_AX = XMS_VERSION; /* 3.0 */
519 R_BX = XMS_REVISION; /* internal revision 0 */
520 R_DX = (HMA_a20 < 0) ? 0x0000 : 0x0001;
521 break;
524 * XXX Not exact! Spec says compare size to a HMAMIN parameter and
525 * refuse HMA, if space is too small. With MSDOS 5.0 and higher DOS
526 * itself uses the HMA (DOS=HIGH), so I think we can safely ignore
527 * that.
529 case XMS_ALLOCATE_HIGH_MEMORY:
530 debug(D_XMS, "XMS: Allocate HMA\n");
531 if (HMA_allocated) {
532 R_AX = 0x0;
533 R_BL = XMS_HMA_ALREADY_USED;
534 } else {
535 HMA_allocated = 1;
536 R_AX = 0x1;
537 R_BL = XMS_SUCCESS;
539 break;
541 case XMS_FREE_HIGH_MEMORY:
542 debug(D_XMS, "XMS: Free HMA\n");
543 if (HMA_allocated) {
544 HMA_allocated = 0;
545 R_AX = 0x1;
546 R_BL = XMS_SUCCESS;
547 } else {
548 R_AX = 0x0;
549 R_BL = XMS_HMA_NOT_ALLOCATED;
551 break;
553 case XMS_GLOBAL_ENABLE_A20:
554 debug(D_XMS, "XMS: Global enable A20\n");
555 if (HMA_a20 == 0)
556 enable_a20();
557 HMA_a20 = 1;
558 R_AX = 0x1;
559 R_BL = XMS_SUCCESS;
560 break;
562 case XMS_GLOBAL_DISABLE_A20:
563 debug(D_XMS, "XMS: Global disable A20\n");
564 if (HMA_a20 != 0)
565 disable_a20();
566 HMA_a20 = 0;
567 R_AX = 0x1;
568 R_BL = XMS_SUCCESS;
569 break;
572 * This is an accumulating call. Every call increments HMA_a20.
573 * Caller must use LOCAL_DISBALE_A20 once for each previous call
574 * to LOCAL_ENABLE_A20.
576 case XMS_LOCAL_ENABLE_A20:
577 debug(D_XMS, "XMS: Local enable A20\n");
578 HMA_a20++;
579 if (HMA_a20 == 1)
580 enable_a20();
581 R_AX = 0x1;
582 R_BL = XMS_SUCCESS;
583 break;
585 case XMS_LOCAL_DISABLE_A20:
586 debug(D_XMS, "XMS: Local disable A20\n");
587 if (HMA_a20 > 0)
588 HMA_a20--;
589 if (HMA_a20 == 0)
590 disable_a20();
591 R_AX = 0x1;
592 R_BL = XMS_SUCCESS;
593 break;
595 case XMS_QUERY_A20:
597 * Disabled because DOS permanently scans this, to avoid endless output.
599 #if 0
600 debug(D_XMS, "XMS: Query A20\n"); */
601 #endif
602 R_AX = (HMA_a20 > 0) ? 0x1 : 0x0;
603 R_BL = XMS_SUCCESS;
604 break;
606 case XMS_QUERY_FREE_EXTENDED_MEMORY:
607 /* DOS MEM.EXE chokes, if the HMA is enabled and the reported
608 * free space includes the HMA. So we subtract 64kB from the
609 * space reported, if the HMA is enabled.
611 if (HMA_a20 < 0)
612 R_EAX = R_EDX = xms_free_mem / 1024;
613 else
614 R_EAX = R_EDX = (xms_free_mem / 1024) - 64;
616 if (xms_free_mem == 0)
617 R_BL = XMS_FULL;
618 else
619 R_BL = XMS_SUCCESS;
620 debug(D_XMS, "XMS: Query free EMM: Returned %dkB\n", R_AX);
621 break;
623 case XMS_ALLOCATE_EXTENDED_MEMORY:
625 size_t req_siz;
626 int hindx, hnum;
627 void *mem;
629 debug(D_XMS, "XMS: Allocate EMM: ");
630 /* Enough handles ? */
631 if ((hnum = get_free_handle()) == 0) {
632 R_AX = 0x00;
633 R_BL = XMS_OUT_OF_HANDLES;
634 debug(D_XMS, " Out of handles\n");
635 break;
637 hindx = hnum - 1;
638 req_siz = R_DX * 1024;
640 /* Enough memory ? */
641 if (req_siz > xms_free_mem) {
642 R_AX = 0x00;
643 R_BL = XMS_FULL;
644 debug(D_XMS, " No memory left\n");
645 break;
648 xms_hand[hindx].size = req_siz;
649 xms_hand[hindx].num_locks = 0;
651 /* XXX
652 * Not sure about that: Is it possible to reserve a handle
653 * but with no memory attached ? XMS specs are unclear on
654 * that point. Linux implementation does it this way.
656 if (req_siz == 0) {
657 /* This handle is reserved, but has size 0 and no address */
658 xms_hand[hindx].addr = XMS_NULL_ALLOC;
659 } else {
660 if ((mem = malloc(req_siz)) == NULL)
661 fatal("XMS: Cannot malloc !");
662 xms_hand[hindx].addr = (u_long)mem;
664 xms_free_mem -= req_siz;
665 xms_used_mem += req_siz;
666 num_free_handle--;
667 R_AX = 0x1;
668 R_DX = hnum;
669 R_BL = XMS_SUCCESS;
670 debug(D_XMS, " Allocated %d kB, handle %d\n",
671 req_siz / 1024, hnum);
672 break;
675 case XMS_FREE_EXTENDED_MEMORY:
677 int hnum, hindx;
679 debug(D_XMS, "XMS: Free EMM: ");
680 hnum = R_DX;
681 if (hnum > NUM_HANDLES || hnum == 0) {
682 R_AX = 0x0;
683 R_BL = XMS_INVALID_HANDLE;
684 debug(D_XMS, " Invalid handle\n");
685 break;
687 hindx = hnum - 1;
689 if (xms_hand[hindx].addr == 0) {
690 R_AX = 0x0;
691 R_BL = XMS_INVALID_HANDLE;
692 debug(D_XMS, " Invalid handle\n");
694 } else if (xms_hand[hindx].num_locks > 0) {
695 R_AX = 0x0;
696 R_BL = XMS_BLOCK_IS_LOCKED;
697 debug(D_XMS, " Is locked\n");
699 } else {
700 if (xms_hand[hindx].addr != XMS_NULL_ALLOC) {
701 free((void *)xms_hand[hindx].addr);
702 xms_free_mem += xms_hand[hindx].size;
703 xms_used_mem -= xms_hand[hindx].size;
705 xms_hand[hindx].addr = 0;
706 xms_hand[hindx].size = 0;
707 xms_hand[hindx].num_locks = 0;
708 num_free_handle++;
709 debug(D_XMS, " Success for handle %d\n", hnum);
710 R_AX = 0x1;
711 R_BL = XMS_SUCCESS;
713 break;
716 case XMS_MOVE_EXTENDED_MEMORY_BLOCK:
718 u_long srcptr, dstptr;
719 u_long srcoffs, dstoffs;
720 int srcidx, dstidx;
721 const struct EMM *eptr;
722 int n;
724 debug(D_XMS, "XMS: Move EMM block: ");
725 eptr = (struct EMM *)MAKEPTR(R_DS, R_SI);
727 /* Sanity check: Don't allow eptr pointing to emulator data */
728 if (((u_long)eptr + sizeof(struct EMM)) >= 0x100000) {
729 R_AX = 0x0;
730 R_BL = XMS_GENERAL_ERROR;
731 debug(D_XMS, " Offset to EMM structure wrong\n");
732 break;
735 /* Validate handles and offsets */
737 if (eptr->src_handle > NUM_HANDLES) {
738 R_AX = 0x0;
739 R_BL = XMS_INVALID_SOURCE_HANDLE;
740 debug(D_XMS, " Invalid handle\n");
741 break;
743 if (eptr->dst_handle > NUM_HANDLES) {
744 R_AX = 0x0;
745 R_BL = XMS_INVALID_DESTINATION_HANDLE;
746 debug(D_XMS, " Invalid handle\n");
747 break;
749 srcidx = eptr->src_handle - 1;
750 dstidx = eptr->dst_handle - 1;
751 srcoffs = eptr->src_offset;
752 dstoffs = eptr->dst_offset;
753 n = eptr->nbytes;
754 /* Length must be even, see XMS spec */
755 if (n & 1) {
756 R_AX = 0x0;
757 R_BL = XMS_INVALID_LENGTH;
758 debug(D_XMS, " Length not even\n");
759 break;
761 if (eptr->src_handle != 0) {
762 srcptr = xms_hand[srcidx].addr;
763 if (srcptr == 0 || srcptr == XMS_NULL_ALLOC) {
764 R_AX = 0x0;
765 R_BL = XMS_INVALID_SOURCE_HANDLE;
766 debug(D_XMS, " Invalid source handle\n");
767 break;
769 if ((srcoffs + n) > xms_hand[srcidx].size) {
770 R_AX = 0x0;
771 R_BL = XMS_INVALID_SOURCE_OFFSET;
772 debug(D_XMS, " Invalid source offset\n");
773 break;
775 srcptr += srcoffs;
776 } else {
777 srcptr = VECPTR(srcoffs);
778 /* Sanity check: Don't allow srcptr pointing to
779 * emulator data above 1M
781 if ((srcptr + n) >= 0x100000) {
782 R_AX = 0x0;
783 R_BL = XMS_GENERAL_ERROR;
784 debug(D_XMS, " Source segment invalid\n");
785 break;
789 if (eptr->dst_handle != 0) {
790 dstptr = xms_hand[dstidx].addr;
791 if (dstptr == 0 || dstptr == XMS_NULL_ALLOC) {
792 R_AX = 0x0;
793 R_BL = XMS_INVALID_DESTINATION_HANDLE;
794 debug(D_XMS, " Invalid dest handle\n");
795 break;
797 if ((dstoffs + n) > xms_hand[dstidx].size) {
798 R_AX = 0x0;
799 R_BL = XMS_INVALID_DESTINATION_OFFSET;
800 debug(D_XMS, " Invalid dest offset\n");
801 break;
803 dstptr += dstoffs;
804 } else {
805 dstptr = VECPTR(dstoffs);
806 /* Sanity check: Don't allow dstptr pointing to
807 * emulator data above 1M
809 if ((dstptr + n) >= 0x100000) {
810 R_AX = 0x0;
811 R_BL = XMS_GENERAL_ERROR;
812 debug(D_XMS, " Dest segment invalid\n");
813 break;
816 memmove((void *)dstptr, (void *)srcptr, n);
817 debug(D_XMS, "Moved from %08lx to %08lx, %04x bytes\n",
818 srcptr, dstptr, n);
819 R_AX = 0x1;
820 R_BL = XMS_SUCCESS;
821 break;
824 case XMS_LOCK_EXTENDED_MEMORY_BLOCK:
826 int hnum,hindx;
828 debug(D_XMS, "XMS: Lock EMM block\n");
829 hnum = R_DX;
830 if (hnum > NUM_HANDLES || hnum == 0) {
831 R_AX = 0x0;
832 R_BL = XMS_INVALID_HANDLE;
833 break;
835 hindx = hnum - 1;
836 if (xms_hand[hindx].addr == 0) {
837 R_AX = 0x0;
838 R_BL = XMS_INVALID_HANDLE;
839 break;
841 if (xms_hand[hindx].num_locks == 255) {
842 R_AX = 0x0;
843 R_BL = XMS_BLOCK_LOCKCOUNT_OVERFLOW;
844 break;
846 xms_hand[hindx].num_locks++;
847 R_AX = 0x1;
849 * The 32 bit "physical" address is returned here. I hope
850 * the solution to simply return the linear address of the
851 * malloced area is good enough. Most DOS programs won't
852 * need this anyway. It could be important for future DPMI.
854 R_BX = xms_hand[hindx].addr & 0xffff;
855 R_DX = (xms_hand[hindx].addr & 0xffff0000) >> 16;
856 break;
859 case XMS_UNLOCK_EXTENDED_MEMORY_BLOCK:
861 int hnum,hindx;
863 debug(D_XMS, "XMS: Unlock EMM block\n");
864 hnum = R_DX;
865 if (hnum > NUM_HANDLES || hnum == 0) {
866 R_AX = 0x0;
867 R_BL = XMS_INVALID_HANDLE;
868 break;
870 hindx = hnum - 1;
871 if (xms_hand[hindx].addr == 0) {
872 R_AX = 0x0;
873 R_BL = XMS_INVALID_HANDLE;
874 break;
876 if (xms_hand[hindx].num_locks == 0) {
877 R_AX = 0x0;
878 R_BL = XMS_BLOCK_NOT_LOCKED;
879 break;
881 xms_hand[hindx].num_locks--;
882 R_AX = 0x1;
883 R_BL = XMS_SUCCESS;
884 break;
887 case XMS_GET_EMB_HANDLE_INFORMATION:
889 int hnum,hindx;
891 debug(D_XMS, "XMS: Get handle information: DX=%04x\n", R_DX);
892 hnum = R_DX;
893 if (hnum > NUM_HANDLES || hnum == 0) {
894 R_AX = 0x0;
895 R_BL = XMS_INVALID_HANDLE;
896 break;
898 hindx = hnum - 1;
899 if (xms_hand[hindx].addr == 0) {
900 R_AX = 0x0;
901 R_BL = XMS_INVALID_HANDLE;
902 break;
904 R_AX = 0x1;
905 R_BH = xms_hand[hindx].num_locks;
906 R_BL = num_free_handle;
907 R_DX = xms_hand[hindx].size / 1024;
908 break;
911 case XMS_RESIZE_EXTENDED_MEMORY_BLOCK:
913 int hnum,hindx;
914 size_t req_siz;
915 long sizediff;
916 void *mem;
918 debug(D_XMS, "XMS: Resize EMM block\n");
919 hnum = R_DX;
920 req_siz = R_BX * 1024;
921 if (hnum > NUM_HANDLES || hnum == 0) {
922 R_AX = 0x0;
923 R_BL = XMS_INVALID_HANDLE;
924 break;
926 hindx = hnum - 1;
927 if (xms_hand[hindx].addr == 0) {
928 R_AX = 0x0;
929 R_BL = XMS_INVALID_HANDLE;
930 break;
932 if (xms_hand[hindx].num_locks > 0) {
933 R_AX = 0x0;
934 R_BL = XMS_BLOCK_IS_LOCKED;
935 break;
937 sizediff = req_siz - xms_hand[hindx].size;
939 if (sizediff > 0) {
940 if ((sizediff + xms_used_mem) > xms_maxsize) {
941 R_AX = 0x0;
942 R_BL = XMS_FULL;
943 break;
947 if (sizediff == 0) { /* Never trust DOS programs */
948 R_AX = 0x1;
949 R_BL = XMS_SUCCESS;
950 break;
953 xms_used_mem += sizediff;
954 xms_free_mem -= sizediff;
956 if (xms_hand[hindx].addr == XMS_NULL_ALLOC) {
957 if ((mem = malloc(req_siz)) == NULL)
958 fatal("XMS: Cannot malloc !");
959 xms_hand[hindx].addr = (u_long)mem;
960 xms_hand[hindx].size = req_siz;
961 } else {
962 if ((mem = realloc((void *)xms_hand[hindx].addr,req_siz))
963 == NULL)
964 fatal("XMS: Cannot realloc !");
965 xms_hand[hindx].addr = (u_long)mem;
966 xms_hand[hindx].size = req_siz;
969 R_AX = 0x1;
970 R_BL = XMS_SUCCESS;
971 break;
974 case XMS_ALLOCATE_UMB:
976 u_long req_siz;
977 UMB_block *bp;
979 debug(D_XMS, "XMS: Allocate UMB: DX=%04x\n", R_DX);
980 req_siz = R_DX * 16;
981 /* Some programs try to allocate 0 bytes. XMS spec says
982 * nothing about this. So the driver grants the request
983 * but it rounds up to the next paragraph size (1) and
984 * returns this amount of memory
986 if (req_siz == 0)
987 req_siz = 0x10;
989 /* First try to find an exact fit */
990 if ((bp = find_exact_block(req_siz)) != NULL) {
991 /* Found ! Move block from free list to alloc list */
992 remove_block(&UMB_freelist, bp);
993 add_block(&UMB_alloclist, bp);
994 R_AX = 0x1;
995 R_DX = req_siz >> 4;
996 R_BX = bp->addr >> 4;
997 break;
999 /* Try to find a block big enough */
1000 bp = find_block(req_siz);
1001 if (bp == NULL) {
1002 R_AX = 0x0;
1003 R_BL = XMS_NO_UMBS_AVAILABLE;
1004 R_DX = 0x0;
1006 } else if (bp->size < req_siz) {
1007 R_AX = 0x0;
1008 R_BL = XMS_REQUESTED_UMB_TOO_BIG;
1009 R_DX = bp->size / 16;
1011 } else {
1012 UMB_block *newbp;
1013 /* Found a block large enough. Split it into the size
1014 * we need, rest remains on the free list. New block
1015 * goes to the alloc list
1017 newbp = create_block(bp->addr, req_siz);
1018 bp->addr += req_siz;
1019 bp->size -= req_siz;
1020 add_block(&UMB_alloclist, newbp);
1021 R_AX = 0x1;
1022 R_BX = newbp->addr >> 4;
1023 R_DX = req_siz / 16;
1025 break;
1028 case XMS_DEALLOCATE_UMB:
1030 u_long req_addr;
1031 UMB_block *blk;
1033 debug(D_XMS, "XMS: Deallocate UMB: DX=%04x\n", R_DX);
1034 req_addr = R_DX << 4;
1035 if ((blk = find_allocated_block(req_addr)) == NULL) {
1036 R_AX = 0x0;
1037 R_BL = XMS_INVALID_UMB_SEGMENT;
1038 } else {
1039 /* Move the block from the alloc list to the free list
1040 * and try to do garbage collection
1042 remove_block(&UMB_alloclist, blk);
1043 add_block(&UMB_freelist, blk);
1044 merge_blocks();
1045 R_AX = 0x1;
1046 R_BL = XMS_SUCCESS;
1048 break;
1053 * If the option DOS=UMB is enabled, DOS grabs the entire UMB
1054 * at boot time. In any other case this is used to load resident
1055 * utilities. I don't think this function is neccesary here.
1057 case XMS_REALLOCATE_UMB:
1058 debug(D_XMS, "XMS: Reallocate UMB\n");
1059 R_AX = 0x0;
1060 R_BL = XMS_NOT_IMPLEMENTED;
1061 break;
1063 /* Some test programs use this call */
1064 case XMS_QUERY_FREE_EXTENDED_MEMORY_LARGE:
1065 /* DOS MEM.EXE chokes, if the HMA is enabled and the reported
1066 * free space includes the HMA. So we subtract 64kB from the
1067 * space reported, if the HMA is enabled.
1069 if (HMA_a20 < 0)
1070 R_EAX = R_EDX = xms_free_mem / 1024;
1071 else
1072 R_EAX = R_EDX = (xms_free_mem / 1024) - 64;
1073 /* ECX should return the highest address of any memory block
1074 * We return 1MB + size of extended memory
1076 R_ECX = 1024 * 1024 + xms_maxsize -1;
1077 if (xms_free_mem == 0)
1078 R_BL = XMS_FULL;
1079 else
1080 R_BL = XMS_SUCCESS;
1081 debug(D_XMS, "XMS: Query free EMM(large): Returned %dkB\n", R_AX);
1082 break;
1084 /* These are the same as the above functions, but they use 32 bit
1085 * registers (i.e. EDX instead of DX). This is for allocations of
1086 * more than 64MB. I think this will hardly be used in the emulator
1087 * It seems to work without them, but the functions are in the XMS 3.0
1088 * spec. If something breaks because they are not here, I can implement
1089 * them
1091 case XMS_ALLOCATE_EXTENDED_MEMORY_LARGE:
1092 case XMS_FREE_EXTENDED_MEMORY_LARGE:
1093 debug(D_XMS, "XMS: %02x function called, not implemented\n", R_AH);
1094 R_AX = 0x0;
1095 R_BL = XMS_NOT_IMPLEMENTED;
1096 break;
1098 default:
1099 debug(D_ALWAYS, "XMS: Unimplemented function %02x, \n", R_AH);
1100 R_AX = 0;
1101 R_BL = XMS_NOT_IMPLEMENTED;
1102 break;