Date   

Re: [PATCH v6 7/9] hv: vrtc calibrate function

Junjie Mao
 

Yuanyuan Zhao <yuanyuan.zhao@...> writes:

For TSC's precision (20~100ppm) is lower than physical RTC (less than 20ppm),
vRTC need to be calibrated by physical RTC. A timer tiggers calibration for
vRTC every 3 hours. This can improve efficiency because physical RTC can be
read once and calibrate all vRTC.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
Reviewed-by: Junjie Mao <junjie.mao@...>

---
hypervisor/dm/vrtc.c | 80 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 77 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 9157a8fc2..3a21f219f 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -51,6 +51,8 @@ struct clktime {
uint32_t dow; /* day of week (0 - 6; 0 = Sunday) */
};

+static spinlock_t vrtc_rebase_lock = { .head = 0U, .tail = 0U };
+
#define POSIX_BASE_YEAR 1970
#define SECDAY (24 * 60 * 60)
#define SECYR (SECDAY * 365)
@@ -381,10 +383,12 @@ static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc)
uint64_t offset;
time_t second = VRTC_BROKEN_TIME;

+ spinlock_obtain(&vrtc_rebase_lock);
if (vrtc->base_rtctime > 0) {
offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U);
second = vrtc->base_rtctime + vrtc->offset_rtctime + (time_t)offset;
}
+ spinlock_release(&vrtc_rebase_lock);
return second;
}

@@ -568,7 +572,9 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
*((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0xFFU);
current = vrtc_get_current_time(vrtc);
after = rtc_to_secs(vrtc);
+ spinlock_obtain(&vrtc_rebase_lock);
vrtc->offset_rtctime += after - current;
+ spinlock_release(&vrtc_rebase_lock);
break;
}
}
@@ -577,9 +583,70 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
return true;
}

+#define CALIBRATE_PERIOD (3 * 3600 * 1000) /* By ms, totally 3 hours. */
+static struct hv_timer calibrate_timer;
+
+static time_t vrtc_get_physical_rtc_time(struct acrn_vrtc *vrtc)
+{
+ uint8_t *rtc = (uint8_t *)&vrtc->rtcdev;
+
+ *(rtc + RTC_SEC) = cmos_get_reg_val(RTC_SEC);
+ *(rtc + RTC_MIN) = cmos_get_reg_val(RTC_MIN);
+ *(rtc + RTC_HRS) = cmos_get_reg_val(RTC_HRS);
+ *(rtc + RTC_DAY) = cmos_get_reg_val(RTC_DAY);
+ *(rtc + RTC_MONTH) = cmos_get_reg_val(RTC_MONTH);
+ *(rtc + RTC_YEAR) = cmos_get_reg_val(RTC_YEAR);
+ *(rtc + RTC_CENTURY) = cmos_get_reg_val(RTC_CENTURY);
+ *(rtc + RTC_STATUSB) = cmos_get_reg_val(RTC_STATUSB);
+
+ return rtc_to_secs(vrtc);
+}
+
+static void vrtc_update_basetime(time_t physical_time, time_t offset)
+{
+ struct acrn_vm *vm;
+ uint32_t vm_id;
+
+ for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
+ vm = get_vm_from_vmid(vm_id);
+ if (is_rt_vm(vm) || is_prelaunched_vm(vm)) {
+ spinlock_obtain(&vrtc_rebase_lock);
+ vm->vrtc.base_tsc = cpu_ticks();
+ vm->vrtc.base_rtctime = physical_time;
+ vm->vrtc.offset_rtctime += offset;
+ spinlock_release(&vrtc_rebase_lock);
+ }
+ }
+}
+
+static void calibrate_timer_callback(__unused void *data)
+{
+ struct acrn_vrtc temp_vrtc;
+ time_t physical_time = vrtc_get_physical_rtc_time(&temp_vrtc);
+
+ vrtc_update_basetime(physical_time, 0);
+}
+
+static void calibrate_setup_timer(void)
+{
+ uint64_t period_in_cycle, fire_tsc;
+
+ period_in_cycle = TICKS_PER_MS * CALIBRATE_PERIOD;
+ fire_tsc = cpu_ticks() + period_in_cycle;
+ initialize_timer(&calibrate_timer,
+ calibrate_timer_callback, NULL,
+ fire_tsc, period_in_cycle);
+
+ /* Start an periodic timer */
+ if (add_timer(&calibrate_timer) != 0) {
+ pr_err("Failed to add calibrate timer");
+ }
+}
+
static void vrtc_set_basetime(struct acrn_vrtc *vrtc)
{
struct rtcdev *vrtcdev = &vrtc->rtcdev;
+ time_t current;

/*
* Read base time from physical rtc.
@@ -596,7 +663,10 @@ static void vrtc_set_basetime(struct acrn_vrtc *vrtc)
vrtcdev->reg_c = cmos_get_reg_val(RTC_INTR);
vrtcdev->reg_d = cmos_get_reg_val(RTC_STATUSD);

- vrtc->base_rtctime = rtc_to_secs(vrtc);
+ current = rtc_to_secs(vrtc);
+ spinlock_obtain(&vrtc_rebase_lock);
+ vrtc->base_rtctime = current;
+ spinlock_release(&vrtc_rebase_lock);
}

void vrtc_init(struct acrn_vm *vm)
@@ -610,6 +680,10 @@ void vrtc_init(struct acrn_vm *vm)
vm->vrtc.vm = vm;
register_pio_emulation_handler(vm, RTC_PIO_IDX, &range, vrtc_read, vrtc_write);

- vrtc_set_basetime(&vm->vrtc);
- vm->vrtc.base_tsc = cpu_ticks();
+ if (is_service_vm(vm)) {
+ calibrate_setup_timer();
+ } else {
+ vrtc_set_basetime(&vm->vrtc);
+ vm->vrtc.base_tsc = cpu_ticks();
+ }
}


Re: [PATCH v6 4/9] hv: add vRTC reg_b and reg_c support

Junjie Mao
 

Yuanyuan Zhao <yuanyuan.zhao@...> writes:

During setting RTC time, driver will halt RTC update. So support
bit 7 of reg_b. When it set to 0, time will be updated. And
when it's set to 1, rtc will keep the second it was set.

In the process of getting RTC time, driver sets alarm interrupt,
waits 1s, and get alarm interrupt flag. So support alarm interrupt
flag update. If alarm interrupt is enabled (bit 5, reg_b set to 1),
interrupt flag register will be set (bit 7 & bit 5 of reg_c) at
appropriate time.

v5->v6: commmit of alarm trigger.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
Reviewed-by: Junjie Mao <junjie.mao@...>

--
Best Regards
Junjie Mao

---
hypervisor/dm/vrtc.c | 74 +++++++++++++++++++++++++++--
hypervisor/include/dm/mc146818rtc.h | 5 ++
2 files changed, 76 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 79ee2439d..b6ebcc49c 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -422,6 +422,45 @@ static uint8_t cmos_get_reg_val(uint8_t addr)
return reg;
}

+#define TRIGGER_ALARM (RTCIR_ALARM | RTCIR_INT)
+#define RTC_DELTA 1 /* For RTC and system time may out of sync for no more than 1s */
+static inline bool rtc_halted(struct acrn_vrtc *rtc)
+{
+ return ((rtc->rtcdev.reg_b & RTCSB_HALT) != 0U);
+}
+
+static uint8_t vrtc_get_reg_c(struct acrn_vrtc *vrtc)
+{
+ uint8_t ret = vrtc->rtcdev.reg_c;
+ struct rtcdev *rtc = &vrtc->rtcdev;
+ time_t current, alarm;
+
+ if ((rtc->reg_b & RTCSB_AINTR) != 0U) {
+ current = rtc->hour * 3600 + rtc->min * 60 + rtc->sec;
+ alarm = rtc->alarm_hour * 3600 + rtc->alarm_min * 60 + rtc->alarm_sec;
+
+ if (current >= alarm - RTC_DELTA && current <= alarm + RTC_DELTA) {
+ /*
+ * Linux RTC driver will trigger alarm interrupt when getting
+ * RTC time, and then read the interrupt flag register. If the value was not
+ * correct, read failure will occurs. So if alarm interrupt is enabled
+ * and rtc time is in alarm time scale, set the interrupt flag. The
+ * interrupt is not acturally triggered for driver will read the register
+ * proactively.
+ */
+ ret |= TRIGGER_ALARM;
+ }
+ }
+
+ vrtc->rtcdev.reg_c = 0;
+ return ret;
+}
+
+static void vrtc_set_reg_b(struct acrn_vrtc *vrtc, uint8_t newval)
+{
+ vrtc->rtcdev.reg_b = newval;
+}
+
/**
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
@@ -435,7 +474,7 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
struct acrn_vm *vm = vcpu->vm;
bool ret = true;

- offset = vm->vrtc.addr;
+ offset = vrtc->addr;

if (addr == CMOS_ADDR_PORT) {
pio_req->value = offset;
@@ -447,7 +486,11 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
current = vrtc_get_current_time(vrtc);
secs_to_rtc(current, vrtc);

- pio_req->value = *((uint8_t *)&vm->vrtc.rtcdev + offset);
+ if(offset == 0xCU) {
+ pio_req->value = vrtc_get_reg_c(vrtc);
+ } else {
+ pio_req->value = *((uint8_t *)&vrtc->rtcdev + offset);
+ }
RTC_DEBUG("read 0x%x, 0x%x", offset, pio_req->value);
} else {
RTC_ERR("vrtc read invalid addr 0x%x", offset);
@@ -466,8 +509,33 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
uint32_t value)
{
+ struct acrn_vrtc *vrtc = &vcpu->vm->vrtc;
+
if ((width == 1U) && (addr == CMOS_ADDR_PORT)) {
- vcpu->vm->vrtc.addr = (uint8_t)value & 0x7FU;
+ vrtc->addr = (uint8_t)(value & 0x7FU);
+ } else {
+ if (!is_service_vm(vcpu->vm)) {
+ switch (vrtc->addr) {
+ case RTC_STATUSA:
+ case RTC_INTR:
+ case RTC_STATUSD:
+ RTC_DEBUG("RTC reg_%x set to %#x (ignored)\n", vrtc->addr, value);
+ break;
+ case RTC_STATUSB:
+ vrtc_set_reg_b(vrtc, value);
+ RTC_DEBUG("RTC reg_b set to %#x\n", value);
+ break;
+ case RTC_SECALRM:
+ case RTC_MINALRM:
+ /* FALLTHRU */
+ case RTC_HRSALRM:
+ *((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0x7FU);
+ RTC_DEBUG("RTC alarm reg(%d) set to %#x (ignored)\n", vrtc->addr, value);
+ break;
+ default:
+ break;
+ }
+ }
}

return true;
diff --git a/hypervisor/include/dm/mc146818rtc.h b/hypervisor/include/dm/mc146818rtc.h
index b23f90d34..1681cc960 100644
--- a/hypervisor/include/dm/mc146818rtc.h
+++ b/hypervisor/include/dm/mc146818rtc.h
@@ -60,8 +60,13 @@
#define RTC_STATUSB 0x0b /* status register B */
#define RTCSB_24HR 0x02 /* 0 = 12 hours, 1 = 24 hours */
#define RTCSB_BCD 0x04 /* 0 = BCD, 1 = Binary coded time */
+#define RTCSB_AINTR 0x20 /* 1 = enable alarm interrupt */
+#define RTCSB_HALT 0x80 /* stop clock updates */

#define RTC_INTR 0x0c /* status register C (R) interrupt source */
+#define RTCIR_ALARM 0x20 /* alarm intr */
+#define RTCIR_INT 0x80 /* interrupt output signal */
+
#define RTC_STATUSD 0x0d /* status register D (R) Lost Power */

#endif /* _MC146818_RTC_H_ */


[PATCH v3] misc: add vUART channel for s5 feature

chenli.wei
 

"S5" is one of the ACPI sleep states which means the function to shut
down the VMs.

On ACRN, the User VM must be shut down before powering off the Service
VM, so we need a vUART channel to communicate between the Service VM
and User VMs.

This patch adds a vUART channel for each User VM connect to Service VM

v2-->v3:
1.split the define of MAX_VUART_NUM_PER_VM to another patch, this patch
will focus on the S5 feature only

v1-->v2:
1.move the define of MAX_VUART_NUM_PER_VM to offline tool

Tracked-On: #8782
Signed-off-by: Chenli Wei <chenli.wei@...>
---
.../service_vm_config/serial_config.py | 30 +++++------
misc/config_tools/static_allocators/intx.py | 30 +++++++++--
.../xforms/vm_configurations.c.xsl | 51 +++++++++++++++++--
3 files changed, 88 insertions(+), 23 deletions(-)

diff --git a/misc/config_tools/service_vm_config/serial_config.py b/misc/config_tools/service_vm_config/serial_config.py
index fda214c07..3c53c42c1 100644
--- a/misc/config_tools/service_vm_config/serial_config.py
+++ b/misc/config_tools/service_vm_config/serial_config.py
@@ -17,7 +17,7 @@ VUART_DEV_NAME_NUM = 8
stadard_uart_port = {'0x3F8', '0x2F8', '0x3E8', '0x2E8'}
UART_IRQ_BAUD = " irq 0 uart 16550A baud_base 115200"

-def find_non_standard_uart(vm, scenario_etree):
+def find_non_standard_uart(vm, scenario_etree, allocation_etree):
uart_list = []
vmname = common.get_node("./name/text()", vm)

@@ -30,7 +30,17 @@ def find_non_standard_uart(vm, scenario_etree):

