From: Suravee Suthikulpanit <[email protected]>

Add error handling/reporting/filtering logic which uses the user-specified
amd_iommu_log option to determine the log reporting behavior.

Signed-off-by: Suravee Suthikulpanit <[email protected]>
---
 drivers/iommu/Makefile          |    2 +-
 drivers/iommu/amd_iommu_fault.c |  368 +++++++++++++++++++++++++++++++++++++++
 drivers/iommu/amd_iommu_init.c  |    2 +
 drivers/iommu/amd_iommu_proto.h |    6 +
 drivers/iommu/amd_iommu_types.h |   10 ++
 5 files changed, 387 insertions(+), 1 deletion(-)
 create mode 100644 drivers/iommu/amd_iommu_fault.c

diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index ef0e520..b18da7c 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -1,7 +1,7 @@
 obj-$(CONFIG_IOMMU_API) += iommu.o
 obj-$(CONFIG_OF_IOMMU) += of_iommu.o
 obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
-obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o
+obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o amd_iommu_fault.o
 obj-$(CONFIG_AMD_IOMMU_V2) += amd_iommu_v2.o
 obj-$(CONFIG_DMAR_TABLE) += dmar.o
 obj-$(CONFIG_INTEL_IOMMU) += iova.o intel-iommu.o
