summaryrefslogtreecommitdiffstats
path: root/contrib/syslinux-4.02/com32/gpllib/memory.c
blob: 28a95ff4e55ba97e52051aa5c82643f1d90e9621 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/* ----------------------------------------------------------------------- *
 *
 *   Copyright 2009 Pierre-Alexandre Meyer
 *
 *   Some parts borrowed from meminfo.c32:
 *
 *   Copyright 2003-2009 H. Peter Anvin - All Rights Reserved
 *   Copyright 2009 Intel Corporation; author: H. Peter Anvin
 *
 *   Some parts borrowed from Linux:
 *
 *   Copyright (C) 1991, 1992 Linus Torvalds
 *   Copyright 2007 rPath, Inc. - All Rights Reserved
 *   Copyright 2009 Intel Corporation; author H. Peter Anvin
 *
 *   Interrupt list from Ralf Brown (http://www.cs.cmu.edu/~ralf/files.html)
 *
 *   This file is part of Syslinux, and is made available under
 *   the terms of the GNU General Public License version 2.
 *
 * ----------------------------------------------------------------------- */

#include <stdint.h>
#include <com32.h>
#include <string.h>
#include <memory.h>

const char *const e820_types[] = {
    "usable",
    "reserved",
    "ACPI reclaim",
    "ACPI NVS",
    "unusable",
};

struct e820_ext_entry {
    struct e820entry std;
    uint32_t ext_flags;
} __attribute__ ((packed));

#define SMAP	0x534d4150	/* ASCII "SMAP" */

void get_type(int type, char *type_ptr, int type_ptr_sz)
{
    unsigned int real_type = type - 1;
    if (real_type < sizeof(e820_types) / sizeof(e820_types[0]))
	strlcpy(type_ptr, e820_types[real_type], type_ptr_sz);
}

/**
 *INT 15 - newer BIOSes - GET SYSTEM MEMORY MAP
 *	AX = E820h
 *	EAX = 0000E820h
 *	EDX = 534D4150h ('SMAP')
 *	EBX = continuation value or 00000000h to start at beginning of map
 *	ECX = size of buffer for result, in bytes (should be >= 20 bytes)
 *	ES:DI -> buffer for result (see #00581)
 *
 * Return: CF clear if successful
 *	    EAX = 534D4150h ('SMAP')
 *	    ES:DI buffer filled
 *	    EBX = next offset from which to copy or 00000000h if all done
 *	    ECX = actual length returned in bytes
 *	CF set on error
 *	    AH = error code (86h) (see #00496 at INT 15/AH=80h)
 *
 * Notes: originally introduced with the Phoenix BIOS v4.0, this function is
 *	  now supported by most newer BIOSes, since various versions of Windows
 *	  call it to find out about the system memory
 *	a maximum of 20 bytes will be transferred at one time, even if ECX is
 *	  higher; some BIOSes (e.g. Award Modular BIOS v4.50PG) ignore the
 *	  value of ECX on entry, and always copy 20 bytes
 *	some BIOSes expect the high word of EAX to be clear on entry, i.e.
 *	  EAX=0000E820h
 *	if this function is not supported, an application should fall back
 *	  to AX=E802h, AX=E801h, and then AH=88h
 *	the BIOS is permitted to return a nonzero continuation value in EBX
 *	  and indicate that the end of the list has already been reached by
 *	  returning with CF set on the next iteration
 *	this function will return base memory and ISA/PCI memory contiguous
 *	  with base memory as normal memory ranges; it will indicate
 *	  chipset-defined address holes which are not in use and motherboard
 *	  memory-mapped devices, and all occurrences of the system BIOS as
 *	  reserved; standard PC address ranges will not be reported
 **/
