1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "dev.h"
6
7#include <acpica/acpi.h>
8#include <ddk/debug.h>
9#include <ddktl/device.h>
10#include <ddktl/protocol/hidbus.h>
11#include <fbl/alloc_checker.h>
12#include <fbl/auto_lock.h>
13#include <fbl/mutex.h>
14#include <fbl/unique_ptr.h>
15#include <hid/descriptor.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <zircon/device/input.h>
19#include <zircon/syscalls.h>
20#include <zircon/thread_annotations.h>
21#include <zircon/types.h>
22
23#include "errors.h"
24
25class AcpiPwrbtnDevice;
26using DeviceType = ddk::Device<AcpiPwrbtnDevice>;
27
28class AcpiPwrbtnDevice : public DeviceType, public ddk::HidBusProtocol<AcpiPwrbtnDevice> {
29public:
30    static zx_status_t Create(zx_device_t* parent,
31                              fbl::unique_ptr<AcpiPwrbtnDevice>* out);
32
33    // hidbus protocol implementation
34    zx_status_t HidBusQuery(uint32_t options, hid_info_t* info);
35    zx_status_t HidBusStart(ddk::HidBusIfcProxy proxy);
36    void HidBusStop();
37    zx_status_t HidBusGetDescriptor(uint8_t desc_type, void** data, size_t* len);
38    zx_status_t HidBusGetReport(uint8_t rpt_type, uint8_t rpt_id, void* data, size_t len,
39                                size_t* out_len);
40    zx_status_t HidBusSetReport(uint8_t rpt_type, uint8_t rpt_id, void* data, size_t len);
41    zx_status_t HidBusGetIdle(uint8_t rpt_id, uint8_t* duration);
42    zx_status_t HidBusSetIdle(uint8_t rpt_id, uint8_t duration);
43    zx_status_t HidBusGetProtocol(uint8_t* protocol);
44    zx_status_t HidBusSetProtocol(uint8_t protocol);
45
46    void DdkRelease();
47    ~AcpiPwrbtnDevice();
48private:
49    explicit AcpiPwrbtnDevice(zx_device_t* parent);
50    DISALLOW_COPY_ASSIGN_AND_MOVE(AcpiPwrbtnDevice);
51
52    static uint32_t FixedEventHandler(void* ctx);
53    static void NotifyHandler(ACPI_HANDLE handle, UINT32 value, void* ctx);
54
55    void HandlePress();
56    void QueueHidReportLocked() TA_REQ(lock_);
57
58    fbl::Mutex lock_;
59
60    // Interface the driver is currently bound to
61    ddk::HidBusIfcProxy proxy_;
62
63    // Track the pressed state.  We don't receive up-events from ACPI, but we
64    // may want to synthesize them in the future if we care about duration of
65    // press.
66    bool pressed_ TA_GUARDED(lock_) = false;
67
68    static const uint8_t kHidDescriptor[];
69    static const size_t kHidDescriptorLen;
70    static constexpr size_t kHidReportLen = 1;
71};
72
73// We encode the power button as a System Power Down control in a System Control
74// collection.
75const uint8_t AcpiPwrbtnDevice::kHidDescriptor[] = {
76    HID_USAGE_PAGE(0x01), // Usage Page (Generic Desktop)
77    HID_USAGE(0x80), // Usage (System Control)
78
79    HID_COLLECTION_APPLICATION,
80    HID_USAGE(0x81), // Usage (System Power Down)
81    HID_LOGICAL_MIN(0),
82    HID_LOGICAL_MAX(1),
83    HID_REPORT_COUNT(1),
84    HID_REPORT_SIZE(1), // 1 bit for power-down
85    HID_INPUT(0x06), // Input (Data,Var,Rel)
86    HID_REPORT_SIZE(7), // 7 bits of padding
87    HID_INPUT(0x03), // Input (Const,Var,Abs)
88};
89
90const size_t AcpiPwrbtnDevice::kHidDescriptorLen = sizeof(AcpiPwrbtnDevice::kHidDescriptor);
91
92AcpiPwrbtnDevice::AcpiPwrbtnDevice(zx_device_t* parent)
93    : DeviceType(parent) {
94}
95
96AcpiPwrbtnDevice::~AcpiPwrbtnDevice() {
97    AcpiRemoveNotifyHandler(ACPI_ROOT_OBJECT, ACPI_SYSTEM_NOTIFY | ACPI_DEVICE_NOTIFY,
98                            NotifyHandler);
99    AcpiRemoveFixedEventHandler(ACPI_EVENT_POWER_BUTTON, FixedEventHandler);
100}
101
102void AcpiPwrbtnDevice::HandlePress() {
103    zxlogf(TRACE, "acpi-pwrbtn: pressed\n");
104
105    fbl::AutoLock guard(&lock_);
106    pressed_ = true;
107    QueueHidReportLocked();
108}
109
110uint32_t AcpiPwrbtnDevice::FixedEventHandler(void* ctx) {
111    auto dev = reinterpret_cast<AcpiPwrbtnDevice*>(ctx);
112
113    dev->HandlePress();
114
115    // Note that the spec indicates to return 0. The code in the
116    // Intel implementation (AcpiEvFixedEventDetect) reads differently.
117    return ACPI_INTERRUPT_HANDLED;
118}
119
120void AcpiPwrbtnDevice::NotifyHandler(ACPI_HANDLE handle, UINT32 value, void* ctx) {
121    auto dev = reinterpret_cast<AcpiPwrbtnDevice*>(ctx);
122
123    ACPI_DEVICE_INFO* info = NULL;
124    ACPI_STATUS status = AcpiGetObjectInfo(handle, &info);
125    if (status != AE_OK) {
126        if (info) {
127            ACPI_FREE(info);
128        }
129        return;
130    }
131    // Handle powerbutton events via the notify interface
132    bool power_btn = false;
133    if (info->Valid & ACPI_VALID_HID) {
134        if (value == 128 &&
135            !strncmp(info->HardwareId.String, "PNP0C0C", info->HardwareId.Length)) {
136
137            power_btn = true;
138        } else if (value == 199 &&
139                   (!strncmp(info->HardwareId.String, "MSHW0028", info->HardwareId.Length) ||
140                    !strncmp(info->HardwareId.String, "MSHW0040", info->HardwareId.Length))) {
141            power_btn = true;
142        }
143    }
144
145    if (power_btn) {
146        dev->HandlePress();
147    }
148
149    ACPI_FREE(info);
150}
151
152void AcpiPwrbtnDevice::QueueHidReportLocked() {
153    if (proxy_.is_valid()) {
154        uint8_t report = 1;
155        proxy_.IoQueue(&report, sizeof(report));
156    }
157}
158
159zx_status_t AcpiPwrbtnDevice::HidBusQuery(uint32_t options, hid_info_t* info) {
160    zxlogf(TRACE, "acpi-pwrbtn: hid bus query\n");
161
162    info->dev_num = 0;
163    info->dev_class = HID_DEV_CLASS_OTHER;
164    info->boot_device = false;
165    return ZX_OK;
166}
167
168zx_status_t AcpiPwrbtnDevice::HidBusStart(ddk::HidBusIfcProxy proxy) {
169    zxlogf(TRACE, "acpi-pwrbtn: hid bus start\n");
170
171    fbl::AutoLock guard(&lock_);
172    if (proxy_.is_valid()) {
173        return ZX_ERR_ALREADY_BOUND;
174    }
175    proxy_ = proxy;
176    return ZX_OK;
177}
178
179void AcpiPwrbtnDevice::HidBusStop() {
180    zxlogf(TRACE, "acpi-pwrbtn: hid bus stop\n");
181
182    fbl::AutoLock guard(&lock_);
183    proxy_.clear();
184}
185
186zx_status_t AcpiPwrbtnDevice::HidBusGetDescriptor(uint8_t desc_type, void** data, size_t* len) {
187    zxlogf(TRACE, "acpi-pwrbtn: hid bus get descriptor\n");
188
189    if (data == nullptr || len == nullptr) {
190        return ZX_ERR_INVALID_ARGS;
191    }
192
193    if (desc_type != HID_DESC_TYPE_REPORT) {
194        return ZX_ERR_NOT_FOUND;
195    }
196
197    *data = malloc(kHidDescriptorLen);
198    if (*data == nullptr) {
199        return ZX_ERR_NO_MEMORY;
200    }
201    *len = kHidDescriptorLen;
202    memcpy(*data, kHidDescriptor, kHidDescriptorLen);
203    return ZX_OK;
204}
205
206zx_status_t AcpiPwrbtnDevice::HidBusGetReport(uint8_t rpt_type, uint8_t rpt_id, void* data,
207                                              size_t len, size_t* out_len) {
208    if (out_len == NULL) {
209        return ZX_ERR_INVALID_ARGS;
210    }
211
212    if (rpt_type != HID_REPORT_TYPE_INPUT || rpt_id != 0) {
213        return ZX_ERR_NOT_FOUND;
214    }
215
216    if (len < kHidReportLen) {
217        return ZX_ERR_BUFFER_TOO_SMALL;
218    }
219
220    fbl::AutoLock guard(&lock_);
221    uint8_t report = pressed_;
222    static_assert(sizeof(report) == kHidReportLen, "");
223    memcpy(data, &report, kHidReportLen);
224
225    *out_len = kHidReportLen;
226    return ZX_OK;
227}
228
229zx_status_t AcpiPwrbtnDevice::HidBusSetReport(uint8_t rpt_type, uint8_t rpt_id, void* data,
230                                            size_t len) {
231    return ZX_ERR_NOT_SUPPORTED;
232}
233
234zx_status_t AcpiPwrbtnDevice::HidBusGetIdle(uint8_t rpt_id, uint8_t* duration) {
235    return ZX_ERR_NOT_SUPPORTED;
236}
237
238zx_status_t AcpiPwrbtnDevice::HidBusSetIdle(uint8_t rpt_id, uint8_t duration) {
239    return ZX_OK;
240}
241
242zx_status_t AcpiPwrbtnDevice::HidBusGetProtocol(uint8_t* protocol) {
243    return ZX_ERR_NOT_SUPPORTED;
244}
245
246zx_status_t AcpiPwrbtnDevice::HidBusSetProtocol(uint8_t protocol) {
247    return ZX_OK;
248}
249
250void AcpiPwrbtnDevice::DdkRelease() {
251    zxlogf(INFO, "acpi-pwrbtn: DdkRelease\n");
252    delete this;
253}
254
255zx_status_t AcpiPwrbtnDevice::Create(zx_device_t* parent,
256                                     fbl::unique_ptr<AcpiPwrbtnDevice>* out) {
257    fbl::AllocChecker ac;
258    fbl::unique_ptr<AcpiPwrbtnDevice> dev(new (&ac) AcpiPwrbtnDevice(parent));
259    if (!ac.check()) {
260        return ZX_ERR_NO_MEMORY;
261    }
262
263    ACPI_STATUS status = AcpiInstallFixedEventHandler(ACPI_EVENT_POWER_BUTTON,
264                                                      FixedEventHandler,
265                                                      dev.get());
266    if (status != AE_OK) {
267        // The dtor for AcpiPwrbtnDevice will clean these global handlers up when we
268        // return here.
269        return acpi_to_zx_status(status);
270    }
271
272    status = AcpiInstallNotifyHandler(ACPI_ROOT_OBJECT,
273                                      ACPI_SYSTEM_NOTIFY | ACPI_DEVICE_NOTIFY,
274                                      NotifyHandler,
275                                      dev.get());
276    if (status != AE_OK) {
277        // The dtor for AcpiPwrbtnDevice will clean these global handlers up when we
278        // return here.
279        return acpi_to_zx_status(status);
280    }
281
282    *out = fbl::move(dev);
283    return ZX_OK;
284}
285
286zx_status_t pwrbtn_init(zx_device_t* parent) {
287    zxlogf(TRACE, "acpi-pwrbtn: init\n");
288
289    fbl::unique_ptr<AcpiPwrbtnDevice> dev;
290    zx_status_t status = AcpiPwrbtnDevice::Create(parent, &dev);
291    if (status != ZX_OK) {
292        return status;
293    }
294
295    status = dev->DdkAdd("acpi-pwrbtn");
296    if (status != ZX_OK) {
297        return status;
298    }
299
300    // devmgr is now in charge of the memory for dev
301    __UNUSED auto ptr = dev.release();
302
303    zxlogf(INFO, "acpi-pwrbtn: initialized\n");
304    return ZX_OK;
305}
306