blob: c55498d1085baff3fe09be3ca897831640b6449f [file] [log] [blame]
// 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 <machina/input.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fdio/watcher.h>
#include <hypervisor/bits.h>
#include <virtio/virtio_ids.h>
static const char* kInputDirPath = "/dev/class/input";
// HID keycode -> evdev keycode.
const uint8_t KeyboardEventSource::kKeyMap[] = {
0, // Reserved
0, // Keyboard ErrorRollOver
0, // Keyboard POSTFail
0, // Keyboard ErrorUndefined
30, // A
48, // B
46, // C
32, // D
18, // E
33, // F
34, // G
35, // H
23, // I
36, // J
37, // K
38, // L
50, // M
49, // N
24, // O
25, // P
16, // Q
19, // R
31, // S
20, // T
22, // U
47, // V
17, // W
45, // X
21, // Y
44, // Z
2, // 1
3, // 2
4, // 3
5, // 4
6, // 5
7, // 6
8, // 7
9, // 8
10, // 9
11, // 0
28, // Enter
1, // Esc
14, // Backspace
15, // Tab
57, // Space
12, // -
13, // =
26, // [
27, // ]
43, // Backslash
43, // Non-US # and ~
39, // ;
40, // '
41, // `
51, // ,
52, // .
53, // /
58, // Caps Lock
59, // F1
60, // F2
61, // F3
62, // F4
63, // F5
64, // F6
65, // F7
66, // F8
67, // F9
68, // F10
87, // F11
88, // F12
99, // Print Screen
70, // ScrollLock
119, // Pause
110, // Insert
102, // Home
104, // PageUp
111, // Delete Forward
107, // End
109, // PageDown
106, // Right
105, // Left
108, // Down
103, // Up
69, // NumLock
98, // Keypad /
55, // Keypad *
74, // Keypad -
78, // Keypad +
96, // Keypad Enter
79, // Keypad 1
80, // Keypad 2
81, // Keypad 3
75, // Keypad 4
76, // Keypad 5
77, // Keypad 6
71, // Keypad 7
72, // Keypad 8
73, // Keypad 9
82, // Keypad 0
83, // Keypad .
86, // Non-US \ and |
127, // Keyboard Application
116, // Power
117, // Keypad =
183, // F13
184, // F14
185, // F15
186, // F16
187, // F17
188, // F18
189, // F19
190, // F20
191, // F21
192, // F22
193, // F23
194, // F24
134, // Execute
138, // Help
130, // Menu
132, // Select
128, // Stop
129, // Again
131, // Undo
137, // Cut
133, // Copy
135, // Paste
136, // Find
113, // Mute
115, // Volume Up
114, // Volume Down
// Skip some more esoteric keys that have no obvious evdev counterparts.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
29, // Left Ctrl
42, // Left Shift
56, // Left Alt
125, // Left Meta
97, // Right Ctrl
54, // Right Shift
100, // Right Alt
126, // Right Meta
};
VirtioInput::VirtioInput(uintptr_t guest_physmem_addr, size_t guest_physmem_size,
const char* device_name, const char* device_serial)
: VirtioDevice(VIRTIO_ID_INPUT, &config_, sizeof(config_), queues_, VIRTIO_INPUT_Q_COUNT,
guest_physmem_addr, guest_physmem_size),
device_name_(device_name), device_serial_(device_serial) {}
zx_status_t VirtioInput::WriteConfig(uint64_t addr, const IoValue& value) {
zx_status_t status = VirtioDevice::WriteConfig(addr, value);
if (status != ZX_OK)
return status;
if (addr >= 2)
return ZX_OK;
// A write to select or subselect modifies the contents of the config.u
// field.
fbl::AutoLock lock(&config_mutex_);
switch (config_.select) {
case VIRTIO_INPUT_CFG_ID_NAME: {
size_t len = strlen(device_name_);
memcpy(&config_.u, device_name_, len);
config_.size = static_cast<uint8_t>(len > sizeof(config_.u) ? sizeof(config_.u) : len);
return ZX_OK;
}
case VIRTIO_INPUT_CFG_ID_SERIAL: {
size_t len = strlen(device_serial_);
config_.size = static_cast<uint8_t>(len > sizeof(config_.u) ? sizeof(config_.u) : len);
memcpy(&config_.u, device_serial_, len);
return ZX_OK;
}
// VIRTIO_INPUT_CFG_EV_BITS: subsel specifies the event type (EV_*).
// If size is non-zero the event type is supported and a bitmap the of
// supported event codes is returned in u.bitmap.
case VIRTIO_INPUT_CFG_EV_BITS: {
if (config_.subsel == VIRTIO_INPUT_EV_KEY) {
// Say we support all key events. This isn't true but it's
// simple.
memset(&config_.u, 0xff, sizeof(config_.u));
config_.size = sizeof(config_.u);
return ZX_OK;
}
// Fall-through.
}
case VIRTIO_INPUT_CFG_UNSET:
case VIRTIO_INPUT_CFG_ID_DEVIDS:
case VIRTIO_INPUT_CFG_PROP_BITS:
case VIRTIO_INPUT_CFG_ABS_INFO:
memset(&config_.u, 0, sizeof(config_.u));
config_.size = 0;
return ZX_OK;
default:
fprintf(stderr, "unsupported select value %u\n", config_.select);
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
static int watch_input_directory_thread(void* input) {
int dirfd = open(kInputDirPath, O_DIRECTORY | O_RDONLY);
if (dirfd < 0)
return ZX_ERR_IO;
zx_status_t status = fdio_watch_directory(dirfd, &VirtioInput::AddInputDevice,
ZX_TIME_INFINITE, input);
close(dirfd);
return status;
}
zx_status_t VirtioInput::Start() {
thrd_t thread;
int ret = thrd_create(&thread, &watch_input_directory_thread, this);
if (ret != thrd_success) {
return ZX_ERR_INTERNAL;
}
ret = thrd_detach(thread);
if (ret != thrd_success) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
zx_status_t VirtioInput::QueueInputEvent(const virtio_input_event_t& event) {
uint16_t head;
virtio_queue_wait(&event_queue(), &head);
virtio_desc_t desc;
zx_status_t status = virtio_queue_read_desc(&event_queue(), head, &desc);
if (status != ZX_OK)
return status;
auto event_out = static_cast<virtio_input_event_t*>(desc.addr);
memcpy(event_out, &event, sizeof(event));
virtio_queue_return(&event_queue(), head, sizeof(event));
return ZX_OK;
}
// static
zx_status_t VirtioInput::AddInputDevice(int dirfd, int event, const char* fn, void* cookie) {
auto input = static_cast<VirtioInput*>(cookie);
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
int fd = openat(dirfd, fn, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open device %s/%s\n", kInputDirPath, fn);
return ZX_OK;
}
auto closer = fbl::MakeAutoCall([fd]() { close(fd); });
int proto = INPUT_PROTO_NONE;
if (ioctl_input_get_protocol(fd, &proto) < 0) {
fprintf(stderr, "Failed to get input device protocol.\n");
return ZX_ERR_INVALID_ARGS;
}
// If the device isn't a keyboard, just continue.
if (proto != INPUT_PROTO_KBD)
return ZX_OK;
fbl::AllocChecker ac;
auto keyboard = fbl::make_unique_checked<KeyboardEventSource>(&ac, input, fd);
if (!ac.check())
return ZX_ERR_NO_MEMORY;
zx_status_t status = keyboard->Start();
if (status != ZX_OK) {
fprintf(stderr, "Failed to start device %s/%s\n", kInputDirPath, fn);
return status;
}
fprintf(stderr, "virtio-input: Polling device %s/%s for key events.\n", kInputDirPath, fn);
closer.cancel();
fbl::AutoLock lock(&input->mutex_);
input->keyboards_.push_front(fbl::move(keyboard));
return ZX_OK;
}
KeyboardEventSource::~KeyboardEventSource() {
if (fd_ >= 0)
close(fd_);
}
static int hid_event_thread(void* cookie) {
return reinterpret_cast<KeyboardEventSource*>(cookie)->HidEventLoop();
}
zx_status_t KeyboardEventSource::Start() {
thrd_t thread;
int ret = thrd_create(&thread, &hid_event_thread, this);
if (ret != thrd_success) {
return ZX_ERR_INTERNAL;
}
ret = thrd_detach(thread);
if (ret != thrd_success) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
zx_status_t KeyboardEventSource::HidEventLoop() {
uint8_t report[8];
while (true) {
ssize_t r = read(fd_, report, sizeof(report));
if (r != sizeof(report)) {
fprintf(stderr, "failed to read from input device\n");
return ZX_ERR_IO;
}
hid_keys_t curr_keys;
hid_kbd_parse_report(report, &curr_keys);
zx_status_t status = HandleHidKeys(curr_keys);
if (status != ZX_OK) {
fprintf(stderr, "Failed to handle HID keys.\n");
return status;
}
}
return ZX_OK;
}
zx_status_t KeyboardEventSource::HandleHidKeys(const hid_keys_t& curr_keys) {
// Send key-down events.
uint8_t keycode;
hid_keys_t pressed;
hid_kbd_pressed_keys(&prev_keys_, &curr_keys, &pressed);
hid_for_every_key(&pressed, keycode) {
zx_status_t status = SendKeyEvent(keycode, true);
if (status != ZX_OK)
return status;
}
// Send key-up events.
hid_keys_t released;
hid_kbd_released_keys(&prev_keys_, &curr_keys, &released);
hid_for_every_key(&released, keycode) {
zx_status_t status = SendKeyEvent(keycode, false);
if (status != ZX_OK)
return status;
}
prev_keys_ = curr_keys;
return SendBarrierEvent();
}
zx_status_t KeyboardEventSource::SendKeyEvent(uint32_t scancode, bool pressed) {
if (scancode >= sizeof(kKeyMap) / sizeof(kKeyMap[0])) {
// Unknown key.
return ZX_OK;
}
virtio_input_event_t event;
event.type = VIRTIO_INPUT_EV_KEY;
event.code = kKeyMap[scancode];
event.value = pressed ? VIRTIO_INPUT_EV_KEY_PRESSED : VIRTIO_INPUT_EV_KEY_RELEASED;
return emitter_->QueueInputEvent(event);
}
zx_status_t KeyboardEventSource::SendBarrierEvent() {
virtio_input_event_t event;
event.type = VIRTIO_INPUT_EV_SYN;
event.code = 0;
event.value = 0;
zx_status_t status = emitter_->QueueInputEvent(event);
if (status != ZX_OK)
return status;
return emitter_->FlushInputEvents();
}