diff --git a/drivers/iommu/amd_iommu_fault.c b/drivers/iommu/amd_iommu_fault.c
new file mode 100644
index 0000000..0bf380d
--- /dev/null
+++ b/drivers/iommu/amd_iommu_fault.c
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2013 Advanced Micro Devices, Inc.
+ * Author: Suravee Suthikulpanit <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/iommu.h>
+#include <linux/module.h>
+#include <linux/time.h>
+
+#include "amd_iommu_proto.h"
+#include "amd_iommu_types.h"
+
+#define AMD_IOMMU_ERR_THRESHOLD    10
+#define AMD_IOMMU_ERR_PERIOD_SEC   5
+#define AMD_IOMMU_SUP_PERIOD_SEC   30
+
+enum dev_error_state {
+       DEV_ERR_NONE,
+       DEV_ERR_PROBATION,
+       DEV_ERR_SUPPRESS,
+};
+
+struct dte_err_info {
+       struct list_head list;
+       struct amd_iommu *iommu;
+       u16 devid;
+       enum dev_error_state state;
+       u32 err_cnt;
+       unsigned long last_err_sec;
+       unsigned long suppress_sec;
+};
+
+struct _iommu_event_flags {
+       u32     gn:1,           /* 16 */
+               nx:1,           /* 17 */
+               us:1,           /* 18 */
+                i:1,           /* 19 */
+               pr:1,           /* 20 */
+               rw:1,           /* 21 */
+               pe:1,           /* 22 */
+               rz:1,           /* 23 */
+               tr:1,           /* 24 */
+               type:3,         /* [27:25] */
+               _reserved_:20;  /* Reserved */
+};
+
+static const char * const _type_field_encodings[] = {
+       "Reserved",             /* 00 */
+       "Master Abort",         /* 01 */
+       "Target Abort",         /* 10 */
+       "Data Error",           /* 11 */
+};
+
+static const char * const _invalid_trnsac_desc[] = {
+       "Read request or non-posted write in the interrupt "
+                "addres range",                                /* 000 */
+       "Pretranslated transaction received from an "
+               "I/O device that has I=0 or V=0 in DTE",        /* 001 */
+       "Port I/O space transaction received from an "
+               "I/O device that has IoCtl=00b in DTE",         /* 010 */
+       "Posted write to invalid address range",                /* 011 */
+       "Invalid read request or non-posted write",             /* 100 */
+       "Posted write to the interrupt/EOI range from an "
+               "I/O device that has IntCtl=00b in DTE",        /* 101 */
+       "Posted write to a reserved interrupt address range",   /* 110 */
+       "Invalid transaction to the system management "
+               "address range",                                /* 111 */
+};
+
+static const char * const _invalid_trnslt_desc[] = {
+       "Translation request received from an I/O device "
+               "that has I=0, or has V=0, or has V=1 and "
+               "TV=0 in DTE",                                  /* 000 */
+       "Translation request in invalid address range",         /* 001 */
+       "Invalid translation request",                          /* 010 */
+       "Reserved",                                             /* 011 */
+       "Reserved",                                             /* 100 */
+       "Reserved",                                             /* 101 */
+       "Reserved",                                             /* 110 */
+       "Reserved",                                             /* 111 */
+};
+
+static void dump_detail_error(struct _iommu_event_flags *p, int ev_type)
+{
+       u32 err_type = p->type;
+
+       pr_err("AMD-Vi: Error type details: (0x%x) ", err_type);
+       if ((ev_type == EVENT_TYPE_DEV_TAB_ERR)  ||
+           (ev_type == EVENT_TYPE_PAGE_TAB_ERR) ||
+           (ev_type == EVENT_TYPE_CMD_HARD_ERR)) {
+               if (err_type < ARRAY_SIZE(_type_field_encodings)) {
+                       pr_cont("%s\n",
+                               _type_field_encodings[err_type]);
+               }
+       } else if (ev_type == EVENT_TYPE_INV_DEV_REQ) {
+               if (p->tr == 0) {
+                       if (err_type < ARRAY_SIZE(_invalid_trnslt_desc))
+                               pr_cont("%s\n",
+                                       _invalid_trnslt_desc[err_type]);
+               } else {
+                       if (err_type < ARRAY_SIZE(_invalid_trnsac_desc))
+                               pr_cont("%s\n",
+                                       _invalid_trnsac_desc[err_type]);
+               }
+       }
+}
+
+static void dump_flags(int flags, int ev_type)
+{
+       struct _iommu_event_flags *p = (struct _iommu_event_flags *) &flags;
+       u32 err_type = p->type;
+
+       pr_cont(" flg=%s %s %s %s %s %s %s %s %s",
+               (p->gn ? "G" : "N"),
+               (p->nx ? "Nx" : "Ex"),
+               (p->us ? "Usr" : "Sup"),
+               (p->i  ? "I" : "M"),
+               (p->pr ? "P" : "NP"),
+               (p->rw ? "W" : "R"),
+               (p->pe ? "NPm" : "Pm"),
+               (p->rz ? "Rsv" : "Ill"),
+               (p->tr ? "Tl" : "Ta"));
+
+       /* Error type only needed for certain events */
+       if (amd_iommu_log_level < AMD_IOMMU_LOG_VERBOSE) {
+               if ((ev_type == EVENT_TYPE_DEV_TAB_ERR)  ||
+                   (ev_type == EVENT_TYPE_PAGE_TAB_ERR) ||
+                   (ev_type == EVENT_TYPE_CMD_HARD_ERR) ||
+                   (ev_type == EVENT_TYPE_INV_DEV_REQ))
+                       pr_cont(" (0x%x)\n", err_type);
+       } else {
+               pr_cont("\n");
+               dump_detail_error(p, ev_type);
+       }
+}
+
+static void dump_dte_entry(u16 devid)
+{
+       if (amd_iommu_log_level < AMD_IOMMU_LOG_VERBOSE)
+               return;
+
+       pr_err("AMD-Vi: DTE[0:3]:%016llx %016llx %016llx %016llx\n",
+               amd_iommu_dev_table[devid].data[0],
+               amd_iommu_dev_table[devid].data[1],
+               amd_iommu_dev_table[devid].data[2],
+               amd_iommu_dev_table[devid].data[3]);
+}
+
+static void dump_command(unsigned long phys_addr)
+{
+       struct iommu_cmd *cmd = phys_to_virt(phys_addr);
+
+       if (amd_iommu_log_level < AMD_IOMMU_LOG_VERBOSE)
+               return;
+
+       pr_err("AMD-Vi: CMD[0:3]:%08x %08x %08x %08x\n",
+               cmd->data[0], cmd->data[1], cmd->data[2], cmd->data[3]);
+}
+
+void amd_iommu_print_event(int type, int devid, int domid,
+                          int flags, u64 address)
+{
+       pr_err("AMD-Vi: Event=");
+
+       switch (type) {
+       case EVENT_TYPE_ILL_DEV:
+               pr_cont("ILLEGAL_DEV_TBL_ENTRY dev=%x:%x.%x addr=0x%llx",
+                      PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid),
+                      address);
+               dump_flags(flags, type);
+               dump_dte_entry(devid);
+               break;
+       case EVENT_TYPE_IO_FAULT:
+               pr_cont("IO_PAGE_FAULT dev=%x:%x.%x dom=0x%x addr=0x%llx",
+                      PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid),
+                      domid, address);
+               dump_flags(flags, type);
+               dump_dte_entry(devid);
+               break;
+       case EVENT_TYPE_DEV_TAB_ERR:
+               pr_cont("DEV_TAB_HW_ERR dev=%x:%x.%x addr=0x%llx",
+                      PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid),
+                      address);
+               dump_flags(flags, type);
+               break;
+       case EVENT_TYPE_PAGE_TAB_ERR:
+               pr_cont("PAGE_TAB_HW_ERR dev=%x:%x.%x dom=0x%4x addr=0x%llx",
+                      PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid),
+                      domid, address);
+               dump_flags(flags, type);
+               break;
+       case EVENT_TYPE_ILL_CMD:
+               pr_cont("ILLEGAL_CMD_ERR addr=0x%llx\n",
+                       address);
+               dump_command(address);
+               break;
+       case EVENT_TYPE_CMD_HARD_ERR:
+               pr_cont("CMD_HW_ERR addr=0x%llx",
+                       address);
+               dump_flags(flags, type);
+               break;
+       case EVENT_TYPE_IOTLB_INV_TO:
+               pr_cont("IOTLB_INV_TIMEOUT dev=%x:%x.%x addr=0x%llx\n",
+                      PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid),
+                      address);
+               break;
+       case EVENT_TYPE_INV_DEV_REQ:
+               pr_cont("INVALID_DEVICE_REQUEST dev=%x:%x.%x addr=0x%llx",
+                      PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid),
+                      address);
+               dump_flags(flags, type);
+               dump_dte_entry(devid);
+               break;
+       default:
+               pr_cont("UNKNOWN type=0x%x\n", type);
+       }
+}
+
+LIST_HEAD(amd_dte_err_list);   /* list of all DTE in probation state */
+
+void amd_iommu_clear_all_dev_faults(void)
+{
+       struct dte_err_info *entry, *next;
+       list_for_each_entry_safe(entry, next, &amd_dte_err_list, list) {
+               list_del(&entry->list);
+               kfree(entry);
+       }
+}
+
+static struct dte_err_info *_get_dte_error_info(struct amd_iommu *iommu,
+                                                u16 devid)
+{
+       struct dte_err_info *entry;
+
+       list_for_each_entry(entry, &amd_dte_err_list, list) {
+               if (entry->devid != devid)
+                       continue;
+               return entry;
+       }
+
+       entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+       if (!entry)
+               return entry;
+
+       entry->iommu = iommu;
+       entry->devid = devid;
+       entry->state = DEV_ERR_NONE;
+       entry->err_cnt = 0;
+       entry->last_err_sec = get_seconds();
+       entry->suppress_sec = 0;
+       list_add(&entry->list, &amd_dte_err_list);
+
+       return entry;
+}
+
+static bool _check_suppress_device(struct dte_err_info *derr)
+{
+       bool ret = false;
+
+       /* Check if the error period is reached */
+       if ((get_seconds() - derr->last_err_sec) > AMD_IOMMU_ERR_PERIOD_SEC) {
+               /* Reset err,int counters */
+               derr->err_cnt = 0;
+               return ret;
+       }
+
+       /* We suppress device error log if the number of errors reaches
+        * the ERR_THRESHOLD within x sec.
+        */
+       if (derr->err_cnt >= AMD_IOMMU_ERR_THRESHOLD)
+               ret = true;
+
+       return ret;
+}
+
+static int _iommu_handle_dev_fault(struct amd_iommu *iommu, u64 address,
+       int type, int flags, int devid, int domid)
+{
+       int ret = 0;
+       struct dte_err_info *derr = _get_dte_error_info(iommu, devid);
+
+       derr->err_cnt++;
+
+       /* For debug mode, we don't do any filtering */
+       if (amd_iommu_log_level == AMD_IOMMU_LOG_DEBUG) {
+               amd_iommu_print_event(type, devid, domid, flags, address);
+               return ret;
+       }
+
+       if (derr->state == DEV_ERR_SUPPRESS) {
+               /* Check if the suppress period has passed */
+               if ((get_seconds() - derr->suppress_sec >=
+                               AMD_IOMMU_SUP_PERIOD_SEC)) {
+                       derr->state = DEV_ERR_PROBATION;
+                       derr->suppress_sec = 0;
+                       derr->err_cnt = 1;
+                       derr->last_err_sec = get_seconds();
+                       amd_iommu_print_event(type, devid, domid,
+                                               flags, address);
+               }
+       } else {
+               amd_iommu_print_event(type, devid, domid, flags, address);
+               if (_check_suppress_device(derr)) {
+                       pr_err("AMD-Vi: Warning: IOMMU error threshold (%u) "
+                               "reached for device=%x:%x.%x. Suppress for "
+                               "%d secs.!!!\n",
+                               derr->err_cnt,
+                               PCI_BUS_NUM(derr->devid),
+                               PCI_SLOT(derr->devid),
+                               PCI_FUNC(derr->devid),
+                               AMD_IOMMU_SUP_PERIOD_SEC);
+
+                       derr->state = DEV_ERR_SUPPRESS;
+                       derr->suppress_sec = get_seconds();
+               }
+       }
+
+       return ret;
+}
+
+int amd_iommu_handle_fault(struct amd_iommu *iommu,
+                       u64 address, int type, int flags,
+                       int devid, int domid)
+{
+       int ret = -EINVAL;
+
+       switch (type) {
+       /* Events which report device specific errors */
+       case EVENT_TYPE_IO_FAULT:
+       case EVENT_TYPE_ILL_DEV:
+       case EVENT_TYPE_INV_DEV_REQ:
+       case EVENT_TYPE_INV_PPR_REQ:
+               ret = _iommu_handle_dev_fault(iommu,
+                       address, type, flags, devid, domid);
+               break;
+       /* Events which report commands errors */
+       case EVENT_TYPE_ILL_CMD:
+               panic("AMD-Vi: Illegal IOMMU command. This has caused"
+                     "IOMMU to stop functioning.\n");
+               break;
+       /* Events which report hardware errors */
+       case EVENT_TYPE_DEV_TAB_ERR:
+       case EVENT_TYPE_PAGE_TAB_ERR:
+       case EVENT_TYPE_CMD_HARD_ERR:
+       {
+               amd_iommu_print_event(type, devid, domid, flags, address);
+               panic("AMD-Vi: IOMMU hardware error.\n");
+               break;
+       }
+       default:
+               break;
+       }
+
+       return ret;
+}
diff --git a/drivers/iommu/amd_iommu_init.c b/drivers/iommu/amd_iommu_init.c
index 66e3722..145d6ab 100644
--- a/drivers/iommu/amd_iommu_init.c
+++ b/drivers/iommu/amd_iommu_init.c
@@ -1682,6 +1682,8 @@ static void __init free_on_init_error(void)
        gart_iommu_init();
 
 #endif
+
+       amd_iommu_clear_all_dev_faults();
 }
 
 /* SB IOAPIC is always on this device in AMD systems */
diff --git a/drivers/iommu/amd_iommu_proto.h b/drivers/iommu/amd_iommu_proto.h
index c294961..eab830d 100644
--- a/drivers/iommu/amd_iommu_proto.h
+++ b/drivers/iommu/amd_iommu_proto.h
@@ -63,6 +63,12 @@ extern struct iommu_domain *amd_iommu_get_v2_domain(struct 
pci_dev *pdev);
 extern int amd_iommu_complete_ppr(struct pci_dev *pdev, int pasid,
                                  int status, int tag);
 
+extern int amd_iommu_handle_fault(struct amd_iommu *iommu,
+                                 u64 address, int type, int flags,
+                                 int devid, int domid);
+
+extern void amd_iommu_clear_all_dev_faults(void);
+
 #ifndef CONFIG_AMD_IOMMU_STATS
 
 static inline void amd_iommu_stats_init(void) { }
diff --git a/drivers/iommu/amd_iommu_types.h b/drivers/iommu/amd_iommu_types.h
index 85b7a65..982411d 100644
--- a/drivers/iommu/amd_iommu_types.h
+++ b/drivers/iommu/amd_iommu_types.h
@@ -116,6 +116,7 @@
 #define EVENT_TYPE_CMD_HARD_ERR        0x6
 #define EVENT_TYPE_IOTLB_INV_TO        0x7
 #define EVENT_TYPE_INV_DEV_REQ 0x8
