@@ -2,6 +2,7 @@
#include "amd64_edac.h"
#include <asm/amd_nb.h>
#include <linux/amd-atl.h>
+#include <linux/fru_mem_poison.h>
static struct edac_pci_ctl_info *pci_ctl;
@@ -574,6 +575,28 @@ static ssize_t dram_hole_show(struct device *dev, struct device_attribute *mattr
hole_size);
}
+/* sysfs entry to read FRU(Field Repaceable Unit) memory Poisons */
+static ssize_t fmpl_show(struct device *dev, struct device_attribute *mattr,
+ char *data)
+{
+ struct mem_ctl_info *mci = to_mci(dev);
+ struct amd64_pvt *pvt = mci->pvt_info;
+ ssize_t ret_len = 0, buf_size = PAGE_SIZE;
+ char *buf;
+
+ buf = kmalloc(buf_size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret_len = copy_fmp_data_from_cache(pvt->mc_node_id, buf, buf_size);
+ if (!ret_len)
+ return -EINVAL;
+
+ memcpy(data, buf, ret_len);
+ kfree(buf);
+ return ret_len;
+}
+
/*
* update NUM_DBG_ATTRS in case you add new members
*/
@@ -581,6 +604,7 @@ static DEVICE_ATTR(dhar, S_IRUGO, dhar_show, NULL);
static DEVICE_ATTR(dbam, S_IRUGO, dbam0_show, NULL);
static DEVICE_ATTR(topmem, S_IRUGO, top_mem_show, NULL);
static DEVICE_ATTR(topmem2, S_IRUGO, top_mem2_show, NULL);
+static DEVICE_ATTR(fmpl, S_IRUGO, fmpl_show, NULL);
static DEVICE_ATTR_RO(dram_hole);
static struct attribute *dbg_attrs[] = {
@@ -589,6 +613,7 @@ static struct attribute *dbg_attrs[] = {
&dev_attr_topmem.attr,
&dev_attr_topmem2.attr,
&dev_attr_dram_hole.attr,
+ &dev_attr_fmpl.attr,
NULL
};
@@ -39,6 +39,11 @@ struct system_fru_poison_info {
struct cper_fru_poison_record *fru_record;
};
+#define REC_HDR() \
+ " FRU_IDX| FRU_ID\t | P_NUM | TIMESTAMP\t\t | MCA_IPID\t | MCA_ADDR\t| SPA\t\t |\n"
+#define REC_DATA() \
+ " %d\t| 0x%llx| %d\t | %s| 0x%017llx| 0x%013llx | 0x%013llx|\n"
+
#define CPER_CREATOR_FMP \
GUID_INIT(0xcd5c2993, 0xf4b2, 0x41b2, 0xb5, 0xd4, 0xf9, 0xc3, \
0xa0, 0x33, 0x08, 0x75)
@@ -122,15 +127,83 @@ static u64 calc_checksum(struct cper_sec_fru_mem_poisons *fmp)
return checksum;
}
-struct tm get_timestamp(u64 timestamp)
+ssize_t get_timestamp(u64 timestamp, char *tbuf, ssize_t t_size)
{
struct timespec64 ts;
struct tm tm;
+ ssize_t tlen = 0;
ts.tv_sec = timestamp;
time64_to_tm(ts.tv_sec, 0, &tm);
- return tm;
+ tlen = scnprintf(tbuf, t_size, "%ld-%02d-%02d %02d:%02d:%02d",
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
+ tm.tm_sec);
+
+ return tlen;
+}
+
+/*
+ * buffer is filled with poison records information and exports to
+ * amd64_edac module to provide the info via sysfs entries
+ * /sys/devices/system/edac/mc/mc<sock_indx>/fmpl
+ */
+ssize_t copy_fmp_data_from_cache(int fru_idx, char *buf, ssize_t buf_size)
+{
+ struct cper_fru_poison_data *temp, *base;
+ int j, p_count;
+ struct mce *m;
+ ssize_t len = 0;
+ ssize_t t_len = 0;
+ ssize_t tb_size = 100;
+ u64 sys_addr;
+ char *t_buf;
+
+ pr_info("FRU_Idx[%d] Record information:\n", fru_idx);
+ pr_info("Record_ID : 0x%llx\n", sys_fmp_info[fru_idx]->recordid);
+ pr_info("FRU_ID : 0x%llx\n", sys_fmp_info[fru_idx]->sys_fru_id);
+
+ p_count = sys_fmp_info[fru_idx]->fru_record->fmp.poison_count;
+ pr_info("FRU Memory poison details under FRU_idx[%d]: %d\n", fru_idx, p_count);
+
+ base = (struct cper_fru_poison_data *)&sys_fmp_info[fru_idx]->fru_record->fmp.p_list_off;
+ len = scnprintf(buf, buf_size, REC_HDR());
+ buf += len;
+ buf_size -= len;
+
+ for (j = 1; j <= p_count; j++) {
+ temp = base + j * sizeof(struct cper_fru_poison_data);
+ m = (struct mce *)temp;
+ fill_mce_poison_data(m, temp, fru_idx);
+
+ if (amd_umc_mca_addr_to_sys_addr(m, &sys_addr)) {
+ pr_warn("normalized address failed for mce addr:0x%llx\n", m->addr);
+ sys_addr = 0;
+ }
+ t_buf = kmalloc(tb_size, GFP_KERNEL);
+ if (!t_buf)
+ return -ENOMEM;
+
+ t_len = get_timestamp(temp->timestamp, t_buf, tb_size);
+ if (!t_len)
+ kfree(t_buf);
+
+ pr_info("poison_number[%d] hw_id:0x%llx addr:0x%llx\n", j, temp->hw_id, temp->addr);
+ len = scnprintf(buf, buf_size, REC_DATA(),
+ fru_idx, sys_fmp_info[fru_idx]->sys_fru_id, j, t_buf, temp->hw_id,
+ temp->addr, sys_addr);
+
+ buf_size -= len;
+ if ((buf_size - len) <= 0) {
+ pr_warn("%s FMP cache Buffer full!", __func__);
+ goto out;
+ }
+ buf += len;
+ }
+out:
+ kfree(t_buf);
+ return (PAGE_SIZE - buf_size);
}
+EXPORT_SYMBOL(copy_fmp_data_from_cache);
/* Fill initial fmp structure variable during empty record creation */
static int init_fru_poison_fmp_cache(struct system_fru_poison_info *p)
@@ -12,4 +12,6 @@
struct system_fru_poison_info **sys_fmp_info;
+ssize_t copy_fmp_data_from_cache(int fru_idx, char *buf, ssize_t buf_size);
+
#endif /* _X86_FMP_H */