void detect_memory_e820(struct e820entry *desc, int size_map, int *size_found)
{
    int count = 0;
    static struct e820_ext_entry buf;	/* static so it is zeroed */

    com32sys_t ireg, oreg;
    memset(&ireg, 0, sizeof ireg);

    ireg.eax.w[0] = 0xe820;
    ireg.edx.l = SMAP;
    ireg.ecx.l = sizeof(struct e820_ext_entry);
    ireg.edi.w[0] = OFFS(__com32.cs_bounce);
    ireg.es = SEG(__com32.cs_bounce);

    /*
     * Set this here so that if the BIOS doesn't change this field
     * but still doesn't change %ecx, we're still okay...
     */
    memset(&buf, 0, sizeof buf);
    buf.ext_flags = 1;

    do {
	memcpy(__com32.cs_bounce, &buf, sizeof buf);

	/* Important: %edx and %esi are clobbered by some BIOSes,
	   so they must be either used for the error output
	   or explicitly marked clobbered.  Given that, assume there
	   is something out there clobbering %ebp and %edi, too. */
	__intcall(0x15, &ireg, &oreg);

	/* Some BIOSes stop returning SMAP in the middle of
	   the search loop.  We don't know exactly how the BIOS
	   screwed up the map at that point, we might have a
	   partial map, the full map, or complete garbage, so
	   just return failure. */
	if (oreg.eax.l != SMAP) {
	    count = 0;
	    break;
	}

	if (oreg.eflags.l & EFLAGS_CF || oreg.ecx.l < 20)
	    break;

	memcpy(&buf, __com32.cs_bounce, sizeof buf);

	/*
	 * ACPI 3.0 added the extended flags support.  If bit 0
	 * in the extended flags is zero, we're supposed to simply
	 * ignore the entry -- a backwards incompatible change!
	 */
	if (oreg.ecx.l > 20 && !(buf.ext_flags & 1))
	    continue;

	memcpy(&desc[count], &buf, sizeof buf);
	count++;

	/* Set continuation value */
	ireg.ebx.l = oreg.ebx.l;
    } while (ireg.ebx.l && count < size_map);

    *size_found = count;
}

/**
 * detect_memory_e801
 *
 *INT 15 - Phoenix BIOS v4.0 - GET MEMORY SIZE FOR >64M CONFIGURATIONS
 *	AX = E801h
 *
 * Return: CF clear if successful
 *	    AX = extended memory between 1M and 16M, in K (max 3C00h = 15MB)
 *	    BX = extended memory above 16M, in 64K blocks
 *	    CX = configured memory 1M to 16M, in K
 *	    DX = configured memory above 16M, in 64K blocks
 *	CF set on error
 *
 * Notes: supported by the A03 level (6/14/94) and later XPS P90 BIOSes, as well
 *	as the Compaq Contura, 3/8/93 DESKPRO/i, and 7/26/93 LTE Lite 386 ROM
 *	BIOS
 *	supported by AMI BIOSes dated 8/23/94 or later
 *	on some systems, the BIOS returns AX=BX=0000h; in this case, use CX
 *	  and DX instead of AX and BX
 *	this interface is used by Windows NT 3.1, OS/2 v2.11/2.20, and is
 *	  used as a fall-back by newer versions if AX=E820h is not supported
 *	this function is not used by MS-DOS 6.0 HIMEM.SYS when an EISA machine
 *	  (for example with parameter /EISA) (see also MEM F000h:FFD9h), or no
 *	  Compaq machine was detected, or parameter /NOABOVE16 was given.
 **/
int detect_memory_e801(int *mem_size_below_16, int *mem_size_above_16)
{
    com32sys_t ireg, oreg;
    memset(&ireg, 0, sizeof ireg);

    ireg.eax.w[0] = 0xe801;

    __intcall(0x15, &ireg, &oreg);

    if (oreg.eflags.l & EFLAGS_CF)
	return -1;

    if (oreg.eax.w[0] > 0x3c00)
	return -1;		/* Bogus! */

    /* Linux seems to use ecx and edx by default if they are defined */
    if (oreg.eax.w[0] || oreg.eax.w[0]) {
	oreg.eax.w[0] = oreg.ecx.w[0];
	oreg.ebx.w[0] = oreg.edx.w[0];
    }

    *mem_size_below_16 = oreg.eax.w[0];	/* 1K blocks */
    *mem_size_above_16 = oreg.ebx.w[0];	/* 64K blocks */

    return 0;
}

