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