acrn-hypervisor/misc/cbc_lifecycle/cbc_lifecycle.c

490 lines
13 KiB
C

/*
* Copyright (C) 2018 Intel Corporation
*
* Author: Alek Du <alek.du@intel.com>
*
* 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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <termios.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/tty.h>
#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;
}