/* * Copyright (C) 2018-2023 Intel Corporation. * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include "vm_event.h" #include "hsm_ioctl_defs.h" #include "sbuf.h" #include "log.h" #include #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; }