Skip to content

Commit

Permalink
dm: vm_event: add support for RTC change event
Browse files Browse the repository at this point in the history
When a guest OS performs an RTC change action, we wish this event be
captured by developers, and then they can decide what to do with it.
(e.g., whether to change physical RTC)

There are some facts that makes RTC change event a bit complicated:
- There are 7 RTC date/time regs (year, month…). They can only be
  updated one by one.
- RTC time is not reliable before date/time update is finished.
- Guests can update RTC date/time regs in any order.
- Guests may update RTC date/time regs during either RTC halted or not
  halted.

A single date/time update event is not reliable. We have to wait for
the guest to finish the update process. So the DM's event handler
sets up a timer, and wait for some time (1 second). If no more change
happens befor the timer expires, we can conclude that the RTC
change has been done. Then the rtc change event is emitted.

This logic of event handler can be used to process HV vrtc time change
event too.

Tracked-On: projectacrn#8547
Signed-off-by: Wu Zhou <wu.zhou@intel.com>
Reviewed-by: Jian Jun Chen <jian.jun.chen@intel.com>
  • Loading branch information
izhouwu committed Jan 19, 2024
1 parent 73422a2 commit ac6505c
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 6 deletions.
88 changes: 84 additions & 4 deletions devicemodel/core/vm_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@

#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);

Expand All @@ -43,6 +45,9 @@ 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,
Expand Down Expand Up @@ -70,14 +75,13 @@ 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;
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 = general_event_handler,
.gen_jdata_handler = NULL,
.ve_handler = rtc_chg_event_handler,
.gen_jdata_handler = gen_rtc_chg_jdata,
.throttle_rate = 1,
},
[VM_EVENT_POWEROFF] = {
Expand Down Expand Up @@ -206,6 +210,7 @@ static char *generate_vm_event_message(struct vm_event *event)
fprintf(stderr, "Failed to generate vm_event message.\n");

cJSON_Delete(event_obj);

return event_msg;
}

Expand All @@ -225,6 +230,79 @@ 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;
Expand Down Expand Up @@ -372,6 +450,8 @@ int vm_event_init(struct vmctx *ctx)
goto out;
}

acrn_timer_init(&rtc_chg_event_timer, rtc_chg_timer_cb, ctx);

started = true;
return 0;

Expand Down
34 changes: 32 additions & 2 deletions devicemodel/hw/platform/rtc.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "timer.h"
#include "acpi.h"
#include "lpc.h"
#include "vm_event.h"

#include "log.h"

Expand Down Expand Up @@ -80,6 +81,7 @@ struct vrtc {
u_int addr; /* RTC register to read or write */
time_t base_uptime;
time_t base_rtctime;
time_t halted_rtctime;
struct rtcdev rtcdev;
};

Expand Down Expand Up @@ -725,6 +727,18 @@ vrtc_set_reg_c(struct vrtc *vrtc, uint8_t newval)
}
}

static void
send_rtc_chg_event(time_t newtime, time_t lasttime)
{
struct vm_event event;
struct rtc_change_event_data *data = (struct rtc_change_event_data *)event.event_data;

event.type = VM_EVENT_RTC_CHG;
data->delta_time = newtime - lasttime;
data->last_time = lasttime;
dm_send_vm_event(&event);
}

static int
vrtc_set_reg_b(struct vrtc *vrtc, uint8_t newval)
{
Expand All @@ -751,12 +765,19 @@ vrtc_set_reg_b(struct vrtc *vrtc, uint8_t newval)
if (rtctime == VRTC_BROKEN_TIME) {
if (rtc_flag_broken_time)
return -1;
} else {
/* send rtc change event if rtc time changed during halt */
if (vrtc->halted_rtctime != VRTC_BROKEN_TIME &&
rtctime != vrtc->halted_rtctime) {
send_rtc_chg_event(rtctime, vrtc->halted_rtctime);
vrtc->halted_rtctime = VRTC_BROKEN_TIME;
}
}
} else {
curtime = vrtc_curtime(vrtc, &basetime);
if (curtime != vrtc->base_rtctime)
return -1;

vrtc->halted_rtctime = curtime;
/*
* Force a refresh of the RTC date/time fields so
* they reflect the time right before the guest set
Expand Down Expand Up @@ -982,10 +1003,18 @@ vrtc_data_handler(struct vmctx *ctx, int vcpu, int in, int port,
* so re-calculate the RTC date/time.
*/
if (vrtc_is_time_register(offset) && !rtc_halted(vrtc)) {
time_t last_time = curtime;
curtime = rtc_to_secs(vrtc);
error = vrtc_time_update(vrtc, curtime, monotonic_time());
if ((error != 0) || (curtime == VRTC_BROKEN_TIME && rtc_flag_broken_time))
if ((error != 0) || (curtime == VRTC_BROKEN_TIME && rtc_flag_broken_time)) {
error = -1;
} else {
/* We don't know when the Guest has finished the RTC change action.
* So send an event each time the date/time regs has been updated.
* The event handler will process those events.
*/
send_rtc_chg_event(rtc_to_secs(vrtc), last_time);
}
}
}

Expand Down Expand Up @@ -1105,6 +1134,7 @@ vrtc_init(struct vmctx *ctx)
/* Reset the index register to a safe value. */
vrtc->addr = RTC_STATUSD;

vrtc->halted_rtctime = VRTC_BROKEN_TIME;
/*
* Initialize RTC time to 00:00:00 Jan 1, 1970 if curtime = 0
*/
Expand Down
1 change: 1 addition & 0 deletions devicemodel/include/vm_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
int vm_event_init(struct vmctx *ctx);
int vm_event_deinit(void);
int dm_send_vm_event(struct vm_event *event);
uint32_t get_dm_vm_event_overrun_count(void);

#endif /* VM_EVENT_H */
10 changes: 10 additions & 0 deletions hypervisor/include/public/acrn_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,16 @@ struct vm_event {
uint8_t event_data[VM_EVENT_DATA_LEN];
};

struct rtc_change_event_data {
int64_t last_time; /* time(in secs) of the RTC defore been set */
int64_t delta_time; /* delta of time(in secs) the RTC has been changed */
};

/* DM vrtc's reference time is besed on sys time, while the HV vrtc is based on pRTC.
* When the delta time is sent to users, they need to know what it is relative to.
*/
#define RTC_CHG_RELATIVE_PHYSICAL_RTC 0
#define RTC_CHG_RELATIVE_SERVICE_VM_SYS_TIME 1
/**
* @}
*/
Expand Down

0 comments on commit ac6505c

Please sign in to comment.