pm.c revision 261265
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 261265 2014-01-29 13:35:12Z 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#include "pci_lpc.h"
43
44static pthread_mutex_t pm_lock = PTHREAD_MUTEX_INITIALIZER;
45static struct mevent *power_button;
46static sig_t old_power_handler;
47
48/*
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"
52 * reset.
53 */
54static int
55reset_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
56    uint32_t *eax, void *arg)
57{
58	static uint8_t reset_control;
59
60	if (bytes != 1)
61		return (-1);
62	if (in)
63		*eax = reset_control;
64	else {
65		reset_control = *eax;
66
67		/* Treat hard and soft resets the same. */
68		if (reset_control & 0x4)
69			return (INOUT_RESET);
70	}
71	return (0);
72}
73INOUT_PORT(reset_reg, 0xCF9, IOPORT_F_INOUT, reset_handler);
74
75/*
76 * ACPI's SCI is a level-triggered interrupt.
77 */
78static int sci_active;
79
80static void
81sci_assert(struct vmctx *ctx)
82{
83
84	if (sci_active)
85		return;
86	vm_ioapic_assert_irq(ctx, SCI_INT);
87	sci_active = 1;
88}
89
90static void
91sci_deassert(struct vmctx *ctx)
92{
93
94	if (!sci_active)
95		return;
96	vm_ioapic_deassert_irq(ctx, SCI_INT);
97	sci_active = 0;
98}
99
100/*
101 * Power Management 1 Event Registers
102 *
103 * The only power management event supported is a power button upon
104 * receiving SIGTERM.
105 */
106static uint16_t pm1_enable, pm1_status;
107
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
115
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
121
122static void
123sci_update(struct vmctx *ctx)
124{
125	int need_sci;
126
127	/* See if the SCI should be active or not. */
128	need_sci = 0;
129	if ((pm1_enable & PM1_TMR_EN) && (pm1_status & PM1_TMR_STS))
130		need_sci = 1;
131	if ((pm1_enable & PM1_GBL_EN) && (pm1_status & PM1_GBL_STS))
132		need_sci = 1;
133	if ((pm1_enable & PM1_PWRBTN_EN) && (pm1_status & PM1_PWRBTN_STS))
134		need_sci = 1;
135	if ((pm1_enable & PM1_SLPBTN_EN) && (pm1_status & PM1_SLPBTN_STS))
136		need_sci = 1;
137	if ((pm1_enable & PM1_RTC_EN) && (pm1_status & PM1_RTC_STS))
138		need_sci = 1;
139	if (need_sci)
140		sci_assert(ctx);
141	else
142		sci_deassert(ctx);
143}
144
145static int
146pm1_status_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
147    uint32_t *eax, void *arg)
148{
149
150	if (bytes != 2)
151		return (-1);
152
153	pthread_mutex_lock(&pm_lock);
154	if (in)
155		*eax = pm1_status;
156	else {
157		/*
158		 * Writes are only permitted to clear certain bits by
159		 * writing 1 to those flags.
160		 */
161		pm1_status &= ~(*eax & (PM1_WAK_STS | PM1_RTC_STS |
162		    PM1_SLPBTN_STS | PM1_PWRBTN_STS | PM1_BM_STS));
163		sci_update(ctx);
164	}
165	pthread_mutex_unlock(&pm_lock);
166	return (0);
167}
168
169static int
170pm1_enable_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
171    uint32_t *eax, void *arg)
172{
173
174	if (bytes != 2)
175		return (-1);
176
177	pthread_mutex_lock(&pm_lock);
178	if (in)
179		*eax = pm1_enable;
180	else {
181		/*
182		 * Only permit certain bits to be set.  We never use
183		 * the global lock, but ACPI-CA whines profusely if it
184		 * can't set GBL_EN.
185		 */
186		pm1_enable = *eax & (PM1_PWRBTN_EN | PM1_GBL_EN);
187		sci_update(ctx);
188	}
189	pthread_mutex_unlock(&pm_lock);
190	return (0);
191}
192INOUT_PORT(pm1_status, PM1A_EVT_ADDR, IOPORT_F_INOUT, pm1_status_handler);
193INOUT_PORT(pm1_enable, PM1A_EVT_ADDR + 2, IOPORT_F_INOUT, pm1_enable_handler);
194
195static void
196power_button_handler(int signal, enum ev_type type, void *arg)
197{
198	struct vmctx *ctx;
199
200	ctx = arg;
201	pthread_mutex_lock(&pm_lock);
202	if (!(pm1_status & PM1_PWRBTN_STS)) {
203		pm1_status |= PM1_PWRBTN_STS;
204		sci_update(ctx);
205	}
206	pthread_mutex_unlock(&pm_lock);
207}
208
209/*
210 * Power Management 1 Control Register
211 *
212 * This is mostly unimplemented except that we wish to handle writes that
213 * set SPL_EN to handle S5 (soft power off).
214 */
215static uint16_t pm1_control;
216
217#define	PM1_SCI_EN	0x0001
218#define	PM1_SLP_TYP	0x1c00
219#define	PM1_SLP_EN	0x2000
220#define	PM1_ALWAYS_ZERO	0xc003
221
222static int
223pm1_control_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
224    uint32_t *eax, void *arg)
225{
226
227	if (bytes != 2)
228		return (-1);
229	if (in)
230		*eax = pm1_control;
231	else {
232		/*
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.
236		 */
237		pm1_control = (pm1_control & PM1_SCI_EN) |
238		    (*eax & ~(PM1_SLP_EN | PM1_ALWAYS_ZERO));
239
240		/*
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.
243		 */
244		if (*eax & PM1_SLP_EN) {
245			if ((pm1_control & PM1_SLP_TYP) >> 10 == 5)
246				return (INOUT_POWEROFF);
247		}
248	}
249	return (0);
250}
251INOUT_PORT(pm1_control, PM1A_CNT_ADDR, IOPORT_F_INOUT, pm1_control_handler);
252SYSRES_IO(PM1A_EVT_ADDR, 8);
253
254/*
255 * ACPI SMI Command Register
256 *
257 * This write-only register is used to enable and disable ACPI.
258 */
259static int
260smi_cmd_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
261    uint32_t *eax, void *arg)
262{
263
264	assert(!in);
265	if (bytes != 1)
266		return (-1);
267
268	pthread_mutex_lock(&pm_lock);
269	switch (*eax) {
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);
276		}
277		break;
278	case BHYVE_ACPI_DISABLE:
279		pm1_control &= ~PM1_SCI_EN;
280		if (power_button != NULL) {
281			mevent_delete(power_button);
282			power_button = NULL;
283			signal(SIGTERM, old_power_handler);
284		}
285		break;
286	}
287	pthread_mutex_unlock(&pm_lock);
288	return (0);
289}
290INOUT_PORT(smi_cmd, SMI_CMD, IOPORT_F_OUT, smi_cmd_handler);
291SYSRES_IO(SMI_CMD, 1);
292