page_retire.c revision af4c679f647cf088543c762e33d41a3ac52cfa14
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Page Retire - Big Theory Statement.
*
* This file handles removing sections of faulty memory from use when the
* user land FMA Diagnosis Engine requests that a page be removed or when
* a CE or UE is detected by the hardware.
*
* In the bad old days, the kernel side of Page Retire did a lot of the work
* on its own. Now, with the DE keeping track of errors, the kernel side is
* rather simple minded on most platforms.
*
* Errors are all reflected to the DE, and after digesting the error and
* looking at all previously reported errors, the DE decides what should
* be done about the current error. If the DE wants a particular page to
* be retired, then the kernel page retire code is invoked via an ioctl.
* On non-FMA platforms, the ue_drain and ce_drain paths ends up calling
* page retire to handle the error. Since page retire is just a simple
* mechanism it doesn't need to differentiate between the different callers.
*
* The p_toxic field in the page_t is used to indicate which errors have
* occurred and what action has been taken on a given page. Because errors are
* reported without regard to the locked state of a page, no locks are used
* to SET the error bits in p_toxic. However, in order to clear the error
* bits, the page_t must be held exclusively locked.
*
* When page_retire() is called, it must be able to acquire locks, sleep, etc.
* It must not be called from high-level interrupt context.
*
* Depending on how the requested page is being used at the time of the retire
* request (and on the availability of sufficient system resources), the page
* may be retired immediately, or just marked for retirement later. For
* example, locked pages are marked, while free pages are retired. Multiple
* requests may be made to retire the same page, although there is no need
* to: once the p_toxic flags are set, the page will be retired as soon as it
* can be exclusively locked.
*
* The retire mechanism is driven centrally out of page_unlock(). To expedite
* the retirement of pages, further requests for SE_SHARED locks are denied
* as long as a page retirement is pending. In addition, as long as pages are
* pending retirement a background thread runs periodically trying to retire
* those pages. Pages which could not be retired while the system is running
* are scrubbed prior to rebooting to avoid latent errors on the next boot.
*
* UE pages without persistent errors are scrubbed and returned to service.
* Recidivist pages, as well as FMA-directed requests for retirement, result
* in the page being taken out of service. Once the decision is made to take
* a page out of service, the page is cleared, hashed onto the retired_pages
* vnode, marked as retired, and it is unlocked. No other requesters (except
* for unretire) are allowed to lock retired pages.
*
* The public routines return (sadly) 0 if they worked and a non-zero error
* value if something went wrong. This is done for the ioctl side of the
* world to allow errors to be reflected all the way out to user land. The
* non-zero values are explained in comments atop each function.
*/
/*
* Things to fix:
*
* 1. Trying to retire non-relocatable kvp pages may result in a
* quagmire. This is because seg_kmem() no longer keeps its pages locked,
* and calls page_lookup() in the free path; since kvp pages are modified
* and don't have a usable backing store, page_retire() can't do anything
* with them, and we'll keep denying the lock to seg_kmem_free() in a
* vicious cycle. To prevent that, we don't deny locks to kvp pages, and
* hence only try to retire a page from page_unlock() in the free path.
* Since most kernel pages are indefinitely held anyway, and don't
* participate in I/O, this is of little consequence.
*
* 2. Low memory situations will be interesting. If we don't have
* enough memory for page_relocate() to succeed, we won't be able to
* retire dirty pages; nobody will be able to push them out to disk
* either, since we aggressively deny the page lock. We could change
* fsflush so it can recognize this situation, grab the lock, and push
* the page out, where we'll catch it in the free path and retire it.
*
* 3. Beware of places that have code like this in them:
*
* if (! page_tryupgrade(pp)) {
* page_unlock(pp);
* while (! page_lock(pp, SE_EXCL, NULL, P_RECLAIM)) {
* / *NOTHING* /
* }
* }
* page_free(pp);
*
* The problem is that pp can change identity right after the
* page_unlock() call. In particular, page_retire() can step in
* there, change pp's identity, and hash pp onto the retired_vnode.
*
* Of course, other functions besides page_retire() can have the
* same effect. A kmem reader can waltz by, set up a mapping to the
* page, and then unlock the page. Page_free() will then go castors
* up. So if anybody is doing this, it's already a bug.
*
* 4. mdboot()'s call into page_retire_mdboot() should probably be
* moved lower. Where the call is made now, we can get into trouble
* by scrubbing a kernel page that is then accessed later.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mman.h>
#include <sys/vnode.h>
#include <sys/vfs_opreg.h>
#include <sys/cmn_err.h>
#include <sys/ksynch.h>
#include <sys/thread.h>
#include <sys/disp.h>
#include <sys/ontrap.h>
#include <sys/vmsystm.h>
#include <sys/mem_config.h>
#include <sys/atomic.h>
#include <sys/callb.h>
#include <vm/page.h>
#include <vm/vm_dep.h>
#include <vm/as.h>
#include <vm/hat.h>
#include <vm/seg_kmem.h>
/*
* vnode for all pages which are retired from the VM system;
*/
vnode_t *retired_pages;
static int page_retire_pp_finish(page_t *, void *, uint_t);
/*
* Make a list of all of the pages that have been marked for retirement
* but are not yet retired. At system shutdown, we will scrub all of the
* pages in the list in case there are outstanding UEs. Then, we
* cross-check this list against the number of pages that are yet to be
* retired, and if we find inconsistencies, we scan every page_t in the
* whole system looking for any pages that need to be scrubbed for UEs.
* The background thread also uses this queue to determine which pages
* it should keep trying to retire.
*/
#ifdef DEBUG
#define PR_PENDING_QMAX 32
#else /* DEBUG */
#define PR_PENDING_QMAX 256
#endif /* DEBUG */
page_t *pr_pending_q[PR_PENDING_QMAX];
kmutex_t pr_q_mutex;
/*
* Page retire global kstats
*/
struct page_retire_kstat {
kstat_named_t pr_retired;
kstat_named_t pr_requested;
kstat_named_t pr_requested_free;
kstat_named_t pr_enqueue_fail;
kstat_named_t pr_dequeue_fail;
kstat_named_t pr_pending;
kstat_named_t pr_pending_kas;
kstat_named_t pr_failed;
kstat_named_t pr_failed_kernel;
kstat_named_t pr_limit;
kstat_named_t pr_limit_exceeded;
kstat_named_t pr_fma;
kstat_named_t pr_mce;
kstat_named_t pr_ue;
kstat_named_t pr_ue_cleared_retire;
kstat_named_t pr_ue_cleared_free;
kstat_named_t pr_ue_persistent;
kstat_named_t pr_unretired;
};
static struct page_retire_kstat page_retire_kstat = {
{ "pages_retired", KSTAT_DATA_UINT64},
{ "pages_retire_request", KSTAT_DATA_UINT64},
{ "pages_retire_request_free", KSTAT_DATA_UINT64},
{ "pages_notenqueued", KSTAT_DATA_UINT64},
{ "pages_notdequeued", KSTAT_DATA_UINT64},
{ "pages_pending", KSTAT_DATA_UINT64},
{ "pages_pending_kas", KSTAT_DATA_UINT64},
{ "pages_deferred", KSTAT_DATA_UINT64},
{ "pages_deferred_kernel", KSTAT_DATA_UINT64},
{ "pages_limit", KSTAT_DATA_UINT64},
{ "pages_limit_exceeded", KSTAT_DATA_UINT64},
{ "pages_fma", KSTAT_DATA_UINT64},
{ "pages_multiple_ce", KSTAT_DATA_UINT64},
{ "pages_ue", KSTAT_DATA_UINT64},
{ "pages_ue_cleared_retired", KSTAT_DATA_UINT64},
{ "pages_ue_cleared_freed", KSTAT_DATA_UINT64},
{ "pages_ue_persistent", KSTAT_DATA_UINT64},
{ "pages_unretired", KSTAT_DATA_UINT64},
};
static kstat_t *page_retire_ksp = NULL;
#define PR_INCR_KSTAT(stat) \
atomic_add_64(&(page_retire_kstat.stat.value.ui64), 1)
#define PR_DECR_KSTAT(stat) \
atomic_add_64(&(page_retire_kstat.stat.value.ui64), -1)
#define PR_KSTAT_RETIRED_CE (page_retire_kstat.pr_mce.value.ui64)
#define PR_KSTAT_RETIRED_FMA (page_retire_kstat.pr_fma.value.ui64)
#define PR_KSTAT_RETIRED_NOTUE (PR_KSTAT_RETIRED_CE + PR_KSTAT_RETIRED_FMA)
#define PR_KSTAT_PENDING (page_retire_kstat.pr_pending.value.ui64)
#define PR_KSTAT_PENDING_KAS (page_retire_kstat.pr_pending_kas.value.ui64)
#define PR_KSTAT_EQFAIL (page_retire_kstat.pr_enqueue_fail.value.ui64)
#define PR_KSTAT_DQFAIL (page_retire_kstat.pr_dequeue_fail.value.ui64)
/*
* page retire kstats to list all retired pages
*/
static int pr_list_kstat_update(kstat_t *ksp, int rw);
static int pr_list_kstat_snapshot(kstat_t *ksp, void *buf, int rw);
kmutex_t pr_list_kstat_mutex;
/*
* Limit the number of multiple CE page retires.
* The default is 0.1% of physmem, or 1 in 1000 pages. This is set in
* basis points, where 100 basis points equals one percent.
*/
#define MCE_BPT 10
uint64_t max_pages_retired_bps = MCE_BPT;
#define PAGE_RETIRE_LIMIT ((physmem * max_pages_retired_bps) / 10000)
/*
* Control over the verbosity of page retirement.
*
* When set to zero (the default), no messages will be printed.
* When set to one, summary messages will be printed.
* When set > one, all messages will be printed.
*
* A value of one will trigger detailed messages for retirement operations,
* and is intended as a platform tunable for processors where FMA's DE does
* not run (e.g., spitfire). Values > one are intended for debugging only.
*/
int page_retire_messages = 0;
/*
* Control whether or not we return scrubbed UE pages to service.
* By default we do not since FMA wants to run its diagnostics first
* and then ask us to unretire the page if it passes. Non-FMA platforms
* may set this to zero so we will only retire recidivist pages. It should
* not be changed by the user.
*/
int page_retire_first_ue = 1;
/*
* Master enable for page retire. This prevents a CE or UE early in boot
* from trying to retire a page before page_retire_init() has finished
* setting things up. This is internal only and is not a tunable!
*/
static int pr_enable = 0;
#ifdef DEBUG
struct page_retire_debug {
int prd_dup1;
int prd_dup2;
int prd_qdup;
int prd_noaction;
int prd_queued;
int prd_notqueued;
int prd_dequeue;
int prd_top;
int prd_locked;
int prd_reloc;
int prd_relocfail;
int prd_mod;
int prd_mod_late;
int prd_kern;
int prd_free;
int prd_noreclaim;
int prd_hashout;
int prd_fma;
int prd_uescrubbed;
int prd_uenotscrubbed;
int prd_mce;
int prd_prlocked;
int prd_prnotlocked;
int prd_prretired;
int prd_ulocked;
int prd_unotretired;
int prd_udestroy;
int prd_uhashout;
int prd_uunretired;
int prd_unotlocked;
int prd_checkhit;
int prd_checkmiss_pend;
int prd_checkmiss_noerr;
int prd_tctop;
int prd_tclocked;
int prd_hunt;
int prd_dohunt;
int prd_earlyhunt;
int prd_latehunt;
int prd_nofreedemote;
int prd_nodemote;
int prd_demoted;
} pr_debug;
#define PR_DEBUG(foo) ((pr_debug.foo)++)
/*
* A type histogram. We record the incidence of the various toxic
* flag combinations along with the interesting page attributes. The
* goal is to get as many combinations as we can while driving all
* pr_debug values nonzero (indicating we've exercised all possible
* code paths across all possible page types). Not all combinations
* will make sense -- e.g. PRT_MOD|PRT_KERNEL.
*
* pr_type offset bit encoding (when examining with a debugger):
*
* PRT_NAMED - 0x4
* PRT_KERNEL - 0x8
* PRT_FREE - 0x10
* PRT_MOD - 0x20
* PRT_FMA - 0x0
* PRT_MCE - 0x40
* PRT_UE - 0x80
*/
#define PRT_NAMED 0x01
#define PRT_KERNEL 0x02
#define PRT_FREE 0x04
#define PRT_MOD 0x08
#define PRT_FMA 0x00 /* yes, this is not a mistake */
#define PRT_MCE 0x10
#define PRT_UE 0x20
#define PRT_ALL 0x3F
int pr_types[PRT_ALL+1];
#define PR_TYPES(pp) { \
int whichtype = 0; \
if (pp->p_vnode) \
whichtype |= PRT_NAMED; \
if (PP_ISKAS(pp)) \
whichtype |= PRT_KERNEL; \
if (PP_ISFREE(pp)) \
whichtype |= PRT_FREE; \
if (hat_ismod(pp)) \
whichtype |= PRT_MOD; \
if (pp->p_toxic & PR_UE) \
whichtype |= PRT_UE; \
if (pp->p_toxic & PR_MCE) \
whichtype |= PRT_MCE; \
pr_types[whichtype]++; \
}
int recl_calls;
int recl_mtbf = 3;
int reloc_calls;
int reloc_mtbf = 7;
int pr_calls;
int pr_mtbf = 15;
#define MTBF(v, f) (((++(v)) & (f)) != (f))
#else /* DEBUG */
#define PR_DEBUG(foo) /* nothing */
#define PR_TYPES(foo) /* nothing */
#define MTBF(v, f) (1)
#endif /* DEBUG */
/*
* page_retire_done() - completion processing
*
* Used by the page_retire code for common completion processing.
* It keeps track of how many times a given result has happened,
* and writes out an occasional message.
*
* May be called with a NULL pp (PRD_INVALID_PA case).
*/
#define PRD_INVALID_KEY -1
#define PRD_SUCCESS 0
#define PRD_PENDING 1
#define PRD_FAILED 2
#define PRD_DUPLICATE 3
#define PRD_INVALID_PA 4
#define PRD_LIMIT 5
#define PRD_UE_SCRUBBED 6
#define PRD_UNR_SUCCESS 7
#define PRD_UNR_CANTLOCK 8
#define PRD_UNR_NOT 9
typedef struct page_retire_op {
int pr_key; /* one of the PRD_* defines from above */
int pr_count; /* How many times this has happened */
int pr_retval; /* return value */
int pr_msglvl; /* message level - when to print */
char *pr_message; /* Cryptic message for field service */
} page_retire_op_t;
static page_retire_op_t page_retire_ops[] = {
/* key count retval msglvl message */
{PRD_SUCCESS, 0, 0, 1,
"Page 0x%08x.%08x removed from service"},
{PRD_PENDING, 0, EAGAIN, 2,
"Page 0x%08x.%08x will be retired on free"},
{PRD_FAILED, 0, EAGAIN, 0, NULL},
{PRD_DUPLICATE, 0, EIO, 2,
"Page 0x%08x.%08x already retired or pending"},
{PRD_INVALID_PA, 0, EINVAL, 2,
"PA 0x%08x.%08x is not a relocatable page"},
{PRD_LIMIT, 0, 0, 1,
"Page 0x%08x.%08x not retired due to limit exceeded"},
{PRD_UE_SCRUBBED, 0, 0, 1,
"Previously reported error on page 0x%08x.%08x cleared"},
{PRD_UNR_SUCCESS, 0, 0, 1,
"Page 0x%08x.%08x returned to service"},
{PRD_UNR_CANTLOCK, 0, EAGAIN, 2,
"Page 0x%08x.%08x could not be unretired"},
{PRD_UNR_NOT, 0, EIO, 2,
"Page 0x%08x.%08x is not retired"},
{PRD_INVALID_KEY, 0, 0, 0, NULL} /* MUST BE LAST! */
};
/*
* print a message if page_retire_messages is true.
*/
#define PR_MESSAGE(debuglvl, msglvl, msg, pa) \
{ \
uint64_t p = (uint64_t)pa; \
if (page_retire_messages >= msglvl && msg != NULL) { \
cmn_err(debuglvl, msg, \
(uint32_t)(p >> 32), (uint32_t)p); \
} \
}
/*
* Note that multiple bits may be set in a single settoxic operation.
* May be called without the page locked.
*/
void
page_settoxic(page_t *pp, uchar_t bits)
{
atomic_or_8(&pp->p_toxic, bits);
}
/*
* Note that multiple bits may cleared in a single clrtoxic operation.
* Must be called with the page exclusively locked to prevent races which
* may attempt to retire a page without any toxic bits set.
* Note that the PR_CAPTURE bit can be cleared without the exclusive lock
* being held as there is a separate mutex which protects that bit.
*/
void
page_clrtoxic(page_t *pp, uchar_t bits)
{
ASSERT((bits & PR_CAPTURE) || PAGE_EXCL(pp));
atomic_and_8(&pp->p_toxic, ~bits);
}
/*
* Prints any page retire messages to the user, and decides what
* error code is appropriate for the condition reported.
*/
static int
page_retire_done(page_t *pp, int code)
{
page_retire_op_t *prop;
uint64_t pa = 0;
int i;
if (pp != NULL) {
pa = mmu_ptob((uint64_t)pp->p_pagenum);
}
prop = NULL;
for (i = 0; page_retire_ops[i].pr_key != PRD_INVALID_KEY; i++) {
if (page_retire_ops[i].pr_key == code) {
prop = &page_retire_ops[i];
break;
}
}
#ifdef DEBUG
if (page_retire_ops[i].pr_key == PRD_INVALID_KEY) {
cmn_err(CE_PANIC, "page_retire_done: Invalid opcode %d", code);
}
#endif
ASSERT(prop->pr_key == code);
prop->pr_count++;
PR_MESSAGE(CE_NOTE, prop->pr_msglvl, prop->pr_message, pa);
if (pp != NULL) {
page_settoxic(pp, PR_MSG);
}
return (prop->pr_retval);
}
/*
* Act like page_destroy(), but instead of freeing the page, hash it onto
* the retired_pages vnode, and mark it retired.
*
* For fun, we try to scrub the page until it's squeaky clean.
* availrmem is adjusted here.
*/
static void
page_retire_destroy(page_t *pp)
{
u_offset_t off = (u_offset_t)((uintptr_t)pp);
ASSERT(PAGE_EXCL(pp));
ASSERT(!PP_ISFREE(pp));
ASSERT(pp->p_szc == 0);
ASSERT(!hat_page_is_mapped(pp));
ASSERT(!pp->p_vnode);
page_clr_all_props(pp);
pagescrub(pp, 0, MMU_PAGESIZE);
pp->p_next = NULL;
pp->p_prev = NULL;
if (page_hashin(pp, retired_pages, off, NULL) == 0) {
cmn_err(CE_PANIC, "retired page %p hashin failed", (void *)pp);
}
page_settoxic(pp, PR_RETIRED);
PR_INCR_KSTAT(pr_retired);
if (pp->p_toxic & PR_FMA) {
PR_INCR_KSTAT(pr_fma);
} else if (pp->p_toxic & PR_UE) {
PR_INCR_KSTAT(pr_ue);
} else {
PR_INCR_KSTAT(pr_mce);
}
mutex_enter(&freemem_lock);
availrmem--;
mutex_exit(&freemem_lock);
page_unlock(pp);
}
/*
* Check whether the number of pages which have been retired already exceeds
* the maximum allowable percentage of memory which may be retired.
*
* Returns 1 if the limit has been exceeded.
*/
static int
page_retire_limit(void)
{
if (PR_KSTAT_RETIRED_NOTUE >= (uint64_t)PAGE_RETIRE_LIMIT) {
PR_INCR_KSTAT(pr_limit_exceeded);
return (1);
}
return (0);
}
#define MSG_DM "Data Mismatch occurred at PA 0x%08x.%08x" \
"[ 0x%x != 0x%x ] while attempting to clear previously " \
"reported error; page removed from service"
#define MSG_UE "Uncorrectable Error occurred at PA 0x%08x.%08x while " \
"attempting to clear previously reported error; page removed " \
"from service"
/*
* Attempt to clear a UE from a page.
* Returns 1 if the error has been successfully cleared.
*/
static int
page_clear_transient_ue(page_t *pp)
{
caddr_t kaddr;
uint8_t rb, wb;
uint64_t pa;
uint32_t pa_hi, pa_lo;
on_trap_data_t otd;
int errors = 0;
int i;
ASSERT(PAGE_EXCL(pp));
ASSERT(PP_PR_REQ(pp));
ASSERT(pp->p_szc == 0);
ASSERT(!hat_page_is_mapped(pp));
/*
* Clear the page and attempt to clear the UE. If we trap
* on the next access to the page, we know the UE has recurred.
*/
pagescrub(pp, 0, PAGESIZE);
/*
* Map the page and write a bunch of bit patterns to compare
* what we wrote with what we read back. This isn't a perfect
* test but it should be good enough to catch most of the
* recurring UEs. If this fails to catch a recurrent UE, we'll
* retire the page the next time we see a UE on the page.
*/
kaddr = ppmapin(pp, PROT_READ|PROT_WRITE, (caddr_t)-1);
pa = ptob((uint64_t)page_pptonum(pp));
pa_hi = (uint32_t)(pa >> 32);
pa_lo = (uint32_t)pa;
/*
* Disable preemption to prevent the off chance that
* we migrate while in the middle of running through
* the bit pattern and run on a different processor
* than what we started on.
*/
kpreempt_disable();
/*
* Fill the page with each (0x00 - 0xFF] bit pattern, flushing
* the cache in between reading and writing. We do this under
* on_trap() protection to avoid recursion.
*/
if (on_trap(&otd, OT_DATA_EC)) {
PR_MESSAGE(CE_WARN, 1, MSG_UE, pa);
errors = 1;
} else {
for (wb = 0xff; wb > 0; wb--) {
for (i = 0; i < PAGESIZE; i++) {
kaddr[i] = wb;
}
sync_data_memory(kaddr, PAGESIZE);
for (i = 0; i < PAGESIZE; i++) {
rb = kaddr[i];
if (rb != wb) {
/*
* We had a mismatch without a trap.
* Uh-oh. Something is really wrong
* with this system.
*/
if (page_retire_messages) {
cmn_err(CE_WARN, MSG_DM,
pa_hi, pa_lo, rb, wb);
}
errors = 1;
goto out; /* double break */
}
}
}
}
out:
no_trap();
kpreempt_enable();
ppmapout(kaddr);
return (errors ? 0 : 1);
}
/*
* Try to clear a page_t with a single UE. If the UE was transient, it is
* returned to service, and we return 1. Otherwise we return 0 meaning
* that further processing is required to retire the page.
*/
static int
page_retire_transient_ue(page_t *pp)
{
ASSERT(PAGE_EXCL(pp));
ASSERT(!hat_page_is_mapped(pp));
/*
* If this page is a repeat offender, retire him under the
* "two strikes and you're out" rule. The caller is responsible
* for scrubbing the page to try to clear the error.
*/
if (pp->p_toxic & PR_UE_SCRUBBED) {
PR_INCR_KSTAT(pr_ue_persistent);
return (0);
}
if (page_clear_transient_ue(pp)) {
/*
* We set the PR_SCRUBBED_UE bit; if we ever see this
* page again, we will retire it, no questions asked.
*/
page_settoxic(pp, PR_UE_SCRUBBED);
if (page_retire_first_ue) {
PR_INCR_KSTAT(pr_ue_cleared_retire);
return (0);
} else {
PR_INCR_KSTAT(pr_ue_cleared_free);
page_clrtoxic(pp, PR_UE | PR_MCE | PR_MSG);
/* LINTED: CONSTCOND */
VN_DISPOSE(pp, B_FREE, 1, kcred);
return (1);
}
}
PR_INCR_KSTAT(pr_ue_persistent);
return (0);
}
/*
* Update the statistics dynamically when our kstat is read.
*/
static int
page_retire_kstat_update(kstat_t *ksp, int rw)
{
struct page_retire_kstat *pr;
if (ksp == NULL)
return (EINVAL);
switch (rw) {
case KSTAT_READ:
pr = (struct page_retire_kstat *)ksp->ks_data;
ASSERT(pr == &page_retire_kstat);
pr->pr_limit.value.ui64 = PAGE_RETIRE_LIMIT;
return (0);
case KSTAT_WRITE:
return (EACCES);
default:
return (EINVAL);
}
/*NOTREACHED*/
}
static int
pr_list_kstat_update(kstat_t *ksp, int rw)
{
uint_t count;
page_t *pp;
kmutex_t *vphm;
if (rw == KSTAT_WRITE)
return (EACCES);
vphm = page_vnode_mutex(retired_pages);
mutex_enter(vphm);
/* Needs to be under a lock so that for loop will work right */
if (retired_pages->v_pages == NULL) {
mutex_exit(vphm);
ksp->ks_ndata = 0;
ksp->ks_data_size = 0;
return (0);
}
count = 1;
for (pp = retired_pages->v_pages->p_vpnext;
pp != retired_pages->v_pages; pp = pp->p_vpnext) {
count++;
}
mutex_exit(vphm);
ksp->ks_ndata = count;
ksp->ks_data_size = count * 2 * sizeof (uint64_t);
return (0);
}
/*
* all spans will be pagesize and no coalescing will be done with the
* list produced.
*/
static int
pr_list_kstat_snapshot(kstat_t *ksp, void *buf, int rw)
{
kmutex_t *vphm;
page_t *pp;
struct memunit {
uint64_t address;
uint64_t size;
} *kspmem;
if (rw == KSTAT_WRITE)
return (EACCES);
ksp->ks_snaptime = gethrtime();
kspmem = (struct memunit *)buf;
vphm = page_vnode_mutex(retired_pages);
mutex_enter(vphm);
pp = retired_pages->v_pages;
if (((caddr_t)kspmem >= (caddr_t)buf + ksp->ks_data_size) ||
(pp == NULL)) {
mutex_exit(vphm);
return (0);
}
kspmem->address = ptob(pp->p_pagenum);
kspmem->size = PAGESIZE;
kspmem++;
for (pp = pp->p_vpnext; pp != retired_pages->v_pages;
pp = pp->p_vpnext, kspmem++) {
if ((caddr_t)kspmem >= (caddr_t)buf + ksp->ks_data_size)
break;
kspmem->address = ptob(pp->p_pagenum);
kspmem->size = PAGESIZE;
}
mutex_exit(vphm);
return (0);
}
/*
* page_retire_pend_count -- helper function for page_capture_thread,
* returns the number of pages pending retirement.
*/
uint64_t
page_retire_pend_count(void)
{
return (PR_KSTAT_PENDING);
}
uint64_t
page_retire_pend_kas_count(void)
{
return (PR_KSTAT_PENDING_KAS);
}
void
page_retire_incr_pend_count(void *datap)
{
PR_INCR_KSTAT(pr_pending);
if ((datap == &kvp) || (datap == &zvp)) {
PR_INCR_KSTAT(pr_pending_kas);
}
}
void
page_retire_decr_pend_count(void *datap)
{
PR_DECR_KSTAT(pr_pending);
if ((datap == &kvp) || (datap == &zvp)) {
PR_DECR_KSTAT(pr_pending_kas);
}
}
/*
* Initialize the page retire mechanism:
*
* - Establish the correctable error retire limit.
* - Initialize locks.
* - Build the retired_pages vnode.
* - Set up the kstats.
* - Fire off the background thread.
* - Tell page_retire() it's OK to start retiring pages.
*/
void
page_retire_init(void)
{
const fs_operation_def_t retired_vnodeops_template[] = {
{ NULL, NULL }
};
struct vnodeops *vops;
kstat_t *ksp;
const uint_t page_retire_ndata =
sizeof (page_retire_kstat) / sizeof (kstat_named_t);
ASSERT(page_retire_ksp == NULL);
if (max_pages_retired_bps <= 0) {
max_pages_retired_bps = MCE_BPT;
}
mutex_init(&pr_q_mutex, NULL, MUTEX_DEFAULT, NULL);
retired_pages = vn_alloc(KM_SLEEP);
if (vn_make_ops("retired_pages", retired_vnodeops_template, &vops)) {
cmn_err(CE_PANIC,
"page_retired_init: can't make retired vnodeops");
}
vn_setops(retired_pages, vops);
if ((page_retire_ksp = kstat_create("unix", 0, "page_retire",
"misc", KSTAT_TYPE_NAMED, page_retire_ndata,
KSTAT_FLAG_VIRTUAL)) == NULL) {
cmn_err(CE_WARN, "kstat_create for page_retire failed");
} else {
page_retire_ksp->ks_data = (void *)&page_retire_kstat;
page_retire_ksp->ks_update = page_retire_kstat_update;
kstat_install(page_retire_ksp);
}
mutex_init(&pr_list_kstat_mutex, NULL, MUTEX_DEFAULT, NULL);
ksp = kstat_create("unix", 0, "page_retire_list", "misc",
KSTAT_TYPE_RAW, 0, KSTAT_FLAG_VAR_SIZE | KSTAT_FLAG_VIRTUAL);
if (ksp != NULL) {
ksp->ks_update = pr_list_kstat_update;
ksp->ks_snapshot = pr_list_kstat_snapshot;
ksp->ks_lock = &pr_list_kstat_mutex;
kstat_install(ksp);
}
page_capture_register_callback(PC_RETIRE, -1, page_retire_pp_finish);
pr_enable = 1;
}
/*
* page_retire_hunt() callback for the retire thread.
*/
static void
page_retire_thread_cb(page_t *pp)
{
PR_DEBUG(prd_tctop);
if (!PP_ISKAS(pp) && page_trylock(pp, SE_EXCL)) {
PR_DEBUG(prd_tclocked);
page_unlock(pp);
}
}
/*
* Callback used by page_trycapture() to finish off retiring a page.
* The page has already been cleaned and we've been given sole access to
* it.
* Always returns 0 to indicate that callback succeded as the callback never
* fails to finish retiring the given page.
*/
/*ARGSUSED*/
static int
page_retire_pp_finish(page_t *pp, void *notused, uint_t flags)
{
int toxic;
ASSERT(PAGE_EXCL(pp));
ASSERT(pp->p_iolock_state == 0);
ASSERT(pp->p_szc == 0);
toxic = pp->p_toxic;
/*
* The problem page is locked, demoted, unmapped, not free,
* hashed out, and not COW or mlocked (whew!).
*
* Now we select our ammunition, take it around back, and shoot it.
*/
if (toxic & PR_UE) {
ue_error:
if (page_retire_transient_ue(pp)) {
PR_DEBUG(prd_uescrubbed);
(void) page_retire_done(pp, PRD_UE_SCRUBBED);
} else {
PR_DEBUG(prd_uenotscrubbed);
page_retire_destroy(pp);
(void) page_retire_done(pp, PRD_SUCCESS);
}
return (0);
} else if (toxic & PR_FMA) {
PR_DEBUG(prd_fma);
page_retire_destroy(pp);
(void) page_retire_done(pp, PRD_SUCCESS);
return (0);
} else if (toxic & PR_MCE) {
PR_DEBUG(prd_mce);
page_retire_destroy(pp);
(void) page_retire_done(pp, PRD_SUCCESS);
return (0);
}
/*
* When page_retire_first_ue is set to zero and a UE occurs which is
* transient, it's possible that we clear some flags set by a second
* UE error on the page which occurs while the first is currently being
* handled and thus we need to handle the case where none of the above
* are set. In this instance, PR_UE_SCRUBBED should be set and thus
* we should execute the UE code above.
*/
if (toxic & PR_UE_SCRUBBED) {
goto ue_error;
}
/*
* It's impossible to get here.
*/
panic("bad toxic flags 0x%x in page_retire_pp_finish\n", toxic);
return (0);
}
/*
* page_retire() - the front door in to retire a page.
*
* Ideally, page_retire() would instantly retire the requested page.
* Unfortunately, some pages are locked or otherwise tied up and cannot be
* retired right away. We use the page capture logic to deal with this
* situation as it will continuously try to retire the page in the background
* if the first attempt fails. Success is determined by looking to see whether
* the page has been retired after the page_trycapture() attempt.
*
* Returns:
*
* - 0 on success,
* - EINVAL when the PA is whacko,
* - EIO if the page is already retired or already pending retirement, or
* - EAGAIN if the page could not be _immediately_ retired but is pending.
*/
int
page_retire(uint64_t pa, uchar_t reason)
{
page_t *pp;
ASSERT(reason & PR_REASONS); /* there must be a reason */
ASSERT(!(reason & ~PR_REASONS)); /* but no other bits */
pp = page_numtopp_nolock(mmu_btop(pa));
if (pp == NULL) {
PR_MESSAGE(CE_WARN, 1, "Cannot schedule clearing of error on"
" page 0x%08x.%08x; page is not relocatable memory", pa);
return (page_retire_done(pp, PRD_INVALID_PA));
}
if (PP_RETIRED(pp)) {
PR_DEBUG(prd_dup1);
return (page_retire_done(pp, PRD_DUPLICATE));
}
if ((reason & PR_UE) && !PP_TOXIC(pp)) {
PR_MESSAGE(CE_NOTE, 1, "Scheduling clearing of error on"
" page 0x%08x.%08x", pa);
} else if (PP_PR_REQ(pp)) {
PR_DEBUG(prd_dup2);
return (page_retire_done(pp, PRD_DUPLICATE));
} else {
PR_MESSAGE(CE_NOTE, 1, "Scheduling removal of"
" page 0x%08x.%08x", pa);
}
/* Avoid setting toxic bits in the first place */
if ((reason & (PR_FMA | PR_MCE)) && !(reason & PR_UE) &&
page_retire_limit()) {
return (page_retire_done(pp, PRD_LIMIT));
}
if (MTBF(pr_calls, pr_mtbf)) {
page_settoxic(pp, reason);
if (page_trycapture(pp, 0, CAPTURE_RETIRE, pp->p_vnode) == 0) {
PR_DEBUG(prd_prlocked);
} else {
PR_DEBUG(prd_prnotlocked);
}
} else {
PR_DEBUG(prd_prnotlocked);
}
if (PP_RETIRED(pp)) {
PR_DEBUG(prd_prretired);
return (0);
} else {
cv_signal(&pc_cv);
PR_INCR_KSTAT(pr_failed);
if (pp->p_toxic & PR_MSG) {
return (page_retire_done(pp, PRD_FAILED));
} else {
return (page_retire_done(pp, PRD_PENDING));
}
}
}
/*
* Take a retired page off the retired-pages vnode and clear the toxic flags.
* If "free" is nonzero, lock it and put it back on the freelist. If "free"
* is zero, the caller already holds SE_EXCL lock so we simply unretire it
* and don't do anything else with it.
*
* Any unretire messages are printed from this routine.
*
* Returns 0 if page pp was unretired; else an error code.
*
* If flags is:
* PR_UNR_FREE - lock the page, clear the toxic flags and free it
* to the freelist.
* PR_UNR_TEMP - lock the page, unretire it, leave the toxic
* bits set as is and return it to the caller.
* PR_UNR_CLEAN - page is SE_EXCL locked, unretire it, clear the
* toxic flags and return it to caller as is.
*/
int
page_unretire_pp(page_t *pp, int flags)
{
/*
* To be retired, a page has to be hashed onto the retired_pages vnode
* and have PR_RETIRED set in p_toxic.
*/
if (flags == PR_UNR_CLEAN ||
page_try_reclaim_lock(pp, SE_EXCL, SE_RETIRED)) {
ASSERT(PAGE_EXCL(pp));
PR_DEBUG(prd_ulocked);
if (!PP_RETIRED(pp)) {
PR_DEBUG(prd_unotretired);
page_unlock(pp);
return (page_retire_done(pp, PRD_UNR_NOT));
}
PR_MESSAGE(CE_NOTE, 1, "unretiring retired"
" page 0x%08x.%08x", mmu_ptob((uint64_t)pp->p_pagenum));
if (pp->p_toxic & PR_FMA) {
PR_DECR_KSTAT(pr_fma);
} else if (pp->p_toxic & PR_UE) {
PR_DECR_KSTAT(pr_ue);
} else {
PR_DECR_KSTAT(pr_mce);
}
if (flags == PR_UNR_TEMP)
page_clrtoxic(pp, PR_RETIRED);
else
page_clrtoxic(pp, PR_TOXICFLAGS);
if (flags == PR_UNR_FREE) {
PR_DEBUG(prd_udestroy);
page_destroy(pp, 0);
} else {
PR_DEBUG(prd_uhashout);
page_hashout(pp, NULL);
}
mutex_enter(&freemem_lock);
availrmem++;
mutex_exit(&freemem_lock);
PR_DEBUG(prd_uunretired);
PR_DECR_KSTAT(pr_retired);
PR_INCR_KSTAT(pr_unretired);
return (page_retire_done(pp, PRD_UNR_SUCCESS));
}
PR_DEBUG(prd_unotlocked);
return (page_retire_done(pp, PRD_UNR_CANTLOCK));
}
/*
* Return a page to service by moving it from the retired_pages vnode
* onto the freelist.
*
* Called from mmioctl_page_retire() on behalf of the FMA DE.
*
* Returns:
*
* - 0 if the page is unretired,
* - EAGAIN if the pp can not be locked,
* - EINVAL if the PA is whacko, and
* - EIO if the pp is not retired.
*/
int
page_unretire(uint64_t pa)
{
page_t *pp;
pp = page_numtopp_nolock(mmu_btop(pa));
if (pp == NULL) {
return (page_retire_done(pp, PRD_INVALID_PA));
}
return (page_unretire_pp(pp, PR_UNR_FREE));
}
/*
* Test a page to see if it is retired. If errors is non-NULL, the toxic
* bits of the page are returned. Returns 0 on success, error code on failure.
*/
int
page_retire_check_pp(page_t *pp, uint64_t *errors)
{
int rc;
if (PP_RETIRED(pp)) {
PR_DEBUG(prd_checkhit);
rc = 0;
} else if (PP_PR_REQ(pp)) {
PR_DEBUG(prd_checkmiss_pend);
rc = EAGAIN;
} else {
PR_DEBUG(prd_checkmiss_noerr);
rc = EIO;
}
/*
* We have magically arranged the bit values returned to fmd(1M)
* to line up with the FMA, MCE, and UE bits of the page_t.
*/
if (errors) {
uint64_t toxic = (uint64_t)(pp->p_toxic & PR_ERRMASK);
if (toxic & PR_UE_SCRUBBED) {
toxic &= ~PR_UE_SCRUBBED;
toxic |= PR_UE;
}
*errors = toxic;
}
return (rc);
}
/*
* Test to see if the page_t for a given PA is retired, and return the
* hardware errors we have seen on the page if requested.
*
* Called from mmioctl_page_retire on behalf of the FMA DE.
*
* Returns:
*
* - 0 if the page is retired,
* - EIO if the page is not retired and has no errors,
* - EAGAIN if the page is not retired but is pending; and
* - EINVAL if the PA is whacko.
*/
int
page_retire_check(uint64_t pa, uint64_t *errors)
{
page_t *pp;
if (errors) {
*errors = 0;
}
pp = page_numtopp_nolock(mmu_btop(pa));
if (pp == NULL) {
return (page_retire_done(pp, PRD_INVALID_PA));
}
return (page_retire_check_pp(pp, errors));
}
/*
* Page retire self-test. For now, it always returns 0.
*/
int
page_retire_test(void)
{
page_t *first, *pp, *cpp, *cpp2, *lpp;
/*
* Tests the corner case where a large page can't be retired
* because one of the constituent pages is locked. We mark
* one page to be retired and try to retire it, and mark the
* other page to be retired but don't try to retire it, so
* that page_unlock() in the failure path will recurse and try
* to retire THAT page. This is the worst possible situation
* we can get ourselves into.
*/
memsegs_lock(0);
pp = first = page_first();
do {
if (pp->p_szc && PP_PAGEROOT(pp) == pp) {
cpp = pp + 1;
lpp = PP_ISFREE(pp)? pp : pp + 2;
cpp2 = pp + 3;
if (!page_trylock(lpp, pp == lpp? SE_EXCL : SE_SHARED))
continue;
if (!page_trylock(cpp, SE_EXCL)) {
page_unlock(lpp);
continue;
}
/* fails */
(void) page_retire(ptob(cpp->p_pagenum), PR_FMA);
page_unlock(lpp);
page_unlock(cpp);
(void) page_retire(ptob(cpp->p_pagenum), PR_FMA);
(void) page_retire(ptob(cpp2->p_pagenum), PR_FMA);
}
} while ((pp = page_next(pp)) != first);
memsegs_unlock(0);
return (0);
}