kcov: Support hypervisor kcov if provided by arch

Change-Id: I3c9dc4ddcbb23607630abbf4c3e9446ddcee3096
diff --git a/include/linux/kcov.h b/include/linux/kcov.h
index ee04256..62659f1 100644
--- a/include/linux/kcov.h
+++ b/include/linux/kcov.h
@@ -90,4 +90,35 @@ static inline void kcov_remote_start_usb_softirq(u64 id) {}
 static inline void kcov_remote_stop_softirq(void) {}
 
 #endif /* CONFIG_KCOV */
+
+/* The architecture shouldn't provide this if CONFIG_KCOV is off, however it can
+ * still do it and the functions just won't be used */
+#ifdef CONFIG_ARCH_HAS_HYPERVISOR_KCOV
+
+int kvm_kcov_hyp_init_tracing_buffer(void *mem, unsigned int size);
+int kvm_kcov_hyp_teardown_tracing_buffer(int buffer_index);
+int kvm_kcov_hyp_enable_tracing(int buffer_index);
+int kvm_kcov_hyp_disable_tracing(void);
+
+#else
+
+static inline int kvm_kcov_hyp_init_tracing_buffer(void *mem, unsigned int size)
+{
+	return -ENOSYS;
+}
+static inline int kvm_kcov_hyp_teardown_tracing_buffer(int buffer_index)
+{
+	return -ENOSYS;
+}
+static inline int kvm_kcov_hyp_enable_tracing(int buffer_index)
+{
+	return -ENOSYS;
+}
+static inline int kvm_kcov_hyp_disable_tracing(void)
+{
+	return -ENOSYS;
+}
+
+#endif /* CONFIG_ARCH_HAS_HYPERVISOR_KCOV */
+
 #endif /* _LINUX_KCOV_H */
