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
37u_int cnvmem, extmem;		/* XXX - compatibility */
38
39bios_memmap_t bios_memmap[64];	/* This is easier */
40
41/*
42 * Check gateA20
43 *
44 * A sanity check.
45 */
46static __inline int
47checkA20(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 */
70static __inline bios_memmap_t *
71bios_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 */
106static __inline bios_memmap_t *
107bios_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 */
132static __inline bios_memmap_t *
133bios_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 */
163const u_int addrprobe_pat[] = {
164	0x00000000, 0xFFFFFFFF,
165	0x01010101, 0x10101010,
166	0x55555555, 0xCCCCCCCC
167};
168static int
169addrprobe(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 */
215static __inline bios_memmap_t *
216badprobe(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
239void
240memprobe(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
314void
315dump_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
336int
337mem_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
358int
359mem_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
395int
396mem_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
437void
438mem_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