511 lines
13 KiB
C
511 lines
13 KiB
C
/*
|
|
* Copyright (C) 2018-2023 Intel Corporation.
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/queue.h>
|
|
#include <pthread.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/eventfd.h>
|
|
#include <acrn_common.h>
|
|
|
|
#include "vm_event.h"
|
|
#include "hsm_ioctl_defs.h"
|
|
#include "sbuf.h"
|
|
#include "log.h"
|
|
#include <cjson/cJSON.h>
|
|
#include "monitor.h"
|
|
#include "timer.h"
|
|
|
|
#define VM_EVENT_ELE_SIZE (sizeof(struct vm_event))
|
|
|
|
#define HV_VM_EVENT_TUNNEL 0
|
|
#define DM_VM_EVENT_TUNNEL 1
|
|
#define MAX_VM_EVENT_TUNNELS 2
|
|
#define MAX_EPOLL_EVENTS MAX_VM_EVENT_TUNNELS
|
|
|
|
#define THROTTLE_WINDOW 1U /* time window for throttle counter, in secs*/
|
|
|
|
#define BROKEN_TIME ((time_t)-1)
|
|
|
|
typedef void (*vm_event_handler)(struct vmctx *ctx, struct vm_event *event);
|
|
typedef void (*vm_event_generate_jdata)(cJSON *event_obj, struct vm_event *event);
|
|
|
|
static int epoll_fd;
|
|
static bool started = false;
|
|
static char hv_vm_event_page[4096] __aligned(4096);
|
|
static char dm_vm_event_page[4096] __aligned(4096);
|
|
static pthread_t vm_event_tid;
|
|
|
|
static void general_event_handler(struct vmctx *ctx, struct vm_event *event);
|
|
static void rtc_chg_event_handler(struct vmctx *ctx, struct vm_event *event);
|
|
|
|
static void gen_rtc_chg_jdata(cJSON *event_obj, struct vm_event *event);
|
|
|
|
enum event_source_type {
|
|
EVENT_SOURCE_TYPE_HV,
|
|
EVENT_SOURCE_TYPE_DM,
|
|
};
|
|
|
|
struct vm_event_tunnel {
|
|
enum event_source_type type;
|
|
struct shared_buf *sbuf;
|
|
uint32_t sbuf_size;
|
|
int kick_fd;
|
|
pthread_mutex_t mtx;
|
|
bool enabled;
|
|
};
|
|
|
|
struct event_throttle_ctl {
|
|
struct acrn_timer timer;
|
|
pthread_mutex_t mtx;
|
|
uint32_t event_counter;
|
|
uint32_t throttle_count; /* how many events has been throttled(dropped) */
|
|
bool is_up;
|
|
};
|
|
|
|
struct vm_event_proc {
|
|
vm_event_handler ve_handler;
|
|
uint32_t throttle_rate; /* how many events allowed per sec */
|
|
struct event_throttle_ctl throttle_ctl;
|
|
vm_event_generate_jdata gen_jdata_handler; /* how to transtfer vm_event data to json txt */
|
|
};
|
|
|
|
static struct vm_event_proc ve_proc[VM_EVENT_COUNT] = {
|
|
[VM_EVENT_RTC_CHG] = {
|
|
.ve_handler = rtc_chg_event_handler,
|
|
.gen_jdata_handler = gen_rtc_chg_jdata,
|
|
.throttle_rate = 1,
|
|
},
|
|
[VM_EVENT_POWEROFF] = {
|
|
.ve_handler = general_event_handler,
|
|
.gen_jdata_handler = NULL,
|
|
.throttle_rate = 1,
|
|
},
|
|
[VM_EVENT_TRIPLE_FAULT] = {
|
|
.ve_handler = general_event_handler,
|
|
.gen_jdata_handler = NULL,
|
|
.throttle_rate = 1,
|
|
},
|
|
};
|
|
|
|
static inline struct vm_event_proc *get_vm_event_proc(struct vm_event *event)
|
|
{
|
|
struct vm_event_proc *proc = NULL;
|
|
if (event->type < VM_EVENT_COUNT) {
|
|
proc = &ve_proc[event->type];
|
|
}
|
|
return proc;
|
|
}
|
|
|
|
static bool event_throttle(struct vm_event *event)
|
|
{
|
|
struct vm_event_proc *proc;
|
|
struct event_throttle_ctl *ctl;
|
|
uint32_t current_rate;
|
|
bool ret = false;
|
|
|
|
proc = get_vm_event_proc(event);
|
|
if (proc) {
|
|
ctl = &proc->throttle_ctl;
|
|
if (ctl->is_up) {
|
|
pthread_mutex_lock(&ctl->mtx);
|
|
current_rate = ctl->event_counter / THROTTLE_WINDOW;
|
|
if (current_rate < proc->throttle_rate) {
|
|
ctl->event_counter++;
|
|
ret = false;
|
|
} else {
|
|
ret = true;
|
|
ctl->throttle_count++;
|
|
pr_notice("event %d throttle: %d dropped\n",
|
|
event->type, ctl->throttle_count);
|
|
}
|
|
pthread_mutex_unlock(&ctl->mtx);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void throttle_timer_cb(void *arg, uint64_t nexp)
|
|
{
|
|
struct event_throttle_ctl *ctl = (struct event_throttle_ctl *)arg;
|
|
pthread_mutex_lock(&ctl->mtx);
|
|
ctl->event_counter = 0;
|
|
pthread_mutex_unlock(&ctl->mtx);
|
|
}
|
|
|
|
static void vm_event_throttle_init(struct vmctx *ctx)
|
|
{
|
|
int i;
|
|
struct event_throttle_ctl *ctl;
|
|
int ret = 0;
|
|
struct itimerspec timer_spec;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ve_proc); i++) {
|
|
ctl = &ve_proc[i].throttle_ctl;
|
|
ctl->event_counter = 0U;
|
|
ctl->throttle_count = 0U;
|
|
ctl->is_up = false;
|
|
pthread_mutex_init(&ctl->mtx, NULL);
|
|
ctl->timer.clockid = CLOCK_MONOTONIC;
|
|
ret = acrn_timer_init(&ctl->timer, throttle_timer_cb, ctl);
|
|
if (ret < 0) {
|
|
pr_warn("failed to create timer for vm_event %d, throttle disabled\n", i);
|
|
continue;
|
|
}
|
|
timer_spec.it_value.tv_sec = THROTTLE_WINDOW;
|
|
timer_spec.it_value.tv_nsec = 0;
|
|
timer_spec.it_interval.tv_sec = THROTTLE_WINDOW;
|
|
timer_spec.it_interval.tv_nsec = 0;
|
|
ret = acrn_timer_settime(&ctl->timer, &timer_spec);
|
|
if (ret < 0) {
|
|
pr_warn("failed to set timer for vm_event %d, throttle disabled\n", i);
|
|
continue;
|
|
}
|
|
ctl->is_up = true;
|
|
}
|
|
}
|
|
|
|
static void vm_event_throttle_deinit(void)
|
|
{
|
|
int i;
|
|
struct event_throttle_ctl *ctl;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ve_proc); i++) {
|
|
ctl = &ve_proc[i].throttle_ctl;
|
|
if (ctl->timer.fd != -1) {
|
|
acrn_timer_deinit(&ctl->timer);
|
|
}
|
|
}
|
|
}
|
|
|
|
static char *generate_vm_event_message(struct vm_event *event)
|
|
{
|
|
char *event_msg = NULL;
|
|
cJSON *val;
|
|
cJSON *event_obj = cJSON_CreateObject();
|
|
struct vm_event_proc *proc;
|
|
|
|
if (event_obj == NULL)
|
|
return NULL;
|
|
val = cJSON_CreateNumber(event->type);
|
|
if (val == NULL)
|
|
return NULL;
|
|
cJSON_AddItemToObject(event_obj, "vm_event", val);
|
|
|
|
proc = get_vm_event_proc(event);
|
|
if (proc && proc->gen_jdata_handler) {
|
|
(proc->gen_jdata_handler)(event_obj, event);
|
|
}
|
|
|
|
event_msg = cJSON_Print(event_obj);
|
|
if (event_msg == NULL)
|
|
fprintf(stderr, "Failed to generate vm_event message.\n");
|
|
|
|
cJSON_Delete(event_obj);
|
|
|
|
return event_msg;
|
|
}
|
|
|
|
static void emit_vm_event(struct vmctx *ctx, struct vm_event *event)
|
|
{
|
|
if (!event_throttle(event)) {
|
|
char *msg = generate_vm_event_message(event);
|
|
if (msg != NULL) {
|
|
vm_monitor_send_vm_event(msg);
|
|
free(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void general_event_handler(struct vmctx *ctx, struct vm_event *event)
|
|
{
|
|
emit_vm_event(ctx, event);
|
|
}
|
|
|
|
static void gen_rtc_chg_jdata(cJSON *event_obj, struct vm_event *event)
|
|
{
|
|
struct rtc_change_event_data *data = (struct rtc_change_event_data *)event->event_data;
|
|
cJSON *val;
|
|
|
|
val = cJSON_CreateNumber(data->delta_time);
|
|
if (val != NULL) {
|
|
cJSON_AddItemToObject(event_obj, "delta_time", val);
|
|
}
|
|
val = cJSON_CreateNumber(data->last_time);
|
|
if (val != NULL) {
|
|
cJSON_AddItemToObject(event_obj, "last_time", val);
|
|
}
|
|
}
|
|
|
|
/* assume we only have one unique rtc source */
|
|
|
|
static struct acrn_timer rtc_chg_event_timer = {
|
|
.clockid = CLOCK_MONOTONIC,
|
|
};
|
|
static pthread_mutex_t rtc_chg_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static struct timespec time_window_start;
|
|
static time_t last_time_cached = BROKEN_TIME;
|
|
static time_t delta_time_sum = 0;
|
|
#define RTC_CHG_WAIT_TIME 1 /* 1 second */
|
|
static void rtc_chg_event_handler(struct vmctx *ctx, struct vm_event *event)
|
|
{
|
|
struct itimerspec timer_spec;
|
|
struct rtc_change_event_data *data = (struct rtc_change_event_data *)event->event_data;
|
|
|
|
/*
|
|
* RTC time is not reliable until guest finishes updating all RTC date/time regs.
|
|
* So wait for some time, if no more change happens, we can conclude that the RTC
|
|
* change has been done.
|
|
*/
|
|
timer_spec.it_value.tv_sec = RTC_CHG_WAIT_TIME;
|
|
timer_spec.it_value.tv_nsec = 0;
|
|
timer_spec.it_interval.tv_sec = 0;
|
|
timer_spec.it_interval.tv_nsec = 0;
|
|
pthread_mutex_lock(&rtc_chg_mutex);
|
|
if (last_time_cached == BROKEN_TIME) {
|
|
last_time_cached = data->last_time;
|
|
}
|
|
delta_time_sum += data->delta_time;
|
|
/* The last timer will be overwriten if it is not triggered yet. */
|
|
acrn_timer_settime(&rtc_chg_event_timer, &timer_spec);
|
|
clock_gettime(CLOCK_MONOTONIC, &time_window_start);
|
|
pthread_mutex_unlock(&rtc_chg_mutex);
|
|
}
|
|
|
|
static void rtc_chg_timer_cb(void *arg, uint64_t nexp)
|
|
{
|
|
struct timespec now, delta;
|
|
struct timespec time_window_size = {RTC_CHG_WAIT_TIME, 0};
|
|
struct vmctx *ctx = arg;
|
|
struct vm_event send_event;
|
|
struct rtc_change_event_data *data = (struct rtc_change_event_data *)send_event.event_data;
|
|
|
|
pthread_mutex_lock(&rtc_chg_mutex);
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
delta = now;
|
|
timespecsub(&delta, &time_window_start);
|
|
/* possible racing problem here. make sure this is the right timer cb for the vm_event */
|
|
if (timespeccmp(&delta, &time_window_size, >=)) {
|
|
data->delta_time = delta_time_sum;
|
|
data->last_time = last_time_cached;
|
|
emit_vm_event(ctx, &send_event);
|
|
last_time_cached = BROKEN_TIME;
|
|
delta_time_sum = 0;
|
|
}
|
|
pthread_mutex_unlock(&rtc_chg_mutex);
|
|
}
|
|
|
|
static void *vm_event_thread(void *param)
|
|
{
|
|
int n, i;
|
|
struct vm_event ve;
|
|
eventfd_t val;
|
|
struct vm_event_tunnel *tunnel;
|
|
struct vmctx *ctx = param;
|
|
|
|
struct epoll_event eventlist[MAX_EPOLL_EVENTS];
|
|
|
|
while (started) {
|
|
n = epoll_wait(epoll_fd, eventlist, MAX_EPOLL_EVENTS, -1);
|
|
if (n < 0) {
|
|
if (errno != EINTR) {
|
|
pr_err("%s: epoll failed %d\n", __func__, errno);
|
|
}
|
|
continue;
|
|
}
|
|
for (i = 0; i < n; i++) {
|
|
if (i < MAX_EPOLL_EVENTS) {
|
|
tunnel = eventlist[i].data.ptr;
|
|
eventfd_read(tunnel->kick_fd, &val);
|
|
if (tunnel && tunnel->enabled) {
|
|
while (!sbuf_is_empty(tunnel->sbuf)) {
|
|
struct vm_event_proc *proc;
|
|
sbuf_get(tunnel->sbuf, (uint8_t*)&ve);
|
|
pr_dbg("%ld vm event from%d %d\n", val, tunnel->type, ve.type);
|
|
proc = get_vm_event_proc(&ve);
|
|
if (proc && proc->ve_handler) {
|
|
(proc->ve_handler)(ctx, &ve);
|
|
} else {
|
|
pr_warn("%s: unhandled vm event type %d\n", __func__, ve.type);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct vm_event_tunnel ve_tunnel[MAX_VM_EVENT_TUNNELS] = {
|
|
{
|
|
.type = EVENT_SOURCE_TYPE_HV,
|
|
.sbuf = (struct shared_buf *)hv_vm_event_page,
|
|
.sbuf_size = 4096,
|
|
.enabled = false,
|
|
},
|
|
{
|
|
.type = EVENT_SOURCE_TYPE_DM,
|
|
.sbuf = (struct shared_buf *)dm_vm_event_page,
|
|
.sbuf_size = 4096,
|
|
.enabled = false,
|
|
},
|
|
};
|
|
|
|
static int create_event_tunnel(struct vmctx *ctx, struct vm_event_tunnel *tunnel, int epoll_fd)
|
|
{
|
|
struct epoll_event ev;
|
|
enum event_source_type type = tunnel->type;
|
|
struct shared_buf *sbuf = tunnel->sbuf;
|
|
int kick_fd = -1;
|
|
int error;
|
|
|
|
sbuf_init(sbuf, tunnel->sbuf_size, VM_EVENT_ELE_SIZE);
|
|
|
|
if (type == EVENT_SOURCE_TYPE_HV) {
|
|
error = ioctl(ctx->fd, ACRN_IOCTL_SETUP_VM_EVENT_RING, sbuf);
|
|
if (error) {
|
|
pr_err("%s: Setting vm_event ring failed %d\n", __func__, error);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
kick_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
|
|
if (kick_fd < 0) {
|
|
pr_err("%s: eventfd failed %d\n", __func__, errno);
|
|
goto out;
|
|
}
|
|
|
|
if (type == EVENT_SOURCE_TYPE_HV) {
|
|
error = ioctl(ctx->fd, ACRN_IOCTL_SETUP_VM_EVENT_FD, kick_fd);
|
|
if (error) {
|
|
pr_err("%s: Setting vm_event fd failed %d\n", __func__, error);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ev.events = EPOLLIN;
|
|
ev.data.ptr = tunnel;
|
|
error = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, kick_fd, &ev);
|
|
if (error < 0) {
|
|
pr_err("%s: failed to add fd, error is %d\n", __func__, errno);
|
|
goto out;
|
|
}
|
|
|
|
tunnel->kick_fd = kick_fd;
|
|
pthread_mutex_init(&tunnel->mtx, NULL);
|
|
tunnel->enabled = true;
|
|
|
|
return 0;
|
|
|
|
out:
|
|
if (kick_fd >= 0) {
|
|
close(kick_fd);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void destory_event_tunnel(struct vm_event_tunnel *tunnel)
|
|
{
|
|
if (tunnel->enabled) {
|
|
close(tunnel->kick_fd);
|
|
tunnel->enabled = false;
|
|
pthread_mutex_destroy(&tunnel->mtx);
|
|
}
|
|
}
|
|
|
|
int vm_event_init(struct vmctx *ctx)
|
|
{
|
|
int error;
|
|
|
|
epoll_fd = epoll_create1(0);
|
|
if (epoll_fd < 0) {
|
|
pr_err("%s: failed to create epoll %d\n", __func__, errno);
|
|
goto out;
|
|
}
|
|
|
|
error = create_event_tunnel(ctx, &ve_tunnel[HV_VM_EVENT_TUNNEL], epoll_fd);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
|
|
error = create_event_tunnel(ctx, &ve_tunnel[DM_VM_EVENT_TUNNEL], epoll_fd);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
|
|
vm_event_throttle_init(ctx);
|
|
|
|
error = pthread_create(&vm_event_tid, NULL, vm_event_thread, ctx);
|
|
if (error) {
|
|
pr_err("%s: vm_event create failed %d\n", __func__, errno);
|
|
goto out;
|
|
}
|
|
|
|
acrn_timer_init(&rtc_chg_event_timer, rtc_chg_timer_cb, ctx);
|
|
|
|
started = true;
|
|
return 0;
|
|
|
|
out:
|
|
if (epoll_fd >= 0) {
|
|
close(epoll_fd);
|
|
}
|
|
destory_event_tunnel(&ve_tunnel[HV_VM_EVENT_TUNNEL]);
|
|
destory_event_tunnel(&ve_tunnel[DM_VM_EVENT_TUNNEL]);
|
|
return -1;
|
|
}
|
|
|
|
int vm_event_deinit(void)
|
|
{
|
|
void *jval;
|
|
|
|
if (started) {
|
|
started = false;
|
|
vm_event_throttle_deinit();
|
|
pthread_kill(vm_event_tid, SIGCONT);
|
|
pthread_join(vm_event_tid, &jval);
|
|
close(epoll_fd);
|
|
destory_event_tunnel(&ve_tunnel[HV_VM_EVENT_TUNNEL]);
|
|
destory_event_tunnel(&ve_tunnel[DM_VM_EVENT_TUNNEL]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Send a dm generated vm_event by putting it to sbuf.
|
|
* A thread will receive and process those events.
|
|
* Events will be dropped if sbuf is full.
|
|
* They also maight be dropped due to event throttle control in receive thread.
|
|
*/
|
|
int dm_send_vm_event(struct vm_event *event)
|
|
{
|
|
struct vm_event_tunnel *tunnel = &ve_tunnel[DM_VM_EVENT_TUNNEL];
|
|
struct shared_buf *sbuf;
|
|
int32_t ret = -1;
|
|
uint32_t size_sent;
|
|
|
|
if (!tunnel->enabled) {
|
|
return -1;
|
|
}
|
|
sbuf = tunnel->sbuf;
|
|
|
|
if (sbuf != NULL) {
|
|
pthread_mutex_lock(&tunnel->mtx);
|
|
size_sent = sbuf_put(sbuf, (uint8_t *)event, sizeof(*event));
|
|
pthread_mutex_unlock(&tunnel->mtx);
|
|
if (size_sent == VM_EVENT_ELE_SIZE) {
|
|
eventfd_write(tunnel->kick_fd, 1UL);
|
|
ret = 0;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|