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