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