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