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