GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/vmd/mc146818.c Lines: 0 111 0.0 %
Date: 2017-11-07 Branches: 0 98 0.0 %

Line Branch Exec Source
1
/* $OpenBSD: mc146818.c,v 1.15 2017/07/09 00:51:40 pd Exp $ */
2
/*
3
 * Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org>
4
 *
5
 * Permission to use, copy, modify, and distribute this software for any
6
 * purpose with or without fee is hereby granted, provided that the above
7
 * copyright notice and this permission notice appear in all copies.
8
 *
9
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
 */
17
18
#include <sys/types.h>
19
20
#include <dev/ic/mc146818reg.h>
21
#include <dev/isa/isareg.h>
22
23
#include <machine/vmmvar.h>
24
25
#include <event.h>
26
#include <stddef.h>
27
#include <string.h>
28
#include <time.h>
29
#include <unistd.h>
30
31
#include "vmd.h"
32
#include "mc146818.h"
33
#include "proc.h"
34
#include "virtio.h"
35
#include "vmm.h"
36
#include "atomicio.h"
37
38
#define MC_DIVIDER_MASK 0xe0
39
#define MC_RATE_MASK 0xf
40
41
#define NVRAM_CENTURY 0x32
42
#define NVRAM_MEMSIZE_LO 0x34
43
#define NVRAM_MEMSIZE_HI 0x35
44
#define NVRAM_HIMEMSIZE_LO 0x5B
45
#define NVRAM_HIMEMSIZE_MID 0x5C
46
#define NVRAM_HIMEMSIZE_HI 0x5D
47
#define NVRAM_SMP_COUNT 0x5F
48
49
#define NVRAM_SIZE 0x60
50
51
#define TOBCD(x)	(((x) / 10 * 16) + ((x) % 10))
52
53
struct mc146818 {
54
	time_t now;
55
	uint8_t idx;
56
	uint8_t regs[NVRAM_SIZE];
57
	uint32_t vm_id;
58
	struct event sec;
59
	struct timeval sec_tv;
60
	struct event per;
61
	struct timeval per_tv;
62
};
63
64
struct mc146818 rtc;
65
66
/*
67
 * rtc_updateregs
68
 *
69
 * Updates the RTC TOD bytes, reflecting 'now'.
70
 */
71
static void
72
rtc_updateregs(void)
73
{
74
	struct tm *gnow;
75
76
	rtc.regs[MC_REGD] &= ~MC_REGD_VRT;
77
	gnow = gmtime(&rtc.now);
78
79
	rtc.regs[MC_SEC] = TOBCD(gnow->tm_sec);
80
	rtc.regs[MC_MIN] = TOBCD(gnow->tm_min);
81
	rtc.regs[MC_HOUR] = TOBCD(gnow->tm_hour);
82
	rtc.regs[MC_DOW] = TOBCD(gnow->tm_wday + 1);
83
	rtc.regs[MC_DOM] = TOBCD(gnow->tm_mday);
84
	rtc.regs[MC_MONTH] = TOBCD(gnow->tm_mon + 1);
85
	rtc.regs[MC_YEAR] = TOBCD((gnow->tm_year + 1900) % 100);
86
	rtc.regs[NVRAM_CENTURY] = TOBCD((gnow->tm_year + 1900) / 100);
87
	rtc.regs[MC_REGD] |= MC_REGD_VRT;
88
}
89
90
/*
91
 * rtc_fire1
92
 *
93
 * Callback for the 1s periodic TOD refresh timer
94
 *
95
 * Parameters:
96
 *  fd: unused
97
 *  type: unused
98
 *  arg: unused
99
 */
100
static void
101
rtc_fire1(int fd, short type, void *arg)
102
{
103
	time_t old = rtc.now;
104
105
	time(&rtc.now);
106
107
	rtc_updateregs();
108
	if (rtc.now - old > 5) {
109
		log_debug("%s: RTC clock drift (%llds), requesting guest "
110
		    "resync", __func__, (rtc.now - old));
111
		vmmci_ctl(VMMCI_SYNCRTC);
112
	}
113
	evtimer_add(&rtc.sec, &rtc.sec_tv);
114
}
115
116
/*
117
 * rtc_fireper
118
 *
119
 * Callback for the periodic interrupt timer
120
 *
121
 * Parameters:
122
 *  fd: unused
123
 *  type: unused
124
 *  arg: (as uint32_t), VM ID to which this RTC belongs
125
 */
