#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>
#define TEST_PAGESIZE MMU_PAGESIZE
struct test_info {
struct test_info *next;
struct ac_mem_info *mem_info;
uint_t board;
uint_t bank;
caddr_t bufp;
caddr_t va;
ac_mem_test_start_t info;
uint_t in_test;
};
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;
if (test_mutex_initialized == FALSE) {
mutex_init(&test_mutex, NULL, MUTEX_DEFAULT, NULL);
test_mutex_initialized = TRUE;
}
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);
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);
}
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) {
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();
AC_ERR_SET(pkt, AC_ERR_BD_STATE);
return (EINVAL);
}
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);
}
mutex_exit(&test_mutex);
fhc_bdlist_unlock();
AC_ERR_SET(pkt, AC_ERR_MEM_BK);
return (EBUSY);
}
test = kmem_zalloc(sizeof (struct test_info), KM_SLEEP);
test->va = vmem_alloc(heap_arena, PAGESIZE, VM_SLEEP);
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_inc_32_nv(&mem_test_sequence_id);
(void) drv_getparm(PPID, (ulong_t *)(&(test->info.tester_pid)));
test->info.prev_condition = mem_info->condition;
test->info.page_size = TEST_PAGESIZE;
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);
if (ddi_copyout(&test->info, pkt->cmd_cfga.private,
sizeof (ac_mem_test_start_t), flag) != 0) {
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;
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
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;
if (ddi_copyin(pkt->cmd_cfga.private, &stop,
sizeof (ac_mem_test_stop_t), flag) != 0)
return (EFAULT);
(void) fhc_bdlist_lock(-1);
mutex_enter(&test_mutex);
prev = &test_base;
for (test = test_base; test != NULL; test = test->next) {
if (test->info.handle == stop.handle)
break;
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
*prev = test->next;
while (test->in_test != 0)
delay(1);
mutex_exit(&test_mutex);
vmem_free(heap_arena, test->va, PAGESIZE);
kmem_free(test->bufp, TEST_PAGESIZE);
test->mem_info->condition = stop.condition;
test->mem_info->status_change = ddi_get_time();
test->mem_info->busy = FALSE;
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;
(void) fhc_bdlist_lock(-1);
mutex_enter(&test_mutex);
prev = &test_base;
for (test = test_base; test != NULL; test = test->next) {
if (test->board == board && test->bank == bank)
break;
prev = &test->next;
}
if (test == NULL) {
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
*prev = test->next;
ASSERT(test->in_test == 0);
mutex_exit(&test_mutex);
vmem_free(heap_arena, test->va, PAGESIZE);
kmem_free(test->bufp, TEST_PAGESIZE);
test->mem_info->condition = condition;
test->mem_info->status_change = ddi_get_time();
test->mem_info->busy = FALSE;
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
if (ddi_copyin(pkt->cmd_cfga.private, &t_read,
sizeof (ac_mem_test_read_t), flag) != 0)
return (EFAULT);
#endif
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);
}
atomic_inc_32(&test->in_test);
mutex_exit(&test_mutex);
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;
kpreempt_disable();
ac_mapin(page_pa, test->va);
pstate_save = disable_vec_intr();
orig_err = get_error_enable();
set_error_enable(orig_err & ~(EER_CEEN | EER_NCEEN));
ac_blkcopy(src_va, dst_va, t_read.address.line_count,
test->info.line_size);
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));
if (error_buf.afsr & (P_AFSR_CE | P_AFSR_UE)) {
extern void clr_datapath(void);
clr_datapath();
set_asyncflt(error_buf.afsr);
retval = EIO;
error_found = TRUE;
} else {
error_found = FALSE;
}
set_error_enable(orig_err);
enable_vec_intr(pstate_save);
ac_unmap(test->va);
kpreempt_enable();
if (error_found) {
if (ddi_copyout(&error_buf, t_read.error_buf,
sizeof (sunfire_processor_error_regs_t), flag) != 0) {
retval = EFAULT;
}
}
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_dec_32(&test->in_test);
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
if (ddi_copyin(pkt->cmd_cfga.private, &t_write,
sizeof (ac_mem_test_write_t), flag) != 0)
return (EFAULT);
#endif
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);
}
atomic_inc_32(&test->in_test);
mutex_exit(&test_mutex);
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;
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;
}
kpreempt_disable();
ac_mapin(page_pa, test->va);
pstate_save = disable_vec_intr();
ac_blkcopy(src_va, dst_va, t_write.address.line_count,
test->info.line_size);
enable_vec_intr(pstate_save);
ac_unmap(test->va);
kpreempt_enable();
write_done:
atomic_dec_32(&test->in_test);
return (retval);
}