rtc.c revision 222105
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$
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include <sys/types.h>
33#include <sys/time.h>
34
35#include <stdio.h>
36#include <time.h>
37#include <assert.h>
38
39#include "inout.h"
40
41#define	IO_RTC	0x70
42
43#define RTC_SEC		0x00	/* seconds */
44#define	RTC_MIN		0x02
45#define	RTC_HRS		0x04
46#define	RTC_WDAY	0x06
47#define	RTC_DAY		0x07
48#define	RTC_MONTH	0x08
49#define	RTC_YEAR	0x09
50#define	RTC_CENTURY	0x32	/* current century */
51
52#define RTC_STATUSA	0xA
53#define  RTCSA_TUP	 0x80	/* time update, don't look now */
54
55#define	RTC_STATUSB	0xB
56#define	 RTCSB_DST	 0x01
57#define	 RTCSB_24HR	 0x02
58#define	 RTCSB_BIN	 0x04	/* 0 = BCD, 1 = Binary */
59#define	 RTCSB_PINTR	 0x40	/* 1 = enable periodic clock interrupt */
60#define	 RTCSB_HALT      0x80	/* stop clock updates */
61
62#define RTC_INTR	0x0c	/* status register C (R) interrupt source */
63
64#define RTC_STATUSD	0x0d	/* status register D (R) Lost Power */
65#define  RTCSD_PWR	 0x80	/* clock power OK */
66
67#define	RTC_DIAG	0x0e
68
69#define RTC_RSTCODE	0x0f
70
71#define	RTC_EQUIPMENT	0x14
72
73static int addr;
74
75/* XXX initialize these to default values as they would be from BIOS */
76static uint8_t status_a, status_b, rstcode;
77
78static u_char const bin2bcd_data[] = {
79	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
80	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
81	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
82	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
83	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
84	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
85	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
86	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
87	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
88	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99
89};
90#define	bin2bcd(bin)	(bin2bcd_data[bin])
91
92#define	rtcout(val)	((status_b & RTCSB_BIN) ? (val) : bin2bcd((val)))
93
94static void
95timevalfix(struct timeval *t1)
96{
97
98	if (t1->tv_usec < 0) {
99		t1->tv_sec--;
100		t1->tv_usec += 1000000;
101	}
102	if (t1->tv_usec >= 1000000) {
103		t1->tv_sec++;
104		t1->tv_usec -= 1000000;
105	}
106}
107
108static void
109timevalsub(struct timeval *t1, const struct timeval *t2)
110{
111
112	t1->tv_sec -= t2->tv_sec;
113	t1->tv_usec -= t2->tv_usec;
114	timevalfix(t1);
115}
116
117static int
118rtc_addr_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
119		 uint32_t *eax, void *arg)
120{
121	assert(in == 0);
122
123	if (bytes != 1)
124		return (-1);
125
126	switch (*eax) {
127	case RTC_SEC:
128	case RTC_MIN:
129	case RTC_HRS:
130	case RTC_WDAY:
131	case RTC_DAY:
132	case RTC_MONTH:
133	case RTC_YEAR:
134	case RTC_CENTURY:
135	case RTC_STATUSA:
136	case RTC_STATUSB:
137	case RTC_INTR:
138	case RTC_STATUSD:
139	case RTC_DIAG:
140	case RTC_RSTCODE:
141	case RTC_EQUIPMENT:
142		break;
143	default:
144		return (-1);
145	}
146
147	addr = *eax;
148	return (0);
149}
150
151static int
152rtc_data_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
153		 uint32_t *eax, void *arg)
154{
155	int hour;
156	time_t t;
157	struct timeval cur, delta;
158
159	static struct timeval last;
160	static struct tm tm;
161
162	if (bytes != 1)
163		return (-1);
164
165	gettimeofday(&cur, NULL);
166
167	/*
168	 * Increment the cached time only once per second so we can guarantee
169	 * that the guest has at least one second to read the hour:min:sec
170	 * separately and still get a coherent view of the time.
171	 */
172	delta = cur;
173	timevalsub(&delta, &last);
174	if (delta.tv_sec >= 1 && (status_b & RTCSB_HALT) == 0) {
175		t = cur.tv_sec;
176		localtime_r(&t, &tm);
177		last = cur;
178	}
179
180	if (in) {
181		switch (addr) {
182		case RTC_SEC:
183			*eax = rtcout(tm.tm_sec);
184			return (0);
185		case RTC_MIN:
186			*eax = rtcout(tm.tm_min);
187			return (0);
188		case RTC_HRS:
189			if (status_b & RTCSB_24HR)
190				hour = tm.tm_hour;
191			else
192				hour = (tm.tm_hour % 12) + 1;
193
194			*eax = rtcout(hour);
195
196			/*
197			 * If we are representing time in the 12-hour format
198			 * then set the MSB to indicate PM.
199			 */
200			if ((status_b & RTCSB_24HR) == 0 && tm.tm_hour >= 12)
201				*eax |= 0x80;
202
203			return (0);
204		case RTC_WDAY:
205			*eax = rtcout(tm.tm_wday + 1);
206			return (0);
207		case RTC_DAY:
208			*eax = rtcout(tm.tm_mday);
209			return (0);
210		case RTC_MONTH:
211			*eax = rtcout(tm.tm_mon + 1);
212			return (0);
213		case RTC_YEAR:
214			*eax = rtcout(tm.tm_year % 100);
215			return (0);
216		case RTC_CENTURY:
217			*eax = rtcout(tm.tm_year / 100);
218			break;
219		case RTC_STATUSA:
220			*eax = status_a;
221			return (0);
222		case RTC_INTR:
223			*eax = 0;
224			return (0);
225		case RTC_STATUSD:
226			*eax = RTCSD_PWR;
227			return (0);
228		case RTC_DIAG:
229			*eax = 0;
230			return (0);
231		case RTC_RSTCODE:
232			*eax = rstcode;
233			return (0);
234		case RTC_EQUIPMENT:
235			*eax = 0;
236			return (0);
237		default:
238			return (-1);
239		}
240	}
241
242	switch (addr) {
243	case RTC_STATUSA:
244		status_a = *eax & ~RTCSA_TUP;
245		break;
246	case RTC_STATUSB:
247		/* XXX not implemented yet XXX */
248		if (*eax & RTCSB_PINTR)
249			return (-1);
250		status_b = *eax;
251		break;
252	case RTC_RSTCODE:
253		rstcode = *eax;
254		break;
255	case RTC_SEC:
256	case RTC_MIN:
257	case RTC_HRS:
258	case RTC_WDAY:
259	case RTC_DAY:
260	case RTC_MONTH:
261	case RTC_YEAR:
262	case RTC_CENTURY:
263		/*
264		 * Ignore writes to the time of day registers
265		 */
266		break;
267	default:
268		return (-1);
269	}
270	return (0);
271}
272
273INOUT_PORT(rtc, IO_RTC, IOPORT_F_OUT, rtc_addr_handler);
274INOUT_PORT(rtc, IO_RTC + 1, IOPORT_F_INOUT, rtc_data_handler);
275