// SPDX-License-Identifier: GPL-2.0-only /* * APEI Error Record Serialization Table support * * ERST is a way provided by APEI to save and retrieve hardware error * information to and from a persistent store. * * For more information about ERST, please refer to ACPI Specification * version 4.0, section 17.4. * * Copyright 2010 Intel Corp. * Author: Huang Ying <ying.huang@intel.com>
*/
int erst_disable;
EXPORT_SYMBOL_GPL(erst_disable);
staticstruct acpi_table_erst *erst_tab;
/* ERST Error Log Address Range attributes */ #define ERST_RANGE_RESERVED 0x0001 #define ERST_RANGE_NVRAM 0x0002 #define ERST_RANGE_SLOW 0x0004
/* ERST Exec max timings */ #define ERST_EXEC_TIMING_MAX_MASK 0xFFFFFFFF00000000 #define ERST_EXEC_TIMING_MAX_SHIFT 32
/* * ERST Error Log Address Range, used as buffer for reading/writing * error records.
*/ staticstruct erst_erange {
u64 base;
u64 size; void __iomem *vaddr;
u32 attr;
u64 timings;
} erst_erange;
/* * Prevent ERST interpreter to run simultaneously, because the * corresponding firmware implementation may not work properly when * invoked simultaneously. * * It is used to provide exclusive accessing for ERST Error Log * Address Range too.
*/ static DEFINE_RAW_SPINLOCK(erst_lock);
staticinlineint erst_errno(int command_status)
{ switch (command_status) { case ERST_STATUS_SUCCESS: return 0; case ERST_STATUS_HARDWARE_NOT_AVAILABLE: return -ENODEV; case ERST_STATUS_NOT_ENOUGH_SPACE: return -ENOSPC; case ERST_STATUS_RECORD_STORE_EMPTY: case ERST_STATUS_RECORD_NOT_FOUND: return -ENOENT; default: return -EINVAL;
}
}
if (ctx->var1 > FIRMWARE_MAX_STALL) { if (!in_nmi())
pr_warn(FW_WARN "Too long stall time for stall while true instruction: 0x%llx.\n",
ctx->var1);
stall_time = FIRMWARE_MAX_STALL;
} else
stall_time = ctx->var1;
for (;;) {
rc = __apei_exec_read_register(entry, &val); if (rc) return rc; if (val != ctx->value) break; if (erst_timedout(&timeout, stall_time * NSEC_PER_USEC)) return -EIO;
} return 0;
}
/* erst_record_id_cache.lock must be held by caller */ staticint __erst_record_id_cache_add_one(void)
{
u64 id, prev_id, first_id; int i, rc;
u64 *entries; unsignedlong flags;
id = prev_id = first_id = APEI_ERST_INVALID_RECORD_ID;
retry:
raw_spin_lock_irqsave(&erst_lock, flags);
rc = __erst_get_next_record_id(&id);
raw_spin_unlock_irqrestore(&erst_lock, flags); if (rc == -ENOENT) return 0; if (rc) return rc; if (id == APEI_ERST_INVALID_RECORD_ID) return 0; /* can not skip current ID, or loop back to first ID */ if (id == prev_id || id == first_id) return 0; if (first_id == APEI_ERST_INVALID_RECORD_ID)
first_id = id;
prev_id = id;
entries = erst_record_id_cache.entries; for (i = 0; i < erst_record_id_cache.len; i++) { if (entries[i] == id) break;
} /* record id already in cache, try next */ if (i < erst_record_id_cache.len) goto retry; if (erst_record_id_cache.len >= erst_record_id_cache.size) { int new_size;
u64 *new_entries;
/* * Get the record ID of an existing error record on the persistent * storage. If there is no error record on the persistent storage, the * returned record_id is APEI_ERST_INVALID_RECORD_ID.
*/ int erst_get_record_id_next(int *pos, u64 *record_id)
{ int rc = 0;
u64 *entries;
if (erst_disable) return -ENODEV;
/* must be enclosed by erst_get_record_id_begin/end */
BUG_ON(!erst_record_id_cache.refcount);
BUG_ON(*pos < 0 || *pos > erst_record_id_cache.len);
mutex_lock(&erst_record_id_cache.lock);
entries = erst_record_id_cache.entries; for (; *pos < erst_record_id_cache.len; (*pos)++) if (entries[*pos] != APEI_ERST_INVALID_RECORD_ID) break; /* found next record id in cache */ if (*pos < erst_record_id_cache.len) {
*record_id = entries[*pos];
(*pos)++; goto out_unlock;
}
/* Try to add one more record ID to cache */
rc = __erst_record_id_cache_add_one(); if (rc < 0) goto out_unlock; /* successfully add one new ID */ if (rc == 1) {
*record_id = erst_record_id_cache.entries[*pos];
(*pos)++;
rc = 0;
} else {
*pos = -1;
*record_id = APEI_ERST_INVALID_RECORD_ID;
}
out_unlock:
mutex_unlock(&erst_record_id_cache.lock);
/* erst_record_id_cache.lock must be held by caller */ staticvoid __erst_record_id_cache_compact(void)
{ int i, wpos = 0;
u64 *entries;
if (erst_record_id_cache.refcount) return;
entries = erst_record_id_cache.entries; for (i = 0; i < erst_record_id_cache.len; i++) { if (entries[i] == APEI_ERST_INVALID_RECORD_ID) continue; if (wpos != i)
entries[wpos] = entries[i];
wpos++;
}
erst_record_id_cache.len = wpos;
}
void erst_get_record_id_end(void)
{ /* * erst_disable != 0 should be detected by invoker via the * return value of erst_get_record_id_begin/next, so this * function should not be called for erst_disable != 0.
*/
BUG_ON(erst_disable);
erst_exec_ctx_init(&ctx);
rc = apei_exec_run_optional(&ctx, ACPI_ERST_BEGIN_CLEAR); if (rc) return rc;
apei_exec_ctx_set_input(&ctx, record_id);
rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID); if (rc) return rc;
rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); if (rc) return rc; for (;;) {
rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); if (rc) return rc;
val = apei_exec_ctx_get_output(&ctx); if (!val) break; if (erst_timedout(&timeout, SPIN_UNIT)) return -EIO;
}
rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); if (rc) return rc;
val = apei_exec_ctx_get_output(&ctx);
rc = apei_exec_run_optional(&ctx, ACPI_ERST_END); if (rc) return rc;
return erst_errno(val);
}
/* NVRAM ERST Error Log Address Range is not supported yet */ staticvoid pr_unimpl_nvram(void)
{ if (printk_ratelimit())
pr_warn("NVRAM ERST Log Address Range not implemented yet.\n");
}
staticint __erst_write_to_nvram(conststruct cper_record_header *record)
{ /* do not print message, because printk is not safe for NMI */ return -ENOSYS;
}
static ssize_t __erst_read(u64 record_id, struct cper_record_header *record,
size_t buflen)
{ int rc;
u64 offset, len = 0; struct cper_record_header *rcd_tmp;
rc = __erst_read_to_erange(record_id, &offset); if (rc) return rc;
rcd_tmp = erst_erange.vaddr + offset;
len = rcd_tmp->record_length; if (len <= buflen)
memcpy(record, rcd_tmp, len);
return len;
}
/* * If return value > buflen, the buffer size is not big enough, * else if return value < 0, something goes wrong, * else everything is OK, and return value is record length
*/
ssize_t erst_read(u64 record_id, struct cper_record_header *record,
size_t buflen)
{
ssize_t len; unsignedlong flags;
/* * if creatorid is NULL, read any record for erst-dbg module
*/ if (creatorid == NULL) {
len = erst_read(record_id, record, buflen); if (len == -ENOENT)
erst_clear_cache(record_id);
return len;
}
len = erst_read(record_id, record, buflen); /* * if erst_read return value is -ENOENT skip to next record_id, * and clear the record_id cache.
*/ if (len == -ENOENT) {
erst_clear_cache(record_id); goto out;
}
if (len < 0) goto out;
/* * if erst_read return value is less than record head length, * consider it as -EIO, and clear the record_id cache.
*/ if (len < recordlen) {
len = -EIO;
erst_clear_cache(record_id); goto out;
}
/* * if creatorid is not wanted, consider it as not found, * for skipping to next record_id.
*/ if (!guid_equal(&record->creator_id, creatorid))
len = -ENOENT;
/* no more record */ if (record_id == APEI_ERST_INVALID_RECORD_ID) {
rc = -EINVAL; goto out;
}
len = erst_read_record(record_id, &rcd->hdr, rcd_len, sizeof(*rcd),
&CPER_CREATOR_PSTORE); /* The record may be cleared by others, try read next record */ if (len == -ENOENT) goto skip; elseif (len < 0) goto out;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.