From ada89fdddbef54b59f55bcd36b054e9b02594df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Thu, 24 Sep 2020 17:53:20 +0200 Subject: [PATCH] Added VirtIO Input Device base class and keyboard device implementation. --- .../virtio/AbstractVirtIOInputDevice.java | 189 ++++++++++++++ .../device/virtio/VirtIOKeyboardDevice.java | 54 ++++ .../li/cil/circuity/vm/evdev/EvdevEvents.java | 58 +++++ .../li/cil/circuity/vm/evdev/EvdevKeys.java | 238 ++++++++++++++++++ 4 files changed, 539 insertions(+) create mode 100644 src/main/java/li/cil/circuity/vm/device/virtio/AbstractVirtIOInputDevice.java create mode 100644 src/main/java/li/cil/circuity/vm/device/virtio/VirtIOKeyboardDevice.java create mode 100644 src/main/java/li/cil/circuity/vm/evdev/EvdevEvents.java create mode 100644 src/main/java/li/cil/circuity/vm/evdev/EvdevKeys.java diff --git a/src/main/java/li/cil/circuity/vm/device/virtio/AbstractVirtIOInputDevice.java b/src/main/java/li/cil/circuity/vm/device/virtio/AbstractVirtIOInputDevice.java new file mode 100644 index 00000000..8a04ce98 --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/virtio/AbstractVirtIOInputDevice.java @@ -0,0 +1,189 @@ +package li.cil.circuity.vm.device.virtio; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.api.vm.device.memory.MemoryAccessException; +import li.cil.circuity.api.vm.device.memory.Sizes; +import li.cil.circuity.vm.evdev.EvdevEvents; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public abstract class AbstractVirtIOInputDevice extends AbstractVirtIODevice { + protected static final int VIRTIO_INPUT_CFG_SELECT_UNSET = 0x00; + protected static final int VIRTIO_INPUT_CFG_SELECT_ID_NAME = 0x01; + protected static final int VIRTIO_INPUT_CFG_SELECT_ID_SERIAL = 0x02; + protected static final int VIRTIO_INPUT_CFG_SELECT_ID_DEVIDS = 0x03; + protected static final int VIRTIO_INPUT_CFG_SELECT_PROP_BITS = 0x10; + protected static final int VIRTIO_INPUT_CFG_SELECT_EV_BITS = 0x11; + protected static final int VIRTIO_INPUT_CFG_SELECT_ABS_INFO = 0x12; + + // Config looks like this: + // struct virtio_input_config { + // u8 select; + // u8 subsel; + // u8 size; + // u8 reserved[5]; + // union { + // char string[128]; + // u8 bitmap[128]; + // struct virtio_input_absinfo abs; + // struct virtio_input_devids ids; + // } u; + // }; + private static final int VIRTIO_INPUT_CFG_SELECT_OFFSET = 0x0; + private static final int VIRTIO_INPUT_CFG_SUBSEL_OFFSET = 0x1; + private static final int VIRTIO_INPUT_CFG_SIZE_OFFSET = 0x2; + private static final int VIRTIO_INPUT_CFG_UNION_OFFSET = 0x8; + private static final int VIRTIO_INPUT_CFG_UNION_SIZE = 128; + + private static final int VIRTQ_EVENT = 0; + private static final int VIRTQ_STATUS = 1; + + private static final ThreadLocal eventBuffer = new ThreadLocal<>(); + + // This holds the union with info in the config struct. We generate this whenever the state + // changes and keep it for more efficient access (and fewer headaches). + private final ByteBuffer config = ByteBuffer.allocate(VIRTIO_INPUT_CFG_UNION_SIZE); + private boolean configDirty = true; + + private DescriptorChain event; + + protected AbstractVirtIOInputDevice(final MemoryMap memoryMap) { + super(memoryMap, VirtIODeviceSpec.builder(VirtIODeviceType.VIRTIO_DEVICE_ID_INPUT_DEVICE) + // We only physically use 2 bytes of config space, but this is used for getLength(), too. + .configSpaceSize(256) + .queueCount(2) + .build()); + } + + protected abstract void generateConfigUnion(final int select, final int subsel, final ByteBuffer config); + + protected void handleStatus(final int type, final int code, final int value) { + } + + protected final void putEvent(final int type, final int code, final int value) { + if ((getStatus() & VIRTIO_STATUS_FAILED) != 0) { + return; + } + + try { + // 5.8.6.1: These buffers [in the eventq] MUST be device-writable [...] + event = validateWriteOnlyDescriptorChain(VIRTQ_EVENT, event); + if (event != null) { + // 5.8.6.1: [eventq buffers] MUST be at least the size of struct virtio_input_event. + if (event.writableBytes() < 8) { + error(); + return; + } + + // VirtIO Input Events look like this: + // struct virtio_input_event { + // le16 type; + // le16 code; + // le32 value; + // }; + final ByteBuffer buffer = getTempBuffer(); + buffer.putShort((short) type); + buffer.putShort((short) code); + buffer.putInt(value); + + buffer.flip(); + event.put(buffer); + event.use(); + } + } catch (final VirtIODeviceException | MemoryAccessException e) { + error(); + } + } + + protected final void putSyn() { + putEvent(EvdevEvents.EV_SYN, 0, 0); + } + + @Override + protected final int loadConfig(final int offset, final int sizeLog2) { + if (sizeLog2 != Sizes.SIZE_8_LOG2) { + return 0; + } + + if (offset == VIRTIO_INPUT_CFG_SIZE_OFFSET) { + validateConfig(); + return config.limit(); + } else if (offset >= VIRTIO_INPUT_CFG_UNION_OFFSET && offset < VIRTIO_INPUT_CFG_UNION_OFFSET + VIRTIO_INPUT_CFG_UNION_SIZE) { + validateConfig(); + return config.get(offset - VIRTIO_INPUT_CFG_UNION_OFFSET); + } + + return super.loadConfig(offset, sizeLog2); + } + + @Override + protected final void storeConfig(final int offset, final int value, final int sizeLog2) { + if (offset > VIRTIO_INPUT_CFG_SUBSEL_OFFSET) { + return; // Only select and subsel are writable by the driver. + } + + super.storeConfig(offset, value, sizeLog2); + configDirty = true; + } + + @Override + protected final void handleFeaturesNegotiated() { + setQueueNotifications(VIRTQ_EVENT, false); + } + + @Override + protected final void handleQueueNotification(final int queueIndex) throws VirtIODeviceException, MemoryAccessException { + if (queueIndex == VIRTQ_STATUS) { + final VirtqueueIterator queue = getQueueIterator(queueIndex); + if (queue != null && queue.hasNext()) { + while (queue.hasNext()) { + final DescriptorChain chain = queue.next(); + while (chain.readableBytes() >= 8) { + final ByteBuffer buffer = getTempBuffer(); + chain.get(buffer); + buffer.flip(); + + final short type = buffer.getShort(); + final short code = buffer.getShort(); + final int value = buffer.getInt(); + handleStatus(type, code, value); + } + chain.use(); + } + } + } + } + + private void validateConfig() { + if (!configDirty) { + return; + } + + final ByteBuffer configSpace = getConfiguration(); + final int select = configSpace.get(VIRTIO_INPUT_CFG_SELECT_OFFSET) & 0xFF; + final int subsel = configSpace.get(VIRTIO_INPUT_CFG_SUBSEL_OFFSET) & 0xFF; + + if (select == VIRTIO_INPUT_CFG_SELECT_UNSET) { + config.limit(0); + } else { + config.clear(); + generateConfigUnion(select, subsel, config); + } + + config.position(0); + + configDirty = false; + } + + private static ByteBuffer getTempBuffer() { + ByteBuffer buffer = eventBuffer.get(); + if (buffer == null) { + buffer = ByteBuffer.allocate(8); + buffer.order(ByteOrder.LITTLE_ENDIAN); + eventBuffer.set(buffer); + } + buffer.clear(); + return buffer; + } +} diff --git a/src/main/java/li/cil/circuity/vm/device/virtio/VirtIOKeyboardDevice.java b/src/main/java/li/cil/circuity/vm/device/virtio/VirtIOKeyboardDevice.java new file mode 100644 index 00000000..bc30d68d --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/device/virtio/VirtIOKeyboardDevice.java @@ -0,0 +1,54 @@ +package li.cil.circuity.vm.device.virtio; + +import li.cil.circuity.api.vm.MemoryMap; +import li.cil.circuity.vm.evdev.EvdevEvents; +import li.cil.circuity.vm.evdev.EvdevKeys; + +import java.nio.ByteBuffer; +import java.util.BitSet; + +public final class VirtIOKeyboardDevice extends AbstractVirtIOInputDevice { + private static final String NAME = "virtio_keyboard"; + + public VirtIOKeyboardDevice(final MemoryMap memoryMap) { + super(memoryMap); + } + + public void putKey(final int keycode, final boolean isDown) { + putEvent(EvdevEvents.EV_KEY, keycode, isDown ? 1 : 0); + putSyn(); + } + + @Override + protected void generateConfigUnion(final int select, final int subsel, final ByteBuffer config) { + switch (select) { + case VIRTIO_INPUT_CFG_SELECT_ID_NAME: { + final char[] chars = NAME.toCharArray(); + for (final char ch : chars) { + config.put((byte) ch); + } + break; + } + case VIRTIO_INPUT_CFG_SELECT_EV_BITS: { + switch (subsel) { + case EvdevEvents.EV_KEY: { + final BitSet bitmap = new BitSet(); + for (final int keycode : EvdevKeys.ALL_KEYS) { + bitmap.set(keycode); + } + final byte[] maskBytes = bitmap.toByteArray(); + config.put(maskBytes); + break; + } + case EvdevEvents.EV_REP: { + config.put((byte) 0); + break; + } + } + break; + } + } + + config.limit(config.position()); + } +} diff --git a/src/main/java/li/cil/circuity/vm/evdev/EvdevEvents.java b/src/main/java/li/cil/circuity/vm/evdev/EvdevEvents.java new file mode 100644 index 00000000..ecec08ca --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/evdev/EvdevEvents.java @@ -0,0 +1,58 @@ +package li.cil.circuity.vm.evdev; + +/** + * Linux evdev input event types. + *

+ * Numeric values from Linux kernel: include/uapi/linux/input-event-codes.h + */ +public final class EvdevEvents { + /** + * Used as markers to separate events. Events may be separated in time or in space, + * such as with the multitouch protocol. + */ + public static final int EV_SYN = 0x00; + /** + * Used to describe state changes of keyboards, buttons, or other key-like devices. + */ + public static final int EV_KEY = 0x01; + /** + * Used to describe relative axis value changes, e.g. moving the mouse 5 units to the left. + */ + public static final int EV_REL = 0x02; + /** + * Used to describe absolute axis value changes, e.g. describing the coordinates of a touch on a touchscreen. + */ + public static final int EV_ABS = 0x03; + /** + * Used to describe miscellaneous input data that do not fit into other types. + */ + public static final int EV_MSC = 0x04; + /** + * Used to describe binary state input switches. + */ + public static final int EV_SW = 0x05; + /** + * Used to turn LEDs on devices on and off. + */ + public static final int EV_LED = 0x11; + /** + * Used to output sound to devices. + */ + public static final int EV_SND = 0x12; + /** + * Used for autorepeating devices. + */ + public static final int EV_REP = 0x14; + /** + * Used to send force feedback commands to an input device. + */ + public static final int EV_FF = 0x15; + /** + * A special type for power button and switch input. + */ + public static final int EV_PWR = 0x16; + /** + * Used to receive force feedback device status. + */ + public static final int EV_FF_STATUS = 0x17; +} diff --git a/src/main/java/li/cil/circuity/vm/evdev/EvdevKeys.java b/src/main/java/li/cil/circuity/vm/evdev/EvdevKeys.java new file mode 100644 index 00000000..2dc6fb3c --- /dev/null +++ b/src/main/java/li/cil/circuity/vm/evdev/EvdevKeys.java @@ -0,0 +1,238 @@ +package li.cil.circuity.vm.evdev; + +/** + * Linux evdev input keycodes. + *

+ * Numeric values from Linux kernel: include/uapi/linux/input-event-codes.h + */ +public final class EvdevKeys { + public static final int KEY_ESC = 1; + public static final int KEY_1 = 2; + public static final int KEY_2 = 3; + public static final int KEY_3 = 4; + public static final int KEY_4 = 5; + public static final int KEY_5 = 6; + public static final int KEY_6 = 7; + public static final int KEY_7 = 8; + public static final int KEY_8 = 9; + public static final int KEY_9 = 10; + public static final int KEY_0 = 11; + public static final int KEY_MINUS = 12; + public static final int KEY_EQUAL = 13; + public static final int KEY_BACKSPACE = 14; + public static final int KEY_TAB = 15; + public static final int KEY_Q = 16; + public static final int KEY_W = 17; + public static final int KEY_E = 18; + public static final int KEY_R = 19; + public static final int KEY_T = 20; + public static final int KEY_Y = 21; + public static final int KEY_U = 22; + public static final int KEY_I = 23; + public static final int KEY_O = 24; + public static final int KEY_P = 25; + public static final int KEY_LEFTBRACE = 26; + public static final int KEY_RIGHTBRACE = 27; + public static final int KEY_ENTER = 28; + public static final int KEY_LEFTCTRL = 29; + public static final int KEY_A = 30; + public static final int KEY_S = 31; + public static final int KEY_D = 32; + public static final int KEY_F = 33; + public static final int KEY_G = 34; + public static final int KEY_H = 35; + public static final int KEY_J = 36; + public static final int KEY_K = 37; + public static final int KEY_L = 38; + public static final int KEY_SEMICOLON = 39; + public static final int KEY_APOSTROPHE = 40; + public static final int KEY_GRAVE = 41; + public static final int KEY_LEFTSHIFT = 42; + public static final int KEY_BACKSLASH = 43; + public static final int KEY_Z = 44; + public static final int KEY_X = 45; + public static final int KEY_C = 46; + public static final int KEY_V = 47; + public static final int KEY_B = 48; + public static final int KEY_N = 49; + public static final int KEY_M = 50; + public static final int KEY_COMMA = 51; + public static final int KEY_DOT = 52; + public static final int KEY_SLASH = 53; + public static final int KEY_RIGHTSHIFT = 54; + public static final int KEY_KPASTERISK = 55; + public static final int KEY_LEFTALT = 56; + public static final int KEY_SPACE = 57; + public static final int KEY_CAPSLOCK = 58; + public static final int KEY_F1 = 59; + public static final int KEY_F2 = 60; + public static final int KEY_F3 = 61; + public static final int KEY_F4 = 62; + public static final int KEY_F5 = 63; + public static final int KEY_F6 = 64; + public static final int KEY_F7 = 65; + public static final int KEY_F8 = 66; + public static final int KEY_F9 = 67; + public static final int KEY_F10 = 68; + public static final int KEY_NUMLOCK = 69; + public static final int KEY_SCROLLLOCK = 70; + public static final int KEY_KP7 = 71; + public static final int KEY_KP8 = 72; + public static final int KEY_KP9 = 73; + public static final int KEY_KPMINUS = 74; + public static final int KEY_KP4 = 75; + public static final int KEY_KP5 = 76; + public static final int KEY_KP6 = 77; + public static final int KEY_KPPLUS = 78; + public static final int KEY_KP1 = 79; + public static final int KEY_KP2 = 80; + public static final int KEY_KP3 = 81; + public static final int KEY_KP0 = 82; + public static final int KEY_KPDOT = 83; + public static final int KEY_F11 = 87; + public static final int KEY_F12 = 88; + public static final int KEY_KPENTER = 96; + public static final int KEY_RIGHTCTRL = 97; + public static final int KEY_KPSLASH = 98; + public static final int KEY_RIGHTALT = 100; + public static final int KEY_HOME = 102; + public static final int KEY_UP = 103; + public static final int KEY_PAGEUP = 104; + public static final int KEY_LEFT = 105; + public static final int KEY_RIGHT = 106; + public static final int KEY_END = 107; + public static final int KEY_DOWN = 108; + public static final int KEY_PAGEDOWN = 109; + public static final int KEY_INSERT = 110; + public static final int KEY_DELETE = 111; + public static final int KEY_KPEQUAL = 117; + public static final int KEY_PAUSE = 119; + public static final int KEY_F13 = 183; + public static final int KEY_F14 = 184; + public static final int KEY_F15 = 185; + public static final int KEY_F16 = 186; + public static final int KEY_F17 = 187; + public static final int KEY_F18 = 188; + public static final int KEY_F19 = 189; + public static final int KEY_F20 = 190; + public static final int KEY_F21 = 191; + public static final int KEY_F22 = 192; + public static final int KEY_F23 = 193; + public static final int KEY_F24 = 194; + + public static final int[] ALL_KEYS = { + KEY_ESC, + KEY_1, + KEY_2, + KEY_3, + KEY_4, + KEY_5, + KEY_6, + KEY_7, + KEY_8, + KEY_9, + KEY_0, + KEY_MINUS, + KEY_EQUAL, + KEY_BACKSPACE, + KEY_TAB, + KEY_Q, + KEY_W, + KEY_E, + KEY_R, + KEY_T, + KEY_Y, + KEY_U, + KEY_I, + KEY_O, + KEY_P, + KEY_LEFTBRACE, + KEY_RIGHTBRACE, + KEY_ENTER, + KEY_LEFTCTRL, + KEY_A, + KEY_S, + KEY_D, + KEY_F, + KEY_G, + KEY_H, + KEY_J, + KEY_K, + KEY_L, + KEY_SEMICOLON, + KEY_APOSTROPHE, + KEY_GRAVE, + KEY_LEFTSHIFT, + KEY_BACKSLASH, + KEY_Z, + KEY_X, + KEY_C, + KEY_V, + KEY_B, + KEY_N, + KEY_M, + KEY_COMMA, + KEY_DOT, + KEY_SLASH, + KEY_RIGHTSHIFT, + KEY_KPASTERISK, + KEY_LEFTALT, + KEY_SPACE, + KEY_CAPSLOCK, + KEY_F1, + KEY_F2, + KEY_F3, + KEY_F4, + KEY_F5, + KEY_F6, + KEY_F7, + KEY_F8, + KEY_F9, + KEY_F10, + KEY_NUMLOCK, + KEY_SCROLLLOCK, + KEY_KP7, + KEY_KP8, + KEY_KP9, + KEY_KPMINUS, + KEY_KP4, + KEY_KP5, + KEY_KP6, + KEY_KPPLUS, + KEY_KP1, + KEY_KP2, + KEY_KP3, + KEY_KP0, + KEY_KPDOT, + KEY_F11, + KEY_F12, + KEY_KPENTER, + KEY_RIGHTCTRL, + KEY_KPSLASH, + KEY_RIGHTALT, + KEY_HOME, + KEY_UP, + KEY_PAGEUP, + KEY_LEFT, + KEY_RIGHT, + KEY_END, + KEY_DOWN, + KEY_PAGEDOWN, + KEY_INSERT, + KEY_DELETE, + KEY_KPEQUAL, + KEY_PAUSE, + KEY_F13, + KEY_F14, + KEY_F15, + KEY_F16, + KEY_F17, + KEY_F18, + KEY_F19, + KEY_F20, + KEY_F21, + KEY_F22, + KEY_F23, + KEY_F24, + }; +}