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