rtc.c revision 255688
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: head/usr.sbin/bhyve/rtc.c 255688 2013-09-19 04:20:18Z grehan $ 27221828Sgrehan */ 28221828Sgrehan 29221828Sgrehan#include <sys/cdefs.h> 30221828Sgrehan__FBSDID("$FreeBSD: head/usr.sbin/bhyve/rtc.c 255688 2013-09-19 04:20:18Z grehan $"); 31221828Sgrehan 32221828Sgrehan#include <sys/types.h> 33221828Sgrehan#include <sys/time.h> 34221828Sgrehan 35221828Sgrehan#include <stdio.h> 36253181Sgrehan#include <string.h> 37221828Sgrehan#include <time.h> 38221828Sgrehan#include <assert.h> 39221828Sgrehan 40253181Sgrehan#include <machine/vmm.h> 41253181Sgrehan#include <vmmapi.h> 42253181Sgrehan 43221828Sgrehan#include "inout.h" 44253181Sgrehan#include "rtc.h" 45221828Sgrehan 46221828Sgrehan#define IO_RTC 0x70 47221828Sgrehan 48221828Sgrehan#define RTC_SEC 0x00 /* seconds */ 49221828Sgrehan#define RTC_MIN 0x02 50221828Sgrehan#define RTC_HRS 0x04 51221828Sgrehan#define RTC_WDAY 0x06 52221828Sgrehan#define RTC_DAY 0x07 53221828Sgrehan#define RTC_MONTH 0x08 54221828Sgrehan#define RTC_YEAR 0x09 55221828Sgrehan#define RTC_CENTURY 0x32 /* current century */ 56221828Sgrehan 57221828Sgrehan#define RTC_STATUSA 0xA 58221828Sgrehan#define RTCSA_TUP 0x80 /* time update, don't look now */ 59221828Sgrehan 60221828Sgrehan#define RTC_STATUSB 0xB 61221828Sgrehan#define RTCSB_DST 0x01 62221828Sgrehan#define RTCSB_24HR 0x02 63221828Sgrehan#define RTCSB_BIN 0x04 /* 0 = BCD, 1 = Binary */ 64221828Sgrehan#define RTCSB_PINTR 0x40 /* 1 = enable periodic clock interrupt */ 65221828Sgrehan#define RTCSB_HALT 0x80 /* stop clock updates */ 66221828Sgrehan 67221828Sgrehan#define RTC_INTR 0x0c /* status register C (R) interrupt source */ 68221828Sgrehan 69221828Sgrehan#define RTC_STATUSD 0x0d /* status register D (R) Lost Power */ 70221828Sgrehan#define RTCSD_PWR 0x80 /* clock power OK */ 71221828Sgrehan 72253181Sgrehan#define RTC_NVRAM_START 0x0e 73253181Sgrehan#define RTC_NVRAM_END 0x7f 74253181Sgrehan#define RTC_NVRAM_SZ (128 - RTC_NVRAM_START) 75253181Sgrehan#define nvoff(x) ((x) - RTC_NVRAM_START) 76253181Sgrehan 77221828Sgrehan#define RTC_DIAG 0x0e 78221828Sgrehan#define RTC_RSTCODE 0x0f 79222105Sgrehan#define RTC_EQUIPMENT 0x14 80253181Sgrehan#define RTC_LMEM_LSB 0x34 81253181Sgrehan#define RTC_LMEM_MSB 0x35 82253181Sgrehan#define RTC_HMEM_LSB 0x5b 83253181Sgrehan#define RTC_HMEM_SB 0x5c 84253181Sgrehan#define RTC_HMEM_MSB 0x5d 85222105Sgrehan 86253181Sgrehan#define m_64KB (64*1024) 87253181Sgrehan#define m_16MB (16*1024*1024) 88253181Sgrehan#define m_4GB (4ULL*1024*1024*1024) 89253181Sgrehan 90221828Sgrehanstatic int addr; 91221828Sgrehan 92253181Sgrehanstatic uint8_t rtc_nvram[RTC_NVRAM_SZ]; 93253181Sgrehan 94221828Sgrehan/* XXX initialize these to default values as they would be from BIOS */ 95253181Sgrehanstatic uint8_t status_a, status_b; 96221828Sgrehan 97221828Sgrehanstatic u_char const bin2bcd_data[] = { 98221828Sgrehan 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 99221828Sgrehan 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 100221828Sgrehan 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 101221828Sgrehan 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 102221828Sgrehan 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 103221828Sgrehan 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 104221828Sgrehan 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 105221828Sgrehan 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 106221828Sgrehan 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 107221828Sgrehan 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99 108221828Sgrehan}; 109221828Sgrehan#define bin2bcd(bin) (bin2bcd_data[bin]) 110221828Sgrehan 111221828Sgrehan#define rtcout(val) ((status_b & RTCSB_BIN) ? (val) : bin2bcd((val))) 112221828Sgrehan 113221828Sgrehanstatic void 114221828Sgrehantimevalfix(struct timeval *t1) 115221828Sgrehan{ 116221828Sgrehan 117221828Sgrehan if (t1->tv_usec < 0) { 118221828Sgrehan t1->tv_sec--; 119221828Sgrehan t1->tv_usec += 1000000; 120221828Sgrehan } 121221828Sgrehan if (t1->tv_usec >= 1000000) { 122221828Sgrehan t1->tv_sec++; 123221828Sgrehan t1->tv_usec -= 1000000; 124221828Sgrehan } 125221828Sgrehan} 126221828Sgrehan 127221828Sgrehanstatic void 128221828Sgrehantimevalsub(struct timeval *t1, const struct timeval *t2) 129221828Sgrehan{ 130221828Sgrehan 131221828Sgrehan t1->tv_sec -= t2->tv_sec; 132221828Sgrehan t1->tv_usec -= t2->tv_usec; 133221828Sgrehan timevalfix(t1); 134221828Sgrehan} 135221828Sgrehan 136221828Sgrehanstatic int 137221828Sgrehanrtc_addr_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, 138221828Sgrehan uint32_t *eax, void *arg) 139221828Sgrehan{ 140221828Sgrehan if (bytes != 1) 141221828Sgrehan return (-1); 142221828Sgrehan 143245920Sgrehan if (in) { 144245920Sgrehan /* straight read of this register will return 0xFF */ 145245920Sgrehan *eax = 0xff; 146245920Sgrehan return (0); 147245920Sgrehan } 148245920Sgrehan 149245920Sgrehan switch (*eax & 0x7f) { 150221828Sgrehan case RTC_SEC: 151221828Sgrehan case RTC_MIN: 152221828Sgrehan case RTC_HRS: 153221828Sgrehan case RTC_WDAY: 154221828Sgrehan case RTC_DAY: 155221828Sgrehan case RTC_MONTH: 156221828Sgrehan case RTC_YEAR: 157221828Sgrehan case RTC_STATUSA: 158221828Sgrehan case RTC_STATUSB: 159221828Sgrehan case RTC_INTR: 160221828Sgrehan case RTC_STATUSD: 161253181Sgrehan case RTC_NVRAM_START ... RTC_NVRAM_END: 162221828Sgrehan break; 163221828Sgrehan default: 164221828Sgrehan return (-1); 165221828Sgrehan } 166221828Sgrehan 167245920Sgrehan addr = *eax & 0x7f; 168221828Sgrehan return (0); 169221828Sgrehan} 170221828Sgrehan 171221828Sgrehanstatic int 172221828Sgrehanrtc_data_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, 173221828Sgrehan uint32_t *eax, void *arg) 174221828Sgrehan{ 175221828Sgrehan int hour; 176221828Sgrehan time_t t; 177221828Sgrehan struct timeval cur, delta; 178221828Sgrehan 179221828Sgrehan static struct timeval last; 180221828Sgrehan static struct tm tm; 181221828Sgrehan 182221828Sgrehan if (bytes != 1) 183221828Sgrehan return (-1); 184221828Sgrehan 185221828Sgrehan gettimeofday(&cur, NULL); 186221828Sgrehan 187221828Sgrehan /* 188221828Sgrehan * Increment the cached time only once per second so we can guarantee 189221828Sgrehan * that the guest has at least one second to read the hour:min:sec 190221828Sgrehan * separately and still get a coherent view of the time. 191221828Sgrehan */ 192221828Sgrehan delta = cur; 193221828Sgrehan timevalsub(&delta, &last); 194221828Sgrehan if (delta.tv_sec >= 1 && (status_b & RTCSB_HALT) == 0) { 195221828Sgrehan t = cur.tv_sec; 196221828Sgrehan localtime_r(&t, &tm); 197221828Sgrehan last = cur; 198221828Sgrehan } 199221828Sgrehan 200221828Sgrehan if (in) { 201221828Sgrehan switch (addr) { 202221828Sgrehan case RTC_SEC: 203221828Sgrehan *eax = rtcout(tm.tm_sec); 204221828Sgrehan return (0); 205221828Sgrehan case RTC_MIN: 206221828Sgrehan *eax = rtcout(tm.tm_min); 207221828Sgrehan return (0); 208221828Sgrehan case RTC_HRS: 209221828Sgrehan if (status_b & RTCSB_24HR) 210221828Sgrehan hour = tm.tm_hour; 211221828Sgrehan else 212221828Sgrehan hour = (tm.tm_hour % 12) + 1; 213221828Sgrehan 214221828Sgrehan *eax = rtcout(hour); 215221828Sgrehan 216221828Sgrehan /* 217221828Sgrehan * If we are representing time in the 12-hour format 218221828Sgrehan * then set the MSB to indicate PM. 219221828Sgrehan */ 220221828Sgrehan if ((status_b & RTCSB_24HR) == 0 && tm.tm_hour >= 12) 221221828Sgrehan *eax |= 0x80; 222221828Sgrehan 223221828Sgrehan return (0); 224221828Sgrehan case RTC_WDAY: 225221828Sgrehan *eax = rtcout(tm.tm_wday + 1); 226221828Sgrehan return (0); 227221828Sgrehan case RTC_DAY: 228221828Sgrehan *eax = rtcout(tm.tm_mday); 229221828Sgrehan return (0); 230221828Sgrehan case RTC_MONTH: 231221828Sgrehan *eax = rtcout(tm.tm_mon + 1); 232221828Sgrehan return (0); 233221828Sgrehan case RTC_YEAR: 234221828Sgrehan *eax = rtcout(tm.tm_year % 100); 235221828Sgrehan return (0); 236221828Sgrehan case RTC_STATUSA: 237221828Sgrehan *eax = status_a; 238221828Sgrehan return (0); 239245920Sgrehan case RTC_STATUSB: 240245920Sgrehan *eax = status_b; 241245920Sgrehan return (0); 242221828Sgrehan case RTC_INTR: 243221828Sgrehan *eax = 0; 244221828Sgrehan return (0); 245221828Sgrehan case RTC_STATUSD: 246221828Sgrehan *eax = RTCSD_PWR; 247221828Sgrehan return (0); 248253181Sgrehan case RTC_NVRAM_START ... RTC_NVRAM_END: 249253181Sgrehan *eax = rtc_nvram[addr - RTC_NVRAM_START]; 250221828Sgrehan return (0); 251221828Sgrehan default: 252221828Sgrehan return (-1); 253221828Sgrehan } 254221828Sgrehan } 255221828Sgrehan 256221828Sgrehan switch (addr) { 257221828Sgrehan case RTC_STATUSA: 258221828Sgrehan status_a = *eax & ~RTCSA_TUP; 259221828Sgrehan break; 260221828Sgrehan case RTC_STATUSB: 261221828Sgrehan /* XXX not implemented yet XXX */ 262221828Sgrehan if (*eax & RTCSB_PINTR) 263221828Sgrehan return (-1); 264221828Sgrehan status_b = *eax; 265221828Sgrehan break; 266245920Sgrehan case RTC_STATUSD: 267245920Sgrehan /* ignore write */ 268245920Sgrehan break; 269221828Sgrehan case RTC_SEC: 270221828Sgrehan case RTC_MIN: 271221828Sgrehan case RTC_HRS: 272221828Sgrehan case RTC_WDAY: 273221828Sgrehan case RTC_DAY: 274221828Sgrehan case RTC_MONTH: 275221828Sgrehan case RTC_YEAR: 276221828Sgrehan /* 277221828Sgrehan * Ignore writes to the time of day registers 278221828Sgrehan */ 279221828Sgrehan break; 280253181Sgrehan case RTC_NVRAM_START ... RTC_NVRAM_END: 281253181Sgrehan rtc_nvram[addr - RTC_NVRAM_START] = *eax; 282253181Sgrehan break; 283221828Sgrehan default: 284221828Sgrehan return (-1); 285221828Sgrehan } 286221828Sgrehan return (0); 287221828Sgrehan} 288221828Sgrehan 289253181Sgrehanvoid 290253181Sgrehanrtc_init(struct vmctx *ctx) 291253181Sgrehan{ 292253181Sgrehan struct timeval cur; 293253181Sgrehan struct tm tm; 294253181Sgrehan size_t himem; 295253181Sgrehan size_t lomem; 296253181Sgrehan int err; 297253181Sgrehan 298253181Sgrehan err = gettimeofday(&cur, NULL); 299253181Sgrehan assert(err == 0); 300253181Sgrehan (void) localtime_r(&cur.tv_sec, &tm); 301253181Sgrehan 302253181Sgrehan memset(rtc_nvram, 0, sizeof(rtc_nvram)); 303253181Sgrehan 304253181Sgrehan rtc_nvram[nvoff(RTC_CENTURY)] = rtcout(tm.tm_year / 100); 305253181Sgrehan 306253181Sgrehan /* XXX init diag/reset code/equipment/checksum ? */ 307253181Sgrehan 308253181Sgrehan /* 309253181Sgrehan * Report guest memory size in nvram cells as required by UEFI. 310253181Sgrehan * Little-endian encoding. 311253181Sgrehan * 0x34/0x35 - 64KB chunks above 16MB, below 4GB 312253181Sgrehan * 0x5b/0x5c/0x5d - 64KB chunks above 4GB 313253181Sgrehan */ 314253181Sgrehan err = vm_get_memory_seg(ctx, 0, &lomem); 315253181Sgrehan assert(err == 0); 316253181Sgrehan 317253181Sgrehan lomem = (lomem - m_16MB) / m_64KB; 318253181Sgrehan rtc_nvram[nvoff(RTC_LMEM_LSB)] = lomem; 319253181Sgrehan rtc_nvram[nvoff(RTC_LMEM_MSB)] = lomem >> 8; 320253181Sgrehan 321253181Sgrehan if (vm_get_memory_seg(ctx, m_4GB, &himem) == 0) { 322253181Sgrehan himem /= m_64KB; 323253181Sgrehan rtc_nvram[nvoff(RTC_HMEM_LSB)] = himem; 324253181Sgrehan rtc_nvram[nvoff(RTC_HMEM_SB)] = himem >> 8; 325255688Sgrehan rtc_nvram[nvoff(RTC_HMEM_MSB)] = himem >> 16; 326253181Sgrehan } 327253181Sgrehan} 328253181Sgrehan 329245920SgrehanINOUT_PORT(rtc, IO_RTC, IOPORT_F_INOUT, rtc_addr_handler); 330221828SgrehanINOUT_PORT(rtc, IO_RTC + 1, IOPORT_F_INOUT, rtc_data_handler); 331