xref: /illumos-gate/usr/src/uts/sun4u/sunfire/io/ac_test.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
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