126
static void
127
rtc_fireper(int fd, short type, void *arg)
128
{
129
	rtc.regs[MC_REGC] |= MC_REGC_PF;
130
131
	vcpu_assert_pic_irq((ptrdiff_t)arg, 0, 8);
132
133
	evtimer_add(&rtc.per, &rtc.per_tv);
134
}
135
136
/*
137
 * mc146818_init
138
 *
139
 * Initializes the emulated RTC/NVRAM
140
 *
141
 * Parameters:
142
 *  vm_id: VM ID to which this RTC belongs
143
 *  memlo: size of memory in bytes between 16MB .. 4GB
144
 *  memhi: size of memory in bytes after 4GB
145
 */
146
void
147
mc146818_init(uint32_t vm_id, uint64_t memlo, uint64_t memhi)
148
{
149
	memset(&rtc, 0, sizeof(rtc));
150
	time(&rtc.now);
151
152
	rtc.regs[MC_REGB] = MC_REGB_24HR;
153
154
	memlo /= 65536;
155
	memhi /= 65536;
156
157
	rtc.regs[NVRAM_MEMSIZE_HI] = (memlo >> 8) & 0xFF;
158
	rtc.regs[NVRAM_MEMSIZE_LO] = memlo & 0xFF;
159
	rtc.regs[NVRAM_HIMEMSIZE_HI] = (memhi >> 16) & 0xFF;
160
	rtc.regs[NVRAM_HIMEMSIZE_MID] = (memhi >> 8) & 0xFF;
161
	rtc.regs[NVRAM_HIMEMSIZE_LO] = memhi & 0xFF;
162
163
	rtc.regs[NVRAM_SMP_COUNT] = 0;
164
165
	rtc_updateregs();
166
	rtc.vm_id = vm_id;
167
168
	timerclear(&rtc.sec_tv);
169
	rtc.sec_tv.tv_sec = 1;
170
171
	timerclear(&rtc.per_tv);
172
173
	evtimer_set(&rtc.sec, rtc_fire1, NULL);
174
	evtimer_add(&rtc.sec, &rtc.sec_tv);
175
176
	evtimer_set(&rtc.per, rtc_fireper, (void *)(intptr_t)rtc.vm_id);
177
}
178
179
/*
180
 * rtc_reschedule_per
181
 *
182
 * Reschedule the periodic interrupt firing rate, based on the currently
183
 * selected REGB values.
184
 */
185
static void
186
rtc_reschedule_per(void)
187
{
188
	uint16_t rate;
189
	uint64_t us;
190
191
	if (rtc.regs[MC_REGB] & MC_REGB_PIE) {
192
		rate = 32768 >> ((rtc.regs[MC_REGA] & MC_RATE_MASK) - 1);
193
		us = (1.0 / rate) * 1000000;
194
		rtc.per_tv.tv_usec = us;
195
		if (evtimer_pending(&rtc.per, NULL))
196
			evtimer_del(&rtc.per);
197
198
		evtimer_add(&rtc.per, &rtc.per_tv);
199
	}
200
}
201
202
/*
203
 * rtc_update_rega
204
 *
205
 * Updates the RTC's REGA register
206
 *
207
 * Parameters:
208
 *  data: REGA register data
209
 */
210
static void
211
rtc_update_rega(uint32_t data)
212
{
213
	if ((data & MC_DIVIDER_MASK) != MC_BASE_32_KHz)
214
		log_warnx("%s: set non-32KHz timebase not supported",
215
		    __func__);
216
217
	rtc.regs[MC_REGA] = data;
218
	if (rtc.regs[MC_REGB] & MC_REGB_PIE)
219
		rtc_reschedule_per();
220
}
221
222
/*
223
 * rtc_update_regb
224
 *
225
 * Updates the RTC's REGB register
226
 *
227
 * Parameters:
228
 *  data: REGB register data
229
 */
