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