diff --git a/include/uapi/linux/kcov.h b/include/uapi/linux/kcov.h
index 983bc27..ed09e58 100644
--- a/include/uapi/linux/kcov.h
+++ b/include/uapi/linux/kcov.h
@@ -19,6 +19,7 @@ struct kcov_remote_arg {
 #define KCOV_REMOTE_MAX_HANDLES		0x100
 
 #define KCOV_INIT_TRACE			_IOR('c', 1, unsigned long)
+#define KCOV_INIT_HYP_TRACE		_IOR('c', 2, unsigned long)
 #define KCOV_ENABLE			_IO('c', 100)
 #define KCOV_DISABLE			_IO('c', 101)
 #define KCOV_REMOTE_ENABLE		_IOW('c', 102, struct kcov_remote_arg)
@@ -39,6 +40,9 @@ enum {
 
 #define KCOV_ENABLE_MODE_MASK (0xffffull)
 #define KCOV_ENABLE_OPTIONS_MASK (0xffffull << 48)
+#define KCOV_ENABLE_HYP (0x1ull << 48)
+#define KCOV_ENABLE_HYP_ONLY (0x1ull << 49)
+
 /*
  * The format for the types of collected comparisons.
  *
diff --git a/kernel/kcov.c b/kernel/kcov.c
index 6721516..1833917 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -59,6 +59,8 @@ struct kcov {
 	unsigned int		size;
 	/* Coverage buffer shared with user space. */
 	void			*area;
+	/* Hypervisor buffer index, -1 if hyp tracing disabled */
+	int			hyp_index;
 	/* Tracing option for coarse filtering */
 	unsigned long		options;
 	/* Task for which we collect coverage, or NULL. */
@@ -344,6 +346,18 @@ void notrace __sanitizer_cov_trace_switch(u64 val, u64 *cases)
 EXPORT_SYMBOL(__sanitizer_cov_trace_switch);
 #endif /* ifdef CONFIG_KCOV_ENABLE_COMPARISONS */
 
+static int kcov_hyp_start(struct kcov *kcov, enum kcov_mode mode,
+				unsigned long options)
+{
+	if (!(options & KCOV_ENABLE_HYP))
+		return 0;
+	if (kcov->hyp_index < 0)
+		return -EINVAL;
+	if (mode != KCOV_MODE_TRACE_PC)
+		return -EINVAL;
+	return kvm_kcov_hyp_enable_tracing(kcov->hyp_index);
+}
+
 static void kcov_start(struct task_struct *t, struct kcov *kcov,
 			unsigned int size, void *area, enum kcov_mode mode,
 			int sequence, unsigned long options)
@@ -354,11 +368,24 @@ static void kcov_start(struct task_struct *t, struct kcov *kcov,
 	t->kcov_size = size;
 	t->kcov_area = area;
 	t->kcov_sequence = sequence;
+	if (options & KCOV_ENABLE_HYP_ONLY)
+		return;
 	/* See comment in check_kcov_mode(). */
 	barrier();
 	WRITE_ONCE(t->kcov_mode, mode);
 }
 
+static int kcov_hyp_stop(struct task_struct *t, struct kcov *kcov)
+{
+	if (kcov->hyp_index < 0)
+		return 0;
+	if (!(kcov->options & KCOV_ENABLE_HYP))
+		return 0;
+	if(t != current)
+		return -1;
+	return kvm_kcov_hyp_disable_tracing();
+}
+
 static void kcov_stop(struct task_struct *t)
 {
 	WRITE_ONCE(t->kcov_mode, KCOV_MODE_DISABLED);
@@ -413,6 +440,7 @@ static void kcov_remote_reset(struct kcov *kcov)
 static void kcov_disable(struct task_struct *t, struct kcov *kcov)
 {
 	kcov_task_reset(t);
+	WARN_ON(kcov_hyp_stop(t, kcov));
 	if (kcov->remote)
 		kcov_remote_reset(kcov);
 	else
@@ -428,6 +456,8 @@ static void kcov_put(struct kcov *kcov)
 {
 	if (refcount_dec_and_test(&kcov->refcount)) {
 		kcov_remote_reset(kcov);
+		if (kcov->hyp_index >= 0)
+			WARN_ON(kvm_kcov_hyp_teardown_tracing_buffer(kcov->hyp_index));
 		vfree(kcov->area);
 		kfree(kcov);
 	}
@@ -517,6 +547,7 @@ static int kcov_open(struct inode *inode, struct file *filep)
 	kcov->mode = KCOV_MODE_DISABLED;
 	kcov->options = 0;
 	kcov->sequence = 1;
+	kcov->hyp_index = -1;
 	refcount_set(&kcov->refcount, 1);
 	spin_lock_init(&kcov->lock);
 	filep->private_data = kcov;
@@ -545,6 +576,8 @@ static int kcov_get_mode(unsigned long arg)
 }
 
 static unsigned long kcov_check_options(unsigned long arg){
+	if ((arg & KCOV_ENABLE_HYP_ONLY) && !(arg & KCOV_ENABLE_HYP))
+		return -EINVAL;
 	return arg & KCOV_ENABLE_OPTIONS_MASK;
 }
 
@@ -585,7 +618,7 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
 {
 	struct task_struct *t;
 	unsigned long flags, unused, options;
-	int mode, i;
+	int mode, i, ret;
 	struct kcov_remote_arg *remote_arg;
 	struct kcov_remote *remote;
 
@@ -610,6 +643,9 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
 		if (options < 0)
 			return options;
 		kcov_fault_in_area(kcov);
+		ret = kcov_hyp_start(kcov, mode, options);
+		if (ret)
+			return ret;
 		kcov->mode = mode;
 		kcov->options = options;
 		kcov_start(t, kcov, kcov->size, kcov->area, kcov->mode,
@@ -704,6 +740,7 @@ static long kcov_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
 	kcov = filep->private_data;
 	switch (cmd) {
 	case KCOV_INIT_TRACE:
+	case KCOV_INIT_HYP_TRACE:
 		/*
 		 * Enable kcov in trace mode and setup buffer size.
 		 * Must happen before anything else.
@@ -723,6 +760,15 @@ static long kcov_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
 			vfree(area);
 			return -EBUSY;
 		}
+		if (cmd == KCOV_INIT_HYP_TRACE) {
+			res = kvm_kcov_hyp_init_tracing_buffer(area, size);
+			if (res < 0) {
+				spin_unlock_irqrestore(&kcov->lock, flags);
+				vfree(area);
+				return res;
+			}
+			kcov->hyp_index = res;
+		}
 		kcov->area = area;
 		kcov->size = size;
 		kcov->mode = KCOV_MODE_INIT;
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index ce51d4d..79bd8ad 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2004,6 +2004,13 @@
 	  build and run with CONFIG_KCOV. This typically requires
 	  disabling instrumentation for some early boot code.
 
+config ARCH_HAS_HYPERVISOR_KCOV
+	bool
+	help
+	  An architecture should select this when it supports building and
+	  running kcov in an KVM hypervisor object separated from the main
+	  kernel (e.g. pKVM)
+
 config CC_HAS_SANCOV_TRACE_PC
 	def_bool $(cc-option,-fsanitize-coverage=trace-pc)