/* * 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; }