port = common.get_node(f".//endpoint[vm_name = '{vmname}']/io_port/text()", connection)
if port not in stadard_uart_port:
- uart_list.append(connection)
+ target_vm_name = common.get_node(f".//endpoint[vm_name != '{vmname}']/vm_name/text()", connection)
+ target_vm_id = common.get_node(f"//vm[name = '{target_vm_name}']/@id", scenario_etree)
+ uart_list.append({"io_port" : port, "target_vm_id" : target_vm_id})
+
+ legacy_uart_list = allocation_etree.xpath(f"//vm[load_order = 'SERVICE_VM']/legacy_vuart")
+ for legacy_uart in legacy_uart_list:
+ port = common.get_node(f"./addr.port_base/text()", legacy_uart)
+ if port is None:
+ continue
+ elif port not in stadard_uart_port:
+ uart_list.append({"io_port" : port, "target_vm_id": common.get_node(f"./t_vuart.vm_id/text()", legacy_uart)})

return uart_list

@@ -45,23 +55,13 @@ def main(args):

vm_list = scenario_etree.xpath("//vm[load_order = 'SERVICE_VM']")
for vm in vm_list:
- vmname = common.get_node("./name/text()", vm)
- for connection in scenario_etree.xpath(f"//vuart_connection[endpoint/vm_name = '{vmname}']"):
- vm_name = common.get_node(f".//endpoint[vm_name != '{vmname}']/vm_name/text()", connection)
- for target_vm in scenario_etree.xpath(f"//vm[name = '{vm_name}']"):
- vuart_target_vmid[connection.find('name').text] = target_vm.attrib["id"]
-
- vm_list = scenario_etree.xpath("//vm[load_order = 'SERVICE_VM']")
- for vm in vm_list:
- vuart_list = find_non_standard_uart(vm, scenario_etree)
+ vuart_list = find_non_standard_uart(vm, scenario_etree, allocation_etree)
vmname = common.get_node("./name/text()", vm)
if len(vuart_list) != 0:
with open(args.out, "w+") as config_f:
for uart_start_num, vuart in enumerate(vuart_list, start=START_VUART_DEV_NAME_NO):
- port = common.get_node(f".//endpoint[vm_name = '{vmname}']/io_port/text()", vuart)
- base = " port " + str(port)
- connection_name = vuart.find('name').text
- vm_id_note = "# User_VM_id: " + str(vuart_target_vmid[connection_name]) + '\n'
+ base = " port " + vuart["io_port"]
+ vm_id_note = "# User_VM_id: " + str(vuart["target_vm_id"])+ '\n'
config_f.write(vm_id_note)
conf = "/dev/ttyS" + str(uart_start_num) + base + UART_IRQ_BAUD + '\n'
config_f.write(conf)
diff --git a/misc/config_tools/static_allocators/intx.py b/misc/config_tools/static_allocators/intx.py
index b36d89eb0..559ebe86d 100644
--- a/misc/config_tools/static_allocators/intx.py
+++ b/misc/config_tools/static_allocators/intx.py
@@ -55,11 +55,33 @@ def alloc_vuart_connection_irqs(board_etree, scenario_etree, allocation_etree):
hv_debug_console = lib.lib.parse_hv_console(scenario_etree)

vm_node_list = scenario_etree.xpath("//vm")
+ user_vm_list = scenario_etree.xpath("//vm[load_order != 'SERVICE_VM']/name/text()")
+ service_vm_id = common.get_node(f"//vm[load_order = 'SERVICE_VM']/@id", scenario_etree)
+
+ native_irq_list = get_native_valid_irq()
+ for index in range(0, len(user_vm_list)):
+ vuart_id = index + 1
+
+ legacy_vuart_irq = alloc_irq(native_irq_list)
+ create_vuart_irq_node(allocation_etree, service_vm_id, "SERVICE_VM", str(vuart_id), legacy_vuart_irq)
+
for vm_node in vm_node_list:
load_order = common.get_node("./load_order/text()", vm_node)
- irq_list = get_native_valid_irq() if load_order == "SERVICE_VM" else [f"{d}" for d in list(range(1,15))]
- vuart_id = '1'
+ irq_list = native_irq_list if load_order == "SERVICE_VM" else [f"{d}" for d in list(range(1,15))]
+
+ if load_order != "SERVICE_VM":
+ vuart_id = 1
+ else:
+ vuart_id = 1 + len(user_vm_list)
+
vmname = common.get_node("./name/text()", vm_node)
+
+ #Allocate irq for S5 vuart
+ if load_order != "SERVICE_VM":
+ legacy_vuart_irq = alloc_irq(irq_list)
+ create_vuart_irq_node(allocation_etree, common.get_node("./@id", vm_node), load_order, str(vuart_id), legacy_vuart_irq)
+ vuart_id = vuart_id + 1
+
vuart_connections = scenario_etree.xpath("//vuart_connection")
for connection in vuart_connections:
endpoint_list = connection.xpath(".//endpoint")
@@ -67,8 +89,8 @@ def alloc_vuart_connection_irqs(board_etree, scenario_etree, allocation_etree):
vm_name = common.get_node("./vm_name/text()",endpoint)
if vm_name == vmname:
legacy_vuart_irq = alloc_irq(irq_list)
- create_vuart_irq_node(allocation_etree, common.get_node("./@id", vm_node), load_order, vuart_id, legacy_vuart_irq)
- vuart_id = str(int(vuart_id) + 1)
+ create_vuart_irq_node(allocation_etree, common.get_node("./@id", vm_node), load_order, str(vuart_id), legacy_vuart_irq)
+ vuart_id = vuart_id + 1

def get_irqs_of_device(device_node):
irqs = set()
diff --git a/misc/config_tools/xforms/vm_configurations.c.xsl b/misc/config_tools/xforms/vm_configurations.c.xsl
index b16565961..539f0f23e 100644
--- a/misc/config_tools/xforms/vm_configurations.c.xsl
+++ b/misc/config_tools/xforms/vm_configurations.c.xsl
@@ -117,6 +117,7 @@
<xsl:apply-templates select="os_config" />
<xsl:call-template name="acpi_config" />
<xsl:apply-templates select="console_vuart" />
+ <xsl:call-template name="s5_vuart" />
<xsl:call-template name="vuart_connection" />
<xsl:call-template name="pci_dev_num" />
<xsl:call-template name="pci_devs" />
@@ -282,13 +283,55 @@
<xsl:value-of select="$newline" />
</xsl:template>

+ <xsl:template name="s5_vuart">
+ <xsl:variable name="vm_id" select="@id" />
+ <xsl:variable name="vm_name" select="name/text()" />
+ <xsl:variable name="load_order" select="load_order/text()" />
+ <xsl:choose>
+ <xsl:when test="acrn:is-service-vm($load_order)">
+ <xsl:for-each select="//vm[load_order != 'SERVICE_VM']/name/text()">
+ <xsl:variable name="vuart_id" select="position()" />
+ <xsl:value-of select="acrn:initializer(concat('vuart[', $vuart_id, ']'), '{', true())" />
+ <xsl:value-of select="acrn:initializer('type', 'VUART_LEGACY_PIO')" />
+ <xsl:value-of select="acrn:initializer('irq', concat(//allocation-data/acrn-config/vm[@id=$vm_id]/legacy_vuart[@id=$vuart_id]/irq, 'U'))" />
+ <xsl:value-of select="acrn:initializer('addr.port_base', concat(//allocation-data/acrn-config/vm[@id=$vm_id]/legacy_vuart[@id=$vuart_id]/addr.port_base, 'U'))" />
+ <xsl:value-of select="acrn:initializer('t_vuart.vm_id', concat(//allocation-data/acrn-config/vm[@id=$vm_id]/legacy_vuart[@id=$vuart_id]/t_vuart.vm_id, 'U'))" />
+ <xsl:value-of select="acrn:initializer('t_vuart.vuart_id', concat(//allocation-data/acrn-config/vm[@id=$vm_id]/legacy_vuart[@id=$vuart_id]/t_vuart.vuart_id, 'U'))" />
+ <xsl:text>},</xsl:text>
+ </xsl:for-each>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:variable name="vuart_id" select="1" />
+ <xsl:value-of select="acrn:initializer(concat('vuart[', $vuart_id, ']'), '{', true())" />
+ <xsl:value-of select="acrn:initializer('type', 'VUART_LEGACY_PIO')" />
+ <xsl:value-of select="acrn:initializer('irq', concat(//allocation-data/acrn-config/vm[@id=$vm_id]/legacy_vuart[@id=$vuart_id]/irq, 'U'))" />
+ <xsl:value-of select="acrn:initializer('addr.port_base', concat(//allocation-data/acrn-config/vm[@id=$vm_id]/legacy_vuart[@id=$vuart_id]/addr.port_base, 'U'))" />
+ <xsl:value-of select="acrn:initializer('t_vuart.vm_id', concat(//allocation-data/acrn-config/vm[@id=$vm_id]/legacy_vuart[@id=$vuart_id]/t_vuart.vm_id, 'U'))" />
+ <xsl:value-of select="acrn:initializer('t_vuart.vuart_id', concat(//allocation-data/acrn-config/vm[@id=$vm_id]/legacy_vuart[@id=$vuart_id]/t_vuart.vuart_id, 'U'))" />
+ <xsl:text>},</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
<xsl:template name="vuart_connection">
<xsl:variable name="vm_id" select="@id" />
- <xsl:variable name="vmname" select="name/text()" />
- <xsl:for-each select="//vuart_connection[endpoint/vm_name = $vmname]">
+ <xsl:variable name="vm_name" select="name/text()" />
+ <xsl:variable name="load_order" select="load_order/text()" />
+ <xsl:for-each select="//vuart_connection[endpoint/vm_name = $vm_name]">
<xsl:variable name="connection_name" select="name/text()" />
<xsl:variable name="type" select="type/text()" />
- <xsl:variable name="vuart_id" select="position()"/>
+ <xsl:variable name="vuart_id">
+ <xsl:choose>
+ <xsl:when test="acrn:is-service-vm($load_order)">
+ <xsl:variable name="pre_launched_num" select="count(//vm[load_order = 'PRE_LAUNCHED_VM']/name/text())" />
+ <xsl:variable name="post_launched_num" select="count(//vm[load_order = 'POST_LAUNCHED_VM']/name/text())" />
+ <xsl:value-of select="position() + $pre_launched_num + $post_launched_num" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="position() + 1" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
<xsl:value-of select="acrn:initializer(concat('vuart[', $vuart_id, ']'), '{', true())" />
<xsl:choose>
<xsl:when test="$type = 'legacy'">
@@ -301,7 +344,7 @@
<xsl:value-of select="acrn:initializer('irq', concat(//allocation-data/acrn-config/vm[@id=$vm_id]/legacy_vuart[@id=$vuart_id]/irq, 'U'))" />
<xsl:for-each select="endpoint">
<xsl:choose>
- <xsl:when test="vm_name = $vmname">
+ <xsl:when test="vm_name = $vm_name">
<xsl:choose>
<xsl:when test="$type = 'legacy'">
<xsl:value-of select="acrn:initializer('addr.port_base', concat(io_port, 'U'))" />
--
2.17.1


[PATCH v6 6/9] hv: add time modification of vrtc

Zhao, Yuanyuan
 

VRTC for hv used to ignore writes to vRTC register.

This patch add time modification to vRTC.
Add base RTC time and TSC offset to get the current time. Convert
current time to vRTC register values (`struct rtcdev`). Then
modify a register, and calculate a new time. Get RTC offset by
substrcting new time from current time.
Thereafter, add RTC offset also when get current time. Then user
can get the modified time.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
Reviewed-by: Junjie Mao <junjie.mao@...>
---
hypervisor/dm/vrtc.c | 14 +++++++++++++-
hypervisor/include/dm/vrtc.h | 2 ++
2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index cb1de7423..9157a8fc2 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -383,7 +383,7 @@ static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc)