+#define EVENT_TYPE_INV_PPR_REQ 0x9
 #define EVENT_DEVID_MASK       0xffff
 #define EVENT_DEVID_SHIFT      0
 #define EVENT_DOMID_MASK       0xffff
@@ -172,6 +173,7 @@
 /* macros and definitions for device table entries */
 #define DEV_ENTRY_VALID         0x00
 #define DEV_ENTRY_TRANSLATION   0x01
+#define DEV_ENTRY_GUEST_TRANSLATION   0x37
 #define DEV_ENTRY_IR            0x3d
 #define DEV_ENTRY_IW            0x3e
 #define DEV_ENTRY_NO_PAGE_FAULT        0x62
@@ -179,6 +181,7 @@
 #define DEV_ENTRY_SYSMGT1       0x68
 #define DEV_ENTRY_SYSMGT2       0x69
 #define DEV_ENTRY_IRQ_TBL_EN   0x80
+#define DEV_ENTRY_IG           0x85
 #define DEV_ENTRY_INIT_PASS     0xb8
 #define DEV_ENTRY_EINT_PASS     0xb9
 #define DEV_ENTRY_NMI_PASS      0xba
@@ -626,6 +629,13 @@ struct dev_table_entry {
 };
 
 /*
+ * general struct to manage commands send to an IOMMU
+ */
+struct iommu_cmd {
+       u32 data[4];
+};
+
+/*
  * One entry for unity mappings parsed out of the ACPI table.
  */
 struct unity_map_entry {
-- 
1.7.10.4


_______________________________________________
iommu mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/iommu

Reply via email to