1 /* $OpenBSD: mc146818.c,v 1.29 2024/07/10 09:27:33 dv 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 #include <dev/vmm/vmm.h> 23 24 #include <event.h> 25 #include <stddef.h> 26 #include <string.h> 27 #include <time.h> 28 #include <unistd.h> 29 30 #include "atomicio.h" 31 #include "mc146818.h" 32 #include "virtio.h" 33 #include "vmd.h" 34 35 #define MC_RATE_MASK 0xf 36 37 #define NVRAM_CENTURY 0x32 38 #define NVRAM_MEMSIZE_LO 0x34 39 #define NVRAM_MEMSIZE_HI 0x35 40 #define NVRAM_HIMEMSIZE_LO 0x5B 41 #define NVRAM_HIMEMSIZE_MID 0x5C 42 #define NVRAM_HIMEMSIZE_HI 0x5D 43 #define NVRAM_SMP_COUNT 0x5F 44 45 #define NVRAM_SIZE 0x60 46 47 #define TOBCD(x) (((x) / 10 * 16) + ((x) % 10)) 48 49 struct mc146818 { 50 time_t now; 51 uint8_t idx; 52 uint8_t regs[NVRAM_SIZE]; 53 uint32_t vm_id; 54 struct event sec; 55 struct timeval sec_tv; 56 struct event per; 57 struct timeval per_tv; 58 }; 59 60 struct mc146818 rtc; 61 62 static struct vm_dev_pipe dev_pipe; 63 64 static void rtc_reschedule_per(void); 65 66 /* 67 * mc146818_pipe_dispatch 68 * 69 * Reads a message off the pipe, expecting a request to reschedule periodic 70 * interrupt rate. 71 */ 72 static void 73 mc146818_pipe_dispatch(int fd, short event, void *arg) 74 { 75 enum pipe_msg_type msg; 76 77 msg = vm_pipe_recv(&dev_pipe); 78 if (msg == MC146818_RESCHEDULE_PER) { 79 rtc_reschedule_per(); 80 } else { 81 fatalx("%s: unexpected pipe message %d", __func__, msg); 82 } 83 } 84 85 /* 86 * rtc_updateregs 87 * 88 * Updates the RTC TOD bytes, reflecting 'now'. 89 */ 90 static void 91 rtc_updateregs(void) 92 { 93 struct tm *gnow; 94 95 rtc.regs[MC_REGD] &= ~MC_REGD_VRT; 96 gnow = gmtime(&rtc.now); 97 98 rtc.regs[MC_SEC] = TOBCD(gnow->tm_sec); 99 rtc.regs[MC_MIN] = TOBCD(gnow->tm_min); 100 rtc.regs[MC_HOUR] = TOBCD(gnow->tm_hour); 101 rtc.regs[MC_DOW] = TOBCD(gnow->tm_wday + 1); 102 rtc.regs[MC_DOM] = TOBCD(gnow->tm_mday); 103 rtc.regs[MC_MONTH] = TOBCD(gnow->tm_mon + 1); 104 rtc.regs[MC_YEAR] = TOBCD((gnow->tm_year + 1900) % 100); 105 rtc.regs[NVRAM_CENTURY] = TOBCD((gnow->tm_year + 1900) / 100); 106 rtc.regs[MC_REGD] |= MC_REGD_VRT; 107 } 108 109 /* 110 * rtc_fire1 111 * 112 * Callback for the 1s periodic TOD refresh timer 113 * 114 * Parameters: 115 * fd: unused 116 * type: unused 117 * arg: unused 118 */ 119 static void 120 rtc_fire1(int fd, short type, void *arg) 121 { 122 time_t old = rtc.now; 123 124 time(&rtc.now); 125 126 rtc_updateregs(); 127 if (rtc.now - old > 5) { 128 log_debug("%s: RTC clock drift (%llds), requesting guest " 129 "resync", __func__, (rtc.now - old)); 130 vmmci_ctl(VMMCI_SYNCRTC); 131 } 132 evtimer_add(&rtc.sec, &rtc.sec_tv); 133 } 134 135 /* 136 * rtc_fireper 137 * 138 * Callback for the periodic interrupt timer 139 * 140 * Parameters: 141 * fd: unused 142 * type: unused 143 * arg: (as uint32_t), VM ID to which this RTC belongs 144 */ 145 static void 146 rtc_fireper(int fd, short type, void *arg) 147 { 148 rtc.regs[MC_REGC] |= MC_REGC_PF; 149 150 vcpu_assert_irq((ptrdiff_t)arg, 0, 8); 151 152 evtimer_add(&rtc.per, &rtc.per_tv); 153 } 154 155 /* 156 * mc146818_init 157 * 158 * Initializes the emulated RTC/NVRAM 159 * 160 * Parameters: 161 * vm_id: VM ID to which this RTC belongs 162 * memlo: size of memory in bytes between 16MB .. 4GB 163 * memhi: size of memory in bytes after 4GB 164 */ 165 void 166 mc146818_init(uint32_t vm_id, uint64_t memlo, uint64_t memhi) 167 { 168 memset(&rtc, 0, sizeof(rtc)); 169 time(&rtc.now); 170 171 rtc.regs[MC_REGB] = MC_REGB_24HR; 172 173 memlo /= 65536; 174 memhi /= 65536; 175 176 rtc.regs[NVRAM_MEMSIZE_HI] = (memlo >> 8) & 0xFF; 177 rtc.regs[NVRAM_MEMSIZE_LO] = memlo & 0xFF; 178 rtc.regs[NVRAM_HIMEMSIZE_HI] = (memhi >> 16) & 0xFF; 179 rtc.regs[NVRAM_HIMEMSIZE_MID] = (memhi >> 8) & 0xFF; 180 rtc.regs[NVRAM_HIMEMSIZE_LO] = memhi & 0xFF; 181 182 rtc.regs[NVRAM_SMP_COUNT] = 0; 183 184 rtc_updateregs(); 185 rtc.vm_id = vm_id; 186 187 timerclear(&rtc.sec_tv); 188 rtc.sec_tv.tv_sec = 1; 189 190 timerclear(&rtc.per_tv); 191 192 evtimer_set(&rtc.sec, rtc_fire1, NULL); 193 evtimer_add(&rtc.sec, &rtc.sec_tv); 194 195 evtimer_set(&rtc.per, rtc_fireper, (void *)(intptr_t)rtc.vm_id); 196 197 vm_pipe_init(&dev_pipe, mc146818_pipe_dispatch); 198 event_add(&dev_pipe.read_ev, NULL); 199 } 200 201 /* 202 * rtc_reschedule_per 203 * 204 * Reschedule the periodic interrupt firing rate, based on the currently 205 * selected REGB values. 206 */ 207 static void 208 rtc_reschedule_per(void) 209 { 210 uint16_t rate; 211 uint64_t us; 212 213 if (rtc.regs[MC_REGB] & MC_REGB_PIE) { 214 rate = 32768 >> ((rtc.regs[MC_REGA] & MC_RATE_MASK) - 1); 215 us = (1.0 / rate) * 1000000; 216 rtc.per_tv.tv_usec = us; 217 if (evtimer_pending(&rtc.per, NULL)) 218 evtimer_del(&rtc.per); 219 220 evtimer_add(&rtc.per, &rtc.per_tv); 221 } 222 } 223 224 /* 225 * rtc_update_rega 226 * 227 * Updates the RTC's REGA register 228 * 229 * Parameters: 230 * data: REGA register data 231 */ 232 static void 233 rtc_update_rega(uint32_t data) 234 { 235 rtc.regs[MC_REGA] = data; 236 if ((rtc.regs[MC_REGA] ^ data) & 0x0f) 237 vm_pipe_send(&dev_pipe, MC146818_RESCHEDULE_PER); 238 } 239 240 /* 241 * rtc_update_regb 242 * 243 * Updates the RTC's REGB register 244 * 245 * Parameters: 246 * data: REGB register data 247 */ 248 static void 249 rtc_update_regb(uint32_t data) 250 { 251 if (data & MC_REGB_DSE) 252 log_warnx("%s: DSE mode not supported", __func__); 253 254 if (!(data & MC_REGB_24HR)) 255 log_warnx("%s: 12 hour mode not supported", __func__); 256 257 rtc.regs[MC_REGB] = data; 258 259 if (data & MC_REGB_PIE) 260 vm_pipe_send(&dev_pipe, MC146818_RESCHEDULE_PER); 261 } 262 263 /* 264 * vcpu_exit_mc146818 265 * 266 * Handles emulated MC146818 RTC access (in/out instruction to RTC ports). 267 * 268 * Parameters: 269 * vrp: vm run parameters containing exit information for the I/O 270 * instruction being performed 271 * 272 * Return value: 273 * Interrupt to inject to the guest VM, or 0xFF if no interrupt should 274 * be injected. 275 */ 276 uint8_t 277 vcpu_exit_mc146818(struct vm_run_params *vrp) 278 { 279 struct vm_exit *vei = vrp->vrp_exit; 280 uint16_t port = vei->vei.vei_port; 281 uint8_t dir = vei->vei.vei_dir; 282 uint32_t data = 0; 283 284 get_input_data(vei, &data); 285 286 if (port == IO_RTC) { 287 /* Discard NMI bit */ 288 if (data & 0x80) 289 data &= ~0x80; 290 291 if (dir == 0) { 292 if (data < (NVRAM_SIZE)) 293 rtc.idx = data; 294 else 295 rtc.idx = MC_REGD; 296 } else 297 set_return_data(vei, rtc.idx); 298 } else if (port == IO_RTC + 1) { 299 if (dir == 0) { 300 switch (rtc.idx) { 301 case MC_SEC ... MC_YEAR: 302 case MC_NVRAM_START ... MC_NVRAM_START + MC_NVRAM_SIZE: 303 rtc.regs[rtc.idx] = data; 304 break; 305 case MC_REGA: 306 rtc_update_rega(data); 307 break; 308 case MC_REGB: 309 rtc_update_regb(data); 310 break; 311 case MC_REGC: 312 case MC_REGD: 313 log_warnx("%s: mc146818 illegal write " 314 "of reg 0x%x", __func__, rtc.idx); 315 break; 316 default: 317 log_warnx("%s: mc146818 illegal reg %x\n", 318 __func__, rtc.idx); 319 } 320 } else { 321 data = rtc.regs[rtc.idx]; 322 set_return_data(vei, data); 323 324 if (rtc.idx == MC_REGC) { 325 /* Reset IRQ state */ 326 rtc.regs[MC_REGC] &= ~MC_REGC_PF; 327 } 328 } 329 } else { 330 log_warnx("%s: mc146818 unknown port 0x%x", 331 __func__, vei->vei.vei_port); 332 } 333 334 return 0xFF; 335 } 336 337 int 338 mc146818_dump(int fd) 339 { 340 log_debug("%s: sending RTC", __func__); 341 if (atomicio(vwrite, fd, &rtc, sizeof(rtc)) != sizeof(rtc)) { 342 log_warnx("%s: error writing RTC to fd", __func__); 343 return (-1); 344 } 345 return (0); 346 } 347 348 int 349 mc146818_restore(int fd, uint32_t vm_id) 350 { 351 log_debug("%s: restoring RTC", __func__); 352 if (atomicio(read, fd, &rtc, sizeof(rtc)) != sizeof(rtc)) { 353 log_warnx("%s: error reading RTC from fd", __func__); 354 return (-1); 355 } 356 rtc.vm_id = vm_id; 357 358 memset(&rtc.sec, 0, sizeof(struct event)); 359 memset(&rtc.per, 0, sizeof(struct event)); 360 evtimer_set(&rtc.sec, rtc_fire1, NULL); 361 evtimer_set(&rtc.per, rtc_fireper, (void *)(intptr_t)rtc.vm_id); 362 363 vm_pipe_init(&dev_pipe, mc146818_pipe_dispatch); 364 365 return (0); 366 } 367 368 void 369 mc146818_stop(void) 370 { 371 evtimer_del(&rtc.per); 372 evtimer_del(&rtc.sec); 373 event_del(&dev_pipe.read_ev); 374 } 375 376 void 377 mc146818_start(void) 378 { 379 evtimer_add(&rtc.sec, &rtc.sec_tv); 380 event_add(&dev_pipe.read_ev, NULL); 381 rtc_reschedule_per(); 382 } 383