if (vrtc->base_rtctime > 0) {
offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U);
- second = vrtc->base_rtctime + (time_t)offset;
+ second = vrtc->base_rtctime + vrtc->offset_rtctime + (time_t)offset;
}
return second;
}
@@ -531,6 +531,7 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
uint32_t value)
{
+ time_t current, after;
struct acrn_vrtc *vrtc = &vcpu->vm->vrtc;

if ((width == 1U) && (addr == CMOS_ADDR_PORT)) {
@@ -556,7 +557,18 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
*((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0x7FU);
RTC_DEBUG("RTC alarm reg(%d) set to %#x (ignored)\n", vrtc->addr, value);
break;
+ case RTC_SEC:
+ /*
+ * High order bit of 'seconds' is readonly.
+ */
+ value &= 0x7f;
+ /* FALLTHRU */
default:
+ RTC_DEBUG("RTC offset %#x set to %#x\n", vrtc->addr, value);
+ *((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0xFFU);
+ current = vrtc_get_current_time(vrtc);
+ after = rtc_to_secs(vrtc);
+ vrtc->offset_rtctime += after - current;
break;
}
}
diff --git a/hypervisor/include/dm/vrtc.h b/hypervisor/include/dm/vrtc.h
index fff2559d2..b760ce445 100644
--- a/hypervisor/include/dm/vrtc.h
+++ b/hypervisor/include/dm/vrtc.h
@@ -34,6 +34,8 @@ struct acrn_vrtc {
uint32_t addr; /* RTC register to read or write */

time_t base_rtctime; /* Base time calulated from physical rtc register. */
+ time_t offset_rtctime; /* RTC offset against base time. */
+
uint64_t base_tsc; /* Base tsc value */

struct rtcdev rtcdev; /* RTC register */
--
2.25.1


[PATCH v6 0/9] add vRTC write emulate

Zhao, Yuanyuan
 

Now ACRN would emulate vRTC for non-RT pre-launched VM, RTVM and
Service VM.
For vRTC read, ACRN would only return the the physical RTC register value;
for vRTC write, ACRN would ignore it.

New implement is:
1. Service OS read and write physical RTC.
2. The initial vRTC of each VM will is same with physical RTC, i.e. the RTC of service VM.
3. Guest VM can re-program its RTC (thu NTP etc.), but the update of guest RTC will be
emulated by Hypervisor through 'offset_rtctime'.
4. Service VM will update physical RTC. So, if the system shuts down, hypervisor can rely
on #2 for each VM to get right initial RTC (sync with real NTP), without saving the “offset_rtctime”.
5. A calibration of vrtc will trigger by timer every 3 hours to keep
precision.
6. When write the physical RTC, will reset the 'offset_rtctime' of each
guest VM.

v6->v5
1. Add a new patch to ensure monotonicity.
2. Alarm interrupts commits.

v4->v5
1. [1/8] 'vrtc_set_basetime' change byte array to 'struct rtcdev' ptr.
2. [3/8] Remove space.
3. [4/8][5/8] Commit message.
4. [6/8] 'vrtc_read' switch case '0' to 'RTC_SEC'.
5. [7/8] 'calibrate_lock' --> 'vrtc_rebase_lock'.

v3->v4:
1. Add a calibrate_lock.
2. Split reg_b and reg_c patch.
3. Other commits in v3.

v2->v3:
1. Remove Servcie VM pass through, add read & write for it in HV.
2. Add periodical calibation of vrtc.
3. When set RTC time, reset offset of vrtc.

v1->v2:

1. Change name of 'mc146818rtc.h'.
2. Add cover letter.
3. Use unsigned int in time transform.
4. Split patches in a new way.

Yuanyuan Zhao (9):
hv: add `rtcdev` emulate vrtc
hv: support vrtc BCD data mode
hv: support 12 hour mode
hv: add vRTC reg_b and reg_c support
hv: add Service VM write physical RTC register
hv: add time modification of vrtc
hv: vrtc calibrate function
hv: calibrate vrtc when modify physical time
hv: vRTC monotonic growth

hypervisor/dm/vrtc.c | 647 ++++++++++++++++++++-
hypervisor/include/arch/x86/asm/guest/vm.h | 3 +-
hypervisor/include/dm/mc146818rtc.h | 72 +++
hypervisor/include/dm/vrtc.h | 45 ++
4 files changed, 755 insertions(+), 12 deletions(-)
create mode 100644 hypervisor/include/dm/mc146818rtc.h
create mode 100644 hypervisor/include/dm/vrtc.h

--
2.25.1


[PATCH v6 8/9] hv: calibrate vrtc when modify physical time

Zhao, Yuanyuan
 

For Service VM modify physical rtc time and vrtc will calibrate
time by read physical rtc. So when Service VM modify physical
time, calibrate all vrtc.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
Reviewed-by: Junjie Mao <junjie.mao@...>
---
hypervisor/dm/vrtc.c | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 3a21f219f..fda4e332d 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -41,6 +41,9 @@
#endif
# define RTC_ERR pr_err

+static time_t vrtc_get_physical_rtc_time(struct acrn_vrtc *vrtc);
+static void vrtc_update_basetime(time_t physical_time, time_t offset);
+
struct clktime {
uint32_t year; /* year (4 digit year) */
uint32_t mon; /* month (1 - 12) */
@@ -528,6 +531,12 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
return ret;
}

+static inline bool vrtc_is_time_register(uint32_t offset)
+{
+ return (offset == RTC_SEC || offset == RTC_MIN || offset == RTC_HRS || offset == RTC_DAY
+ || offset == RTC_MONTH || offset == RTC_YEAR || offset == RTC_CENTURY);
+}
+
/**
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
@@ -537,12 +546,22 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
{
time_t current, after;
struct acrn_vrtc *vrtc = &vcpu->vm->vrtc;
+ struct acrn_vrtc temp_vrtc;
+ bool is_time_register;

if ((width == 1U) && (addr == CMOS_ADDR_PORT)) {
vrtc->addr = (uint8_t)(value & 0x7FU);
} else {
if (is_service_vm(vcpu->vm)) {
- cmos_set_reg_val(vrtc->addr, (uint8_t)(value & 0xFFU));
+ is_time_register = vrtc_is_time_register(vrtc->addr);
+ if (is_time_register) {
+ current = vrtc_get_physical_rtc_time(&temp_vrtc);
+ }
+ cmos_set_reg_val(vcpu->vm->vrtc.addr, (uint8_t)(value & 0xFFU));
+ if (is_time_register) {
+ after = vrtc_get_physical_rtc_time(&temp_vrtc);
+ vrtc_update_basetime(after, current - after);
+ }
} else {
switch (vrtc->addr) {
case RTC_STATUSA:
--
2.25.1


[PATCH v6 2/9] hv: support vrtc BCD data mode

Zhao, Yuanyuan
 

Add judging of the vRTC data mode. If BCD data mode is inuse,
make conversion of data.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
Reviewed-by: Junjie Mao <junjie.mao@...>
---
hypervisor/dm/vrtc.c | 79 ++++++++++++++++++++++++-----
hypervisor/include/dm/mc146818rtc.h | 2 +
2 files changed, 67 insertions(+), 14 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 9a62bf9cc..b9c9d1f4d 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -104,6 +104,51 @@ static inline uint32_t day_of_week(uint32_t days)
return ((days) + 4U) % 7U;
}

+uint8_t const bin2bcd_data[] = {
+ 0x00U, 0x01U, 0x02U, 0x03U, 0x04U, 0x05U, 0x06U, 0x07U, 0x08U, 0x09U,
+ 0x10U, 0x11U, 0x12U, 0x13U, 0x14U, 0x15U, 0x16U, 0x17U, 0x18U, 0x19U,
+ 0x20U, 0x21U, 0x22U, 0x23U, 0x24U, 0x25U, 0x26U, 0x27U, 0x28U, 0x29U,
+ 0x30U, 0x31U, 0x32U, 0x33U, 0x34U, 0x35U, 0x36U, 0x37U, 0x38U, 0x39U,
+ 0x40U, 0x41U, 0x42U, 0x43U, 0x44U, 0x45U, 0x46U, 0x47U, 0x48U, 0x49U,
+ 0x50U, 0x51U, 0x52U, 0x53U, 0x54U, 0x55U, 0x56U, 0x57U, 0x58U, 0x59U,
+ 0x60U, 0x61U, 0x62U, 0x63U, 0x64U, 0x65U, 0x66U, 0x67U, 0x68U, 0x69U,
+ 0x70U, 0x71U, 0x72U, 0x73U, 0x74U, 0x75U, 0x76U, 0x77U, 0x78U, 0x79U,
+ 0x80U, 0x81U, 0x82U, 0x83U, 0x84U, 0x85U, 0x86U, 0x87U, 0x88U, 0x89U,
+ 0x90U, 0x91U, 0x92U, 0x93U, 0x94U, 0x95U, 0x96U, 0x97U, 0x98U, 0x99U
+};
+
+/*
+ * @pre val < 100
+ */
+static inline uint8_t rtcset(struct rtcdev *rtc, uint32_t val)
+{
+ return ((rtc->reg_b & RTCSB_BCD) ? val : bin2bcd_data[val]);
+}
+
+/*
+ * Get rtc time register binary value.
+ * If BCD data mode is enabled, translate BCD to binary.
+ */
+static int32_t rtcget(const struct rtcdev *rtc, int32_t val, uint32_t *retval)
+{
+ uint8_t upper, lower;
+ int32_t errno = 0;
+
+ if (rtc->reg_b & RTCSB_BCD) {
+ *retval = val;
+ } else {
+ lower = val & 0xfU;
+ upper = (val >> 4) & 0xfU;
+
+ if (lower > 9U || upper > 9U) {
+ errno = -EINVAL;
+ } else {
+ *retval = upper * 10U + lower;
+ }
+ }
+ return errno;
+}
+
/*
* Translate clktime (such as year, month, day) to time_t.
*/
@@ -195,13 +240,19 @@ static time_t rtc_to_secs(const struct acrn_vrtc *vrtc)
time_t second = VRTC_BROKEN_TIME;
const struct rtcdev *rtc= &vrtc->rtcdev;
int32_t err = 0;
+ uint32_t century = 0, year = 0;

do {
- ct.sec = rtc->sec;
- ct.min = rtc->min;
- ct.hour = rtc->hour;
- ct.day = rtc->day_of_month;
- ct.mon = rtc->month;
+ if (rtcget(rtc, rtc->sec, &ct.sec) < 0 || rtcget(rtc, rtc->min, &ct.min) < 0 ||
+ rtcget(rtc, rtc->hour, &ct.hour) < 0 || rtcget(rtc, rtc->day_of_month, &ct.day) < 0 ||
+ rtcget(rtc, rtc->month, &ct.mon) < 0 || rtcget(rtc, rtc->year, &year) < 0 ||
+ rtcget(rtc, rtc->century, &century) < 0) {
+ RTC_ERR("Invalid RTC sec %#x hour %#x day %#x mon %#x year %#x century %#x\n",
+ rtc->sec, rtc->min, rtc->day_of_month, rtc->month,
+ rtc->year, rtc->century);
+ err = -1;
+ break;
+ }

/*
* Ignore 'rtc->dow' because some guests like Linux don't bother
@@ -211,7 +262,7 @@ static time_t rtc_to_secs(const struct acrn_vrtc *vrtc)
*/
ct.dow = -1;

- ct.year = rtc->century * 100 + rtc->year;
+ ct.year = century * 100 + year;
if (ct.year < POSIX_BASE_YEAR) {
RTC_ERR("Invalid RTC century %#x/%d\n", rtc->century,
ct.year);
@@ -241,14 +292,14 @@ static void secs_to_rtc(time_t rtctime, struct acrn_vrtc *vrtc)

if (rtctime > 0 && clk_ts_to_ct(rtctime, &ct) == 0) {
rtc = &vrtc->rtcdev;
- rtc->sec = ct.sec;
- rtc->min = ct.min;
- rtc->hour = ct.hour;
- rtc->day_of_week = ct.dow + 1;
- rtc->day_of_month = ct.day;
- rtc->month = ct.mon;
- rtc->year = ct.year % 100;
- rtc->century = ct.year / 100;
+ rtc->sec = rtcset(rtc, ct.sec);
+ rtc->min = rtcset(rtc, ct.min);
+ rtc->hour = rtcset(rtc, ct.hour);
+ rtc->day_of_week = rtcset(rtc, ct.dow + 1);
+ rtc->day_of_month = rtcset(rtc, ct.day);
+ rtc->month = rtcset(rtc, ct.mon);
+ rtc->year = rtcset(rtc, ct.year % 100);
+ rtc->century = rtcset(rtc, ct.year / 100);
}
}

diff --git a/hypervisor/include/dm/mc146818rtc.h b/hypervisor/include/dm/mc146818rtc.h
index 023bbe599..4baf9de69 100644
--- a/hypervisor/include/dm/mc146818rtc.h
+++ b/hypervisor/include/dm/mc146818rtc.h
@@ -58,6 +58,8 @@
#define RTCSA_TUP 0x80 /* time update, don't look now */

#define RTC_STATUSB 0x0b /* status register B */
+#define RTCSB_BCD 0x04 /* 0 = BCD, 1 = Binary coded time */
+
#define RTC_INTR 0x0c /* status register C (R) interrupt source */
#define RTC_STATUSD 0x0d /* status register D (R) Lost Power */

--
2.25.1


[PATCH v6 1/9] hv: add `rtcdev` emulate vrtc

Zhao, Yuanyuan
 

Current code would read physical RTC register and return it directly to guest.

This patch would read a base physical RTC time and a base physical TSC time
at initialize stage. Then when guest tries to read vRTC time, ACRN HV would
read the real TSC time and use the TSC offset to calculate the real RTC time.

This patch only support BIN data mode and 24 hour mode.
BCD data mode and 12 hour mode will add in other patch.
The accuracy of clock provided by this patch is limited by TSC, and will
be improved in a following patch also.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
Reviewed-by: Junjie Mao <junjie.mao@...>
---
hypervisor/dm/vrtc.c | 321 ++++++++++++++++++++-
hypervisor/include/arch/x86/asm/guest/vm.h | 3 +-
hypervisor/include/dm/mc146818rtc.h | 64 ++++
hypervisor/include/dm/vrtc.h | 42 +++
4 files changed, 418 insertions(+), 12 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 23fb978c2..9a62bf9cc 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -1,18 +1,275 @@
/*
- * Copyright (C) 2018 Intel Corporation.
+ * Copyright (c) 2014, Neel Natu (neel@...)
+ * Copyright (c) 2022 Intel Corporation
+ * All rights reserved.
*
- * SPDX-License-Identifier: BSD-3-Clause
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice unmodified, this list of conditions, and the following
+ * disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <asm/guest/vm.h>
#include <asm/io.h>
+#include <asm/tsc.h>
+#include <vrtc.h>
+#include <logmsg.h>
+
+#include "mc146818rtc.h"
+
+/* #define DEBUG_RTC */
+#ifdef DEBUG_RTC
+# define RTC_DEBUG pr_info
+#else
+# define RTC_DEBUG(format, ...) do { } while (false)
+#endif
+# define RTC_ERR pr_err
+
+struct clktime {
+ uint32_t year; /* year (4 digit year) */
+ uint32_t mon; /* month (1 - 12) */
+ uint32_t day; /* day (1 - 31) */
+ uint32_t hour; /* hour (0 - 23) */
+ uint32_t min; /* minute (0 - 59) */
+ uint32_t sec; /* second (0 - 59) */
+ uint32_t dow; /* day of week (0 - 6; 0 = Sunday) */
+};
+
+#define POSIX_BASE_YEAR 1970
+#define SECDAY (24 * 60 * 60)
+#define SECYR (SECDAY * 365)
+#define VRTC_BROKEN_TIME ((time_t)-1)
+
+#define FEBRUARY 2U
+
+static const uint32_t month_days[12] = {
+ 31U, 28U, 31U, 30U, 31U, 30U, 31U, 31U, 30U, 31U, 30U, 31U
+};
+
+/*
+ * This inline avoids some unnecessary modulo operations
+ * as compared with the usual macro:
+ * ( ((year % 4) == 0 &&
+ * (year % 100) != 0) ||
+ * ((year % 400) == 0) )
+ * It is otherwise equivalent.
+ */
+static inline uint32_t leapyear(uint32_t year)
+{
+ uint32_t rv = 0U;
+
+ if ((year & 3U) == 0) {
+ rv = 1U;
+ if ((year % 100U) == 0) {
+ rv = 0U;
+ if ((year % 400U) == 0) {
+ rv = 1U;
+ }
+ }
+ }
+ return rv;
+}
+
+static inline uint32_t days_in_year(uint32_t year)
+{
+ return leapyear(year) ? 366U : 365U;
+}
+
+static inline uint32_t days_in_month(uint32_t year, uint32_t month)
+{
+ return month_days[(month) - 1U] + (month == FEBRUARY ? leapyear(year) : 0U);
+}
+
+/*
+ * Day of week. Days are counted from 1/1/1970, which was a Thursday.
+ */
+static inline uint32_t day_of_week(uint32_t days)
+{
+ return ((days) + 4U) % 7U;
+}
+
+/*
+ * Translate clktime (such as year, month, day) to time_t.
+ */
+static int32_t clk_ct_to_ts(struct clktime *ct, time_t *sec)
+{
+ uint32_t i, year, days;
+ int32_t err = 0;
+
+ year = ct->year;
+
+ /* Sanity checks. */
+ if (ct->mon < 1U || ct->mon > 12U || ct->day < 1U ||
+ ct->day > days_in_month(year, ct->mon) ||
+ ct->hour > 23U || ct->min > 59U || ct->sec > 59U ||
+ year < POSIX_BASE_YEAR || year > 2037U) {
+ /* time_t overflow */
+ err = -EINVAL;
+ } else {
+ /*
+ * Compute days since start of time
+ * First from years, then from months.
+ */
+ days = 0U;
+ for (i = POSIX_BASE_YEAR; i < year; i++) {
+ days += days_in_year(i);
+ }
+
+ /* Months */
+ for (i = 1; i < ct->mon; i++) {
+ days += days_in_month(year, i);
+ }
+ days += (ct->day - 1);
+
+ *sec = (((time_t)days * 24 + ct->hour) * 60 + ct->min) * 60 + ct->sec;
+ }
+ return err;
+}
+
+/*
+ * Translate time_t to clktime (such as year, month, day)
+ */
+static int32_t clk_ts_to_ct(time_t secs, struct clktime *ct)
+{
+ uint32_t i, year, days;
+ time_t rsec; /* remainder seconds */
+ int32_t err = 0;
+
+ days = secs / SECDAY;
+ rsec = secs % SECDAY;
+
+ ct->dow = day_of_week(days);
+
+ /* Substract out whole years, counting them in i. */
+ for (year = POSIX_BASE_YEAR; days >= days_in_year(year); year++) {
+ days -= days_in_year(year);
+ }
+ ct->year = year;
+
+ /* Substract out whole months, counting them in i. */
+ for (i = 1; days >= days_in_month(year, i); i++) {
+ days -= days_in_month(year, i);
+ }
+ ct->mon = i;
+
+ /* Days are what is left over (+1) from all that. */
+ ct->day = days + 1;
+
+ /* Hours, minutes, seconds are easy */
+ ct->hour = rsec / 3600U;
+ rsec = rsec % 3600U;
+ ct->min = rsec / 60U;
+ rsec = rsec % 60U;
+ ct->sec = rsec;
+
+ /* time_t is defined as int32_t, so year should not be more than 2037. */
+ if (ct->mon > 12U || ct->year > 2037 || ct->day > days_in_month(ct->year, ct->mon)) {
+ RTC_ERR("Invalid vRTC param mon %d, year %d, day %d\n", ct->mon, ct->year, ct->day);
+ err = -EINVAL;
+ }
+ return err;
+}
+
+/*
+ * Calculate second value from rtcdev register info which save in vrtc.
+ */
+static time_t rtc_to_secs(const struct acrn_vrtc *vrtc)
+{
+ struct clktime ct;
+ time_t second = VRTC_BROKEN_TIME;
+ const struct rtcdev *rtc= &vrtc->rtcdev;
+ int32_t err = 0;
+
+ do {
+ ct.sec = rtc->sec;
+ ct.min = rtc->min;
+ ct.hour = rtc->hour;
+ ct.day = rtc->day_of_month;
+ ct.mon = rtc->month;
+
+ /*
+ * Ignore 'rtc->dow' because some guests like Linux don't bother
+ * setting it at all while others like OpenBSD/i386 set it incorrectly.
+ *
+ * clock_ct_to_ts() does not depend on 'ct.dow' anyways so ignore it.
+ */
+ ct.dow = -1;
+
+ ct.year = rtc->century * 100 + rtc->year;
+ if (ct.year < POSIX_BASE_YEAR) {
+ RTC_ERR("Invalid RTC century %#x/%d\n", rtc->century,
+ ct.year);
+ break;
+ }
+
+ err = clk_ct_to_ts(&ct, &second);
+ if (err) {
+ RTC_ERR("Invalid RTC clocktime.date %04d-%02d-%02d\n",
+ ct.year, ct.mon, ct.day);
+ RTC_ERR("Invalid RTC clocktime.time %02d:%02d:%02d\n",
+ ct.hour, ct.min, ct.sec);
+ break;
+ }
+ } while (false);
+
+ return second;
+}
+
+/*
+ * Translate second value to rtcdev register info and save it in vrtc.
+ */
+static void secs_to_rtc(time_t rtctime, struct acrn_vrtc *vrtc)
+{
+ struct clktime ct;
+ struct rtcdev *rtc;
+
+ if (rtctime > 0 && clk_ts_to_ct(rtctime, &ct) == 0) {
+ rtc = &vrtc->rtcdev;
+ rtc->sec = ct.sec;
+ rtc->min = ct.min;
+ rtc->hour = ct.hour;
+ rtc->day_of_week = ct.dow + 1;
+ rtc->day_of_month = ct.day;
+ rtc->month = ct.mon;
+ rtc->year = ct.year % 100;
+ rtc->century = ct.year / 100;
+ }
+}
+
+/*
+ * If the base_rtctime is valid, calculate current time by add tsc offset and offset_rtctime.
+ */
+static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc)
+{
+ uint64_t offset;
+ time_t second = VRTC_BROKEN_TIME;
+
+ if (vrtc->base_rtctime > 0) {
+ offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U);
+ second = vrtc->base_rtctime + (time_t)offset;
+ }
+ return second;
+}

#define CMOS_ADDR_PORT 0x70U
#define CMOS_DATA_PORT 0x71U

-#define RTC_STATUSA 0x0AU /* status register A */
-#define RTCSA_TUP 0x80U /* time update, don't look now */
-
static spinlock_t cmos_lock = { .head = 0U, .tail = 0U };

static uint8_t cmos_read(uint8_t addr)
@@ -51,18 +308,34 @@ static uint8_t cmos_get_reg_val(uint8_t addr)
static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t width)
{
uint8_t offset;
+ time_t current;
+ struct acrn_vrtc *vrtc = &vcpu->vm->vrtc;
struct acrn_pio_request *pio_req = &vcpu->req.reqs.pio_request;
struct acrn_vm *vm = vcpu->vm;
+ bool ret = true;

- offset = vm->vrtc_offset;
+ offset = vm->vrtc.addr;

if (addr == CMOS_ADDR_PORT) {
- pio_req->value = vm->vrtc_offset;
+ pio_req->value = offset;
} else {
- pio_req->value = cmos_get_reg_val(offset);
+ if (is_service_vm(vm)) {
+ pio_req->value = cmos_get_reg_val(offset);
+ } else {
+ if (offset <= RTC_CENTURY) {
+ current = vrtc_get_current_time(vrtc);
+ secs_to_rtc(current, vrtc);
+
+ pio_req->value = *((uint8_t *)&vm->vrtc.rtcdev + offset);
+ RTC_DEBUG("read 0x%x, 0x%x", offset, pio_req->value);
+ } else {
+ RTC_ERR("vrtc read invalid addr 0x%x", offset);
+ ret = false;
+ }
+ }
}

- return true;
+ return ret;
}

/**
@@ -73,19 +346,45 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
uint32_t value)
{
if ((width == 1U) && (addr == CMOS_ADDR_PORT)) {
- vcpu->vm->vrtc_offset = (uint8_t)value & 0x7FU;
+ vcpu->vm->vrtc.addr = (uint8_t)value & 0x7FU;
}

return true;
}

+static void vrtc_set_basetime(struct acrn_vrtc *vrtc)
+{
+ struct rtcdev *vrtcdev = &vrtc->rtcdev;
+
+ /*
+ * Read base time from physical rtc.
+ */
+ vrtcdev->sec = cmos_get_reg_val(RTC_SEC);
+ vrtcdev->min = cmos_get_reg_val(RTC_MIN);
+ vrtcdev->hour = cmos_get_reg_val(RTC_HRS);
+ vrtcdev->day_of_month = cmos_get_reg_val(RTC_DAY);
+ vrtcdev->month = cmos_get_reg_val(RTC_MONTH);
+ vrtcdev->year = cmos_get_reg_val(RTC_YEAR);
+ vrtcdev->century = cmos_get_reg_val(RTC_CENTURY);
+ vrtcdev->reg_a = cmos_get_reg_val(RTC_STATUSA) & (~RTCSA_TUP);
+ vrtcdev->reg_b = cmos_get_reg_val(RTC_STATUSB);
+ vrtcdev->reg_c = cmos_get_reg_val(RTC_INTR);
+ vrtcdev->reg_d = cmos_get_reg_val(RTC_STATUSD);
+
+ vrtc->base_rtctime = rtc_to_secs(vrtc);
+}
+
void vrtc_init(struct acrn_vm *vm)
{
struct vm_io_range range = {
.base = CMOS_ADDR_PORT, .len = 2U};

/* Initializing the CMOS RAM offset to 0U */
- vm->vrtc_offset = 0U;
+ vm->vrtc.addr = 0U;

+ vm->vrtc.vm = vm;
register_pio_emulation_handler(vm, RTC_PIO_IDX, &range, vrtc_read, vrtc_write);
+
+ vrtc_set_basetime(&vm->vrtc);
+ vm->vrtc.base_tsc = cpu_ticks();
}
diff --git a/hypervisor/include/arch/x86/asm/guest/vm.h b/hypervisor/include/arch/x86/asm/guest/vm.h
index 589d0a913..c05b9b212 100644
--- a/hypervisor/include/arch/x86/asm/guest/vm.h
+++ b/hypervisor/include/arch/x86/asm/guest/vm.h
@@ -21,6 +21,7 @@
#include <vpic.h>
#include <asm/guest/vmx_io.h>
#include <vuart.h>
+#include <vrtc.h>
#include <asm/guest/trusty.h>
#include <asm/guest/vcpuid.h>
#include <vpci.h>
@@ -169,7 +170,7 @@ struct acrn_vm {
uint32_t vcpuid_entry_nr, vcpuid_level, vcpuid_xlevel;
struct vcpuid_entry vcpuid_entries[MAX_VM_VCPUID_ENTRIES];
struct acrn_vpci vpci;
- uint8_t vrtc_offset;
+ struct acrn_vrtc vrtc;

uint64_t intr_inject_delay_delta; /* delay of intr injection */
} __aligned(PAGE_SIZE);
diff --git a/hypervisor/include/dm/mc146818rtc.h b/hypervisor/include/dm/mc146818rtc.h
new file mode 100644
index 000000000..023bbe599
--- /dev/null
+++ b/hypervisor/include/dm/mc146818rtc.h
@@ -0,0 +1,64 @@
+/*-
+ * Copyright (c) 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Copyright (C) 2022 Intel Corporation.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * William Jolitz.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * from: @(#)rtc.h 7.1 (Berkeley) 5/12/91
+ * $FreeBSD$
+ */
+
+#ifndef _MC146818_RTC_H_
+#define _MC146818_RTC_H_
+
+/*
+ * MC146818 RTC Register locations
+ */
+
+#define RTC_SEC 0x00 /* seconds */
+#define RTC_SECALRM 0x01 /* seconds alarm */
+#define RTC_MIN 0x02 /* minutes */
+#define RTC_MINALRM 0x03 /* minutes alarm */
+#define RTC_HRS 0x04 /* hours */
+#define RTC_HRSALRM 0x05 /* hours alarm */
+#define RTC_WDAY 0x06 /* week day */
+#define RTC_DAY 0x07 /* day of month */
+#define RTC_MONTH 0x08 /* month of year */
+#define RTC_YEAR 0x09 /* year in century*/
+#define RTC_CENTURY 0x32 /* century */
+
+#define RTC_STATUSA 0x0a /* status register A */
+#define RTCSA_TUP 0x80 /* time update, don't look now */
+
+#define RTC_STATUSB 0x0b /* status register B */
+#define RTC_INTR 0x0c /* status register C (R) interrupt source */
+#define RTC_STATUSD 0x0d /* status register D (R) Lost Power */
+
+#endif /* _MC146818_RTC_H_ */
diff --git a/hypervisor/include/dm/vrtc.h b/hypervisor/include/dm/vrtc.h
new file mode 100644
index 000000000..fff2559d2
--- /dev/null
+++ b/hypervisor/include/dm/vrtc.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 Intel Corporation.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef VRTC_H
+#define VRTC_H
+
+typedef int32_t time_t;
+
+/* Register layout of the RTC */
+struct rtcdev {
+ uint8_t sec;
+ uint8_t alarm_sec;
+ uint8_t min;
+ uint8_t alarm_min;
+ uint8_t hour;
+ uint8_t alarm_hour;
+ uint8_t day_of_week;
+ uint8_t day_of_month;
+ uint8_t month;
+ uint8_t year;
+ uint8_t reg_a;
+ uint8_t reg_b;
+ uint8_t reg_c;
+ uint8_t reg_d;
+ uint8_t res[36];
+ uint8_t century;
+};
+
+struct acrn_vrtc {
+ struct acrn_vm *vm;
+ uint32_t addr; /* RTC register to read or write */
+
+ time_t base_rtctime; /* Base time calulated from physical rtc register. */
+ uint64_t base_tsc; /* Base tsc value */
+
+ struct rtcdev rtcdev; /* RTC register */
+};
+
+#endif /* VRTC_H */
--
2.25.1


