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