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