[PATCH v6 9/9] hv: vRTC monotonic growth

Zhao, Yuanyuan
 

For physical RTC is monotonic growth, ensure vRTC monotonicity.
Periodical calibration and physical RTC modification may have
impact. Check it before reading

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
---
hypervisor/dm/vrtc.c | 8 ++++++++
hypervisor/include/dm/vrtc.h | 1 +
2 files changed, 9 insertions(+)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index fda4e332d..b1e35aa17 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -390,6 +390,11 @@ static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc)
if (vrtc->base_rtctime > 0) {
offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U);
second = vrtc->base_rtctime + vrtc->offset_rtctime + (time_t)offset;
+ if (second < vrtc->last_rtctime) {
+ second = vrtc->last_rtctime;
+ } else {
+ vrtc->last_rtctime = second;
+ }
}
spinlock_release(&vrtc_rebase_lock);
return second;
@@ -593,6 +598,7 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
after = rtc_to_secs(vrtc);
spinlock_obtain(&vrtc_rebase_lock);
vrtc->offset_rtctime += after - current;
+ vrtc->last_rtctime = VRTC_BROKEN_TIME;
spinlock_release(&vrtc_rebase_lock);
break;
}
@@ -632,6 +638,7 @@ static void vrtc_update_basetime(time_t physical_time, time_t offset)
spinlock_obtain(&vrtc_rebase_lock);
vm->vrtc.base_tsc = cpu_ticks();
vm->vrtc.base_rtctime = physical_time;
+ vm->vrtc.last_rtctime = VRTC_BROKEN_TIME;
vm->vrtc.offset_rtctime += offset;
spinlock_release(&vrtc_rebase_lock);
}
@@ -685,6 +692,7 @@ static void vrtc_set_basetime(struct acrn_vrtc *vrtc)
current = rtc_to_secs(vrtc);
spinlock_obtain(&vrtc_rebase_lock);
vrtc->base_rtctime = current;
+ vrtc->last_rtctime = VRTC_BROKEN_TIME;
spinlock_release(&vrtc_rebase_lock);
}

