From 4919ab2b83e7ccbe7a1ab7f2f6afd51e2a028b97 Mon Sep 17 00:00:00 2001 From: Peng Yang Date: Sun, 3 Jul 2022 03:06:29 -0700 Subject: [PATCH] gunyah: pops: Porting Gunyah Guest Power-Operations driver to Pineapple Add gunyah guest power-operations driver for Pineapple. This is just snapshot of kalama's gunyah power-operations driver from 'commit 5beb98404269 ("Merge "i3c: i3c-master-msm-geni: Fix IBI for invalid device OR handler"")'from msm-5.15 branch. Change-Id: I87f37b3462d1655d5db051ab3ae861d2345c0c2f Signed-off-by: Peng Yang --- drivers/virt/gunyah/Kconfig | 11 ++ drivers/virt/gunyah/Makefile | 1 + drivers/virt/gunyah/gh_guest_pops.c | 227 ++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 drivers/virt/gunyah/gh_guest_pops.c diff --git a/drivers/virt/gunyah/Kconfig b/drivers/virt/gunyah/Kconfig index 9d0cbb336e46..c15a1cc730f5 100644 --- a/drivers/virt/gunyah/Kconfig +++ b/drivers/virt/gunyah/Kconfig @@ -78,4 +78,15 @@ config GH_MEM_NOTIFIER machines, and register callbacks that get invoked only when notifications pertaining to their memory resources arrive. +config GH_GUEST_POPS + tristate "Gunyah Guest Power-Operations driver" + depends on GH_RM_DRV + help + The driver runs on guest VMs and is majorly responsible for coordinating + the guest's power operations. This includes, setting the VM's status, + such as OS_STATUS_* or APPS_STATUS_*. It also acts as an input power key + driver. That is, it listens to the shutdown requests, and when it receives + one, it'll emulate a power key press action by sending an input + notification to user-space. + endif diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile index f68c17fad10f..63baffbaba7e 100644 --- a/drivers/virt/gunyah/Makefile +++ b/drivers/virt/gunyah/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_GH_RM_DRV) += gh_rm_drv.o gh_rm_drv-y += gh_rm_core.o gh_rm_iface.o obj-$(CONFIG_GH_IRQ_LEND) += gh_irq_lend.o obj-$(CONFIG_GH_MEM_NOTIFIER) += gh_mem_notifier.o +obj-$(CONFIG_GH_GUEST_POPS) += gh_guest_pops.o diff --git a/drivers/virt/gunyah/gh_guest_pops.c b/drivers/virt/gunyah/gh_guest_pops.c new file mode 100644 index 000000000000..5638ec1e8776 --- /dev/null +++ b/drivers/virt/gunyah/gh_guest_pops.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +#include + +#define GH_GUEST_POPS_POFF_BUTTON_HOLD_SHUTDOWN_DELAY_MS 1000 +#define GH_GUEST_POPS_POFF_BUTTON_HOLD_RESTART_DELAY_MS 500 + +static struct input_dev *gh_vm_poff_input; +static struct notifier_block rm_nb; + +static struct kobj_type gh_guest_kobj_type = { + .sysfs_ops = &kobj_sysfs_ops, +}; +static struct kobject gh_guest_kobj; + +static int gh_guest_pops_handle_stop_shutdown(u32 stop_reason) +{ + /* Emulate a KEY_POWER event to notify user-space of a shutdown */ + pr_info("Sending KEY_POWER event\n"); + + input_report_key(gh_vm_poff_input, KEY_POWER, 1); + input_sync(gh_vm_poff_input); + + switch (stop_reason) { + case GH_VM_STOP_SHUTDOWN: + msleep(GH_GUEST_POPS_POFF_BUTTON_HOLD_SHUTDOWN_DELAY_MS); + break; + case GH_VM_STOP_RESTART: + msleep(GH_GUEST_POPS_POFF_BUTTON_HOLD_RESTART_DELAY_MS); + break; + } + + input_report_key(gh_vm_poff_input, KEY_POWER, 0); + input_sync(gh_vm_poff_input); + + return 0; +} + +static int gh_guest_pops_handle_stop_crash(void) +{ + panic("Panic requested by Primary-VM\n"); + return 0; +} + +static int +gh_guest_pops_vm_shutdown(struct gh_rm_notif_vm_shutdown_payload *vm_shutdown) +{ + switch (vm_shutdown->stop_reason) { + case GH_VM_STOP_SHUTDOWN: + return gh_guest_pops_handle_stop_shutdown(vm_shutdown->stop_reason); + case GH_VM_STOP_CRASH: + return gh_guest_pops_handle_stop_crash(); + case GH_VM_STOP_RESTART: + return gh_guest_pops_handle_stop_shutdown(vm_shutdown->stop_reason); + } + + return 0; +} + +static int gh_guest_pops_rm_notifer_fn(struct notifier_block *nb, + unsigned long cmd, void *data) +{ + switch (cmd) { + case GH_RM_NOTIF_VM_SHUTDOWN: + return gh_guest_pops_vm_shutdown(data); + } + + return NOTIFY_DONE; +} + +static int __init gh_guest_pops_init_poff(void) +{ + int ret; + + gh_vm_poff_input = input_allocate_device(); + if (!gh_vm_poff_input) + return -ENOMEM; + + input_set_capability(gh_vm_poff_input, EV_KEY, KEY_POWER); + + ret = input_register_device(gh_vm_poff_input); + if (ret) + goto fail_register; + + rm_nb.notifier_call = gh_guest_pops_rm_notifer_fn; + ret = gh_rm_register_notifier(&rm_nb); + if (ret) + goto fail_init; + + return 0; + +fail_init: + input_unregister_device(gh_vm_poff_input); +fail_register: + input_free_device(gh_vm_poff_input); + return ret; +} + +static ssize_t gh_guest_set_app_status(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + int ret = 0; + u16 app_status; + + ret = kstrtou16(buf, 0, &app_status); + if (ret) + return -EINVAL; + + if (app_status != GH_RM_APP_STATUS_TUI_SERVICE_BOOT) + return -EINVAL; + + ret = gh_rm_vm_set_app_status(app_status); + if (ret) { + pr_err("Failed to set the APP status\n"); + return -EINVAL; + } + + return count; +} + +static struct kobj_attribute gh_guest_attribute = +__ATTR(app_status, 0220, NULL, gh_guest_set_app_status); + +static struct attribute *attrs[] = { + &gh_guest_attribute.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +static int gh_guest_sysfs_init(void) +{ + int ret = 0; + + ret = kobject_init_and_add(&gh_guest_kobj, &gh_guest_kobj_type, + kernel_kobj, "%s", "vm_set_status"); + if (ret) { + pr_err("sysfs create and add failed\n"); + ret = -ENOMEM; + goto error_return; + } + + ret = sysfs_create_group(&gh_guest_kobj, &attr_group); + if (ret) { + pr_err("sysfs create group failed %d\n", ret); + goto error_return; + } + + return 0; + +error_return: + if (kobject_name(&gh_guest_kobj) != NULL) { + kobject_del(&gh_guest_kobj); + kobject_put(&gh_guest_kobj); + memset(&gh_guest_kobj, 0, sizeof(gh_guest_kobj)); + } + + return ret; +} + +static void gh_guest_sysfs_cleanup(void) +{ + sysfs_remove_group(&gh_guest_kobj, &attr_group); + kobject_del(&gh_guest_kobj); + kobject_put(&gh_guest_kobj); + memset(&gh_guest_kobj, 0, sizeof(gh_guest_kobj)); +} + +static void gh_guest_pops_exit_poff(void) +{ + gh_rm_unregister_notifier(&rm_nb); + + input_unregister_device(gh_vm_poff_input); + input_free_device(gh_vm_poff_input); +} + +static int __init gh_guest_pops_init(void) +{ + int ret; + + ret = gh_guest_pops_init_poff(); + if (ret) { + pr_err("Failed to initialize guest poweroff driver\n"); + return 0; + } + + ret = gh_guest_sysfs_init(); + if (ret) { + pr_err("Failed to init sysfs of guest_pops\n"); + gh_guest_pops_exit_poff(); + return 0; + } + + ret = gh_rm_vm_set_os_status(GH_RM_OS_STATUS_BOOT); + if (ret) { + pr_err("Failed to set the OS status\n"); + gh_guest_pops_exit_poff(); + gh_guest_sysfs_cleanup(); + } + + return 0; +} +module_init(gh_guest_pops_init); + +static void __exit gh_guest_pops_exit(void) +{ + gh_guest_pops_exit_poff(); + gh_guest_sysfs_cleanup(); +} +module_exit(gh_guest_pops_exit); + +MODULE_LICENSE("GPL");