]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * QEMU ICH9 TCO emulation | |
3 | * | |
4 | * Copyright (c) 2015 Paulo Alcantara <pcacjr@zytor.com> | |
5 | * | |
6 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
7 | * See the COPYING file in the top-level directory. | |
8 | */ | |
9 | ||
10 | #include "qemu/osdep.h" | |
11 | #include "sysemu/watchdog.h" | |
12 | #include "hw/i386/ich9.h" | |
13 | #include "migration/vmstate.h" | |
14 | ||
15 | #include "hw/acpi/tco.h" | |
16 | #include "trace.h" | |
17 | ||
18 | enum { | |
19 | TCO_RLD_DEFAULT = 0x0000, | |
20 | TCO_DAT_IN_DEFAULT = 0x00, | |
21 | TCO_DAT_OUT_DEFAULT = 0x00, | |
22 | TCO1_STS_DEFAULT = 0x0000, | |
23 | TCO2_STS_DEFAULT = 0x0000, | |
24 | TCO1_CNT_DEFAULT = 0x0000, | |
25 | TCO2_CNT_DEFAULT = 0x0008, | |
26 | TCO_MESSAGE1_DEFAULT = 0x00, | |
27 | TCO_MESSAGE2_DEFAULT = 0x00, | |
28 | TCO_WDCNT_DEFAULT = 0x00, | |
29 | TCO_TMR_DEFAULT = 0x0004, | |
30 | SW_IRQ_GEN_DEFAULT = 0x03, | |
31 | }; | |
32 | ||
33 | static inline void tco_timer_reload(TCOIORegs *tr) | |
34 | { | |
35 | int ticks = tr->tco.tmr & TCO_TMR_MASK; | |
36 | int64_t nsec = (int64_t)ticks * TCO_TICK_NSEC; | |
37 | ||
38 | trace_tco_timer_reload(ticks, nsec / 1000000); | |
39 | tr->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + nsec; | |
40 | timer_mod(tr->tco_timer, tr->expire_time); | |
41 | } | |
42 | ||
43 | static inline void tco_timer_stop(TCOIORegs *tr) | |
44 | { | |
45 | tr->expire_time = -1; | |
46 | timer_del(tr->tco_timer); | |
47 | } | |
48 | ||
49 | static void tco_timer_expired(void *opaque) | |
50 | { | |
51 | TCOIORegs *tr = opaque; | |
52 | ICH9LPCPMRegs *pm = container_of(tr, ICH9LPCPMRegs, tco_regs); | |
53 | ICH9LPCState *lpc = container_of(pm, ICH9LPCState, pm); | |
54 | uint32_t gcs = pci_get_long(lpc->chip_config + ICH9_CC_GCS); | |
55 | ||
56 | trace_tco_timer_expired(tr->timeouts_no, | |
57 | lpc->pin_strap.spkr_hi, | |
58 | !!(gcs & ICH9_CC_GCS_NO_REBOOT)); | |
59 | tr->tco.rld = 0; | |
60 | tr->tco.sts1 |= TCO_TIMEOUT; | |
61 | if (++tr->timeouts_no == 2) { | |
62 | tr->tco.sts2 |= TCO_SECOND_TO_STS; | |
63 | tr->tco.sts2 |= TCO_BOOT_STS; | |
64 | tr->timeouts_no = 0; | |
65 | ||
66 | if (!lpc->pin_strap.spkr_hi && !(gcs & ICH9_CC_GCS_NO_REBOOT)) { | |
67 | watchdog_perform_action(); | |
68 | tco_timer_stop(tr); | |
69 | return; | |
70 | } | |
71 | } | |
72 | ||
73 | if (pm->smi_en & ICH9_PMIO_SMI_EN_TCO_EN) { | |
74 | ich9_generate_smi(); | |
75 | } | |
76 | tr->tco.rld = tr->tco.tmr; | |
77 | tco_timer_reload(tr); | |
78 | } | |
79 | ||
80 | /* NOTE: values of 0 or 1 will be ignored by ICH */ | |
81 | static inline int can_start_tco_timer(TCOIORegs *tr) | |
82 | { | |
83 | return !(tr->tco.cnt1 & TCO_TMR_HLT) && tr->tco.tmr > 1; | |
84 | } | |
85 | ||
86 | static uint32_t tco_ioport_readw(TCOIORegs *tr, uint32_t addr) | |
87 | { | |
88 | uint16_t rld; | |
89 | ||
90 | switch (addr) { | |
91 | case TCO_RLD: | |
92 | if (tr->expire_time != -1) { | |
93 | int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
94 | int64_t elapsed = (tr->expire_time - now) / TCO_TICK_NSEC; | |
95 | rld = (uint16_t)elapsed | (tr->tco.rld & ~TCO_RLD_MASK); | |
96 | } else { | |
97 | rld = tr->tco.rld; | |
98 | } | |
99 | return rld; | |
100 | case TCO_DAT_IN: | |
101 | return tr->tco.din; | |
102 | case TCO_DAT_OUT: | |
103 | return tr->tco.dout; | |
104 | case TCO1_STS: | |
105 | return tr->tco.sts1; | |
106 | case TCO2_STS: | |
107 | return tr->tco.sts2; | |
108 | case TCO1_CNT: | |
109 | return tr->tco.cnt1; | |
110 | case TCO2_CNT: | |
111 | return tr->tco.cnt2; | |
112 | case TCO_MESSAGE1: | |
113 | return tr->tco.msg1; | |
114 | case TCO_MESSAGE2: | |
115 | return tr->tco.msg2; | |
116 | case TCO_WDCNT: | |
117 | return tr->tco.wdcnt; | |
118 | case TCO_TMR: | |
119 | return tr->tco.tmr; | |
120 | case SW_IRQ_GEN: | |
121 | return tr->sw_irq_gen; | |
122 | } | |
123 | return 0; | |
124 | } | |
125 | ||
126 | static void tco_ioport_writew(TCOIORegs *tr, uint32_t addr, uint32_t val) | |
127 | { | |
128 | switch (addr) { | |
129 | case TCO_RLD: | |
130 | tr->timeouts_no = 0; | |
131 | if (can_start_tco_timer(tr)) { | |
132 | tr->tco.rld = tr->tco.tmr; | |
133 | tco_timer_reload(tr); | |
134 | } else { | |
135 | tr->tco.rld = val; | |
136 | } | |
137 | break; | |
138 | case TCO_DAT_IN: | |
139 | tr->tco.din = val; | |
140 | tr->tco.sts1 |= SW_TCO_SMI; | |
141 | ich9_generate_smi(); | |
142 | break; | |
143 | case TCO_DAT_OUT: | |
144 | tr->tco.dout = val; | |
145 | tr->tco.sts1 |= TCO_INT_STS; | |
146 | /* TODO: cause an interrupt, as selected by the TCO_INT_SEL bits */ | |
147 | break; | |
148 | case TCO1_STS: | |
149 | tr->tco.sts1 = val & TCO1_STS_MASK; | |
150 | break; | |
151 | case TCO2_STS: | |
152 | tr->tco.sts2 = val & TCO2_STS_MASK; | |
153 | break; | |
154 | case TCO1_CNT: | |
155 | val &= TCO1_CNT_MASK; | |
156 | /* | |
157 | * once TCO_LOCK bit is set, it can not be cleared by software. a reset | |
158 | * is required to change this bit from 1 to 0 -- it defaults to 0. | |
159 | */ | |
160 | tr->tco.cnt1 = val | (tr->tco.cnt1 & TCO_LOCK); | |
161 | if (can_start_tco_timer(tr)) { | |
162 | tr->tco.rld = tr->tco.tmr; | |
163 | tco_timer_reload(tr); | |
164 | } else { | |
165 | tco_timer_stop(tr); | |
166 | } | |
167 | break; | |
168 | case TCO2_CNT: | |
169 | tr->tco.cnt2 = val; | |
170 | break; | |
171 | case TCO_MESSAGE1: | |
172 | tr->tco.msg1 = val; | |
173 | break; | |
174 | case TCO_MESSAGE2: | |
175 | tr->tco.msg2 = val; | |
176 | break; | |
177 | case TCO_WDCNT: | |
178 | tr->tco.wdcnt = val; | |
179 | break; | |
180 | case TCO_TMR: | |
181 | tr->tco.tmr = val; | |
182 | break; | |
183 | case SW_IRQ_GEN: | |
184 | tr->sw_irq_gen = val; | |
185 | break; | |
186 | } | |
187 | } | |
188 | ||
189 | static uint64_t tco_io_readw(void *opaque, hwaddr addr, unsigned width) | |
190 | { | |
191 | TCOIORegs *tr = opaque; | |
192 | return tco_ioport_readw(tr, addr); | |
193 | } | |
194 | ||
195 | static void tco_io_writew(void *opaque, hwaddr addr, uint64_t val, | |
196 | unsigned width) | |
197 | { | |
198 | TCOIORegs *tr = opaque; | |
199 | tco_ioport_writew(tr, addr, val); | |
200 | } | |
201 | ||
202 | static const MemoryRegionOps tco_io_ops = { | |
203 | .read = tco_io_readw, | |
204 | .write = tco_io_writew, | |
205 | .valid.min_access_size = 1, | |
206 | .valid.max_access_size = 4, | |
207 | .impl.min_access_size = 1, | |
208 | .impl.max_access_size = 2, | |
209 | .endianness = DEVICE_LITTLE_ENDIAN, | |
210 | }; | |
211 | ||
212 | void acpi_pm_tco_init(TCOIORegs *tr, MemoryRegion *parent) | |
213 | { | |
214 | *tr = (TCOIORegs) { | |
215 | .tco = { | |
216 | .rld = TCO_RLD_DEFAULT, | |
217 | .din = TCO_DAT_IN_DEFAULT, | |
218 | .dout = TCO_DAT_OUT_DEFAULT, | |
219 | .sts1 = TCO1_STS_DEFAULT, | |
220 | .sts2 = TCO2_STS_DEFAULT, | |
221 | .cnt1 = TCO1_CNT_DEFAULT, | |
222 | .cnt2 = TCO2_CNT_DEFAULT, | |
223 | .msg1 = TCO_MESSAGE1_DEFAULT, | |
224 | .msg2 = TCO_MESSAGE2_DEFAULT, | |
225 | .wdcnt = TCO_WDCNT_DEFAULT, | |
226 | .tmr = TCO_TMR_DEFAULT, | |
227 | }, | |
228 | .sw_irq_gen = SW_IRQ_GEN_DEFAULT, | |
229 | .tco_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tco_timer_expired, tr), | |
230 | .expire_time = -1, | |
231 | .timeouts_no = 0, | |
232 | }; | |
233 | memory_region_init_io(&tr->io, memory_region_owner(parent), | |
234 | &tco_io_ops, tr, "sm-tco", ICH9_PMIO_TCO_LEN); | |
235 | memory_region_add_subregion(parent, ICH9_PMIO_TCO_RLD, &tr->io); | |
236 | } | |
237 | ||
238 | const VMStateDescription vmstate_tco_io_sts = { | |
239 | .name = "tco io device status", | |
240 | .version_id = 1, | |
241 | .minimum_version_id = 1, | |
242 | .minimum_version_id_old = 1, | |
243 | .fields = (VMStateField[]) { | |
244 | VMSTATE_UINT16(tco.rld, TCOIORegs), | |
245 | VMSTATE_UINT8(tco.din, TCOIORegs), | |
246 | VMSTATE_UINT8(tco.dout, TCOIORegs), | |
247 | VMSTATE_UINT16(tco.sts1, TCOIORegs), | |
248 | VMSTATE_UINT16(tco.sts2, TCOIORegs), | |
249 | VMSTATE_UINT16(tco.cnt1, TCOIORegs), | |
250 | VMSTATE_UINT16(tco.cnt2, TCOIORegs), | |
251 | VMSTATE_UINT8(tco.msg1, TCOIORegs), | |
252 | VMSTATE_UINT8(tco.msg2, TCOIORegs), | |
253 | VMSTATE_UINT8(tco.wdcnt, TCOIORegs), | |
254 | VMSTATE_UINT16(tco.tmr, TCOIORegs), | |
255 | VMSTATE_UINT8(sw_irq_gen, TCOIORegs), | |
256 | VMSTATE_TIMER_PTR(tco_timer, TCOIORegs), | |
257 | VMSTATE_INT64(expire_time, TCOIORegs), | |
258 | VMSTATE_UINT8(timeouts_no, TCOIORegs), | |
259 | VMSTATE_END_OF_LIST() | |
260 | } | |
261 | }; |