mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-31 12:10:52 -04:00
Merge branch 'apei-release' into release
This commit is contained in:
commit
25076246e8
11 changed files with 421 additions and 152 deletions
|
|
@ -21,6 +21,13 @@ config ACPI_APEI_GHES
|
|||
by firmware to produce more valuable hardware error
|
||||
information for Linux.
|
||||
|
||||
config ACPI_APEI_PCIEAER
|
||||
bool "APEI PCIe AER logging/recovering support"
|
||||
depends on ACPI_APEI && PCIEAER
|
||||
help
|
||||
PCIe AER errors may be reported via APEI firmware first mode.
|
||||
Turn on this option to enable the corresponding support.
|
||||
|
||||
config ACPI_APEI_EINJ
|
||||
tristate "APEI Error INJection (EINJ)"
|
||||
depends on ACPI_APEI && DEBUG_FS
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include <linux/time.h>
|
||||
#include <linux/cper.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/aer.h>
|
||||
|
||||
/*
|
||||
* CPER record ID need to be unique even after reboot, because record
|
||||
|
|
@ -70,8 +71,8 @@ static const char *cper_severity_str(unsigned int severity)
|
|||
* If the output length is longer than 80, multiple line will be
|
||||
* printed, with @pfx is printed at the beginning of each line.
|
||||
*/
|
||||
static void cper_print_bits(const char *pfx, unsigned int bits,
|
||||
const char *strs[], unsigned int strs_size)
|
||||
void cper_print_bits(const char *pfx, unsigned int bits,
|
||||
const char *strs[], unsigned int strs_size)
|
||||
{
|
||||
int i, len = 0;
|
||||
const char *str;
|
||||
|
|
@ -81,6 +82,8 @@ static void cper_print_bits(const char *pfx, unsigned int bits,
|
|||
if (!(bits & (1U << i)))
|
||||
continue;
|
||||
str = strs[i];
|
||||
if (!str)
|
||||
continue;
|
||||
if (len && len + strlen(str) + 2 > 80) {
|
||||
printk("%s\n", buf);
|
||||
len = 0;
|
||||
|
|
@ -243,7 +246,8 @@ static const char *cper_pcie_port_type_strs[] = {
|
|||
"root complex event collector",
|
||||
};
|
||||
|
||||
static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie)
|
||||
static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie,
|
||||
const struct acpi_hest_generic_data *gdata)
|
||||
{
|
||||
if (pcie->validation_bits & CPER_PCIE_VALID_PORT_TYPE)
|
||||
printk("%s""port_type: %d, %s\n", pfx, pcie->port_type,
|
||||
|
|
@ -276,6 +280,12 @@ static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie)
|
|||
printk(
|
||||
"%s""bridge: secondary_status: 0x%04x, control: 0x%04x\n",
|
||||
pfx, pcie->bridge.secondary_status, pcie->bridge.control);
|
||||
#ifdef CONFIG_ACPI_APEI_PCIEAER
|
||||
if (pcie->validation_bits & CPER_PCIE_VALID_AER_INFO) {
|
||||
struct aer_capability_regs *aer_regs = (void *)pcie->aer_info;
|
||||
cper_print_aer(pfx, gdata->error_severity, aer_regs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static const char *apei_estatus_section_flag_strs[] = {
|
||||
|
|
@ -322,7 +332,7 @@ static void apei_estatus_print_section(
|
|||
struct cper_sec_pcie *pcie = (void *)(gdata + 1);
|
||||
printk("%s""section_type: PCIe error\n", pfx);
|
||||
if (gdata->error_data_length >= sizeof(*pcie))
|
||||
cper_print_pcie(pfx, pcie);
|
||||
cper_print_pcie(pfx, pcie, gdata);
|
||||
else
|
||||
goto err_section_too_small;
|
||||
} else
|
||||
|
|
|
|||
|
|
@ -43,12 +43,27 @@ static DEFINE_MUTEX(erst_dbg_mutex);
|
|||
|
||||
static int erst_dbg_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
int rc, *pos;
|
||||
|
||||
if (erst_disable)
|
||||
return -ENODEV;
|
||||
|
||||
pos = (int *)&file->private_data;
|
||||
|
||||
rc = erst_get_record_id_begin(pos);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return nonseekable_open(inode, file);
|
||||
}
|
||||
|
||||
static int erst_dbg_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
erst_get_record_id_end();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int rc;
|
||||
|
|
@ -79,18 +94,20 @@ static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
|
|||
static ssize_t erst_dbg_read(struct file *filp, char __user *ubuf,
|
||||
size_t usize, loff_t *off)
|
||||
{
|
||||
int rc;
|
||||
int rc, *pos;
|
||||
ssize_t len = 0;
|
||||
u64 id;
|
||||
|
||||
if (*off != 0)
|
||||
if (*off)
|
||||
return -EINVAL;
|
||||
|
||||
if (mutex_lock_interruptible(&erst_dbg_mutex) != 0)
|
||||
return -EINTR;
|
||||
|
||||
pos = (int *)&filp->private_data;
|
||||
|
||||
retry_next:
|
||||
rc = erst_get_next_record_id(&id);
|
||||
rc = erst_get_record_id_next(pos, &id);
|
||||
if (rc)
|
||||
goto out;
|
||||
/* no more record */
|
||||
|
|
@ -181,6 +198,7 @@ out:
|
|||
static const struct file_operations erst_dbg_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = erst_dbg_open,
|
||||
.release = erst_dbg_release,
|
||||
.read = erst_dbg_read,
|
||||
.write = erst_dbg_write,
|
||||
.unlocked_ioctl = erst_dbg_ioctl,
|
||||
|
|
|
|||
|
|
@ -429,6 +429,22 @@ ssize_t erst_get_record_count(void)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(erst_get_record_count);
|
||||
|
||||
#define ERST_RECORD_ID_CACHE_SIZE_MIN 16
|
||||
#define ERST_RECORD_ID_CACHE_SIZE_MAX 1024
|
||||
|
||||
struct erst_record_id_cache {
|
||||
struct mutex lock;
|
||||
u64 *entries;
|
||||
int len;
|
||||
int size;
|
||||
int refcount;
|
||||
};
|
||||
|
||||
static struct erst_record_id_cache erst_record_id_cache = {
|
||||
.lock = __MUTEX_INITIALIZER(erst_record_id_cache.lock),
|
||||
.refcount = 0,
|
||||
};
|
||||
|
||||
static int __erst_get_next_record_id(u64 *record_id)
|
||||
{
|
||||
struct apei_exec_context ctx;
|
||||
|
|
@ -443,26 +459,179 @@ static int __erst_get_next_record_id(u64 *record_id)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int erst_get_record_id_begin(int *pos)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (erst_disable)
|
||||
return -ENODEV;
|
||||
|
||||
rc = mutex_lock_interruptible(&erst_record_id_cache.lock);
|
||||
if (rc)
|
||||
return rc;
|
||||
erst_record_id_cache.refcount++;
|
||||
mutex_unlock(&erst_record_id_cache.lock);
|
||||
|
||||
*pos = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(erst_get_record_id_begin);
|
||||
|
||||
/* erst_record_id_cache.lock must be held by caller */
|
||||
static int __erst_record_id_cache_add_one(void)
|
||||
{
|
||||
u64 id, prev_id, first_id;
|
||||
int i, rc;
|
||||
u64 *entries;
|
||||
unsigned long 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, alloc_size;
|
||||
u64 *new_entries;
|
||||
|
||||
new_size = erst_record_id_cache.size * 2;
|
||||
new_size = clamp_val(new_size, ERST_RECORD_ID_CACHE_SIZE_MIN,
|
||||
ERST_RECORD_ID_CACHE_SIZE_MAX);
|
||||
if (new_size <= erst_record_id_cache.size) {
|
||||
if (printk_ratelimit())
|
||||
pr_warning(FW_WARN ERST_PFX
|
||||
"too many record ID!\n");
|
||||
return 0;
|
||||
}
|
||||
alloc_size = new_size * sizeof(entries[0]);
|
||||
if (alloc_size < PAGE_SIZE)
|
||||
new_entries = kmalloc(alloc_size, GFP_KERNEL);
|
||||
else
|
||||
new_entries = vmalloc(alloc_size);
|
||||
if (!new_entries)
|
||||
return -ENOMEM;
|
||||
memcpy(new_entries, entries,
|
||||
erst_record_id_cache.len * sizeof(entries[0]));
|
||||
if (erst_record_id_cache.size < PAGE_SIZE)
|
||||
kfree(entries);
|
||||
else
|
||||
vfree(entries);
|
||||
erst_record_id_cache.entries = entries = new_entries;
|
||||
erst_record_id_cache.size = new_size;
|
||||
}
|
||||
entries[i] = id;
|
||||
erst_record_id_cache.len++;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_next_record_id(u64 *record_id)
|
||||
int erst_get_record_id_next(int *pos, u64 *record_id)
|
||||
{
|
||||
int rc;
|
||||
unsigned long flags;
|
||||
int rc = 0;
|
||||
u64 *entries;
|
||||
|
||||
if (erst_disable)
|
||||
return -ENODEV;
|
||||
|
||||
raw_spin_lock_irqsave(&erst_lock, flags);
|
||||
rc = __erst_get_next_record_id(record_id);
|
||||
raw_spin_unlock_irqrestore(&erst_lock, flags);
|
||||
/* 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);
|
||||
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(erst_get_next_record_id);
|
||||
EXPORT_SYMBOL_GPL(erst_get_record_id_next);
|
||||
|
||||
/* erst_record_id_cache.lock must be held by caller */
|
||||
static void __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)
|
||||
memcpy(&entries[wpos], &entries[i], sizeof(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);
|
||||
|
||||
mutex_lock(&erst_record_id_cache.lock);
|
||||
erst_record_id_cache.refcount--;
|
||||
BUG_ON(erst_record_id_cache.refcount < 0);
|
||||
__erst_record_id_cache_compact();
|
||||
mutex_unlock(&erst_record_id_cache.lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(erst_get_record_id_end);
|
||||
|
||||
static int __erst_write_to_storage(u64 offset)
|
||||
{
|
||||
|
|
@ -703,56 +872,34 @@ ssize_t erst_read(u64 record_id, struct cper_record_header *record,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(erst_read);
|
||||
|
||||
/*
|
||||
* If return value > buflen, the buffer size is not big enough,
|
||||
* else if return value = 0, there is no more record to read,
|
||||
* else if return value < 0, something goes wrong,
|
||||
* else everything is OK, and return value is record length
|
||||
*/
|
||||
ssize_t erst_read_next(struct cper_record_header *record, size_t buflen)
|
||||
{
|
||||
int rc;
|
||||
ssize_t len;
|
||||
unsigned long flags;
|
||||
u64 record_id;
|
||||
|
||||
if (erst_disable)
|
||||
return -ENODEV;
|
||||
|
||||
raw_spin_lock_irqsave(&erst_lock, flags);
|
||||
rc = __erst_get_next_record_id(&record_id);
|
||||
if (rc) {
|
||||
raw_spin_unlock_irqrestore(&erst_lock, flags);
|
||||
return rc;
|
||||
}
|
||||
/* no more record */
|
||||
if (record_id == APEI_ERST_INVALID_RECORD_ID) {
|
||||
raw_spin_unlock_irqrestore(&erst_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
len = __erst_read(record_id, record, buflen);
|
||||
raw_spin_unlock_irqrestore(&erst_lock, flags);
|
||||
|
||||
return len;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(erst_read_next);
|
||||
|
||||
int erst_clear(u64 record_id)
|
||||
{
|
||||
int rc;
|
||||
int rc, i;
|
||||
unsigned long flags;
|
||||
u64 *entries;
|
||||
|
||||
if (erst_disable)
|
||||
return -ENODEV;
|
||||
|
||||
rc = mutex_lock_interruptible(&erst_record_id_cache.lock);
|
||||
if (rc)
|
||||
return rc;
|
||||
raw_spin_lock_irqsave(&erst_lock, flags);
|
||||
if (erst_erange.attr & ERST_RANGE_NVRAM)
|
||||
rc = __erst_clear_from_nvram(record_id);
|
||||
else
|
||||
rc = __erst_clear_from_storage(record_id);
|
||||
raw_spin_unlock_irqrestore(&erst_lock, flags);
|
||||
|
||||
if (rc)
|
||||
goto out;
|
||||
entries = erst_record_id_cache.entries;
|
||||
for (i = 0; i < erst_record_id_cache.len; i++) {
|
||||
if (entries[i] == record_id)
|
||||
entries[i] = APEI_ERST_INVALID_RECORD_ID;
|
||||
}
|
||||
__erst_record_id_cache_compact();
|
||||
out:
|
||||
mutex_unlock(&erst_record_id_cache.lock);
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(erst_clear);
|
||||
|
|
|
|||
Loading…
Reference in a new issue