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>
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 */
49255689Sgrehan#define	RTC_SEC_ALARM	0x01
50221828Sgrehan#define	RTC_MIN		0x02
51255689Sgrehan#define	RTC_MIN_ALARM	0x03
52221828Sgrehan#define	RTC_HRS		0x04
53255689Sgrehan#define	RTC_HRS_ALARM	0x05
54221828Sgrehan#define	RTC_WDAY	0x06
55221828Sgrehan#define	RTC_DAY		0x07
56221828Sgrehan#define	RTC_MONTH	0x08
57221828Sgrehan#define	RTC_YEAR	0x09
58221828Sgrehan#define	RTC_CENTURY	0x32	/* current century */
59221828Sgrehan
60221828Sgrehan#define RTC_STATUSA	0xA
61221828Sgrehan#define  RTCSA_TUP	 0x80	/* time update, don't look now */
62221828Sgrehan
63221828Sgrehan#define	RTC_STATUSB	0xB
64221828Sgrehan#define	 RTCSB_DST	 0x01
65221828Sgrehan#define	 RTCSB_24HR	 0x02
66221828Sgrehan#define	 RTCSB_BIN	 0x04	/* 0 = BCD, 1 = Binary */
67221828Sgrehan#define	 RTCSB_PINTR	 0x40	/* 1 = enable periodic clock interrupt */
68221828Sgrehan#define	 RTCSB_HALT      0x80	/* stop clock updates */
69221828Sgrehan
70221828Sgrehan#define RTC_INTR	0x0c	/* status register C (R) interrupt source */
71221828Sgrehan
72221828Sgrehan#define RTC_STATUSD	0x0d	/* status register D (R) Lost Power */
73221828Sgrehan#define  RTCSD_PWR	 0x80	/* clock power OK */
74221828Sgrehan
75253181Sgrehan#define	RTC_NVRAM_START	0x0e
76253181Sgrehan#define	RTC_NVRAM_END	0x7f
77253181Sgrehan#define RTC_NVRAM_SZ	(128 - RTC_NVRAM_START)
78253181Sgrehan#define	nvoff(x)	((x) - RTC_NVRAM_START)
79253181Sgrehan
80221828Sgrehan#define	RTC_DIAG	0x0e
81221828Sgrehan#define RTC_RSTCODE	0x0f
82222105Sgrehan#define	RTC_EQUIPMENT	0x14
83253181Sgrehan#define	RTC_LMEM_LSB	0x34
84253181Sgrehan#define	RTC_LMEM_MSB	0x35
85253181Sgrehan#define	RTC_HMEM_LSB	0x5b
86253181Sgrehan#define	RTC_HMEM_SB	0x5c
87253181Sgrehan#define	RTC_HMEM_MSB	0x5d
88222105Sgrehan
89253181Sgrehan#define m_64KB		(64*1024)
90253181Sgrehan#define	m_16MB		(16*1024*1024)
91253181Sgrehan#define	m_4GB		(4ULL*1024*1024*1024)
92253181Sgrehan
93221828Sgrehanstatic int addr;
94221828Sgrehan
95253181Sgrehanstatic uint8_t rtc_nvram[RTC_NVRAM_SZ];
96253181Sgrehan
97221828Sgrehan/* XXX initialize these to default values as they would be from BIOS */
98253181Sgrehanstatic uint8_t status_a, status_b;
99221828Sgrehan
100255689Sgrehanstatic struct {
101255689Sgrehan	uint8_t  hours;
102255689Sgrehan	uint8_t  mins;
103255689Sgrehan	uint8_t  secs;
104255689Sgrehan} rtc_alarm;
105255689Sgrehan
106221828Sgrehanstatic u_char const bin2bcd_data[] = {
107221828Sgrehan	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
108221828Sgrehan	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
109221828Sgrehan	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
110221828Sgrehan	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
111221828Sgrehan	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
112221828Sgrehan	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
113221828Sgrehan	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
114221828Sgrehan	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
115221828Sgrehan	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
116221828Sgrehan	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99
117221828Sgrehan};
118221828Sgrehan#define	bin2bcd(bin)	(bin2bcd_data[bin])
119221828Sgrehan
120221828Sgrehan#define	rtcout(val)	((status_b & RTCSB_BIN) ? (val) : bin2bcd((val)))
121221828Sgrehan
122221828Sgrehanstatic void
123221828Sgrehantimevalfix(struct timeval *t1)
124221828Sgrehan{
125221828Sgrehan
126221828Sgrehan	if (t1->tv_usec < 0) {
127221828Sgrehan		t1->tv_sec--;
128221828Sgrehan		t1->tv_usec += 1000000;
129221828Sgrehan	}
130221828Sgrehan	if (t1->tv_usec >= 1000000) {
131221828Sgrehan		t1->tv_sec++;
132221828Sgrehan		t1->tv_usec -= 1000000;
133221828Sgrehan	}
134221828Sgrehan}
135221828Sgrehan
136221828Sgrehanstatic void
137221828Sgrehantimevalsub(struct timeval *t1, const struct timeval *t2)
138221828Sgrehan{
139221828Sgrehan
140221828Sgrehan	t1->tv_sec -= t2->tv_sec;
141221828Sgrehan	t1->tv_usec -= t2->tv_usec;
142221828Sgrehan	timevalfix(t1);
143221828Sgrehan}
144221828Sgrehan
145221828Sgrehanstatic int
146221828Sgrehanrtc_addr_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
147221828Sgrehan		 uint32_t *eax, void *arg)
148221828Sgrehan{
149221828Sgrehan	if (bytes != 1)
150221828Sgrehan		return (-1);
151221828Sgrehan
152245920Sgrehan	if (in) {
153245920Sgrehan		/* straight read of this register will return 0xFF */
154245920Sgrehan		*eax = 0xff;
155245920Sgrehan		return (0);
156245920Sgrehan	}
157245920Sgrehan
158245920Sgrehan	switch (*eax & 0x7f) {
159221828Sgrehan	case RTC_SEC:
160255689Sgrehan	case RTC_SEC_ALARM:
161221828Sgrehan	case RTC_MIN:
162255689Sgrehan	case RTC_MIN_ALARM:
163221828Sgrehan	case RTC_HRS:
164255689Sgrehan	case RTC_HRS_ALARM:
165221828Sgrehan	case RTC_WDAY:
166221828Sgrehan	case RTC_DAY:
167221828Sgrehan	case RTC_MONTH:
168221828Sgrehan	case RTC_YEAR:
169221828Sgrehan	case RTC_STATUSA:
170221828Sgrehan	case RTC_STATUSB:
171221828Sgrehan	case RTC_INTR:
172221828Sgrehan	case RTC_STATUSD:
173253181Sgrehan	case RTC_NVRAM_START ... RTC_NVRAM_END:
174221828Sgrehan		break;
175221828Sgrehan	default:
176221828Sgrehan		return (-1);
177221828Sgrehan	}
178221828Sgrehan
179245920Sgrehan	addr = *eax & 0x7f;
180221828Sgrehan	return (0);
181221828Sgrehan}
182221828Sgrehan
183221828Sgrehanstatic int
184221828Sgrehanrtc_data_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
185221828Sgrehan		 uint32_t *eax, void *arg)
186221828Sgrehan{
187221828Sgrehan	int hour;
188221828Sgrehan	time_t t;
189221828Sgrehan	struct timeval cur, delta;
190221828Sgrehan
191221828Sgrehan	static struct timeval last;
192221828Sgrehan	static struct tm tm;
193221828Sgrehan
194221828Sgrehan	if (bytes != 1)
195221828Sgrehan		return (-1);
196221828Sgrehan
197221828Sgrehan	gettimeofday(&cur, NULL);
198221828Sgrehan
199221828Sgrehan	/*
200221828Sgrehan	 * Increment the cached time only once per second so we can guarantee
201221828Sgrehan	 * that the guest has at least one second to read the hour:min:sec
202221828Sgrehan	 * separately and still get a coherent view of the time.
203221828Sgrehan	 */
204221828Sgrehan	delta = cur;
205221828Sgrehan	timevalsub(&delta, &last);
206221828Sgrehan	if (delta.tv_sec >= 1 && (status_b & RTCSB_HALT) == 0) {
207221828Sgrehan		t = cur.tv_sec;
208221828Sgrehan		localtime_r(&t, &tm);
209221828Sgrehan		last = cur;
210221828Sgrehan	}
211221828Sgrehan
212221828Sgrehan	if (in) {
213221828Sgrehan		switch (addr) {
214255689Sgrehan		case RTC_SEC_ALARM:
215255689Sgrehan			*eax = rtc_alarm.secs;
216255689Sgrehan			break;
217255689Sgrehan		case RTC_MIN_ALARM:
218255689Sgrehan			*eax = rtc_alarm.mins;
219255689Sgrehan			break;
220255689Sgrehan		case RTC_HRS_ALARM:
221255689Sgrehan			*eax = rtc_alarm.hours;
222255689Sgrehan			break;
223221828Sgrehan		case RTC_SEC:
224221828Sgrehan			*eax = rtcout(tm.tm_sec);
225221828Sgrehan			return (0);
226221828Sgrehan		case RTC_MIN:
227221828Sgrehan			*eax = rtcout(tm.tm_min);
228221828Sgrehan			return (0);
229221828Sgrehan		case RTC_HRS:
230221828Sgrehan			if (status_b & RTCSB_24HR)
231221828Sgrehan				hour = tm.tm_hour;
232221828Sgrehan			else
233221828Sgrehan				hour = (tm.tm_hour % 12) + 1;
234221828Sgrehan
235221828Sgrehan			*eax = rtcout(hour);
236221828Sgrehan
237221828Sgrehan			/*
238221828Sgrehan			 * If we are representing time in the 12-hour format
239221828Sgrehan			 * then set the MSB to indicate PM.
240221828Sgrehan			 */
241221828Sgrehan			if ((status_b & RTCSB_24HR) == 0 && tm.tm_hour >= 12)
242221828Sgrehan				*eax |= 0x80;
243221828Sgrehan
244221828Sgrehan			return (0);
245221828Sgrehan		case RTC_WDAY:
246221828Sgrehan			*eax = rtcout(tm.tm_wday + 1);
247221828Sgrehan			return (0);
248221828Sgrehan		case RTC_DAY:
249221828Sgrehan			*eax = rtcout(tm.tm_mday);
250221828Sgrehan			return (0);
251221828Sgrehan		case RTC_MONTH:
252221828Sgrehan			*eax = rtcout(tm.tm_mon + 1);
253221828Sgrehan			return (0);
254221828Sgrehan		case RTC_YEAR:
255221828Sgrehan			*eax = rtcout(tm.tm_year % 100);
256221828Sgrehan			return (0);
257221828Sgrehan		case RTC_STATUSA:
258221828Sgrehan			*eax = status_a;
259221828Sgrehan			return (0);
260245920Sgrehan		case RTC_STATUSB:
261245920Sgrehan			*eax = status_b;
262245920Sgrehan			return (0);
263221828Sgrehan		case RTC_INTR:
264221828Sgrehan			*eax = 0;
265221828Sgrehan			return (0);
266221828Sgrehan		case RTC_STATUSD:
267221828Sgrehan			*eax = RTCSD_PWR;
268221828Sgrehan			return (0);
269253181Sgrehan		case RTC_NVRAM_START ... RTC_NVRAM_END:
270253181Sgrehan			*eax = rtc_nvram[addr - RTC_NVRAM_START];
271221828Sgrehan			return (0);
272221828Sgrehan		default:
273221828Sgrehan			return (-1);
274221828Sgrehan		}
275221828Sgrehan	}
276221828Sgrehan
277221828Sgrehan	switch (addr) {
278221828Sgrehan	case RTC_STATUSA:
279221828Sgrehan		status_a = *eax & ~RTCSA_TUP;
280221828Sgrehan		break;
281221828Sgrehan	case RTC_STATUSB:
282221828Sgrehan		/* XXX not implemented yet XXX */
283221828Sgrehan		if (*eax & RTCSB_PINTR)
284221828Sgrehan			return (-1);
285221828Sgrehan		status_b = *eax;
286221828Sgrehan		break;
287245920Sgrehan	case RTC_STATUSD:
288245920Sgrehan		/* ignore write */
289245920Sgrehan		break;
290255689Sgrehan	case RTC_SEC_ALARM:
291255689Sgrehan		rtc_alarm.secs = *eax;
292255689Sgrehan		break;
293255689Sgrehan	case RTC_MIN_ALARM:
294255689Sgrehan		rtc_alarm.mins = *eax;
295255689Sgrehan		break;
296255689Sgrehan	case RTC_HRS_ALARM:
297255689Sgrehan		rtc_alarm.hours = *eax;
298255689Sgrehan		break;
299221828Sgrehan	case RTC_SEC:
300221828Sgrehan	case RTC_MIN:
301221828Sgrehan	case RTC_HRS:
302221828Sgrehan	case RTC_WDAY:
303221828Sgrehan	case RTC_DAY:
304221828Sgrehan	case RTC_MONTH:
305221828Sgrehan	case RTC_YEAR:
306221828Sgrehan		/*
307221828Sgrehan		 * Ignore writes to the time of day registers
308221828Sgrehan		 */
309221828Sgrehan		break;
310253181Sgrehan	case RTC_NVRAM_START ... RTC_NVRAM_END:
311253181Sgrehan		rtc_nvram[addr - RTC_NVRAM_START] = *eax;
312253181Sgrehan		break;
313221828Sgrehan	default:
314221828Sgrehan		return (-1);
315221828Sgrehan	}
316221828Sgrehan	return (0);
317221828Sgrehan}
318221828Sgrehan
319253181Sgrehanvoid
320253181Sgrehanrtc_init(struct vmctx *ctx)
321253181Sgrehan{
322253181Sgrehan	struct timeval cur;
323253181Sgrehan	struct tm tm;
324253181Sgrehan	size_t himem;
325253181Sgrehan	size_t lomem;
326253181Sgrehan	int err;
327253181Sgrehan
328253181Sgrehan	err = gettimeofday(&cur, NULL);
329253181Sgrehan	assert(err == 0);
330253181Sgrehan	(void) localtime_r(&cur.tv_sec, &tm);
331253181Sgrehan
332253181Sgrehan	memset(rtc_nvram, 0, sizeof(rtc_nvram));
333253181Sgrehan
334256755Sgrehan	rtc_nvram[nvoff(RTC_CENTURY)] = bin2bcd((tm.tm_year + 1900) / 100);
335253181Sgrehan
336253181Sgrehan	/* XXX init diag/reset code/equipment/checksum ? */
337253181Sgrehan
338253181Sgrehan	/*
339253181Sgrehan	 * Report guest memory size in nvram cells as required by UEFI.
340253181Sgrehan	 * Little-endian encoding.
341253181Sgrehan	 * 0x34/0x35 - 64KB chunks above 16MB, below 4GB
342253181Sgrehan	 * 0x5b/0x5c/0x5d - 64KB chunks above 4GB
343253181Sgrehan	 */
344256072Sneel	err = vm_get_memory_seg(ctx, 0, &lomem, NULL);
345253181Sgrehan	assert(err == 0);
346253181Sgrehan
347253181Sgrehan	lomem = (lomem - m_16MB) / m_64KB;
348253181Sgrehan	rtc_nvram[nvoff(RTC_LMEM_LSB)] = lomem;
349253181Sgrehan	rtc_nvram[nvoff(RTC_LMEM_MSB)] = lomem >> 8;
350253181Sgrehan
351256072Sneel	if (vm_get_memory_seg(ctx, m_4GB, &himem, NULL) == 0) {
352253181Sgrehan		himem /= m_64KB;
353253181Sgrehan		rtc_nvram[nvoff(RTC_HMEM_LSB)] = himem;
354253181Sgrehan		rtc_nvram[nvoff(RTC_HMEM_SB)]  = himem >> 8;
355255688Sgrehan		rtc_nvram[nvoff(RTC_HMEM_MSB)] = himem >> 16;
356253181Sgrehan	}
357253181Sgrehan}
358253181Sgrehan
359245920SgrehanINOUT_PORT(rtc, IO_RTC, IOPORT_F_INOUT, rtc_addr_handler);
360221828SgrehanINOUT_PORT(rtc, IO_RTC + 1, IOPORT_F_INOUT, rtc_data_handler);
361