/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/obpdefs.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/vmem.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/machsystm.h>
#include <sys/machparam.h>
#include <sys/modctl.h>
#include <sys/atomic.h>
#include <sys/fhc.h>
#include <sys/ac.h>
#include <sys/jtag.h>
#include <sys/cpu_module.h>
#include <sys/spitregs.h>
#include <sys/vm.h>
#include <vm/seg_kmem.h>
#include <vm/hat_sfmmu.h>

/* memory setup parameters */
#define	TEST_PAGESIZE	MMU_PAGESIZE

struct test_info {
	struct test_info	*next;		/* linked list of tests */
	struct ac_mem_info	*mem_info;
	uint_t			board;
	uint_t			bank;
	caddr_t			bufp;		/* pointer to buffer page */
	caddr_t			va;		/* test target VA */
	ac_mem_test_start_t	info;
	uint_t			in_test;	/* count of threads in test */
};

/* list of tests in progress (list protected test_mutex) */
static struct test_info 	*test_base = NULL;
static kmutex_t			test_mutex;
static int			test_mutex_initialized = FALSE;

static mem_test_handle_t	mem_test_sequence_id = 0;

void
ac_mapin(uint64_t pa, caddr_t va)
{
	pfn_t	pfn;
	tte_t	tte;

	pfn = pa >> MMU_PAGESHIFT;
	tte.tte_inthi = TTE_VALID_INT | TTE_SZ_INT(TTE8K) |
	    TTE_PFN_INTHI(pfn);
	tte.tte_intlo = TTE_PFN_INTLO(pfn) | TTE_CP_INT |
	    TTE_PRIV_INT | TTE_LCK_INT | TTE_HWWR_INT;
	sfmmu_dtlb_ld_kva(va, &tte);

}

void
ac_unmap(caddr_t va)
{
	vtag_flushpage(va, (uint64_t)ksfmmup);
}

int
ac_mem_test_start(ac_cfga_pkt_t *pkt, int flag)
{
	struct ac_soft_state	*softsp;
	struct ac_mem_info	*mem_info;
	struct bd_list		*board;
	struct test_info	*test;
	uint64_t		decode;

	/* XXX if ac ever detaches... */
	if (test_mutex_initialized == FALSE) {
		mutex_init(&test_mutex, NULL, MUTEX_DEFAULT, NULL);
		test_mutex_initialized = TRUE;
	}

	/*
	 * Is the specified bank testable?
	 */

	board = fhc_bdlist_lock(pkt->softsp->board);
	if (board == NULL || board->ac_softsp == NULL) {
		fhc_bdlist_unlock();
		AC_ERR_SET(pkt, AC_ERR_BD);
		return (EINVAL);
	}
	ASSERT(pkt->softsp == board->ac_softsp);

	/* verify the board is of the correct type */
	switch (board->sc.type) {
	case CPU_BOARD:
	case MEM_BOARD:
		break;
	default:
		fhc_bdlist_unlock();
		AC_ERR_SET(pkt, AC_ERR_BD_TYPE);
		return (EINVAL);
	}

	/*
	 * Memory must be in the spare state to be testable.
	 * However, spare memory that is testing can't be tested
	 * again, instead return the current test info.
	 */
	softsp = pkt->softsp;
	mem_info = &softsp->bank[pkt->bank];
	if (!MEM_BOARD_VISIBLE(board) ||
	    fhc_bd_busy(softsp->board) ||
	    mem_info->rstate != SYSC_CFGA_RSTATE_CONNECTED ||
	    mem_info->ostate != SYSC_CFGA_OSTATE_UNCONFIGURED) {
		fhc_bdlist_unlock();
		AC_ERR_SET(pkt, AC_ERR_BD_STATE);
		return (EINVAL);
	}
	if (mem_info->busy) {	/* oops, testing? */
		/*
		 * find the test entry
		 */
		ASSERT(test_mutex_initialized);
		mutex_enter(&test_mutex);
		for (test = test_base; test != NULL; test = test->next) {
			if (test->board == softsp->board &&
			    test->bank == pkt->bank)
				break;
		}
		if (test == NULL) {
			mutex_exit(&test_mutex);
			fhc_bdlist_unlock();
			/* Not busy testing. */
			AC_ERR_SET(pkt, AC_ERR_BD_STATE);
			return (EINVAL);
		}

		/*
		 * return the current test information to the new caller
		 */
		if (ddi_copyout(&test->info, pkt->cmd_cfga.private,
		    sizeof (ac_mem_test_start_t), flag) != 0) {
			mutex_exit(&test_mutex);
			fhc_bdlist_unlock();
			return (EFAULT);		/* !broken user app */
		}
		mutex_exit(&test_mutex);
		fhc_bdlist_unlock();
		AC_ERR_SET(pkt, AC_ERR_MEM_BK);
		return (EBUSY);				/* signal bank in use */
	}

	/*
	 * at this point, we have an available bank to test.
	 * create a test buffer
	 */
	test = kmem_zalloc(sizeof (struct test_info), KM_SLEEP);
	test->va = vmem_alloc(heap_arena, PAGESIZE, VM_SLEEP);

	/* fill in all the test info details now */
	test->mem_info = mem_info;
	test->board = softsp->board;
	test->bank = pkt->bank;
	test->bufp = kmem_alloc(TEST_PAGESIZE, KM_SLEEP);
	test->info.handle = atomic_add_32_nv(&mem_test_sequence_id, 1);
	(void) drv_getparm(PPID, (ulong_t *)(&(test->info.tester_pid)));
	test->info.prev_condition = mem_info->condition;
	test->info.page_size = TEST_PAGESIZE;
	/* If Blackbird ever gets a variable line size, this will change. */
	test->info.line_size = cpunodes[CPU->cpu_id].ecache_linesize;
	decode = (pkt->bank == Bank0) ?
	    *softsp->ac_memdecode0 : *softsp->ac_memdecode1;
	test->info.afar_base = GRP_REALBASE(decode);
	test->info.bank_size = GRP_UK2SPAN(decode);

	/* return the information to the user */
	if (ddi_copyout(&test->info, pkt->cmd_cfga.private,
	    sizeof (ac_mem_test_start_t), flag) != 0) {

		/* oh well, tear down the test now */
		kmem_free(test->bufp, TEST_PAGESIZE);
		vmem_free(heap_arena, test->va, PAGESIZE);
		kmem_free(test, sizeof (struct test_info));

		fhc_bdlist_unlock();
		return (EFAULT);
	}

	mem_info->busy = TRUE;

	/* finally link us into the test database */
	mutex_enter(&test_mutex);
	test->next = test_base;
	test_base = test;
	mutex_exit(&test_mutex);

	fhc_bdlist_unlock();

#ifdef DEBUG
	cmn_err(CE_NOTE, "!memtest: start test[%u]: board %d, bank %d",
		test->info.handle, test->board, test->bank);
#endif /* DEBUG */
	return (DDI_SUCCESS);
}

