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