Enable but ignore hypervisor timer IRQ.

This will be used by Hafnium for emulating the virtual timer for primary
vCPUs.

Bug: 117271574
Change-Id: I2368d9fbd1596dcfdbeb88613fb29a61b14157a4
diff --git a/main.c b/main.c
index 9ff6e32..6f83699 100644
--- a/main.c
+++ b/main.c
@@ -12,16 +12,22 @@
  * GNU General Public License for more details.
  */
 
-#include <linux/hrtimer.h>
+#include <clocksource/arm_arch_timer.h>
 #include <linux/atomic.h>
+#include <linux/cpuhotplug.h>
+#include <linux/hrtimer.h>
 #include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
 #include <linux/kernel.h>
 #include <linux/kthread.h>
 #include <linux/mm.h>
 #include <linux/module.h>
+#include <linux/net.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
 #include <linux/sched/task.h>
 #include <linux/slab.h>
-#include <linux/net.h>
 #include <net/sock.h>
 
 #include <hf/call.h>
@@ -30,6 +36,8 @@
 #define AF_HF AF_ECONET
 #define PF_HF AF_HF
 
+#define HYPERVISOR_TIMER_NAME "el2_timer"
+
 #define CONFIG_HAFNIUM_MAX_VMS   16
 #define CONFIG_HAFNIUM_MAX_VCPUS 32
 
@@ -87,6 +95,7 @@
 static DEFINE_SPINLOCK(hf_send_lock);
 static DEFINE_HASHTABLE(hf_local_port_hash, 7);
 static DEFINE_SPINLOCK(hf_local_port_hash_lock);
+static int hf_irq;
 
 /**
  * Retrieves a VM from its ID, returning NULL if the VM doesn't exist.
@@ -738,6 +747,119 @@
 }
 
 /**
+ * Handles the hypervisor timer interrupt.
+ */
+static irqreturn_t hf_nop_irq_handler(int irq, void *dev)
+{
+	/*
+	 * No need to do anything, the interrupt only exists to return to the
+	 * primary vCPU so that the virtual timer will be restored and fire as
+	 * normal.
+	 */
+	return IRQ_HANDLED;
+}
+
+/**
+ * Enables the hypervisor timer interrupt on a CPU, when it starts or after the
+ * driver is first loaded.
+ */
+static int hf_starting_cpu(unsigned int cpu)
+{
+	if (hf_irq != 0) {
+		/* Enable the interrupt, and set it to be edge-triggered. */
+		enable_percpu_irq(hf_irq, IRQ_TYPE_EDGE_RISING);
+	}
+	return 0;
+}
+
+/**
+ * Disables the hypervisor timer interrupt on a CPU when it is powered down.
+ */
+static int hf_dying_cpu(unsigned int cpu)
+{
+	if (hf_irq != 0) {
+		/* Disable the interrupt while the CPU is asleep. */
+		disable_percpu_irq(hf_irq);
+	}
+
+	return 0;
+}
+
+/**
+ * Registers for the hypervisor timer interrupt.
+ */
+static int hf_int_driver_probe(struct platform_device *pdev)
+{
+	int irq;
+	int ret;
+
+	/*
+	 * Register a handler for the hyperviser timer IRQ, as it is needed for
+	 * Hafnium to emulate the virtual timer for Linux while a secondary vCPU
+	 * is running.
+	 */
+	irq = platform_get_irq(pdev, ARCH_TIMER_HYP_PPI);
+	if (irq < 0) {
+		pr_err("Error getting hypervisor timer IRQ: %d\n", irq);
+		return irq;
+	}
+	hf_irq = irq;
+
+	ret = request_percpu_irq(irq, hf_nop_irq_handler, HYPERVISOR_TIMER_NAME,
+				 pdev);
+	if (ret != 0) {
+		pr_err("Error registering hypervisor timer IRQ %d: %d\n",
+		       irq, ret);
+		return ret;
+	}
+	pr_info("Hafnium registered for IRQ %d\n", irq);
+	ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+				"hafnium/hypervisor_timer:starting",
+				hf_starting_cpu, hf_dying_cpu);
+	if (ret < 0) {
+		pr_err("Error enabling timer on all CPUs: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * Unregisters for the hypervisor timer interrupt.
+ */
+static int hf_int_driver_remove(struct platform_device *pdev)
+{
+	int irq;
+
+	irq = platform_get_irq(pdev, ARCH_TIMER_HYP_PPI);
+	if (irq < 0) {
+		pr_err("Error getting hypervisor timer IRQ: %d\n", irq);
+		return irq;
+	}
+
+	disable_percpu_irq(irq);
+	free_percpu_irq(irq, pdev);
+
+	return 0;
+}
+
+static const struct of_device_id hf_int_driver_id[] = {
+	{.compatible = "arm,armv7-timer"},
+	{.compatible = "arm,armv8-timer"},
+	{}
+};
+
+static struct platform_driver hf_int_driver = {
+	.driver = {
+		.name = HYPERVISOR_TIMER_NAME,
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(hf_int_driver_id),
+	},
+	.probe = hf_int_driver_probe,
+	.remove = hf_int_driver_remove,
+};
+
+/**
  * Initializes the Hafnium driver by creating a thread for each vCPU of each
  * virtual machine.
  */
@@ -892,10 +1014,20 @@
 	}
 
 	/*
+	 * Register as a driver for the timer device, so we can register a
+	 * handler for the hyperviser timer IRQ.
+	 */
+	ret = platform_driver_register(&hf_int_driver);
+	if (ret != 0) {
+		pr_err("Error registering timer driver %lld\n", ret);
+		goto fail_unregister_socket;
+	}
+
+	/*
 	 * Start running threads now that all is initialized.
 	 *
-	 * Any failures from this point on must also unregister the socket
-	 * family with a call to sock_unregister().
+	 * Any failures from this point on must also unregister the driver with
+	 * platform_driver_unregister().
 	 */
 	for (i = 0; i < hf_vm_count; i++) {
 		struct hf_vm *vm = &hf_vms[i];
@@ -914,6 +1046,8 @@
 
 	return 0;
 
+fail_unregister_socket:
+	sock_unregister(PF_HF);
 fail_unregister_proto:
 	proto_unregister(&hf_sock_proto);
 fail_with_cleanup:
@@ -930,6 +1064,7 @@
 	pr_info("Preparing to unload Hafnium\n");
 	sock_unregister(PF_HF);
 	proto_unregister(&hf_sock_proto);
+	platform_driver_unregister(&hf_int_driver);
 	hf_free_resources();
 	pr_info("Hafnium ready to unload\n");
 }