int
ac_mem_test_stop(ac_cfga_pkt_t *pkt, int flag)
{
	struct test_info *test, **prev;
	ac_mem_test_stop_t stop;

	/* get test result information */
	if (ddi_copyin(pkt->cmd_cfga.private, &stop,
	    sizeof (ac_mem_test_stop_t), flag) != 0)
		return (EFAULT);

	/* bdlist protects all state changes... */
	(void) fhc_bdlist_lock(-1);

	/* find the test */
	mutex_enter(&test_mutex);
	prev = &test_base;
	for (test = test_base; test != NULL; test = test->next) {
		if (test->info.handle == stop.handle)
			break;			/* found the test */
		prev = &test->next;
	}
	if (test == NULL) {
		mutex_exit(&test_mutex);
		fhc_bdlist_unlock();
		AC_ERR_SET(pkt, AC_ERR_MEM_TEST);
		return (EINVAL);
	}

#ifdef DEBUG
	cmn_err(CE_NOTE,
		"!memtest: stop test[%u]: board %d, bank %d,"
		" condition %d",
		test->info.handle, test->board,
		test->bank, stop.condition);
#endif /* DEBUG */

	/* first unlink us from the test list (to allow no more entries) */
	*prev = test->next;

	/* then, wait for current tests to complete */
	while (test->in_test != 0)
		delay(1);

	mutex_exit(&test_mutex);

	/* clean up the test related allocations */
	vmem_free(heap_arena, test->va, PAGESIZE);
	kmem_free(test->bufp, TEST_PAGESIZE);

	/* update the bank condition accordingly */
	test->mem_info->condition = stop.condition;
	test->mem_info->status_change = ddi_get_time();

	test->mem_info->busy = FALSE;

	/* finally, delete the test element */
	kmem_free(test, sizeof (struct test_info));

	fhc_bdlist_unlock();

	return (DDI_SUCCESS);
}

