ac_test.c revision 1341:6d7c4f090a72
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
57struct 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) */
69static struct test_info 	*test_base = NULL;
70static kmutex_t			test_mutex;
71static int			test_mutex_initialized = FALSE;
72
73static mem_test_handle_t	mem_test_sequence_id = 0;
74
75void
76ac_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(va, KCONTEXT, &tte);
87
88}
89
90void
91ac_unmap(caddr_t va)
92{
93	vtag_flushpage(va, KCONTEXT);
94}
95
96int
97ac_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
236int
237ac_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
300void
301ac_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
353int
354ac_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
500read_done:
501	atomic_add_32(&test->in_test, -1);
502	return (retval);
503}
504
505int
506ac_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
604write_done:
605	atomic_add_32(&test->in_test, -1);
606	return (retval);
607}
608