xref: /openbsd/sys/arch/i386/stand/libsa/memprobe.c (revision 8529ddd3)
1 /*	$OpenBSD: memprobe.c,v 1.53 2014/03/29 18:09:29 guenther Exp $	*/
2 
3 /*
4  * Copyright (c) 1997-1999 Michael Shalayeff
5  * Copyright (c) 1997-1999 Tobias Weingartner
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
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
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  */
30 
31 #include <sys/param.h>
32 #include <machine/biosvar.h>
33 #include <dev/isa/isareg.h>
34 #include <stand/boot/bootarg.h>
35 #include "libsa.h"
36 
37 u_int cnvmem, extmem;		/* XXX - compatibility */
38 
39 
40 /*
41  * Check gateA20
42  *
43  * A sanity check.
44  */
45 static __inline int
46 checkA20(void)
47 {
48 	register char *p = (char *)0x100000;
49 	register char *q = (char *)0x000000;
50 	int st;
51 
52 	/* Simple check */
53 	if (*p != *q)
54 		return 1;
55 
56 	/* Complex check */
57 	*p = ~(*p);
58 	st = (*p != *q);
59 	*p = ~(*p);
60 
61 	return st;
62 }
63 
64 /*
65  * BIOS int 15, AX=E820
66  *
67  * This is the "preferred" method.
68  */
69 static __inline bios_memmap_t *
70 bios_E820(bios_memmap_t *mp)
71 {
72 	int rc, off = 0, sig, gotcha = 0;
73 
74 	do {
75 		BIOS_regs.biosr_es = ((u_int)(mp) >> 4);
76 		__asm volatile(DOINT(0x15) "; setc %b1"
77 		    : "=a" (sig), "=d" (rc), "=b" (off)
78 		    : "0" (0xE820), "1" (0x534d4150), "b" (off),
79 		      "c" (sizeof(*mp)), "D" (((u_int)mp) & 0xf)
80 		    : "cc", "memory");
81 		off = BIOS_regs.biosr_bx;
82 
83 		if (rc & 0xff || sig != 0x534d4150)
84 			break;
85 		gotcha++;
86 		if (!mp->type)
87 			mp->type = BIOS_MAP_RES;
88 		mp++;
89 	} while (off);
90 
91 	if (!gotcha)
92 		return NULL;
93 #ifdef DEBUG
94 	printf("0x15[E820] ");
95 #endif
96 	return mp;
97 }
98 
99 #if 0
100 /*
101  * BIOS int 15, AX=E801
102  *
103  * Only used if int 15, AX=E820 does not work.
104  * This should work for more than 64MB on most
105  * modern machines.  However, there is always
106  * an exception, the older IBM machine do not
107  * like this call.
108  */
109 static __inline bios_memmap_t *
110 bios_E801(bios_memmap_t *mp)
111 {
112 	int rc, m1, m2, m3, m4;
113 	u_int8_t *info;
114 
115 	/* Test for possibility of 0xE801 */
116 	info =  getSYSCONFaddr();
117 	if (!info)
118 		return NULL;
119 	/* XXX - Should test model/submodel/rev here */
120 	printf("model(%d,%d,%d)", info[2], info[3], info[4]);
121 
122 	/* Check for 94 or later bios */
123 	info = (void *)0xFFFFB;
124 	if (info[0] == '9' && info[1] <= '3')
125 		return NULL;
126 
127 	/* We might have this call */
128 	__asm volatile(DOINT(0x15) "; mov %%ax, %%si; setc %b0"
129 	    : "=a" (rc), "=S" (m1), "=b" (m2), "=c" (m3), "=d" (m4)
130 	    : "0" (0xE801));
131 
132 	/* Test for failure */
133 	if (rc & 0xff)
134 		return NULL;
135 
136 	/* Fixup for screwed up machines */
137 	if (m1 == 0) {
138 		m1 = m3;
139 		m2 = m4;
140 	}
141 #ifdef DEBUG
142 	printf("0x15[E801] ");
143 #endif
144 	/* Fill out BIOS map */
145 	mp->addr = (1024 * 1024);	/* 1MB */
146 	mp->size = (m1 & 0xffff) * 1024;
147 	mp->type = BIOS_MAP_FREE;
148 
149 	mp++;
150 	mp->addr = (1024 * 1024) * 16;	/* 16MB */
151 	mp->size = (m2 & 0xffff) * 64L * 1024;
152 	mp->type = BIOS_MAP_FREE;
153 
154 	return ++mp;
155 }
156 #endif
157 
158 /*
159  * BIOS int 15, AX=8800
160  *
161  * Only used if int 15, AX=E801 does not work.
162  * Machines with this are restricted to 64MB.
163  */
164 static __inline bios_memmap_t *
165 bios_8800(bios_memmap_t *mp)
166 {
167 	int rc, mem;
168 
169 	__asm volatile(DOINT(0x15) "; setc %b0"
170 	    : "=c" (rc), "=a" (mem) : "a" (0x8800));
171 
172 	if (rc & 0xff)
173 		return NULL;
174 #ifdef DEBUG
175 	printf("0x15[8800] ");
176 #endif
177 	/* Fill out a BIOS_MAP */
178 	mp->addr = 1024 * 1024;		/* 1MB */
179 	mp->size = (mem & 0xffff) * 1024;
180 	mp->type = BIOS_MAP_FREE;
181 
182 	return ++mp;
183 }
184 
185 /*
186  * BIOS int 0x12 Get Conventional Memory
187  *
188  * Only used if int 15, AX=E820 does not work.
189  */
190 static __inline bios_memmap_t *
191 bios_int12(bios_memmap_t *mp)
192 {
193 	int mem;
194 #ifdef DEBUG
195 	printf("0x12 ");
196 #endif
197 	__asm volatile(DOINT(0x12) : "=a" (mem) :: "%ecx", "%edx", "cc");
198 
199 	/* Fill out a bios_memmap_t */
200 	mp->addr = 0;
201 	mp->size = (mem & 0xffff) * 1024;
202 	mp->type = BIOS_MAP_FREE;
203 
204 	return ++mp;
205 }
206 
207 
208 /*
209  * addrprobe(kloc): Probe memory at address kloc * 1024.
210  *
211  * This is a hack, but it seems to work ok.  Maybe this is
212  * the *real* way that you are supposed to do probing???
213  *
214  * Modify the original a bit.  We write everything first, and
215  * then test for the values.  This should croak on machines that
216  * return values just written on non-existent memory...
217  *
218  * BTW: These machines are pretty broken IMHO.
219  *
220  * XXX - Does not detect aliased memory.
221  */
222 const u_int addrprobe_pat[] = {
223 	0x00000000, 0xFFFFFFFF,
224 	0x01010101, 0x10101010,
225 	0x55555555, 0xCCCCCCCC
226 };
227 static int
228 addrprobe(u_int kloc)
229 {
230 	volatile u_int *loc;
231 	register u_int i, ret = 0;
232 	u_int save[nitems(addrprobe_pat)];
233 
234 	/* Get location */
235 	loc = (int *)(kloc * 1024);
236 
237 	save[0] = *loc;
238 	/* Probe address */
239 	for (i = 0; i < nitems(addrprobe_pat); i++) {
240 		*loc = addrprobe_pat[i];
241 		if (*loc != addrprobe_pat[i])
242 			ret++;
243 	}
244 	*loc = save[0];
245 
246 	if (!ret) {
247 		/* Write address */
248 		for (i = 0; i < nitems(addrprobe_pat); i++) {
249 			save[i] = loc[i];
250 			loc[i] = addrprobe_pat[i];
251 		}
252 
253 		/* Read address */
254 		for (i = 0; i < nitems(addrprobe_pat); i++) {
255 			if (loc[i] != addrprobe_pat[i])
256 				ret++;
257 			loc[i] = save[i];
258 		}
259 	}
260 
261 	return ret;
262 }
263 
264 /*
265  * Probe for all extended memory.
266  *
267  * This is only used as a last resort.  If we resort to this
268  * routine, we are getting pretty desperate.  Hopefully nobody
269  * has to rely on this after all the work above.
270  *
271  * XXX - Does not detect aliased memory.
272  * XXX - Could be destructive, as it does write.
273  */
274 static __inline bios_memmap_t *
275 badprobe(bios_memmap_t *mp)
276 {
277 	u_int64_t ram;
278 #ifdef DEBUG
279 	printf("scan ");
280 #endif
281 	/*
282 	 * probe extended memory
283 	 *
284 	 * There is no need to do this in assembly language.  This is
285 	 * much easier to debug in C anyways.
286 	 */
287 	for (ram = 1024; ram < 512 * 1024; ram += 4)
288 		if (addrprobe(ram))
289 			break;
290 
291 	mp->addr = 1024 * 1024;
292 	mp->size = (ram - 1024) * 1024;
293 	mp->type = BIOS_MAP_FREE;
294 
295 	return ++mp;
296 }
297 
298 bios_memmap_t bios_memmap[64];	/* This is easier */
299 #ifndef _TEST
300 void
301 memprobe(void)
302 {
303 	bios_memmap_t *pm = bios_memmap, *im;
304 
305 #ifdef DEBUG
306 	printf(" mem(");
307 #else
308 	printf(" mem[");
309 #endif
310 
311 	if ((pm = bios_E820(bios_memmap)) == NULL) {
312 		im = bios_int12(bios_memmap);
313 #if 0
314 		pm = bios_E801(im);
315 		if (pm == NULL)
316 #endif
317 			pm = bios_8800(im);
318 		if (pm == NULL)
319 			pm = badprobe(im);
320 		if (pm == NULL) {
321 			printf(" No Extended memory detected.");
322 			pm = im;
323 		}
324 	}
325 	pm->type = BIOS_MAP_END;
326 
327 	/* XXX - gotta peephole optimize the list */
328 
329 	/* Remove APM needed RAM */
330 	apmfixmem();
331 
332 #ifdef DEBUG
333 	printf(")[");
334 #endif
335 
336 	/* XXX - Compatibility, remove later (smpprobe() relies on it) */
337 	extmem = cnvmem = 0;
338 	for (im = bios_memmap; im->type != BIOS_MAP_END; im++) {
339 		/* Count only "good" memory chunks 12K and up in size */
340 		if ((im->type == BIOS_MAP_FREE) && (im->size >= 12 * 1024)) {
341 			if (im->size > 1024 * 1024)
342 				printf("%uM ", (u_int)(im->size /
343 				    (1024 * 1024)));
344 			else
345 				printf("%uK ", (u_int)im->size / 1024);
346 
347 			/*
348 			 * Compute compatibility values:
349 			 * cnvmem -- is the upper boundary of conventional
350 			 *	memory (below IOM_BEGIN (=640k))
351 			 * extmem -- is the size of the contignous extended
352 			 *	memory segment starting at 1M
353 			 *
354 			 * We ignore "good" memory in the 640K-1M hole.
355 			 * We drop "machine {cnvmem,extmem}" commands.
356 			 */
357 			if (im->addr < IOM_BEGIN)
358 				cnvmem = max(cnvmem,
359 				    im->addr + im->size) / 1024;
360 			if (im->addr >= IOM_END)
361 				extmem += im->size / 1024;
362 		}
363 	}
364 
365 	/* Check if gate A20 is on */
366 	printf("a20=o%s] ", checkA20()? "n" : "ff!");
367 }
368 #endif
369 
370 void
371 dump_biosmem(bios_memmap_t *tm)
372 {
373 	register bios_memmap_t *p;
374 	register u_int total = 0;
375 
376 	if (tm == NULL)
377 		tm = bios_memmap;
378 
379 	for (p = tm; p->type != BIOS_MAP_END; p++) {
380 		printf("Region %ld: type %u at 0x%llx for %uKB\n",
381 		    (long)(p - tm), p->type, p->addr,
382 		    (u_int)(p->size / 1024));
383 
384 		if (p->type == BIOS_MAP_FREE)
385 			total += p->size / 1024;
386 	}
387 
388 	printf("Low ram: %dKB  High ram: %dKB\n", cnvmem, extmem);
389 	printf("Total free memory: %uKB\n", total);
390 }
391 
392 int
393 mem_limit(long long ml)
394 {
395 	register bios_memmap_t *p;
396 
397 	for (p = bios_memmap; p->type != BIOS_MAP_END; p++) {
398 		register int64_t sp = p->addr, ep = p->addr + p->size;
399 
400 		if (p->type != BIOS_MAP_FREE)
401 			continue;
402 
403 		/* Wholy above limit, nuke it */
404 		if ((sp >= ml) && (ep >= ml)) {
405 			bcopy (p + 1, p, (char *)bios_memmap +
406 			       sizeof(bios_memmap) - (char *)p);
407 		} else if ((sp < ml) && (ep >= ml)) {
408 			p->size -= (ep - ml);
409 		}
410 	}
411 	return 0;
412 }
413 
414 int
415 mem_delete(long long sa, long long ea)
416 {
417 	register bios_memmap_t *p;
418 
419 	for (p = bios_memmap; p->type != BIOS_MAP_END; p++) {
420 		if (p->type == BIOS_MAP_FREE) {
421 			register int64_t sp = p->addr, ep = p->addr + p->size;
422 
423 			/* can we eat it as a whole? */
424 			if ((sa - sp) <= PAGE_SIZE && (ep - ea) <= PAGE_SIZE) {
425 				bcopy(p + 1, p, (char *)bios_memmap +
426 				    sizeof(bios_memmap) - (char *)p);
427 				break;
428 			/* eat head or legs */
429 			} else if (sa <= sp && sp < ea) {
430 				p->addr = ea;
431 				p->size = ep - ea;
432 				break;
433 			} else if (sa < ep && ep <= ea) {
434 				p->size = sa - sp;
435 				break;
436 			} else if (sp < sa && ea < ep) {
437 				/* bite in half */
438 				bcopy(p, p + 1, (char *)bios_memmap +
439 				    sizeof(bios_memmap) - (char *)p -
440 				    sizeof(bios_memmap[0]));
441 				p[1].addr = ea;
442 				p[1].size = ep - ea;
443 				p->size = sa - sp;
444 				break;
445 			}
446 		}
447 	}
448 	return 0;
449 }
450 
451 int
452 mem_add(long long sa, long long ea)
453 {
454 	register bios_memmap_t *p;
455 
456 	for (p = bios_memmap; p->type != BIOS_MAP_END; p++) {
457 		if (p->type == BIOS_MAP_FREE) {
458 			register int64_t sp = p->addr, ep = p->addr + p->size;
459 
460 			/* is it already there? */
461 			if (sp <= sa && ea <= ep) {
462 				break;
463 			/* join head or legs */
464 			} else if (sa < sp && sp <= ea) {
465 				p->addr = sa;
466 				p->size = ep - sa;
467 				break;
468 			} else if (sa <= ep && ep < ea) {
469 				p->size = ea - sp;
470 				break;
471 			} else if (ea < sp) {
472 				/* insert before */
473 				bcopy(p, p + 1, (char *)bios_memmap +
474 				    sizeof(bios_memmap) - (char *)(p - 1));
475 				p->addr = sa;
476 				p->size = ea - sa;
477 				break;
478 			}
479 		}
480 	}
481 
482 	/* meaning add new item at the end of the list */
483 	if (p->type == BIOS_MAP_END) {
484 		p[1] = p[0];
485 		p->type = BIOS_MAP_FREE;
486 		p->addr = sa;
487 		p->size = ea - sa;
488 	}
489 
490 	return 0;
491 }
492 
493 void
494 mem_pass(void)
495 {
496 	bios_memmap_t *p;
497 
498 	for (p = bios_memmap; p->type != BIOS_MAP_END; p++)
499 		;
500 	addbootarg(BOOTARG_MEMMAP, (p - bios_memmap + 1) * sizeof *bios_memmap,
501 	    bios_memmap);
502 }
503