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