diff mbox series

[v12,23/24] virt: geniezone: Add support for guest VM CPU idle

Message ID 20240730082436.9151-24-liju-clr.chen@mediatek.com (mailing list archive)
State Handled Elsewhere
Headers show
Series GenieZone hypervisor drivers | expand

Commit Message

Liju-clr Chen July 30, 2024, 8:24 a.m. UTC
From: Kevenny Hsieh <kevenny.hsieh@mediatek.com>

Implement CPU idle functionality for guest VMs, allowing the guest
VM's CPU to relinquish control to the host when it enters an idle
state. Enable the host to schedule other processes, thereby reducing
CPU usage and achieving power savings.

Introduce a new capability (`enable_idle_support`) to support the idle
feature. Emulate the WFI (Wait For Interrupt) instruction for the
guest VM when it enters an idle state. Allow the host Linux kernel to
schedule other processes and enable the vCPU to enter the `wait`
state. Ensure that the vCPU can be woken up by interrupts such as the
virtual timer (vtimer) and virtio interrupts.

With the idle feature, the vCPU can efficiently enter a lower power
state, such as decreasing CPU frequency or even entering an idle
state, thereby reducing CPU usage and improving overall system power
efficiency.

Signed-off-by: Kevenny Hsieh <kevenny.hsieh@mediatek.com>
Signed-off-by: Liju Chen <liju-clr.chen@mediatek.com>
---
 arch/arm64/geniezone/vm.c               |  3 ++
 drivers/virt/geniezone/gzvm_exception.c | 46 +++++++++++++++++++++++++
 drivers/virt/geniezone/gzvm_vcpu.c      | 26 +++++++++++++-
 drivers/virt/geniezone/gzvm_vm.c        | 15 ++++++++
 include/linux/soc/mediatek/gzvm_drv.h   |  7 ++++
 include/uapi/linux/gzvm.h               |  2 ++
 6 files changed, 98 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/arch/arm64/geniezone/vm.c b/arch/arm64/geniezone/vm.c
index 549d8611c8d2..d9129080536e 100644
--- a/arch/arm64/geniezone/vm.c
+++ b/arch/arm64/geniezone/vm.c
@@ -411,6 +411,9 @@  int gzvm_vm_ioctl_arch_enable_cap(struct gzvm *gzvm,
 	case GZVM_CAP_BLOCK_BASED_DEMAND_PAGING:
 		ret = gzvm_vm_arch_enable_cap(gzvm, cap, &res);
 		return ret;
+	case GZVM_CAP_ENABLE_IDLE:
+		ret = gzvm_vm_arch_enable_cap(gzvm, cap, &res);
+		return ret;
 	default:
 		break;
 	}
diff --git a/drivers/virt/geniezone/gzvm_exception.c b/drivers/virt/geniezone/gzvm_exception.c
index 5ffee863e378..391168a3f737 100644
--- a/drivers/virt/geniezone/gzvm_exception.c
+++ b/drivers/virt/geniezone/gzvm_exception.c
@@ -5,6 +5,8 @@ 
 
 #include <linux/device.h>
 #include <linux/soc/mediatek/gzvm_drv.h>
+#include <linux/sched.h>
+#include <linux/rcuwait.h>
 
 /**
  * gzvm_handle_guest_exception() - Handle guest exception
@@ -59,3 +61,47 @@  bool gzvm_handle_guest_hvc(struct gzvm_vcpu *vcpu)
 		return gzvm_arch_handle_guest_hvc(vcpu);
 	}
 }
+
+static void vcpu_block_wait(struct gzvm_vcpu *vcpu)
+{
+	struct rcuwait *wait = &vcpu->wait;
+
+	prepare_to_rcuwait(wait);
+
+	while (true) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (vcpu->idle_events.virtio_irq) {
+			vcpu->idle_events.virtio_irq = 0;
+			break;
+		}
+		if (vcpu->idle_events.vtimer_irq) {
+			vcpu->idle_events.vtimer_irq = 0;
+			break;
+		}
+		if (signal_pending(current))
+			break;
+		schedule();
+	}
+	finish_rcuwait(wait);
+}
+
+/**
+ * gzvm_handle_guest_idle() - Handle guest vm entering idle
+ * @vcpu: Pointer to struct gzvm_vcpu struct
+ * Return:
+ */
+int gzvm_handle_guest_idle(struct gzvm_vcpu *vcpu)
+{
+	int ret = 0;
+	u64 ns = 0;
+
+	ns = gzvm_vcpu_arch_get_timer_delay_ns(vcpu);
+
+	if (ns) {
+		gzvm_vtimer_set(vcpu, ns);
+		vcpu_block_wait(vcpu);
+		gzvm_vtimer_release(vcpu);
+	}
+
+	return ret;
+}
diff --git a/drivers/virt/geniezone/gzvm_vcpu.c b/drivers/virt/geniezone/gzvm_vcpu.c
index ba85a6ae04a0..247848ee126c 100644
--- a/drivers/virt/geniezone/gzvm_vcpu.c
+++ b/drivers/virt/geniezone/gzvm_vcpu.c
@@ -16,8 +16,29 @@ 
 /* maximum size needed for holding an integer */
 #define ITOA_MAX_LEN 12
 
+/**
+ * gzvm_vcpu_wakeup_all - wakes up all vCPUs associated with the specified
+ * gzvm.
+ * @gzvm: Pointer to gzvm structure.
+ */
+void gzvm_vcpu_wakeup_all(struct gzvm *gzvm)
+{
+	for (int i = 0; i < GZVM_MAX_VCPUS; i++) {
+		if (gzvm->vcpus[i]) {
+			gzvm->vcpus[i]->idle_events.virtio_irq += 1;
+			rcuwait_wake_up(&gzvm->vcpus[i]->wait);
+		}
+	}
+}
+
 static enum hrtimer_restart gzvm_vtimer_expire(struct hrtimer *hrt)
 {
+	struct gzvm_vcpu *vcpu;
+
+	vcpu = container_of(hrt, struct gzvm_vcpu, gzvm_vtimer);
+
+	gzvm_vcpu_wakeup_all(vcpu->gzvm);
+
 	return HRTIMER_NORESTART;
 }
 
