clear-pkgs-linux-iot-lts2018/0879-trusty-Add-interrupt-s...

594 lines
16 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Arve=20Hj=C3=B8nnev=C3=A5g?= <arve@android.com>
Date: Mon, 18 Nov 2013 20:52:55 -0800
Subject: [PATCH] trusty: Add interrupt support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Get list of interrupts from secure mode and register handlers for them.
When an interrupt triggers, disable the interrupt and schedule a work
function. The work functions then masks interrupts at the cpu, reenables
the interrupt and calls into secure mode.
Edge triggered interrupts are not supported.
Change-Id: I6df62e791140f0f2a8b5718b30edd86cca3dde5b
Signed-off-by: Arve Hjønnevåg <arve@android.com>
---
.../devicetree/bindings/trusty/trusty-irq.txt | 8 +
drivers/trusty/Makefile | 1 +
drivers/trusty/trusty-irq.c | 536 ++++++++++++++++++
3 files changed, 545 insertions(+)
create mode 100644 Documentation/devicetree/bindings/trusty/trusty-irq.txt
create mode 100644 drivers/trusty/trusty-irq.c
diff --git a/Documentation/devicetree/bindings/trusty/trusty-irq.txt b/Documentation/devicetree/bindings/trusty/trusty-irq.txt
new file mode 100644
index 000000000000..85fe1f1c7458
--- /dev/null
+++ b/Documentation/devicetree/bindings/trusty/trusty-irq.txt
@@ -0,0 +1,8 @@
+Trusty irq interface
+
+Trusty requires non-secure irqs to be forwarded to the secure OS.
+
+Required properties:
+- compatible: "android,trusty-irq-v1"
+
+Must be a child of the node that provides the trusty std/fast call interface.
diff --git a/drivers/trusty/Makefile b/drivers/trusty/Makefile
index 1d77805d7dd6..89acb6f7868a 100644
--- a/drivers/trusty/Makefile
+++ b/drivers/trusty/Makefile
@@ -3,3 +3,4 @@
#
obj-$(CONFIG_TRUSTY) += trusty.o
+obj-$(CONFIG_TRUSTY) += trusty-irq.o
diff --git a/drivers/trusty/trusty-irq.c b/drivers/trusty/trusty-irq.c
new file mode 100644
index 000000000000..ae9535af77dd
--- /dev/null
+++ b/drivers/trusty/trusty-irq.c
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2013 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/cpu.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/trusty/smcall.h>
+#include <linux/trusty/sm_err.h>
+#include <linux/trusty/trusty.h>
+
+struct trusty_irq {
+ struct trusty_irq_state *is;
+ struct hlist_node node;
+ unsigned int irq;
+ bool percpu;
+ bool enable;
+ struct trusty_irq __percpu *percpu_ptr;
+};
+
+struct trusty_irq_work {
+ struct trusty_irq_state *is;
+ struct work_struct work;
+};
+
+struct trusty_irq_irqset {
+ struct hlist_head pending;
+ struct hlist_head inactive;
+};
+
+struct trusty_irq_state {
+ struct device *dev;
+ struct device *trusty_dev;
+ struct trusty_irq_work __percpu *irq_work;
+ struct trusty_irq_irqset normal_irqs;
+ spinlock_t normal_irqs_lock;
+ struct trusty_irq_irqset __percpu *percpu_irqs;
+ struct notifier_block trusty_call_notifier;
+ struct notifier_block cpu_notifier;
+};
+
+static void trusty_irq_enable_pending_irqs(struct trusty_irq_state *is,
+ struct trusty_irq_irqset *irqset,
+ bool percpu)
+{
+ struct hlist_node *n;
+ struct trusty_irq *trusty_irq;
+
+ hlist_for_each_entry_safe(trusty_irq, n, &irqset->pending, node) {
+ dev_dbg(is->dev,
+ "%s: enable pending irq %d, percpu %d, cpu %d\n",
+ __func__, trusty_irq->irq, percpu, smp_processor_id());
+ if (percpu)
+ enable_percpu_irq(trusty_irq->irq, 0);
+ else
+ enable_irq(trusty_irq->irq);
+ hlist_del(&trusty_irq->node);
+ hlist_add_head(&trusty_irq->node, &irqset->inactive);
+ }
+}
+
+static void trusty_irq_enable_irqset(struct trusty_irq_state *is,
+ struct trusty_irq_irqset *irqset)
+{
+ struct trusty_irq *trusty_irq;
+
+ hlist_for_each_entry(trusty_irq, &irqset->inactive, node) {
+ if (trusty_irq->enable) {
+ dev_warn(is->dev,
+ "%s: percpu irq %d already enabled, cpu %d\n",
+ __func__, trusty_irq->irq, smp_processor_id());
+ continue;
+ }
+ dev_dbg(is->dev, "%s: enable percpu irq %d, cpu %d\n",
+ __func__, trusty_irq->irq, smp_processor_id());
+ enable_percpu_irq(trusty_irq->irq, 0);
+ trusty_irq->enable = true;
+ }
+}
+
+static void trusty_irq_disable_irqset(struct trusty_irq_state *is,
+ struct trusty_irq_irqset *irqset)
+{
+ struct hlist_node *n;
+ struct trusty_irq *trusty_irq;
+
+ hlist_for_each_entry(trusty_irq, &irqset->inactive, node) {
+ if (!trusty_irq->enable) {
+ dev_warn(is->dev,
+ "irq %d already disabled, percpu %d, cpu %d\n",
+ trusty_irq->irq, trusty_irq->percpu,
+ smp_processor_id());
+ continue;
+ }
+ dev_dbg(is->dev, "%s: disable irq %d, percpu %d, cpu %d\n",
+ __func__, trusty_irq->irq, trusty_irq->percpu,
+ smp_processor_id());
+ trusty_irq->enable = false;
+ if (trusty_irq->percpu)
+ disable_percpu_irq(trusty_irq->irq);
+ else
+ disable_irq_nosync(trusty_irq->irq);
+ }
+ hlist_for_each_entry_safe(trusty_irq, n, &irqset->pending, node) {
+ if (!trusty_irq->enable) {
+ dev_warn(is->dev,
+ "pending irq %d already disabled, percpu %d, cpu %d\n",
+ trusty_irq->irq, trusty_irq->percpu,
+ smp_processor_id());
+ }
+ dev_dbg(is->dev,
+ "%s: disable pending irq %d, percpu %d, cpu %d\n",
+ __func__, trusty_irq->irq, trusty_irq->percpu,
+ smp_processor_id());
+ trusty_irq->enable = false;
+ hlist_del(&trusty_irq->node);
+ hlist_add_head(&trusty_irq->node, &irqset->inactive);
+ }
+}
+
+static int trusty_irq_call_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct trusty_irq_state *is;
+
+ BUG_ON(!irqs_disabled());
+
+ if (action != TRUSTY_CALL_PREPARE)
+ return NOTIFY_DONE;
+
+ is = container_of(nb, struct trusty_irq_state, trusty_call_notifier);
+
+ spin_lock(&is->normal_irqs_lock);
+ trusty_irq_enable_pending_irqs(is, &is->normal_irqs, false);
+ spin_unlock(&is->normal_irqs_lock);
+ trusty_irq_enable_pending_irqs(is, this_cpu_ptr(is->percpu_irqs), true);
+
+ return NOTIFY_OK;
+}
+
+
+static void trusty_irq_work_func(struct work_struct *work)
+{
+ int ret;
+ struct trusty_irq_state *is =
+ container_of(work, struct trusty_irq_work, work)->is;
+
+ dev_dbg(is->dev, "%s\n", __func__);
+
+ ret = trusty_std_call32(is->trusty_dev, SMC_SC_NOP, 0, 0, 0);
+ if (ret != 0)
+ dev_err(is->dev, "%s: SMC_SC_NOP failed %d", __func__, ret);
+
+ dev_dbg(is->dev, "%s: done\n", __func__);
+}
+
+irqreturn_t trusty_irq_handler(int irq, void *data)
+{
+ struct trusty_irq *trusty_irq = data;
+ struct trusty_irq_state *is = trusty_irq->is;
+ struct trusty_irq_work *trusty_irq_work = this_cpu_ptr(is->irq_work);
+ struct trusty_irq_irqset *irqset;
+
+ dev_dbg(is->dev, "%s: irq %d, percpu %d, cpu %d, enable %d\n",
+ __func__, irq, trusty_irq->irq, smp_processor_id(),
+ trusty_irq->enable);
+
+ if (trusty_irq->percpu) {
+ disable_percpu_irq(irq);
+ irqset = this_cpu_ptr(is->percpu_irqs);
+ } else {
+ disable_irq_nosync(irq);
+ irqset = &is->normal_irqs;
+ }
+
+ spin_lock(&is->normal_irqs_lock);
+ if (trusty_irq->enable) {
+ hlist_del(&trusty_irq->node);
+ hlist_add_head(&trusty_irq->node, &irqset->pending);
+ }
+ spin_unlock(&is->normal_irqs_lock);
+
+ schedule_work_on(raw_smp_processor_id(), &trusty_irq_work->work);
+
+ dev_dbg(is->dev, "%s: irq %d done\n", __func__, irq);
+
+ return IRQ_HANDLED;
+}
+
+static void trusty_irq_cpu_up(void *info)
+{
+ unsigned long irq_flags;
+ struct trusty_irq_state *is = info;
+
+ dev_dbg(is->dev, "%s: cpu %d\n", __func__, smp_processor_id());
+
+ local_irq_save(irq_flags);
+ trusty_irq_enable_irqset(is, this_cpu_ptr(is->percpu_irqs));
+ local_irq_restore(irq_flags);
+}
+
+static void trusty_irq_cpu_down(void *info)
+{
+ unsigned long irq_flags;
+ struct trusty_irq_state *is = info;
+
+ dev_dbg(is->dev, "%s: cpu %d\n", __func__, smp_processor_id());
+
+ local_irq_save(irq_flags);
+ trusty_irq_disable_irqset(is, this_cpu_ptr(is->percpu_irqs));
+ local_irq_restore(irq_flags);
+}
+
+static int trusty_irq_cpu_notify(struct notifier_block *nb,
+ unsigned long action, void *hcpu)
+{
+ struct trusty_irq_state *is;
+
+ is = container_of(nb, struct trusty_irq_state, cpu_notifier);
+
+ dev_dbg(is->dev, "%s: 0x%lx\n", __func__, action);
+
+ switch (action & ~CPU_TASKS_FROZEN) {
+ case CPU_STARTING:
+ trusty_irq_cpu_up(is);
+ break;
+ case CPU_DYING:
+ trusty_irq_cpu_down(is);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int trusty_irq_init_normal_irq(struct trusty_irq_state *is, int irq)
+{
+ int ret;
+ unsigned long irq_flags;
+ struct trusty_irq *trusty_irq;
+
+ dev_dbg(is->dev, "%s: irq %d\n", __func__, irq);
+
+ trusty_irq = kzalloc(sizeof(*trusty_irq), GFP_KERNEL);
+ if (!trusty_irq)
+ return -ENOMEM;
+
+ trusty_irq->is = is;
+ trusty_irq->irq = irq;
+ trusty_irq->enable = true;
+
+ spin_lock_irqsave(&is->normal_irqs_lock, irq_flags);
+ hlist_add_head(&trusty_irq->node, &is->normal_irqs.inactive);
+ spin_unlock_irqrestore(&is->normal_irqs_lock, irq_flags);
+
+ ret = request_irq(irq, trusty_irq_handler, IRQF_NO_THREAD,
+ "trusty", trusty_irq);
+ if (ret) {
+ dev_err(is->dev, "request_irq failed %d\n", ret);
+ goto err_request_irq;
+ }
+ return 0;
+
+err_request_irq:
+ spin_lock_irqsave(&is->normal_irqs_lock, irq_flags);
+ hlist_del(&trusty_irq->node);
+ spin_unlock_irqrestore(&is->normal_irqs_lock, irq_flags);
+ kfree(trusty_irq);
+ return ret;
+}
+
+static int trusty_irq_init_per_cpu_irq(struct trusty_irq_state *is, int irq)
+{
+ int ret;
+ unsigned int cpu;
+ struct trusty_irq __percpu *trusty_irq_handler_data;
+
+ dev_dbg(is->dev, "%s: irq %d\n", __func__, irq);
+
+ trusty_irq_handler_data = alloc_percpu(struct trusty_irq);
+ if (!trusty_irq_handler_data)
+ return -ENOMEM;
+
+ for_each_possible_cpu(cpu) {
+ struct trusty_irq *trusty_irq;
+ struct trusty_irq_irqset *irqset;
+
+ trusty_irq = per_cpu_ptr(trusty_irq_handler_data, cpu);
+ irqset = per_cpu_ptr(is->percpu_irqs, cpu);
+
+ trusty_irq->is = is;
+ hlist_add_head(&trusty_irq->node, &irqset->inactive);
+ trusty_irq->irq = irq;
+ trusty_irq->percpu = true;
+ trusty_irq->percpu_ptr = trusty_irq_handler_data;
+ }
+
+ ret = request_percpu_irq(irq, trusty_irq_handler, "trusty",
+ trusty_irq_handler_data);
+ if (ret) {
+ dev_err(is->dev, "request_percpu_irq failed %d\n", ret);
+ goto err_request_percpu_irq;
+ }
+
+ return 0;
+
+err_request_percpu_irq:
+ for_each_possible_cpu(cpu) {
+ struct trusty_irq *trusty_irq;
+
+ trusty_irq = per_cpu_ptr(trusty_irq_handler_data, cpu);
+ hlist_del(&trusty_irq->node);
+ }
+
+ free_percpu(trusty_irq_handler_data);
+ return ret;
+}
+
+static int trusty_smc_get_next_irq(struct trusty_irq_state *is,
+ unsigned long min_irq, bool per_cpu)
+{
+ return trusty_fast_call32(is->trusty_dev, SMC_FC_GET_NEXT_IRQ,
+ min_irq, per_cpu, 0);
+}
+
+static int trusty_irq_init_one(struct trusty_irq_state *is,
+ int irq, bool per_cpu)
+{
+ int ret;
+
+ irq = trusty_smc_get_next_irq(is, irq, per_cpu);
+ if (irq < 0)
+ return irq;
+
+ if (per_cpu)
+ ret = trusty_irq_init_per_cpu_irq(is, irq);
+ else
+ ret = trusty_irq_init_normal_irq(is, irq);
+
+ if (ret) {
+ dev_warn(is->dev,
+ "failed to initialize irq %d, irq will be ignored\n",
+ irq);
+ }
+
+ return irq + 1;
+}
+
+static void trusty_irq_free_irqs(struct trusty_irq_state *is)
+{
+ struct trusty_irq *irq;
+ struct hlist_node *n;
+ unsigned int cpu;
+
+ hlist_for_each_entry_safe(irq, n, &is->normal_irqs.inactive, node) {
+ dev_dbg(is->dev, "%s: irq %d\n", __func__, irq->irq);
+ free_irq(irq->irq, irq);
+ hlist_del(&irq->node);
+ kfree(irq);
+ }
+ hlist_for_each_entry_safe(irq, n,
+ &this_cpu_ptr(is->percpu_irqs)->inactive,
+ node) {
+ struct trusty_irq __percpu *trusty_irq_handler_data;
+
+ dev_dbg(is->dev, "%s: percpu irq %d\n", __func__, irq->irq);
+ trusty_irq_handler_data = irq->percpu_ptr;
+ free_percpu_irq(irq->irq, trusty_irq_handler_data);
+ for_each_possible_cpu(cpu) {
+ struct trusty_irq *irq_tmp;
+
+ irq_tmp = per_cpu_ptr(trusty_irq_handler_data, cpu);
+ hlist_del(&irq_tmp->node);
+ }
+ free_percpu(trusty_irq_handler_data);
+ }
+}
+
+static int trusty_irq_probe(struct platform_device *pdev)
+{
+ int ret;
+ int irq;
+ unsigned int cpu;
+ unsigned long irq_flags;
+ struct trusty_irq_state *is;
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ is = kzalloc(sizeof(*is), GFP_KERNEL);
+ if (!is) {
+ ret = -ENOMEM;
+ goto err_alloc_is;
+ }
+
+ is->dev = &pdev->dev;
+ is->trusty_dev = is->dev->parent;
+ is->irq_work = alloc_percpu(struct trusty_irq_work);
+ if (!is->irq_work) {
+ ret = -ENOMEM;
+ goto err_alloc_irq_work;
+ }
+ spin_lock_init(&is->normal_irqs_lock);
+ is->percpu_irqs = alloc_percpu(struct trusty_irq_irqset);
+ if (!is->percpu_irqs) {
+ ret = -ENOMEM;
+ goto err_alloc_pending_percpu_irqs;
+ }
+
+ platform_set_drvdata(pdev, is);
+
+ is->trusty_call_notifier.notifier_call = trusty_irq_call_notify;
+ ret = trusty_call_notifier_register(is->trusty_dev,
+ &is->trusty_call_notifier);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to register trusty call notifier\n");
+ goto err_trusty_call_notifier_register;
+ }
+
+ for_each_possible_cpu(cpu) {
+ struct trusty_irq_work *trusty_irq_work;
+
+ trusty_irq_work = per_cpu_ptr(is->irq_work, cpu);
+ trusty_irq_work->is = is;
+ INIT_WORK(&trusty_irq_work->work, trusty_irq_work_func);
+ }
+
+ for (irq = 0; irq >= 0;)
+ irq = trusty_irq_init_one(is, irq, true);
+ for (irq = 0; irq >= 0;)
+ irq = trusty_irq_init_one(is, irq, false);
+
+ is->cpu_notifier.notifier_call = trusty_irq_cpu_notify;
+ ret = register_hotcpu_notifier(&is->cpu_notifier);
+ if (ret) {
+ dev_err(&pdev->dev, "register_cpu_notifier failed %d\n", ret);
+ goto err_register_hotcpu_notifier;
+ }
+ ret = on_each_cpu(trusty_irq_cpu_up, is, 0);
+ if (ret) {
+ dev_err(&pdev->dev, "register_cpu_notifier failed %d\n", ret);
+ goto err_on_each_cpu;
+ }
+
+ return 0;
+
+err_on_each_cpu:
+ unregister_hotcpu_notifier(&is->cpu_notifier);
+ on_each_cpu(trusty_irq_cpu_down, is, 1);
+err_register_hotcpu_notifier:
+ spin_lock_irqsave(&is->normal_irqs_lock, irq_flags);
+ trusty_irq_disable_irqset(is, &is->normal_irqs);
+ spin_unlock_irqrestore(&is->normal_irqs_lock, irq_flags);
+ trusty_irq_free_irqs(is);
+ trusty_call_notifier_unregister(is->trusty_dev,
+ &is->trusty_call_notifier);
+err_trusty_call_notifier_register:
+ free_percpu(is->percpu_irqs);
+err_alloc_pending_percpu_irqs:
+ for_each_possible_cpu(cpu) {
+ struct trusty_irq_work *trusty_irq_work;
+
+ trusty_irq_work = per_cpu_ptr(is->irq_work, cpu);
+ flush_work(&trusty_irq_work->work);
+ }
+ free_percpu(is->irq_work);
+err_alloc_irq_work:
+ kfree(is);
+err_alloc_is:
+ return ret;
+}
+
+static int trusty_irq_remove(struct platform_device *pdev)
+{
+ int ret;
+ unsigned int cpu;
+ unsigned long irq_flags;
+ struct trusty_irq_state *is = platform_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ unregister_hotcpu_notifier(&is->cpu_notifier);
+ ret = on_each_cpu(trusty_irq_cpu_down, is, 1);
+ if (ret)
+ dev_err(&pdev->dev, "on_each_cpu failed %d\n", ret);
+ spin_lock_irqsave(&is->normal_irqs_lock, irq_flags);
+ trusty_irq_disable_irqset(is, &is->normal_irqs);
+ spin_unlock_irqrestore(&is->normal_irqs_lock, irq_flags);
+
+ trusty_irq_free_irqs(is);
+
+ trusty_call_notifier_unregister(is->trusty_dev,
+ &is->trusty_call_notifier);
+ free_percpu(is->percpu_irqs);
+ for_each_possible_cpu(cpu) {
+ struct trusty_irq_work *trusty_irq_work;
+
+ trusty_irq_work = per_cpu_ptr(is->irq_work, cpu);
+ flush_work(&trusty_irq_work->work);
+ }
+ free_percpu(is->irq_work);
+ kfree(is);
+
+ return 0;
+}
+
+static const struct of_device_id trusty_test_of_match[] = {
+ { .compatible = "android,trusty-irq-v1", },
+ {},
+};
+
+static struct platform_driver trusty_irq_driver = {
+ .probe = trusty_irq_probe,
+ .remove = trusty_irq_remove,
+ .driver = {
+ .name = "trusty-irq",
+ .owner = THIS_MODULE,
+ .of_match_table = trusty_test_of_match,
+ },
+};
+
+module_platform_driver(trusty_irq_driver);
--
https://clearlinux.org