diff --git a/misc/cbc_lifecycle/Makefile b/misc/cbc_lifecycle/Makefile new file mode 100644 index 000000000..1dbcdd879 --- /dev/null +++ b/misc/cbc_lifecycle/Makefile @@ -0,0 +1,12 @@ + +OUT_DIR ?= . + +$(OUT_DIR)/cbc_lifecycle: cbc_lifecycle.c ../../tools/acrn-manager/acrn_mngr.h + gcc -o $@ cbc_lifecycle.c -pthread -L$(OUT_DIR)/../../build/tools -lacrn-mngr + +clean: + rm $(OUT_DIR)/cbc_lifecycle + +install: $(OUT_DIR)/cbc_lifecycle + install -d $(DESTDIR)/usr/bin + install -t $(DESTDIR)/usr/bin $^ diff --git a/misc/cbc_lifecycle/cbc_lifecycle.c b/misc/cbc_lifecycle/cbc_lifecycle.c new file mode 100644 index 000000000..7743cb93f --- /dev/null +++ b/misc/cbc_lifecycle/cbc_lifecycle.c @@ -0,0 +1,489 @@ +/* + * Copyright (C) 2018 Intel Corporation + * + * Author: Alek Du + * + * SPDX-License-identifier: BSD-3-Clause + */ + +/* CBC lifecycle state machine transition flow + * + * .------------------------------------------- + * -------------+-------------- | + * | IOC V IOC | | + * (default) ==> (Active) ==> (shutdown) ==> (shutdown delay) (Off) + * |_____________|__________________| + * _________|________ (ACRN select) ^ + * ACRN/ ACRN| ACRN\ | + * (reboot) (suspend) (shutdown) | + * | | | | + * ------------------------------------------------------ + * + * IOC: state transition due to cbc-lifecycle wakeup reason + * ACRN: state transition due to ACRND IPC request + */ + +/* Basic cbc-lifecycle workflow on Service OS + * ____________________ + * | sos lifecycle | + * | service | + * | | + * |*incoming request |(Listen on server socket @ /run/acrn/sos-lcs.socket) + * | wakeup reason | + * | shutdown | + * | reboot |(Requests from ACRN VM manager) + * | suspend | + * | RTC set wakeup | + * | | + * |*out events |(Send to ACRN VM manager @ /run/acrn/acrnd.socket) + * | vm start | + * | vm stop |(Events from /dev/cbc-lifecycle port) + * ~~~~~~~~~~~~~~~~~~~~ + * + * 3 threads: 1. wake on /dev/cbc-lifecycle and receive wakeup reasons + * 2. heartbeat thread to send heartbeat msg to /dev/cbc-lifecycle + * 3. server socket handler thread to handle incoming msg + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../tools/acrn-manager/acrn_mngr.h" + +static char cbcd_name[] = "sos-lcs"; +static char acrnd_name[] = "acrnd"; +static char cbc_lifecycle_dev[] = "/dev/cbc-lifecycle"; + +typedef enum { + S_DEFAULT = 0, /* default, not receiving any status */ + S_ALIVE = 1, /* receive wakeup_reason bit 0~22 not all 0 */ + S_SHUTDOWN, /* receive wakeup_reason bit 0~22 off 23 on */ + S_SHUTDOWN_DELAY, /* before ACRND confirm off, sent delay to IOC */ + S_ACRND_SHUTDOWN, /* ACRND confirm ioc can off */ + S_IOC_SHUTDOWN, /* receiving wakeup_reason bit 0~23 off */ + S_ACRND_REBOOT, /* receive request from ACRND to go reboot */ + S_ACRND_SUSPEND, /* receive request from ACRND to go S3 */ + S_MAX, +} state_machine_t; + +/* state machine valid transition table */ +char valid_map[S_MAX][S_MAX] = { + { 1, 1, 1, 0, 0, 0, 0, 0 }, /* default can go alive or shutdown */ + { 0, 1, 1, 0, 1, 0, 1, 1 }, /* alive can go S_ACRND_* state */ + { 0, 0, 1, 1, 1, 1, 1, 1 }, /* shutdown can go upper states */ + { 0, 0, 0, 1, 1, 1, 1, 1 }, /* delay can go upper states */ + { 0, 0, 0, 0, 1, 1, 0, 0 }, /* acrnd shutdown can go ioc_shutdown */ + { 0, 1, 0, 0, 0, 1, 0, 0 }, /* ioc_shutdown can go alive (s3 case) */ + { 0, 0, 0, 0, 0, 1, 1, 0 }, /* acrnd_reboot can only go ioc_shutdown */ + { 0, 1, 0, 0, 0, 1, 0, 1 }, /* acrnd_suspend can go alive/ioc_shutdown */ +}; + +const char *state_name[] = { + "default", + "keep_alive", + "shutdown", + "shutdown_delay", + "acrnd_shutdown", + "ioc_shutdown", + "acrnd_reboot", + "acrnd_suspend", +}; + +static state_machine_t state; +static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER; + +typedef struct { + uint8_t header; + uint8_t wakeup[3]; +} __attribute__((packed)) wakeup_reason_frame; + +typedef pthread_t cbc_thread_t; +typedef void* (*cbc_thread_func_t)(void *arg); + +/* cbc suppress heartbeat is not used so far, keep here for future use */ +static char cbc_suppress_heartbeat_1min[] = {0x04, 0x60, 0xEA, 0x00}; +static char cbc_suppress_heartbeat_5min[] = {0x04, 0xE0, 0x93, 0x04}; +static char cbc_suppress_heartbeat_10min[] = {0x04, 0xC0, 0x27, 0x09}; +static char cbc_suppress_heartbeat_30min[] = {0x04, 0x40, 0x77, 0x1B}; +static char cbc_heartbeat_shutdown[] = {0x02, 0x00, 0x01, 0x00}; +static char cbc_heartbeat_reboot[] = {0x02, 0x00, 0x02, 0x00}; +/* hearbeat shutdown ignore number is not used so far for non-native case */ +static char cbc_heartbeat_shutdown_ignore_1[] = {0x02, 0x00, 0x03, 0x00}; +static char cbc_heartbeat_reboot_ignore_1[] = {0x02, 0x00, 0x04, 0x00}; +static char cbc_heartbeat_shutdown_ignore_2[] = {0x02, 0x00, 0x05, 0x00}; +static char cbc_heartbeat_reboot_ignore_2[] = {0x02, 0x00, 0x06, 0x00}; +static char cbc_heartbeat_s3[] = {0x02, 0x00, 0x07, 0x00}; +static char cbc_heartbeat_active[] = {0x02, 0x01, 0x00, 0x00}; +static char cbc_heartbeat_shutdown_delay[] = {0x02, 0x02, 0x00, 0x00}; +static char cbc_heartbeat_init[] = {0x02, 0x03, 0x00, 0x00}; + +static int cbc_lifecycle_fd = -1; + +static int wakeup_reason; + +static void wait_for_device(const char *dev_name) +{ + int loop = 360; // 180 seconds + if (dev_name == NULL) + return; + + while (loop--) { + if (access(dev_name, F_OK)) { + fprintf(stderr, "waiting for %s\n", dev_name); + usleep(500000); + continue; + } + break; + } +} + +static int open_cbc_device(const char *cbc_device) +{ + int fd; + wait_for_device(cbc_device); + + if ((fd = open(cbc_device, O_RDWR | O_NOCTTY)) < 0) { + fprintf(stderr, "%s failed to open %s\n", __func__, cbc_device); + return -1; + } + return fd; +} + +static void close_cbc_device(int fd) +{ + close(fd); +} + +static int cbc_send_data(int fd, const char *payload, int len) +{ + int ret = 0; + + while (1) { + ret = write(fd, payload, len); + if (ret <= 0) { + if (errno == EDQUOT) { + usleep(1000); + break; + } else if (errno == EINTR) { + continue; + } else { + fprintf(stderr, "%s issue : %d", __func__, + errno); + break; + } + } + break; + } + return ret; +} + +static int cbc_read_data(int fd, char *payload, int len) +{ + int nbytes = 0; + + while (1) { + nbytes = read(fd, payload, len); + + if (nbytes < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "%s issue %d", __func__, errno); + usleep(5000); + return 0; + } + break; + } + return nbytes; +} + +static __inline__ int cbc_thread_create(cbc_thread_t *pthread, + cbc_thread_func_t start, void *arg) +{ + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + return pthread_create(pthread, (const pthread_attr_t *)&attr, + start, arg); +} + +state_machine_t get_state(void) +{ + state_machine_t _state; + pthread_mutex_lock(&state_mutex); + _state = state; + pthread_mutex_unlock(&state_mutex); + return _state; +} + +state_machine_t state_transit(state_machine_t new) +{ + state_machine_t _state; + pthread_mutex_lock(&state_mutex); + + _state = state; + if (valid_map[state][new]) { + state = new; + pthread_mutex_unlock(&state_mutex); + if (_state != new) { + fprintf(stderr, "transit (%s to %s)\n", + state_name[_state], state_name[new]); + _state = new; + } + } else { + pthread_mutex_unlock(&state_mutex); + } + return _state; +} + +static int send_acrnd_stop(void); +static int send_acrnd_start(void); + +void *cbc_heartbeat_loop(void) +{ + state_machine_t last_state = S_DEFAULT; + const int p_size = sizeof(cbc_heartbeat_init); + cbc_send_data(cbc_lifecycle_fd, cbc_heartbeat_init, p_size); + fprintf(stderr, "send heartbeat init\n"); + while (1) { + char *heartbeat; + state_machine_t cur_state = get_state(); + + switch (cur_state) { + case S_DEFAULT: + heartbeat = NULL; + break; + case S_ALIVE: + if (last_state != S_ALIVE) { + send_acrnd_start(); + } + heartbeat = cbc_heartbeat_active; + break; + case S_SHUTDOWN: + /* when ACRND detects our off request, we must wait if + * UOS really accept the requst, thus we send shutdown + * delay */ + send_acrnd_stop(); + cur_state = state_transit(S_SHUTDOWN_DELAY); + // falling through + case S_SHUTDOWN_DELAY: + heartbeat = cbc_heartbeat_shutdown_delay; + break; + case S_ACRND_SHUTDOWN: + heartbeat = cbc_heartbeat_shutdown; + break; + case S_ACRND_REBOOT: + heartbeat = cbc_heartbeat_reboot; + break; + case S_ACRND_SUSPEND: + heartbeat = cbc_heartbeat_s3; + break; + case S_IOC_SHUTDOWN: + if (last_state == S_ACRND_SHUTDOWN) + system("shutdown 0"); + else if (last_state == S_ACRND_REBOOT) + system("reboot"); + else if (last_state == S_ACRND_SUSPEND) + system("echo mem > /sys/power/state"); + //no heartbeat sent from now + heartbeat = NULL; + break; + } + if (heartbeat) { + cbc_send_data(cbc_lifecycle_fd, heartbeat, p_size); + fprintf(stderr, "."); + } + last_state = cur_state; + /* delay 1 second to send next heart beat */ + sleep(1); + } + return NULL; +} + +void *cbc_wakeup_reason_thread(void *arg) +{ + wakeup_reason_frame data; + int len; + + while (1) { + len = cbc_read_data(cbc_lifecycle_fd, (uint8_t *)&data, sizeof(data)); + if (len > 0) { + if (data.header != 1) { + fprintf(stderr, "received wrong wakeup reason"); + continue; + } + wakeup_reason = data.wakeup[0] | data.wakeup[1] << 8 | data.wakeup[2] << 16; + if (!wakeup_reason) + state_transit(S_IOC_SHUTDOWN); + else if (!(wakeup_reason & ~(1 << 23))) + state_transit(S_SHUTDOWN); + else + state_transit(S_ALIVE); + } + } + return NULL; +} + +static int cbcd_fd; + +static void handle_shutdown(struct mngr_msg *msg, int client_fd, void *param) +{ + struct req_power_state *req = (void *)msg; + struct ack_power_state ack; + + memcpy(&ack.msg, &req->msg, sizeof(req->msg)); + ack.msg.len = sizeof(ack); + ack.err = 0; + + fprintf(stderr, "acrnd agreed to shutdown\n"); + state_transit(S_ACRND_SHUTDOWN); + mngr_send_msg(client_fd, &ack.msg, NULL, 0, 0); +} + +static void handle_suspend(struct mngr_msg *msg, int client_fd, void *param) +{ + struct req_power_state *req = (void *)msg; + struct ack_power_state ack; + + memcpy(&ack.msg, &req->msg, sizeof(req->msg)); + ack.msg.len = sizeof(ack); + ack.err = 0; + + state_transit(S_ACRND_SUSPEND); + mngr_send_msg(client_fd, &ack.msg, NULL, 0, 0); +} + +static void handle_reboot(struct mngr_msg *msg, int client_fd, void *param) +{ + struct req_power_state *req = (void *)msg; + struct ack_power_state ack; + + memcpy(&ack.msg, &req->msg, sizeof(req->msg)); + ack.msg.len = sizeof(ack); + ack.err = 0; + + state_transit(S_ACRND_REBOOT); + mngr_send_msg(client_fd, &ack.msg, NULL, 0, 0); +} + +static void handle_wakeup_reason(struct mngr_msg *msg, int client_fd, void *param) +{ + struct req_wakeup_reason *req = (void *)msg; + struct ack_wakeup_reason ack; + + memcpy(&ack.msg, &req->msg, sizeof(req->msg)); + ack.msg.len = sizeof(ack); + ack.reason = wakeup_reason; + mngr_send_msg(client_fd, &ack.msg, NULL, 0, 0); +} + +static void handle_rtc(struct mngr_msg *msg, int client_fd, void *param) +{ + struct req_rtc_timer *req = (void *)msg; + struct ack_rtc_timer ack; + + memcpy(&ack.msg, &req->msg, sizeof(req->msg)); + ack.msg.len = sizeof(ack); + + fprintf(stderr, "%s request rtc timer at %lu, result will be %d\n", + req->vmname, req->t, ack.err); + /* Need wait IOC firmware to support RTC */ + ack.err = -1; + + mngr_send_msg(client_fd, &ack.msg, NULL, 0, 0); +} + +static int send_acrnd_start(void) +{ + int acrnd_fd; + int ret; + struct req_acrnd_resume req = { + .msg = { + .msgid = ACRND_RESUME, + .magic = MNGR_MSG_MAGIC, + .len = sizeof (req), + } + }; + struct ack_acrnd_resume ack; + + req.msg.timestamp = time(NULL); + acrnd_fd = mngr_open_un(acrnd_name, MNGR_CLIENT); + if (acrnd_fd < 0) { + fprintf(stderr, "cannot open %s socket\n", acrnd_name); + return -1; + } + ret = mngr_send_msg(acrnd_fd, &req.msg, &ack.msg, sizeof(ack), 2); + if (ret > 0) + fprintf(stderr, "result %d\n", ack.err); + mngr_close(acrnd_fd); + return ret; +} + +static int send_acrnd_stop(void) +{ + int acrnd_fd; + int ret; + struct req_acrnd_stop req = { + .msg = { + .msgid = ACRND_STOP, + .magic = MNGR_MSG_MAGIC, + .len = sizeof (req), + }, + .force = 0, + .timeout = 20, + }; + struct ack_acrnd_stop ack; + + req.msg.timestamp = time(NULL); + acrnd_fd = mngr_open_un(acrnd_name, MNGR_CLIENT); + if (acrnd_fd < 0) { + fprintf(stderr, "cannot open %s socket\n", acrnd_name); + return -1; + } + ret = mngr_send_msg(acrnd_fd, &req.msg, &ack.msg, sizeof(ack), 2); + if (ret > 0) + fprintf(stderr, "result %d\n", ack.err); + return ret; +} + +int main(void) +{ + cbc_thread_t wakeup_reason_thread_ptr; + + cbc_lifecycle_fd = open_cbc_device(cbc_lifecycle_dev); + if (cbc_lifecycle_fd < 0) + goto err_cbc; + /* the handle_* function may reply on a close fd, since the client + * can close the client_fd and ignore the ack */ + signal(SIGPIPE, SIG_IGN); + cbcd_fd = mngr_open_un(cbcd_name, MNGR_SERVER); + if (cbcd_fd < 0) { + fprintf(stderr, "cannot open %s socket\n", cbcd_name); + goto err_un; + } + cbc_thread_create(&wakeup_reason_thread_ptr, cbc_wakeup_reason_thread, + NULL); // thread to handle wakeup_reason Rx data + + mngr_add_handler(cbcd_fd, WAKEUP_REASON, handle_wakeup_reason, NULL); + mngr_add_handler(cbcd_fd, RTC_TIMER, handle_rtc, NULL); + mngr_add_handler(cbcd_fd, SHUTDOWN, handle_shutdown, NULL); + mngr_add_handler(cbcd_fd, SUSPEND, handle_suspend, NULL); + mngr_add_handler(cbcd_fd, REBOOT, handle_reboot, NULL); + cbc_heartbeat_loop(); + // shouldn't be here + mngr_close(cbcd_fd); +err_un: + close_cbc_device(cbc_lifecycle_fd); +err_cbc: + return 0; +}