| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "dev.h" |
| |
| #include <acpica/acpi.h> |
| #include <ddk/debug.h> |
| #include <ddktl/device.h> |
| #include <ddktl/protocol/hidbus.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/mutex.h> |
| #include <fbl/unique_ptr.h> |
| #include <hid/descriptor.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/thread_annotations.h> |
| #include <zircon/types.h> |
| |
| #include <utility> |
| |
| #include "errors.h" |
| |
| class AcpiPwrbtnDevice; |
| using DeviceType = ddk::Device<AcpiPwrbtnDevice>; |
| |
| class AcpiPwrbtnDevice : public DeviceType, |
| public ddk::HidbusProtocol<AcpiPwrbtnDevice, ddk::base_protocol> { |
| public: |
| static zx_status_t Create(zx_device_t* parent, |
| fbl::unique_ptr<AcpiPwrbtnDevice>* out); |
| |
| // hidbus protocol implementation |
| zx_status_t HidbusQuery(uint32_t options, hid_info_t* info); |
| zx_status_t HidbusStart(const hidbus_ifc_t* ifc); |
| void HidbusStop(); |
| zx_status_t HidbusGetDescriptor(uint8_t desc_type, void** data, size_t* len); |
| zx_status_t HidbusGetReport(uint8_t rpt_type, uint8_t rpt_id, void* data, size_t len, |
| size_t* out_len); |
| zx_status_t HidbusSetReport(uint8_t rpt_type, uint8_t rpt_id, const void* data, size_t len); |
| zx_status_t HidbusGetIdle(uint8_t rpt_id, uint8_t* duration); |
| zx_status_t HidbusSetIdle(uint8_t rpt_id, uint8_t duration); |
| zx_status_t HidbusGetProtocol(uint8_t* protocol); |
| zx_status_t HidbusSetProtocol(uint8_t protocol); |
| |
| void DdkRelease(); |
| ~AcpiPwrbtnDevice(); |
| private: |
| explicit AcpiPwrbtnDevice(zx_device_t* parent); |
| DISALLOW_COPY_ASSIGN_AND_MOVE(AcpiPwrbtnDevice); |
| |
| static uint32_t FixedEventHandler(void* ctx); |
| static void NotifyHandler(ACPI_HANDLE handle, UINT32 value, void* ctx); |
| |
| void HandlePress(); |
| void QueueHidReportLocked() TA_REQ(lock_); |
| |
| fbl::Mutex lock_; |
| |
| // Interface the driver is currently bound to |
| ddk::HidbusIfcClient client_; |
| |
| // Track the pressed state. We don't receive up-events from ACPI, but we |
| // may want to synthesize them in the future if we care about duration of |
| // press. |
| bool pressed_ TA_GUARDED(lock_) = false; |
| |
| static const uint8_t kHidDescriptor[]; |
| static const size_t kHidDescriptorLen; |
| static constexpr size_t kHidReportLen = 1; |
| }; |
| |
| // We encode the power button as a System Power Down control in a System Control |
| // collection. |
| const uint8_t AcpiPwrbtnDevice::kHidDescriptor[] = { |
| HID_USAGE_PAGE(0x01), // Usage Page (Generic Desktop) |
| HID_USAGE(0x80), // Usage (System Control) |
| |
| HID_COLLECTION_APPLICATION, |
| HID_USAGE(0x81), // Usage (System Power Down) |
| HID_LOGICAL_MIN(0), |
| HID_LOGICAL_MAX(1), |
| HID_REPORT_COUNT(1), |
| HID_REPORT_SIZE(1), // 1 bit for power-down |
| HID_INPUT(0x06), // Input (Data,Var,Rel) |
| HID_REPORT_SIZE(7), // 7 bits of padding |
| HID_INPUT(0x03), // Input (Const,Var,Abs) |
| }; |
| |
| const size_t AcpiPwrbtnDevice::kHidDescriptorLen = sizeof(AcpiPwrbtnDevice::kHidDescriptor); |
| |
| AcpiPwrbtnDevice::AcpiPwrbtnDevice(zx_device_t* parent) |
| : DeviceType(parent) { |
| } |
| |
| AcpiPwrbtnDevice::~AcpiPwrbtnDevice() { |
| AcpiRemoveNotifyHandler(ACPI_ROOT_OBJECT, ACPI_SYSTEM_NOTIFY | ACPI_DEVICE_NOTIFY, |
| NotifyHandler); |
| AcpiRemoveFixedEventHandler(ACPI_EVENT_POWER_BUTTON, FixedEventHandler); |
| } |
| |
| void AcpiPwrbtnDevice::HandlePress() { |
| zxlogf(TRACE, "acpi-pwrbtn: pressed\n"); |
| |
| fbl::AutoLock guard(&lock_); |
| pressed_ = true; |
| QueueHidReportLocked(); |
| } |
| |
| uint32_t AcpiPwrbtnDevice::FixedEventHandler(void* ctx) { |
| auto dev = reinterpret_cast<AcpiPwrbtnDevice*>(ctx); |
| |
| dev->HandlePress(); |
| |
| // Note that the spec indicates to return 0. The code in the |
| // Intel implementation (AcpiEvFixedEventDetect) reads differently. |
| return ACPI_INTERRUPT_HANDLED; |
| } |
| |
| void AcpiPwrbtnDevice::NotifyHandler(ACPI_HANDLE handle, UINT32 value, void* ctx) { |
| auto dev = reinterpret_cast<AcpiPwrbtnDevice*>(ctx); |
| |
| ACPI_DEVICE_INFO* info = NULL; |
| ACPI_STATUS status = AcpiGetObjectInfo(handle, &info); |
| if (status != AE_OK) { |
| if (info) { |
| ACPI_FREE(info); |
| } |
| return; |
| } |
| // Handle powerbutton events via the notify interface |
| bool power_btn = false; |
| if (info->Valid & ACPI_VALID_HID) { |
| if (value == 128 && |
| !strncmp(info->HardwareId.String, "PNP0C0C", info->HardwareId.Length)) { |
| |
| power_btn = true; |
| } else if (value == 199 && |
| (!strncmp(info->HardwareId.String, "MSHW0028", info->HardwareId.Length) || |
| !strncmp(info->HardwareId.String, "MSHW0040", info->HardwareId.Length))) { |
| power_btn = true; |
| } |
| } |
| |
| if (power_btn) { |
| dev->HandlePress(); |
| } |
| |
| ACPI_FREE(info); |
| } |
| |
| void AcpiPwrbtnDevice::QueueHidReportLocked() { |
| if (client_.is_valid()) { |
| uint8_t report = 1; |
| client_.IoQueue(&report, sizeof(report)); |
| } |
| } |
| |
| zx_status_t AcpiPwrbtnDevice::HidbusQuery(uint32_t options, hid_info_t* info) { |
| zxlogf(TRACE, "acpi-pwrbtn: hid bus query\n"); |
| |
| info->dev_num = 0; |
| info->device_class = HID_DEVICE_CLASS_OTHER; |
| info->boot_device = false; |
| return ZX_OK; |
| } |
| |
| zx_status_t AcpiPwrbtnDevice::HidbusStart(const hidbus_ifc_t* ifc) { |
| zxlogf(TRACE, "acpi-pwrbtn: hid bus start\n"); |
| |
| fbl::AutoLock guard(&lock_); |
| if (client_.is_valid()) { |
| return ZX_ERR_ALREADY_BOUND; |
| } |
| client_ = ddk::HidbusIfcClient(ifc); |
| return ZX_OK; |
| } |
| |
| void AcpiPwrbtnDevice::HidbusStop() { |
| zxlogf(TRACE, "acpi-pwrbtn: hid bus stop\n"); |
| |
| fbl::AutoLock guard(&lock_); |
| client_.clear(); |
| } |
| |
| zx_status_t AcpiPwrbtnDevice::HidbusGetDescriptor(uint8_t desc_type, void** data, size_t* len) { |
| zxlogf(TRACE, "acpi-pwrbtn: hid bus get descriptor\n"); |
| |
| if (data == nullptr || len == nullptr) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (desc_type != HID_DESCRIPTION_TYPE_REPORT) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| *data = malloc(kHidDescriptorLen); |
| if (*data == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| *len = kHidDescriptorLen; |
| memcpy(*data, kHidDescriptor, kHidDescriptorLen); |
| return ZX_OK; |
| } |
| |
| zx_status_t AcpiPwrbtnDevice::HidbusGetReport(uint8_t rpt_type, uint8_t rpt_id, void* data, |
| size_t len, size_t* out_len) { |
| if (out_len == NULL) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (rpt_type != HID_REPORT_TYPE_INPUT || rpt_id != 0) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| if (len < kHidReportLen) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| |
| fbl::AutoLock guard(&lock_); |
| uint8_t report = pressed_; |
| static_assert(sizeof(report) == kHidReportLen, ""); |
| memcpy(data, &report, kHidReportLen); |
| |
| *out_len = kHidReportLen; |
| return ZX_OK; |
| } |
| |
| zx_status_t AcpiPwrbtnDevice::HidbusSetReport(uint8_t rpt_type, uint8_t rpt_id, const void* data, |
| size_t len) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t AcpiPwrbtnDevice::HidbusGetIdle(uint8_t rpt_id, uint8_t* duration) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t AcpiPwrbtnDevice::HidbusSetIdle(uint8_t rpt_id, uint8_t duration) { |
| return ZX_OK; |
| } |
| |
| zx_status_t AcpiPwrbtnDevice::HidbusGetProtocol(uint8_t* protocol) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t AcpiPwrbtnDevice::HidbusSetProtocol(uint8_t protocol) { |
| return ZX_OK; |
| } |
| |
| void AcpiPwrbtnDevice::DdkRelease() { |
| zxlogf(INFO, "acpi-pwrbtn: DdkRelease\n"); |
| delete this; |
| } |
| |
| zx_status_t AcpiPwrbtnDevice::Create(zx_device_t* parent, |
| fbl::unique_ptr<AcpiPwrbtnDevice>* out) { |
| fbl::AllocChecker ac; |
| fbl::unique_ptr<AcpiPwrbtnDevice> dev(new (&ac) AcpiPwrbtnDevice(parent)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| ACPI_STATUS status = AcpiInstallFixedEventHandler(ACPI_EVENT_POWER_BUTTON, |
| FixedEventHandler, |
| dev.get()); |
| if (status != AE_OK) { |
| // The dtor for AcpiPwrbtnDevice will clean these global handlers up when we |
| // return here. |
| return acpi_to_zx_status(status); |
| } |
| |
| status = AcpiInstallNotifyHandler(ACPI_ROOT_OBJECT, |
| ACPI_SYSTEM_NOTIFY | ACPI_DEVICE_NOTIFY, |
| NotifyHandler, |
| dev.get()); |
| if (status != AE_OK) { |
| // The dtor for AcpiPwrbtnDevice will clean these global handlers up when we |
| // return here. |
| return acpi_to_zx_status(status); |
| } |
| |
| *out = std::move(dev); |
| return ZX_OK; |
| } |
| |
| zx_status_t pwrbtn_init(zx_device_t* parent) { |
| zxlogf(TRACE, "acpi-pwrbtn: init\n"); |
| |
| fbl::unique_ptr<AcpiPwrbtnDevice> dev; |
| zx_status_t status = AcpiPwrbtnDevice::Create(parent, &dev); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = dev->DdkAdd("acpi-pwrbtn"); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // devmgr is now in charge of the memory for dev |
| __UNUSED auto ptr = dev.release(); |
| |
| zxlogf(INFO, "acpi-pwrbtn: initialized\n"); |
| return ZX_OK; |
| } |