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 <ddk/debug.h>
8 #include <ddktl/device.h>
9 #include <ddktl/protocol/hidbus.h>
10 
11 #include <zircon/types.h>
12 #include <zircon/syscalls.h>
13 #include <fbl/alloc_checker.h>
14 #include <fbl/auto_lock.h>
15 #include <fbl/mutex.h>
16 #include <fbl/unique_ptr.h>
17 #include <stdlib.h>
18 #include <stdio.h>
19 
20 #include <acpica/acpi.h>
21 
22 #include <utility>
23 
24 #include "errors.h"
25 
26 class AcpiTbmcDevice;
27 using DeviceType = ddk::Device<AcpiTbmcDevice>;
28 
29 // An instance of a GOOG0006 Tablet Motion Control device.  It presents a HID
30 // interface with a single input, the state of the tablet mode switch.
31 class AcpiTbmcDevice : public DeviceType,
32                        public ddk::HidbusProtocol<AcpiTbmcDevice, ddk::base_protocol> {
33 public:
34     static zx_status_t Create(zx_device_t* parent, ACPI_HANDLE acpi_handle,
35                               fbl::unique_ptr<AcpiTbmcDevice>* out);
36 
37     // hidbus protocol implementation
38     zx_status_t HidbusQuery(uint32_t options, hid_info_t* info);
39     zx_status_t HidbusStart(const hidbus_ifc_t* ifc);
40     void HidbusStop();
41     zx_status_t HidbusGetDescriptor(uint8_t desc_type, void** data, size_t* len);
42     zx_status_t HidbusGetReport(uint8_t rpt_type, uint8_t rpt_id, void* data, size_t len,
43                                 size_t* out_len);
44     zx_status_t HidbusSetReport(uint8_t rpt_type, uint8_t rpt_id, const void* data, size_t len);
45     zx_status_t HidbusGetIdle(uint8_t rpt_id, uint8_t* duration);
46     zx_status_t HidbusSetIdle(uint8_t rpt_id, uint8_t duration);
47     zx_status_t HidbusGetProtocol(uint8_t* protocol);
48     zx_status_t HidbusSetProtocol(uint8_t protocol);
49 
50     void DdkRelease();
51     ~AcpiTbmcDevice();
52 private:
53     AcpiTbmcDevice(zx_device_t* parent, ACPI_HANDLE acpi_handle);
54     DISALLOW_COPY_ASSIGN_AND_MOVE(AcpiTbmcDevice);
55 
56     zx_status_t CallTbmcMethod();
57     static void NotifyHandler(ACPI_HANDLE handle, UINT32 value, void* ctx);
58 
59     zx_status_t QueueHidReportLocked();
60 
61     const ACPI_HANDLE acpi_handle_;
62 
63     fbl::Mutex lock_;
64 
65     // Current state of the tablet mode switch
66     bool tablet_mode_ = false;
67 
68     // Interface the driver is currently bound to
69     ddk::HidbusIfcClient client_;
70 
71     static const uint8_t kHidDescriptor[];
72     static const size_t kHidDescriptorLen;
73     static constexpr size_t kHidReportLen = 1;
74 };
75 
76 // We encode the tablet mode switch events as a vendor-defined System Control.
77 // This is a bit hacky, but there is no tablet mode switch usage switch defined
78 // that we can find.  System Control collections are meant to be consumed by the
79 // operating system, not user applications.
80 const uint8_t AcpiTbmcDevice::kHidDescriptor[] = {
81     0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
82     0x09, 0x80,        // Usage (Sys Control)
83     0xA1, 0x01,        // Collection (Application)
84     0x0B, 0x01, 0x00, 0x00, 0xFF,  //   Usage (0x0-FFFFFF) [Vendor Defined]
85     0x15, 0x00,        //   Logical Minimum (0)
86     0x25, 0x01,        //   Logical Maximum (1)
87     0x75, 0x01,        //   Report Size (1)
88     0x95, 0x01,        //   Report Count (1)
89     0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
90     0x75, 0x07,        //   Report Size (7)
91     0x95, 0x01,        //   Report Count (1)
92     0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
93     0xC0,              // End Collection
94 };
95 
96 const size_t AcpiTbmcDevice::kHidDescriptorLen = sizeof(AcpiTbmcDevice::kHidDescriptor);
97 
AcpiTbmcDevice(zx_device_t * parent,ACPI_HANDLE acpi_handle)98 AcpiTbmcDevice::AcpiTbmcDevice(zx_device_t* parent, ACPI_HANDLE acpi_handle)
99     : DeviceType(parent), acpi_handle_(acpi_handle) {
100 }
101 
~AcpiTbmcDevice()102 AcpiTbmcDevice::~AcpiTbmcDevice() {
103     AcpiRemoveNotifyHandler(acpi_handle_, ACPI_DEVICE_NOTIFY, NotifyHandler);
104 }
105 
CallTbmcMethod()106 zx_status_t AcpiTbmcDevice::CallTbmcMethod() {
107     ACPI_OBJECT obj = { };
108     ACPI_BUFFER buffer = {
109         .Length = sizeof(obj),
110         .Pointer = &obj,
111     };
112     ACPI_STATUS acpi_status = AcpiEvaluateObjectTyped(acpi_handle_, const_cast<char*>("TBMC"),
113                                                       nullptr, &buffer, ACPI_TYPE_INTEGER);
114     if (acpi_status != AE_OK) {
115         zxlogf(ERROR, "acpi-tbmc: TBMC failed: %d\n", acpi_status);
116         return acpi_to_zx_status(acpi_status);
117     }
118 
119     zxlogf(TRACE, "acpi-tbmc: TMBC returned 0x%llx\n", obj.Integer.Value);
120 
121     fbl::AutoLock guard(&lock_);
122 
123     bool old_mode = tablet_mode_;
124     tablet_mode_ = obj.Integer.Value;
125     if (tablet_mode_ != old_mode) {
126         zx_status_t status = QueueHidReportLocked();
127         if (status != ZX_OK) {
128             return status;
129         }
130     }
131 
132     return ZX_OK;
133 }
134 
NotifyHandler(ACPI_HANDLE handle,UINT32 value,void * ctx)135 void AcpiTbmcDevice::NotifyHandler(ACPI_HANDLE handle, UINT32 value, void* ctx) {
136     auto dev = reinterpret_cast<AcpiTbmcDevice*>(ctx);
137 
138     zxlogf(TRACE, "acpi-tbmc: got event 0x%x\n", value);
139     switch (value) {
140     case 0x80:
141         // Tablet mode has changed
142         dev->CallTbmcMethod();
143         break;
144     }
145 }
146 
QueueHidReportLocked()147 zx_status_t AcpiTbmcDevice::QueueHidReportLocked() {
148     if (client_.is_valid()) {
149         zxlogf(TRACE, "acpi-tbmc:  queueing report\n");
150         uint8_t report = tablet_mode_;
151         client_.IoQueue(&report, sizeof(report));
152     }
153     return ZX_OK;
154 }
155 
HidbusQuery(uint32_t options,hid_info_t * info)156 zx_status_t AcpiTbmcDevice::HidbusQuery(uint32_t options, hid_info_t* info) {
157     zxlogf(TRACE, "acpi-tbmc: hid bus query\n");
158 
159     info->dev_num = 0;
160     info->device_class = HID_DEVICE_CLASS_OTHER;
161     info->boot_device = false;
162     return ZX_OK;
163 }
164 
HidbusStart(const hidbus_ifc_t * ifc)165 zx_status_t AcpiTbmcDevice::HidbusStart(const hidbus_ifc_t* ifc) {
166     zxlogf(TRACE, "acpi-tbmc: hid bus start\n");
167 
168     fbl::AutoLock guard(&lock_);
169     if (client_.is_valid()) {
170         return ZX_ERR_ALREADY_BOUND;
171     }
172     client_ = ddk::HidbusIfcClient(ifc);
173     return ZX_OK;
174 }
175 
HidbusStop()176 void AcpiTbmcDevice::HidbusStop() {
177     zxlogf(TRACE, "acpi-tbmc: hid bus stop\n");
178 
179     fbl::AutoLock guard(&lock_);
180     client_.clear();
181 }
182 
HidbusGetDescriptor(uint8_t desc_type,void ** data,size_t * len)183 zx_status_t AcpiTbmcDevice::HidbusGetDescriptor(uint8_t desc_type, void** data, size_t* len) {
184     zxlogf(TRACE, "acpi-tbmc: hid bus get descriptor\n");
185 
186     if (data == nullptr || len == nullptr) {
187         return ZX_ERR_INVALID_ARGS;
188     }
189 
190     if (desc_type != HID_DESCRIPTION_TYPE_REPORT) {
191         return ZX_ERR_NOT_FOUND;
192     }
193 
194     *data = malloc(kHidDescriptorLen);
195     if (*data == nullptr) {
196         return ZX_ERR_NO_MEMORY;
197     }
198     *len = kHidDescriptorLen;
199     memcpy(*data, kHidDescriptor, kHidDescriptorLen);
200     return ZX_OK;
201 }
202 
HidbusGetReport(uint8_t rpt_type,uint8_t rpt_id,void * data,size_t len,size_t * out_len)203 zx_status_t AcpiTbmcDevice::HidbusGetReport(uint8_t rpt_type, uint8_t rpt_id, void* data,
204                                             size_t len, size_t* out_len) {
205     if (out_len == NULL) {
206         return ZX_ERR_INVALID_ARGS;
207     }
208 
209     if (rpt_type != HID_REPORT_TYPE_INPUT || rpt_id != 0) {
210         return ZX_ERR_NOT_FOUND;
211     }
212 
213     if (len < kHidReportLen) {
214         return ZX_ERR_BUFFER_TOO_SMALL;
215     }
216 
217     fbl::AutoLock guard(&lock_);
218     uint8_t report = tablet_mode_;
219     static_assert(sizeof(report) == kHidReportLen, "");
220     memcpy(data, &report, kHidReportLen);
221 
222     *out_len = kHidReportLen;
223     return ZX_OK;
224 }
225 
HidbusSetReport(uint8_t rpt_type,uint8_t rpt_id,const void * data,size_t len)226 zx_status_t AcpiTbmcDevice::HidbusSetReport(uint8_t rpt_type, uint8_t rpt_id, const void* data,
227                                             size_t len) {
228     return ZX_ERR_NOT_SUPPORTED;
229 }
230 
HidbusGetIdle(uint8_t rpt_id,uint8_t * duration)231 zx_status_t AcpiTbmcDevice::HidbusGetIdle(uint8_t rpt_id, uint8_t* duration) {
232     return ZX_ERR_NOT_SUPPORTED;
233 }
234 
HidbusSetIdle(uint8_t rpt_id,uint8_t duration)235 zx_status_t AcpiTbmcDevice::HidbusSetIdle(uint8_t rpt_id, uint8_t duration) {
236     return ZX_OK;
237 }
238 
HidbusGetProtocol(uint8_t * protocol)239 zx_status_t AcpiTbmcDevice::HidbusGetProtocol(uint8_t* protocol) {
240     return ZX_ERR_NOT_SUPPORTED;
241 }
242 
HidbusSetProtocol(uint8_t protocol)243 zx_status_t AcpiTbmcDevice::HidbusSetProtocol(uint8_t protocol) {
244     return ZX_OK;
245 }
246 
DdkRelease()247 void AcpiTbmcDevice::DdkRelease() {
248     zxlogf(INFO, "acpi-tbmc: release\n");
249     delete this;
250 }
251 
Create(zx_device_t * parent,ACPI_HANDLE acpi_handle,fbl::unique_ptr<AcpiTbmcDevice> * out)252 zx_status_t AcpiTbmcDevice::Create(zx_device_t* parent, ACPI_HANDLE acpi_handle,
253                                    fbl::unique_ptr<AcpiTbmcDevice>* out) {
254     fbl::AllocChecker ac;
255     fbl::unique_ptr<AcpiTbmcDevice> dev(new (&ac) AcpiTbmcDevice(parent, acpi_handle));
256     if (!ac.check()) {
257         return ZX_ERR_NO_MEMORY;
258     }
259 
260     // Initialize tracked state
261     dev->CallTbmcMethod();
262 
263     // Install acpi event handler
264     ACPI_STATUS acpi_status = AcpiInstallNotifyHandler(acpi_handle, ACPI_DEVICE_NOTIFY,
265                                                        NotifyHandler, dev.get());
266     if (acpi_status != AE_OK) {
267         zxlogf(ERROR, "acpi-tbmc: could not install notify handler\n");
268         return acpi_to_zx_status(acpi_status);
269     }
270 
271     *out = std::move(dev);
272     return ZX_OK;
273 }
274 
tbmc_init(zx_device_t * parent,ACPI_HANDLE acpi_handle)275 zx_status_t tbmc_init(zx_device_t* parent, ACPI_HANDLE acpi_handle) {
276     zxlogf(TRACE, "acpi-tbmc: init\n");
277 
278     fbl::unique_ptr<AcpiTbmcDevice> dev;
279     zx_status_t status = AcpiTbmcDevice::Create(parent, acpi_handle, &dev);
280     if (status != ZX_OK) {
281         return status;
282     }
283 
284     status = dev->DdkAdd("acpi-tbmc");
285     if (status != ZX_OK) {
286         return status;
287     }
288 
289     // devmgr is now in charge of the memory for dev
290     __UNUSED auto ptr = dev.release();
291 
292     zxlogf(INFO, "acpi-tbmc: initialized\n");
293     return ZX_OK;
294 }
295