diff --git a/hypervisor/include/dm/vrtc.h b/hypervisor/include/dm/vrtc.h
index b760ce445..fa57eb542 100644
--- a/hypervisor/include/dm/vrtc.h
+++ b/hypervisor/include/dm/vrtc.h
@@ -35,6 +35,7 @@ struct acrn_vrtc {

time_t base_rtctime; /* Base time calulated from physical rtc register. */
time_t offset_rtctime; /* RTC offset against base time. */
+ time_t last_rtctime; /* Last RTC time, to keep monotonicity. */

uint64_t base_tsc; /* Base tsc value */

--
2.25.1


[PATCH v6 4/9] hv: add vRTC reg_b and reg_c support

Zhao, Yuanyuan
 

During setting RTC time, driver will halt RTC update. So support
bit 7 of reg_b. When it set to 0, time will be updated. And
when it's set to 1, rtc will keep the second it was set.

In the process of getting RTC time, driver sets alarm interrupt,
waits 1s, and get alarm interrupt flag. So support alarm interrupt
flag update. If alarm interrupt is enabled (bit 5, reg_b set to 1),
interrupt flag register will be set (bit 7 & bit 5 of reg_c) at
appropriate time.

v5->v6: commmit of alarm trigger.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
---
hypervisor/dm/vrtc.c | 74 +++++++++++++++++++++++++++--
hypervisor/include/dm/mc146818rtc.h | 5 ++
2 files changed, 76 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 79ee2439d..b6ebcc49c 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -422,6 +422,45 @@ static uint8_t cmos_get_reg_val(uint8_t addr)
return reg;
}

+#define TRIGGER_ALARM (RTCIR_ALARM | RTCIR_INT)
+#define RTC_DELTA 1 /* For RTC and system time may out of sync for no more than 1s */
+static inline bool rtc_halted(struct acrn_vrtc *rtc)
+{
+ return ((rtc->rtcdev.reg_b & RTCSB_HALT) != 0U);
+}
+
+static uint8_t vrtc_get_reg_c(struct acrn_vrtc *vrtc)
+{
+ uint8_t ret = vrtc->rtcdev.reg_c;
+ struct rtcdev *rtc = &vrtc->rtcdev;
+ time_t current, alarm;
+
+ if ((rtc->reg_b & RTCSB_AINTR) != 0U) {
+ current = rtc->hour * 3600 + rtc->min * 60 + rtc->sec;
+ alarm = rtc->alarm_hour * 3600 + rtc->alarm_min * 60 + rtc->alarm_sec;
+
+ if (current >= alarm - RTC_DELTA && current <= alarm + RTC_DELTA) {
+ /*
+ * Linux RTC driver will trigger alarm interrupt when getting
+ * RTC time, and then read the interrupt flag register. If the value was not
+ * correct, read failure will occurs. So if alarm interrupt is enabled
+ * and rtc time is in alarm time scale, set the interrupt flag. The
+ * interrupt is not acturally triggered for driver will read the register
+ * proactively.
+ */
+ ret |= TRIGGER_ALARM;
+ }
+ }
+
+ vrtc->rtcdev.reg_c = 0;
+ return ret;
+}
+
+static void vrtc_set_reg_b(struct acrn_vrtc *vrtc, uint8_t newval)
+{
+ vrtc->rtcdev.reg_b = newval;
+}
+
/**
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
@@ -435,7 +474,7 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
struct acrn_vm *vm = vcpu->vm;
bool ret = true;

- offset = vm->vrtc.addr;
+ offset = vrtc->addr;

if (addr == CMOS_ADDR_PORT) {
pio_req->value = offset;
@@ -447,7 +486,11 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
current = vrtc_get_current_time(vrtc);
secs_to_rtc(current, vrtc);

- pio_req->value = *((uint8_t *)&vm->vrtc.rtcdev + offset);
+ if(offset == 0xCU) {
+ pio_req->value = vrtc_get_reg_c(vrtc);
+ } else {
+ pio_req->value = *((uint8_t *)&vrtc->rtcdev + offset);
+ }
RTC_DEBUG("read 0x%x, 0x%x", offset, pio_req->value);
} else {
RTC_ERR("vrtc read invalid addr 0x%x", offset);
@@ -466,8 +509,33 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
uint32_t value)
{
+ struct acrn_vrtc *vrtc = &vcpu->vm->vrtc;
+
if ((width == 1U) && (addr == CMOS_ADDR_PORT)) {
- vcpu->vm->vrtc.addr = (uint8_t)value & 0x7FU;
+ vrtc->addr = (uint8_t)(value & 0x7FU);
+ } else {
+ if (!is_service_vm(vcpu->vm)) {
+ switch (vrtc->addr) {
+ case RTC_STATUSA:
+ case RTC_INTR:
+ case RTC_STATUSD:
+ RTC_DEBUG("RTC reg_%x set to %#x (ignored)\n", vrtc->addr, value);
+ break;
+ case RTC_STATUSB:
+ vrtc_set_reg_b(vrtc, value);
+ RTC_DEBUG("RTC reg_b set to %#x\n", value);
+ break;
+ case RTC_SECALRM:
+ case RTC_MINALRM:
+ /* FALLTHRU */
+ case RTC_HRSALRM:
+ *((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0x7FU);
+ RTC_DEBUG("RTC alarm reg(%d) set to %#x (ignored)\n", vrtc->addr, value);
+ break;
+ default:
+ break;
+ }
+ }
}

return true;
diff --git a/hypervisor/include/dm/mc146818rtc.h b/hypervisor/include/dm/mc146818rtc.h
index b23f90d34..1681cc960 100644
--- a/hypervisor/include/dm/mc146818rtc.h
+++ b/hypervisor/include/dm/mc146818rtc.h
@@ -60,8 +60,13 @@
#define RTC_STATUSB 0x0b /* status register B */
#define RTCSB_24HR 0x02 /* 0 = 12 hours, 1 = 24 hours */
#define RTCSB_BCD 0x04 /* 0 = BCD, 1 = Binary coded time */
+#define RTCSB_AINTR 0x20 /* 1 = enable alarm interrupt */
+#define RTCSB_HALT 0x80 /* stop clock updates */

#define RTC_INTR 0x0c /* status register C (R) interrupt source */
+#define RTCIR_ALARM 0x20 /* alarm intr */
+#define RTCIR_INT 0x80 /* interrupt output signal */
+
#define RTC_STATUSD 0x0d /* status register D (R) Lost Power */

#endif /* _MC146818_RTC_H_ */
--
2.25.1


[PATCH v6 7/9] hv: vrtc calibrate function

Zhao, Yuanyuan
 

For TSC's precision (20~100ppm) is lower than physical RTC (less than 20ppm),
vRTC need to be calibrated by physical RTC. A timer tiggers calibration for
vRTC every 3 hours. This can improve efficiency because physical RTC can be
read once and calibrate all vRTC.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
---
hypervisor/dm/vrtc.c | 80 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 77 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 9157a8fc2..3a21f219f 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -51,6 +51,8 @@ struct clktime {
uint32_t dow; /* day of week (0 - 6; 0 = Sunday) */
};

+static spinlock_t vrtc_rebase_lock = { .head = 0U, .tail = 0U };
+
#define POSIX_BASE_YEAR 1970
#define SECDAY (24 * 60 * 60)
#define SECYR (SECDAY * 365)
@@ -381,10 +383,12 @@ static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc)
uint64_t offset;
time_t second = VRTC_BROKEN_TIME;

+ spinlock_obtain(&vrtc_rebase_lock);
if (vrtc->base_rtctime > 0) {
offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U);
second = vrtc->base_rtctime + vrtc->offset_rtctime + (time_t)offset;
}
+ spinlock_release(&vrtc_rebase_lock);
return second;
}

@@ -568,7 +572,9 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
*((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0xFFU);
current = vrtc_get_current_time(vrtc);
after = rtc_to_secs(vrtc);
+ spinlock_obtain(&vrtc_rebase_lock);
vrtc->offset_rtctime += after - current;
+ spinlock_release(&vrtc_rebase_lock);
break;
}
}
@@ -577,9 +583,70 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
return true;
}

