RTC virtualization in QEMU
文章目录
本文将结合QEMU代码,解析RTC虚拟化。一些细节作者也没有捋清楚,待日后更新吧。
1. Prerequisite
- 读者需对RTC有一定的了解
- 往PIO 0x70写入0x00后,从PIO 0x71寄存器中读到的就是当前的秒数
- 往PIO 0x70写入0x02后,从PIO 0x71寄存器中读到的就是当前的分钟数
- 往PIO 0x70写入0x04后,从PIO 0x71寄存器中读到的就是当前的小时数
- PIO virtualization in QEMU/KVM
2. How to use RTC in QEMU
2.1 QEMU document
1 | ``-rtc [base=utc|localtime|datetime][,clock=host|rt|vm][,driftfix=none|slew]`` |
UTC is the primary time standard by which the world regulates clocks and time.
system time vs monotonic clock
NTP
system time:
monotonic clock:
与
clock=rt
相比,clock=vm
增加了一个新的特性:当guest suspend的时候,RTC暂停计时icount: instruction counter
2.2 QEMUClockType
1 | /** |
2.3 QEMU参数解析
至于QEMU是如何解析base
、clock
和driftfix
这些参数的呢?请参考configure_rtc
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26static void configure_rtc(QemuOpts *opts)
{
const char *value;
/* Set defaults */
rtc_clock = QEMU_CLOCK_HOST;
rtc_ref_start_datetime = qemu_clock_get_ms(QEMU_CLOCK_HOST) / 1000;
rtc_realtime_clock_offset = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) / 1000;
value = qemu_opt_get(opts, "base");
if (value) {
if (!strcmp(value, "utc")) {
rtc_base_type = RTC_BASE_UTC;
} else if (!strcmp(value, "localtime")) {
Error *blocker = NULL;
rtc_base_type = RTC_BASE_LOCALTIME;
error_setg(&blocker, QERR_REPLAY_NOT_SUPPORTED,
"-rtc base=localtime");
replay_add_blocker(blocker);
} else {
rtc_base_type = RTC_BASE_DATETIME;
configure_rtc_base_datetime(value);
}
}
...
}
3. Full picture
往PIO 0x70写入0x00后,从PIO 0x71寄存器中读到的就是当前的秒数。本节以该操作为例,介绍下整个流程。
3.1 Guest写PIO 0x70
- Guest在Non-root mode下执行了OUT指令
- PIO VM Exit
- KVM发现自己处理不了这个PIO,就将这个IO请求forward给QEMU
- QEMU处理这个IO请求
至于QEMU如何处理这个IO请求,请参考cmos_ioport_write。
3.2 Guest读PIO 0x71
- Guest在Non-root mode下执行了IN指令
- PIO VM Exit
- KVM发现自己处理不了这个PIO,就将这个IO请求forward给QEMU
- QEMU处理这个IO请求
至于QEMU如何处理这个IO请求,请参考cmos_ioport_read。
4. 模拟mc146818时钟芯片
4.1 数据结构
1 | typedef struct RTCState { |
cmos_data
存放128字节的数据base_rtc
is the RTC value when the RTC was last updatedlast_update
is the guest time when the RTC was last updated
4.2 初始化
1 | static const MemoryRegionOps cmos_ops = { |
4.3 cmos_ioport_write
Guest往PIO 0x70写入0x00后,QEMU中的处理:1
2
3
4
5
6
7
8
9
10
11static void cmos_ioport_write(void *opaque, hwaddr addr,
uint64_t data, unsigned size)
{
RTCState *s = opaque;
uint32_t old_period;
bool update_periodic_timer;
if ((addr & 1) == 0) {
s->cmos_index = data & 0x7f;
} else {
...
此时,addr & 1
为0(0x70),因此会执行s->cmos_index = data & 0x7f
,设置cmos_index
为0。
4.4 cmos_ioport_read
Guest从PIO 0x71寄存器中读到的就是当前的秒数,QEMU中的处理:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28static uint64_t cmos_ioport_read(void *opaque, hwaddr addr,
unsigned size)
{
RTCState *s = opaque;
int ret;
if ((addr & 1) == 0) {
return 0xff;
} else {
switch(s->cmos_index) {
case RTC_IBM_PS2_CENTURY_BYTE:
s->cmos_index = RTC_CENTURY;
/* fall through */
case RTC_CENTURY:
case RTC_SECONDS:
case RTC_MINUTES:
case RTC_HOURS:
case RTC_DAY_OF_WEEK:
case RTC_DAY_OF_MONTH:
case RTC_MONTH:
case RTC_YEAR:
/* if not in set mode, calibrate cmos before
* reading*/
if (rtc_running(s)) {
rtc_update_time(s);
}
ret = s->cmos_data[s->cmos_index];
break;
...
此时,addr & 1
为1(0x71),因此会执行rtc_update_time
,然后返回s->cmos_data[s->cmos_index]
。
1 | static void rtc_update_time(RTCState *s) |
rtc_set_cmos
会设置cmos_data
中的内容。
1 | static uint64_t get_guest_rtc_ns(RTCState *s) |
https://lore.kernel.org/qemu-devel/1342781633-7288-5-git-send-email-pbonzini@redhat.com/
Calculate guest RTC based on the time of the last update.The formula is:(base_rtc + guest_time_now - guest_time_last_update + offset)
- base_rtc is the RTC value when the RTC was last updated
- guest_time_now is the guest time when the access happens
- guest_time_last_update is the guest time when the RTC was last updated
- offset is used when divider reset happens or the set bit is toggled(可以暂时忽略,若想深入研究,需仔细阅读RTC的spec)
4.5 Update guest RTC
什么时候会更新guest的RTC呢?
一旦更新guest的RTC,就会更新base_rtc
和last_update
。
4.5.1 base_rtc
和last_update
的初始化
1 | static void rtc_set_date_from_host(ISADevice *dev) |
4.5.2 base_rtc
和last_update
的更新
1 | static void rtc_set_time(RTCState *s) |
rtc_set_time
会更新base_rtc
和last_update
。
1 | static void cmos_ioport_write(void *opaque, hwaddr addr, |
从第34、57和90行可知,当guest往PIO 0x71设置值的时候,就可能会调用rtc_set_time
。
比如在guest中,当前为2022年,如果guest想设置为2021年,此时就会触发base_rtc
和last_update
的更新。
1 | static int rtc_post_load(void *opaque, int version_id) |
当live migration时,在目的端,也可能会调用rtc_set_time
。
4.6 MISC
rtc_policy_slew_deliver_irq
是driftfix=slew
参数对应的操作,细节未研究,待日后更新。- rtc作为定时器的用法,本文也没有阐述,待日后更新。
参考资料: