blob: f5dee33818cab41fa9d6a54a8f216001db809400 [file] [log] [blame]
// Copyright 2019 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-lid.h"
#include <fuchsia/hardware/hidbus/cpp/banjo.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <utility>
#include <acpica/acpi.h>
#include <ddk/debug.h>
#include <ddktl/device.h>
#include <fbl/auto_lock.h>
#include <hid/descriptor.h>
#include "dev.h"
#include "errors.h"
namespace {
constexpr static acpi_lid::LidState LidStateFromAcpiValue(uint64_t value) {
return value ? acpi_lid::LidState::OPEN : acpi_lid::LidState::CLOSED;
}
constexpr static uint8_t LidStateToHidReport(acpi_lid::LidState state) {
ZX_DEBUG_ASSERT(state != acpi_lid::LidState::UNKNOWN);
return static_cast<uint8_t>(state);
}
} // namespace
namespace acpi_lid {
// We encode the lid switch events as a vendor-defined System Control.
// This is a bit hacky, but there is no lid switch defined in the HID usage
// tables.
// System Control collections are meant to be consumed by the
// operating system, not user applications.
const uint8_t AcpiLidDevice::kHidDescriptor[] = {
HID_USAGE_PAGE(0x01), // Usage Page (Generic Desktop)
HID_USAGE(0x80), // Usage (System Control)
HID_COLLECTION_APPLICATION,
HID_USAGE16(0x01FF), // Usage (Vendor defined)
HID_LOGICAL_MIN(0),
HID_LOGICAL_MAX(1),
HID_REPORT_COUNT(1),
HID_REPORT_SIZE(1), // 1 bit for lid state
HID_INPUT(0x02), // Input (Data,Var,Abs)
HID_REPORT_SIZE(7), // 7 bits of padding
HID_INPUT(0x03), // Input (Const,Var,Abs)
HID_END_COLLECTION,
};
const size_t AcpiLidDevice::kHidDescriptorLen = sizeof(AcpiLidDevice::kHidDescriptor);
zx_status_t AcpiLidDevice::UpdateLidStateLocked() TA_REQ(lock_) {
ACPI_OBJECT obj = {};
ACPI_BUFFER buffer = {
.Length = sizeof(obj),
.Pointer = &obj,
};
ACPI_STATUS acpi_status =
acpi_eval_(acpi_handle_, const_cast<char*>("_LID"), nullptr, &buffer, ACPI_TYPE_INTEGER);
if (acpi_status != AE_OK) {
zx_status_t status = acpi_to_zx_status(acpi_status);
zxlogf(ERROR, "acpi-lid: _LID failed: %d (%s)", acpi_status, zx_status_get_string(status));
return status;
}
zxlogf(DEBUG, "acpi-lid: _LID returned 0x%llx", obj.Integer.Value);
lid_state_ = LidStateFromAcpiValue(obj.Integer.Value);
zxlogf(DEBUG, "acpi-lid: Lid is %s", (lid_state_ == LidState::OPEN ? "open" : "closed"));
return ZX_OK;
}
void AcpiLidDevice::QueueHidReportLocked() TA_REQ(lock_) {
if (client_.is_valid()) {
zxlogf(DEBUG, "acpi-lid: queueing report");
uint8_t report = LidStateToHidReport(lid_state_);
client_.IoQueue(&report, sizeof(report), zx_clock_get_monotonic());
}
}
void AcpiLidDevice::PublishLidStateIfChanged() {
fbl::AutoLock guard(&lock_);
LidState old_state = lid_state_;
if (UpdateLidStateLocked() == ZX_OK && old_state != lid_state_) {
QueueHidReportLocked();
}
}
void AcpiLidDevice::NotifyHandler(ACPI_HANDLE handle, UINT32 value, void* ctx) {
auto dev = reinterpret_cast<AcpiLidDevice*>(ctx);
zxlogf(DEBUG, "acpi-lid: got event 0x%x", value);
if (value == 0x80) {
// Lid state has changed
dev->PublishLidStateIfChanged();
}
}
zx_status_t AcpiLidDevice::HidbusQuery(uint32_t options, hid_info_t* info) {
zxlogf(DEBUG, "acpi-lid: hid bus query");
info->dev_num = 0;
info->device_class = HID_DEVICE_CLASS_OTHER;
info->boot_device = false;
return ZX_OK;
}
zx_status_t AcpiLidDevice::HidbusStart(const hidbus_ifc_protocol_t* ifc) {
zxlogf(DEBUG, "acpi-lid: hid bus start");
fbl::AutoLock guard(&lock_);
if (client_.is_valid()) {
return ZX_ERR_ALREADY_BOUND;
}
auto acpi_status = acpi_install_notify_(acpi_handle_, ACPI_DEVICE_NOTIFY, NotifyHandler, this);
if (acpi_status != AE_OK) {
auto status = acpi_to_zx_status(acpi_status);
zxlogf(ERROR, "acpi-lid: Failed to install notify handler: %d (%s)", acpi_status,
zx_status_get_string(status));
return status;
}
client_ = ddk::HidbusIfcProtocolClient(ifc);
return ZX_OK;
}
void AcpiLidDevice::HidbusStop() {
zxlogf(DEBUG, "acpi-lid: hid bus stop");
fbl::AutoLock guard(&lock_);
ACPI_STATUS acpi_status;
if ((acpi_status = acpi_remove_notify_(acpi_handle_, ACPI_DEVICE_NOTIFY, NotifyHandler)) !=
AE_OK) {
zxlogf(ERROR, "acpi-lid: Failed to uninstall notify handler: %d (%s)", acpi_status,
zx_status_get_string(acpi_to_zx_status(acpi_status)));
}
client_.clear();
}
zx_status_t AcpiLidDevice::HidbusGetDescriptor(hid_description_type_t desc_type,
void* out_data_buffer, size_t data_size,
size_t* out_data_actual) {
zxlogf(DEBUG, "acpi-lid: hid bus get descriptor");
if (out_data_buffer == nullptr || out_data_actual == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
if (desc_type != HID_DESCRIPTION_TYPE_REPORT) {
return ZX_ERR_NOT_FOUND;
}
if (data_size < kHidDescriptorLen) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(out_data_buffer, kHidDescriptor, kHidDescriptorLen);
*out_data_actual = kHidDescriptorLen;
return ZX_OK;
}
zx_status_t AcpiLidDevice::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 = LidStateToHidReport(lid_state_);
static_assert(sizeof(report) == kHidReportLen);
memcpy(data, &report, kHidReportLen);
*out_len = kHidReportLen;
return ZX_OK;
}
zx_status_t AcpiLidDevice::HidbusSetReport(uint8_t rpt_type, uint8_t rpt_id, const void* data,
size_t len) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t AcpiLidDevice::HidbusGetIdle(uint8_t rpt_id, uint8_t* duration) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t AcpiLidDevice::HidbusSetIdle(uint8_t rpt_id, uint8_t duration) { return ZX_OK; }
zx_status_t AcpiLidDevice::HidbusGetProtocol(uint8_t* protocol) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t AcpiLidDevice::HidbusSetProtocol(uint8_t protocol) { return ZX_OK; }
void AcpiLidDevice::DdkUnbind(ddk::UnbindTxn txn) {
zxlogf(INFO, "acpi-lid: unbind");
txn.Reply();
}
void AcpiLidDevice::DdkRelease() {
zxlogf(INFO, "acpi-lid: release");
delete this;
}
zx_status_t AcpiLidDevice::Create(zx_device_t* parent, ACPI_HANDLE acpi_handle,
std::unique_ptr<AcpiLidDevice>* out, AcpiObjectEvalFunc acpi_eval,
AcpiInstallNotifyHandlerFunc acpi_install_notify,
AcpiRemoveNotifyHandlerFunc acpi_remove_notify) {
auto dev = std::make_unique<AcpiLidDevice>(parent, acpi_handle, std::move(acpi_eval),
std::move(acpi_install_notify),
std::move(acpi_remove_notify));
// Initialize tracked state
{
fbl::AutoLock guard(&dev->lock_);
dev->UpdateLidStateLocked();
}
*out = std::move(dev);
return ZX_OK;
}
} // namespace acpi_lid
zx_status_t lid_init(zx_device_t* parent, ACPI_HANDLE acpi_handle) {
zxlogf(DEBUG, "acpi-lid: init");
std::unique_ptr<acpi_lid::AcpiLidDevice> dev;
zx_status_t status = acpi_lid::AcpiLidDevice::Create(parent, acpi_handle, &dev);
if (status != ZX_OK) {
return status;
}
status = dev->DdkAdd("acpi-lid");
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the memory for dev
__UNUSED auto* ptr = dev.release();
zxlogf(INFO, "acpi-lid: initialized");
return ZX_OK;
}