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