]>
Commit | Line | Data |
---|---|---|
d811d61f MCA |
1 | /* |
2 | * QEMU PowerMac PMU device support | |
3 | * | |
4 | * Copyright (c) 2016 Benjamin Herrenschmidt, IBM Corp. | |
5 | * Copyright (c) 2018 Mark Cave-Ayland | |
6 | * | |
7 | * Based on the CUDA device by: | |
8 | * | |
9 | * Copyright (c) 2004-2007 Fabrice Bellard | |
10 | * Copyright (c) 2007 Jocelyn Mayer | |
11 | * | |
12 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
13 | * of this software and associated documentation files (the "Software"), to deal | |
14 | * in the Software without restriction, including without limitation the rights | |
15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
16 | * copies of the Software, and to permit persons to whom the Software is | |
17 | * furnished to do so, subject to the following conditions: | |
18 | * | |
19 | * The above copyright notice and this permission notice shall be included in | |
20 | * all copies or substantial portions of the Software. | |
21 | * | |
22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
25 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
28 | * THE SOFTWARE. | |
29 | */ | |
30 | ||
31 | #include "qemu/osdep.h" | |
a8d25326 | 32 | #include "qemu-common.h" |
d811d61f | 33 | #include "hw/ppc/mac.h" |
a27bd6c7 | 34 | #include "hw/qdev-properties.h" |
d6454270 | 35 | #include "migration/vmstate.h" |
d811d61f | 36 | #include "hw/input/adb.h" |
64552b6b | 37 | #include "hw/irq.h" |
d811d61f MCA |
38 | #include "hw/misc/mos6522.h" |
39 | #include "hw/misc/macio/gpio.h" | |
40 | #include "hw/misc/macio/pmu.h" | |
41 | #include "qemu/timer.h" | |
54d31236 | 42 | #include "sysemu/runstate.h" |
d811d61f MCA |
43 | #include "qemu/cutils.h" |
44 | #include "qemu/log.h" | |
0b8fa32f | 45 | #include "qemu/module.h" |
d811d61f MCA |
46 | #include "trace.h" |
47 | ||
48 | ||
49 | /* Bits in B data register: all active low */ | |
50 | #define TACK 0x08 /* Transfer request (input) */ | |
51 | #define TREQ 0x10 /* Transfer acknowledge (output) */ | |
52 | ||
53 | /* PMU returns time_t's offset from Jan 1, 1904, not 1970 */ | |
54 | #define RTC_OFFSET 2082844800 | |
55 | ||
56 | #define VIA_TIMER_FREQ (4700000 / 6) | |
57 | ||
58 | static void via_update_irq(PMUState *s) | |
59 | { | |
60 | MOS6522PMUState *mps = MOS6522_PMU(&s->mos6522_pmu); | |
61 | MOS6522State *ms = MOS6522(mps); | |
62 | ||
63 | bool new_state = !!(ms->ifr & ms->ier & (SR_INT | T1_INT | T2_INT)); | |
64 | ||
65 | if (new_state != s->via_irq_state) { | |
66 | s->via_irq_state = new_state; | |
67 | qemu_set_irq(s->via_irq, new_state); | |
68 | } | |
69 | } | |
70 | ||
71 | static void via_set_sr_int(void *opaque) | |
72 | { | |
73 | PMUState *s = opaque; | |
74 | MOS6522PMUState *mps = MOS6522_PMU(&s->mos6522_pmu); | |
75 | MOS6522State *ms = MOS6522(mps); | |
76 | MOS6522DeviceClass *mdc = MOS6522_DEVICE_GET_CLASS(ms); | |
77 | ||
78 | mdc->set_sr_int(ms); | |
79 | } | |
80 | ||
81 | static void pmu_update_extirq(PMUState *s) | |
82 | { | |
83 | if ((s->intbits & s->intmask) != 0) { | |
84 | macio_set_gpio(s->gpio, 1, false); | |
85 | } else { | |
86 | macio_set_gpio(s->gpio, 1, true); | |
87 | } | |
88 | } | |
89 | ||
90 | static void pmu_adb_poll(void *opaque) | |
91 | { | |
92 | PMUState *s = opaque; | |
93 | int olen; | |
94 | ||
95 | if (!(s->intbits & PMU_INT_ADB)) { | |
96 | olen = adb_poll(&s->adb_bus, s->adb_reply, s->adb_poll_mask); | |
97 | trace_pmu_adb_poll(olen); | |
98 | ||
99 | if (olen > 0) { | |
100 | s->adb_reply_size = olen; | |
101 | s->intbits |= PMU_INT_ADB | PMU_INT_ADB_AUTO; | |
102 | pmu_update_extirq(s); | |
103 | } | |
104 | } | |
105 | ||
106 | timer_mod(s->adb_poll_timer, | |
107 | qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 30); | |
108 | } | |
109 | ||
110 | static void pmu_one_sec_timer(void *opaque) | |
111 | { | |
112 | PMUState *s = opaque; | |
113 | ||
114 | trace_pmu_one_sec_timer(); | |
115 | ||
116 | s->intbits |= PMU_INT_TICK; | |
117 | pmu_update_extirq(s); | |
118 | s->one_sec_target += 1000; | |
119 | ||
120 | timer_mod(s->one_sec_timer, s->one_sec_target); | |
121 | } | |
122 | ||
123 | static void pmu_cmd_int_ack(PMUState *s, | |
124 | const uint8_t *in_data, uint8_t in_len, | |
125 | uint8_t *out_data, uint8_t *out_len) | |
126 | { | |
127 | if (in_len != 0) { | |
128 | qemu_log_mask(LOG_GUEST_ERROR, | |
129 | "PMU: INT_ACK command, invalid len: %d want: 0\n", | |
130 | in_len); | |
131 | return; | |
132 | } | |
133 | ||
134 | /* Make appropriate reply packet */ | |
135 | if (s->intbits & PMU_INT_ADB) { | |
136 | if (!s->adb_reply_size) { | |
137 | qemu_log_mask(LOG_GUEST_ERROR, | |
138 | "Odd, PMU_INT_ADB set with no reply in buffer\n"); | |
139 | } | |
140 | ||
141 | memcpy(out_data + 1, s->adb_reply, s->adb_reply_size); | |
142 | out_data[0] = s->intbits & (PMU_INT_ADB | PMU_INT_ADB_AUTO); | |
143 | *out_len = s->adb_reply_size + 1; | |
144 | s->intbits &= ~(PMU_INT_ADB | PMU_INT_ADB_AUTO); | |
145 | s->adb_reply_size = 0; | |
146 | } else { | |
147 | out_data[0] = s->intbits; | |
148 | s->intbits = 0; | |
149 | *out_len = 1; | |
150 | } | |
151 | ||
152 | pmu_update_extirq(s); | |
153 | } | |
154 | ||
155 | static void pmu_cmd_set_int_mask(PMUState *s, | |
156 | const uint8_t *in_data, uint8_t in_len, | |
157 | uint8_t *out_data, uint8_t *out_len) | |
158 | { | |
159 | if (in_len != 1) { | |
160 | qemu_log_mask(LOG_GUEST_ERROR, | |
161 | "PMU: SET_INT_MASK command, invalid len: %d want: 1\n", | |
162 | in_len); | |
163 | return; | |
164 | } | |
165 | ||
166 | trace_pmu_cmd_set_int_mask(s->intmask); | |
167 | s->intmask = in_data[0]; | |
168 | ||
169 | pmu_update_extirq(s); | |
170 | } | |
171 | ||
172 | static void pmu_cmd_set_adb_autopoll(PMUState *s, uint16_t mask) | |
173 | { | |
174 | trace_pmu_cmd_set_adb_autopoll(mask); | |
175 | ||
176 | if (s->autopoll_mask == mask) { | |
177 | return; | |
178 | } | |
179 | ||
180 | s->autopoll_mask = mask; | |
181 | if (mask) { | |
182 | timer_mod(s->adb_poll_timer, | |
183 | qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 30); | |
184 | } else { | |
185 | timer_del(s->adb_poll_timer); | |
186 | } | |
187 | } | |
188 | ||
189 | static void pmu_cmd_adb(PMUState *s, | |
190 | const uint8_t *in_data, uint8_t in_len, | |
191 | uint8_t *out_data, uint8_t *out_len) | |
192 | { | |
193 | int len, adblen; | |
194 | uint8_t adb_cmd[255]; | |
195 | ||
196 | if (in_len < 2) { | |
197 | qemu_log_mask(LOG_GUEST_ERROR, | |
198 | "PMU: ADB PACKET, invalid len: %d want at least 2\n", | |
199 | in_len); | |
200 | return; | |
201 | } | |
202 | ||
203 | *out_len = 0; | |
204 | ||
205 | if (!s->has_adb) { | |
206 | trace_pmu_cmd_adb_nobus(); | |
207 | return; | |
208 | } | |
209 | ||
210 | /* Set autopoll is a special form of the command */ | |
211 | if (in_data[0] == 0 && in_data[1] == 0x86) { | |
212 | uint16_t mask = in_data[2]; | |
213 | mask = (mask << 8) | in_data[3]; | |
214 | if (in_len != 4) { | |
215 | qemu_log_mask(LOG_GUEST_ERROR, | |
216 | "PMU: ADB Autopoll requires 4 bytes, got %d\n", | |
217 | in_len); | |
218 | return; | |
219 | } | |
220 | ||
221 | pmu_cmd_set_adb_autopoll(s, mask); | |
222 | return; | |
223 | } | |
224 | ||
225 | trace_pmu_cmd_adb_request(in_len, in_data[0], in_data[1], in_data[2], | |
226 | in_data[3], in_data[4]); | |
227 | ||
228 | *out_len = 0; | |
229 | ||
230 | /* Check ADB len */ | |
231 | adblen = in_data[2]; | |
232 | if (adblen > (in_len - 3)) { | |
233 | qemu_log_mask(LOG_GUEST_ERROR, | |
234 | "PMU: ADB len is %d > %d (in_len -3)...erroring\n", | |
235 | adblen, in_len - 3); | |
236 | len = -1; | |
237 | } else if (adblen > 252) { | |
238 | qemu_log_mask(LOG_GUEST_ERROR, "PMU: ADB command too big!\n"); | |
239 | len = -1; | |
240 | } else { | |
241 | /* Format command */ | |
242 | adb_cmd[0] = in_data[0]; | |
243 | memcpy(&adb_cmd[1], &in_data[3], in_len - 3); | |
244 | len = adb_request(&s->adb_bus, s->adb_reply + 2, adb_cmd, in_len - 2); | |
245 | ||
246 | trace_pmu_cmd_adb_reply(len); | |
247 | } | |
248 | ||
249 | if (len > 0) { | |
250 | /* XXX Check this */ | |
251 | s->adb_reply_size = len + 2; | |
252 | s->adb_reply[0] = 0x01; | |
253 | s->adb_reply[1] = len; | |
254 | } else { | |
255 | /* XXX Check this */ | |
256 | s->adb_reply_size = 1; | |
257 | s->adb_reply[0] = 0x00; | |
258 | } | |
259 | ||
260 | s->intbits |= PMU_INT_ADB; | |
261 | pmu_update_extirq(s); | |
262 | } | |
263 | ||
264 | static void pmu_cmd_adb_poll_off(PMUState *s, | |
265 | const uint8_t *in_data, uint8_t in_len, | |
266 | uint8_t *out_data, uint8_t *out_len) | |
267 | { | |
268 | if (in_len != 0) { | |
269 | qemu_log_mask(LOG_GUEST_ERROR, | |
270 | "PMU: ADB POLL OFF command, invalid len: %d want: 0\n", | |
271 | in_len); | |
272 | return; | |
273 | } | |
274 | ||
275 | if (s->has_adb && s->autopoll_mask) { | |
276 | timer_del(s->adb_poll_timer); | |
277 | s->autopoll_mask = false; | |
278 | } | |
279 | } | |
280 | ||
281 | static void pmu_cmd_shutdown(PMUState *s, | |
282 | const uint8_t *in_data, uint8_t in_len, | |
283 | uint8_t *out_data, uint8_t *out_len) | |
284 | { | |
285 | if (in_len != 4) { | |
286 | qemu_log_mask(LOG_GUEST_ERROR, | |
287 | "PMU: SHUTDOWN command, invalid len: %d want: 4\n", | |
288 | in_len); | |
289 | return; | |
290 | } | |
291 | ||
292 | *out_len = 1; | |
293 | out_data[0] = 0; | |
294 | ||
295 | if (in_data[0] != 'M' || in_data[1] != 'A' || in_data[2] != 'T' || | |
296 | in_data[3] != 'T') { | |
297 | ||
298 | qemu_log_mask(LOG_GUEST_ERROR, | |
299 | "PMU: SHUTDOWN command, Bad MATT signature\n"); | |
300 | return; | |
301 | } | |
302 | ||
303 | qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); | |
304 | } | |
305 | ||
306 | static void pmu_cmd_reset(PMUState *s, | |
307 | const uint8_t *in_data, uint8_t in_len, | |
308 | uint8_t *out_data, uint8_t *out_len) | |
309 | { | |
310 | if (in_len != 0) { | |
311 | qemu_log_mask(LOG_GUEST_ERROR, | |
312 | "PMU: RESET command, invalid len: %d want: 0\n", | |
313 | in_len); | |
314 | return; | |
315 | } | |
316 | ||
317 | qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); | |
318 | } | |
319 | ||
320 | static void pmu_cmd_get_rtc(PMUState *s, | |
321 | const uint8_t *in_data, uint8_t in_len, | |
322 | uint8_t *out_data, uint8_t *out_len) | |
323 | { | |
324 | uint32_t ti; | |
325 | ||
326 | if (in_len != 0) { | |
327 | qemu_log_mask(LOG_GUEST_ERROR, | |
328 | "PMU: GET_RTC command, invalid len: %d want: 0\n", | |
329 | in_len); | |
330 | return; | |
331 | } | |
332 | ||
333 | ti = s->tick_offset + (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) | |
334 | / NANOSECONDS_PER_SECOND); | |
335 | out_data[0] = ti >> 24; | |
336 | out_data[1] = ti >> 16; | |
337 | out_data[2] = ti >> 8; | |
338 | out_data[3] = ti; | |
339 | *out_len = 4; | |
340 | } | |
341 | ||
342 | static void pmu_cmd_set_rtc(PMUState *s, | |
343 | const uint8_t *in_data, uint8_t in_len, | |
344 | uint8_t *out_data, uint8_t *out_len) | |
345 | { | |
346 | uint32_t ti; | |
347 | ||
348 | if (in_len != 4) { | |
349 | qemu_log_mask(LOG_GUEST_ERROR, | |
350 | "PMU: SET_RTC command, invalid len: %d want: 4\n", | |
351 | in_len); | |
352 | return; | |
353 | } | |
354 | ||
355 | ti = (((uint32_t)in_data[0]) << 24) + (((uint32_t)in_data[1]) << 16) | |
356 | + (((uint32_t)in_data[2]) << 8) + in_data[3]; | |
357 | ||
358 | s->tick_offset = ti - (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) | |
359 | / NANOSECONDS_PER_SECOND); | |
360 | } | |
361 | ||
362 | static void pmu_cmd_system_ready(PMUState *s, | |
363 | const uint8_t *in_data, uint8_t in_len, | |
364 | uint8_t *out_data, uint8_t *out_len) | |
365 | { | |
366 | /* Do nothing */ | |
367 | } | |
368 | ||
369 | static void pmu_cmd_get_version(PMUState *s, | |
370 | const uint8_t *in_data, uint8_t in_len, | |
371 | uint8_t *out_data, uint8_t *out_len) | |
372 | { | |
373 | *out_len = 1; | |
374 | *out_data = 1; /* ??? Check what Apple does */ | |
375 | } | |
376 | ||
377 | static void pmu_cmd_power_events(PMUState *s, | |
378 | const uint8_t *in_data, uint8_t in_len, | |
379 | uint8_t *out_data, uint8_t *out_len) | |
380 | { | |
381 | if (in_len < 1) { | |
382 | qemu_log_mask(LOG_GUEST_ERROR, | |
383 | "PMU: POWER EVENTS command, invalid len %d, want at least 1\n", | |
384 | in_len); | |
385 | return; | |
386 | } | |
387 | ||
388 | switch (in_data[0]) { | |
389 | /* Dummies for now */ | |
390 | case PMU_PWR_GET_POWERUP_EVENTS: | |
391 | *out_len = 2; | |
392 | out_data[0] = 0; | |
393 | out_data[1] = 0; | |
394 | break; | |
395 | case PMU_PWR_SET_POWERUP_EVENTS: | |
396 | case PMU_PWR_CLR_POWERUP_EVENTS: | |
397 | break; | |
398 | case PMU_PWR_GET_WAKEUP_EVENTS: | |
399 | *out_len = 2; | |
400 | out_data[0] = 0; | |
401 | out_data[1] = 0; | |
402 | break; | |
403 | case PMU_PWR_SET_WAKEUP_EVENTS: | |
404 | case PMU_PWR_CLR_WAKEUP_EVENTS: | |
405 | break; | |
406 | default: | |
407 | qemu_log_mask(LOG_GUEST_ERROR, | |
408 | "PMU: POWER EVENTS unknown subcommand 0x%02x\n", | |
409 | in_data[0]); | |
410 | } | |
411 | } | |
412 | ||
413 | static void pmu_cmd_get_cover(PMUState *s, | |
414 | const uint8_t *in_data, uint8_t in_len, | |
415 | uint8_t *out_data, uint8_t *out_len) | |
416 | { | |
417 | /* Not 100% sure here, will have to check what a real Mac | |
418 | * returns other than byte 0 bit 0 is LID closed on laptops | |
419 | */ | |
420 | *out_len = 1; | |
421 | *out_data = 0x00; | |
422 | } | |
423 | ||
424 | static void pmu_cmd_download_status(PMUState *s, | |
425 | const uint8_t *in_data, uint8_t in_len, | |
426 | uint8_t *out_data, uint8_t *out_len) | |
427 | { | |
428 | /* This has to do with PMU firmware updates as far as I can tell. | |
429 | * | |
430 | * We return 0x62 which is what OpenPMU expects | |
431 | */ | |
432 | *out_len = 1; | |
433 | *out_data = 0x62; | |
434 | } | |
435 | ||
436 | static void pmu_cmd_read_pmu_ram(PMUState *s, | |
437 | const uint8_t *in_data, uint8_t in_len, | |
438 | uint8_t *out_data, uint8_t *out_len) | |
439 | { | |
440 | if (in_len < 3) { | |
441 | qemu_log_mask(LOG_GUEST_ERROR, | |
442 | "PMU: READ_PMU_RAM command, invalid len %d, expected 3\n", | |
443 | in_len); | |
444 | return; | |
445 | } | |
446 | ||
447 | qemu_log_mask(LOG_GUEST_ERROR, | |
448 | "PMU: Unsupported READ_PMU_RAM, args: %02x %02x %02x\n", | |
449 | in_data[0], in_data[1], in_data[2]); | |
450 | ||
451 | *out_len = 0; | |
452 | } | |
453 | ||
454 | /* description of commands */ | |
455 | typedef struct PMUCmdHandler { | |
456 | uint8_t command; | |
457 | const char *name; | |
458 | void (*handler)(PMUState *s, | |
459 | const uint8_t *in_args, uint8_t in_len, | |
460 | uint8_t *out_args, uint8_t *out_len); | |
461 | } PMUCmdHandler; | |
462 | ||
463 | static const PMUCmdHandler PMUCmdHandlers[] = { | |
464 | { PMU_INT_ACK, "INT ACK", pmu_cmd_int_ack }, | |
465 | { PMU_SET_INTR_MASK, "SET INT MASK", pmu_cmd_set_int_mask }, | |
466 | { PMU_ADB_CMD, "ADB COMMAND", pmu_cmd_adb }, | |
467 | { PMU_ADB_POLL_OFF, "ADB POLL OFF", pmu_cmd_adb_poll_off }, | |
468 | { PMU_RESET, "REBOOT", pmu_cmd_reset }, | |
469 | { PMU_SHUTDOWN, "SHUTDOWN", pmu_cmd_shutdown }, | |
470 | { PMU_READ_RTC, "GET RTC", pmu_cmd_get_rtc }, | |
471 | { PMU_SET_RTC, "SET RTC", pmu_cmd_set_rtc }, | |
472 | { PMU_SYSTEM_READY, "SYSTEM READY", pmu_cmd_system_ready }, | |
473 | { PMU_GET_VERSION, "GET VERSION", pmu_cmd_get_version }, | |
474 | { PMU_POWER_EVENTS, "POWER EVENTS", pmu_cmd_power_events }, | |
475 | { PMU_GET_COVER, "GET_COVER", pmu_cmd_get_cover }, | |
476 | { PMU_DOWNLOAD_STATUS, "DOWNLOAD STATUS", pmu_cmd_download_status }, | |
477 | { PMU_READ_PMU_RAM, "READ PMGR RAM", pmu_cmd_read_pmu_ram }, | |
478 | }; | |
479 | ||
480 | static void pmu_dispatch_cmd(PMUState *s) | |
481 | { | |
482 | unsigned int i; | |
483 | ||
484 | /* No response by default */ | |
485 | s->cmd_rsp_sz = 0; | |
486 | ||
487 | for (i = 0; i < ARRAY_SIZE(PMUCmdHandlers); i++) { | |
488 | const PMUCmdHandler *desc = &PMUCmdHandlers[i]; | |
489 | ||
490 | if (desc->command != s->cmd) { | |
491 | continue; | |
492 | } | |
493 | ||
494 | trace_pmu_dispatch_cmd(desc->name); | |
495 | desc->handler(s, s->cmd_buf, s->cmd_buf_pos, | |
496 | s->cmd_rsp, &s->cmd_rsp_sz); | |
497 | ||
498 | if (s->rsplen != -1 && s->rsplen != s->cmd_rsp_sz) { | |
499 | trace_pmu_debug_protocol_string("QEMU internal cmd resp mismatch!"); | |
500 | } else { | |
501 | trace_pmu_debug_protocol_resp_size(s->cmd_rsp_sz); | |
502 | } | |
503 | ||
504 | return; | |
505 | } | |
506 | ||
507 | trace_pmu_dispatch_unknown_cmd(s->cmd); | |
508 | ||
509 | /* Manufacture fake response with 0's */ | |
510 | if (s->rsplen == -1) { | |
511 | s->cmd_rsp_sz = 0; | |
512 | } else { | |
513 | s->cmd_rsp_sz = s->rsplen; | |
514 | memset(s->cmd_rsp, 0, s->rsplen); | |
515 | } | |
516 | } | |
517 | ||
518 | static void pmu_update(PMUState *s) | |
519 | { | |
520 | MOS6522PMUState *mps = &s->mos6522_pmu; | |
521 | MOS6522State *ms = MOS6522(mps); | |
522 | ||
523 | /* Only react to changes in reg B */ | |
524 | if (ms->b == s->last_b) { | |
525 | return; | |
526 | } | |
527 | s->last_b = ms->b; | |
528 | ||
529 | /* Check the TREQ / TACK state */ | |
530 | switch (ms->b & (TREQ | TACK)) { | |
531 | case TREQ: | |
532 | /* This is an ack release, handle it and bail out */ | |
533 | ms->b |= TACK; | |
534 | s->last_b = ms->b; | |
535 | ||
536 | trace_pmu_debug_protocol_string("handshake: TREQ high, setting TACK"); | |
537 | return; | |
538 | case TACK: | |
539 | /* This is a valid request, handle below */ | |
540 | break; | |
541 | case TREQ | TACK: | |
542 | /* This is an idle state */ | |
543 | return; | |
544 | default: | |
545 | /* Invalid state, log and ignore */ | |
546 | trace_pmu_debug_protocol_error(ms->b); | |
547 | return; | |
548 | } | |
549 | ||
550 | /* If we wanted to handle commands asynchronously, this is where | |
551 | * we would delay the clearing of TACK until we are ready to send | |
552 | * the response | |
553 | */ | |
554 | ||
555 | /* We have a request, handshake TACK so we don't stay in | |
556 | * an invalid state. If we were concurrent with the OS we | |
557 | * should only do this after we grabbed the SR but that isn't | |
558 | * a problem here. | |
559 | */ | |
560 | ||
561 | trace_pmu_debug_protocol_clear_treq(s->cmd_state); | |
562 | ||
563 | ms->b &= ~TACK; | |
564 | s->last_b = ms->b; | |
565 | ||
566 | /* Act according to state */ | |
567 | switch (s->cmd_state) { | |
568 | case pmu_state_idle: | |
569 | if (!(ms->acr & SR_OUT)) { | |
570 | trace_pmu_debug_protocol_string("protocol error! " | |
571 | "state idle, ACR reading"); | |
572 | break; | |
573 | } | |
574 | ||
575 | s->cmd = ms->sr; | |
576 | via_set_sr_int(s); | |
577 | s->cmdlen = pmu_data_len[s->cmd][0]; | |
578 | s->rsplen = pmu_data_len[s->cmd][1]; | |
579 | s->cmd_buf_pos = 0; | |
580 | s->cmd_rsp_pos = 0; | |
581 | s->cmd_state = pmu_state_cmd; | |
582 | ||
583 | trace_pmu_debug_protocol_cmd(s->cmd, s->cmdlen, s->rsplen); | |
584 | break; | |
585 | ||
586 | case pmu_state_cmd: | |
587 | if (!(ms->acr & SR_OUT)) { | |
588 | trace_pmu_debug_protocol_string("protocol error! " | |
589 | "state cmd, ACR reading"); | |
590 | break; | |
591 | } | |
592 | ||
593 | if (s->cmdlen == -1) { | |
594 | trace_pmu_debug_protocol_cmdlen(ms->sr); | |
595 | ||
596 | s->cmdlen = ms->sr; | |
597 | if (s->cmdlen > sizeof(s->cmd_buf)) { | |
598 | trace_pmu_debug_protocol_cmd_toobig(s->cmdlen); | |
599 | } | |
600 | } else if (s->cmd_buf_pos < sizeof(s->cmd_buf)) { | |
601 | s->cmd_buf[s->cmd_buf_pos++] = ms->sr; | |
602 | } | |
603 | ||
604 | via_set_sr_int(s); | |
605 | break; | |
606 | ||
607 | case pmu_state_rsp: | |
608 | if (ms->acr & SR_OUT) { | |
609 | trace_pmu_debug_protocol_string("protocol error! " | |
610 | "state resp, ACR writing"); | |
611 | break; | |
612 | } | |
613 | ||
614 | if (s->rsplen == -1) { | |
615 | trace_pmu_debug_protocol_cmd_send_resp_size(s->cmd_rsp_sz); | |
616 | ||
617 | ms->sr = s->cmd_rsp_sz; | |
618 | s->rsplen = s->cmd_rsp_sz; | |
619 | } else if (s->cmd_rsp_pos < s->cmd_rsp_sz) { | |
620 | trace_pmu_debug_protocol_cmd_send_resp(s->cmd_rsp_pos, s->rsplen); | |
621 | ||
622 | ms->sr = s->cmd_rsp[s->cmd_rsp_pos++]; | |
623 | } | |
624 | ||
625 | via_set_sr_int(s); | |
626 | break; | |
627 | } | |
628 | ||
629 | /* Check for state completion */ | |
630 | if (s->cmd_state == pmu_state_cmd && s->cmdlen == s->cmd_buf_pos) { | |
631 | trace_pmu_debug_protocol_string("Command reception complete, " | |
632 | "dispatching..."); | |
633 | ||
634 | pmu_dispatch_cmd(s); | |
635 | s->cmd_state = pmu_state_rsp; | |
636 | } | |
637 | ||
638 | if (s->cmd_state == pmu_state_rsp && s->rsplen == s->cmd_rsp_pos) { | |
639 | trace_pmu_debug_protocol_cmd_resp_complete(ms->ier); | |
640 | ||
641 | s->cmd_state = pmu_state_idle; | |
642 | } | |
643 | } | |
644 | ||
645 | static uint64_t mos6522_pmu_read(void *opaque, hwaddr addr, unsigned size) | |
646 | { | |
647 | PMUState *s = opaque; | |
648 | MOS6522PMUState *mps = &s->mos6522_pmu; | |
649 | MOS6522State *ms = MOS6522(mps); | |
650 | ||
651 | addr = (addr >> 9) & 0xf; | |
652 | return mos6522_read(ms, addr, size); | |
653 | } | |
654 | ||
655 | static void mos6522_pmu_write(void *opaque, hwaddr addr, uint64_t val, | |
656 | unsigned size) | |
657 | { | |
658 | PMUState *s = opaque; | |
659 | MOS6522PMUState *mps = &s->mos6522_pmu; | |
660 | MOS6522State *ms = MOS6522(mps); | |
661 | ||
662 | addr = (addr >> 9) & 0xf; | |
663 | mos6522_write(ms, addr, val, size); | |
664 | } | |
665 | ||
666 | static const MemoryRegionOps mos6522_pmu_ops = { | |
667 | .read = mos6522_pmu_read, | |
668 | .write = mos6522_pmu_write, | |
669 | .endianness = DEVICE_BIG_ENDIAN, | |
670 | .impl = { | |
671 | .min_access_size = 1, | |
672 | .max_access_size = 1, | |
673 | }, | |
674 | }; | |
675 | ||
676 | static bool pmu_adb_state_needed(void *opaque) | |
677 | { | |
678 | PMUState *s = opaque; | |
679 | ||
680 | return s->has_adb; | |
681 | } | |
682 | ||
683 | static const VMStateDescription vmstate_pmu_adb = { | |
684 | .name = "pmu/adb", | |
685 | .version_id = 0, | |
686 | .minimum_version_id = 0, | |
687 | .needed = pmu_adb_state_needed, | |
688 | .fields = (VMStateField[]) { | |
689 | VMSTATE_UINT16(adb_poll_mask, PMUState), | |
690 | VMSTATE_TIMER_PTR(adb_poll_timer, PMUState), | |
691 | VMSTATE_UINT8(adb_reply_size, PMUState), | |
692 | VMSTATE_BUFFER(adb_reply, PMUState), | |
0c2adc17 | 693 | VMSTATE_END_OF_LIST() |
d811d61f MCA |
694 | } |
695 | }; | |
696 | ||
697 | static const VMStateDescription vmstate_pmu = { | |
698 | .name = "pmu", | |
699 | .version_id = 0, | |
700 | .minimum_version_id = 0, | |
701 | .fields = (VMStateField[]) { | |
702 | VMSTATE_STRUCT(mos6522_pmu.parent_obj, PMUState, 0, vmstate_mos6522, | |
703 | MOS6522State), | |
704 | VMSTATE_UINT8(last_b, PMUState), | |
705 | VMSTATE_UINT8(cmd, PMUState), | |
706 | VMSTATE_UINT32(cmdlen, PMUState), | |
707 | VMSTATE_UINT32(rsplen, PMUState), | |
708 | VMSTATE_UINT8(cmd_buf_pos, PMUState), | |
709 | VMSTATE_BUFFER(cmd_buf, PMUState), | |
710 | VMSTATE_UINT8(cmd_rsp_pos, PMUState), | |
711 | VMSTATE_UINT8(cmd_rsp_sz, PMUState), | |
712 | VMSTATE_BUFFER(cmd_rsp, PMUState), | |
713 | VMSTATE_UINT8(intbits, PMUState), | |
714 | VMSTATE_UINT8(intmask, PMUState), | |
715 | VMSTATE_UINT8(autopoll_rate_ms, PMUState), | |
716 | VMSTATE_UINT8(autopoll_mask, PMUState), | |
717 | VMSTATE_UINT32(tick_offset, PMUState), | |
718 | VMSTATE_TIMER_PTR(one_sec_timer, PMUState), | |
719 | VMSTATE_INT64(one_sec_target, PMUState), | |
720 | VMSTATE_END_OF_LIST() | |
721 | }, | |
722 | .subsections = (const VMStateDescription * []) { | |
723 | &vmstate_pmu_adb, | |
724 | } | |
725 | }; | |
726 | ||
727 | static void pmu_reset(DeviceState *dev) | |
728 | { | |
729 | PMUState *s = VIA_PMU(dev); | |
730 | ||
731 | /* OpenBIOS needs to do this? MacOS 9 needs it */ | |
732 | s->intmask = PMU_INT_ADB | PMU_INT_TICK; | |
733 | s->intbits = 0; | |
734 | ||
735 | s->cmd_state = pmu_state_idle; | |
736 | s->autopoll_mask = 0; | |
737 | } | |
738 | ||
739 | static void pmu_realize(DeviceState *dev, Error **errp) | |
740 | { | |
741 | PMUState *s = VIA_PMU(dev); | |
742 | SysBusDevice *sbd; | |
743 | MOS6522State *ms; | |
744 | DeviceState *d; | |
745 | struct tm tm; | |
746 | ||
747 | /* Pass IRQ from 6522 */ | |
748 | d = DEVICE(&s->mos6522_pmu); | |
749 | ms = MOS6522(d); | |
750 | sbd = SYS_BUS_DEVICE(s); | |
751 | sysbus_pass_irq(sbd, SYS_BUS_DEVICE(ms)); | |
752 | ||
753 | qemu_get_timedate(&tm, 0); | |
754 | s->tick_offset = (uint32_t)mktimegm(&tm) + RTC_OFFSET; | |
755 | s->one_sec_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, pmu_one_sec_timer, s); | |
756 | s->one_sec_target = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000; | |
757 | timer_mod(s->one_sec_timer, s->one_sec_target); | |
758 | ||
759 | if (s->has_adb) { | |
760 | qbus_create_inplace(&s->adb_bus, sizeof(s->adb_bus), TYPE_ADB_BUS, | |
761 | DEVICE(dev), "adb.0"); | |
762 | s->adb_poll_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, pmu_adb_poll, s); | |
763 | s->adb_poll_mask = 0xffff; | |
764 | s->autopoll_rate_ms = 20; | |
765 | } | |
766 | } | |
767 | ||
768 | static void pmu_init(Object *obj) | |
769 | { | |
770 | SysBusDevice *d = SYS_BUS_DEVICE(obj); | |
771 | PMUState *s = VIA_PMU(obj); | |
772 | ||
773 | object_property_add_link(obj, "gpio", TYPE_MACIO_GPIO, | |
774 | (Object **) &s->gpio, | |
775 | qdev_prop_allow_set_link_before_realize, | |
776 | 0, NULL); | |
777 | ||
1069a3c6 TH |
778 | sysbus_init_child_obj(obj, "mos6522-pmu", &s->mos6522_pmu, |
779 | sizeof(s->mos6522_pmu), TYPE_MOS6522_PMU); | |
d811d61f MCA |
780 | |
781 | memory_region_init_io(&s->mem, obj, &mos6522_pmu_ops, s, "via-pmu", | |
782 | 0x2000); | |
783 | sysbus_init_mmio(d, &s->mem); | |
784 | } | |
785 | ||
786 | static Property pmu_properties[] = { | |
787 | DEFINE_PROP_BOOL("has-adb", PMUState, has_adb, true), | |
788 | DEFINE_PROP_END_OF_LIST() | |
789 | }; | |
790 | ||
791 | static void pmu_class_init(ObjectClass *oc, void *data) | |
792 | { | |
793 | DeviceClass *dc = DEVICE_CLASS(oc); | |
794 | ||
795 | dc->realize = pmu_realize; | |
796 | dc->reset = pmu_reset; | |
797 | dc->vmsd = &vmstate_pmu; | |
798 | dc->props = pmu_properties; | |
799 | set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); | |
800 | } | |
801 | ||
802 | static const TypeInfo pmu_type_info = { | |
803 | .name = TYPE_VIA_PMU, | |
804 | .parent = TYPE_SYS_BUS_DEVICE, | |
805 | .instance_size = sizeof(PMUState), | |
806 | .instance_init = pmu_init, | |
807 | .class_init = pmu_class_init, | |
808 | }; | |
809 | ||
810 | static void mos6522_pmu_portB_write(MOS6522State *s) | |
811 | { | |
812 | MOS6522PMUState *mps = container_of(s, MOS6522PMUState, parent_obj); | |
813 | PMUState *ps = container_of(mps, PMUState, mos6522_pmu); | |
814 | ||
815 | if ((s->pcr & 0xe0) == 0x20 || (s->pcr & 0xe0) == 0x60) { | |
816 | s->ifr &= ~CB2_INT; | |
817 | } | |
818 | s->ifr &= ~CB1_INT; | |
819 | ||
820 | via_update_irq(ps); | |
821 | pmu_update(ps); | |
822 | } | |
823 | ||
824 | static void mos6522_pmu_portA_write(MOS6522State *s) | |
825 | { | |
826 | MOS6522PMUState *mps = container_of(s, MOS6522PMUState, parent_obj); | |
827 | PMUState *ps = container_of(mps, PMUState, mos6522_pmu); | |
828 | ||
829 | if ((s->pcr & 0x0e) == 0x02 || (s->pcr & 0x0e) == 0x06) { | |
830 | s->ifr &= ~CA2_INT; | |
831 | } | |
832 | s->ifr &= ~CA1_INT; | |
833 | ||
834 | via_update_irq(ps); | |
835 | } | |
836 | ||
837 | static void mos6522_pmu_reset(DeviceState *dev) | |
838 | { | |
839 | MOS6522State *ms = MOS6522(dev); | |
840 | MOS6522PMUState *mps = container_of(ms, MOS6522PMUState, parent_obj); | |
841 | PMUState *s = container_of(mps, PMUState, mos6522_pmu); | |
842 | MOS6522DeviceClass *mdc = MOS6522_DEVICE_GET_CLASS(ms); | |
843 | ||
844 | mdc->parent_reset(dev); | |
845 | ||
846 | ms->timers[0].frequency = VIA_TIMER_FREQ; | |
847 | ms->timers[1].frequency = (SCALE_US * 6000) / 4700; | |
848 | ||
849 | s->last_b = ms->b = TACK | TREQ; | |
850 | } | |
851 | ||
852 | static void mos6522_pmu_class_init(ObjectClass *oc, void *data) | |
853 | { | |
854 | DeviceClass *dc = DEVICE_CLASS(oc); | |
855 | MOS6522DeviceClass *mdc = MOS6522_DEVICE_CLASS(oc); | |
856 | ||
857 | dc->reset = mos6522_pmu_reset; | |
858 | mdc->portB_write = mos6522_pmu_portB_write; | |
859 | mdc->portA_write = mos6522_pmu_portA_write; | |
860 | } | |
861 | ||
862 | static const TypeInfo mos6522_pmu_type_info = { | |
863 | .name = TYPE_MOS6522_PMU, | |
864 | .parent = TYPE_MOS6522, | |
865 | .instance_size = sizeof(MOS6522PMUState), | |
866 | .class_init = mos6522_pmu_class_init, | |
867 | }; | |
868 | ||
869 | static void pmu_register_types(void) | |
870 | { | |
871 | type_register_static(&pmu_type_info); | |
872 | type_register_static(&mos6522_pmu_type_info); | |
873 | } | |
874 | ||
875 | type_init(pmu_register_types) |