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 |
|
|
} |