Added VirtIO Input Device base class and keyboard device implementation.

This commit is contained in:
Florian Nücke
2020-09-24 17:53:20 +02:00
parent 78ec00379e
commit ada89fdddb
4 changed files with 539 additions and 0 deletions

View File

@@ -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<ByteBuffer> 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;
}
}

View File

@@ -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());
}
}

View File

@@ -0,0 +1,58 @@
package li.cil.circuity.vm.evdev;
/**
* Linux evdev <a href="https://www.kernel.org/doc/html/latest/input/event-codes.html#input-event-codes">input event types</a>.
* <p>
* 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;
}

View File

@@ -0,0 +1,238 @@
package li.cil.circuity.vm.evdev;
/**
* Linux evdev input keycodes.
* <p>
* 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,
};
}