/*
* 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 2007 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/systm.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/obpdefs.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/fhc.h>
#include <sys/ac.h>
#include <sys/vm.h>
#include <sys/cpu_module.h>
#include <vm/seg_kmem.h>
#include <vm/hat_sfmmu.h>
#include <sys/mem_config.h>
#include <sys/mem_cage.h>
/*
* Default to always clean memory on add to reduce chance
* of uncorrectable errors.
*/
int ac_add_clean = 1;
#define ADD_PAGESIZE MMU_PAGESIZE
ac_err_t
ac_kpm_err_cvt(int err)
{
switch (err) {
case KPHYSM_ESPAN:
return (AC_ERR_KPM_SPAN);
case KPHYSM_EFAULT:
return (AC_ERR_KPM_FAULT);
case KPHYSM_ERESOURCE:
return (AC_ERR_KPM_RESOURCE);
case KPHYSM_ENOTSUP:
return (AC_ERR_KPM_NOTSUP);
case KPHYSM_ENOHANDLES:
return (AC_ERR_KPM_NOHANDLES);
case KPHYSM_ENONRELOC:
return (AC_ERR_KPM_NONRELOC);
case KPHYSM_EHANDLE:
return (AC_ERR_KPM_HANDLE);
case KPHYSM_EBUSY:
return (AC_ERR_KPM_BUSY);
case KPHYSM_ENOTVIABLE:
return (AC_ERR_KPM_NOTVIABLE);
case KPHYSM_ESEQUENCE:
return (AC_ERR_KPM_SEQUENCE);
case KPHYSM_ENOWORK:
return (AC_ERR_KPM_NOWORK);
case KPHYSM_ECANCELLED:
return (AC_ERR_KPM_CANCELLED);
case KPHYSM_ENOTFINISHED:
return (AC_ERR_KPM_NOTFINISHED);
case KPHYSM_ENOTRUNNING:
return (AC_ERR_KPM_NOTRUNNING);
case KPHYSM_EREFUSED:
return (AC_ERR_KPM_REFUSED);
case KPHYSM_EDUP:
return (AC_ERR_KPM_DUP);
default:
return (AC_ERR_DEFAULT);
}
}
static int
ac_add_bank(struct bd_list *add, ac_cfga_pkt_t *pkt)
{
uint64_t decode;
uint64_t base_pa;
uint64_t limit_pa;
uint64_t current_pa;
int errs;
uint64_t bank_size;
struct ac_mem_info *mem_info;
struct ac_soft_state *asp = pkt->softsp;
uint_t ilv;
/*
* Cannot add interleaved banks at the moment.
*/
ilv = (pkt->bank == Bank0) ?
INTLV0(*asp->ac_memctl) : INTLV1(*asp->ac_memctl);
if (ilv != 1) {
AC_ERR_SET(pkt, AC_ERR_MEM_DEINTLV);
return (EINVAL);
}
/*
* Determine the physical location of the selected bank
*/
decode = (pkt->bank == Bank0) ?
*asp->ac_memdecode0 : *asp->ac_memdecode1;
base_pa = GRP_REALBASE(decode);
bank_size = GRP_UK2SPAN(decode);
limit_pa = base_pa + bank_size;
mem_info = &asp->bank[pkt->bank];
if (ac_add_clean || mem_info->condition != SYSC_CFGA_COND_OK) {
caddr_t base_va;
caddr_t fill_buf;
int linesize;
/*
* We need a page_va and a fill buffer for this operation
*/
base_va = vmem_alloc(heap_arena, PAGESIZE, VM_SLEEP);
fill_buf = kmem_zalloc(ADD_PAGESIZE, KM_SLEEP);
linesize = cpunodes[CPU->cpu_id].ecache_linesize;
/*
* zero fill the memory -- indirectly initializes the ECC
*/
kpreempt_disable();
for (current_pa = base_pa; current_pa < limit_pa;
current_pa += ADD_PAGESIZE) {
/* map current pa */
ac_mapin(current_pa, base_va);
/* fill the target page */
ac_blkcopy(fill_buf, base_va,
ADD_PAGESIZE/linesize, linesize);
/* tear down translation */
ac_unmap(base_va);
}
kpreempt_enable();
/*
* clean up temporary resources
*/
kmem_free(fill_buf, ADD_PAGESIZE);
vmem_free(heap_arena, base_va, PAGESIZE);
}
/*
* give the memory to Solaris
*/
errs = kphysm_add_memory_dynamic(base_pa >> PAGESHIFT,
bank_size >> PAGESHIFT);
if (errs != KPHYSM_OK) {
AC_ERR_SET(pkt, ac_kpm_err_cvt(errs));
return (EINVAL);
}
/*
* Add the board to the cage growth list.
*/
errs = kcage_range_add(btop(base_pa), btop(bank_size), KCAGE_DOWN);
/* TODO: deal with error return. */
if (errs != 0)
cmn_err(CE_NOTE, "ac_add_bank(): board %d, bank %d, "
"kcage_range_add() returned %d",
add->sc.board, pkt->bank, errs);
return (0);
}
int
ac_add_memory(ac_cfga_pkt_t *pkt)
{
struct bd_list *board;
struct ac_mem_info *mem_info;
int force = pkt->cmd_cfga.force;
int retval;
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);
}
/* verify the memory condition is acceptable */
mem_info = &pkt->softsp->bank[pkt->bank];
if (!MEM_BOARD_VISIBLE(board) || mem_info->busy ||
fhc_bd_busy(pkt->softsp->board) ||
mem_info->rstate != SYSC_CFGA_RSTATE_CONNECTED ||
mem_info->ostate != SYSC_CFGA_OSTATE_UNCONFIGURED ||
(!force && mem_info->condition != SYSC_CFGA_COND_OK)) {
fhc_bdlist_unlock();
AC_ERR_SET(pkt, AC_ERR_BD_STATE);
return (EINVAL);
}
/*
* at this point, we have an available bank to add.
* mark it busy and initiate the add function.
*/
mem_info->busy = TRUE;
fhc_bdlist_unlock();
retval = ac_add_bank(board, pkt);
/*
* We made it! Update the status and get out of here.
*/
(void) fhc_bdlist_lock(-1);
mem_info->busy = FALSE;
if (retval == 0) {
mem_info->ostate = SYSC_CFGA_OSTATE_CONFIGURED;
mem_info->status_change = ddi_get_time();
}
fhc_bdlist_unlock();
if (retval != 0) {
return (retval);
}
return (DDI_SUCCESS);
}