From patchwork Mon Mar 19 06:14:31 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Zhang, Yang Z" X-Patchwork-Id: 147532 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 4037FB6EEC for ; Tue, 20 Mar 2012 01:14:25 +1100 (EST) Received: from localhost ([::1]:55611 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1S9d0I-0007bw-D4 for incoming@patchwork.ozlabs.org; Mon, 19 Mar 2012 09:52:06 -0400 Received: from eggs.gnu.org ([208.118.235.92]:54792) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1S9Vrw-0005OD-0h for qemu-devel@nongnu.org; Mon, 19 Mar 2012 02:15:01 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1S9Vrb-0001Y2-5V for qemu-devel@nongnu.org; Mon, 19 Mar 2012 02:14:59 -0400 Received: from mga14.intel.com ([143.182.124.37]:2564) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1S9Vra-0001XS-IG for qemu-devel@nongnu.org; Mon, 19 Mar 2012 02:14:39 -0400 Received: from azsmga001.ch.intel.com ([10.2.17.19]) by azsmga102.ch.intel.com with ESMTP; 18 Mar 2012 23:14:36 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="4.71,315,1320652800"; d="scan'208";a="120496861" Received: from azsmsx603.amr.corp.intel.com ([10.2.161.23]) by azsmga001.ch.intel.com with ESMTP; 18 Mar 2012 23:14:36 -0700 Received: from azsmsx604.amr.corp.intel.com (10.2.161.34) by azsmsx603.amr.corp.intel.com (10.2.161.23) with Microsoft SMTP Server (TLS) id 8.2.255.0; Sun, 18 Mar 2012 23:14:35 -0700 Received: from shsmsx151.ccr.corp.intel.com (10.239.6.50) by azsmsx604.amr.corp.intel.com (10.2.161.34) with Microsoft SMTP Server (TLS) id 8.2.255.0; Sun, 18 Mar 2012 23:14:35 -0700 Received: from shsmsx101.ccr.corp.intel.com ([169.254.1.142]) by SHSMSX151.ccr.corp.intel.com ([169.254.3.91]) with mapi id 14.01.0355.002; Mon, 19 Mar 2012 14:14:33 +0800 From: "Zhang, Yang Z" To: "qemu-devel@nongnu.org" Thread-Topic: [PATCH v4 6/7] RTC:Add alarm support Thread-Index: Ac0Fl4vGPfmNL4oWR16v7w1ILExDVw== Date: Mon, 19 Mar 2012 06:14:31 +0000 Message-ID: Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-originating-ip: [10.239.127.40] MIME-Version: 1.0 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 143.182.124.37 X-Mailman-Approved-At: Mon, 19 Mar 2012 09:50:26 -0400 Cc: Paolo Bonzini , "aliguori@us.ibm.com" , "kvm@vger.kernel.org" Subject: [Qemu-devel] [PATCH v4 6/7] RTC:Add alarm support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Changing in this patch: Set the timer to one second earlier before target alarm when AF bit is clear. In version 3, in order to solve the async between UF, AF and UIP, the timer will keep running when UF or AF are clear. This is a little ugly, especially when a userspace program is using the alarm and we cannot achieve any power saving. In this version, when the AF bit is cleared, we will set the timer to one second earlier before the alarm. With this changing, we can avoid the unnecessary timer and keep the sync between UF, AF and UIP. Set the timer to one second earlier before target alarm when AF bit is clear. Signed-off-by: Yang Zhang --- hw/mc146818rtc.c | 274 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 files changed, 255 insertions(+), 19 deletions(-) -- 1.7.1 diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c index fae049e..c03606f 100644 --- a/hw/mc146818rtc.c +++ b/hw/mc146818rtc.c @@ -46,6 +46,11 @@ #define USEC_PER_SEC 1000000L #define NS_PER_USEC 1000L +#define NS_PER_SEC 1000000000ULL +#define SEC_PER_MIN 60 +#define SEC_PER_HOUR 3600 +#define MIN_PER_HOUR 60 +#define HOUR_PER_DAY 24 #define RTC_REINJECT_ON_ACK_COUNT 20 @@ -114,6 +119,8 @@ typedef struct RTCState { static void rtc_set_time(RTCState *s); static void rtc_calibrate_time(RTCState *s); static void rtc_set_cmos(RTCState *s); +static inline int rtc_from_bcd(RTCState *s, int a); +static uint64_t get_next_alarm(RTCState *s); static int32_t divider_reset; @@ -232,29 +239,47 @@ static void rtc_periodic_timer(void *opaque) static void check_update_timer(RTCState *s) { uint64_t next_update_time, expire_time; - uint64_t guest_usec; + uint64_t guest_usec, next_alarm_sec; + qemu_del_timer(s->update_timer); qemu_del_timer(s->update_timer2); - if (!((s->cmos_data[RTC_REG_C] & (REG_C_UF | REG_C_AF)) == - (REG_C_UF | REG_C_AF)) && !(s->cmos_data[RTC_REG_B] & REG_B_SET)) { - s->use_timer = 1; + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { guest_usec = get_guest_rtc_us(s) % USEC_PER_SEC; - if (guest_usec >= (USEC_PER_SEC - 244)) { - /* RTC is in update cycle when enabling UIE */ - s->cmos_data[RTC_REG_A] |= REG_A_UIP; - next_update_time = (USEC_PER_SEC - guest_usec) * NS_PER_USEC; - expire_time = qemu_get_clock_ns(rtc_clock) + next_update_time; - qemu_mod_timer(s->update_timer2, expire_time); - } else { - next_update_time = (USEC_PER_SEC - guest_usec - 244) * NS_PER_USEC; - expire_time = qemu_get_clock_ns(rtc_clock) + next_update_time; - s->next_update_time = expire_time; - qemu_mod_timer(s->update_timer, expire_time); + /* if UF is clear, reprogram to next second */ + if (!(s->cmos_data[RTC_REG_C] & REG_C_UF)) { +program_next_second: + s->use_timer = 1; + if (guest_usec >= (USEC_PER_SEC - 244)) { + /* RTC is in update cycle when enabling UIE */ + s->cmos_data[RTC_REG_A] |= REG_A_UIP; + next_update_time = (USEC_PER_SEC - guest_usec) * NS_PER_USEC; + expire_time = qemu_get_clock_ns(rtc_clock) + next_update_time; + qemu_mod_timer(s->update_timer2, expire_time); + } else { + next_update_time = (USEC_PER_SEC - guest_usec - 244) + * NS_PER_USEC; + expire_time = qemu_get_clock_ns(rtc_clock) + next_update_time; + s->next_update_time = expire_time; + qemu_mod_timer(s->update_timer, expire_time); + } + return ; + } else if (!(s->cmos_data[RTC_REG_C] & REG_C_AF)) { + /* UF is set, but AF is clear. Program to one second + * earlier before target alarm*/ + next_alarm_sec = get_next_alarm(s); + if (next_alarm_sec == 1) { + goto program_next_second; + } else { + next_update_time = (USEC_PER_SEC - guest_usec) * NS_PER_USEC; + next_update_time += (next_alarm_sec - 1) * NS_PER_SEC; + expire_time = qemu_get_clock_ns(rtc_clock) + next_update_time; + s->next_update_time = expire_time; + qemu_mod_timer(s->update_timer2, expire_time); + } } - } else { - s->use_timer = 0; } + s->use_timer = 0; } static void rtc_update_timer(void *opaque) @@ -267,15 +292,215 @@ static void rtc_update_timer(void *opaque) } } +static inline uint8_t convert_hour(RTCState *s, uint8_t hour) +{ + if (!(s->cmos_data[RTC_REG_B] & REG_B_24H)) { + hour %= 12; + if (s->cmos_data[RTC_HOURS] & 0x80) { + hour += 12; + } + } + return hour; +} + +static uint64_t get_next_alarm(RTCState *s) +{ + int32_t alarm_sec, alarm_min, alarm_hour, cur_hour, cur_min, cur_sec; + int32_t hour, min; + uint64_t next_alarm_sec; + + rtc_calibrate_time(s); + rtc_set_cmos(s); + + alarm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]); + alarm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]); + alarm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]); + alarm_hour = convert_hour(s, alarm_hour); + + cur_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]); + cur_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]); + cur_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS]); + cur_hour = convert_hour(s, cur_hour); + + if ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0) { + if ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0) { + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + /* All of the three alarm are in "don't care" mode */ + next_alarm_sec = 1; + } else if (cur_sec < alarm_sec) { + /* Hour and minute alarm are in "don't care" mode and + * second alarm > current second*/ + next_alarm_sec = alarm_sec - cur_sec; + } else { + /* Hour and minute alarm are in "don't care" mode and + * second alarm < current second*/ + next_alarm_sec = alarm_sec + SEC_PER_MIN - cur_sec; + } + } else { + /* Houre alarm is in "don't care mode', but minute alarm + * is in normal mode*/ + if (cur_min < alarm_min) { + /* minute alarm > current minute */ + min = alarm_min - cur_min; + next_alarm_sec = min * SEC_PER_MIN - cur_sec; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } else if (cur_min == alarm_min) { + /* minute alarm == current minute */ + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec = 1; + } else if (cur_sec < alarm_sec) { + next_alarm_sec = alarm_sec - cur_sec; + } else { + min = alarm_min + MIN_PER_HOUR - cur_min; + next_alarm_sec = + alarm_sec + min * SEC_PER_MIN - cur_sec; + } + } else { + /* minute alarm < current minute */ + min = alarm_min + MIN_PER_HOUR - cur_min; + next_alarm_sec = min * SEC_PER_MIN - cur_sec; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } + } + } else { + /* Hour alarm is not in "don't care mode' */ + if (cur_hour < alarm_hour) { + /* hour alarm > current hour */ + hour = alarm_hour - cur_hour; + next_alarm_sec = hour * SEC_PER_HOUR - + cur_min * SEC_PER_MIN - cur_sec; + if ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0) { + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } else { + next_alarm_sec += alarm_min * SEC_PER_MIN; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } + } else if (cur_hour == alarm_hour) { + /* hour alarm == current hour */ + if ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0) { + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec = 1; + } else if (cur_sec < alarm_sec) { + next_alarm_sec = alarm_sec - cur_sec; + } else { + next_alarm_sec = alarm_sec + SEC_PER_MIN - cur_sec; + } + } else if (cur_min < alarm_min) { + min = alarm_min - cur_min; + next_alarm_sec = min * SEC_PER_MIN - cur_sec; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } else if (cur_min == alarm_min) { + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec = 1; + } else if (cur_sec < alarm_sec) { + next_alarm_sec = alarm_sec - cur_sec; + } else { + hour = alarm_hour + HOUR_PER_DAY - cur_hour; + next_alarm_sec = hour * SEC_PER_HOUR - + cur_min * SEC_PER_MIN - cur_sec; + next_alarm_sec += alarm_min * SEC_PER_MIN + alarm_sec; + } + } else { + hour = alarm_hour + HOUR_PER_DAY - cur_hour; + next_alarm_sec = hour * SEC_PER_HOUR - + cur_min * SEC_PER_MIN - cur_sec; + next_alarm_sec += alarm_min * SEC_PER_MIN; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } + } else { + /* hour alarm < current hour */ + hour = alarm_hour + HOUR_PER_DAY - cur_hour; + next_alarm_sec = hour * SEC_PER_HOUR - + cur_min * SEC_PER_MIN - cur_sec; + if ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0) { + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } else { + next_alarm_sec += alarm_min * SEC_PER_MIN; + if ((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0) { + next_alarm_sec += 0; + } else { + next_alarm_sec += alarm_sec; + } + } + } + } + + return next_alarm_sec; +} + +static uint32_t check_alarm(RTCState *s) +{ + uint8_t alarm_hour, alarm_min, alarm_sec; + uint8_t cur_hour, cur_min, cur_sec; + + rtc_calibrate_time(s); + rtc_set_cmos(s); + + alarm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]); + alarm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]); + alarm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]); + alarm_hour = convert_hour(s, alarm_hour); + + cur_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]); + cur_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]); + cur_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS]); + cur_hour = convert_hour(s, cur_hour); + + if (((alarm_sec & 0xc0) == 0xc0 || alarm_sec == cur_sec) && + ((alarm_min & 0xc0) == 0xc0 || alarm_min == cur_min) && + ((alarm_hour & 0xc0) == 0xc0 || alarm_hour == cur_hour)) { + return 1; + } + return 0; + +} + static void rtc_update_timer2(void *opaque) { RTCState *s = opaque; + int32_t alarm_fired; if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { s->cmos_data[RTC_REG_C] |= REG_C_UF; + if (check_alarm(s)) { + s->cmos_data[RTC_REG_C] |= REG_C_AF; + alarm_fired = 1; + } + s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; - s->cmos_data[RTC_REG_C] |= REG_C_IRQF; - qemu_irq_raise(s->irq); + if ((s->cmos_data[RTC_REG_B] & REG_B_UIE) || + ((alarm_fired == 1) && (s->cmos_data[RTC_REG_B] & REG_B_AIE))) { + s->cmos_data[RTC_REG_C] |= REG_C_IRQF; + qemu_irq_raise(s->irq); + } } check_update_timer(s); } @@ -316,6 +541,7 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) case RTC_MINUTES_ALARM: case RTC_HOURS_ALARM: s->cmos_data[s->cmos_index] = data; + check_update_timer(s); break; case RTC_SECONDS: case RTC_MINUTES: @@ -373,6 +599,16 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) } } } + /* if an interrupt flag is already set when the interrupt + * becomes enabled, raise an interrupt imemediately*/ + if (!(s->cmos_data[RTC_REG_B] & REG_B_UIE) && (data & REG_B_UIE) + && (s->cmos_data[RTC_REG_C] & REG_C_UF)) { + qemu_irq_raise(s->irq); + } + if (!(s->cmos_data[RTC_REG_B] & REG_B_AIE) && (data & REG_B_AIE) + && (s->cmos_data[RTC_REG_C] & REG_C_AF)) { + qemu_irq_raise(s->irq); + } s->cmos_data[RTC_REG_B] = data; periodic_timer_update(s, qemu_get_clock_ns(rtc_clock)); check_update_timer(s);