230
static void
231
rtc_update_regb(uint32_t data)
232
{
233
	if (data & MC_REGB_DSE)
234
		log_warnx("%s: DSE mode not supported", __func__);
235
236
	if (!(data & MC_REGB_24HR))
237
		log_warnx("%s: 12 hour mode not supported", __func__);
238
239
	rtc.regs[MC_REGB] = data;
240
241
	if (data & MC_REGB_PIE)
242
		rtc_reschedule_per();
243
}
244
245
/*
246
 * vcpu_exit_mc146818
247
 *
248
 * Handles emulated MC146818 RTC access (in/out instruction to RTC ports).
249
 *
250
 * Parameters:
251
 *  vrp: vm run parameters containing exit information for the I/O
252
 *      instruction being performed
253
 *
254
 * Return value:
255
 *  Interrupt to inject to the guest VM, or 0xFF if no interrupt should
256
 *      be injected.
257
 */
258
uint8_t
259
vcpu_exit_mc146818(struct vm_run_params *vrp)
260
{
261
	union vm_exit *vei = vrp->vrp_exit;
262
	uint16_t port = vei->vei.vei_port;
263
	uint8_t dir = vei->vei.vei_dir;
264
	uint32_t data = 0;
265
266
	get_input_data(vei, &data);
267
268
	if (port == IO_RTC) {
269
		/* Discard NMI bit */
270
		if (data & 0x80)
271
			data &= ~0x80;
272
273
		if (dir == 0) {
274
			if (data < (NVRAM_SIZE))
275
				rtc.idx = data;
276
			else
277
				rtc.idx = MC_REGD;
278
		} else
279
			set_return_data(vei, rtc.idx);
280
	} else if (port == IO_RTC + 1) {
281
		if (dir == 0) {
282
			switch (rtc.idx) {
283
			case MC_SEC ... MC_YEAR:
284
			case MC_NVRAM_START ... MC_NVRAM_START + MC_NVRAM_SIZE:
285
				rtc.regs[rtc.idx] = data;
286
				break;
287
			case MC_REGA:
288
				rtc_update_rega(data);
289
				break;
290
			case MC_REGB:
291
				rtc_update_regb(data);
292
				break;
293
			case MC_REGC:
294
			case MC_REGD:
295
				log_warnx("%s: mc146818 illegal write "
296
				    "of reg 0x%x", __func__, rtc.idx);
297
				break;
298
			default:
299
				log_warnx("%s: mc146818 illegal reg %x\n",
300
				    __func__, rtc.idx);
301
			}
302
		} else {
303
			data = rtc.regs[rtc.idx];
304
			set_return_data(vei, data);
305
306
			if (rtc.idx == MC_REGC) {
307
				/* Reset IRQ state */
308
				rtc.regs[MC_REGC] &= ~MC_REGC_PF;
309
			}
310
		}
311
	} else {
312
		log_warnx("%s: mc146818 unknown port 0x%x",
313
		    __func__, vei->vei.vei_port);
314
	}
315
316
	return 0xFF;
317
}
318
319
int
320
mc146818_dump(int fd)
321
{
322
	log_debug("%s: sending RTC", __func__);
323
	if (atomicio(vwrite, fd, &rtc, sizeof(rtc)) != sizeof(rtc)) {
324
		log_warnx("%s: error writing RTC to fd", __func__);
325
		return (-1);
326
	}
327
	return (0);
328
}
329
330
int
331
mc146818_restore(int fd, uint32_t vm_id)
332
{
333
	log_debug("%s: restoring RTC", __func__);
334
	if (atomicio(read, fd, &rtc, sizeof(rtc)) != sizeof(rtc)) {
335
		log_warnx("%s: error reading RTC from fd", __func__);
336
		return (-1);
337
	}
338
	rtc.vm_id = vm_id;
339
340
	memset(&rtc.sec, 0, sizeof(struct event));
341
	memset(&rtc.per, 0, sizeof(struct event));
342
	evtimer_set(&rtc.sec, rtc_fire1, NULL);
343
	evtimer_set(&rtc.per, rtc_fireper, (void *)(intptr_t)rtc.vm_id);
344
	return (0);
345
}
346
347
void
348
mc146818_stop()
349
{
350
	evtimer_del(&rtc.per);
351
	evtimer_del(&rtc.sec);
352
}
353
354
void
355
mc146818_start()
356
{
357
	evtimer_add(&rtc.per, &rtc.per_tv);
358
	evtimer_add(&rtc.sec, &rtc.sec_tv);
359
}