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