2 * Copyright (c) 2013 Advanced Computing Technologies LLC
3 * Written by: John H. Baldwin <jhb@FreeBSD.org>
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
31 #include <sys/types.h>
32 #include <machine/vmm.h>
44 static pthread_mutex_t pm_lock = PTHREAD_MUTEX_INITIALIZER;
45 static struct mevent *power_button;
46 static sig_t old_power_handler;
49 * Reset Control register at I/O port 0xcf9. Bit 2 forces a system
50 * reset when it transitions from 0 to 1. Bit 1 selects the type of
51 * reset to attempt: 0 selects a "soft" reset, and 1 selects a "hard"
55 reset_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
56 uint32_t *eax, void *arg)
58 static uint8_t reset_control;
67 /* Treat hard and soft resets the same. */
68 if (reset_control & 0x4)
73 INOUT_PORT(reset_reg, 0xCF9, IOPORT_F_INOUT, reset_handler);
76 * ACPI's SCI is a level-triggered interrupt.
78 static int sci_active;
81 sci_assert(struct vmctx *ctx)
86 vm_isa_assert_irq(ctx, SCI_INT, SCI_INT);
91 sci_deassert(struct vmctx *ctx)
96 vm_isa_deassert_irq(ctx, SCI_INT, SCI_INT);
101 * Power Management 1 Event Registers
103 * The only power management event supported is a power button upon
106 static uint16_t pm1_enable, pm1_status;
108 #define PM1_TMR_STS 0x0001
109 #define PM1_BM_STS 0x0010
110 #define PM1_GBL_STS 0x0020
111 #define PM1_PWRBTN_STS 0x0100
112 #define PM1_SLPBTN_STS 0x0200
113 #define PM1_RTC_STS 0x0400
114 #define PM1_WAK_STS 0x8000
116 #define PM1_TMR_EN 0x0001
117 #define PM1_GBL_EN 0x0020
118 #define PM1_PWRBTN_EN 0x0100
119 #define PM1_SLPBTN_EN 0x0200
120 #define PM1_RTC_EN 0x0400
123 sci_update(struct vmctx *ctx)
127 /* See if the SCI should be active or not. */
129 if ((pm1_enable & PM1_TMR_EN) && (pm1_status & PM1_TMR_STS))
131 if ((pm1_enable & PM1_GBL_EN) && (pm1_status & PM1_GBL_STS))
133 if ((pm1_enable & PM1_PWRBTN_EN) && (pm1_status & PM1_PWRBTN_STS))
135 if ((pm1_enable & PM1_SLPBTN_EN) && (pm1_status & PM1_SLPBTN_STS))
137 if ((pm1_enable & PM1_RTC_EN) && (pm1_status & PM1_RTC_STS))
146 pm1_status_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
147 uint32_t *eax, void *arg)
153 pthread_mutex_lock(&pm_lock);
158 * Writes are only permitted to clear certain bits by
159 * writing 1 to those flags.
161 pm1_status &= ~(*eax & (PM1_WAK_STS | PM1_RTC_STS |
162 PM1_SLPBTN_STS | PM1_PWRBTN_STS | PM1_BM_STS));
165 pthread_mutex_unlock(&pm_lock);
170 pm1_enable_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
171 uint32_t *eax, void *arg)
177 pthread_mutex_lock(&pm_lock);
182 * Only permit certain bits to be set. We never use
183 * the global lock, but ACPI-CA whines profusely if it
186 pm1_enable = *eax & (PM1_PWRBTN_EN | PM1_GBL_EN);
189 pthread_mutex_unlock(&pm_lock);
192 INOUT_PORT(pm1_status, PM1A_EVT_ADDR, IOPORT_F_INOUT, pm1_status_handler);
193 INOUT_PORT(pm1_enable, PM1A_EVT_ADDR + 2, IOPORT_F_INOUT, pm1_enable_handler);
196 power_button_handler(int signal, enum ev_type type, void *arg)
201 pthread_mutex_lock(&pm_lock);
202 if (!(pm1_status & PM1_PWRBTN_STS)) {
203 pm1_status |= PM1_PWRBTN_STS;
206 pthread_mutex_unlock(&pm_lock);
210 * Power Management 1 Control Register
212 * This is mostly unimplemented except that we wish to handle writes that
213 * set SPL_EN to handle S5 (soft power off).
215 static uint16_t pm1_control;
217 #define PM1_SCI_EN 0x0001
218 #define PM1_SLP_TYP 0x1c00
219 #define PM1_SLP_EN 0x2000
220 #define PM1_ALWAYS_ZERO 0xc003
223 pm1_control_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
224 uint32_t *eax, void *arg)
233 * Various bits are write-only or reserved, so force them
234 * to zero in pm1_control. Always preserve SCI_EN as OSPM
235 * can never change it.
237 pm1_control = (pm1_control & PM1_SCI_EN) |
238 (*eax & ~(PM1_SLP_EN | PM1_ALWAYS_ZERO));
241 * If SLP_EN is set, check for S5. Bhyve's _S5_ method
242 * says that '5' should be stored in SLP_TYP for S5.
244 if (*eax & PM1_SLP_EN) {
245 if ((pm1_control & PM1_SLP_TYP) >> 10 == 5)
246 return (INOUT_POWEROFF);
251 INOUT_PORT(pm1_control, PM1A_CNT_ADDR, IOPORT_F_INOUT, pm1_control_handler);
252 SYSRES_IO(PM1A_EVT_ADDR, 8);
255 * ACPI SMI Command Register
257 * This write-only register is used to enable and disable ACPI.
260 smi_cmd_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
261 uint32_t *eax, void *arg)
268 pthread_mutex_lock(&pm_lock);
270 case BHYVE_ACPI_ENABLE:
271 pm1_control |= PM1_SCI_EN;
272 if (power_button == NULL) {
273 power_button = mevent_add(SIGTERM, EVF_SIGNAL,
274 power_button_handler, ctx);
275 old_power_handler = signal(SIGTERM, SIG_IGN);
278 case BHYVE_ACPI_DISABLE:
279 pm1_control &= ~PM1_SCI_EN;
280 if (power_button != NULL) {
281 mevent_delete(power_button);
283 signal(SIGTERM, old_power_handler);
287 pthread_mutex_unlock(&pm_lock);
290 INOUT_PORT(smi_cmd, SMI_CMD, IOPORT_F_OUT, smi_cmd_handler);
291 SYSRES_IO(SMI_CMD, 1);