int detect_memory_88(int *mem_size)
{
    com32sys_t ireg, oreg;
    memset(&ireg, 0, sizeof ireg);

    ireg.eax.w[0] = 0x8800;

    __intcall(0x15, &ireg, &oreg);

    if (oreg.eflags.l & EFLAGS_CF)
	return -1;

    *mem_size = oreg.eax.w[0];
    return 0;
}

/*
 * Sanitize the BIOS e820 map.
 *
 * This code come from the memtest86 project. It have been adjusted to match
 * the syslinux environement.
 * Some e820 responses include overlapping entries.  The following 
 * replaces the original e820 map with a new one, removing overlaps.
 * 
 * The following stuff could be merge once the addr_t will be set to 64bits.
 * syslinux_scan_memory can be used for that purpose 
 */
int sanitize_e820_map(struct e820entry *orig_map, struct e820entry *new_bios,
		      short old_nr)
{
    struct change_member {
	struct e820entry *pbios;	/* pointer to original bios entry */
	unsigned long long addr;	/* address for this change point */
    };
    struct change_member change_point_list[2 * E820MAX];
    struct change_member *change_point[2 * E820MAX];
    struct e820entry *overlap_list[E820MAX];
    struct e820entry biosmap[E820MAX];
    struct change_member *change_tmp;
    unsigned long current_type, last_type;
    unsigned long long last_addr;
    int chgidx, still_changing;
    int overlap_entries;
    int new_bios_entry;
    int i;

    /*
       Visually we're performing the following (1,2,3,4 = memory types)...
       Sample memory map (w/overlaps):
       ____22__________________
       ______________________4_
       ____1111________________
       _44_____________________
       11111111________________
       ____________________33__
       ___________44___________
       __________33333_________
       ______________22________
       ___________________2222_
       _________111111111______
       _____________________11_
       _________________4______

       Sanitized equivalent (no overlap):
       1_______________________
       _44_____________________
       ___1____________________
       ____22__________________
       ______11________________
       _________1______________
       __________3_____________
       ___________44___________
       _____________33_________
       _______________2________
       ________________1_______
       _________________4______
       ___________________2____
       ____________________33__
       ______________________4_
     */
    /* First make a copy of the map */
    for (i = 0; i < old_nr; i++) {
	biosmap[i].addr = orig_map[i].addr;
	biosmap[i].size = orig_map[i].size;
	biosmap[i].type = orig_map[i].type;
    }

    /* bail out if we find any unreasonable addresses in bios map */
    for (i = 0; i < old_nr; i++) {
	if (biosmap[i].addr + biosmap[i].size < biosmap[i].addr)
	    return 0;
    }

    /* create pointers for initial change-point information (for sorting) */
    for (i = 0; i < 2 * old_nr; i++)
	change_point[i] = &change_point_list[i];

    /* record all known change-points (starting and ending addresses) */
    chgidx = 0;
    for (i = 0; i < old_nr; i++) {
	change_point[chgidx]->addr = biosmap[i].addr;
	change_point[chgidx++]->pbios = &biosmap[i];
	change_point[chgidx]->addr = biosmap[i].addr + biosmap[i].size;
	change_point[chgidx++]->pbios = &biosmap[i];
    }

    /* sort change-point list by memory addresses (low -> high) */
    still_changing = 1;
    while (still_changing) {
	still_changing = 0;
	for (i = 1; i < 2 * old_nr; i++) {
	    /* if <current_addr> > <last_addr>, swap */
	    /* or, if current=<start_addr> & last=<end_addr>, swap */
	    if ((change_point[i]->addr < change_point[i - 1]->addr) ||
		((change_point[i]->addr == change_point[i - 1]->addr) &&
		 (change_point[i]->addr == change_point[i]->pbios->addr) &&
		 (change_point[i - 1]->addr !=
		  change_point[i - 1]->pbios->addr))
		) {
		change_tmp = change_point[i];
		change_point[i] = change_point[i - 1];
		change_point[i - 1] = change_tmp;
		still_changing = 1;
	    }
	}
    }

    /* create a new bios memory map, removing overlaps */
    overlap_entries = 0;	/* number of entries in the overlap table */
    new_bios_entry = 0;		/* index for creating new bios map entries */
    last_type = 0;		/* start with undefined memory type */
    last_addr = 0;		/* start with 0 as last starting address */
    /* loop through change-points, determining affect on the new bios map */
    for (chgidx = 0; chgidx < 2 * old_nr; chgidx++) {
	/* keep track of all overlapping bios entries */
	if (change_point[chgidx]->addr == change_point[chgidx]->pbios->addr) {
	    /* add map entry to overlap list (> 1 entry implies an overlap) */
	    overlap_list[overlap_entries++] = change_point[chgidx]->pbios;
	} else {
	    /* remove entry from list (order independent, so swap with last) */
	    for (i = 0; i < overlap_entries; i++) {
		if (overlap_list[i] == change_point[chgidx]->pbios)
		    overlap_list[i] = overlap_list[overlap_entries - 1];
	    }
	    overlap_entries--;
	}
	/* if there are overlapping entries, decide which "type" to use */
	/* (larger value takes precedence -- 1=usable, 2,3,4,4+=unusable) */
	current_type = 0;
	for (i = 0; i < overlap_entries; i++)
	    if (overlap_list[i]->type > current_type)
		current_type = overlap_list[i]->type;
	/* continue building up new bios map based on this information */
	if (current_type != last_type) {
	    if (last_type != 0) {
		new_bios[new_bios_entry].size =
		    change_point[chgidx]->addr - last_addr;
		/* move forward only if the new size was non-zero */
		if (new_bios[new_bios_entry].size != 0)
		    if (++new_bios_entry >= E820MAX)
			break;	/* no more space left for new bios entries */
	    }
	    if (current_type != 0) {
		new_bios[new_bios_entry].addr = change_point[chgidx]->addr;
		new_bios[new_bios_entry].type = current_type;
		last_addr = change_point[chgidx]->addr;
	    }
	    last_type = current_type;
	}
    }
    return (new_bios_entry);
}

