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 <errno.h>
6#include <fcntl.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#include <fbl/alloc_checker.h>
12#include <fbl/array.h>
13#include <fbl/auto_call.h>
14#include <fbl/unique_fd.h>
15#include <hid-parser/parser.h>
16#include <hid-parser/usages.h>
17#include <lib/fdio/util.h>
18#include <lib/fdio/watcher.h>
19#include <zircon/device/device.h>
20#include <zircon/device/input.h>
21#include <zircon/processargs.h>
22#include <zircon/status.h>
23#include <zircon/syscalls.h>
24
25#define MAX_DESC_LEN 1024
26
27#define INPUT_PATH "/input"
28#define DMCTL_PATH "/misc/dmctl"
29
30namespace {
31
32bool usage_eq(const hid::Usage& u1, const hid::Usage& u2) {
33    return u1.page == u2.page && u1.usage == u2.usage;
34}
35
36// Search the report descriptor for a System Power Down input field within a
37// Generic Desktop:System Control collection.
38//
39// This method assumes the HID descriptor does not contain more than one such field.
40zx_status_t FindSystemPowerDown(const hid::DeviceDescriptor* desc,
41                                uint8_t* report_id, size_t* bit_offset) {
42
43    const hid::Usage system_control = {
44        .page = static_cast<uint16_t>(hid::usage::Page::kGenericDesktop),
45        .usage = static_cast<uint32_t>(hid::usage::GenericDesktop::kSystemControl),
46    };
47
48    const hid::Usage power_down = {
49        .page = static_cast<uint16_t>(hid::usage::Page::kGenericDesktop),
50        .usage = static_cast<uint32_t>(hid::usage::GenericDesktop::kSystemPowerDown),
51    };
52
53    // Search for the field
54    bool found = false;
55    for (size_t rpt_idx = 0; rpt_idx < desc->rep_count && !found; ++rpt_idx) {
56        const hid::ReportDescriptor& report = desc->report[rpt_idx];
57        for (size_t i = 0; i < report.count; ++i) {
58            const hid::ReportField& field = report.first_field[i];
59            if (field.type != hid::kInput || !usage_eq(field.attr.usage, power_down)) {
60                continue;
61            }
62
63            const hid::Collection* collection = hid::GetAppCollection(&field);
64            if (!collection || !usage_eq(collection->usage, system_control)) {
65                continue;
66            }
67
68            found = true;
69            *report_id = field.report_id;
70            break;
71        }
72    }
73
74    if (!found) {
75        return ZX_ERR_NOT_FOUND;
76    }
77
78    // Compute the offset of the field.  Since reports may be discontinuous, we
79    // have to search from the beginning.
80    *bit_offset = 0;
81    for (size_t rpt_idx = 0; rpt_idx < desc->rep_count; ++rpt_idx) {
82        const hid::ReportDescriptor& report = desc->report[rpt_idx];
83        if (report.report_id != *report_id) {
84            continue;
85        }
86
87        for (size_t i = 0; i < report.count; ++i) {
88            const hid::ReportField& field = report.first_field[i];
89            if (field.type != hid::kInput) {
90                continue;
91            }
92
93            *bit_offset += field.attr.bit_sz;
94
95            // Check if we found the field again, and if so return.
96            if (!usage_eq(field.attr.usage, power_down)) {
97                continue;
98            }
99            const hid::Collection* collection = hid::GetAppCollection(&field);
100            if (!collection || !usage_eq(collection->usage, system_control)) {
101                continue;
102            }
103
104            // Subtract out the field, since we want its start not its end.
105            *bit_offset -= field.attr.bit_sz;
106            return ZX_OK;
107        }
108    }
109
110    // Should be unreachable
111    return ZX_ERR_INTERNAL;
112}
113
114struct PowerButtonInfo {
115    fbl::unique_fd fd;
116    uint8_t report_id;
117    size_t bit_offset;
118    bool has_report_id_byte;
119};
120
121static zx_status_t InputDeviceAdded(int dirfd, int event, const char* name, void* cookie) {
122    if (event != WATCH_EVENT_ADD_FILE) {
123        return ZX_OK;
124    }
125
126    fbl::unique_fd fd;
127    {
128        int raw_fd;
129        if ((raw_fd = openat(dirfd, name, O_RDWR)) < 0) {
130            return ZX_OK;
131        }
132        fd.reset(raw_fd);
133    }
134
135    // Retrieve and parse the report descriptor
136    size_t desc_len = 0;
137    if (ioctl_input_get_report_desc_size(fd.get(), &desc_len) < 0) {
138        return ZX_OK;
139    }
140    if (desc_len > MAX_DESC_LEN) {
141        return ZX_OK;
142    }
143
144    fbl::AllocChecker ac;
145    fbl::Array<uint8_t> raw_desc(new (&ac) uint8_t[desc_len](), desc_len);
146    if (!ac.check()) {
147        return ZX_OK;
148    }
149    if (ioctl_input_get_report_desc(fd.get(), raw_desc.get(), raw_desc.size()) < 0) {
150        return ZX_OK;
151    }
152
153    hid::DeviceDescriptor* desc;
154    if (hid::ParseReportDescriptor(raw_desc.get(), raw_desc.size(), &desc) != hid::kParseOk) {
155        return ZX_OK;
156    }
157    auto cleanup_desc = fbl::MakeAutoCall([desc]() { hid::FreeDeviceDescriptor(desc); });
158
159    uint8_t report_id;
160    size_t bit_offset;
161    zx_status_t status = FindSystemPowerDown(desc, &report_id, &bit_offset);
162    if (status != ZX_OK) {
163        return ZX_OK;
164    }
165
166    auto info = reinterpret_cast<PowerButtonInfo*>(cookie);
167    info->fd = fbl::move(fd);
168    info->report_id = report_id;
169    info->bit_offset = bit_offset;
170    info->has_report_id_byte = (desc->rep_count > 1 || desc->report[0].report_id != 0);
171    return ZX_ERR_STOP;
172}
173
174} // namespace
175
176int main(int argc, char**argv) {
177    fbl::unique_fd dirfd;
178    {
179        int fd = open(INPUT_PATH, O_DIRECTORY | O_RDONLY);
180        if (fd < 0) {
181            printf("pwrbtn-monitor: Failed to open " INPUT_PATH ": %d\n", errno);
182            return 1;
183        }
184        dirfd.reset(fd);
185    }
186
187    PowerButtonInfo info;
188    zx_status_t status = fdio_watch_directory(dirfd.get(), InputDeviceAdded, ZX_TIME_INFINITE, &info);
189    if (status != ZX_ERR_STOP) {
190        printf("pwrbtn-monitor: Failed to find power button device\n");
191        return 1;
192    }
193    dirfd.reset();
194
195    input_report_size_t report_size = 0;
196    if (ioctl_input_get_max_reportsize(info.fd.get(), &report_size) < 0) {
197        printf("pwrbtn-monitor: Failed to to get max report size\n");
198        return 1;
199    }
200
201    // Double-check the size looks right
202    const size_t byte_index = info.has_report_id_byte + info.bit_offset / 8;
203    if (report_size <= byte_index) {
204        printf("pwrbtn-monitor: Suspicious looking max report size\n");
205        return 1;
206    }
207
208    fbl::AllocChecker ac;
209    fbl::Array<uint8_t> report(new (&ac) uint8_t[report_size](), report_size);
210    if (!ac.check()) {
211        return 1;
212    }
213
214    // Watch the power button device for reports
215    while (true) {
216        ssize_t r = read(info.fd.get(), report.get(), report.size());
217        if (r < 0) {
218            printf("pwrbtn-monitor: got read error %zd, bailing\n", r);
219            return 1;
220        }
221
222        // Ignore reports from different report IDs
223        if (info.has_report_id_byte && report[0] != info.report_id) {
224            printf("pwrbtn-monitor: input-watcher: wrong id\n");
225            continue;
226        }
227
228        if (static_cast<size_t>(r) <= byte_index) {
229            printf("pwrbtn-monitor: input-watcher: too short\n");
230            continue;
231        }
232
233        // Check if the power button is pressed, and request a poweroff if so.
234        if (report[byte_index] & (1u << (info.bit_offset % 8))) {
235            int fd = open(DMCTL_PATH, O_WRONLY);
236            if (fd < 0) {
237                printf("pwrbtn-monitor: input-watcher: failed to open dmctl\n");
238                continue;
239            }
240            write(fd, "poweroff", strlen("poweroff"));
241            close(fd);
242        }
243    }
244}
245