void
ac_mem_test_stop_on_close(uint_t board, uint_t bank)
{
	struct test_info *test, **prev;
	sysc_cfga_cond_t condition = SYSC_CFGA_COND_UNKNOWN;

	/* bdlist protects all state changes... */
	(void) fhc_bdlist_lock(-1);

	/* find the test */
	mutex_enter(&test_mutex);
	prev = &test_base;
	for (test = test_base; test != NULL; test = test->next) {
		if (test->board == board && test->bank == bank)
			break;			/* found the test */
		prev = &test->next;
	}
	if (test == NULL) {
		/* No test running, nothing to do. */
		mutex_exit(&test_mutex);
		fhc_bdlist_unlock();
		return;
	}

#ifdef DEBUG
	cmn_err(CE_NOTE, "!memtest: stop test[%u] on close: "
	    "board %d, bank %d, condition %d", test->info.handle,
	    test->board, test->bank, condition);
#endif /* DEBUG */

	/* first unlink us from the test list (to allow no more entries) */
	*prev = test->next;

	ASSERT(test->in_test == 0);

	mutex_exit(&test_mutex);

	/* clean up the test related allocations */
	vmem_free(heap_arena, test->va, PAGESIZE);
	kmem_free(test->bufp, TEST_PAGESIZE);

	/* update the bank condition accordingly */
	test->mem_info->condition = condition;
	test->mem_info->status_change = ddi_get_time();

	test->mem_info->busy = FALSE;

	/* finally, delete the test element */
	kmem_free(test, sizeof (struct test_info));

	fhc_bdlist_unlock();
}

int
ac_mem_test_read(ac_cfga_pkt_t *pkt, int flag)
{
	struct test_info *test;
	uint_t page_offset;
	uint64_t page_pa;
	uint_t pstate_save;
	caddr_t	src_va, dst_va;
	uint64_t orig_err;
	int retval = DDI_SUCCESS;
	sunfire_processor_error_regs_t error_buf;
	int error_found;
	ac_mem_test_read_t t_read;

#ifdef _MULTI_DATAMODEL
	switch (ddi_model_convert_from(flag & FMODELS)) {
	case DDI_MODEL_ILP32: {
		ac_mem_test_read32_t t_read32;

		if (ddi_copyin(pkt->cmd_cfga.private, &t_read32,
		    sizeof (ac_mem_test_read32_t), flag) != 0)
			return (EFAULT);
		t_read.handle = t_read32.handle;
		t_read.page_buf = (void *)(uintptr_t)t_read32.page_buf;
		t_read.address = t_read32.address;
		t_read.error_buf = (sunfire_processor_error_regs_t *)
		    (uintptr_t)t_read32.error_buf;
		break;
	}
	case DDI_MODEL_NONE:
		if (ddi_copyin(pkt->cmd_cfga.private, &t_read,
		    sizeof (ac_mem_test_read_t), flag) != 0)
			return (EFAULT);
		break;
	}
#else /* _MULTI_DATAMODEL */
	if (ddi_copyin(pkt->cmd_cfga.private, &t_read,
	    sizeof (ac_mem_test_read_t), flag) != 0)
		return (EFAULT);
#endif /* _MULTI_DATAMODEL */

	/* verify the handle */
	mutex_enter(&test_mutex);
	for (test = test_base; test != NULL; test = test->next) {
		if (test->info.handle == t_read.handle)
			break;
	}
	if (test == NULL) {
		mutex_exit(&test_mutex);
		AC_ERR_SET(pkt, AC_ERR_MEM_TEST);
		return (EINVAL);
	}

	/* bump the busy bit */
	atomic_add_32(&test->in_test, 1);
	mutex_exit(&test_mutex);

	/* verify the remaining parameters */
	if ((t_read.address.page_num >=
	    test->info.bank_size / test->info.page_size) ||
	    (t_read.address.line_count == 0) ||
	    (t_read.address.line_count >
	    test->info.page_size / test->info.line_size) ||
	    (t_read.address.line_offset >=
	    test->info.page_size / test->info.line_size) ||
	    ((t_read.address.line_offset + t_read.address.line_count) >
	    test->info.page_size / test->info.line_size)) {
		AC_ERR_SET(pkt, AC_ERR_MEM_TEST_PAR);
		retval = EINVAL;
		goto read_done;
	}

	page_offset = t_read.address.line_offset * test->info.line_size;
	page_pa = test->info.afar_base +
	    t_read.address.page_num * test->info.page_size;
	dst_va = test->bufp + page_offset;
	src_va = test->va + page_offset;

	/* time to go quiet */
	kpreempt_disable();

	/* we need a va for the block instructions */
	ac_mapin(page_pa, test->va);

	pstate_save = disable_vec_intr();

	/* disable errors */
	orig_err = get_error_enable();
	set_error_enable(orig_err & ~(EER_CEEN | EER_NCEEN));

	/* copy the data again (using our very special copy) */
	ac_blkcopy(src_va, dst_va, t_read.address.line_count,
	    test->info.line_size);

	/* process errors (if any) */
	error_buf.module_id = CPU->cpu_id;
	get_asyncflt(&(error_buf.afsr));
	get_asyncaddr(&(error_buf.afar));
	get_udb_errors(&(error_buf.udbh_error_reg),
	    &(error_buf.udbl_error_reg));

	/*
	 * clean up after our no-error copy but before enabling ints.
	 * XXX what to do about other error types?
	 */
	if (error_buf.afsr & (P_AFSR_CE | P_AFSR_UE)) {
		extern void clr_datapath(void); /* XXX */

		clr_datapath();
		set_asyncflt(error_buf.afsr);
		retval = EIO;
		error_found = TRUE;
	} else {
		error_found = FALSE;
	}

	/* errors back on */
	set_error_enable(orig_err);

	enable_vec_intr(pstate_save);

	/* tear down translation (who needs an mmu) */
	ac_unmap(test->va);

	/* we're back! */
	kpreempt_enable();

	/*
	 * If there was a data error, attempt to return the error_buf
	 * to the user.
	 */
	if (error_found) {
		if (ddi_copyout(&error_buf, t_read.error_buf,
		    sizeof (sunfire_processor_error_regs_t), flag) != 0) {
			retval = EFAULT;
			/* Keep going */
		}
	}

	/*
	 * Then, return the page to the user (always)
	 */
	if (ddi_copyout(dst_va, (caddr_t)(t_read.page_buf) + page_offset,
	    t_read.address.line_count * test->info.line_size, flag) != 0) {
		retval = EFAULT;
	}

read_done:
	atomic_add_32(&test->in_test, -1);
	return (retval);
}

