[COMMON] iommu/exynos: add fault handler API
authorJanghyuck Kim <janghyuck.kim@samsung.com>
Mon, 9 May 2016 06:16:24 +0000 (15:16 +0900)
committerSangwook Ju <sw.ju@samsung.com>
Mon, 14 May 2018 10:45:19 +0000 (19:45 +0900)
By fault handler API, master driver can register their own fault handler
that would be called when related sysmmu fault occurred.
In fault handler, master driver can print out their useful information
like SFR for debugging.

Change-Id: Ica6b7df8108d3165a7e1a3632aba027f328d1ecf
Signed-off-by: Janghyuck Kim <janghyuck.kim@samsung.com>
drivers/iommu/exynos-iommu.c
drivers/iommu/exynos-iommu.h
drivers/iommu/exynos-iovmm.c

index e69b0cb63bcc54798f876134eb022f51c7f481e9..62ec5882d83913497b91d763232dac6518cfc880 100644 (file)
@@ -53,6 +53,11 @@ struct exynos_client {
 static LIST_HEAD(exynos_client_list);
 static DEFINE_SPINLOCK(exynos_client_lock);
 
+struct owner_fault_info {
+       struct device *master;
+       struct notifier_block nb;
+};
+
 int exynos_client_add(struct device_node *np, struct exynos_iovmm *vmm_data)
 {
        struct exynos_client *client = kzalloc(sizeof(*client), GFP_KERNEL);
@@ -303,6 +308,7 @@ static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id)
        flags |= SYSMMU_FAULT_FLAG(itype);
 
        show_fault_information(drvdata, flags, addr);
+       atomic_notifier_call_chain(&drvdata->fault_notifiers, addr, &flags);
 
        panic("Unrecoverable System MMU Fault!!");
 
@@ -373,6 +379,7 @@ static int __init exynos_sysmmu_probe(struct platform_device *pdev)
 
        data->sysmmu = dev;
        spin_lock_init(&data->lock);
+       ATOMIC_INIT_NOTIFIER_HEAD(&data->fault_notifiers);
        if (!sysmmu_drvdata_list) {
                sysmmu_drvdata_list = data;
        } else {
@@ -1090,11 +1097,61 @@ void exynos_sysmmu_show_status(struct device *dev)
        dev_info(dev, "Called sysmmu show status\n");
 }
 
-void iovmm_set_fault_handler(struct device *dev,
+static int sysmmu_fault_notifier(struct notifier_block *nb,
+                                    unsigned long fault_addr, void *data)
+{
+       struct owner_fault_info *info;
+       struct exynos_iommu_owner *owner = NULL;
+
+       info = container_of(nb, struct owner_fault_info, nb);
+       owner = info->master->archdata.iommu;
+
+       if (owner && owner->fault_handler)
+               owner->fault_handler(owner->domain, owner->master,
+                       fault_addr, (unsigned long)data, owner->token);
+
+       return 0;
+}
+
+int exynos_iommu_add_fault_handler(struct device *master,
                             iommu_fault_handler_t handler, void *token)
 {
-       /* DUMMY */
-       dev_info(dev, "Called set fault handler\n");
+       struct exynos_iommu_owner *owner = master->archdata.iommu;
+       struct sysmmu_list_data *list;
+       struct sysmmu_drvdata *drvdata;
+       struct owner_fault_info *info;
+       unsigned long flags;
+
+       if (!has_sysmmu(master)) {
+               dev_info(master, "%s doesn't have sysmmu\n", dev_name(master));
+               return -ENODEV;
+       }
+
+       spin_lock_irqsave(&owner->lock, flags);
+
+       owner->fault_handler = handler;
+       owner->token = token;
+
+       list_for_each_entry(list, &owner->sysmmu_list, node) {
+               info = kzalloc(sizeof(*info), GFP_ATOMIC);
+               if (!info) {
+                       spin_unlock_irqrestore(&owner->lock, flags);
+                       return -ENOMEM;
+               }
+               info->master = master;
+               info->nb.notifier_call = sysmmu_fault_notifier;
+
+               drvdata = dev_get_drvdata(list->sysmmu);
+
+               atomic_notifier_chain_register(
+                               &drvdata->fault_notifiers, &info->nb);
+               dev_info(master, "Fault handler is registered for %s\n",
+                                               dev_name(list->sysmmu));
+       }
+
+       spin_unlock_irqrestore(&owner->lock, flags);
+
+       return 0;
 }
 
 int sysmmu_set_prefetch_buffer_property(struct device *dev,
index 03b8afba201fcf6eadf907adbdc7a3ce1c48cb63..8350c962bb075399a54affa8b89806a542ff29fe 100644 (file)
@@ -185,6 +185,8 @@ struct exynos_iommu_owner {
        struct device *master;          /* master device */
        struct list_head client;        /* node for owner clients_list */
        struct exynos_iovmm *vmm_data;
+       iommu_fault_handler_t fault_handler;
+       void *token;
 };
 
 /*
@@ -204,6 +206,7 @@ struct sysmmu_drvdata {
        spinlock_t lock;                /* lock for modyfying state */
        phys_addr_t pgtable;            /* assigned page table structure */
        int version;                    /* our version */
+       struct atomic_notifier_head fault_notifiers;
 };
 
 struct exynos_vm_region {
@@ -237,6 +240,8 @@ int exynos_iommu_map_userptr(struct iommu_domain *dom, unsigned long addr,
                              dma_addr_t iova, size_t size, int prot);
 void exynos_iommu_unmap_userptr(struct iommu_domain *dom,
                                dma_addr_t iova, size_t size);
+int exynos_iommu_add_fault_handler(struct device *dev,
+                               iommu_fault_handler_t handler, void *token);
 
 static inline bool get_sysmmu_runtime_active(struct sysmmu_drvdata *data)
 {
index 7f0f4963fcc43788e5abcee7caa512a296cd7a03..3f8643937b97b4ef2531adc73f2b1b6d08d1627b 100644 (file)
@@ -723,3 +723,13 @@ err_alloc_vmm:
 
        return ERR_PTR(ret);
 }
+
+void iovmm_set_fault_handler(struct device *dev,
+                            iommu_fault_handler_t handler, void *token)
+{
+       int ret;
+
+       ret = exynos_iommu_add_fault_handler(dev, handler, token);
+       if (ret)
+               dev_err(dev, "Failed to add fault handler\n");
+}