rtc.c revision 253181
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 253181 2013-07-11 03:54:35Z grehan $
27221828Sgrehan */
28221828Sgrehan
29221828Sgrehan#include <sys/cdefs.h>
30221828Sgrehan__FBSDID("$FreeBSD: head/usr.sbin/bhyve/rtc.c 253181 2013-07-11 03:54:35Z 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;
325253181Sgrehan		rtc_nvram[nvoff(RTC_NVRAM_START)] = 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