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
43261265Sjhb#include "acpi.h"
44221828Sgrehan#include "inout.h"
45261265Sjhb#include "pci_lpc.h"
46253181Sgrehan#include "rtc.h"
47221828Sgrehan
48221828Sgrehan#define	IO_RTC	0x70
49221828Sgrehan
50221828Sgrehan#define RTC_SEC		0x00	/* seconds */
51255689Sgrehan#define	RTC_SEC_ALARM	0x01
52221828Sgrehan#define	RTC_MIN		0x02
53255689Sgrehan#define	RTC_MIN_ALARM	0x03
54221828Sgrehan#define	RTC_HRS		0x04
55255689Sgrehan#define	RTC_HRS_ALARM	0x05
56221828Sgrehan#define	RTC_WDAY	0x06
57221828Sgrehan#define	RTC_DAY		0x07
58221828Sgrehan#define	RTC_MONTH	0x08
59221828Sgrehan#define	RTC_YEAR	0x09
60221828Sgrehan#define	RTC_CENTURY	0x32	/* current century */
61221828Sgrehan
62221828Sgrehan#define RTC_STATUSA	0xA
63221828Sgrehan#define  RTCSA_TUP	 0x80	/* time update, don't look now */
64221828Sgrehan
65221828Sgrehan#define	RTC_STATUSB	0xB
66221828Sgrehan#define	 RTCSB_DST	 0x01
67221828Sgrehan#define	 RTCSB_24HR	 0x02
68221828Sgrehan#define	 RTCSB_BIN	 0x04	/* 0 = BCD, 1 = Binary */
69221828Sgrehan#define	 RTCSB_PINTR	 0x40	/* 1 = enable periodic clock interrupt */
70221828Sgrehan#define	 RTCSB_HALT      0x80	/* stop clock updates */
71221828Sgrehan
72221828Sgrehan#define RTC_INTR	0x0c	/* status register C (R) interrupt source */
73221828Sgrehan
74221828Sgrehan#define RTC_STATUSD	0x0d	/* status register D (R) Lost Power */
75221828Sgrehan#define  RTCSD_PWR	 0x80	/* clock power OK */
76221828Sgrehan
77253181Sgrehan#define	RTC_NVRAM_START	0x0e
78253181Sgrehan#define	RTC_NVRAM_END	0x7f
79253181Sgrehan#define RTC_NVRAM_SZ	(128 - RTC_NVRAM_START)
80253181Sgrehan#define	nvoff(x)	((x) - RTC_NVRAM_START)
81253181Sgrehan
82221828Sgrehan#define	RTC_DIAG	0x0e
83221828Sgrehan#define RTC_RSTCODE	0x0f
84222105Sgrehan#define	RTC_EQUIPMENT	0x14
85253181Sgrehan#define	RTC_LMEM_LSB	0x34
86253181Sgrehan#define	RTC_LMEM_MSB	0x35
87253181Sgrehan#define	RTC_HMEM_LSB	0x5b
88253181Sgrehan#define	RTC_HMEM_SB	0x5c
89253181Sgrehan#define	RTC_HMEM_MSB	0x5d
90222105Sgrehan
91253181Sgrehan#define m_64KB		(64*1024)
92253181Sgrehan#define	m_16MB		(16*1024*1024)
93253181Sgrehan#define	m_4GB		(4ULL*1024*1024*1024)
94253181Sgrehan
95221828Sgrehanstatic int addr;
96221828Sgrehan
97253181Sgrehanstatic uint8_t rtc_nvram[RTC_NVRAM_SZ];
98253181Sgrehan
99221828Sgrehan/* XXX initialize these to default values as they would be from BIOS */
100253181Sgrehanstatic uint8_t status_a, status_b;
101221828Sgrehan
102255689Sgrehanstatic struct {
103255689Sgrehan	uint8_t  hours;
104255689Sgrehan	uint8_t  mins;
105255689Sgrehan	uint8_t  secs;
106255689Sgrehan} rtc_alarm;
107255689Sgrehan
108221828Sgrehanstatic u_char const bin2bcd_data[] = {
109221828Sgrehan	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
110221828Sgrehan	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
111221828Sgrehan	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
112221828Sgrehan	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
113221828Sgrehan	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
114221828Sgrehan	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
115221828Sgrehan	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
116221828Sgrehan	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
117221828Sgrehan	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
118221828Sgrehan	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99
119221828Sgrehan};
120221828Sgrehan#define	bin2bcd(bin)	(bin2bcd_data[bin])
121221828Sgrehan
122221828Sgrehan#define	rtcout(val)	((status_b & RTCSB_BIN) ? (val) : bin2bcd((val)))
123221828Sgrehan
124221828Sgrehanstatic void
125221828Sgrehantimevalfix(struct timeval *t1)
126221828Sgrehan{
127221828Sgrehan
128221828Sgrehan	if (t1->tv_usec < 0) {
129221828Sgrehan		t1->tv_sec--;
130221828Sgrehan		t1->tv_usec += 1000000;
131221828Sgrehan	}
132221828Sgrehan	if (t1->tv_usec >= 1000000) {
133221828Sgrehan		t1->tv_sec++;
134221828Sgrehan		t1->tv_usec -= 1000000;
135221828Sgrehan	}
136221828Sgrehan}
137221828Sgrehan
138221828Sgrehanstatic void
139221828Sgrehantimevalsub(struct timeval *t1, const struct timeval *t2)
140221828Sgrehan{
141221828Sgrehan
142221828Sgrehan	t1->tv_sec -= t2->tv_sec;
143221828Sgrehan	t1->tv_usec -= t2->tv_usec;
144221828Sgrehan	timevalfix(t1);
145221828Sgrehan}
146221828Sgrehan
147221828Sgrehanstatic int
148221828Sgrehanrtc_addr_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
149221828Sgrehan		 uint32_t *eax, void *arg)
150221828Sgrehan{
151221828Sgrehan	if (bytes != 1)
152221828Sgrehan		return (-1);
153221828Sgrehan
154245920Sgrehan	if (in) {
155245920Sgrehan		/* straight read of this register will return 0xFF */
156245920Sgrehan		*eax = 0xff;
157245920Sgrehan		return (0);
158245920Sgrehan	}
159245920Sgrehan
160245920Sgrehan	switch (*eax & 0x7f) {
161221828Sgrehan	case RTC_SEC:
162255689Sgrehan	case RTC_SEC_ALARM:
163221828Sgrehan	case RTC_MIN:
164255689Sgrehan	case RTC_MIN_ALARM:
165221828Sgrehan	case RTC_HRS:
166255689Sgrehan	case RTC_HRS_ALARM:
167221828Sgrehan	case RTC_WDAY:
168221828Sgrehan	case RTC_DAY:
169221828Sgrehan	case RTC_MONTH:
170221828Sgrehan	case RTC_YEAR:
171221828Sgrehan	case RTC_STATUSA:
172221828Sgrehan	case RTC_STATUSB:
173221828Sgrehan	case RTC_INTR:
174221828Sgrehan	case RTC_STATUSD:
175253181Sgrehan	case RTC_NVRAM_START ... RTC_NVRAM_END:
176221828Sgrehan		break;
177221828Sgrehan	default:
178221828Sgrehan		return (-1);
179221828Sgrehan	}
180221828Sgrehan
181245920Sgrehan	addr = *eax & 0x7f;
182221828Sgrehan	return (0);
183221828Sgrehan}
184221828Sgrehan
185221828Sgrehanstatic int
186221828Sgrehanrtc_data_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
187221828Sgrehan		 uint32_t *eax, void *arg)
188221828Sgrehan{
189221828Sgrehan	int hour;
190221828Sgrehan	time_t t;
191221828Sgrehan	struct timeval cur, delta;
192221828Sgrehan
193221828Sgrehan	static struct timeval last;
194221828Sgrehan	static struct tm tm;
195221828Sgrehan
196221828Sgrehan	if (bytes != 1)
197221828Sgrehan		return (-1);
198221828Sgrehan
199221828Sgrehan	gettimeofday(&cur, NULL);
200221828Sgrehan
201221828Sgrehan	/*
202221828Sgrehan	 * Increment the cached time only once per second so we can guarantee
203221828Sgrehan	 * that the guest has at least one second to read the hour:min:sec
204221828Sgrehan	 * separately and still get a coherent view of the time.
205221828Sgrehan	 */
206221828Sgrehan	delta = cur;
207221828Sgrehan	timevalsub(&delta, &last);
208221828Sgrehan	if (delta.tv_sec >= 1 && (status_b & RTCSB_HALT) == 0) {
209221828Sgrehan		t = cur.tv_sec;
210221828Sgrehan		localtime_r(&t, &tm);
211221828Sgrehan		last = cur;
212221828Sgrehan	}
213221828Sgrehan
214221828Sgrehan	if (in) {
215221828Sgrehan		switch (addr) {
216255689Sgrehan		case RTC_SEC_ALARM:
217255689Sgrehan			*eax = rtc_alarm.secs;
218255689Sgrehan			break;
219255689Sgrehan		case RTC_MIN_ALARM:
220255689Sgrehan			*eax = rtc_alarm.mins;
221255689Sgrehan			break;
222255689Sgrehan		case RTC_HRS_ALARM:
223255689Sgrehan			*eax = rtc_alarm.hours;
224255689Sgrehan			break;
225221828Sgrehan		case RTC_SEC:
226221828Sgrehan			*eax = rtcout(tm.tm_sec);
227221828Sgrehan			return (0);
228221828Sgrehan		case RTC_MIN:
229221828Sgrehan			*eax = rtcout(tm.tm_min);
230221828Sgrehan			return (0);
231221828Sgrehan		case RTC_HRS:
232221828Sgrehan			if (status_b & RTCSB_24HR)
233221828Sgrehan				hour = tm.tm_hour;
234221828Sgrehan			else
235221828Sgrehan				hour = (tm.tm_hour % 12) + 1;
236221828Sgrehan
237221828Sgrehan			*eax = rtcout(hour);
238221828Sgrehan
239221828Sgrehan			/*
240221828Sgrehan			 * If we are representing time in the 12-hour format
241221828Sgrehan			 * then set the MSB to indicate PM.
242221828Sgrehan			 */
243221828Sgrehan			if ((status_b & RTCSB_24HR) == 0 && tm.tm_hour >= 12)
244221828Sgrehan				*eax |= 0x80;
245221828Sgrehan
246221828Sgrehan			return (0);
247221828Sgrehan		case RTC_WDAY:
248221828Sgrehan			*eax = rtcout(tm.tm_wday + 1);
249221828Sgrehan			return (0);
250221828Sgrehan		case RTC_DAY:
251221828Sgrehan			*eax = rtcout(tm.tm_mday);
252221828Sgrehan			return (0);
253221828Sgrehan		case RTC_MONTH:
254221828Sgrehan			*eax = rtcout(tm.tm_mon + 1);
255221828Sgrehan			return (0);
256221828Sgrehan		case RTC_YEAR:
257221828Sgrehan			*eax = rtcout(tm.tm_year % 100);
258221828Sgrehan			return (0);
259221828Sgrehan		case RTC_STATUSA:
260221828Sgrehan			*eax = status_a;
261221828Sgrehan			return (0);
262245920Sgrehan		case RTC_STATUSB:
263245920Sgrehan			*eax = status_b;
264245920Sgrehan			return (0);
265221828Sgrehan		case RTC_INTR:
266221828Sgrehan			*eax = 0;
267221828Sgrehan			return (0);
268221828Sgrehan		case RTC_STATUSD:
269221828Sgrehan			*eax = RTCSD_PWR;
270221828Sgrehan			return (0);
271253181Sgrehan		case RTC_NVRAM_START ... RTC_NVRAM_END:
272253181Sgrehan			*eax = rtc_nvram[addr - RTC_NVRAM_START];
273221828Sgrehan			return (0);
274221828Sgrehan		default:
275221828Sgrehan			return (-1);
276221828Sgrehan		}
277221828Sgrehan	}
278221828Sgrehan
279221828Sgrehan	switch (addr) {
280221828Sgrehan	case RTC_STATUSA:
281221828Sgrehan		status_a = *eax & ~RTCSA_TUP;
282221828Sgrehan		break;
283221828Sgrehan	case RTC_STATUSB:
284221828Sgrehan		/* XXX not implemented yet XXX */
285221828Sgrehan		if (*eax & RTCSB_PINTR)
286221828Sgrehan			return (-1);
287221828Sgrehan		status_b = *eax;
288221828Sgrehan		break;
289245920Sgrehan	case RTC_STATUSD:
290245920Sgrehan		/* ignore write */
291245920Sgrehan		break;
292255689Sgrehan	case RTC_SEC_ALARM:
293255689Sgrehan		rtc_alarm.secs = *eax;
294255689Sgrehan		break;
295255689Sgrehan	case RTC_MIN_ALARM:
296255689Sgrehan		rtc_alarm.mins = *eax;
297255689Sgrehan		break;
298255689Sgrehan	case RTC_HRS_ALARM:
299255689Sgrehan		rtc_alarm.hours = *eax;
300255689Sgrehan		break;
301221828Sgrehan	case RTC_SEC:
302221828Sgrehan	case RTC_MIN:
303221828Sgrehan	case RTC_HRS:
304221828Sgrehan	case RTC_WDAY:
305221828Sgrehan	case RTC_DAY:
306221828Sgrehan	case RTC_MONTH:
307221828Sgrehan	case RTC_YEAR:
308221828Sgrehan		/*
309221828Sgrehan		 * Ignore writes to the time of day registers
310221828Sgrehan		 */
311221828Sgrehan		break;
312253181Sgrehan	case RTC_NVRAM_START ... RTC_NVRAM_END:
313253181Sgrehan		rtc_nvram[addr - RTC_NVRAM_START] = *eax;
314253181Sgrehan		break;
315221828Sgrehan	default:
316221828Sgrehan		return (-1);
317221828Sgrehan	}
318221828Sgrehan	return (0);
319221828Sgrehan}
320221828Sgrehan
321253181Sgrehanvoid
322253181Sgrehanrtc_init(struct vmctx *ctx)
323253181Sgrehan{
324253181Sgrehan	struct timeval cur;
325253181Sgrehan	struct tm tm;
326253181Sgrehan	size_t himem;
327253181Sgrehan	size_t lomem;
328253181Sgrehan	int err;
329253181Sgrehan
330253181Sgrehan	err = gettimeofday(&cur, NULL);
331253181Sgrehan	assert(err == 0);
332253181Sgrehan	(void) localtime_r(&cur.tv_sec, &tm);
333253181Sgrehan
334253181Sgrehan	memset(rtc_nvram, 0, sizeof(rtc_nvram));
335253181Sgrehan
336256755Sgrehan	rtc_nvram[nvoff(RTC_CENTURY)] = bin2bcd((tm.tm_year + 1900) / 100);
337253181Sgrehan
338253181Sgrehan	/* XXX init diag/reset code/equipment/checksum ? */
339253181Sgrehan
340253181Sgrehan	/*
341253181Sgrehan	 * Report guest memory size in nvram cells as required by UEFI.
342253181Sgrehan	 * Little-endian encoding.
343253181Sgrehan	 * 0x34/0x35 - 64KB chunks above 16MB, below 4GB
344253181Sgrehan	 * 0x5b/0x5c/0x5d - 64KB chunks above 4GB
345253181Sgrehan	 */
346270074Sgrehan	lomem = (vm_get_lowmem_size(ctx) - m_16MB) / m_64KB;
347253181Sgrehan	rtc_nvram[nvoff(RTC_LMEM_LSB)] = lomem;
348253181Sgrehan	rtc_nvram[nvoff(RTC_LMEM_MSB)] = lomem >> 8;
349253181Sgrehan
350270074Sgrehan	himem = vm_get_highmem_size(ctx) / m_64KB;
351270074Sgrehan	rtc_nvram[nvoff(RTC_HMEM_LSB)] = himem;
352270074Sgrehan	rtc_nvram[nvoff(RTC_HMEM_SB)]  = himem >> 8;
353270074Sgrehan	rtc_nvram[nvoff(RTC_HMEM_MSB)] = himem >> 16;
354253181Sgrehan}
355253181Sgrehan
356245920SgrehanINOUT_PORT(rtc, IO_RTC, IOPORT_F_INOUT, rtc_addr_handler);
357221828SgrehanINOUT_PORT(rtc, IO_RTC + 1, IOPORT_F_INOUT, rtc_data_handler);
358261265Sjhb
359261265Sjhbstatic void
360261265Sjhbrtc_dsdt(void)
361261265Sjhb{
362261265Sjhb
363261265Sjhb	dsdt_line("");
364261265Sjhb	dsdt_line("Device (RTC)");
365261265Sjhb	dsdt_line("{");
366261265Sjhb	dsdt_line("  Name (_HID, EisaId (\"PNP0B00\"))");
367261265Sjhb	dsdt_line("  Name (_CRS, ResourceTemplate ()");
368261265Sjhb	dsdt_line("  {");
369261265Sjhb	dsdt_indent(2);
370261265Sjhb	dsdt_fixed_ioport(IO_RTC, 2);
371261265Sjhb	dsdt_fixed_irq(8);
372261265Sjhb	dsdt_unindent(2);
373261265Sjhb	dsdt_line("  })");
374261265Sjhb	dsdt_line("}");
375261265Sjhb}
376261265SjhbLPC_DSDT(rtc_dsdt);
377261265Sjhb
378261265SjhbSYSRES_IO(0x72, 6);
379