rtc.c revision 221828
1221828Sgrehan/*- 2221828Sgrehan * Copyright (c) 2011 NetApp, Inc. 3221828Sgrehan * All rights reserved. 4221828Sgrehan * 5221828Sgrehan * Redistribution and use in source and binary forms, with or without 6221828Sgrehan * modification, are permitted provided that the following conditions 7221828Sgrehan * are met: 8221828Sgrehan * 1. Redistributions of source code must retain the above copyright 9221828Sgrehan * notice, this list of conditions and the following disclaimer. 10221828Sgrehan * 2. Redistributions in binary form must reproduce the above copyright 11221828Sgrehan * notice, this list of conditions and the following disclaimer in the 12221828Sgrehan * documentation and/or other materials provided with the distribution. 13221828Sgrehan * 14221828Sgrehan * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND 15221828Sgrehan * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16221828Sgrehan * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17221828Sgrehan * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE 18221828Sgrehan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19221828Sgrehan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20221828Sgrehan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21221828Sgrehan * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22221828Sgrehan * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23221828Sgrehan * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24221828Sgrehan * SUCH DAMAGE. 25221828Sgrehan * 26221828Sgrehan * $FreeBSD$ 27221828Sgrehan */ 28221828Sgrehan 29221828Sgrehan#include <sys/cdefs.h> 30221828Sgrehan__FBSDID("$FreeBSD$"); 31221828Sgrehan 32221828Sgrehan#include <sys/types.h> 33221828Sgrehan#include <sys/time.h> 34221828Sgrehan 35221828Sgrehan#include <stdio.h> 36221828Sgrehan#include <time.h> 37221828Sgrehan#include <assert.h> 38221828Sgrehan 39221828Sgrehan#include "inout.h" 40221828Sgrehan 41221828Sgrehan#define IO_RTC 0x70 42221828Sgrehan 43221828Sgrehan#define RTC_SEC 0x00 /* seconds */ 44221828Sgrehan#define RTC_MIN 0x02 45221828Sgrehan#define RTC_HRS 0x04 46221828Sgrehan#define RTC_WDAY 0x06 47221828Sgrehan#define RTC_DAY 0x07 48221828Sgrehan#define RTC_MONTH 0x08 49221828Sgrehan#define RTC_YEAR 0x09 50221828Sgrehan#define RTC_CENTURY 0x32 /* current century */ 51221828Sgrehan 52221828Sgrehan#define RTC_STATUSA 0xA 53221828Sgrehan#define RTCSA_TUP 0x80 /* time update, don't look now */ 54221828Sgrehan 55221828Sgrehan#define RTC_STATUSB 0xB 56221828Sgrehan#define RTCSB_DST 0x01 57221828Sgrehan#define RTCSB_24HR 0x02 58221828Sgrehan#define RTCSB_BIN 0x04 /* 0 = BCD, 1 = Binary */ 59221828Sgrehan#define RTCSB_PINTR 0x40 /* 1 = enable periodic clock interrupt */ 60221828Sgrehan#define RTCSB_HALT 0x80 /* stop clock updates */ 61221828Sgrehan 62221828Sgrehan#define RTC_INTR 0x0c /* status register C (R) interrupt source */ 63221828Sgrehan 64221828Sgrehan#define RTC_STATUSD 0x0d /* status register D (R) Lost Power */ 65221828Sgrehan#define RTCSD_PWR 0x80 /* clock power OK */ 66221828Sgrehan 67221828Sgrehan#define RTC_DIAG 0x0e 68221828Sgrehan 69221828Sgrehan#define RTC_RSTCODE 0x0f 70221828Sgrehan 71221828Sgrehanstatic int addr; 72221828Sgrehan 73221828Sgrehan/* XXX initialize these to default values as they would be from BIOS */ 74221828Sgrehanstatic uint8_t status_a, status_b, rstcode; 75221828Sgrehan 76221828Sgrehanstatic u_char const bin2bcd_data[] = { 77221828Sgrehan 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 78221828Sgrehan 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 79221828Sgrehan 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 80221828Sgrehan 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 81221828Sgrehan 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 82221828Sgrehan 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 83221828Sgrehan 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 84221828Sgrehan 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 85221828Sgrehan 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 86221828Sgrehan 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99 87221828Sgrehan}; 88221828Sgrehan#define bin2bcd(bin) (bin2bcd_data[bin]) 89221828Sgrehan 90221828Sgrehan#define rtcout(val) ((status_b & RTCSB_BIN) ? (val) : bin2bcd((val))) 91221828Sgrehan 92221828Sgrehanstatic void 93221828Sgrehantimevalfix(struct timeval *t1) 94221828Sgrehan{ 95221828Sgrehan 96221828Sgrehan if (t1->tv_usec < 0) { 97221828Sgrehan t1->tv_sec--; 98221828Sgrehan t1->tv_usec += 1000000; 99221828Sgrehan } 100221828Sgrehan if (t1->tv_usec >= 1000000) { 101221828Sgrehan t1->tv_sec++; 102221828Sgrehan t1->tv_usec -= 1000000; 103221828Sgrehan } 104221828Sgrehan} 105221828Sgrehan 106221828Sgrehanstatic void 107221828Sgrehantimevalsub(struct timeval *t1, const struct timeval *t2) 108221828Sgrehan{ 109221828Sgrehan 110221828Sgrehan t1->tv_sec -= t2->tv_sec; 111221828Sgrehan t1->tv_usec -= t2->tv_usec; 112221828Sgrehan timevalfix(t1); 113221828Sgrehan} 114221828Sgrehan 115221828Sgrehanstatic int 116221828Sgrehanrtc_addr_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, 117221828Sgrehan uint32_t *eax, void *arg) 118221828Sgrehan{ 119221828Sgrehan assert(in == 0); 120221828Sgrehan 121221828Sgrehan if (bytes != 1) 122221828Sgrehan return (-1); 123221828Sgrehan 124221828Sgrehan switch (*eax) { 125221828Sgrehan case RTC_SEC: 126221828Sgrehan case RTC_MIN: 127221828Sgrehan case RTC_HRS: 128221828Sgrehan case RTC_WDAY: 129221828Sgrehan case RTC_DAY: 130221828Sgrehan case RTC_MONTH: 131221828Sgrehan case RTC_YEAR: 132221828Sgrehan case RTC_CENTURY: 133221828Sgrehan case RTC_STATUSA: 134221828Sgrehan case RTC_STATUSB: 135221828Sgrehan case RTC_INTR: 136221828Sgrehan case RTC_STATUSD: 137221828Sgrehan case RTC_DIAG: 138221828Sgrehan case RTC_RSTCODE: 139221828Sgrehan break; 140221828Sgrehan default: 141221828Sgrehan return (-1); 142221828Sgrehan } 143221828Sgrehan 144221828Sgrehan addr = *eax; 145221828Sgrehan return (0); 146221828Sgrehan} 147221828Sgrehan 148221828Sgrehanstatic int 149221828Sgrehanrtc_data_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, 150221828Sgrehan uint32_t *eax, void *arg) 151221828Sgrehan{ 152221828Sgrehan int hour; 153221828Sgrehan time_t t; 154221828Sgrehan struct timeval cur, delta; 155221828Sgrehan 156221828Sgrehan static struct timeval last; 157221828Sgrehan static struct tm tm; 158221828Sgrehan 159221828Sgrehan if (bytes != 1) 160221828Sgrehan return (-1); 161221828Sgrehan 162221828Sgrehan gettimeofday(&cur, NULL); 163221828Sgrehan 164221828Sgrehan /* 165221828Sgrehan * Increment the cached time only once per second so we can guarantee 166221828Sgrehan * that the guest has at least one second to read the hour:min:sec 167221828Sgrehan * separately and still get a coherent view of the time. 168221828Sgrehan */ 169221828Sgrehan delta = cur; 170221828Sgrehan timevalsub(&delta, &last); 171221828Sgrehan if (delta.tv_sec >= 1 && (status_b & RTCSB_HALT) == 0) { 172221828Sgrehan t = cur.tv_sec; 173221828Sgrehan localtime_r(&t, &tm); 174221828Sgrehan last = cur; 175221828Sgrehan } 176221828Sgrehan 177221828Sgrehan if (in) { 178221828Sgrehan switch (addr) { 179221828Sgrehan case RTC_SEC: 180221828Sgrehan *eax = rtcout(tm.tm_sec); 181221828Sgrehan return (0); 182221828Sgrehan case RTC_MIN: 183221828Sgrehan *eax = rtcout(tm.tm_min); 184221828Sgrehan return (0); 185221828Sgrehan case RTC_HRS: 186221828Sgrehan if (status_b & RTCSB_24HR) 187221828Sgrehan hour = tm.tm_hour; 188221828Sgrehan else 189221828Sgrehan hour = (tm.tm_hour % 12) + 1; 190221828Sgrehan 191221828Sgrehan *eax = rtcout(hour); 192221828Sgrehan 193221828Sgrehan /* 194221828Sgrehan * If we are representing time in the 12-hour format 195221828Sgrehan * then set the MSB to indicate PM. 196221828Sgrehan */ 197221828Sgrehan if ((status_b & RTCSB_24HR) == 0 && tm.tm_hour >= 12) 198221828Sgrehan *eax |= 0x80; 199221828Sgrehan 200221828Sgrehan return (0); 201221828Sgrehan case RTC_WDAY: 202221828Sgrehan *eax = rtcout(tm.tm_wday + 1); 203221828Sgrehan return (0); 204221828Sgrehan case RTC_DAY: 205221828Sgrehan *eax = rtcout(tm.tm_mday); 206221828Sgrehan return (0); 207221828Sgrehan case RTC_MONTH: 208221828Sgrehan *eax = rtcout(tm.tm_mon + 1); 209221828Sgrehan return (0); 210221828Sgrehan case RTC_YEAR: 211221828Sgrehan *eax = rtcout(tm.tm_year % 100); 212221828Sgrehan return (0); 213221828Sgrehan case RTC_CENTURY: 214221828Sgrehan *eax = rtcout(tm.tm_year / 100); 215221828Sgrehan break; 216221828Sgrehan case RTC_STATUSA: 217221828Sgrehan *eax = status_a; 218221828Sgrehan return (0); 219221828Sgrehan case RTC_INTR: 220221828Sgrehan *eax = 0; 221221828Sgrehan return (0); 222221828Sgrehan case RTC_STATUSD: 223221828Sgrehan *eax = RTCSD_PWR; 224221828Sgrehan return (0); 225221828Sgrehan case RTC_DIAG: 226221828Sgrehan *eax = 0; 227221828Sgrehan return (0); 228221828Sgrehan case RTC_RSTCODE: 229221828Sgrehan *eax = rstcode; 230221828Sgrehan return (0); 231221828Sgrehan default: 232221828Sgrehan return (-1); 233221828Sgrehan } 234221828Sgrehan } 235221828Sgrehan 236221828Sgrehan switch (addr) { 237221828Sgrehan case RTC_STATUSA: 238221828Sgrehan status_a = *eax & ~RTCSA_TUP; 239221828Sgrehan break; 240221828Sgrehan case RTC_STATUSB: 241221828Sgrehan /* XXX not implemented yet XXX */ 242221828Sgrehan if (*eax & RTCSB_PINTR) 243221828Sgrehan return (-1); 244221828Sgrehan status_b = *eax; 245221828Sgrehan break; 246221828Sgrehan case RTC_RSTCODE: 247221828Sgrehan rstcode = *eax; 248221828Sgrehan break; 249221828Sgrehan case RTC_SEC: 250221828Sgrehan case RTC_MIN: 251221828Sgrehan case RTC_HRS: 252221828Sgrehan case RTC_WDAY: 253221828Sgrehan case RTC_DAY: 254221828Sgrehan case RTC_MONTH: 255221828Sgrehan case RTC_YEAR: 256221828Sgrehan case RTC_CENTURY: 257221828Sgrehan /* 258221828Sgrehan * Ignore writes to the time of day registers 259221828Sgrehan */ 260221828Sgrehan break; 261221828Sgrehan default: 262221828Sgrehan return (-1); 263221828Sgrehan } 264221828Sgrehan return (0); 265221828Sgrehan} 266221828Sgrehan 267221828SgrehanINOUT_PORT(rtc, IO_RTC, IOPORT_F_OUT, rtc_addr_handler); 268221828SgrehanINOUT_PORT(rtc, IO_RTC + 1, IOPORT_F_INOUT, rtc_data_handler); 269