+#define CALIBRATE_PERIOD (3 * 3600 * 1000) /* By ms, totally 3 hours. */
+static struct hv_timer calibrate_timer;
+
+static time_t vrtc_get_physical_rtc_time(struct acrn_vrtc *vrtc)
+{
+ uint8_t *rtc = (uint8_t *)&vrtc->rtcdev;
+
+ *(rtc + RTC_SEC) = cmos_get_reg_val(RTC_SEC);
+ *(rtc + RTC_MIN) = cmos_get_reg_val(RTC_MIN);
+ *(rtc + RTC_HRS) = cmos_get_reg_val(RTC_HRS);
+ *(rtc + RTC_DAY) = cmos_get_reg_val(RTC_DAY);
+ *(rtc + RTC_MONTH) = cmos_get_reg_val(RTC_MONTH);
+ *(rtc + RTC_YEAR) = cmos_get_reg_val(RTC_YEAR);
+ *(rtc + RTC_CENTURY) = cmos_get_reg_val(RTC_CENTURY);
+ *(rtc + RTC_STATUSB) = cmos_get_reg_val(RTC_STATUSB);
+
+ return rtc_to_secs(vrtc);
+}
+
+static void vrtc_update_basetime(time_t physical_time, time_t offset)
+{
+ struct acrn_vm *vm;
+ uint32_t vm_id;
+
+ for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
+ vm = get_vm_from_vmid(vm_id);
+ if (is_rt_vm(vm) || is_prelaunched_vm(vm)) {
+ spinlock_obtain(&vrtc_rebase_lock);
+ vm->vrtc.base_tsc = cpu_ticks();
+ vm->vrtc.base_rtctime = physical_time;
+ vm->vrtc.offset_rtctime += offset;
+ spinlock_release(&vrtc_rebase_lock);
+ }
+ }
+}
+
+static void calibrate_timer_callback(__unused void *data)
+{
+ struct acrn_vrtc temp_vrtc;
+ time_t physical_time = vrtc_get_physical_rtc_time(&temp_vrtc);
+
+ vrtc_update_basetime(physical_time, 0);
+}
+
+static void calibrate_setup_timer(void)
+{
+ uint64_t period_in_cycle, fire_tsc;
+
+ period_in_cycle = TICKS_PER_MS * CALIBRATE_PERIOD;
+ fire_tsc = cpu_ticks() + period_in_cycle;
+ initialize_timer(&calibrate_timer,
+ calibrate_timer_callback, NULL,
+ fire_tsc, period_in_cycle);
+
+ /* Start an periodic timer */
+ if (add_timer(&calibrate_timer) != 0) {
+ pr_err("Failed to add calibrate timer");
+ }
+}
+
static void vrtc_set_basetime(struct acrn_vrtc *vrtc)
{
struct rtcdev *vrtcdev = &vrtc->rtcdev;
+ time_t current;

/*
* Read base time from physical rtc.
@@ -596,7 +663,10 @@ static void vrtc_set_basetime(struct acrn_vrtc *vrtc)
vrtcdev->reg_c = cmos_get_reg_val(RTC_INTR);
vrtcdev->reg_d = cmos_get_reg_val(RTC_STATUSD);

- vrtc->base_rtctime = rtc_to_secs(vrtc);
+ current = rtc_to_secs(vrtc);
+ spinlock_obtain(&vrtc_rebase_lock);
+ vrtc->base_rtctime = current;
+ spinlock_release(&vrtc_rebase_lock);
}

void vrtc_init(struct acrn_vm *vm)
@@ -610,6 +680,10 @@ void vrtc_init(struct acrn_vm *vm)
vm->vrtc.vm = vm;
register_pio_emulation_handler(vm, RTC_PIO_IDX, &range, vrtc_read, vrtc_write);

- vrtc_set_basetime(&vm->vrtc);
- vm->vrtc.base_tsc = cpu_ticks();
+ if (is_service_vm(vm)) {
+ calibrate_setup_timer();
+ } else {
+ vrtc_set_basetime(&vm->vrtc);
+ vm->vrtc.base_tsc = cpu_ticks();
+ }
}
--
2.25.1


[PATCH v6 5/9] hv: add Service VM write physical RTC register

Zhao, Yuanyuan
 

Service VM write physical RTC register.
Both RTC time modify and Configuration register is available.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
Reviewed-by: Junjie Mao <junjie.mao@...>
---
hypervisor/dm/vrtc.c | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index b6ebcc49c..cb1de7423 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -399,6 +399,12 @@ static uint8_t cmos_read(uint8_t addr)
return pio_read8(CMOS_DATA_PORT);
}

+static void cmos_write(uint8_t addr, uint8_t value)
+{
+ pio_write8(addr, CMOS_ADDR_PORT);
+ pio_write8(value, CMOS_DATA_PORT);
+}
+
static bool cmos_update_in_progress(void)
{
return (cmos_read(RTC_STATUSA) & RTCSA_TUP)?1:0;
@@ -422,6 +428,22 @@ static uint8_t cmos_get_reg_val(uint8_t addr)
return reg;
}

+static void cmos_set_reg_val(uint8_t addr, uint8_t value)
+{
+ int32_t tries = 2000;
+
+ spinlock_obtain(&cmos_lock);
+
+ /* Make sure an update isn't in progress */
+ while (cmos_update_in_progress() && (tries != 0)) {
+ tries -= 1;
+ }
+
+ cmos_write(addr, value);
+
+ spinlock_release(&cmos_lock);
+}
+
#define TRIGGER_ALARM (RTCIR_ALARM | RTCIR_INT)
#define RTC_DELTA 1 /* For RTC and system time may out of sync for no more than 1s */
static inline bool rtc_halted(struct acrn_vrtc *rtc)
@@ -514,7 +536,9 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
if ((width == 1U) && (addr == CMOS_ADDR_PORT)) {
vrtc->addr = (uint8_t)(value & 0x7FU);
} else {
- if (!is_service_vm(vcpu->vm)) {
+ if (is_service_vm(vcpu->vm)) {
+ cmos_set_reg_val(vrtc->addr, (uint8_t)(value & 0xFFU));
+ } else {
switch (vrtc->addr) {
case RTC_STATUSA:
case RTC_INTR:
--
2.25.1


[PATCH v6 3/9] hv: support 12 hour mode

Zhao, Yuanyuan
 

Add support for hour format.
Bit 1 of register B indicates the hour byte format. When it's 0,
twelve-hour mode is selected. Then the seventh bit of hour register
presents AM as 0 and PM as 1.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
Reviewed-by: Junjie Mao <junjie.mao@...>
---
hypervisor/dm/vrtc.c | 76 +++++++++++++++++++++++++++--
hypervisor/include/dm/mc146818rtc.h | 1 +
2 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index b9c9d1f4d..79ee2439d 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -239,12 +239,12 @@ static time_t rtc_to_secs(const struct acrn_vrtc *vrtc)
struct clktime ct;
time_t second = VRTC_BROKEN_TIME;
const struct rtcdev *rtc= &vrtc->rtcdev;
- int32_t err = 0;
+ int32_t err = 0, hour = 0, pm = 0;
uint32_t century = 0, year = 0;

do {
if (rtcget(rtc, rtc->sec, &ct.sec) < 0 || rtcget(rtc, rtc->min, &ct.min) < 0 ||
- rtcget(rtc, rtc->hour, &ct.hour) < 0 || rtcget(rtc, rtc->day_of_month, &ct.day) < 0 ||
+ rtcget(rtc, rtc->day_of_month, &ct.day) < 0 ||
rtcget(rtc, rtc->month, &ct.mon) < 0 || rtcget(rtc, rtc->year, &year) < 0 ||
rtcget(rtc, rtc->century, &century) < 0) {
RTC_ERR("Invalid RTC sec %#x hour %#x day %#x mon %#x year %#x century %#x\n",
@@ -254,6 +254,47 @@ static time_t rtc_to_secs(const struct acrn_vrtc *vrtc)
break;
}

+ /*
+ * If 12 hour format is inuse, translate it to 24 hour format here.
+ */
+ pm = 0;
+ hour = rtc->hour;
+ if ((rtc->reg_b & RTCSB_24HR) == 0) {
+ if (hour & 0x80U) {
+ hour &= ~0x80U;
+ pm = 1;
+ }
+ }
+ err = rtcget(rtc, hour, &ct.hour);
+ if (err) {
+ RTC_ERR("Invalid RTC hour %#x/%d\n", rtc->hour, ct.hour);
+ break;
+ }
+ if ((rtc->reg_b & RTCSB_24HR) == 0) {
+ if (ct.hour >= 1 && ct.hour <= 12) {
+ /*
+ * Convert from 12-hour format to internal 24-hour
+ * representation as follows:
+ *
+ * 12-hour format ct.hour
+ * 12 AM 0
+ * 1 - 11 AM 1 - 11
+ * 12 PM 12
+ * 1 - 11 PM 13 - 23
+ */
+ if (ct.hour == 12) {
+ ct.hour = 0;
+ }
+ if (pm) {
+ ct.hour += 12;
+ }
+ } else {
+ RTC_ERR("Invalid RTC 12-hour format %#x/%d\n",
+ rtc->hour, ct.hour);
+ break;
+ }
+ }
+
/*
* Ignore 'rtc->dow' because some guests like Linux don't bother
* setting it at all while others like OpenBSD/i386 set it incorrectly.
@@ -289,12 +330,41 @@ static void secs_to_rtc(time_t rtctime, struct acrn_vrtc *vrtc)
{
struct clktime ct;
struct rtcdev *rtc;
+ int32_t hour;

if (rtctime > 0 && clk_ts_to_ct(rtctime, &ct) == 0) {
rtc = &vrtc->rtcdev;
rtc->sec = rtcset(rtc, ct.sec);
rtc->min = rtcset(rtc, ct.min);
- rtc->hour = rtcset(rtc, ct.hour);
+
+ if (rtc->reg_b & RTCSB_24HR) {
+ hour = ct.hour;
+ } else {
+ /*
+ * Convert to the 12-hour format.
+ */
+ switch (ct.hour) {
+ case 0: /* 12 AM */
+ case 12: /* 12 PM */
+ hour = 12;
+ break;
+ default:
+ /*
+ * The remaining 'ct.hour' values are interpreted as:
+ * [1 - 11] -> 1 - 11 AM
+ * [13 - 23] -> 1 - 11 PM
+ */
+ hour = ct.hour % 12;
+ break;
+ }
+ }
+
+ rtc->hour = rtcset(rtc, hour);
+
+ if ((rtc->reg_b & RTCSB_24HR) == 0 && ct.hour >= 12) {
+ rtc->hour |= 0x80; /* set MSB to indicate PM */
+ }
+
rtc->day_of_week = rtcset(rtc, ct.dow + 1);
rtc->day_of_month = rtcset(rtc, ct.day);
rtc->month = rtcset(rtc, ct.mon);
diff --git a/hypervisor/include/dm/mc146818rtc.h b/hypervisor/include/dm/mc146818rtc.h
index 4baf9de69..b23f90d34 100644
--- a/hypervisor/include/dm/mc146818rtc.h
+++ b/hypervisor/include/dm/mc146818rtc.h
@@ -58,6 +58,7 @@
#define RTCSA_TUP 0x80 /* time update, don't look now */

#define RTC_STATUSB 0x0b /* status register B */
+#define RTCSB_24HR 0x02 /* 0 = 12 hours, 1 = 24 hours */
#define RTCSB_BCD 0x04 /* 0 = BCD, 1 = Binary coded time */

#define RTC_INTR 0x0c /* status register C (R) interrupt source */
--
2.25.1


Re: [PATCH] hv: update SSRAM regions EPT memory type to WB

Yonghua Huang
 

Hi Fei,

-----Original Message-----
From: Li, Fei1 <fei1.li@...>
Sent: Saturday, May 7, 2022 10:28
To: acrn-dev@...
Cc: Huang, Yonghua <yonghua.huang@...>
Subject: Re: [acrn-dev] [PATCH] hv: update SSRAM regions EPT memory type
to WB

On Sat, May 07, 2022 at 04:40:51AM +0300, Yonghua Huang wrote:
when SSRAM regions are assigned to service VM
to support virtulization of SSRAM for post-launched
RTVMs, service VM need to access all SSRAM regions
for management, typically, service VM does data
cleanup in SSRAM region when it is reclaimed from
a shutdown RTVM.

This patch update memory type from UC(by default)
to WB.
You didn't commit why need to update this memory type.
Please update.

Make sense.
With memory type UC in EPT table, any write to SSRAM region will flush
SSRAM cache buffer and make this region cache unlocked.

Thanks for comments.
-Yonghua



Tracked-On: #7425
Signed-off-by: Yonghua Huang <yonghua.huang@...>
---
hypervisor/arch/x86/guest/vm.c | 38
+++++++++++++++++++++++++---------
1 file changed, 28 insertions(+), 10 deletions(-)

diff --git a/hypervisor/arch/x86/guest/vm.c
b/hypervisor/arch/x86/guest/vm.c index 786b390fa4e8..aff477abb0ac
100644
--- a/hypervisor/arch/x86/guest/vm.c
+++ b/hypervisor/arch/x86/guest/vm.c
@@ -538,16 +538,34 @@ static void prepare_service_vm_memmap(struct
acrn_vm *vm)
pci_mmcfg = get_mmcfg_region();
ept_del_mr(vm, (uint64_t *)vm->arch_vm.nworld_eptp,
pci_mmcfg->address, get_pci_mmcfg_size(pci_mmcfg));

- /* remove Software SRAM region from Service VM EPT, to prevent
Service VM from using clflush to
- * flush the Software SRAM cache.
- * This is applicable to prelaunch RTVM case only. And only the part
that prelaunch RTVM uses needs
- * to be removed from Service VM EPT.
- *
- * PRE_RTVM_SW_SRAM_MAX_SIZE is the size of Software SRAM
that prelaunch RTVM uses, presumed to be
- * starting from Software SRAM base. For other cases,
PRE_RTVM_SW_SRAM_MAX_SIZE should be defined
- * as 0, and no region is removed from Service VM EPT.
- */
- ept_del_mr(vm, pml4_page,
service_vm_hpa2gpa(get_software_sram_base()),
PRE_RTVM_SW_SRAM_MAX_SIZE);
+ if (is_software_sram_enabled()) {
+ /*
+ * Native Software SRAM resources shall be assigned to either
Pre-launched RTVM
+ * or Service VM. Software SRAM support for Post-launched
RTVM is virtualized
+ * in Service VM.
+ *
+ * 1) Native Software SRAM resources are assigned to Pre-
launched RTVM:
+ * - Remove Software SRAM regions from Service VM EPT,
to prevent
+ * Service VM from using clflush to flush the Software
SRAM cache.
+ * - PRE_RTVM_SW_SRAM_MAX_SIZE is the size of
Software SRAM that
+ * Pre-launched RTVM uses, presumed to be starting from
Software SRAM base.
+ * For other cases, PRE_RTVM_SW_SRAM_MAX_SIZE
should be defined as 0,
+ * and no region will be removed from Service VM EPT.
+ *
+ * 2) Native Software SRAM resources are assigned to Service
VM:
+ * - Software SRAM regions are added to EPT of Service VM
by default
+ * with memory type UC.
+ * - But, Service VM need to access Software SRAM regions
+ * when virtualizing them for Post-launched RTVM.
+ * - So memory type of Software SRAM regions in EPT shall
be updated to EPT_WB.
+ */
+#if (PRE_RTVM_SW_SRAM_MAX_SIZE > 0U)
+ ept_del_mr(vm, pml4_page,
+service_vm_hpa2gpa(get_software_sram_base()),
PRE_RTVM_SW_SRAM_MAX_SIZE); #else
+ ept_modify_mr(vm, pml4_page,
service_vm_hpa2gpa(get_software_sram_base()),
+ get_software_sram_size(), EPT_WB, EPT_MT_MASK);
#endif
+ }

/* unmap Intel IOMMU register pages for below reason:
* Service VM can detect IOMMU capability in its ACPI table hence it
may access
--
2.25.1






Re: [PATCH v5 4/8] hv: add vRTC reg_b and reg_c support

Zhao, Yuanyuan
 

On 5/7/2022 2:00 PM, Junjie Mao wrote:
Yuanyuan Zhao <yuanyuan.zhao@...> writes:

During setting RTC time, driver will halt RTC update. So support
bit 7 of reg_b. When it set to 0, time will be updated. And
when it's set to 1, rtc will keep the second it was set.

In the process of getting RTC time, driver sets alarm interrupt,
waits 1s, and get alarm interrupt flag. So support alarm interrupt
flag update. If alarm interrupt is enabled (bit 5, reg_b set to 1),
interrupt flag register will be set (bit 7 & bit 5 of reg_c) at
appropriate time.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
---
hypervisor/dm/vrtc.c | 70 +++++++++++++++++++++++++++--
hypervisor/include/dm/mc146818rtc.h | 5 +++
2 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 79ee2439d..1d2416df2 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -422,6 +422,41 @@ static uint8_t cmos_get_reg_val(uint8_t addr)
return reg;
}
+#define TRIGGER_ALARM (RTCIR_ALARM | RTCIR_INT)
+#define RTC_DELTA 1 /* For RTC and system time may out of sync for no more than 1s */
+static inline bool rtc_halted(struct acrn_vrtc *rtc)
+{
+ return ((rtc->rtcdev.reg_b & RTCSB_HALT) != 0U);
+}
+
+static uint8_t vrtc_get_reg_c(struct acrn_vrtc *vrtc)
+{
+ uint8_t ret = vrtc->rtcdev.reg_c;
+ struct rtcdev *rtc = &vrtc->rtcdev;
+ time_t current, alarm;
+
+ if ((rtc->reg_b & RTCSB_AINTR) != 0U) {
+ current = rtc->hour * 3600 + rtc->min * 60 + rtc->sec;
+ alarm = rtc->alarm_hour * 3600 + rtc->alarm_min * 60 + rtc->alarm_sec;
+
+ if (current >= alarm - RTC_DELTA && current <= alarm + RTC_DELTA) {
+ /*
+ * If alarm interrut is enabled and rtc time is in alarm time scale,
+ * set the interrupt flag as alarm triggered.
I do not see any change to the comment here. At least highlight that
alarm interrupts are not implemented, but only the alarm flag, and why
that is sufficient for now.
If alarm interrut is enabled and rtc time is in alarm time scale,
set the interrupt flag. The interrupt is not acturally triggered for driver will read the register.


Re: [PATCH v4 7/8] hv: vrtc calibrate function

Junjie Mao
 

"Zhao, Yuanyuan" <yuanyuan.zhao@...> writes:

On 5/7/2022 1:54 PM, Junjie Mao wrote:
"Zhao, Yuanyuan" <yuanyuan.zhao@...> writes:

On 5/6/2022 10:23 AM, Junjie Mao wrote:
"Zhao, Yuanyuan" <yuanyuan.zhao@...> writes:

On 5/6/2022 9:46 AM, Junjie Mao wrote:
Yuanyuan Zhao <yuanyuan.zhao@...> writes:

For TSC's precision (20~100ppm) is lower than physical RTC (less than 20ppm),
vRTC need to be calibrated by physical RTC. A timer tiggers calibration for
vRTC every 3 hours. This can improve efficiency because physical RTC can be
read once and calibrate all vRTC.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
---
hypervisor/dm/vrtc.c | 80 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 77 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index ba0f510e2..ee9b47087 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -51,6 +51,8 @@ struct clktime {
uint32_t dow; /* day of week (0 - 6; 0 = Sunday) */
};
+static spinlock_t calibrate_lock = { .head = 0U, .tail = 0U };
`Calibrate` is a verb and thus makes `calibrate lock` misleading
(because you are not calibrate a lock).
What's the complete list of variables you want to protect using this
lock?

+
#define POSIX_BASE_YEAR 1970
#define SECDAY (24 * 60 * 60)
#define SECYR (SECDAY * 365)
@@ -381,10 +383,12 @@ static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc)
uint64_t offset;
time_t second = VRTC_BROKEN_TIME;
+ spinlock_obtain(&calibrate_lock);
if (vrtc->base_rtctime > 0) {
offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U);
second = vrtc->base_rtctime + vrtc->offset_rtctime + (time_t)offset;
}
+ spinlock_release(&calibrate_lock);
return second;
}
@@ -564,7 +568,9 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t
addr, size_t width,
*((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0xFFU);
current = vrtc_get_current_time(vrtc);
after = rtc_to_secs(vrtc);
+ spinlock_obtain(&calibrate_lock);
vrtc->offset_rtctime += after - current;
+ spinlock_release(&calibrate_lock);
break;
}
}
@@ -573,9 +579,70 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
return true;
}
+#define CALIBRATE_PERIOD (3 * 3600 * 1000) /* By ms, totally 3
hours. */
+static struct hv_timer calibrate_timer;
+
+static time_t vrtc_get_physical_rtc_time(struct acrn_vrtc *vrtc)
+{
+ uint8_t *rtc = (uint8_t *)&vrtc->rtcdev;
+
+ *(rtc + RTC_SEC) = cmos_get_reg_val(RTC_SEC);
+ *(rtc + RTC_MIN) = cmos_get_reg_val(RTC_MIN);
+ *(rtc + RTC_HRS) = cmos_get_reg_val(RTC_HRS);
+ *(rtc + RTC_DAY) = cmos_get_reg_val(RTC_DAY);
+ *(rtc + RTC_MONTH) = cmos_get_reg_val(RTC_MONTH);
+ *(rtc + RTC_YEAR) = cmos_get_reg_val(RTC_YEAR);
+ *(rtc + RTC_CENTURY) = cmos_get_reg_val(RTC_CENTURY);
+ *(rtc + RTC_STATUSB) = cmos_get_reg_val(RTC_STATUSB);
+
+ return rtc_to_secs(vrtc);
+}
+
+static void vrtc_update_basetime(time_t physical_time, time_t offset)
+{
+ struct acrn_vm *vm;
+ uint32_t vm_id;
+
+ for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
+ vm = get_vm_from_vmid(vm_id);
+ if (is_rt_vm(vm) || is_prelaunched_vm(vm)) {
+ spinlock_obtain(&calibrate_lock);
+ vm->vrtc.base_tsc = cpu_ticks();
+ vm->vrtc.base_rtctime = physical_time;
+ vm->vrtc.offset_rtctime += offset;
I don't see any change to ensure monotonicity. Have I missed anything?
Monotonicity ensured by calibration period:


For TSC's precision (20~100ppm) is lower than physical RTC (less than
20ppm),
vRTC need to be calibrated by physical RTC. A timer tiggers calibration for
vRTC every 3 hours. This can improve efficiency because physical RTC can be
read once and calibrate all vRTC.
How is monotonicity derived from the method above?
1. Given the parameters above, the wrost-case difference between two
clocks can be 120ppm, which is ~1.3s.
2. Even the rebasing is done more frequently to control the worst-case
difference, there's still a possibility that the vRTC is rebased like
the following:
1ms later, -10ms
12:30:58 --> 12:30:59 ----------------> rebased to 12:30:58
system time: 12:30:59 ----------------> get rtc time 12:30:58

---> wait alarm interrupt 12:30:59 ----> system time 10:30.59
There is no restriction that RTC must be read after an alarm interrupt.
I think monotonicity is not a problem that only we encounter, becuse RTC
graduation is 1s. Under normal scenario, the use of calibrate system
time by RTC may also cause system time rollback though the difference is less
than 1s. So trigger alarm and get time again is a necessary option.
We are referring to different problems. I'm talking about the
monotonicity of two consecutive readings from vRTC (regardless of why a
VM does that), while you are talking about the calibration between vRTC
and system time in the VM.

Or I can shorten the calibrate period if you think it's necessary.
Theoretically shortening the period cannot avoid this completely. An
ad-hoc way is to memorize the last reading (in seconds, as 32-bit
integer) of a vRTC and check against it when that VM reads vRTC again
(as long as it does not modify the vRTC date and time after its last
read).

--
Best Regards
Junjie Mao


Re: [PATCH v4 7/8] hv: vrtc calibrate function

Zhao, Yuanyuan
 

On 5/7/2022 1:54 PM, Junjie Mao wrote:
"Zhao, Yuanyuan" <yuanyuan.zhao@...> writes:

On 5/6/2022 10:23 AM, Junjie Mao wrote:
"Zhao, Yuanyuan" <yuanyuan.zhao@...> writes:

On 5/6/2022 9:46 AM, Junjie Mao wrote:
Yuanyuan Zhao <yuanyuan.zhao@...> writes:

For TSC's precision (20~100ppm) is lower than physical RTC (less than 20ppm),
vRTC need to be calibrated by physical RTC. A timer tiggers calibration for
vRTC every 3 hours. This can improve efficiency because physical RTC can be
read once and calibrate all vRTC.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
---
hypervisor/dm/vrtc.c | 80 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 77 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index ba0f510e2..ee9b47087 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -51,6 +51,8 @@ struct clktime {
uint32_t dow; /* day of week (0 - 6; 0 = Sunday) */
};
+static spinlock_t calibrate_lock = { .head = 0U, .tail = 0U };
`Calibrate` is a verb and thus makes `calibrate lock` misleading
(because you are not calibrate a lock).
What's the complete list of variables you want to protect using this
lock?

+
#define POSIX_BASE_YEAR 1970
#define SECDAY (24 * 60 * 60)
#define SECYR (SECDAY * 365)
@@ -381,10 +383,12 @@ static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc)
uint64_t offset;
time_t second = VRTC_BROKEN_TIME;
+ spinlock_obtain(&calibrate_lock);
if (vrtc->base_rtctime > 0) {
offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U);
second = vrtc->base_rtctime + vrtc->offset_rtctime + (time_t)offset;
}
+ spinlock_release(&calibrate_lock);
return second;
}
@@ -564,7 +568,9 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t
addr, size_t width,
*((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0xFFU);
current = vrtc_get_current_time(vrtc);
after = rtc_to_secs(vrtc);
+ spinlock_obtain(&calibrate_lock);
vrtc->offset_rtctime += after - current;
+ spinlock_release(&calibrate_lock);
break;
}
}
@@ -573,9 +579,70 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
return true;
}
+#define CALIBRATE_PERIOD (3 * 3600 * 1000) /* By ms, totally 3
hours. */
+static struct hv_timer calibrate_timer;
+
+static time_t vrtc_get_physical_rtc_time(struct acrn_vrtc *vrtc)
+{
+ uint8_t *rtc = (uint8_t *)&vrtc->rtcdev;
+
+ *(rtc + RTC_SEC) = cmos_get_reg_val(RTC_SEC);
+ *(rtc + RTC_MIN) = cmos_get_reg_val(RTC_MIN);
+ *(rtc + RTC_HRS) = cmos_get_reg_val(RTC_HRS);
+ *(rtc + RTC_DAY) = cmos_get_reg_val(RTC_DAY);
+ *(rtc + RTC_MONTH) = cmos_get_reg_val(RTC_MONTH);
+ *(rtc + RTC_YEAR) = cmos_get_reg_val(RTC_YEAR);
+ *(rtc + RTC_CENTURY) = cmos_get_reg_val(RTC_CENTURY);
+ *(rtc + RTC_STATUSB) = cmos_get_reg_val(RTC_STATUSB);
+
+ return rtc_to_secs(vrtc);
+}
+
+static void vrtc_update_basetime(time_t physical_time, time_t offset)
+{
+ struct acrn_vm *vm;
+ uint32_t vm_id;
+
+ for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
+ vm = get_vm_from_vmid(vm_id);
+ if (is_rt_vm(vm) || is_prelaunched_vm(vm)) {
+ spinlock_obtain(&calibrate_lock);
+ vm->vrtc.base_tsc = cpu_ticks();
+ vm->vrtc.base_rtctime = physical_time;
+ vm->vrtc.offset_rtctime += offset;
I don't see any change to ensure monotonicity. Have I missed anything?
Monotonicity ensured by calibration period:


For TSC's precision (20~100ppm) is lower than physical RTC (less than
20ppm),
vRTC need to be calibrated by physical RTC. A timer tiggers calibration for
vRTC every 3 hours. This can improve efficiency because physical RTC can be
read once and calibrate all vRTC.
How is monotonicity derived from the method above?
1. Given the parameters above, the wrost-case difference between two
clocks can be 120ppm, which is ~1.3s.
2. Even the rebasing is done more frequently to control the worst-case
difference, there's still a possibility that the vRTC is rebased like
the following:
1ms later, -10ms
12:30:58 --> 12:30:59 ----------------> rebased to 12:30:58
system time: 12:30:59 ----------------> get rtc time 12:30:58

---> wait alarm interrupt 12:30:59 ----> system time 10:30.59
There is no restriction that RTC must be read after an alarm interrupt.
I think monotonicity is not a problem that only we encounter, becuse RTC graduation is 1s. Under normal scenario, the use of calibrate system time by RTC may also cause system time rollback though the difference is less than 1s. So trigger alarm and get time again is a necessary option.

Or I can shorten the calibrate period if you think it's necessary.


Re: [PATCH v5 6/8] hv: add time modification of vrtc

Junjie Mao
 

Yuanyuan Zhao <yuanyuan.zhao@...> writes:

VRTC for hv used to ignore writes to vRTC register.

This patch add time modification to vRTC.
Add base RTC time and TSC offset to get the current time. Convert
current time to vRTC register values (`struct rtcdev`). Then
modify a register, and calculate a new time. Get RTC offset by
substrcting new time from current time.
Thereafter, add RTC offset also when get current time. Then user
can get the modified time.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
Reviewed-by: Junjie Mao <junjie.mao@...>

---
hypervisor/dm/vrtc.c | 14 +++++++++++++-
hypervisor/include/dm/vrtc.h | 2 ++
2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 7295d7d58..4a404cb69 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -383,7 +383,7 @@ static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc)