/* The following stuff could be merge once the addr_t will be set to 64bits.
 * syslinux_scan_memory can be used for that purpose */
unsigned long detect_memsize(void)
{
    unsigned long memory_size = 0;

    /* Try to detect memory via e820 */
    struct e820entry map[E820MAX];
    int count = 0;
    detect_memory_e820(map, E820MAX, &count);
    memory_size = memsize_e820(map, count);
    if (memory_size > 0)
	return memory_size;

    /*e820 failed, let's try e801 */
    int mem_low, mem_high = 0;
    if (!detect_memory_e801(&mem_low, &mem_high))
	return mem_low + (mem_high << 6);

    /*e801 failed, let's try e88 */
    int mem_size = 0;
    if (!detect_memory_88(&mem_size))
	return mem_size;

    /* We were enable to detect any kind of memory */
    return 0;
}

/* The following stuff could be merge once the addr_t will be set to 64bits.
 * syslinux_scan_memory can be used for that purpose */
unsigned long memsize_e820(struct e820entry *e820, int e820_nr)
{
    int i, n, nr;
    unsigned long memory_size = 0;
    struct e820entry nm[E820MAX];

    /* Clean up, adjust and copy the BIOS-supplied E820-map. */
    nr = sanitize_e820_map(e820, nm, e820_nr);

    /* If there is not a good 820 map returning 0 to indicate 
       that we don't have any idea of the amount of ram we have */
    if (nr < 1 || nr > E820MAX) {
	return 0;
    }

    /* Build the memory map for testing */
    n = 0;
    for (i = 0; i < nr; i++) {
	if (nm[i].type == E820_RAM || nm[i].type == E820_ACPI) {
	    unsigned long long start;
	    unsigned long long end;
	    start = nm[i].addr;
	    end = start + nm[i].size;

	    /* Don't ever use memory between 640 and 1024k */
	    if (start > RES_START && start < RES_END) {
		if (end < RES_END) {
		    continue;
		}
		start = RES_END;
	    }
	    if (end > RES_START && end < RES_END) {
		end = RES_START;
	    }
	    memory_size += (end >> 12) - ((start + 4095) >> 12);
	    n++;
	} else if (nm[i].type == E820_NVS) {
	    memory_size += nm[i].size >> 12;
	}
    }
    return memory_size * 4;
}