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