pm.c revision 261090
1/*-
2 * Copyright (c) 2013 Advanced Computing Technologies LLC
3 * Written by: John H. Baldwin <jhb@FreeBSD.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
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.
14 *
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
25 * SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD: stable/10/usr.sbin/bhyve/pm.c 261090 2014-01-23 20:35:32Z jhb $");
30
31#include <sys/types.h>
32#include <machine/vmm.h>
33
34#include <assert.h>
35#include <pthread.h>
36#include <signal.h>
37#include <vmmapi.h>
38
39#include "acpi.h"
40#include "inout.h"
41#include "mevent.h"
42
43static pthread_mutex_t pm_lock = PTHREAD_MUTEX_INITIALIZER;
44static struct mevent *power_button;
45static sig_t old_power_handler;
46
47/*
48 * Reset Control register at I/O port 0xcf9.  Bit 2 forces a system
49 * reset when it transitions from 0 to 1.  Bit 1 selects the type of
50 * reset to attempt: 0 selects a "soft" reset, and 1 selects a "hard"
51 * reset.
52 */
53static int
54reset_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
55    uint32_t *eax, void *arg)
56{
57	static uint8_t reset_control;
58
59	if (bytes != 1)
60		return (-1);
61	if (in)
62		*eax = reset_control;
63	else {
64		reset_control = *eax;
65
66		/* Treat hard and soft resets the same. */
67		if (reset_control & 0x4)
68			return (INOUT_RESET);
69	}
70	return (0);
71}
72INOUT_PORT(reset_reg, 0xCF9, IOPORT_F_INOUT, reset_handler);
73
74/*
75 * ACPI's SCI is a level-triggered interrupt.
76 */
77static int sci_active;
78
79static void
80sci_assert(struct vmctx *ctx)
81{
82
83	if (sci_active)
84		return;
85	vm_ioapic_assert_irq(ctx, SCI_INT);
86	sci_active = 1;
87}
88
89static void
90sci_deassert(struct vmctx *ctx)
91{
92
93	if (!sci_active)
94		return;
95	vm_ioapic_deassert_irq(ctx, SCI_INT);
96	sci_active = 0;
97}
98
99/*
100 * Power Management 1 Event Registers
101 *
102 * The only power management event supported is a power button upon
103 * receiving SIGTERM.
104 */
105static uint16_t pm1_enable, pm1_status;
106
107#define	PM1_TMR_STS		0x0001
108#define	PM1_BM_STS		0x0010
109#define	PM1_GBL_STS		0x0020
110#define	PM1_PWRBTN_STS		0x0100
111#define	PM1_SLPBTN_STS		0x0200
112#define	PM1_RTC_STS		0x0400
113#define	PM1_WAK_STS		0x8000
114
115#define	PM1_TMR_EN		0x0001
116#define	PM1_GBL_EN		0x0020
117#define	PM1_PWRBTN_EN		0x0100
118#define	PM1_SLPBTN_EN		0x0200
119#define	PM1_RTC_EN		0x0400
120
121static void
122sci_update(struct vmctx *ctx)
123{
124	int need_sci;
125
126	/* See if the SCI should be active or not. */
127	need_sci = 0;
128	if ((pm1_enable & PM1_TMR_EN) && (pm1_status & PM1_TMR_STS))
129		need_sci = 1;
130	if ((pm1_enable & PM1_GBL_EN) && (pm1_status & PM1_GBL_STS))
131		need_sci = 1;
132	if ((pm1_enable & PM1_PWRBTN_EN) && (pm1_status & PM1_PWRBTN_STS))
133		need_sci = 1;
134	if ((pm1_enable & PM1_SLPBTN_EN) && (pm1_status & PM1_SLPBTN_STS))
135		need_sci = 1;
136	if ((pm1_enable & PM1_RTC_EN) && (pm1_status & PM1_RTC_STS))
137		need_sci = 1;
138	if (need_sci)
139		sci_assert(ctx);
140	else
141		sci_deassert(ctx);
142}
143
144static int
145pm1_status_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
146    uint32_t *eax, void *arg)
147{
148
149	if (bytes != 2)
150		return (-1);
151
152	pthread_mutex_lock(&pm_lock);
153	if (in)
154		*eax = pm1_status;
155	else {
156		/*
157		 * Writes are only permitted to clear certain bits by
158		 * writing 1 to those flags.
159		 */
160		pm1_status &= ~(*eax & (PM1_WAK_STS | PM1_RTC_STS |
161		    PM1_SLPBTN_STS | PM1_PWRBTN_STS | PM1_BM_STS));
162		sci_update(ctx);
163	}
164	pthread_mutex_unlock(&pm_lock);
165	return (0);
166}
167
168static int
169pm1_enable_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
170    uint32_t *eax, void *arg)
171{
172
173	if (bytes != 2)
174		return (-1);
175
176	pthread_mutex_lock(&pm_lock);
177	if (in)
178		*eax = pm1_enable;
179	else {
180		/*
181		 * Only permit certain bits to be set.  We never use
182		 * the global lock, but ACPI-CA whines profusely if it
183		 * can't set GBL_EN.
184		 */
185		pm1_enable = *eax & (PM1_PWRBTN_EN | PM1_GBL_EN);
186		sci_update(ctx);
187	}
188	pthread_mutex_unlock(&pm_lock);
189	return (0);
190}
191INOUT_PORT(pm1_status, PM1A_EVT_ADDR, IOPORT_F_INOUT, pm1_status_handler);
192INOUT_PORT(pm1_enable, PM1A_EVT_ADDR + 2, IOPORT_F_INOUT, pm1_enable_handler);
193
194static void
195power_button_handler(int signal, enum ev_type type, void *arg)
196{
197	struct vmctx *ctx;
198
199	ctx = arg;
200	pthread_mutex_lock(&pm_lock);
201	if (!(pm1_status & PM1_PWRBTN_STS)) {
202		pm1_status |= PM1_PWRBTN_STS;
203		sci_update(ctx);
204	}
205	pthread_mutex_unlock(&pm_lock);
206}
207
208/*
209 * Power Management 1 Control Register
210 *
211 * This is mostly unimplemented except that we wish to handle writes that
212 * set SPL_EN to handle S5 (soft power off).
213 */
214static uint16_t pm1_control;
215
216#define	PM1_SCI_EN	0x0001
217#define	PM1_SLP_TYP	0x1c00
218#define	PM1_SLP_EN	0x2000
219#define	PM1_ALWAYS_ZERO	0xc003
220
221static int
222pm1_control_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
223    uint32_t *eax, void *arg)
224{
225
226	if (bytes != 2)
227		return (-1);
228	if (in)
229		*eax = pm1_control;
230	else {
231		/*
232		 * Various bits are write-only or reserved, so force them
233		 * to zero in pm1_control.  Always preserve SCI_EN as OSPM
234		 * can never change it.
235		 */
236		pm1_control = (pm1_control & PM1_SCI_EN) |
237		    (*eax & ~(PM1_SLP_EN | PM1_ALWAYS_ZERO));
238
239		/*
240		 * If SLP_EN is set, check for S5.  Bhyve's _S5_ method
241		 * says that '5' should be stored in SLP_TYP for S5.
242		 */
243		if (*eax & PM1_SLP_EN) {
244			if ((pm1_control & PM1_SLP_TYP) >> 10 == 5)
245				return (INOUT_POWEROFF);
246		}
247	}
248	return (0);
249}
250INOUT_PORT(pm1_control, PM1A_CNT_ADDR, IOPORT_F_INOUT, pm1_control_handler);
251
252/*
253 * ACPI SMI Command Register
254 *
255 * This write-only register is used to enable and disable ACPI.
256 */
257static int
258smi_cmd_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
259    uint32_t *eax, void *arg)
260{
261
262	assert(!in);
263	if (bytes != 1)
264		return (-1);
265
266	pthread_mutex_lock(&pm_lock);
267	switch (*eax) {
268	case BHYVE_ACPI_ENABLE:
269		pm1_control |= PM1_SCI_EN;
270		if (power_button == NULL) {
271			power_button = mevent_add(SIGTERM, EVF_SIGNAL,
272			    power_button_handler, ctx);
273			old_power_handler = signal(SIGTERM, SIG_IGN);
274		}
275		break;
276	case BHYVE_ACPI_DISABLE:
277		pm1_control &= ~PM1_SCI_EN;
278		if (power_button != NULL) {
279			mevent_delete(power_button);
280			power_button = NULL;
281			signal(SIGTERM, old_power_handler);
282		}
283		break;
284	}
285	pthread_mutex_unlock(&pm_lock);
286	return (0);
287}
288INOUT_PORT(smi_cmd, SMI_CMD, IOPORT_F_OUT, smi_cmd_handler);
289