if (vrtc->base_rtctime > 0) {
offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U);
- second = vrtc->base_rtctime + (time_t)offset;
+ second = vrtc->base_rtctime + vrtc->offset_rtctime + (time_t)offset;
}
return second;
}
@@ -527,6 +527,7 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
uint32_t value)
{
+ time_t current, after;
struct acrn_vrtc *vrtc = &vcpu->vm->vrtc;

if ((width == 1U) && (addr == CMOS_ADDR_PORT)) {
@@ -552,7 +553,18 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
*((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0x7FU);
RTC_DEBUG("RTC alarm reg(%d) set to %#x (ignored)\n", vrtc->addr, value);
break;
+ case RTC_SEC:
+ /*
+ * High order bit of 'seconds' is readonly.
+ */
+ value &= 0x7f;
+ /* FALLTHRU */
default:
+ RTC_DEBUG("RTC offset %#x set to %#x\n", vrtc->addr, value);
+ *((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0xFFU);
+ current = vrtc_get_current_time(vrtc);
+ after = rtc_to_secs(vrtc);
+ vrtc->offset_rtctime += after - current;
break;
}
}
diff --git a/hypervisor/include/dm/vrtc.h b/hypervisor/include/dm/vrtc.h
index fff2559d2..b760ce445 100644
--- a/hypervisor/include/dm/vrtc.h
+++ b/hypervisor/include/dm/vrtc.h
@@ -34,6 +34,8 @@ struct acrn_vrtc {
uint32_t addr; /* RTC register to read or write */

time_t base_rtctime; /* Base time calulated from physical rtc register. */
+ time_t offset_rtctime; /* RTC offset against base time. */
+
uint64_t base_tsc; /* Base tsc value */

struct rtcdev rtcdev; /* RTC register */
--
Best Regards
Junjie Mao


Re: [PATCH v5 4/8] hv: add vRTC reg_b and reg_c support

Junjie Mao
 

Yuanyuan Zhao <yuanyuan.zhao@...> writes:

During setting RTC time, driver will halt RTC update. So support
bit 7 of reg_b. When it set to 0, time will be updated. And
when it's set to 1, rtc will keep the second it was set.

In the process of getting RTC time, driver sets alarm interrupt,
waits 1s, and get alarm interrupt flag. So support alarm interrupt
flag update. If alarm interrupt is enabled (bit 5, reg_b set to 1),
interrupt flag register will be set (bit 7 & bit 5 of reg_c) at
appropriate time.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
---
hypervisor/dm/vrtc.c | 70 +++++++++++++++++++++++++++--
hypervisor/include/dm/mc146818rtc.h | 5 +++
2 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 79ee2439d..1d2416df2 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -422,6 +422,41 @@ static uint8_t cmos_get_reg_val(uint8_t addr)
return reg;
}

+#define TRIGGER_ALARM (RTCIR_ALARM | RTCIR_INT)
+#define RTC_DELTA 1 /* For RTC and system time may out of sync for no more than 1s */
+static inline bool rtc_halted(struct acrn_vrtc *rtc)
+{
+ return ((rtc->rtcdev.reg_b & RTCSB_HALT) != 0U);
+}
+
+static uint8_t vrtc_get_reg_c(struct acrn_vrtc *vrtc)
+{
+ uint8_t ret = vrtc->rtcdev.reg_c;
+ struct rtcdev *rtc = &vrtc->rtcdev;
+ time_t current, alarm;
+
+ if ((rtc->reg_b & RTCSB_AINTR) != 0U) {
+ current = rtc->hour * 3600 + rtc->min * 60 + rtc->sec;
+ alarm = rtc->alarm_hour * 3600 + rtc->alarm_min * 60 + rtc->alarm_sec;
+
+ if (current >= alarm - RTC_DELTA && current <= alarm + RTC_DELTA) {
+ /*
+ * If alarm interrut is enabled and rtc time is in alarm time scale,
+ * set the interrupt flag as alarm triggered.
I do not see any change to the comment here. At least highlight that
alarm interrupts are not implemented, but only the alarm flag, and why
that is sufficient for now.

--
Best Regards
Junjie Mao

+ */
+ ret |= TRIGGER_ALARM;
+ }
+ }
+
+ vrtc->rtcdev.reg_c = 0;
+ return ret;
+}
+
+static void vrtc_set_reg_b(struct acrn_vrtc *vrtc, uint8_t newval)
+{
+ vrtc->rtcdev.reg_b = newval;
+}
+
/**
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
@@ -435,7 +470,7 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
struct acrn_vm *vm = vcpu->vm;
bool ret = true;

- offset = vm->vrtc.addr;
+ offset = vrtc->addr;

if (addr == CMOS_ADDR_PORT) {
pio_req->value = offset;
@@ -447,7 +482,11 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
current = vrtc_get_current_time(vrtc);
secs_to_rtc(current, vrtc);

- pio_req->value = *((uint8_t *)&vm->vrtc.rtcdev + offset);
+ if(offset == 0xCU) {
+ pio_req->value = vrtc_get_reg_c(vrtc);
+ } else {
+ pio_req->value = *((uint8_t *)&vrtc->rtcdev + offset);
+ }
RTC_DEBUG("read 0x%x, 0x%x", offset, pio_req->value);
} else {
RTC_ERR("vrtc read invalid addr 0x%x", offset);
@@ -466,8 +505,33 @@ static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t wid
static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
uint32_t value)
{
+ struct acrn_vrtc *vrtc = &vcpu->vm->vrtc;
+
if ((width == 1U) && (addr == CMOS_ADDR_PORT)) {
- vcpu->vm->vrtc.addr = (uint8_t)value & 0x7FU;
+ vrtc->addr = (uint8_t)(value & 0x7FU);
+ } else {
+ if (!is_service_vm(vcpu->vm)) {
+ switch (vrtc->addr) {
+ case RTC_STATUSA:
+ case RTC_INTR:
+ case RTC_STATUSD:
+ RTC_DEBUG("RTC reg_%x set to %#x (ignored)\n", vrtc->addr, value);
+ break;
+ case RTC_STATUSB:
+ vrtc_set_reg_b(vrtc, value);
+ RTC_DEBUG("RTC reg_b set to %#x\n", value);
+ break;
+ case RTC_SECALRM:
+ case RTC_MINALRM:
+ /* FALLTHRU */
+ case RTC_HRSALRM:
+ *((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0x7FU);
+ RTC_DEBUG("RTC alarm reg(%d) set to %#x (ignored)\n", vrtc->addr, value);
+ break;
+ default:
+ break;
+ }
+ }
}

