blob: f6839c96a72b0cb5e5b3ef2a3c2272d3500c8620 [file] [log] [blame]
Thomas G. Lane4a6b7301992-03-17 00:00:00 +00001/*
2 * jmemdos.c (jmemsys.c)
3 *
4 * Copyright (C) 1992, Thomas G. Lane.
5 * This file is part of the Independent JPEG Group's software.
6 * For conditions of distribution and use, see the accompanying README file.
7 *
8 * This file provides an MS-DOS-compatible implementation of the system-
9 * dependent portion of the JPEG memory manager. Temporary data can be
10 * stored in extended or expanded memory as well as in regular DOS files.
11 *
12 * If you use this file, you must be sure that NEED_FAR_POINTERS is defined
13 * if you compile in a small-data memory model; it should NOT be defined if
14 * you use a large-data memory model. This file is not recommended if you
15 * are using a flat-memory-space 386 environment such as DJGCC or Watcom C.
16 *
17 * Based on code contributed by Ge' Weijers.
18 */
19
20/*
21 * If you have both extended and expanded memory, you may want to change the
22 * order in which they are tried in jopen_backing_store. On a 286 machine
23 * expanded memory is usually faster, since extended memory access involves
24 * an expensive protected-mode-and-back switch. On 386 and better, extended
25 * memory is usually faster. As distributed, the code tries extended memory
26 * first (what? not everyone has a 386? :-).
27 *
28 * You can disable use of extended/expanded memory entirely by altering these
29 * definitions or overriding them from the Makefile (eg, -DEMS_SUPPORTED=0).
30 */
31
32#ifndef XMS_SUPPORTED
33#define XMS_SUPPORTED 1
34#endif
35#ifndef EMS_SUPPORTED
36#define EMS_SUPPORTED 1
37#endif
38
39
40#include "jinclude.h"
41#include "jmemsys.h"
42
43#ifdef INCLUDES_ARE_ANSI
44#include <stdlib.h> /* to declare malloc(), free() */
45#else
46extern void * malloc PP((size_t size));
47extern void free PP((void *ptr));
48#endif
49
50#ifdef NEED_FAR_POINTERS
51
52#ifdef __TURBOC__
53/* These definitions work for Borland C (Turbo C) */
54#include <alloc.h> /* need farmalloc(), farfree() */
55#define far_malloc(x) farmalloc(x)
56#define far_free(x) farfree(x)
57#else
58/* These definitions work for Microsoft C and compatible compilers */
59#include <malloc.h> /* need _fmalloc(), _ffree() */
60#define far_malloc(x) _fmalloc(x)
61#define far_free(x) _ffree(x)
62#endif
63
64#endif
65
66#ifdef DONT_USE_B_MODE /* define mode parameters for fopen() */
67#define READ_BINARY "r"
68#else
69#define READ_BINARY "rb"
70#endif
71
72
73/*
74 * Declarations for assembly-language support routines (see jmemdosa.asm).
75 *
76 * The functions are declared "far" as are all pointer arguments;
77 * this ensures the assembly source code will work regardless of the
78 * compiler memory model. We assume "short" is 16 bits, "long" is 32.
79 */
80
81typedef void far * XMSDRIVER; /* actually a pointer to code */
82typedef struct { /* registers for calling XMS driver */
83 unsigned short ax, dx, bx;
84 void far * ds_si;
85 } XMScontext;
86typedef struct { /* registers for calling EMS driver */
87 unsigned short ax, dx, bx;
88 void far * ds_si;
89 } EMScontext;
90
91EXTERN short far jdos_open PP((short far * handle, char far * filename));
92EXTERN short far jdos_close PP((short handle));
93EXTERN short far jdos_seek PP((short handle, long offset));
94EXTERN short far jdos_read PP((short handle, void far * buffer,
95 unsigned short count));
96EXTERN short far jdos_write PP((short handle, void far * buffer,
97 unsigned short count));
98EXTERN void far jxms_getdriver PP((XMSDRIVER far *));
99EXTERN void far jxms_calldriver PP((XMSDRIVER, XMScontext far *));
100EXTERN short far jems_available PP((void));
101EXTERN void far jems_calldriver PP((EMScontext far *));
102
103
104static external_methods_ptr methods; /* saved for access to error_exit */
105
106static long total_used; /* total FAR memory requested so far */
107
108
109/*
110 * Selection of a file name for a temporary file.
111 * This is highly system-dependent, and you may want to customize it.
112 */
113
114static int next_file_num; /* to distinguish among several temp files */
115
116LOCAL void
117select_file_name (char * fname)
118{
119 const char * env;
120 char * ptr;
121 FILE * tfile;
122
123 /* Keep generating file names till we find one that's not in use */
124 for (;;) {
125 /* Get temp directory name from environment TMP or TEMP variable;
126 * if none, use "."
127 */
128 if ((env = (const char *) getenv("TMP")) == NULL)
129 if ((env = (const char *) getenv("TEMP")) == NULL)
130 env = ".";
131 if (*env == '\0') /* null string means "." */
132 env = ".";
133 ptr = fname; /* copy name to fname */
134 while (*env != '\0')
135 *ptr++ = *env++;
136 if (ptr[-1] != '\\' && ptr[-1] != '/')
137 *ptr++ = '\\'; /* append backslash if not in env variable */
138 /* Append a suitable file name */
139 next_file_num++; /* advance counter */
140 sprintf(ptr, "JPG%03d.TMP", next_file_num);
141 /* Probe to see if file name is already in use */
142 if ((tfile = fopen(fname, READ_BINARY)) == NULL)
143 break;
144 fclose(tfile); /* oops, it's there; close tfile & try again */
145 }
146}
147
148
149/*
150 * Near-memory allocation and freeing are controlled by the regular library
151 * routines malloc() and free().
152 */
153
154GLOBAL void *
155jget_small (size_t sizeofobject)
156{
157 /* near data space is NOT counted in total_used */
158#ifndef NEED_FAR_POINTERS
159 total_used += sizeofobject;
160#endif
161 return (void *) malloc(sizeofobject);
162}
163
164GLOBAL void
165jfree_small (void * object)
166{
167 free(object);
168}
169
170
171/*
172 * Far-memory allocation and freeing
173 */
174
175#ifdef NEED_FAR_POINTERS
176
177GLOBAL void FAR *
178jget_large (size_t sizeofobject)
179{
180 total_used += sizeofobject;
181 return (void FAR *) far_malloc(sizeofobject);
182}
183
184GLOBAL void
185jfree_large (void FAR * object)
186{
187 far_free(object);
188}
189
190#endif
191
192
193/*
194 * This routine computes the total memory space available for allocation.
195 * It's impossible to do this in a portable way; our current solution is
196 * to make the user tell us (with a default value set at compile time).
197 * If you can actually get the available space, it's a good idea to subtract
198 * a slop factor of 5% or so.
199 */
200
201#ifndef DEFAULT_MAX_MEM /* so can override from makefile */
202#define DEFAULT_MAX_MEM 300000L /* for total usage about 450K */
203#endif
204
205GLOBAL long
206jmem_available (long min_bytes_needed, long max_bytes_needed)
207{
208 return methods->max_memory_to_use - total_used;
209}
210
211
212/*
213 * Backing store (temporary file) management.
214 * Backing store objects are only used when the value returned by
215 * jmem_available is less than the total space needed. You can dispense
216 * with these routines if you have plenty of virtual memory; see jmemnobs.c.
217 */
218
219/*
220 * For MS-DOS we support three types of backing storage:
221 * 1. Conventional DOS files. We access these by direct DOS calls rather
222 * than via the stdio package. This provides a bit better performance,
223 * but the real reason is that the buffers to be read or written are FAR.
224 * The stdio library for small-data memory models can't cope with that.
225 * 2. Extended memory, accessed per the XMS V2.0 specification.
226 * 3. Expanded memory, accessed per the LIM/EMS 4.0 specification.
227 * You'll need copies of those specs to make sense of the related code.
228 * The specs are available by Internet FTP from SIMTEL20 and its various
229 * mirror sites; see microsoft/xms20.arc and info/limems41.zip.
230 */
231
232
233/*
234 * Access methods for a DOS file.
235 */
236
237
238METHODDEF void
239read_file_store (backing_store_ptr info, void FAR * buffer_address,
240 long file_offset, long byte_count)
241{
242 if (jdos_seek(info->handle.file_handle, file_offset))
243 ERREXIT(methods, "seek failed on temporary file");
244 /* Since MAX_ALLOC_CHUNK is less than 64K, byte_count will be too. */
245 if (byte_count > 65535L) /* safety check */
246 ERREXIT(methods, "MAX_ALLOC_CHUNK should be less than 64K");
247 if (jdos_read(info->handle.file_handle, buffer_address,
248 (unsigned short) byte_count))
249 ERREXIT(methods, "read failed on temporary file");
250}
251
252
253METHODDEF void
254write_file_store (backing_store_ptr info, void FAR * buffer_address,
255 long file_offset, long byte_count)
256{
257 if (jdos_seek(info->handle.file_handle, file_offset))
258 ERREXIT(methods, "seek failed on temporary file");
259 /* Since MAX_ALLOC_CHUNK is less than 64K, byte_count will be too. */
260 if (byte_count > 65535L) /* safety check */
261 ERREXIT(methods, "MAX_ALLOC_CHUNK should be less than 64K");
262 if (jdos_write(info->handle.file_handle, buffer_address,
263 (unsigned short) byte_count))
264 ERREXIT(methods, "write failed on temporary file --- out of disk space?");
265}
266
267
268METHODDEF void
269close_file_store (backing_store_ptr info)
270{
271 jdos_close(info->handle.file_handle); /* close the file */
272 remove(info->temp_name); /* delete the file */
273/* If your system doesn't have remove(), try unlink() instead.
274 * remove() is the ANSI-standard name for this function, but
275 * unlink() was more common in pre-ANSI systems.
276 */
277 TRACEMS1(methods, 1, "Closed DOS file %d", info->handle.file_handle);
278}
279
280
281LOCAL boolean
282open_file_store (backing_store_ptr info, long total_bytes_needed)
283{
284 short handle;
285 char tracemsg[TEMP_NAME_LENGTH+40];
286
287 select_file_name(info->temp_name);
288 if (jdos_open((short far *) & handle, (char far *) info->temp_name))
289 return FALSE;
290 info->handle.file_handle = handle;
291 info->read_backing_store = read_file_store;
292 info->write_backing_store = write_file_store;
293 info->close_backing_store = close_file_store;
294 /* hack to get around TRACEMS' inability to handle string parameters */
295 sprintf(tracemsg, "Opened DOS file %d %s", handle, info->temp_name);
296 TRACEMS(methods, 1, tracemsg);
297 return TRUE; /* succeeded */
298}
299
300
301/*
302 * Access methods for extended memory.
303 */
304
305#if XMS_SUPPORTED
306
307static XMSDRIVER xms_driver; /* saved address of XMS driver */
308
309typedef union { /* either long offset or real-mode pointer */
310 long offset;
311 void far * ptr;
312 } XMSPTR;
313
314typedef struct { /* XMS move specification structure */
315 long length;
316 XMSH src_handle;
317 XMSPTR src;
318 XMSH dst_handle;
319 XMSPTR dst;
320 } XMSspec;
321
322#define ODD(X) (((X) & 1L) != 0)
323
324
325METHODDEF void
326read_xms_store (backing_store_ptr info, void FAR * buffer_address,
327 long file_offset, long byte_count)
328{
329 XMScontext ctx;
330 XMSspec spec;
331 char endbuffer[2];
332
333 /* The XMS driver can't cope with an odd length, so handle the last byte
334 * specially if byte_count is odd. We don't expect this to be common.
335 */
336
337 spec.length = byte_count & (~ 1L);
338 spec.src_handle = info->handle.xms_handle;
339 spec.src.offset = file_offset;
340 spec.dst_handle = 0;
341 spec.dst.ptr = buffer_address;
342
343 ctx.ds_si = (void far *) & spec;
344 ctx.ax = 0x0b00; /* EMB move */
345 jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
346 if (ctx.ax != 1)
347 ERREXIT(methods, "read from extended memory failed");
348
349 if (ODD(byte_count)) {
350 read_xms_store(info, (void FAR *) endbuffer,
351 file_offset + byte_count - 1L, 2L);
352 ((char FAR *) buffer_address)[byte_count - 1L] = endbuffer[0];
353 }
354}
355
356
357METHODDEF void
358write_xms_store (backing_store_ptr info, void FAR * buffer_address,
359 long file_offset, long byte_count)
360{
361 XMScontext ctx;
362 XMSspec spec;
363 char endbuffer[2];
364
365 /* The XMS driver can't cope with an odd length, so handle the last byte
366 * specially if byte_count is odd. We don't expect this to be common.
367 */
368
369 spec.length = byte_count & (~ 1L);
370 spec.src_handle = 0;
371 spec.src.ptr = buffer_address;
372 spec.dst_handle = info->handle.xms_handle;
373 spec.dst.offset = file_offset;
374
375 ctx.ds_si = (void far *) & spec;
376 ctx.ax = 0x0b00; /* EMB move */
377 jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
378 if (ctx.ax != 1)
379 ERREXIT(methods, "write to extended memory failed");
380
381 if (ODD(byte_count)) {
382 read_xms_store(info, (void FAR *) endbuffer,
383 file_offset + byte_count - 1L, 2L);
384 endbuffer[0] = ((char FAR *) buffer_address)[byte_count - 1L];
385 write_xms_store(info, (void FAR *) endbuffer,
386 file_offset + byte_count - 1L, 2L);
387 }
388}
389
390
391METHODDEF void
392close_xms_store (backing_store_ptr info)
393{
394 XMScontext ctx;
395
396 ctx.dx = info->handle.xms_handle;
397 ctx.ax = 0x0a00;
398 jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
399 TRACEMS1(methods, 1, "Freed XMS handle %u", info->handle.xms_handle);
400 /* we ignore any error return from the driver */
401}
402
403
404LOCAL boolean
405open_xms_store (backing_store_ptr info, long total_bytes_needed)
406{
407 XMScontext ctx;
408
409 /* Get address of XMS driver */
410 jxms_getdriver((XMSDRIVER far *) & xms_driver);
411 if (xms_driver == NULL)
412 return FALSE; /* no driver to be had */
413
414 /* Get version number, must be >= 2.00 */
415 ctx.ax = 0x0000;
416 jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
417 if (ctx.ax < (unsigned short) 0x0200)
418 return FALSE;
419
420 /* Try to get space (expressed in kilobytes) */
421 ctx.dx = (unsigned short) ((total_bytes_needed + 1023L) >> 10);
422 ctx.ax = 0x0900;
423 jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
424 if (ctx.ax != 1)
425 return FALSE;
426
427 /* Succeeded, save the handle and away we go */
428 info->handle.xms_handle = ctx.dx;
429 info->read_backing_store = read_xms_store;
430 info->write_backing_store = write_xms_store;
431 info->close_backing_store = close_xms_store;
432 TRACEMS1(methods, 1, "Obtained XMS handle %u", ctx.dx);
433 return TRUE; /* succeeded */
434}
435
436#endif /* XMS_SUPPORTED */
437
438
439/*
440 * Access methods for expanded memory.
441 */
442
443#if EMS_SUPPORTED
444
445typedef union { /* either offset/page or real-mode pointer */
446 struct { unsigned short offset, page; } ems;
447 void far * ptr;
448 } EMSPTR;
449
450typedef struct { /* EMS move specification structure */
451 long length;
452 char src_type; /* 1 = EMS, 0 = conventional memory */
453 EMSH src_handle; /* use 0 if conventional memory */
454 EMSPTR src;
455 char dst_type;
456 EMSH dst_handle;
457 EMSPTR dst;
458 } EMSspec;
459
460#define EMSPAGESIZE 16384L /* gospel, see the EMS specs */
461
462#define HIBYTE(W) (((W) >> 8) & 0xFF)
463#define LOBYTE(W) ((W) & 0xFF)
464
465
466METHODDEF void
467read_ems_store (backing_store_ptr info, void FAR * buffer_address,
468 long file_offset, long byte_count)
469{
470 EMScontext ctx;
471 EMSspec spec;
472
473 spec.length = byte_count;
474 spec.src_type = 1;
475 spec.src_handle = info->handle.ems_handle;
476 spec.src.ems.page = (unsigned short) (file_offset / EMSPAGESIZE);
477 spec.src.ems.offset = (unsigned short) (file_offset % EMSPAGESIZE);
478 spec.dst_type = 0;
479 spec.dst_handle = 0;
480 spec.dst.ptr = buffer_address;
481
482 ctx.ds_si = (void far *) & spec;
483 ctx.ax = 0x5700; /* move memory region */
484 jems_calldriver((EMScontext far *) & ctx);
485 if (HIBYTE(ctx.ax) != 0)
486 ERREXIT(methods, "read from expanded memory failed");
487}
488
489
490METHODDEF void
491write_ems_store (backing_store_ptr info, void FAR * buffer_address,
492 long file_offset, long byte_count)
493{
494 EMScontext ctx;
495 EMSspec spec;
496
497 spec.length = byte_count;
498 spec.src_type = 0;
499 spec.src_handle = 0;
500 spec.src.ptr = buffer_address;
501 spec.dst_type = 1;
502 spec.dst_handle = info->handle.ems_handle;
503 spec.dst.ems.page = (unsigned short) (file_offset / EMSPAGESIZE);
504 spec.dst.ems.offset = (unsigned short) (file_offset % EMSPAGESIZE);
505
506 ctx.ds_si = (void far *) & spec;
507 ctx.ax = 0x5700; /* move memory region */
508 jems_calldriver((EMScontext far *) & ctx);
509 if (HIBYTE(ctx.ax) != 0)
510 ERREXIT(methods, "write to expanded memory failed");
511}
512
513
514METHODDEF void
515close_ems_store (backing_store_ptr info)
516{
517 EMScontext ctx;
518
519 ctx.ax = 0x4500;
520 ctx.dx = info->handle.ems_handle;
521 jems_calldriver((EMScontext far *) & ctx);
522 TRACEMS1(methods, 1, "Freed EMS handle %u", info->handle.ems_handle);
523 /* we ignore any error return from the driver */
524}
525
526
527LOCAL boolean
528open_ems_store (backing_store_ptr info, long total_bytes_needed)
529{
530 EMScontext ctx;
531
532 /* Is EMS driver there? */
533 if (! jems_available())
534 return FALSE;
535
536 /* Get status, make sure EMS is OK */
537 ctx.ax = 0x4000;
538 jems_calldriver((EMScontext far *) & ctx);
539 if (HIBYTE(ctx.ax) != 0)
540 return FALSE;
541
542 /* Get version, must be >= 4.0 */
543 ctx.ax = 0x4600;
544 jems_calldriver((EMScontext far *) & ctx);
545 if (HIBYTE(ctx.ax) != 0 || LOBYTE(ctx.ax) < 0x40)
546 return FALSE;
547
548 /* Try to allocate requested space */
549 ctx.ax = 0x4300;
550 ctx.bx = (unsigned short) ((total_bytes_needed + EMSPAGESIZE-1L) / EMSPAGESIZE);
551 jems_calldriver((EMScontext far *) & ctx);
552 if (HIBYTE(ctx.ax) != 0)
553 return FALSE;
554
555 /* Succeeded, save the handle and away we go */
556 info->handle.ems_handle = ctx.dx;
557 info->read_backing_store = read_ems_store;
558 info->write_backing_store = write_ems_store;
559 info->close_backing_store = close_ems_store;
560 TRACEMS1(methods, 1, "Obtained EMS handle %u", ctx.dx);
561 return TRUE; /* succeeded */
562}
563
564#endif /* EMS_SUPPORTED */
565
566
567/*
568 * Initial opening of a backing-store object.
569 */
570
571GLOBAL void
572jopen_backing_store (backing_store_ptr info, long total_bytes_needed)
573{
574 /* Try extended memory, then expanded memory, then regular file. */
575#if XMS_SUPPORTED
576 if (open_xms_store(info, total_bytes_needed))
577 return;
578#endif
579#if EMS_SUPPORTED
580 if (open_ems_store(info, total_bytes_needed))
581 return;
582#endif
583 if (open_file_store(info, total_bytes_needed))
584 return;
585 ERREXIT(methods, "Failed to create temporary file");
586}
587
588
589/*
590 * These routines take care of any system-dependent initialization and
591 * cleanup required. Keep in mind that jmem_term may be called more than
592 * once.
593 */
594
595GLOBAL void
596jmem_init (external_methods_ptr emethods)
597{
598 methods = emethods; /* save struct addr for error exit access */
599 emethods->max_memory_to_use = DEFAULT_MAX_MEM;
600 total_used = 0;
601 next_file_num = 0;
602}
603
604GLOBAL void
605jmem_term (void)
606{
607 /* no work */
608}