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