/* * Copyright (C) 2019 Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vmmapi.h" #include "acpi.h" #include "mevent.h" #include "monitor.h" #include "log.h" #define POWER_BUTTON_NAME "power_button" #define POWER_BUTTON_ACPI_DRV "/sys/bus/acpi/drivers/button/LNXPWRBN:00/" #define POWER_BUTTON_INPUT_DIR POWER_BUTTON_ACPI_DRV"input" #define POWER_BUTTON_PNP0C0C_DRV "/sys/bus/acpi/drivers/button/PNP0C0C:00/" #define POWER_BUTTON_PNP0C0C_DIR POWER_BUTTON_PNP0C0C_DRV"input" static struct mevent *input_evt0; static int pwrbtn_fd = -1; static bool monitor_run; static void input_event0_handler(int fd, enum ev_type type, void *arg) { struct input_event ev; int rc; rc = read(fd, &ev, sizeof(ev)); if (rc < 0 || rc != sizeof(ev)) return; /* * The input key defines in input-event-codes.h * KEY_POWER 116 SC System Power Down */ if (ev.code == KEY_POWER && ev.value == 1) inject_power_button_event(arg); } static int vm_stop_handler(void *arg) { if (!arg) return -EINVAL; inject_power_button_event(arg); return 0; } static int vm_suspend_handler(void *arg) { /* * Invoke vm_stop_handler directly in here since suspend of User VM is * set by User VM power button setting. */ return vm_stop_handler(arg); } static struct monitor_vm_ops vm_ops = { .stop = vm_stop_handler, .suspend = vm_suspend_handler, }; static int input_dir_filter(const struct dirent *dir) { return !strncmp(dir->d_name, "input", 5); } static int event_dir_filter(const struct dirent *dir) { return !strncmp(dir->d_name, "event", 5); } static int open_power_button_input_device(const char *drv, const char *dir) { struct dirent **input_dirs = NULL; struct dirent **event_dirs = NULL; int ninput = 0; int nevent = 0; char path[256] = {0}; char name[256] = {0}; int rc, fd; if (access(drv, F_OK) != 0) return -1; /* * Scan path to get inputN * path is /sys/bus/acpi/drivers/button/LNXPWRBN:00/input */ ninput = scandir(dir, &input_dirs, input_dir_filter, alphasort); if (ninput < 0) { pr_err("failed to scan power button %s\n", dir); goto err; } else if (ninput == 1) { rc = snprintf(path, sizeof(path), "%s/%s", dir, input_dirs[0]->d_name); if (rc < 0 || rc >= sizeof(path)) { pr_err("failed to set power button path %d\n", rc); goto err_input; } /* * Scan path to get eventN * path is /sys/bus/acpi/drivers/button/LNXPWRBN:00/input/inputN */ nevent = scandir(path, &event_dirs, event_dir_filter, alphasort); if (nevent < 0) { pr_err("failed to get power button event %s\n", path); goto err_input; } else if (nevent == 1) { /* Get the power button input event name */ rc = snprintf(name, sizeof(name), "/dev/input/%s", event_dirs[0]->d_name); if (rc < 0 || rc >= sizeof(name)) { pr_err("power button error %d\n", rc); goto err_input; } } else { pr_err("power button event number error %d\n", nevent); goto err_event; } } else { pr_err("power button input number error %d\n", nevent); goto err_input; } /* Open the input device */ fd = open(name, O_RDONLY); if (fd > 0) pr_info("Watching power button on %s\n", name); while (nevent--) free(event_dirs[nevent]); free(event_dirs); while (ninput--) free(input_dirs[ninput]); free(input_dirs); return fd; err_event: while (nevent--) free(event_dirs[nevent]); free(event_dirs); err_input: while (ninput--) free(input_dirs[ninput]); free(input_dirs); err: return -1; } static int open_native_power_button() { int fd; /* * Open fixed power button firstly, if it can't be opened * try to open control method power button. */ fd = open_power_button_input_device(POWER_BUTTON_ACPI_DRV, POWER_BUTTON_INPUT_DIR); if (fd < 0) return open_power_button_input_device( POWER_BUTTON_PNP0C0C_DRV, POWER_BUTTON_PNP0C0C_DIR); else return fd; } void power_button_init(struct vmctx *ctx) { if (input_evt0 == NULL) { pwrbtn_fd = open_native_power_button(); if (pwrbtn_fd < 0) pr_err("open power button error=%d\n", errno); else input_evt0 = mevent_add(pwrbtn_fd, EVF_READ, input_event0_handler, ctx, NULL, NULL); } /* * Suspend or shutdown User VM by acrnctl suspend and * stop command. */ if (monitor_run == false) { if (monitor_register_vm_ops(&vm_ops, ctx, POWER_BUTTON_NAME) < 0) pr_err("failed to register vm ops for power button\n"); else monitor_run = true; } } void power_button_deinit(struct vmctx *ctx) { if (input_evt0 != NULL) { mevent_delete_close(input_evt0); input_evt0 = NULL; pwrbtn_fd = -1; } }