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