int
ac_mem_test_write(ac_cfga_pkt_t *pkt, int flag)
{
	struct test_info *test;
	uint_t page_offset;
	uint64_t page_pa;
	uint_t pstate_save;
	caddr_t	src_va, dst_va;
	int retval = DDI_SUCCESS;
	ac_mem_test_write_t t_write;

#ifdef _MULTI_DATAMODEL
	switch (ddi_model_convert_from(flag & FMODELS)) {
	case DDI_MODEL_ILP32: {
		ac_mem_test_write32_t t_write32;

		if (ddi_copyin(pkt->cmd_cfga.private, &t_write32,
		    sizeof (ac_mem_test_write32_t), flag) != 0)
			return (EFAULT);
		t_write.handle = t_write32.handle;
		t_write.page_buf = (void *)(uintptr_t)t_write32.page_buf;
		t_write.address = t_write32.address;
		break;
	}
	case DDI_MODEL_NONE:
		if (ddi_copyin(pkt->cmd_cfga.private, &t_write,
		    sizeof (ac_mem_test_write_t), flag) != 0)
			return (EFAULT);
		break;
	}
#else /* _MULTI_DATAMODEL */
	if (ddi_copyin(pkt->cmd_cfga.private, &t_write,
	    sizeof (ac_mem_test_write_t), flag) != 0)
		return (EFAULT);
#endif /* _MULTI_DATAMODEL */

	/* verify the handle */
	mutex_enter(&test_mutex);
	for (test = test_base; test != NULL; test = test->next) {
		if (test->info.handle == t_write.handle)
			break;
	}
	if (test == NULL) {
		mutex_exit(&test_mutex);
		return (EINVAL);
	}

	/* bump the busy bit */
	atomic_add_32(&test->in_test, 1);
	mutex_exit(&test_mutex);

	/* verify the remaining parameters */
	if ((t_write.address.page_num >=
	    test->info.bank_size / test->info.page_size) ||
	    (t_write.address.line_count == 0) ||
	    (t_write.address.line_count >
	    test->info.page_size / test->info.line_size) ||
	    (t_write.address.line_offset >=
	    test->info.page_size / test->info.line_size) ||
	    ((t_write.address.line_offset + t_write.address.line_count) >
	    test->info.page_size / test->info.line_size)) {
		AC_ERR_SET(pkt, AC_ERR_MEM_TEST_PAR);
		retval = EINVAL;
		goto write_done;
	}

	page_offset = t_write.address.line_offset * test->info.line_size;
	page_pa = test->info.afar_base +
	    t_write.address.page_num * test->info.page_size;
	src_va = test->bufp + page_offset;
	dst_va = test->va + page_offset;

	/* copy in the specified user data */
	if (ddi_copyin((caddr_t)(t_write.page_buf) + page_offset, src_va,
	    t_write.address.line_count * test->info.line_size, flag) != 0) {
		retval = EFAULT;
		goto write_done;
	}

	/* time to go quiet */
	kpreempt_disable();

	/* we need a va for the block instructions */
	ac_mapin(page_pa, test->va);

	pstate_save = disable_vec_intr();

	/* copy the data again (using our very special copy) */
	ac_blkcopy(src_va, dst_va, t_write.address.line_count,
	    test->info.line_size);

	enable_vec_intr(pstate_save);

	/* tear down translation (who needs an mmu) */
	ac_unmap(test->va);

	/* we're back! */
	kpreempt_enable();

write_done:
	atomic_add_32(&test->in_test, -1);
	return (retval);
}