]>
Commit | Line | Data |
---|---|---|
45dcdb9d PA |
1 | /* |
2 | * QEMU ICH9 TCO emulation tests | |
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 | */ | |
681c28a3 | 9 | #include "qemu/osdep.h" |
45dcdb9d | 10 | #include <glib.h> |
45dcdb9d PA |
11 | |
12 | #include "libqtest.h" | |
13 | #include "libqos/pci.h" | |
14 | #include "libqos/pci-pc.h" | |
15 | #include "hw/pci/pci_regs.h" | |
16 | #include "hw/i386/ich9.h" | |
17 | #include "hw/acpi/ich9.h" | |
18 | #include "hw/acpi/tco.h" | |
19 | ||
20 | #define RCBA_BASE_ADDR 0xfed1c000 | |
21 | #define PM_IO_BASE_ADDR 0xb000 | |
22 | ||
23 | enum { | |
24 | TCO_RLD_DEFAULT = 0x0000, | |
25 | TCO_DAT_IN_DEFAULT = 0x00, | |
26 | TCO_DAT_OUT_DEFAULT = 0x00, | |
27 | TCO1_STS_DEFAULT = 0x0000, | |
28 | TCO2_STS_DEFAULT = 0x0000, | |
29 | TCO1_CNT_DEFAULT = 0x0000, | |
30 | TCO2_CNT_DEFAULT = 0x0008, | |
31 | TCO_MESSAGE1_DEFAULT = 0x00, | |
32 | TCO_MESSAGE2_DEFAULT = 0x00, | |
33 | TCO_WDCNT_DEFAULT = 0x00, | |
34 | TCO_TMR_DEFAULT = 0x0004, | |
35 | SW_IRQ_GEN_DEFAULT = 0x03, | |
36 | }; | |
37 | ||
38 | #define TCO_SECS_TO_TICKS(secs) (((secs) * 10) / 6) | |
39 | #define TCO_TICKS_TO_SECS(ticks) (((ticks) * 6) / 10) | |
40 | ||
41 | typedef struct { | |
42 | const char *args; | |
5add35be | 43 | bool noreboot; |
45dcdb9d | 44 | QPCIDevice *dev; |
45dcdb9d PA |
45 | void *tco_io_base; |
46 | } TestData; | |
47 | ||
48 | static void test_init(TestData *d) | |
49 | { | |
50 | QPCIBus *bus; | |
51 | QTestState *qs; | |
52 | char *s; | |
53 | ||
5add35be PA |
54 | s = g_strdup_printf("-machine q35 %s %s", |
55 | d->noreboot ? "" : "-global ICH9-LPC.noreboot=false", | |
56 | !d->args ? "" : d->args); | |
45dcdb9d PA |
57 | qs = qtest_start(s); |
58 | qtest_irq_intercept_in(qs, "ioapic"); | |
59 | g_free(s); | |
60 | ||
61 | bus = qpci_init_pc(); | |
62 | d->dev = qpci_device_find(bus, QPCI_DEVFN(0x1f, 0x00)); | |
63 | g_assert(d->dev != NULL); | |
64 | ||
45dcdb9d PA |
65 | qpci_device_enable(d->dev); |
66 | ||
45dcdb9d | 67 | /* set ACPI PM I/O space base address */ |
c4fc82bf | 68 | qpci_config_writel(d->dev, ICH9_LPC_PMBASE, PM_IO_BASE_ADDR | 0x1); |
45dcdb9d | 69 | /* enable ACPI I/O */ |
c4fc82bf | 70 | qpci_config_writeb(d->dev, ICH9_LPC_ACPI_CTRL, 0x80); |
45dcdb9d | 71 | /* set Root Complex BAR */ |
c4fc82bf | 72 | qpci_config_writel(d->dev, ICH9_LPC_RCBA, RCBA_BASE_ADDR | 0x1); |
45dcdb9d PA |
73 | |
74 | d->tco_io_base = (void *)((uintptr_t)PM_IO_BASE_ADDR + 0x60); | |
75 | } | |
76 | ||
77 | static void stop_tco(const TestData *d) | |
78 | { | |
79 | uint32_t val; | |
80 | ||
81 | val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); | |
82 | val |= TCO_TMR_HLT; | |
83 | qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); | |
84 | } | |
85 | ||
86 | static void start_tco(const TestData *d) | |
87 | { | |
88 | uint32_t val; | |
89 | ||
90 | val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); | |
91 | val &= ~TCO_TMR_HLT; | |
92 | qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); | |
93 | } | |
94 | ||
95 | static void load_tco(const TestData *d) | |
96 | { | |
97 | qpci_io_writew(d->dev, d->tco_io_base + TCO_RLD, 4); | |
98 | } | |
99 | ||
100 | static void set_tco_timeout(const TestData *d, uint16_t ticks) | |
101 | { | |
102 | qpci_io_writew(d->dev, d->tco_io_base + TCO_TMR, ticks); | |
103 | } | |
104 | ||
105 | static void clear_tco_status(const TestData *d) | |
106 | { | |
107 | qpci_io_writew(d->dev, d->tco_io_base + TCO1_STS, 0x0008); | |
108 | qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0002); | |
109 | qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0004); | |
110 | } | |
111 | ||
112 | static void reset_on_second_timeout(bool enable) | |
113 | { | |
114 | uint32_t val; | |
115 | ||
116 | val = readl(RCBA_BASE_ADDR + ICH9_CC_GCS); | |
117 | if (enable) { | |
118 | val &= ~ICH9_CC_GCS_NO_REBOOT; | |
119 | } else { | |
120 | val |= ICH9_CC_GCS_NO_REBOOT; | |
121 | } | |
122 | writel(RCBA_BASE_ADDR + ICH9_CC_GCS, val); | |
123 | } | |
124 | ||
125 | static void test_tco_defaults(void) | |
126 | { | |
127 | TestData d; | |
128 | ||
129 | d.args = NULL; | |
5add35be | 130 | d.noreboot = true; |
45dcdb9d PA |
131 | test_init(&d); |
132 | g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, | |
133 | TCO_RLD_DEFAULT); | |
134 | /* TCO_DAT_IN & TCO_DAT_OUT */ | |
135 | g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_DAT_IN), ==, | |
136 | (TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT); | |
137 | /* TCO1_STS & TCO2_STS */ | |
138 | g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_STS), ==, | |
139 | (TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT); | |
140 | /* TCO1_CNT & TCO2_CNT */ | |
141 | g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_CNT), ==, | |
142 | (TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT); | |
143 | /* TCO_MESSAGE1 & TCO_MESSAGE2 */ | |
144 | g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_MESSAGE1), ==, | |
145 | (TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT); | |
146 | g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + TCO_WDCNT), ==, | |
147 | TCO_WDCNT_DEFAULT); | |
148 | g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + SW_IRQ_GEN), ==, | |
149 | SW_IRQ_GEN_DEFAULT); | |
150 | g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_TMR), ==, | |
151 | TCO_TMR_DEFAULT); | |
152 | qtest_end(); | |
153 | } | |
154 | ||
155 | static void test_tco_timeout(void) | |
156 | { | |
157 | TestData d; | |
158 | const uint16_t ticks = TCO_SECS_TO_TICKS(4); | |
159 | uint32_t val; | |
160 | int ret; | |
161 | ||
162 | d.args = NULL; | |
5add35be | 163 | d.noreboot = true; |
45dcdb9d PA |
164 | test_init(&d); |
165 | ||
166 | stop_tco(&d); | |
167 | clear_tco_status(&d); | |
168 | reset_on_second_timeout(false); | |
169 | set_tco_timeout(&d, ticks); | |
170 | load_tco(&d); | |
171 | start_tco(&d); | |
172 | clock_step(ticks * TCO_TICK_NSEC); | |
173 | ||
174 | /* test first timeout */ | |
175 | val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); | |
176 | ret = val & TCO_TIMEOUT ? 1 : 0; | |
177 | g_assert(ret == 1); | |
178 | ||
179 | /* test clearing timeout bit */ | |
180 | val |= TCO_TIMEOUT; | |
181 | qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); | |
182 | val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); | |
183 | ret = val & TCO_TIMEOUT ? 1 : 0; | |
184 | g_assert(ret == 0); | |
185 | ||
186 | /* test second timeout */ | |
187 | clock_step(ticks * TCO_TICK_NSEC); | |
188 | val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); | |
189 | ret = val & TCO_TIMEOUT ? 1 : 0; | |
190 | g_assert(ret == 1); | |
191 | val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); | |
192 | ret = val & TCO_SECOND_TO_STS ? 1 : 0; | |
193 | g_assert(ret == 1); | |
194 | ||
195 | stop_tco(&d); | |
196 | qtest_end(); | |
197 | } | |
198 | ||
199 | static void test_tco_max_timeout(void) | |
200 | { | |
201 | TestData d; | |
202 | const uint16_t ticks = 0xffff; | |
203 | uint32_t val; | |
204 | int ret; | |
205 | ||
206 | d.args = NULL; | |
5add35be | 207 | d.noreboot = true; |
45dcdb9d PA |
208 | test_init(&d); |
209 | ||
210 | stop_tco(&d); | |
211 | clear_tco_status(&d); | |
212 | reset_on_second_timeout(false); | |
213 | set_tco_timeout(&d, ticks); | |
214 | load_tco(&d); | |
215 | start_tco(&d); | |
216 | clock_step(((ticks & TCO_TMR_MASK) - 1) * TCO_TICK_NSEC); | |
217 | ||
218 | val = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD); | |
219 | g_assert_cmpint(val & TCO_RLD_MASK, ==, 1); | |
220 | val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); | |
221 | ret = val & TCO_TIMEOUT ? 1 : 0; | |
222 | g_assert(ret == 0); | |
223 | clock_step(TCO_TICK_NSEC); | |
224 | val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); | |
225 | ret = val & TCO_TIMEOUT ? 1 : 0; | |
226 | g_assert(ret == 1); | |
227 | ||
228 | stop_tco(&d); | |
229 | qtest_end(); | |
230 | } | |
231 | ||
232 | static QDict *get_watchdog_action(void) | |
233 | { | |
234 | QDict *ev = qmp(""); | |
235 | QDict *data; | |
236 | g_assert(!strcmp(qdict_get_str(ev, "event"), "WATCHDOG")); | |
237 | ||
238 | data = qdict_get_qdict(ev, "data"); | |
239 | QINCREF(data); | |
240 | QDECREF(ev); | |
241 | return data; | |
242 | } | |
243 | ||
244 | static void test_tco_second_timeout_pause(void) | |
245 | { | |
246 | TestData td; | |
247 | const uint16_t ticks = TCO_SECS_TO_TICKS(32); | |
248 | QDict *ad; | |
249 | ||
250 | td.args = "-watchdog-action pause"; | |
5add35be | 251 | td.noreboot = false; |
45dcdb9d PA |
252 | test_init(&td); |
253 | ||
254 | stop_tco(&td); | |
255 | clear_tco_status(&td); | |
256 | reset_on_second_timeout(true); | |
257 | set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); | |
258 | load_tco(&td); | |
259 | start_tco(&td); | |
260 | clock_step(ticks * TCO_TICK_NSEC * 2); | |
261 | ad = get_watchdog_action(); | |
262 | g_assert(!strcmp(qdict_get_str(ad, "action"), "pause")); | |
263 | QDECREF(ad); | |
264 | ||
265 | stop_tco(&td); | |
266 | qtest_end(); | |
267 | } | |
268 | ||
269 | static void test_tco_second_timeout_reset(void) | |
270 | { | |
271 | TestData td; | |
272 | const uint16_t ticks = TCO_SECS_TO_TICKS(16); | |
273 | QDict *ad; | |
274 | ||
275 | td.args = "-watchdog-action reset"; | |
5add35be | 276 | td.noreboot = false; |
45dcdb9d PA |
277 | test_init(&td); |
278 | ||
279 | stop_tco(&td); | |
280 | clear_tco_status(&td); | |
281 | reset_on_second_timeout(true); | |
282 | set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); | |
283 | load_tco(&td); | |
284 | start_tco(&td); | |
285 | clock_step(ticks * TCO_TICK_NSEC * 2); | |
286 | ad = get_watchdog_action(); | |
287 | g_assert(!strcmp(qdict_get_str(ad, "action"), "reset")); | |
288 | QDECREF(ad); | |
289 | ||
290 | stop_tco(&td); | |
291 | qtest_end(); | |
292 | } | |
293 | ||
294 | static void test_tco_second_timeout_shutdown(void) | |
295 | { | |
296 | TestData td; | |
297 | const uint16_t ticks = TCO_SECS_TO_TICKS(128); | |
298 | QDict *ad; | |
299 | ||
300 | td.args = "-watchdog-action shutdown"; | |
5add35be | 301 | td.noreboot = false; |
45dcdb9d PA |
302 | test_init(&td); |
303 | ||
304 | stop_tco(&td); | |
305 | clear_tco_status(&td); | |
306 | reset_on_second_timeout(true); | |
307 | set_tco_timeout(&td, ticks); | |
308 | load_tco(&td); | |
309 | start_tco(&td); | |
310 | clock_step(ticks * TCO_TICK_NSEC * 2); | |
311 | ad = get_watchdog_action(); | |
312 | g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown")); | |
313 | QDECREF(ad); | |
314 | ||
315 | stop_tco(&td); | |
316 | qtest_end(); | |
317 | } | |
318 | ||
319 | static void test_tco_second_timeout_none(void) | |
320 | { | |
321 | TestData td; | |
322 | const uint16_t ticks = TCO_SECS_TO_TICKS(256); | |
323 | QDict *ad; | |
324 | ||
325 | td.args = "-watchdog-action none"; | |
5add35be | 326 | td.noreboot = false; |
45dcdb9d PA |
327 | test_init(&td); |
328 | ||
329 | stop_tco(&td); | |
330 | clear_tco_status(&td); | |
331 | reset_on_second_timeout(true); | |
332 | set_tco_timeout(&td, ticks); | |
333 | load_tco(&td); | |
334 | start_tco(&td); | |
335 | clock_step(ticks * TCO_TICK_NSEC * 2); | |
336 | ad = get_watchdog_action(); | |
337 | g_assert(!strcmp(qdict_get_str(ad, "action"), "none")); | |
338 | QDECREF(ad); | |
339 | ||
340 | stop_tco(&td); | |
341 | qtest_end(); | |
342 | } | |
343 | ||
344 | static void test_tco_ticks_counter(void) | |
345 | { | |
346 | TestData d; | |
347 | uint16_t ticks = TCO_SECS_TO_TICKS(8); | |
348 | uint16_t rld; | |
349 | ||
350 | d.args = NULL; | |
5add35be | 351 | d.noreboot = true; |
45dcdb9d PA |
352 | test_init(&d); |
353 | ||
354 | stop_tco(&d); | |
355 | clear_tco_status(&d); | |
356 | reset_on_second_timeout(false); | |
357 | set_tco_timeout(&d, ticks); | |
358 | load_tco(&d); | |
359 | start_tco(&d); | |
360 | ||
361 | do { | |
362 | rld = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD) & TCO_RLD_MASK; | |
363 | g_assert_cmpint(rld, ==, ticks); | |
364 | clock_step(TCO_TICK_NSEC); | |
365 | ticks--; | |
366 | } while (!(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS) & TCO_TIMEOUT)); | |
367 | ||
368 | stop_tco(&d); | |
369 | qtest_end(); | |
370 | } | |
371 | ||
372 | static void test_tco1_control_bits(void) | |
373 | { | |
374 | TestData d; | |
375 | uint16_t val; | |
376 | ||
377 | d.args = NULL; | |
5add35be | 378 | d.noreboot = true; |
45dcdb9d PA |
379 | test_init(&d); |
380 | ||
381 | val = TCO_LOCK; | |
382 | qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); | |
383 | val &= ~TCO_LOCK; | |
384 | qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); | |
385 | g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_CNT), ==, | |
386 | TCO_LOCK); | |
387 | qtest_end(); | |
388 | } | |
389 | ||
390 | static void test_tco1_status_bits(void) | |
391 | { | |
392 | TestData d; | |
393 | uint16_t ticks = 8; | |
394 | uint16_t val; | |
395 | int ret; | |
396 | ||
397 | d.args = NULL; | |
5add35be | 398 | d.noreboot = true; |
45dcdb9d PA |
399 | test_init(&d); |
400 | ||
401 | stop_tco(&d); | |
402 | clear_tco_status(&d); | |
403 | reset_on_second_timeout(false); | |
404 | set_tco_timeout(&d, ticks); | |
405 | load_tco(&d); | |
406 | start_tco(&d); | |
407 | clock_step(ticks * TCO_TICK_NSEC); | |
408 | ||
409 | qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_IN, 0); | |
410 | qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_OUT, 0); | |
411 | val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); | |
412 | ret = val & (TCO_TIMEOUT | SW_TCO_SMI | TCO_INT_STS) ? 1 : 0; | |
413 | g_assert(ret == 1); | |
414 | qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); | |
415 | g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS), ==, 0); | |
416 | qtest_end(); | |
417 | } | |
418 | ||
419 | static void test_tco2_status_bits(void) | |
420 | { | |
421 | TestData d; | |
422 | uint16_t ticks = 8; | |
423 | uint16_t val; | |
424 | int ret; | |
425 | ||
5add35be PA |
426 | d.args = NULL; |
427 | d.noreboot = true; | |
45dcdb9d PA |
428 | test_init(&d); |
429 | ||
430 | stop_tco(&d); | |
431 | clear_tco_status(&d); | |
432 | reset_on_second_timeout(true); | |
433 | set_tco_timeout(&d, ticks); | |
434 | load_tco(&d); | |
435 | start_tco(&d); | |
436 | clock_step(ticks * TCO_TICK_NSEC * 2); | |
437 | ||
438 | val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); | |
439 | ret = val & (TCO_SECOND_TO_STS | TCO_BOOT_STS) ? 1 : 0; | |
440 | g_assert(ret == 1); | |
441 | qpci_io_writew(d.dev, d.tco_io_base + TCO2_STS, val); | |
442 | g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS), ==, 0); | |
443 | qtest_end(); | |
444 | } | |
445 | ||
446 | int main(int argc, char **argv) | |
447 | { | |
448 | g_test_init(&argc, &argv, NULL); | |
449 | ||
450 | qtest_add_func("tco/defaults", test_tco_defaults); | |
451 | qtest_add_func("tco/timeout/no_action", test_tco_timeout); | |
452 | qtest_add_func("tco/timeout/no_action/max", test_tco_max_timeout); | |
453 | qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause); | |
454 | qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset); | |
455 | qtest_add_func("tco/second_timeout/shutdown", | |
456 | test_tco_second_timeout_shutdown); | |
457 | qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none); | |
458 | qtest_add_func("tco/counter", test_tco_ticks_counter); | |
459 | qtest_add_func("tco/tco1_control/bits", test_tco1_control_bits); | |
460 | qtest_add_func("tco/tco1_status/bits", test_tco1_status_bits); | |
461 | qtest_add_func("tco/tco2_status/bits", test_tco2_status_bits); | |
462 | return g_test_run(); | |
463 | } |