From patchwork Thu May 17 02:29:12 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: 159801 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 D2C28B6FBA for ; Thu, 17 May 2012 12:30:28 +1000 (EST) Received: from localhost ([::1]:49326 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SUqTy-0006sG-Ni for incoming@patchwork.ozlabs.org; Wed, 16 May 2012 22:30:26 -0400 Received: from eggs.gnu.org ([208.118.235.92]:47825) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SUqSw-0004j4-1h for qemu-devel@nongnu.org; Wed, 16 May 2012 22:29:24 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SUqSt-0007k0-AZ for qemu-devel@nongnu.org; Wed, 16 May 2012 22:29:21 -0400 Received: from mga14.intel.com ([143.182.124.37]:62569) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SUqSs-0007iz-Qg for qemu-devel@nongnu.org; Wed, 16 May 2012 22:29:19 -0400 Received: from azsmga001.ch.intel.com ([10.2.17.19]) by azsmga102.ch.intel.com with ESMTP; 16 May 2012 19:29:16 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="4.71,315,1320652800"; d="scan'208";a="144183594" Received: from azsmsx601.amr.corp.intel.com ([10.2.121.193]) by azsmga001.ch.intel.com with ESMTP; 16 May 2012 19:29:16 -0700 Received: from shsmsx151.ccr.corp.intel.com (10.239.6.50) by azsmsx601.amr.corp.intel.com (10.2.121.193) with Microsoft SMTP Server (TLS) id 8.2.255.0; Wed, 16 May 2012 19:29:15 -0700 Received: from shsmsx101.ccr.corp.intel.com ([169.254.1.6]) by SHSMSX151.ccr.corp.intel.com ([169.254.3.90]) with mapi id 14.01.0355.002; Thu, 17 May 2012 10:29:13 +0800 From: "Zhang, Yang Z" To: "'qemu-devel@nongnu.org'" Thread-Topic: [PATCH v6 6/7] RTC:Add alarm support Thread-Index: Ac0z1Ni66UEQ7yOeSraRixturTbhTQ== Date: Thu, 17 May 2012 02:29:12 +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 Cc: 'Paolo Bonzini' , "'aliguori@us.ibm.com'" , "'qemu-devel@nongnu.org'" Subject: [Qemu-devel] [PATCH v6 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 Use a timer to emulate alarm. The timer runs only when the AF is cleared. Signed-off-by: Yang Zhang --- hw/mc146818rtc.c | 276 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 files changed, 257 insertions(+), 19 deletions(-) -- 1.7.1 diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c index 461e8f3..956f10b 100644 --- a/hw/mc146818rtc.c +++ b/hw/mc146818rtc.c @@ -47,6 +47,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 @@ -84,6 +89,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; @@ -207,29 +214,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) @@ -243,15 +268,217 @@ 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 (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 + || alarm_sec == cur_sec) && + ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 + || alarm_min == cur_min) && + ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 + || alarm_hour == cur_hour)) { + return 1; + } + return 0; + +} + static void rtc_update_timer2(void *opaque) { RTCState *s = opaque; + int32_t alarm_fired = 0; if (rtc_running(s)) { 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); } @@ -278,6 +505,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: @@ -343,6 +571,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);