1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #include <sys/types.h> 28 #include <sys/conf.h> 29 #include <sys/ddi.h> 30 #include <sys/sunddi.h> 31 #include <sys/ddi_impldefs.h> 32 #include <sys/obpdefs.h> 33 #include <sys/cmn_err.h> 34 #include <sys/errno.h> 35 #include <sys/kmem.h> 36 #include <sys/vmem.h> 37 #include <sys/debug.h> 38 #include <sys/sysmacros.h> 39 #include <sys/machsystm.h> 40 #include <sys/machparam.h> 41 #include <sys/modctl.h> 42 #include <sys/atomic.h> 43 #include <sys/fhc.h> 44 #include <sys/ac.h> 45 #include <sys/jtag.h> 46 #include <sys/cpu_module.h> 47 #include <sys/spitregs.h> 48 #include <sys/vm.h> 49 #include <vm/seg_kmem.h> 50 #include <vm/hat_sfmmu.h> 51 52 /* memory setup parameters */ 53 #define TEST_PAGESIZE MMU_PAGESIZE 54 55 struct test_info { 56 struct test_info *next; /* linked list of tests */ 57 struct ac_mem_info *mem_info; 58 uint_t board; 59 uint_t bank; 60 caddr_t bufp; /* pointer to buffer page */ 61 caddr_t va; /* test target VA */ 62 ac_mem_test_start_t info; 63 uint_t in_test; /* count of threads in test */ 64 }; 65 66 /* list of tests in progress (list protected test_mutex) */ 67 static struct test_info *test_base = NULL; 68 static kmutex_t test_mutex; 69 static int test_mutex_initialized = FALSE; 70 71 static mem_test_handle_t mem_test_sequence_id = 0; 72 73 void 74 ac_mapin(uint64_t pa, caddr_t va) 75 { 76 pfn_t pfn; 77 tte_t tte; 78 79 pfn = pa >> MMU_PAGESHIFT; 80 tte.tte_inthi = TTE_VALID_INT | TTE_SZ_INT(TTE8K) | 81 TTE_PFN_INTHI(pfn); 82 tte.tte_intlo = TTE_PFN_INTLO(pfn) | TTE_CP_INT | 83 TTE_PRIV_INT | TTE_LCK_INT | TTE_HWWR_INT; 84 sfmmu_dtlb_ld_kva(va, &tte); 85 86 } 87 88 void 89 ac_unmap(caddr_t va) 90 { 91 vtag_flushpage(va, (uint64_t)ksfmmup); 92 } 93 94 int 95 ac_mem_test_start(ac_cfga_pkt_t *pkt, int flag) 96 { 97 struct ac_soft_state *softsp; 98 struct ac_mem_info *mem_info; 99 struct bd_list *board; 100 struct test_info *test; 101 uint64_t decode; 102 103 /* XXX if ac ever detaches... */ 104 if (test_mutex_initialized == FALSE) { 105 mutex_init(&test_mutex, NULL, MUTEX_DEFAULT, NULL); 106 test_mutex_initialized = TRUE; 107 } 108 109 /* 110 * Is the specified bank testable? 111 */ 112 113 board = fhc_bdlist_lock(pkt->softsp->board); 114 if (board == NULL || board->ac_softsp == NULL) { 115 fhc_bdlist_unlock(); 116 AC_ERR_SET(pkt, AC_ERR_BD); 117 return (EINVAL); 118 } 119 ASSERT(pkt->softsp == board->ac_softsp); 120 121 /* verify the board is of the correct type */ 122 switch (board->sc.type) { 123 case CPU_BOARD: 124 case MEM_BOARD: 125 break; 126 default: 127 fhc_bdlist_unlock(); 128 AC_ERR_SET(pkt, AC_ERR_BD_TYPE); 129 return (EINVAL); 130 } 131 132 /* 133 * Memory must be in the spare state to be testable. 134 * However, spare memory that is testing can't be tested 135 * again, instead return the current test info. 136 */ 137 softsp = pkt->softsp; 138 mem_info = &softsp->bank[pkt->bank]; 139 if (!MEM_BOARD_VISIBLE(board) || 140 fhc_bd_busy(softsp->board) || 141 mem_info->rstate != SYSC_CFGA_RSTATE_CONNECTED || 142 mem_info->ostate != SYSC_CFGA_OSTATE_UNCONFIGURED) { 143 fhc_bdlist_unlock(); 144 AC_ERR_SET(pkt, AC_ERR_BD_STATE); 145 return (EINVAL); 146 } 147 if (mem_info->busy) { /* oops, testing? */ 148 /* 149 * find the test entry 150 */ 151 ASSERT(test_mutex_initialized); 152 mutex_enter(&test_mutex); 153 for (test = test_base; test != NULL; test = test->next) { 154 if (test->board == softsp->board && 155 test->bank == pkt->bank) 156 break; 157 } 158 if (test == NULL) { 159 mutex_exit(&test_mutex); 160 fhc_bdlist_unlock(); 161 /* Not busy testing. */ 162 AC_ERR_SET(pkt, AC_ERR_BD_STATE); 163 return (EINVAL); 164 } 165 166 /* 167 * return the current test information to the new caller 168 */ 169 if (ddi_copyout(&test->info, pkt->cmd_cfga.private, 170 sizeof (ac_mem_test_start_t), flag) != 0) { 171 mutex_exit(&test_mutex); 172 fhc_bdlist_unlock(); 173 return (EFAULT); /* !broken user app */ 174 } 175 mutex_exit(&test_mutex); 176 fhc_bdlist_unlock(); 177 AC_ERR_SET(pkt, AC_ERR_MEM_BK); 178 return (EBUSY); /* signal bank in use */ 179 } 180 181 /* 182 * at this point, we have an available bank to test. 183 * create a test buffer 184 */ 185 test = kmem_zalloc(sizeof (struct test_info), KM_SLEEP); 186 test->va = vmem_alloc(heap_arena, PAGESIZE, VM_SLEEP); 187 188 /* fill in all the test info details now */ 189 test->mem_info = mem_info; 190 test->board = softsp->board; 191 test->bank = pkt->bank; 192 test->bufp = kmem_alloc(TEST_PAGESIZE, KM_SLEEP); 193 test->info.handle = atomic_inc_32_nv(&mem_test_sequence_id); 194 (void) drv_getparm(PPID, (ulong_t *)(&(test->info.tester_pid))); 195 test->info.prev_condition = mem_info->condition; 196 test->info.page_size = TEST_PAGESIZE; 197 /* If Blackbird ever gets a variable line size, this will change. */ 198 test->info.line_size = cpunodes[CPU->cpu_id].ecache_linesize; 199 decode = (pkt->bank == Bank0) ? 200 *softsp->ac_memdecode0 : *softsp->ac_memdecode1; 201 test->info.afar_base = GRP_REALBASE(decode); 202 test->info.bank_size = GRP_UK2SPAN(decode); 203 204 /* return the information to the user */ 205 if (ddi_copyout(&test->info, pkt->cmd_cfga.private, 206 sizeof (ac_mem_test_start_t), flag) != 0) { 207 208 /* oh well, tear down the test now */ 209 kmem_free(test->bufp, TEST_PAGESIZE); 210 vmem_free(heap_arena, test->va, PAGESIZE); 211 kmem_free(test, sizeof (struct test_info)); 212 213 fhc_bdlist_unlock(); 214 return (EFAULT); 215 } 216 217 mem_info->busy = TRUE; 218 219 /* finally link us into the test database */ 220 mutex_enter(&test_mutex); 221 test->next = test_base; 222 test_base = test; 223 mutex_exit(&test_mutex); 224 225 fhc_bdlist_unlock(); 226 227 #ifdef DEBUG 228 cmn_err(CE_NOTE, "!memtest: start test[%u]: board %d, bank %d", 229 test->info.handle, test->board, test->bank); 230 #endif /* DEBUG */ 231 return (DDI_SUCCESS); 232 } 233 234 int 235 ac_mem_test_stop(ac_cfga_pkt_t *pkt, int flag) 236 { 237 struct test_info *test, **prev; 238 ac_mem_test_stop_t stop; 239 240 /* get test result information */ 241 if (ddi_copyin(pkt->cmd_cfga.private, &stop, 242 sizeof (ac_mem_test_stop_t), flag) != 0) 243 return (EFAULT); 244 245 /* bdlist protects all state changes... */ 246 (void) fhc_bdlist_lock(-1); 247 248 /* find the test */ 249 mutex_enter(&test_mutex); 250 prev = &test_base; 251 for (test = test_base; test != NULL; test = test->next) { 252 if (test->info.handle == stop.handle) 253 break; /* found the test */ 254 prev = &test->next; 255 } 256 if (test == NULL) { 257 mutex_exit(&test_mutex); 258 fhc_bdlist_unlock(); 259 AC_ERR_SET(pkt, AC_ERR_MEM_TEST); 260 return (EINVAL); 261 } 262 263 #ifdef DEBUG 264 cmn_err(CE_NOTE, 265 "!memtest: stop test[%u]: board %d, bank %d," 266 " condition %d", 267 test->info.handle, test->board, 268 test->bank, stop.condition); 269 #endif /* DEBUG */ 270 271 /* first unlink us from the test list (to allow no more entries) */ 272 *prev = test->next; 273 274 /* then, wait for current tests to complete */ 275 while (test->in_test != 0) 276 delay(1); 277 278 mutex_exit(&test_mutex); 279 280 /* clean up the test related allocations */ 281 vmem_free(heap_arena, test->va, PAGESIZE); 282 kmem_free(test->bufp, TEST_PAGESIZE); 283 284 /* update the bank condition accordingly */ 285 test->mem_info->condition = stop.condition; 286 test->mem_info->status_change = ddi_get_time(); 287 288 test->mem_info->busy = FALSE; 289 290 /* finally, delete the test element */ 291 kmem_free(test, sizeof (struct test_info)); 292 293 fhc_bdlist_unlock(); 294 295 return (DDI_SUCCESS); 296 } 297 298 void 299 ac_mem_test_stop_on_close(uint_t board, uint_t bank) 300 { 301 struct test_info *test, **prev; 302 sysc_cfga_cond_t condition = SYSC_CFGA_COND_UNKNOWN; 303 304 /* bdlist protects all state changes... */ 305 (void) fhc_bdlist_lock(-1); 306 307 /* find the test */ 308 mutex_enter(&test_mutex); 309 prev = &test_base; 310 for (test = test_base; test != NULL; test = test->next) { 311 if (test->board == board && test->bank == bank) 312 break; /* found the test */ 313 prev = &test->next; 314 } 315 if (test == NULL) { 316 /* No test running, nothing to do. */ 317 mutex_exit(&test_mutex); 318 fhc_bdlist_unlock(); 319 return; 320 } 321 322 #ifdef DEBUG 323 cmn_err(CE_NOTE, "!memtest: stop test[%u] on close: " 324 "board %d, bank %d, condition %d", test->info.handle, 325 test->board, test->bank, condition); 326 #endif /* DEBUG */ 327 328 /* first unlink us from the test list (to allow no more entries) */ 329 *prev = test->next; 330 331 ASSERT(test->in_test == 0); 332 333 mutex_exit(&test_mutex); 334 335 /* clean up the test related allocations */ 336 vmem_free(heap_arena, test->va, PAGESIZE); 337 kmem_free(test->bufp, TEST_PAGESIZE); 338 339 /* update the bank condition accordingly */ 340 test->mem_info->condition = condition; 341 test->mem_info->status_change = ddi_get_time(); 342 343 test->mem_info->busy = FALSE; 344 345 /* finally, delete the test element */ 346 kmem_free(test, sizeof (struct test_info)); 347 348 fhc_bdlist_unlock(); 349 } 350 351 int 352 ac_mem_test_read(ac_cfga_pkt_t *pkt, int flag) 353 { 354 struct test_info *test; 355 uint_t page_offset; 356 uint64_t page_pa; 357 uint_t pstate_save; 358 caddr_t src_va, dst_va; 359 uint64_t orig_err; 360 int retval = DDI_SUCCESS; 361 sunfire_processor_error_regs_t error_buf; 362 int error_found; 363 ac_mem_test_read_t t_read; 364 365 #ifdef _MULTI_DATAMODEL 366 switch (ddi_model_convert_from(flag & FMODELS)) { 367 case DDI_MODEL_ILP32: { 368 ac_mem_test_read32_t t_read32; 369 370 if (ddi_copyin(pkt->cmd_cfga.private, &t_read32, 371 sizeof (ac_mem_test_read32_t), flag) != 0) 372 return (EFAULT); 373 t_read.handle = t_read32.handle; 374 t_read.page_buf = (void *)(uintptr_t)t_read32.page_buf; 375 t_read.address = t_read32.address; 376 t_read.error_buf = (sunfire_processor_error_regs_t *) 377 (uintptr_t)t_read32.error_buf; 378 break; 379 } 380 case DDI_MODEL_NONE: 381 if (ddi_copyin(pkt->cmd_cfga.private, &t_read, 382 sizeof (ac_mem_test_read_t), flag) != 0) 383 return (EFAULT); 384 break; 385 } 386 #else /* _MULTI_DATAMODEL */ 387 if (ddi_copyin(pkt->cmd_cfga.private, &t_read, 388 sizeof (ac_mem_test_read_t), flag) != 0) 389 return (EFAULT); 390 #endif /* _MULTI_DATAMODEL */ 391 392 /* verify the handle */ 393 mutex_enter(&test_mutex); 394 for (test = test_base; test != NULL; test = test->next) { 395 if (test->info.handle == t_read.handle) 396 break; 397 } 398 if (test == NULL) { 399 mutex_exit(&test_mutex); 400 AC_ERR_SET(pkt, AC_ERR_MEM_TEST); 401 return (EINVAL); 402 } 403 404 /* bump the busy bit */ 405 atomic_inc_32(&test->in_test); 406 mutex_exit(&test_mutex); 407 408 /* verify the remaining parameters */ 409 if ((t_read.address.page_num >= 410 test->info.bank_size / test->info.page_size) || 411 (t_read.address.line_count == 0) || 412 (t_read.address.line_count > 413 test->info.page_size / test->info.line_size) || 414 (t_read.address.line_offset >= 415 test->info.page_size / test->info.line_size) || 416 ((t_read.address.line_offset + t_read.address.line_count) > 417 test->info.page_size / test->info.line_size)) { 418 AC_ERR_SET(pkt, AC_ERR_MEM_TEST_PAR); 419 retval = EINVAL; 420 goto read_done; 421 } 422 423 page_offset = t_read.address.line_offset * test->info.line_size; 424 page_pa = test->info.afar_base + 425 t_read.address.page_num * test->info.page_size; 426 dst_va = test->bufp + page_offset; 427 src_va = test->va + page_offset; 428 429 /* time to go quiet */ 430 kpreempt_disable(); 431 432 /* we need a va for the block instructions */ 433 ac_mapin(page_pa, test->va); 434 435 pstate_save = disable_vec_intr(); 436 437 /* disable errors */ 438 orig_err = get_error_enable(); 439 set_error_enable(orig_err & ~(EER_CEEN | EER_NCEEN)); 440 441 /* copy the data again (using our very special copy) */ 442 ac_blkcopy(src_va, dst_va, t_read.address.line_count, 443 test->info.line_size); 444 445 /* process errors (if any) */ 446 error_buf.module_id = CPU->cpu_id; 447 get_asyncflt(&(error_buf.afsr)); 448 get_asyncaddr(&(error_buf.afar)); 449 get_udb_errors(&(error_buf.udbh_error_reg), 450 &(error_buf.udbl_error_reg)); 451 452 /* 453 * clean up after our no-error copy but before enabling ints. 454 * XXX what to do about other error types? 455 */ 456 if (error_buf.afsr & (P_AFSR_CE | P_AFSR_UE)) { 457 extern void clr_datapath(void); /* XXX */ 458 459 clr_datapath(); 460 set_asyncflt(error_buf.afsr); 461 retval = EIO; 462 error_found = TRUE; 463 } else { 464 error_found = FALSE; 465 } 466 467 /* errors back on */ 468 set_error_enable(orig_err); 469 470 enable_vec_intr(pstate_save); 471 472 /* tear down translation (who needs an mmu) */ 473 ac_unmap(test->va); 474 475 /* we're back! */ 476 kpreempt_enable(); 477 478 /* 479 * If there was a data error, attempt to return the error_buf 480 * to the user. 481 */ 482 if (error_found) { 483 if (ddi_copyout(&error_buf, t_read.error_buf, 484 sizeof (sunfire_processor_error_regs_t), flag) != 0) { 485 retval = EFAULT; 486 /* Keep going */ 487 } 488 } 489 490 /* 491 * Then, return the page to the user (always) 492 */ 493 if (ddi_copyout(dst_va, (caddr_t)(t_read.page_buf) + page_offset, 494 t_read.address.line_count * test->info.line_size, flag) != 0) { 495 retval = EFAULT; 496 } 497 498 read_done: 499 atomic_dec_32(&test->in_test); 500 return (retval); 501 } 502 503 int 504 ac_mem_test_write(ac_cfga_pkt_t *pkt, int flag) 505 { 506 struct test_info *test; 507 uint_t page_offset; 508 uint64_t page_pa; 509 uint_t pstate_save; 510 caddr_t src_va, dst_va; 511 int retval = DDI_SUCCESS; 512 ac_mem_test_write_t t_write; 513 514 #ifdef _MULTI_DATAMODEL 515 switch (ddi_model_convert_from(flag & FMODELS)) { 516 case DDI_MODEL_ILP32: { 517 ac_mem_test_write32_t t_write32; 518 519 if (ddi_copyin(pkt->cmd_cfga.private, &t_write32, 520 sizeof (ac_mem_test_write32_t), flag) != 0) 521 return (EFAULT); 522 t_write.handle = t_write32.handle; 523 t_write.page_buf = (void *)(uintptr_t)t_write32.page_buf; 524 t_write.address = t_write32.address; 525 break; 526 } 527 case DDI_MODEL_NONE: 528 if (ddi_copyin(pkt->cmd_cfga.private, &t_write, 529 sizeof (ac_mem_test_write_t), flag) != 0) 530 return (EFAULT); 531 break; 532 } 533 #else /* _MULTI_DATAMODEL */ 534 if (ddi_copyin(pkt->cmd_cfga.private, &t_write, 535 sizeof (ac_mem_test_write_t), flag) != 0) 536 return (EFAULT); 537 #endif /* _MULTI_DATAMODEL */ 538 539 /* verify the handle */ 540 mutex_enter(&test_mutex); 541 for (test = test_base; test != NULL; test = test->next) { 542 if (test->info.handle == t_write.handle) 543 break; 544 } 545 if (test == NULL) { 546 mutex_exit(&test_mutex); 547 return (EINVAL); 548 } 549 550 /* bump the busy bit */ 551 atomic_inc_32(&test->in_test); 552 mutex_exit(&test_mutex); 553 554 /* verify the remaining parameters */ 555 if ((t_write.address.page_num >= 556 test->info.bank_size / test->info.page_size) || 557 (t_write.address.line_count == 0) || 558 (t_write.address.line_count > 559 test->info.page_size / test->info.line_size) || 560 (t_write.address.line_offset >= 561 test->info.page_size / test->info.line_size) || 562 ((t_write.address.line_offset + t_write.address.line_count) > 563 test->info.page_size / test->info.line_size)) { 564 AC_ERR_SET(pkt, AC_ERR_MEM_TEST_PAR); 565 retval = EINVAL; 566 goto write_done; 567 } 568 569 page_offset = t_write.address.line_offset * test->info.line_size; 570 page_pa = test->info.afar_base + 571 t_write.address.page_num * test->info.page_size; 572 src_va = test->bufp + page_offset; 573 dst_va = test->va + page_offset; 574 575 /* copy in the specified user data */ 576 if (ddi_copyin((caddr_t)(t_write.page_buf) + page_offset, src_va, 577 t_write.address.line_count * test->info.line_size, flag) != 0) { 578 retval = EFAULT; 579 goto write_done; 580 } 581 582 /* time to go quiet */ 583 kpreempt_disable(); 584 585 /* we need a va for the block instructions */ 586 ac_mapin(page_pa, test->va); 587 588 pstate_save = disable_vec_intr(); 589 590 /* copy the data again (using our very special copy) */ 591 ac_blkcopy(src_va, dst_va, t_write.address.line_count, 592 test->info.line_size); 593 594 enable_vec_intr(pstate_save); 595 596 /* tear down translation (who needs an mmu) */ 597 ac_unmap(test->va); 598 599 /* we're back! */ 600 kpreempt_enable(); 601 602 write_done: 603 atomic_dec_32(&test->in_test); 604 return (retval); 605 } 606