/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern ac_err_t ac_kpm_err_cvt(int); int ac_del_clean = 0; /* * Default timeout, in seconds, for delete. * Time is counted when no progress is being made. */ static int ac_del_timeout = 60; #define DEL_PAGESIZE MMU_PAGESIZE struct del_status { struct del_status *next; memhandle_t handle; volatile int its_done; int done_error; kcondvar_t ac_del_cv; int del_timeout; int del_noprogress; ac_err_t cancel_code; timeout_id_t to_id; pgcnt_t last_collected; }; static struct del_status *ac_del_list; static kmutex_t ac_del_mutex; static struct del_status * ac_del_alloc_status() { struct del_status *dsp; dsp = (struct del_status *)kmem_zalloc(sizeof (*dsp), KM_SLEEP); mutex_enter(&ac_del_mutex); dsp->next = ac_del_list; ac_del_list = dsp; mutex_exit(&ac_del_mutex); return (dsp); } static void ac_del_free_status(struct del_status *dsp) { struct del_status **dspp; mutex_enter(&ac_del_mutex); dspp = &ac_del_list; while (*dspp != NULL) { if (*dspp == dsp) break; dspp = &(*dspp)->next; } ASSERT(*dspp == dsp); if (*dspp == dsp) { *dspp = dsp->next; } mutex_exit(&ac_del_mutex); kmem_free((void *)dsp, sizeof (*dsp)); } static void del_comp(void *arg, int error) { struct del_status *dsp; dsp = (struct del_status *)arg; mutex_enter(&ac_del_mutex); #ifdef DEBUG { struct del_status *adsp; for (adsp = ac_del_list; adsp != NULL; adsp = adsp->next) { if (adsp == dsp) break; } ASSERT(adsp != NULL); } #endif /* DEBUG */ dsp->its_done = 1; dsp->done_error = error; cv_signal(&dsp->ac_del_cv); mutex_exit(&ac_del_mutex); } /*ARGSUSED*/ static void del_to_scan(void *arg) { struct del_status *dsp; int do_cancel; memdelstat_t dstat; int err; dsp = arg; #ifdef DEBUG { struct del_status *adsp; mutex_enter(&ac_del_mutex); for (adsp = ac_del_list; adsp != NULL; adsp = adsp->next) { if (adsp == dsp) break; } ASSERT(adsp != NULL); mutex_exit(&ac_del_mutex); } #endif /* DEBUG */ do_cancel = 0; err = kphysm_del_status(dsp->handle, &dstat); mutex_enter(&ac_del_mutex); if (dsp->its_done) { mutex_exit(&ac_del_mutex); return; } if ((err == KPHYSM_OK) && (dsp->last_collected != dstat.collected)) { dsp->del_noprogress = 0; dsp->last_collected = dstat.collected; } else { dsp->del_noprogress++; if (dsp->del_noprogress >= dsp->del_timeout) { if (dsp->cancel_code == 0) dsp->cancel_code = AC_ERR_TIMEOUT; do_cancel = 1; } } if (!do_cancel) dsp->to_id = timeout(del_to_scan, arg, hz); else dsp->to_id = 0; mutex_exit(&ac_del_mutex); if (do_cancel) (void) kphysm_del_cancel(dsp->handle); } static void del_to_start(struct del_status *dsp) { if (dsp->del_timeout != 0) dsp->to_id = timeout(del_to_scan, dsp, hz); } static void del_to_stop(struct del_status *dsp) { timeout_id_t tid; while ((tid = dsp->to_id) != 0) { dsp->to_id = 0; mutex_exit(&ac_del_mutex); (void) untimeout(tid); mutex_enter(&ac_del_mutex); } } static int ac_del_bank_add_span( memhandle_t handle, ac_cfga_pkt_t *pkt) { uint64_t decode; uint64_t base_pa; uint64_t bank_size; pfn_t base; pgcnt_t npgs; int errs; int ret; struct ac_soft_state *asp = pkt->softsp; uint_t ilv; /* * Cannot delete 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); base = base_pa >> PAGESHIFT; npgs = bank_size >> PAGESHIFT; /* * Delete the pages from the cage growth list. */ kcage_range_lock(); ret = kcage_range_delete(base, npgs); kcage_range_unlock(); if (ret != 0) { /* TODO: Should this be a separate error? */ AC_ERR_SET(pkt, AC_ERR_KPM_NONRELOC); return (EINVAL); } /* * Add to delete memory list. */ if ((errs = kphysm_del_span(handle, base, npgs)) != KPHYSM_OK) { AC_ERR_SET(pkt, ac_kpm_err_cvt(errs)); /* * Restore the pages to the cage growth list. * TODO: We should not unconditionally add back * if we conditionally add at memory add time. */ kcage_range_lock(); errs = kcage_range_add(base, npgs, 1); /* TODO: deal with error return. */ if (errs != 0) { AC_ERR_SET(pkt, ac_kpm_err_cvt(errs)); cmn_err(CE_NOTE, "ac_del_bank_add_span(): " "board %d, bank %d, " "kcage_range_add() returned %d", pkt->softsp->board, pkt->bank, errs); } kcage_range_unlock(); return (EINVAL); } return (0); } static void ac_del_bank_add_cage( struct bd_list *del, enum ac_bank_id bank) { uint64_t decode; uint64_t base_pa; uint64_t bank_size; pfn_t base; pgcnt_t npgs; int errs; struct ac_soft_state *asp = (struct ac_soft_state *)(del->ac_softsp); /* * Determine the physical location of the selected bank */ decode = (bank == Bank0) ? *asp->ac_memdecode0 : *asp->ac_memdecode1; base_pa = GRP_REALBASE(decode); bank_size = GRP_UK2SPAN(decode); base = base_pa >> PAGESHIFT; npgs = bank_size >> PAGESHIFT; /* * Restore the pages to the cage growth list. * TODO: We should not unconditionally add back * if we conditionally add at memory add time. */ kcage_range_lock(); errs = kcage_range_add(base, npgs, 1); /* TODO: deal with error return. */ if (errs != 0) cmn_err(CE_NOTE, "ac_del_bank_add_cage(): " "board %d, bank %d, " "kcage_range_add() returned %d", del->sc.board, bank, errs); kcage_range_unlock(); } static int ac_del_bank_run(struct del_status *dsp, ac_cfga_pkt_t *pkt) { int errs; dsp->its_done = 0; if ((errs = kphysm_del_start(dsp->handle, del_comp, (void *)dsp)) != KPHYSM_OK) { AC_ERR_SET(pkt, ac_kpm_err_cvt(errs)); return (EINVAL); } /* Wait for it to complete. */ mutex_enter(&ac_del_mutex); del_to_start(dsp); while (!dsp->its_done) { if (!cv_wait_sig(&dsp->ac_del_cv, &ac_del_mutex)) { if (dsp->cancel_code == 0) dsp->cancel_code = AC_ERR_INTR; mutex_exit(&ac_del_mutex); errs = kphysm_del_cancel(dsp->handle); mutex_enter(&ac_del_mutex); if (errs != KPHYSM_OK) { ASSERT(errs == KPHYSM_ENOTRUNNING); } break; } } /* * If the loop exited due to a signal, we must continue to wait * using cv_wait() as the signal is pending until syscall exit. */ while (!dsp->its_done) { cv_wait(&dsp->ac_del_cv, &ac_del_mutex); } if (dsp->done_error != KPHYSM_OK) { AC_ERR_SET(pkt, ac_kpm_err_cvt(dsp->done_error)); if ((dsp->done_error == KPHYSM_ECANCELLED) || (dsp->done_error == KPHYSM_EREFUSED)) { errs = EINTR; if (dsp->cancel_code != 0) { AC_ERR_SET(pkt, dsp->cancel_code); } } else { errs = EINVAL; } } else errs = 0; del_to_stop(dsp); mutex_exit(&ac_del_mutex); return (errs); } /* * set the memory to known state for debugging */ static void ac_bank_write_pattern(struct bd_list *del, enum ac_bank_id bank) { uint64_t decode; uint64_t base_pa; uint64_t limit_pa; uint64_t bank_size; uint64_t current_pa; caddr_t base_va; caddr_t fill_buf; struct ac_soft_state *asp = (struct ac_soft_state *)(del->ac_softsp); int linesize; /* * Determine the physical location of the selected bank */ decode = (bank == Bank0) ? *asp->ac_memdecode0 : *asp->ac_memdecode1; base_pa = GRP_REALBASE(decode); bank_size = GRP_UK2SPAN(decode); limit_pa = base_pa + bank_size; linesize = cpunodes[CPU->cpu_id].ecache_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(DEL_PAGESIZE, KM_SLEEP); { typedef uint32_t patt_t; patt_t *bf, *bfe, patt; bf = (patt_t *)fill_buf; bfe = (patt_t *)((char *)fill_buf + DEL_PAGESIZE); patt = 0xbeaddeed; while (bf < bfe) *bf++ = patt; } /* * 'empty' the memory */ kpreempt_disable(); for (current_pa = base_pa; current_pa < limit_pa; current_pa += DEL_PAGESIZE) { /* map current pa */ ac_mapin(current_pa, base_va); /* fill the target page */ ac_blkcopy(fill_buf, base_va, DEL_PAGESIZE/linesize, linesize); /* tear down translation */ ac_unmap(base_va); } kpreempt_enable(); /* * clean up temporary resources */ { /* Distinguish the fill buf from memory deleted! */ typedef uint32_t patt_t; patt_t *bf, *bfe, patt; bf = (patt_t *)fill_buf; bfe = (patt_t *)((char *)fill_buf + DEL_PAGESIZE); patt = 0xbeadfeed; while (bf < bfe) *bf++ = patt; } kmem_free(fill_buf, DEL_PAGESIZE); vmem_free(heap_arena, base_va, PAGESIZE); } int ac_del_memory(ac_cfga_pkt_t *pkt) { struct bd_list *board; struct ac_mem_info *mem_info; int busy_set; struct del_status *dsp; memdelstat_t dstat; int retval; int r_errs; struct ac_soft_state *asp; if (!kcage_on) { static int cage_msg_done = 0; if (!cage_msg_done) { cage_msg_done = 1; cmn_err(CE_NOTE, "ac: memory delete" " refused: cage is off"); } AC_ERR_SET(pkt, ac_kpm_err_cvt(KPHYSM_ENONRELOC)); return (EINVAL); } dsp = ac_del_alloc_status(); if ((retval = kphysm_del_gethandle(&dsp->handle)) != KPHYSM_OK) { ac_del_free_status(dsp); AC_ERR_SET(pkt, ac_kpm_err_cvt(retval)); return (EINVAL); } retval = 0; busy_set = 0; board = fhc_bdlist_lock(pkt->softsp->board); if (board == NULL || board->ac_softsp == NULL) { fhc_bdlist_unlock(); AC_ERR_SET(pkt, AC_ERR_BD); retval = EINVAL; goto out; } ASSERT(pkt->softsp == board->ac_softsp); asp = pkt->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); retval = EINVAL; goto out; } /* verify the memory condition is acceptable */ mem_info = &asp->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_CONFIGURED) { fhc_bdlist_unlock(); AC_ERR_SET(pkt, AC_ERR_BD_STATE); retval = EINVAL; goto out; } if ((dsp->del_timeout = pkt->cmd_cfga.arg) == -1) dsp->del_timeout = ac_del_timeout; /* * at this point, we have an available bank to del. * mark it busy and initiate the del function. */ mem_info->busy = TRUE; fhc_bdlist_unlock(); busy_set = 1; retval = ac_del_bank_add_span(dsp->handle, pkt); out: if (retval != 0) { r_errs = kphysm_del_release(dsp->handle); ASSERT(r_errs == KPHYSM_OK); if (busy_set) { board = fhc_bdlist_lock(pkt->softsp->board); ASSERT(board != NULL && board->ac_softsp != NULL); ASSERT(board->sc.type == CPU_BOARD || board->sc.type == MEM_BOARD); ASSERT(asp == (struct ac_soft_state *)(board->ac_softsp)); mem_info = &asp->bank[pkt->bank]; ASSERT(mem_info->busy != FALSE); ASSERT(mem_info->ostate == SYSC_CFGA_OSTATE_CONFIGURED); mem_info->busy = FALSE; fhc_bdlist_unlock(); } ac_del_free_status(dsp); return (retval); } (void) kphysm_del_status(dsp->handle, &dstat); retval = ac_del_bank_run(dsp, pkt); r_errs = kphysm_del_release(dsp->handle); ASSERT(r_errs == KPHYSM_OK); board = fhc_bdlist_lock(pkt->softsp->board); ASSERT(board != NULL && board->ac_softsp != NULL); ASSERT(board->sc.type == CPU_BOARD || board->sc.type == MEM_BOARD); ASSERT(asp == (struct ac_soft_state *)(board->ac_softsp)); mem_info = &asp->bank[pkt->bank]; ASSERT(mem_info->busy != FALSE); ASSERT(mem_info->ostate == SYSC_CFGA_OSTATE_CONFIGURED); mem_info->busy = FALSE; if (retval == 0) { mem_info->ostate = SYSC_CFGA_OSTATE_UNCONFIGURED; mem_info->status_change = ddi_get_time(); if (ac_del_clean) { /* DEBUG - set memory to known state */ ac_bank_write_pattern(board, pkt->bank); } } else { /* * Restore the pages to the cage growth list. */ ac_del_bank_add_cage(board, pkt->bank); } fhc_bdlist_unlock(); ac_del_free_status(dsp); return (retval); }