]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/bhyve/pm.c
MFV illumos r266986:
[FreeBSD/FreeBSD.git] / usr.sbin / bhyve / pm.c
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$");
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
45 static pthread_mutex_t pm_lock = PTHREAD_MUTEX_INITIALIZER;
46 static struct mevent *power_button;
47 static 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  */
55 static int
56 reset_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 }
74 INOUT_PORT(reset_reg, 0xCF9, IOPORT_F_INOUT, reset_handler);
75
76 /*
77  * ACPI's SCI is a level-triggered interrupt.
78  */
79 static int sci_active;
80
81 static void
82 sci_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
91 static void
92 sci_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  */
107 static 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
123 static void
124 sci_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
146 static int
147 pm1_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
170 static int
171 pm1_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 }
193 INOUT_PORT(pm1_status, PM1A_EVT_ADDR, IOPORT_F_INOUT, pm1_status_handler);
194 INOUT_PORT(pm1_enable, PM1A_EVT_ADDR + 2, IOPORT_F_INOUT, pm1_enable_handler);
195
196 static void
197 power_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  */
216 static 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
223 static int
224 pm1_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 }
252 INOUT_PORT(pm1_control, PM1A_CNT_ADDR, IOPORT_F_INOUT, pm1_control_handler);
253 SYSRES_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  */
260 static int
261 smi_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 }
291 INOUT_PORT(smi_cmd, SMI_CMD, IOPORT_F_OUT, smi_cmd_handler);
292 SYSRES_IO(SMI_CMD, 1);
293
294 void
295 sci_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 }