1// Copyright 2017 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7#include "system_priv.h"
8
9#include <arch/arch_ops.h>
10#include <arch/mp.h>
11#include <fbl/auto_call.h>
12#include <inttypes.h>
13#include <kernel/timer.h>
14#include <platform.h>
15#include <trace.h>
16#include <vm/vm_aspace.h>
17
18#include <arch/x86/feature.h>
19#include <arch/x86/bootstrap16.h>
20#include <arch/x86/acpi.h>
21extern "C" {
22#include <acpica/acpi.h>
23#include <acpica/accommon.h>
24#include <acpica/achware.h>
25}
26
27#define LOCAL_TRACE 0
28
29namespace {
30
31// This thread performs the work for suspend/resume.  We use a separate thread
32// rather than the invoking thread to let us lean on the context switch code
33// path to persist all of the usermode thread state that is not saved on a plain
34// mode switch.
35zx_status_t suspend_thread(void* raw_arg) {
36    auto arg = reinterpret_cast<const zx_system_powerctl_arg_t*>(raw_arg);
37    uint8_t target_s_state = arg->acpi_transition_s_state.target_s_state;
38    uint8_t sleep_type_a = arg->acpi_transition_s_state.sleep_type_a;
39    uint8_t sleep_type_b = arg->acpi_transition_s_state.sleep_type_b;
40
41    // Acquire resources for suspend and resume if necessary.
42    fbl::RefPtr<VmAspace> temp_aspace;
43    x86_realmode_entry_data* bootstrap_data;
44    struct x86_realmode_entry_data_registers regs;
45    paddr_t bootstrap_ip;
46    zx_status_t status;
47    status = x86_bootstrap16_acquire(reinterpret_cast<uintptr_t>(_x86_suspend_wakeup),
48                                     &temp_aspace,
49                                     reinterpret_cast<void**>(&bootstrap_data),
50                                     &bootstrap_ip);
51    if (status != ZX_OK) {
52        return status;
53    }
54    auto bootstrap_cleanup = fbl::MakeAutoCall([&bootstrap_data]() {
55        x86_bootstrap16_release(bootstrap_data);
56    });
57
58    // Setup our resume path
59    ACPI_TABLE_FACS* facs = nullptr;
60    ACPI_STATUS acpi_status = AcpiGetTable((char *)ACPI_SIG_FACS, 1,
61                                           reinterpret_cast<ACPI_TABLE_HEADER**>(&facs));
62    if (acpi_status != AE_OK) {
63        return ZX_ERR_BAD_STATE;
64    }
65    acpi_status = AcpiHwSetFirmwareWakingVector(facs, bootstrap_ip, 0);
66    if (acpi_status != AE_OK) {
67        return ZX_ERR_BAD_STATE;
68    }
69    auto wake_vector_cleanup = fbl::MakeAutoCall([facs]() {
70        AcpiHwSetFirmwareWakingVector(facs, 0, 0);
71    });
72
73    bootstrap_data->registers_ptr = reinterpret_cast<uintptr_t>(&regs);
74
75    arch_disable_ints();
76
77    // Save system state.
78    platform_suspend();
79    arch_suspend();
80
81    // Do the actual suspend
82    TRACEF("Entering x86_acpi_transition_s_state\n");
83    acpi_status = x86_acpi_transition_s_state(&regs, target_s_state,
84                                              sleep_type_a, sleep_type_b);
85    if (acpi_status != AE_OK) {
86        arch_enable_ints();
87        TRACEF("x86_acpi_transition_s_state failed: %x\n", acpi_status);
88        return ZX_ERR_INTERNAL;
89    }
90    TRACEF("Left x86_acpi_transition_s_state\n");
91
92    // If we're here, we've resumed and need to restore our CPU context
93    DEBUG_ASSERT(arch_ints_disabled());
94
95    arch_resume();
96    platform_resume();
97    timer_thaw_percpu();
98
99    DEBUG_ASSERT(arch_ints_disabled());
100    arch_enable_ints();
101    return ZX_OK;
102}
103
104zx_status_t x86_set_pkg_pl1(const zx_system_powerctl_arg_t* arg) {
105    if ((x86_microarch != X86_MICROARCH_INTEL_SANDY_BRIDGE) &&
106        (x86_microarch != X86_MICROARCH_INTEL_SILVERMONT) &&
107        (x86_microarch != X86_MICROARCH_INTEL_BROADWELL) &&
108        (x86_microarch != X86_MICROARCH_INTEL_HASWELL) &&
109        (x86_microarch != X86_MICROARCH_INTEL_SKYLAKE) &&
110        (x86_microarch != X86_MICROARCH_INTEL_KABYLAKE)) {
111        return ZX_ERR_NOT_SUPPORTED;
112    }
113
114    uint32_t power_limit = arg->x86_power_limit.power_limit;
115    uint8_t clamp = arg->x86_power_limit.clamp;
116    uint8_t enable = arg->x86_power_limit.enable;
117
118    uint64_t u = read_msr(X86_MSR_RAPL_POWER_UNIT);
119    uint64_t v = read_msr(X86_MSR_PKG_POWER_LIMIT);
120
121    uint64_t pu = 1 << (u & 0xf);
122
123    // TODO(ZX-1429) time window is not currently supported
124
125    v &= ~0x7fff;
126    if (power_limit > 0) {
127        uint64_t n = (power_limit * pu / 1000);
128        if (n > 0x7fff) {
129            return ZX_ERR_INVALID_ARGS;
130        }
131        v |= n;
132    } else {
133        // set to default if 0
134        v |= read_msr(X86_MSR_PKG_POWER_INFO) & 0x7fff;
135    }
136
137    if (clamp) {
138        v |= X86_MSR_PKG_POWER_LIMIT_PL1_CLAMP;
139    } else {
140        v &= ~X86_MSR_PKG_POWER_LIMIT_PL1_CLAMP;
141    }
142
143    if (enable) {
144        v |= X86_MSR_PKG_POWER_LIMIT_PL1_ENABLE;
145    } else {
146        v &= ~X86_MSR_PKG_POWER_LIMIT_PL1_ENABLE;
147    }
148
149    write_msr(X86_MSR_PKG_POWER_LIMIT, v);
150    return ZX_OK;
151}
152
153zx_status_t acpi_transition_s_state(const zx_system_powerctl_arg_t* arg) {
154    uint8_t target_s_state = arg->acpi_transition_s_state.target_s_state;
155    uint8_t sleep_type_a = arg->acpi_transition_s_state.sleep_type_a;
156    uint8_t sleep_type_b = arg->acpi_transition_s_state.sleep_type_b;
157    if (target_s_state == 0 || target_s_state > 5) {
158        TRACEF("Bad S-state: S%u\n", target_s_state);
159        return ZX_ERR_INVALID_ARGS;
160    }
161
162    // If not a shutdown, ensure CPU 0 is the only cpu left running.
163    if (target_s_state != 5 && mp_get_online_mask() != cpu_num_to_mask(0)) {
164        TRACEF("Too many CPUs running for state S%u\n", target_s_state);
165        return ZX_ERR_BAD_STATE;
166    }
167
168    // Acquire resources for suspend and resume if necessary.
169    if (target_s_state < 5) {
170        // If we're not shutting down, prepare a resume path and execute the
171        // suspend on a separate thread (see comment on |suspend_thread()| for
172        // explanation).
173        thread_t* t = thread_create("suspend-thread", suspend_thread,
174                                    const_cast<zx_system_powerctl_arg_t*>(arg),
175                                    HIGHEST_PRIORITY);
176        if (!t) {
177            return ZX_ERR_NO_MEMORY;
178        }
179
180        thread_resume(t);
181
182        zx_status_t retcode;
183        zx_status_t status = thread_join(t, &retcode, ZX_TIME_INFINITE);
184        ASSERT(status == ZX_OK);
185
186        if (retcode != ZX_OK) {
187            return retcode;
188        }
189    } else {
190        struct x86_realmode_entry_data_registers regs;
191
192        DEBUG_ASSERT(target_s_state == 5);
193        arch_disable_ints();
194
195        ACPI_STATUS acpi_status = x86_acpi_transition_s_state(&regs, target_s_state,
196                                                              sleep_type_a, sleep_type_b);
197        arch_enable_ints();
198        if (acpi_status != AE_OK) {
199            return ZX_ERR_INTERNAL;
200        }
201    }
202
203    return ZX_OK;
204}
205
206} // namespace
207
208zx_status_t arch_system_powerctl(uint32_t cmd, const zx_system_powerctl_arg_t* arg) {
209    switch (cmd) {
210    case ZX_SYSTEM_POWERCTL_ACPI_TRANSITION_S_STATE:
211        return acpi_transition_s_state(arg);
212    case ZX_SYSTEM_POWERCTL_X86_SET_PKG_PL1:
213        return x86_set_pkg_pl1(arg);
214    default:
215        return ZX_ERR_NOT_SUPPORTED;
216    }
217}
218