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