rtc.c revision 255688
1/*-
2 * Copyright (c) 2011 NetApp, Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD: head/usr.sbin/bhyve/rtc.c 255688 2013-09-19 04:20:18Z grehan $
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD: head/usr.sbin/bhyve/rtc.c 255688 2013-09-19 04:20:18Z grehan $");
31
32#include <sys/types.h>
33#include <sys/time.h>
34
35#include <stdio.h>
36#include <string.h>
37#include <time.h>
38#include <assert.h>
39
40#include <machine/vmm.h>
41#include <vmmapi.h>
42
43#include "inout.h"
44#include "rtc.h"
45
46#define	IO_RTC	0x70
47
48#define RTC_SEC		0x00	/* seconds */
49#define	RTC_MIN		0x02
50#define	RTC_HRS		0x04
51#define	RTC_WDAY	0x06
52#define	RTC_DAY		0x07
53#define	RTC_MONTH	0x08
54#define	RTC_YEAR	0x09
55#define	RTC_CENTURY	0x32	/* current century */
56
57#define RTC_STATUSA	0xA
58#define  RTCSA_TUP	 0x80	/* time update, don't look now */
59
60#define	RTC_STATUSB	0xB
61#define	 RTCSB_DST	 0x01
62#define	 RTCSB_24HR	 0x02
63#define	 RTCSB_BIN	 0x04	/* 0 = BCD, 1 = Binary */
64#define	 RTCSB_PINTR	 0x40	/* 1 = enable periodic clock interrupt */
65#define	 RTCSB_HALT      0x80	/* stop clock updates */
66
67#define RTC_INTR	0x0c	/* status register C (R) interrupt source */
68
69#define RTC_STATUSD	0x0d	/* status register D (R) Lost Power */
70#define  RTCSD_PWR	 0x80	/* clock power OK */
71
72#define	RTC_NVRAM_START	0x0e
73#define	RTC_NVRAM_END	0x7f
74#define RTC_NVRAM_SZ	(128 - RTC_NVRAM_START)
75#define	nvoff(x)	((x) - RTC_NVRAM_START)
76
77#define	RTC_DIAG	0x0e
78#define RTC_RSTCODE	0x0f
79#define	RTC_EQUIPMENT	0x14
80#define	RTC_LMEM_LSB	0x34
81#define	RTC_LMEM_MSB	0x35
82#define	RTC_HMEM_LSB	0x5b
83#define	RTC_HMEM_SB	0x5c
84#define	RTC_HMEM_MSB	0x5d
85
86#define m_64KB		(64*1024)
87#define	m_16MB		(16*1024*1024)
88#define	m_4GB		(4ULL*1024*1024*1024)
89
90static int addr;
91
92static uint8_t rtc_nvram[RTC_NVRAM_SZ];
93
94/* XXX initialize these to default values as they would be from BIOS */
95static uint8_t status_a, status_b;
96
97static u_char const bin2bcd_data[] = {
98	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
99	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
100	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
101	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
102	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
103	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
104	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
105	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
106	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
107	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99
108};
109#define	bin2bcd(bin)	(bin2bcd_data[bin])
110
111#define	rtcout(val)	((status_b & RTCSB_BIN) ? (val) : bin2bcd((val)))
112
113static void
114timevalfix(struct timeval *t1)
115{
116
117	if (t1->tv_usec < 0) {
118		t1->tv_sec--;
119		t1->tv_usec += 1000000;
120	}
121	if (t1->tv_usec >= 1000000) {
122		t1->tv_sec++;
123		t1->tv_usec -= 1000000;
124	}
125}
126
127static void
128timevalsub(struct timeval *t1, const struct timeval *t2)
129{
130
131	t1->tv_sec -= t2->tv_sec;
132	t1->tv_usec -= t2->tv_usec;
133	timevalfix(t1);
134}
135
136static int
137rtc_addr_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
138		 uint32_t *eax, void *arg)
139{
140	if (bytes != 1)
141		return (-1);
142
143	if (in) {
144		/* straight read of this register will return 0xFF */
145		*eax = 0xff;
146		return (0);
147	}
148
149	switch (*eax & 0x7f) {
150	case RTC_SEC:
151	case RTC_MIN:
152	case RTC_HRS:
153	case RTC_WDAY:
154	case RTC_DAY:
155	case RTC_MONTH:
156	case RTC_YEAR:
157	case RTC_STATUSA:
158	case RTC_STATUSB:
159	case RTC_INTR:
160	case RTC_STATUSD:
161	case RTC_NVRAM_START ... RTC_NVRAM_END:
162		break;
163	default:
164		return (-1);
165	}
166
167	addr = *eax & 0x7f;
168	return (0);
169}
170
171static int
172rtc_data_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
173		 uint32_t *eax, void *arg)
174{
175	int hour;
176	time_t t;
177	struct timeval cur, delta;
178
179	static struct timeval last;
180	static struct tm tm;
181
182	if (bytes != 1)
183		return (-1);
184
185	gettimeofday(&cur, NULL);
186
187	/*
188	 * Increment the cached time only once per second so we can guarantee
189	 * that the guest has at least one second to read the hour:min:sec
190	 * separately and still get a coherent view of the time.
191	 */
192	delta = cur;
193	timevalsub(&delta, &last);
194	if (delta.tv_sec >= 1 && (status_b & RTCSB_HALT) == 0) {
195		t = cur.tv_sec;
196		localtime_r(&t, &tm);
197		last = cur;
198	}
199
200	if (in) {
201		switch (addr) {
202		case RTC_SEC:
203			*eax = rtcout(tm.tm_sec);
204			return (0);
205		case RTC_MIN:
206			*eax = rtcout(tm.tm_min);
207			return (0);
208		case RTC_HRS:
209			if (status_b & RTCSB_24HR)
210				hour = tm.tm_hour;
211			else
212				hour = (tm.tm_hour % 12) + 1;
213
214			*eax = rtcout(hour);
215
216			/*
217			 * If we are representing time in the 12-hour format
218			 * then set the MSB to indicate PM.
219			 */
220			if ((status_b & RTCSB_24HR) == 0 && tm.tm_hour >= 12)
221				*eax |= 0x80;
222
223			return (0);
224		case RTC_WDAY:
225			*eax = rtcout(tm.tm_wday + 1);
226			return (0);
227		case RTC_DAY:
228			*eax = rtcout(tm.tm_mday);
229			return (0);
230		case RTC_MONTH:
231			*eax = rtcout(tm.tm_mon + 1);
232			return (0);
233		case RTC_YEAR:
234			*eax = rtcout(tm.tm_year % 100);
235			return (0);
236		case RTC_STATUSA:
237			*eax = status_a;
238			return (0);
239		case RTC_STATUSB:
240			*eax = status_b;
241			return (0);
242		case RTC_INTR:
243			*eax = 0;
244			return (0);
245		case RTC_STATUSD:
246			*eax = RTCSD_PWR;
247			return (0);
248		case RTC_NVRAM_START ... RTC_NVRAM_END:
249			*eax = rtc_nvram[addr - RTC_NVRAM_START];
250			return (0);
251		default:
252			return (-1);
253		}
254	}
255
256	switch (addr) {
257	case RTC_STATUSA:
258		status_a = *eax & ~RTCSA_TUP;
259		break;
260	case RTC_STATUSB:
261		/* XXX not implemented yet XXX */
262		if (*eax & RTCSB_PINTR)
263			return (-1);
264		status_b = *eax;
265		break;
266	case RTC_STATUSD:
267		/* ignore write */
268		break;
269	case RTC_SEC:
270	case RTC_MIN:
271	case RTC_HRS:
272	case RTC_WDAY:
273	case RTC_DAY:
274	case RTC_MONTH:
275	case RTC_YEAR:
276		/*
277		 * Ignore writes to the time of day registers
278		 */
279		break;
280	case RTC_NVRAM_START ... RTC_NVRAM_END:
281		rtc_nvram[addr - RTC_NVRAM_START] = *eax;
282		break;
283	default:
284		return (-1);
285	}
286	return (0);
287}
288
289void
290rtc_init(struct vmctx *ctx)
291{
292	struct timeval cur;
293	struct tm tm;
294	size_t himem;
295	size_t lomem;
296	int err;
297
298	err = gettimeofday(&cur, NULL);
299	assert(err == 0);
300	(void) localtime_r(&cur.tv_sec, &tm);
301
302	memset(rtc_nvram, 0, sizeof(rtc_nvram));
303
304	rtc_nvram[nvoff(RTC_CENTURY)] = rtcout(tm.tm_year / 100);
305
306	/* XXX init diag/reset code/equipment/checksum ? */
307
308	/*
309	 * Report guest memory size in nvram cells as required by UEFI.
310	 * Little-endian encoding.
311	 * 0x34/0x35 - 64KB chunks above 16MB, below 4GB
312	 * 0x5b/0x5c/0x5d - 64KB chunks above 4GB
313	 */
314	err = vm_get_memory_seg(ctx, 0, &lomem);
315	assert(err == 0);
316
317	lomem = (lomem - m_16MB) / m_64KB;
318	rtc_nvram[nvoff(RTC_LMEM_LSB)] = lomem;
319	rtc_nvram[nvoff(RTC_LMEM_MSB)] = lomem >> 8;
320
321	if (vm_get_memory_seg(ctx, m_4GB, &himem) == 0) {
322		himem /= m_64KB;
323		rtc_nvram[nvoff(RTC_HMEM_LSB)] = himem;
324		rtc_nvram[nvoff(RTC_HMEM_SB)]  = himem >> 8;
325		rtc_nvram[nvoff(RTC_HMEM_MSB)] = himem >> 16;
326	}
327}
328
329INOUT_PORT(rtc, IO_RTC, IOPORT_F_INOUT, rtc_addr_handler);
330INOUT_PORT(rtc, IO_RTC + 1, IOPORT_F_INOUT, rtc_data_handler);
331