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