/*- * Copyright (c) 2011 NetApp, Inc. * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``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 NETAPP, INC 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. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "acpi.h" #include "inout.h" #include "pci_lpc.h" #include "rtc.h" #define IO_RTC 0x70 #define RTC_SEC 0x00 /* seconds */ #define RTC_SEC_ALARM 0x01 #define RTC_MIN 0x02 #define RTC_MIN_ALARM 0x03 #define RTC_HRS 0x04 #define RTC_HRS_ALARM 0x05 #define RTC_WDAY 0x06 #define RTC_DAY 0x07 #define RTC_MONTH 0x08 #define RTC_YEAR 0x09 #define RTC_CENTURY 0x32 /* current century */ #define RTC_STATUSA 0xA #define RTCSA_TUP 0x80 /* time update, don't look now */ #define RTC_STATUSB 0xB #define RTCSB_DST 0x01 #define RTCSB_24HR 0x02 #define RTCSB_BIN 0x04 /* 0 = BCD, 1 = Binary */ #define RTCSB_PINTR 0x40 /* 1 = enable periodic clock interrupt */ #define RTCSB_HALT 0x80 /* stop clock updates */ #define RTC_INTR 0x0c /* status register C (R) interrupt source */ #define RTC_STATUSD 0x0d /* status register D (R) Lost Power */ #define RTCSD_PWR 0x80 /* clock power OK */ #define RTC_NVRAM_START 0x0e #define RTC_NVRAM_END 0x7f #define RTC_NVRAM_SZ (128 - RTC_NVRAM_START) #define nvoff(x) ((x) - RTC_NVRAM_START) #define RTC_DIAG 0x0e #define RTC_RSTCODE 0x0f #define RTC_EQUIPMENT 0x14 #define RTC_LMEM_LSB 0x34 #define RTC_LMEM_MSB 0x35 #define RTC_HMEM_LSB 0x5b #define RTC_HMEM_SB 0x5c #define RTC_HMEM_MSB 0x5d #define m_64KB (64*1024) #define m_16MB (16*1024*1024) #define m_4GB (4ULL*1024*1024*1024) static int addr; static uint8_t rtc_nvram[RTC_NVRAM_SZ]; /* XXX initialize these to default values as they would be from BIOS */ static uint8_t status_a, status_b; static struct { uint8_t hours; uint8_t mins; uint8_t secs; } rtc_alarm; static u_char const bin2bcd_data[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99 }; #define bin2bcd(bin) (bin2bcd_data[bin]) #define rtcout(val) ((status_b & RTCSB_BIN) ? (val) : bin2bcd((val))) static void timevalfix(struct timeval *t1) { if (t1->tv_usec < 0) { t1->tv_sec--; t1->tv_usec += 1000000; } if (t1->tv_usec >= 1000000) { t1->tv_sec++; t1->tv_usec -= 1000000; } } static void timevalsub(struct timeval *t1, const struct timeval *t2) { t1->tv_sec -= t2->tv_sec; t1->tv_usec -= t2->tv_usec; timevalfix(t1); } static int rtc_addr_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, uint32_t *eax, void *arg) { if (bytes != 1) return (-1); if (in) { /* straight read of this register will return 0xFF */ *eax = 0xff; return (0); } switch (*eax & 0x7f) { case RTC_SEC: case RTC_SEC_ALARM: case RTC_MIN: case RTC_MIN_ALARM: case RTC_HRS: case RTC_HRS_ALARM: case RTC_WDAY: case RTC_DAY: case RTC_MONTH: case RTC_YEAR: case RTC_STATUSA: case RTC_STATUSB: case RTC_INTR: case RTC_STATUSD: case RTC_NVRAM_START ... RTC_NVRAM_END: break; default: return (-1); } addr = *eax & 0x7f; return (0); } static int rtc_data_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, uint32_t *eax, void *arg) { int hour; time_t t; struct timeval cur, delta; static struct timeval last; static struct tm tm; if (bytes != 1) return (-1); gettimeofday(&cur, NULL); /* * Increment the cached time only once per second so we can guarantee * that the guest has at least one second to read the hour:min:sec * separately and still get a coherent view of the time. */ delta = cur; timevalsub(&delta, &last); if (delta.tv_sec >= 1 && (status_b & RTCSB_HALT) == 0) { t = cur.tv_sec; localtime_r(&t, &tm); last = cur; } if (in) { switch (addr) { case RTC_SEC_ALARM: *eax = rtc_alarm.secs; break; case RTC_MIN_ALARM: *eax = rtc_alarm.mins; break; case RTC_HRS_ALARM: *eax = rtc_alarm.hours; break; case RTC_SEC: *eax = rtcout(tm.tm_sec); return (0); case RTC_MIN: *eax = rtcout(tm.tm_min); return (0); case RTC_HRS: if (status_b & RTCSB_24HR) hour = tm.tm_hour; else hour = (tm.tm_hour % 12) + 1; *eax = rtcout(hour); /* * If we are representing time in the 12-hour format * then set the MSB to indicate PM. */ if ((status_b & RTCSB_24HR) == 0 && tm.tm_hour >= 12) *eax |= 0x80; return (0); case RTC_WDAY: *eax = rtcout(tm.tm_wday + 1); return (0); case RTC_DAY: *eax = rtcout(tm.tm_mday); return (0); case RTC_MONTH: *eax = rtcout(tm.tm_mon + 1); return (0); case RTC_YEAR: *eax = rtcout(tm.tm_year % 100); return (0); case RTC_STATUSA: *eax = status_a; return (0); case RTC_STATUSB: *eax = status_b; return (0); case RTC_INTR: *eax = 0; return (0); case RTC_STATUSD: *eax = RTCSD_PWR; return (0); case RTC_NVRAM_START ... RTC_NVRAM_END: *eax = rtc_nvram[addr - RTC_NVRAM_START]; return (0); default: return (-1); } } switch (addr) { case RTC_STATUSA: status_a = *eax & ~RTCSA_TUP; break; case RTC_STATUSB: /* XXX not implemented yet XXX */ if (*eax & RTCSB_PINTR) return (-1); status_b = *eax; break; case RTC_STATUSD: /* ignore write */ break; case RTC_SEC_ALARM: rtc_alarm.secs = *eax; break; case RTC_MIN_ALARM: rtc_alarm.mins = *eax; break; case RTC_HRS_ALARM: rtc_alarm.hours = *eax; break; case RTC_SEC: case RTC_MIN: case RTC_HRS: case RTC_WDAY: case RTC_DAY: case RTC_MONTH: case RTC_YEAR: /* * Ignore writes to the time of day registers */ break; case RTC_NVRAM_START ... RTC_NVRAM_END: rtc_nvram[addr - RTC_NVRAM_START] = *eax; break; default: return (-1); } return (0); } void rtc_init(struct vmctx *ctx) { struct timeval cur; struct tm tm; size_t himem; size_t lomem; int err; err = gettimeofday(&cur, NULL); assert(err == 0); (void) localtime_r(&cur.tv_sec, &tm); memset(rtc_nvram, 0, sizeof(rtc_nvram)); rtc_nvram[nvoff(RTC_CENTURY)] = bin2bcd((tm.tm_year + 1900) / 100); /* XXX init diag/reset code/equipment/checksum ? */ /* * Report guest memory size in nvram cells as required by UEFI. * Little-endian encoding. * 0x34/0x35 - 64KB chunks above 16MB, below 4GB * 0x5b/0x5c/0x5d - 64KB chunks above 4GB */ lomem = (vm_get_lowmem_size(ctx) - m_16MB) / m_64KB; rtc_nvram[nvoff(RTC_LMEM_LSB)] = lomem; rtc_nvram[nvoff(RTC_LMEM_MSB)] = lomem >> 8; himem = vm_get_highmem_size(ctx) / m_64KB; rtc_nvram[nvoff(RTC_HMEM_LSB)] = himem; rtc_nvram[nvoff(RTC_HMEM_SB)] = himem >> 8; rtc_nvram[nvoff(RTC_HMEM_MSB)] = himem >> 16; } INOUT_PORT(rtc, IO_RTC, IOPORT_F_INOUT, rtc_addr_handler); INOUT_PORT(rtc, IO_RTC + 1, IOPORT_F_INOUT, rtc_data_handler); static void rtc_dsdt(void) { dsdt_line(""); dsdt_line("Device (RTC)"); dsdt_line("{"); dsdt_line(" Name (_HID, EisaId (\"PNP0B00\"))"); dsdt_line(" Name (_CRS, ResourceTemplate ()"); dsdt_line(" {"); dsdt_indent(2); dsdt_fixed_ioport(IO_RTC, 2); dsdt_fixed_irq(8); dsdt_unindent(2); dsdt_line(" })"); dsdt_line("}"); } LPC_DSDT(rtc_dsdt); /* * Reserve the extended RTC I/O ports although they are not emulated at this * time. */ SYSRES_IO(0x72, 6);