return true;
diff --git a/hypervisor/include/dm/mc146818rtc.h b/hypervisor/include/dm/mc146818rtc.h
index b23f90d34..1681cc960 100644
--- a/hypervisor/include/dm/mc146818rtc.h
+++ b/hypervisor/include/dm/mc146818rtc.h
@@ -60,8 +60,13 @@
#define RTC_STATUSB 0x0b /* status register B */
#define RTCSB_24HR 0x02 /* 0 = 12 hours, 1 = 24 hours */
#define RTCSB_BCD 0x04 /* 0 = BCD, 1 = Binary coded time */
+#define RTCSB_AINTR 0x20 /* 1 = enable alarm interrupt */
+#define RTCSB_HALT 0x80 /* stop clock updates */

#define RTC_INTR 0x0c /* status register C (R) interrupt source */
+#define RTCIR_ALARM 0x20 /* alarm intr */
+#define RTCIR_INT 0x80 /* interrupt output signal */
+
#define RTC_STATUSD 0x0d /* status register D (R) Lost Power */

#endif /* _MC146818_RTC_H_ */


Re: [PATCH v5 3/8] hv: support 12 hour mode

Junjie Mao
 

Yuanyuan Zhao <yuanyuan.zhao@...> writes:

Add support for hour format.
Bit 1 of register B indicates the hour byte format. When it's 0,
twelve-hour mode is selected. Then the seventh bit of hour register
presents AM as 0 and PM as 1.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
Reviewed-by: Junjie Mao <junjie.mao@...>

One stylish comment below.

---
hypervisor/dm/vrtc.c | 76 +++++++++++++++++++++++++++--
hypervisor/include/dm/mc146818rtc.h | 1 +
2 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index d65c27cbf..79ee2439d 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -239,12 +239,12 @@ static time_t rtc_to_secs(const struct acrn_vrtc *vrtc)
struct clktime ct;
time_t second = VRTC_BROKEN_TIME;
const struct rtcdev *rtc= &vrtc->rtcdev;
- int32_t err = 0;
+ int32_t err = 0, hour = 0, pm = 0;
Fix the space issue in the FIRST patch that introduces it.

--
Best Regards
Junjie Mao

uint32_t century = 0, year = 0;

do {
if (rtcget(rtc, rtc->sec, &ct.sec) < 0 || rtcget(rtc, rtc->min, &ct.min) < 0 ||
- rtcget(rtc, rtc->hour, &ct.hour) < 0 || rtcget(rtc, rtc->day_of_month, &ct.day) < 0 ||
+ rtcget(rtc, rtc->day_of_month, &ct.day) < 0 ||
rtcget(rtc, rtc->month, &ct.mon) < 0 || rtcget(rtc, rtc->year, &year) < 0 ||
rtcget(rtc, rtc->century, &century) < 0) {
RTC_ERR("Invalid RTC sec %#x hour %#x day %#x mon %#x year %#x century %#x\n",
@@ -254,6 +254,47 @@ static time_t rtc_to_secs(const struct acrn_vrtc *vrtc)
break;
}

+ /*
+ * If 12 hour format is inuse, translate it to 24 hour format here.
+ */
+ pm = 0;
+ hour = rtc->hour;
+ if ((rtc->reg_b & RTCSB_24HR) == 0) {
+ if (hour & 0x80U) {
+ hour &= ~0x80U;
+ pm = 1;
+ }
+ }
+ err = rtcget(rtc, hour, &ct.hour);
+ if (err) {
+ RTC_ERR("Invalid RTC hour %#x/%d\n", rtc->hour, ct.hour);
+ break;
+ }
+ if ((rtc->reg_b & RTCSB_24HR) == 0) {
+ if (ct.hour >= 1 && ct.hour <= 12) {
+ /*
+ * Convert from 12-hour format to internal 24-hour
+ * representation as follows:
+ *
+ * 12-hour format ct.hour
+ * 12 AM 0
+ * 1 - 11 AM 1 - 11
+ * 12 PM 12
+ * 1 - 11 PM 13 - 23
+ */
+ if (ct.hour == 12) {
+ ct.hour = 0;
+ }
+ if (pm) {
+ ct.hour += 12;
+ }
+ } else {
+ RTC_ERR("Invalid RTC 12-hour format %#x/%d\n",
+ rtc->hour, ct.hour);
+ break;
+ }
+ }
+
/*
* Ignore 'rtc->dow' because some guests like Linux don't bother
* setting it at all while others like OpenBSD/i386 set it incorrectly.
@@ -289,12 +330,41 @@ static void secs_to_rtc(time_t rtctime, struct acrn_vrtc *vrtc)
{
struct clktime ct;
struct rtcdev *rtc;
+ int32_t hour;

if (rtctime > 0 && clk_ts_to_ct(rtctime, &ct) == 0) {
rtc = &vrtc->rtcdev;
rtc->sec = rtcset(rtc, ct.sec);
rtc->min = rtcset(rtc, ct.min);
- rtc->hour = rtcset(rtc, ct.hour);
+
+ if (rtc->reg_b & RTCSB_24HR) {
+ hour = ct.hour;
+ } else {
+ /*
+ * Convert to the 12-hour format.
+ */
+ switch (ct.hour) {
+ case 0: /* 12 AM */
+ case 12: /* 12 PM */
+ hour = 12;
+ break;
+ default:
+ /*
+ * The remaining 'ct.hour' values are interpreted as:
+ * [1 - 11] -> 1 - 11 AM
+ * [13 - 23] -> 1 - 11 PM
+ */
+ hour = ct.hour % 12;
+ break;
+ }
+ }
+
+ rtc->hour = rtcset(rtc, hour);
+
+ if ((rtc->reg_b & RTCSB_24HR) == 0 && ct.hour >= 12) {
+ rtc->hour |= 0x80; /* set MSB to indicate PM */
+ }
+
rtc->day_of_week = rtcset(rtc, ct.dow + 1);
rtc->day_of_month = rtcset(rtc, ct.day);
rtc->month = rtcset(rtc, ct.mon);
diff --git a/hypervisor/include/dm/mc146818rtc.h b/hypervisor/include/dm/mc146818rtc.h
index 4baf9de69..b23f90d34 100644
--- a/hypervisor/include/dm/mc146818rtc.h
+++ b/hypervisor/include/dm/mc146818rtc.h
@@ -58,6 +58,7 @@
#define RTCSA_TUP 0x80 /* time update, don't look now */

#define RTC_STATUSB 0x0b /* status register B */
+#define RTCSB_24HR 0x02 /* 0 = 12 hours, 1 = 24 hours */
#define RTCSB_BCD 0x04 /* 0 = BCD, 1 = Binary coded time */

#define RTC_INTR 0x0c /* status register C (R) interrupt source */