]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * ACPI implementation | |
3 | * | |
4 | * Copyright (c) 2006 Fabrice Bellard | |
5 | * | |
6 | * This library is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU Lesser General Public | |
8 | * License version 2 as published by the Free Software Foundation. | |
9 | * | |
10 | * This library is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | * Lesser General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU Lesser General Public | |
16 | * License along with this library; if not, write to the Free Software | |
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
18 | */ | |
19 | #include "vl.h" | |
20 | ||
21 | //#define DEBUG | |
22 | #define USE_SMM | |
23 | ||
24 | /* i82731AB (PIIX4) compatible power management function */ | |
25 | #define PM_FREQ 3579545 | |
26 | ||
27 | #define ACPI_DBG_IO_ADDR 0xb044 | |
28 | ||
29 | typedef struct PIIX4PMState { | |
30 | PCIDevice dev; | |
31 | uint16_t pmsts; | |
32 | uint16_t pmen; | |
33 | uint16_t pmcntrl; | |
34 | uint8_t apmc; | |
35 | uint8_t apms; | |
36 | QEMUTimer *tmr_timer; | |
37 | int64_t tmr_overflow_time; | |
38 | } PIIX4PMState; | |
39 | ||
40 | #define RTC_EN (1 << 10) | |
41 | #define PWRBTN_EN (1 << 8) | |
42 | #define GBL_EN (1 << 5) | |
43 | #define TMROF_EN (1 << 0) | |
44 | ||
45 | #define SCI_EN (1 << 0) | |
46 | ||
47 | #define SUS_EN (1 << 13) | |
48 | ||
49 | static uint32_t get_pmtmr(PIIX4PMState *s) | |
50 | { | |
51 | uint32_t d; | |
52 | d = muldiv64(qemu_get_clock(vm_clock), PM_FREQ, ticks_per_sec); | |
53 | return d & 0xffffff; | |
54 | } | |
55 | ||
56 | static int get_pmsts(PIIX4PMState *s) | |
57 | { | |
58 | int64_t d; | |
59 | int pmsts; | |
60 | pmsts = s->pmsts; | |
61 | d = muldiv64(qemu_get_clock(vm_clock), PM_FREQ, ticks_per_sec); | |
62 | if (d >= s->tmr_overflow_time) | |
63 | s->pmsts |= TMROF_EN; | |
64 | return pmsts; | |
65 | } | |
66 | ||
67 | static void pm_update_sci(PIIX4PMState *s) | |
68 | { | |
69 | int sci_level, pmsts; | |
70 | int64_t expire_time; | |
71 | ||
72 | pmsts = get_pmsts(s); | |
73 | sci_level = (((pmsts & s->pmen) & | |
74 | (RTC_EN | PWRBTN_EN | GBL_EN | TMROF_EN)) != 0); | |
75 | pci_set_irq(&s->dev, 0, sci_level); | |
76 | /* schedule a timer interruption if needed */ | |
77 | if ((s->pmen & TMROF_EN) && !(pmsts & TMROF_EN)) { | |
78 | expire_time = muldiv64(s->tmr_overflow_time, ticks_per_sec, PM_FREQ); | |
79 | qemu_mod_timer(s->tmr_timer, expire_time); | |
80 | } else { | |
81 | qemu_del_timer(s->tmr_timer); | |
82 | } | |
83 | } | |
84 | ||
85 | static void pm_tmr_timer(void *opaque) | |
86 | { | |
87 | PIIX4PMState *s = opaque; | |
88 | pm_update_sci(s); | |
89 | } | |
90 | ||
91 | static void pm_ioport_writew(void *opaque, uint32_t addr, uint32_t val) | |
92 | { | |
93 | PIIX4PMState *s = opaque; | |
94 | addr &= 0x3f; | |
95 | switch(addr) { | |
96 | case 0x00: | |
97 | { | |
98 | int64_t d; | |
99 | int pmsts; | |
100 | pmsts = get_pmsts(s); | |
101 | if (pmsts & val & TMROF_EN) { | |
102 | /* if TMRSTS is reset, then compute the new overflow time */ | |
103 | d = muldiv64(qemu_get_clock(vm_clock), PM_FREQ, ticks_per_sec); | |
104 | s->tmr_overflow_time = (d + 0x800000LL) & ~0x7fffffLL; | |
105 | } | |
106 | s->pmsts &= ~val; | |
107 | pm_update_sci(s); | |
108 | } | |
109 | break; | |
110 | case 0x02: | |
111 | s->pmen = val; | |
112 | pm_update_sci(s); | |
113 | break; | |
114 | case 0x04: | |
115 | { | |
116 | int sus_typ; | |
117 | s->pmcntrl = val & ~(SUS_EN); | |
118 | if (val & SUS_EN) { | |
119 | /* change suspend type */ | |
120 | sus_typ = (val >> 10) & 3; | |
121 | switch(sus_typ) { | |
122 | case 0: /* soft power off */ | |
123 | qemu_system_shutdown_request(); | |
124 | break; | |
125 | default: | |
126 | break; | |
127 | } | |
128 | } | |
129 | } | |
130 | break; | |
131 | default: | |
132 | break; | |
133 | } | |
134 | #ifdef DEBUG | |
135 | printf("PM writew port=0x%04x val=0x%04x\n", addr, val); | |
136 | #endif | |
137 | } | |
138 | ||
139 | static uint32_t pm_ioport_readw(void *opaque, uint32_t addr) | |
140 | { | |
141 | PIIX4PMState *s = opaque; | |
142 | uint32_t val; | |
143 | ||
144 | addr &= 0x3f; | |
145 | switch(addr) { | |
146 | case 0x00: | |
147 | val = get_pmsts(s); | |
148 | break; | |
149 | case 0x02: | |
150 | val = s->pmen; | |
151 | break; | |
152 | case 0x04: | |
153 | val = s->pmcntrl; | |
154 | break; | |
155 | default: | |
156 | val = 0; | |
157 | break; | |
158 | } | |
159 | #ifdef DEBUG | |
160 | printf("PM readw port=0x%04x val=0x%04x\n", addr, val); | |
161 | #endif | |
162 | return val; | |
163 | } | |
164 | ||
165 | static void pm_ioport_writel(void *opaque, uint32_t addr, uint32_t val) | |
166 | { | |
167 | // PIIX4PMState *s = opaque; | |
168 | addr &= 0x3f; | |
169 | #ifdef DEBUG | |
170 | printf("PM writel port=0x%04x val=0x%08x\n", addr, val); | |
171 | #endif | |
172 | } | |
173 | ||
174 | static uint32_t pm_ioport_readl(void *opaque, uint32_t addr) | |
175 | { | |
176 | PIIX4PMState *s = opaque; | |
177 | uint32_t val; | |
178 | ||
179 | addr &= 0x3f; | |
180 | switch(addr) { | |
181 | case 0x08: | |
182 | val = get_pmtmr(s); | |
183 | break; | |
184 | default: | |
185 | val = 0; | |
186 | break; | |
187 | } | |
188 | #ifdef DEBUG | |
189 | printf("PM readl port=0x%04x val=0x%08x\n", addr, val); | |
190 | #endif | |
191 | return val; | |
192 | } | |
193 | ||
194 | static void pm_smi_writeb(void *opaque, uint32_t addr, uint32_t val) | |
195 | { | |
196 | PIIX4PMState *s = opaque; | |
197 | addr &= 1; | |
198 | #ifdef DEBUG | |
199 | printf("pm_smi_writeb addr=0x%x val=0x%02x\n", addr, val); | |
200 | #endif | |
201 | if (addr == 0) { | |
202 | s->apmc = val; | |
203 | #ifdef USE_SMM | |
204 | cpu_interrupt(first_cpu, CPU_INTERRUPT_SMI); | |
205 | #else | |
206 | /* emulation of what the SMM BIOS should do */ | |
207 | switch(val) { | |
208 | case 0xf0: /* ACPI disable */ | |
209 | s->pmcntrl &= ~SCI_EN; | |
210 | break; | |
211 | case 0xf1: /* ACPI enable */ | |
212 | s->pmcntrl |= SCI_EN; | |
213 | break; | |
214 | } | |
215 | #endif | |
216 | } else { | |
217 | s->apms = val; | |
218 | } | |
219 | } | |
220 | ||
221 | static uint32_t pm_smi_readb(void *opaque, uint32_t addr) | |
222 | { | |
223 | PIIX4PMState *s = opaque; | |
224 | uint32_t val; | |
225 | ||
226 | addr &= 1; | |
227 | if (addr == 0) { | |
228 | val = s->apmc; | |
229 | } else { | |
230 | val = s->apms; | |
231 | } | |
232 | #ifdef DEBUG | |
233 | printf("pm_smi_readb addr=0x%x val=0x%02x\n", addr, val); | |
234 | #endif | |
235 | return val; | |
236 | } | |
237 | ||
238 | static void acpi_dbg_writel(void *opaque, uint32_t addr, uint32_t val) | |
239 | { | |
240 | #if defined(DEBUG) | |
241 | printf("ACPI: DBG: 0x%08x\n", val); | |
242 | #endif | |
243 | } | |
244 | ||
245 | static void pm_io_space_update(PIIX4PMState *s) | |
246 | { | |
247 | uint32_t pm_io_base; | |
248 | ||
249 | if (s->dev.config[0x80] & 1) { | |
250 | pm_io_base = le32_to_cpu(*(uint32_t *)(s->dev.config + 0x40)); | |
251 | pm_io_base &= 0xfffe; | |
252 | ||
253 | /* XXX: need to improve memory and ioport allocation */ | |
254 | #if defined(DEBUG) | |
255 | printf("PM: mapping to 0x%x\n", pm_io_base); | |
256 | #endif | |
257 | register_ioport_write(pm_io_base, 64, 2, pm_ioport_writew, s); | |
258 | register_ioport_read(pm_io_base, 64, 2, pm_ioport_readw, s); | |
259 | register_ioport_write(pm_io_base, 64, 4, pm_ioport_writel, s); | |
260 | register_ioport_read(pm_io_base, 64, 4, pm_ioport_readl, s); | |
261 | } | |
262 | } | |
263 | ||
264 | static void pm_write_config(PCIDevice *d, | |
265 | uint32_t address, uint32_t val, int len) | |
266 | { | |
267 | pci_default_write_config(d, address, val, len); | |
268 | if (address == 0x80) | |
269 | pm_io_space_update((PIIX4PMState *)d); | |
270 | } | |
271 | ||
272 | static void pm_save(QEMUFile* f,void *opaque) | |
273 | { | |
274 | PIIX4PMState *s = opaque; | |
275 | ||
276 | pci_device_save(&s->dev, f); | |
277 | ||
278 | qemu_put_be16s(f, &s->pmsts); | |
279 | qemu_put_be16s(f, &s->pmen); | |
280 | qemu_put_be16s(f, &s->pmcntrl); | |
281 | qemu_put_8s(f, &s->apmc); | |
282 | qemu_put_8s(f, &s->apms); | |
283 | qemu_put_timer(f, s->tmr_timer); | |
284 | qemu_put_be64s(f, &s->tmr_overflow_time); | |
285 | } | |
286 | ||
287 | static int pm_load(QEMUFile* f,void* opaque,int version_id) | |
288 | { | |
289 | PIIX4PMState *s = opaque; | |
290 | int ret; | |
291 | ||
292 | if (version_id > 1) | |
293 | return -EINVAL; | |
294 | ||
295 | ret = pci_device_load(&s->dev, f); | |
296 | if (ret < 0) | |
297 | return ret; | |
298 | ||
299 | qemu_get_be16s(f, &s->pmsts); | |
300 | qemu_get_be16s(f, &s->pmen); | |
301 | qemu_get_be16s(f, &s->pmcntrl); | |
302 | qemu_get_8s(f, &s->apmc); | |
303 | qemu_get_8s(f, &s->apms); | |
304 | qemu_get_timer(f, s->tmr_timer); | |
305 | qemu_get_be64s(f, &s->tmr_overflow_time); | |
306 | ||
307 | pm_io_space_update(s); | |
308 | ||
309 | return 0; | |
310 | } | |
311 | ||
312 | void piix4_pm_init(PCIBus *bus, int devfn) | |
313 | { | |
314 | PIIX4PMState *s; | |
315 | uint8_t *pci_conf; | |
316 | ||
317 | s = (PIIX4PMState *)pci_register_device(bus, | |
318 | "PM", sizeof(PIIX4PMState), | |
319 | devfn, NULL, pm_write_config); | |
320 | pci_conf = s->dev.config; | |
321 | pci_conf[0x00] = 0x86; | |
322 | pci_conf[0x01] = 0x80; | |
323 | pci_conf[0x02] = 0x13; | |
324 | pci_conf[0x03] = 0x71; | |
325 | pci_conf[0x08] = 0x00; // revision number | |
326 | pci_conf[0x09] = 0x00; | |
327 | pci_conf[0x0a] = 0x80; // other bridge device | |
328 | pci_conf[0x0b] = 0x06; // bridge device | |
329 | pci_conf[0x0e] = 0x00; // header_type | |
330 | pci_conf[0x3d] = 0x01; // interrupt pin 1 | |
331 | ||
332 | pci_conf[0x40] = 0x01; /* PM io base read only bit */ | |
333 | ||
334 | register_ioport_write(0xb2, 2, 1, pm_smi_writeb, s); | |
335 | register_ioport_read(0xb2, 2, 1, pm_smi_readb, s); | |
336 | ||
337 | register_ioport_write(ACPI_DBG_IO_ADDR, 4, 4, acpi_dbg_writel, s); | |
338 | ||
339 | /* XXX: which specification is used ? The i82731AB has different | |
340 | mappings */ | |
341 | pci_conf[0x5f] = (parallel_hds[0] != NULL ? 0x80 : 0) | 0x10; | |
342 | pci_conf[0x63] = 0x60; | |
343 | pci_conf[0x67] = (serial_hds[0] != NULL ? 0x08 : 0) | | |
344 | (serial_hds[1] != NULL ? 0x90 : 0); | |
345 | ||
346 | s->tmr_timer = qemu_new_timer(vm_clock, pm_tmr_timer, s); | |
347 | ||
348 | register_savevm("piix4_pm", 0, 1, pm_save, pm_load, s); | |
349 | } |