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