ioc: add cbc_lifecycle service

The cbc_lifecycle service will listen on /dev/cbc-lifecycle tty port to
receive "wakeup reason" information from ioc controller as well as send
heartbeat to ioc to set different power cycle method.

The RTC funtionality is not implemented by current ioc firmware yet,
hence the service leaves it as an empty handler now.

Signed-off-by: Alek Du <alek.du@intel.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
This commit is contained in:
Alek Du 2018-05-24 15:53:32 +08:00 committed by lijinxia
parent 247c5eeb22
commit c6d8e7fd73
2 changed files with 501 additions and 0 deletions

View File

@ -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 $^

View File

@ -0,0 +1,489 @@
/*
* 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;
}