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