@@ -111,7 +132,7 @@  static bool gzvm_vcpu_handle_mmio(struct gzvm_vcpu *vcpu)
 static long gzvm_vcpu_run(struct gzvm_vcpu *vcpu, void __user *argp)
 {
 	bool need_userspace = false;
-	u64 exit_reason = 0;
+	u64 exit_reason;
 
 	if (copy_from_user(vcpu->run, argp, sizeof(struct gzvm_vcpu_run)))
 		return -EFAULT;
@@ -160,6 +181,9 @@  static long gzvm_vcpu_run(struct gzvm_vcpu *vcpu, void __user *argp)
 			fallthrough;
 		case GZVM_EXIT_GZ:
 			break;
+		case GZVM_EXIT_IDLE:
+			gzvm_handle_guest_idle(vcpu);
+			break;
 		case GZVM_EXIT_UNKNOWN:
 			fallthrough;
 		default:
diff --git a/drivers/virt/geniezone/gzvm_vm.c b/drivers/virt/geniezone/gzvm_vm.c
index db430e532602..7a5f97e8881c 100644
--- a/drivers/virt/geniezone/gzvm_vm.c
+++ b/drivers/virt/geniezone/gzvm_vm.c
@@ -172,6 +172,7 @@  gzvm_vm_ioctl_set_memory_region(struct gzvm *gzvm,
 int gzvm_irqchip_inject_irq(struct gzvm *gzvm, unsigned int vcpu_idx,
 			    u32 irq, bool level)
 {
+	gzvm_vcpu_wakeup_all(gzvm);
 	return gzvm_arch_inject_irq(gzvm, vcpu_idx, irq, level);
 }
 
@@ -556,6 +557,18 @@  static int setup_mem_alloc_mode(struct gzvm *vm)
 	return 0;
 }
 
+static int enable_idle_support(struct gzvm *vm)
+{
+	int ret;
+	struct gzvm_enable_cap cap = {0};
+
+	cap.cap = GZVM_CAP_ENABLE_IDLE;
+	ret = gzvm_vm_ioctl_enable_cap(vm, &cap, NULL);
+	if (ret)
+		pr_info("Hypervisor doesn't support idle\n");
+	return ret;
+}
+
 static struct gzvm *gzvm_create_vm(unsigned long vm_type)
 {
 	int ret;
@@ -603,6 +616,8 @@  static struct gzvm *gzvm_create_vm(unsigned long vm_type)
 
 	pr_debug("VM-%u is created\n", gzvm->vm_id);
 
+	enable_idle_support(gzvm);
+
 	return gzvm;
 }
 
diff --git a/include/linux/soc/mediatek/gzvm_drv.h b/include/linux/soc/mediatek/gzvm_drv.h
index 3c460281cdd4..61f3ae4ee793 100644
--- a/include/linux/soc/mediatek/gzvm_drv.h
+++ b/include/linux/soc/mediatek/gzvm_drv.h
@@ -108,6 +108,11 @@  struct gzvm_vcpu {
 	struct gzvm_vcpu_run *run;
 	struct gzvm_vcpu_hwstate *hwstate;
 	struct hrtimer gzvm_vtimer;
+	struct {
+		u32 vtimer_irq;
+		u32 virtio_irq;
+	} idle_events;
+	struct rcuwait wait;
 };
 
 struct gzvm_pinned_page {
@@ -235,6 +240,8 @@  bool gzvm_handle_guest_exception(struct gzvm_vcpu *vcpu);
 int gzvm_handle_relinquish(struct gzvm_vcpu *vcpu, phys_addr_t ipa);
 bool gzvm_handle_guest_hvc(struct gzvm_vcpu *vcpu);
 bool gzvm_arch_handle_guest_hvc(struct gzvm_vcpu *vcpu);
+int gzvm_handle_guest_idle(struct gzvm_vcpu *vcpu);
+void gzvm_vcpu_wakeup_all(struct gzvm *gzvm);
 
 int gzvm_arch_create_device(u16 vm_id, struct gzvm_create_device *gzvm_dev);
 int gzvm_arch_inject_irq(struct gzvm *gzvm, unsigned int vcpu_idx,
diff --git a/include/uapi/linux/gzvm.h b/include/uapi/linux/gzvm.h
index 1cf89213a383..1fe483ef2ed5 100644
--- a/include/uapi/linux/gzvm.h
+++ b/include/uapi/linux/gzvm.h
@@ -21,6 +21,7 @@ 
 /* query hypervisor supported block-based demand page */
 #define GZVM_CAP_BLOCK_BASED_DEMAND_PAGING	0x9201
 #define GZVM_CAP_ENABLE_DEMAND_PAGING	0x9202
+#define GZVM_CAP_ENABLE_IDLE		0x9203
 
 /* sub-commands put in args[0] for GZVM_CAP_PROTECTED_VM */
 #define GZVM_CAP_PVM_SET_PVMFW_GPA		0
@@ -187,6 +188,7 @@  enum {
 	GZVM_EXIT_SYSTEM_EVENT = 0x92920008,
 	GZVM_EXIT_SHUTDOWN = 0x92920009,
 	GZVM_EXIT_GZ = 0x9292000a,
+	GZVM_EXIT_IDLE = 0x9292000b,
 };
 
 /* exception definitions of GZVM_EXIT_EXCEPTION */