Merge branch 'vendor/OPENSSL'
[dragonfly.git] / usr.bin / doscmd / xms.c
1 /*-
2  * Copyright (c) 1997 Helmut Wirth <hfwirth@ping.at>
3  * All rights reserved.
4  *
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.
16  *
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.
27  *
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 $
30  */
31
32 /*
33  * XMS memory manmagement 
34  *
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.
39  *
40  * Credits to:
41  *      The original author of this file, some parts are still here
42  *      Linux dosemu programmers. I looked into their code.
43  */
44
45 #include <sys/types.h>
46 #include <sys/param.h>
47 #include <sys/mman.h>
48 #include <unistd.h>
49
50 #include "doscmd.h"
51 #include "xms.h"
52
53
54 /* Extended memory handle management */
55
56 static XMS_handle xms_hand[NUM_HANDLES];
57 int num_free_handle = NUM_HANDLES;
58
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;
64
65 /* Address entry for zero size allocated handles */
66 #define XMS_NULL_ALLOC 0xffffffff
67
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;
72
73 /* high memory mapfiles */
74 static char memfile[] = "/tmp/doscmd.XXXXXX";
75
76 /* Upper memory block (UMB) management */
77 UMB_block *UMB_freelist = NULL;
78 UMB_block *UMB_alloclist = NULL;
79
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 */
90 };
91
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);
100
101 /* Init the entire module */
102 void
103 xms_init(void)
104 {
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 */
116
117 /* This is used as window for EMS, will be configurable ! */
118 /*    add_block(&UMB_freelist, create_block(0xe0000, 64*1024)); */
119
120     merge_blocks();
121
122     xms_vector = insert_generic_trampoline(
123         sizeof(xms_trampoline), xms_trampoline);
124     register_callback(xms_vector + 5, xms_entry, "xms");
125 }
126
127 /*
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.
137  */
138
139 /* Add a block to a list, maintain ascending start address order */
140
141 static void
142 add_block(UMB_block **listp, UMB_block *blk)
143 {
144     UMB_block *bp, *obp;
145
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;
157         }
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;
167             }
168         }
169         /* Append to the end of the list */
170         obp->next = blk;
171         blk->next = NULL;
172     }
173     return;
174 }
175
176 /* Find a block with address addr in the alloc list */
177 static UMB_block *
178 find_allocated_block(u_long addr)
179 {
180     UMB_block *bp;
181
182     if (UMB_alloclist == NULL)
183         return NULL;
184
185     for (bp = UMB_alloclist; bp != NULL; bp = bp->next)
186         if (bp->addr == addr)
187             return bp;
188     return NULL;
189 }
190
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)
194 {
195     UMB_block *bp;
196
197     if (*listp == NULL)
198         goto faterr;
199
200     if (*listp == blk) {
201         *listp = (*listp)->next;
202         return;
203     }
204     bp = *listp;
205     do {
206         if (bp->next == blk) {
207             bp->next = bp->next->next;
208             return;
209         }
210         bp = bp->next;
211     } while(bp != NULL);
212 faterr:
213     fatal("XMS: UMB remove_block did not find block\n");
214 }
215
216 /* Try to merge neighbouring blocks in the free list */
217 static void
218 merge_blocks(void)
219 {
220     UMB_block *bp;
221     u_long endaddr;
222
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; 
237         }
238     } while (bp != NULL);
239 }
240
241 /* Try to find a free block of size exactly siz */ 
242 static UMB_block *
243 find_exact_block(u_long siz)
244 {
245     UMB_block *bp;
246
247     if (UMB_freelist == NULL)
248         return NULL;
249
250     for (bp = UMB_freelist; bp != NULL; bp = bp->next)
251         if (bp->size == siz)
252             return bp;
253     return NULL;
254 }
255
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
259  */
260 static UMB_block *
261 find_block(u_long siz)
262 {
263     UMB_block *bp;
264     UMB_block *biggest = NULL;
265
266     if (UMB_freelist == NULL)
267         return NULL;
268
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;
275         }
276         if (biggest->size < bp->size)
277             biggest = bp;
278     }
279     return biggest;
280 }
281
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)
286 {
287     UMB_block *blk;
288
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;
295
296
297
298 /*
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.)
316  *
317  * This routine is called from doscmd.c at startup. A20 is disabled after
318  * startup.
319  */
320
321 void initHMA(void)
322 {
323     int mfd;
324
325     /*
326      * We need two files, one for the wrap around mapping and one 
327      * for the HMA contents
328      */
329
330     mfd = mkstemp(memfile);
331
332     if (mfd < 0) {
333         fprintf(stderr, "memfile: %s\n", strerror(errno));
334         fprintf(stderr, "High memory will not be mapped\n");
335
336         /* We need this for XMS services. If it fails, turn HMA off */
337         HMA_a20 = -1;
338         return;
339     }
340     unlink(memfile);
341     HMA_fd_off = squirrel_fd(mfd);
342
343     lseek(HMA_fd_off, 64 * 1024 - 1, 0);
344     write(HMA_fd_off, "", 1);
345
346     mfd = mkstemp(memfile);
347
348     if (mfd < 0) {
349         fprintf(stderr, "memfile: %s\n", strerror(errno));
350         fprintf(stderr, "High memory will not be mapped\n");
351
352         /* We need this for XMS services. If it fails, turn HMA off */
353         HMA_a20 = -1;
354         return;
355     }
356     unlink(memfile);
357     HMA_fd_on = squirrel_fd(mfd);
358
359     lseek(HMA_fd_on, 64 * 1024 - 1, 0);
360     write(HMA_fd_on, "", 1);
361
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;
371     }
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;
381     }
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;
391     }
392     HMA_a20 = 0;
393 }
394
395
396 /* Enable the a20 "address line" by unmapping the 64kB over 1MB */
397 static void enable_a20(void)
398 {
399     if (HMA_a20 < 0)
400         return;
401
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.
406      */
407 #if 1
408     if (munmap((caddr_t)0x100000, 64 * 1024) < 0) {
409         fatal("HMA unmapping error: %s\nCannot recover\n", strerror(errno));
410     }
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));
418     }
419 }
420
421 /* Disable the a20 "address line" by mapping the 64kB over 1MB again */
422 static void disable_a20(void)
423 {
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));
430     }
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));
438     }
439 }
440
441
442 /*
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."
447  */
448
449 void
450 get_raw_extmemory_info(regcontext_t *REGS)
451 {
452     if (vec_grabbed)
453         R_AX = 0x0;
454     else 
455         R_AX = xms_maxsize / 1024;
456     return;
457 }
458
459
460 /* Handle management routine: Find next free handle */
461
462 static int
463 get_free_handle(void)
464 {
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;
470     }
471     return 0;
472 }
473
474
475 /* Installation check */
476 int
477 int2f_43(regcontext_t *REGS)
478 {               
479
480     switch (R_AL) {
481     case 0x00:                  /* installation check */
482         R_AL = 0x80;
483         break;
484
485     case 0x10:                  /* get handler address */
486         PUTVEC(R_ES, R_BX, xms_vector);
487         break;
488
489     default:
490         return (0);
491     }
492     return (1);
493 }
494
495 /* Main call entry point for the XMS handler from DOS */
496 static void
497 xms_entry(regcontext_t *REGS)
498 {
499
500     if (R_AH != 0)
501         vec_grabbed = 1;
502
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;
512         }
513     }
514
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;
522
523     /*
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.
528      */
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;
538         }
539         break;
540
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;
550         }
551         break;
552
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;
561
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;
570
571    /* 
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.
575     */
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;
584
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;
594
595     case XMS_QUERY_A20:
596     /*
597      * Disabled because DOS permanently scans this, to avoid endless output.
598      */
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;
605
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.
610          */
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;
615
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;
622
623     case XMS_ALLOCATE_EXTENDED_MEMORY:
624         {
625             size_t req_siz;
626             int hindx, hnum;
627             void *mem;
628
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;
636             }
637             hindx = hnum - 1;
638             req_siz = R_DX * 1024;
639
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;
646             }
647
648             xms_hand[hindx].size = req_siz;
649             xms_hand[hindx].num_locks = 0;
650
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.
655              */
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;
663             }
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;
673         }       
674
675     case XMS_FREE_EXTENDED_MEMORY:
676         {
677             int hnum, hindx;
678
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;
686             }
687             hindx = hnum - 1;
688
689             if (xms_hand[hindx].addr == 0) {
690                 R_AX = 0x0;
691                 R_BL = XMS_INVALID_HANDLE;
692                 debug(D_XMS, " Invalid handle\n");
693
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");
698
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;
704                 }
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;
712             }
713             break;
714         }
715
716     case XMS_MOVE_EXTENDED_MEMORY_BLOCK:
717         {
718             u_long srcptr, dstptr;
719             u_long srcoffs, dstoffs;
720             int srcidx, dstidx;
721             const struct EMM *eptr;
722             int n;
723
724             debug(D_XMS, "XMS: Move EMM block: ");
725             eptr = (struct EMM *)MAKEPTR(R_DS, R_SI);
726
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;
733             }
734
735             /* Validate handles and offsets */
736
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;
742             }
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;
748             }
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;
760             }
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;
768                 }
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;
774                 }
775                 srcptr += srcoffs;
776             } else {
777                 srcptr = VECPTR(srcoffs);
778                 /* Sanity check: Don't allow srcptr pointing to 
779                  * emulator data above 1M
780                  */
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;
786                 }
787             }
788
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;
796                 }
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;
802                 }
803                 dstptr += dstoffs;
804             } else {
805                 dstptr = VECPTR(dstoffs);
806                 /* Sanity check: Don't allow dstptr pointing to 
807                  * emulator data above 1M
808                  */
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;
814                 }
815             }
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;
822         }
823
824     case XMS_LOCK_EXTENDED_MEMORY_BLOCK:
825         {
826             int hnum,hindx;
827
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;
834             }
835             hindx = hnum - 1;
836             if (xms_hand[hindx].addr == 0) {
837                 R_AX = 0x0;
838                 R_BL = XMS_INVALID_HANDLE;
839                 break;
840             }
841             if (xms_hand[hindx].num_locks == 255) {
842                 R_AX = 0x0;
843                 R_BL = XMS_BLOCK_LOCKCOUNT_OVERFLOW;
844                 break;
845             }
846             xms_hand[hindx].num_locks++;
847             R_AX = 0x1;
848             /* 
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.
853              */
854             R_BX = xms_hand[hindx].addr & 0xffff;
855             R_DX = (xms_hand[hindx].addr & 0xffff0000) >> 16;
856             break;
857         }
858
859     case XMS_UNLOCK_EXTENDED_MEMORY_BLOCK:
860         {
861             int hnum,hindx;
862
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;
869             }
870             hindx = hnum - 1;
871             if (xms_hand[hindx].addr == 0) {
872                 R_AX = 0x0;
873                 R_BL = XMS_INVALID_HANDLE;
874                 break;
875             }
876             if (xms_hand[hindx].num_locks == 0) {
877                 R_AX = 0x0;
878                 R_BL = XMS_BLOCK_NOT_LOCKED;
879                 break;
880             }
881             xms_hand[hindx].num_locks--;
882             R_AX = 0x1;
883             R_BL = XMS_SUCCESS;
884             break;
885         }
886
887     case XMS_GET_EMB_HANDLE_INFORMATION:
888         {
889             int hnum,hindx;
890
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;
897             }
898             hindx = hnum - 1;
899             if (xms_hand[hindx].addr == 0) {
900                 R_AX = 0x0;
901                 R_BL = XMS_INVALID_HANDLE;
902                 break;
903             }
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;
909         }
910
911     case XMS_RESIZE_EXTENDED_MEMORY_BLOCK:
912         {
913             int hnum,hindx;
914             size_t req_siz;
915             long sizediff;
916             void *mem;
917
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;
925             }
926             hindx = hnum - 1;
927             if (xms_hand[hindx].addr == 0) {
928                 R_AX = 0x0;
929                 R_BL = XMS_INVALID_HANDLE;
930                 break;
931             }
932             if (xms_hand[hindx].num_locks > 0) {
933                 R_AX = 0x0;
934                 R_BL = XMS_BLOCK_IS_LOCKED;
935                 break;
936             }
937             sizediff = req_siz - xms_hand[hindx].size;
938
939             if (sizediff > 0) {
940                 if ((sizediff + xms_used_mem) > xms_maxsize) {
941                     R_AX = 0x0;
942                     R_BL = XMS_FULL;
943                     break;
944                 }
945             }
946
947             if (sizediff == 0) {        /* Never trust DOS programs */
948                 R_AX = 0x1;
949                 R_BL = XMS_SUCCESS;
950                 break;
951             }
952
953             xms_used_mem += sizediff;
954             xms_free_mem -= sizediff;
955
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;
967             }
968
969             R_AX = 0x1;
970             R_BL = XMS_SUCCESS;
971             break;
972         }
973
974     case XMS_ALLOCATE_UMB:
975         {
976             u_long req_siz;
977             UMB_block *bp;
978
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
985              */
986             if (req_siz == 0)
987                 req_siz = 0x10;
988
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;
998             }
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;
1005
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;
1010
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
1016                  */
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;
1024             }
1025             break;
1026         }
1027
1028     case XMS_DEALLOCATE_UMB:
1029         {
1030             u_long req_addr;
1031             UMB_block *blk;
1032
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
1041                  */
1042                 remove_block(&UMB_alloclist, blk);
1043                 add_block(&UMB_freelist, blk);
1044                 merge_blocks();
1045                 R_AX = 0x1;
1046                 R_BL = XMS_SUCCESS;
1047             }
1048             break;
1049         }
1050
1051
1052     /* 
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.
1056      */
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;
1062
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.
1068          */
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 
1075          */
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;
1083
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
1090      */
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;
1097
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;
1103     }
1104 }