diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 627525b..97bd5cd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: sudo apt-get install build-essential device-tree-compiler sudo apt-get install expect - name: default build - run: make + run: make ENABLE_SDL=0 shell: bash - name: automated test run: .ci/autorun.sh diff --git a/Makefile b/Makefile index ceb397f..51a895d 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ include mk/common.mk CC ?= gcc CFLAGS := -O2 -g -Wall -Wextra CFLAGS += -include common.h +LDFLAGS := # clock frequency CLOCK_FREQ ?= 65000000 @@ -13,6 +14,8 @@ OBJS_EXTRA := # command line option OPTS := +LDFLAGS += -lpthread + # virtio-blk ENABLE_VIRTIOBLK ?= 1 $(call set-feature, VIRTIOBLK) @@ -41,6 +44,52 @@ ifeq ($(call has, VIRTIONET), 1) OBJS_EXTRA += virtio-net.o endif +# virtio-input +ENABLE_VIRTIOINPUT ?= 1 +ifneq ($(UNAME_S),Linux) + ENABLE_VIRTIOINPUT := 0 +endif +$(call set-feature, VIRTIOINPUT) +ifeq ($(call has, VIRTIOINPUT), 1) + OBJS_EXTRA += virtio-input.o +endif + +# virtio-gpu +ENABLE_VIRTIOGPU ?= 1 +ifneq ($(UNAME_S),Linux) + ENABLE_VIRTIOGPU := 0 +endif + +# VirGL +ENABLE_VIRGL ?= 1 +ifneq (ENABLE_VIRTIOGPU,0) + CFLAGS += $(shell pkg-config virglrenderer --cflags) + LDFLAGS += $(shell pkg-config virglrenderer --libs) +endif + +$(call set-feature, VIRGL) + +# SDL2 +ENABLE_SDL ?= 1 +ifeq (, $(shell which sdl2-config)) + $(warning No sdl2-config in $$PATH. Check SDL2 installation in advance) + override ENABLE_SDL := 0 +endif + +ifeq ($(ENABLE_SDL),1) + CFLAGS += $(shell sdl2-config --cflags) + LDFLAGS += $(shell sdl2-config --libs) +else + override ENABLE_VIRTIOGPU := 0 +endif + +ifeq ($(ENABLE_VIRTIOGPU),1) + OBJS_EXTRA += window.o + OBJS_EXTRA += virtio-gpu.o +endif + +$(call set-feature, VIRTIOGPU) + BIN = semu all: $(BIN) minimal.dtb diff --git a/common.h b/common.h index 5c6d807..f2cd655 100644 --- a/common.h +++ b/common.h @@ -2,6 +2,9 @@ #include "feature.h" +#define BITS_PER_CHAR 8 +#define BITS_PER_LONG (BITS_PER_CHAR * sizeof(long)) + #define unlikely(x) __builtin_expect((x), 0) #define likely(x) __builtin_expect((x), 1) @@ -17,6 +20,16 @@ static inline int ilog2(int x) return 31 - __builtin_clz(x | 1); } +static inline void set_bit(unsigned long bit, unsigned long *word) +{ + *word |= (1 << bit); +} + +static inline void bitmap_set_bit(unsigned long *map, unsigned long bit) +{ + set_bit(bit % BITS_PER_LONG, &map[bit / BITS_PER_LONG]); +} + /* Range check * For any variable range checking: * if (x >= minx && x <= maxx) ... diff --git a/configs/buildroot.config b/configs/buildroot.config index 6f51a68..5563735 100644 --- a/configs/buildroot.config +++ b/configs/buildroot.config @@ -29,6 +29,8 @@ BR2_EXTRA_GCC_CONFIG_OPTIONS="--enable-softfloat" BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.1" BR2_TOOLCHAIN_GCC_AT_LEAST_14=y BR2_TOOLCHAIN_GCC_AT_LEAST="14" +BR2_TOOLCHAIN_BUILDROOT_CXX=y +BR2_INSTALL_LIBSTDCPP=y BR2_SSP_NONE=y # BR2_SSP_REGULAR is not set # BR2_SSP_STRONG is not set @@ -42,4 +44,13 @@ BR2_TARGET_ROOTFS_CPIO_FULL=y # BR2_TARGET_ROOTFS_CPIO_DRACUT is not set BR2_TARGET_ROOTFS_CPIO_NONE=y # BR2_TARGET_ROOTFS_TAR is not set -BR2_PACKAGE_BUSYBOX_CONFIG="busybox.config" +BR2_PACKAGE_LIBDRM=y +BR2_PACKAGE_LIBDRM_HAS_ATOMIC=y +BR2_PACKAGE_PROVIDES_LIBEGL="mesa3d" +BR2_PACKAGE_MESA3D=y +BR2_PACKAGE_MESA3D_GALLIUM_DRIVER=y +BR2_PACKAGE_MESA3D_DRIVER=y +BR2_PACKAGE_MESA3D_GALLIUM_DRIVER_VIRGL=y +BR2_PACKAGE_MESA3D_GBM=y +BR2_PACKAGE_MESA3D_OPENGL_EGL=y +BR2_PACKAGE_PROVIDES_LIBGBM="mesa3d" diff --git a/configs/linux.config b/configs/linux.config index 99078d4..bb75eb2 100644 --- a/configs/linux.config +++ b/configs/linux.config @@ -589,6 +589,7 @@ CONFIG_ALLOW_DEV_COREDUMP=y # CONFIG_TEST_ASYNC_DRIVER_PROBE is not set CONFIG_REGMAP=y CONFIG_REGMAP_MMIO=y +CONFIG_DMA_SHARED_BUFFER=y CONFIG_GENERIC_ARCH_TOPOLOGY=y # end of Generic Driver Options @@ -754,7 +755,7 @@ CONFIG_INPUT=y # # CONFIG_INPUT_MOUSEDEV is not set # CONFIG_INPUT_JOYDEV is not set -# CONFIG_INPUT_EVDEV is not set +CONFIG_INPUT_EVDEV=y # CONFIG_INPUT_EVBUG is not set # @@ -842,7 +843,11 @@ CONFIG_DEVMEM=y # # I2C support # -# CONFIG_I2C is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_COMPAT=y +CONFIG_I2C_HELPER_AUTO=y +CONFIG_I2C_ALGOBIT=y # end of I2C support # CONFIG_I3C is not set @@ -905,18 +910,41 @@ CONFIG_MFD_SYSCON=y # # Graphics support # -# CONFIG_DRM is not set +CONFIG_DRM=y +CONFIG_DRM_KMS_HELPER=y # CONFIG_DRM_DEBUG_MODESET_LOCK is not set +CONFIG_DRM_FBDEV_EMULATION=y +CONFIG_DRM_FBDEV_OVERALLOC=100 +CONFIG_DRM_GEM_SHMEM_HELPER=y # # ARM devices # # end of ARM devices +CONFIG_DRM_VIRTIO_GPU=y +CONFIG_DRM_PANEL=y + +CONFIG_DRM_BRIDGE=y +CONFIG_DRM_PANEL_BRIDGE=y + +CONFIG_DRM_PANEL_ORIENTATION_QUIRKS=y +CONFIG_DRM_NOMODESET=y + # # Frame buffer Devices # -# CONFIG_FB is not set +CONFIG_FB_CMDLINE=y +CONFIG_FB_NOTIFY=y +CONFIG_FB=y +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +CONFIG_FB_SYS_FILLRECT=y +CONFIG_FB_SYS_COPYAREA=y +CONFIG_FB_SYS_IMAGEBLIT=y +CONFIG_FB_SYS_FOPS=y +CONFIG_FB_DEFERRED_IO=y # end of Frame buffer Devices # @@ -926,6 +954,8 @@ CONFIG_MFD_SYSCON=y # CONFIG_BACKLIGHT_CLASS_DEVICE is not set # end of Backlight & LCD device support +CONFIG_HDMI=y + # # Console display driver support # @@ -1032,7 +1062,7 @@ CONFIG_EDAC_SUPPORT=y # # DMABUF options # -# CONFIG_SYNC_FILE is not set +CONFIG_SYNC_FILE=y # CONFIG_DMABUF_HEAPS is not set # end of DMABUF options @@ -1044,9 +1074,10 @@ CONFIG_VIRTIO_ANCHOR=y CONFIG_VIRTIO=y CONFIG_VIRTIO_MENU=y # CONFIG_VIRTIO_BALLOON is not set -# CONFIG_VIRTIO_INPUT is not set +CONFIG_VIRTIO_INPUT=y CONFIG_VIRTIO_MMIO=y # CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES is not set +CONFIG_VIRTIO_DMA_SHARED_BUFFER=y # CONFIG_VDPA is not set # CONFIG_VHOST_MENU is not set diff --git a/device.h b/device.h index 1bb7900..7ef4884 100644 --- a/device.h +++ b/device.h @@ -7,7 +7,10 @@ #define RAM_SIZE (512 * 1024 * 1024) #define DTB_SIZE (1 * 1024 * 1024) -#define INITRD_SIZE (8 * 1024 * 1024) +#define INITRD_SIZE (65 * 1024 * 1024) + +#define SCREEN_WIDTH 1024 +#define SCREEN_HEIGHT 768 void ram_read(hart_t *core, uint32_t *mem, @@ -171,6 +174,117 @@ void virtio_blk_write(hart_t *vm, uint32_t *virtio_blk_init(virtio_blk_state_t *vblk, char *disk_file); #endif /* SEMU_HAS(VIRTIOBLK) */ +/* VirtIO-GPU */ + +#if SEMU_HAS(VIRTIOGPU) + +#define IRQ_VGPU 4 +#define IRQ_VGPU_BIT (1 << IRQ_VGPU) + +typedef struct { + uint32_t QueueNum; + uint32_t QueueDesc; + uint32_t QueueAvail; + uint32_t QueueUsed; + uint16_t last_avail; + bool ready; +} virtio_gpu_queue_t; + +typedef struct { + /* feature negotiation */ + uint32_t DeviceFeaturesSel; + uint32_t DriverFeatures; + uint32_t DriverFeaturesSel; + /* queue config */ + uint32_t QueueSel; + virtio_gpu_queue_t queues[2]; + /* status */ + uint32_t Status; + uint32_t InterruptStatus; + /* supplied by environment */ + uint32_t *ram; + /* implementation-specific */ + void *priv; +} virtio_gpu_state_t; + +void virtio_gpu_read(hart_t *vm, + virtio_gpu_state_t *vgpu, + uint32_t addr, + uint8_t width, + uint32_t *value); + +void virtio_gpu_write(hart_t *vm, + virtio_gpu_state_t *vgpu, + uint32_t addr, + uint8_t width, + uint32_t value); + +void semu_virgl_init(void); + +void virtio_gpu_init(virtio_gpu_state_t *vgpu); +void virtio_gpu_add_scanout(virtio_gpu_state_t *vgpu, + uint32_t width, + uint32_t height); +#endif /* SEMU_HAS(VIRTIOGPU) */ + +/* VirtIO Input */ + +#if SEMU_HAS(VIRTIOINPUT) + +#define IRQ_VINPUT_KEYBOARD 5 +#define IRQ_VINPUT_KEYBOARD_BIT (1 << IRQ_VINPUT_KEYBOARD) + +#define IRQ_VINPUT_MOUSE 6 +#define IRQ_VINPUT_MOUSE_BIT (1 << IRQ_VINPUT_MOUSE) + +typedef struct { + uint32_t QueueNum; + uint32_t QueueDesc; + uint32_t QueueAvail; + uint32_t QueueUsed; + uint16_t last_avail; + bool ready; +} virtio_input_queue_t; + +typedef struct { + /* feature negotiation */ + uint32_t DeviceFeaturesSel; + uint32_t DriverFeatures; + uint32_t DriverFeaturesSel; + /* queue config */ + uint32_t QueueSel; + virtio_input_queue_t queues[2]; + /* status */ + uint32_t Status; + uint32_t InterruptStatus; + /* supplied by environment */ + uint32_t *ram; + /* implementation-specific */ + int id; // FIXME + void *priv; +} virtio_input_state_t; + +void virtio_input_read(hart_t *vm, + virtio_input_state_t *vinput, + uint32_t addr, + uint8_t width, + uint32_t *value); + +void virtio_input_write(hart_t *vm, + virtio_input_state_t *vinput, + uint32_t addr, + uint8_t width, + uint32_t value); + +void virtio_input_init(virtio_input_state_t *vinput); + +void virtio_input_update_key(uint32_t key, uint32_t state); + +void virtio_input_update_mouse_button_state(uint32_t button, bool pressed); + +void virtio_input_update_cursor(uint32_t x, uint32_t y); +#endif /* SEMU_HAS(VIRTIOINPUT) */ + /* clint */ typedef struct { uint32_t msip[4096]; @@ -203,6 +317,13 @@ typedef struct { #endif #if SEMU_HAS(VIRTIOBLK) virtio_blk_state_t vblk; +#endif +#if SEMU_HAS(VIRTIOGPU) + virtio_gpu_state_t vgpu; +#endif +#if SEMU_HAS(VIRTIOINPUT) + virtio_input_state_t vkeyboard; + virtio_input_state_t vmouse; #endif clint_state_t clint; } emu_state_t; diff --git a/feature.h b/feature.h index 1dee984..64e3457 100644 --- a/feature.h +++ b/feature.h @@ -12,5 +12,20 @@ #define SEMU_FEATURE_VIRTIONET 1 #endif +/* virtio-gpu */ +#ifndef SEMU_FEATURE_VIRTIOGPU +#define SEMU_FEATURE_VIRTIOGPU 1 +#endif + +/* VirGL */ +#ifndef SEMU_FEATURE_VIRGL +#define SEMU_FEATURE_VIRGL 1 +#endif + +/* virtio-input */ +#ifndef SEMU_FEATURE_VIRTIOINPUT +#define SEMU_FEATURE_VIRTIOINPUT 1 +#endif + /* Feature test macro */ #define SEMU_HAS(x) SEMU_FEATURE_##x diff --git a/input-event-codes.h b/input-event-codes.h new file mode 100644 index 0000000..50ad928 --- /dev/null +++ b/input-event-codes.h @@ -0,0 +1,992 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Input event codes + * + * *** IMPORTANT *** + * This file is not only included from C-code but also from devicetree source + * files. As such this file MUST only contain comments and defines. + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * Copyright (c) 2015 Hans de Goede + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#ifndef _UAPI_INPUT_EVENT_CODES_H +#define _UAPI_INPUT_EVENT_CODES_H + +/* + * Device properties and quirks + */ + +#define INPUT_PROP_POINTER 0x00 /* needs a pointer */ +#define INPUT_PROP_DIRECT 0x01 /* direct input devices */ +#define INPUT_PROP_BUTTONPAD 0x02 /* has button(s) under pad */ +#define INPUT_PROP_SEMI_MT 0x03 /* touch rectangle only */ +#define INPUT_PROP_TOPBUTTONPAD 0x04 /* softbuttons at top of pad */ +#define INPUT_PROP_POINTING_STICK 0x05 /* is a pointing stick */ +#define INPUT_PROP_ACCELEROMETER 0x06 /* has accelerometer */ + +#define INPUT_PROP_MAX 0x1f +#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1) + +/* + * Event types + */ + +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_REL 0x02 +#define EV_ABS 0x03 +#define EV_MSC 0x04 +#define EV_SW 0x05 +#define EV_LED 0x11 +#define EV_SND 0x12 +#define EV_REP 0x14 +#define EV_FF 0x15 +#define EV_PWR 0x16 +#define EV_FF_STATUS 0x17 +#define EV_MAX 0x1f +#define EV_CNT (EV_MAX + 1) + +/* + * Synchronization events. + */ + +#define SYN_REPORT 0 +#define SYN_CONFIG 1 +#define SYN_MT_REPORT 2 +#define SYN_DROPPED 3 +#define SYN_MAX 0xf +#define SYN_CNT (SYN_MAX + 1) + +/* + * Keys and buttons + * + * Most of the keys/buttons are modeled after USB HUT 1.12 + * (see http://www.usb.org/developers/hidpage). + * Abbreviations in the comments: + * AC - Application Control + * AL - Application Launch Button + * SC - System Control + */ + +#define KEY_RESERVED 0 +#define KEY_ESC 1 +#define KEY_1 2 +#define KEY_2 3 +#define KEY_3 4 +#define KEY_4 5 +#define KEY_5 6 +#define KEY_6 7 +#define KEY_7 8 +#define KEY_8 9 +#define KEY_9 10 +#define KEY_0 11 +#define KEY_MINUS 12 +#define KEY_EQUAL 13 +#define KEY_BACKSPACE 14 +#define KEY_TAB 15 +#define KEY_Q 16 +#define KEY_W 17 +#define KEY_E 18 +#define KEY_R 19 +#define KEY_T 20 +#define KEY_Y 21 +#define KEY_U 22 +#define KEY_I 23 +#define KEY_O 24 +#define KEY_P 25 +#define KEY_LEFTBRACE 26 +#define KEY_RIGHTBRACE 27 +#define KEY_ENTER 28 +#define KEY_LEFTCTRL 29 +#define KEY_A 30 +#define KEY_S 31 +#define KEY_D 32 +#define KEY_F 33 +#define KEY_G 34 +#define KEY_H 35 +#define KEY_J 36 +#define KEY_K 37 +#define KEY_L 38 +#define KEY_SEMICOLON 39 +#define KEY_APOSTROPHE 40 +#define KEY_GRAVE 41 +#define KEY_LEFTSHIFT 42 +#define KEY_BACKSLASH 43 +#define KEY_Z 44 +#define KEY_X 45 +#define KEY_C 46 +#define KEY_V 47 +#define KEY_B 48 +#define KEY_N 49 +#define KEY_M 50 +#define KEY_COMMA 51 +#define KEY_DOT 52 +#define KEY_SLASH 53 +#define KEY_RIGHTSHIFT 54 +#define KEY_KPASTERISK 55 +#define KEY_LEFTALT 56 +#define KEY_SPACE 57 +#define KEY_CAPSLOCK 58 +#define KEY_F1 59 +#define KEY_F2 60 +#define KEY_F3 61 +#define KEY_F4 62 +#define KEY_F5 63 +#define KEY_F6 64 +#define KEY_F7 65 +#define KEY_F8 66 +#define KEY_F9 67 +#define KEY_F10 68 +#define KEY_NUMLOCK 69 +#define KEY_SCROLLLOCK 70 +#define KEY_KP7 71 +#define KEY_KP8 72 +#define KEY_KP9 73 +#define KEY_KPMINUS 74 +#define KEY_KP4 75 +#define KEY_KP5 76 +#define KEY_KP6 77 +#define KEY_KPPLUS 78 +#define KEY_KP1 79 +#define KEY_KP2 80 +#define KEY_KP3 81 +#define KEY_KP0 82 +#define KEY_KPDOT 83 + +#define KEY_ZENKAKUHANKAKU 85 +#define KEY_102ND 86 +#define KEY_F11 87 +#define KEY_F12 88 +#define KEY_RO 89 +#define KEY_KATAKANA 90 +#define KEY_HIRAGANA 91 +#define KEY_HENKAN 92 +#define KEY_KATAKANAHIRAGANA 93 +#define KEY_MUHENKAN 94 +#define KEY_KPJPCOMMA 95 +#define KEY_KPENTER 96 +#define KEY_RIGHTCTRL 97 +#define KEY_KPSLASH 98 +#define KEY_SYSRQ 99 +#define KEY_RIGHTALT 100 +#define KEY_LINEFEED 101 +#define KEY_HOME 102 +#define KEY_UP 103 +#define KEY_PAGEUP 104 +#define KEY_LEFT 105 +#define KEY_RIGHT 106 +#define KEY_END 107 +#define KEY_DOWN 108 +#define KEY_PAGEDOWN 109 +#define KEY_INSERT 110 +#define KEY_DELETE 111 +#define KEY_MACRO 112 +#define KEY_MUTE 113 +#define KEY_VOLUMEDOWN 114 +#define KEY_VOLUMEUP 115 +#define KEY_POWER 116 /* SC System Power Down */ +#define KEY_KPEQUAL 117 +#define KEY_KPPLUSMINUS 118 +#define KEY_PAUSE 119 +#define KEY_SCALE 120 /* AL Compiz Scale (Expose) */ + +#define KEY_KPCOMMA 121 +#define KEY_HANGEUL 122 +#define KEY_HANGUEL KEY_HANGEUL +#define KEY_HANJA 123 +#define KEY_YEN 124 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 +#define KEY_COMPOSE 127 + +#define KEY_STOP 128 /* AC Stop */ +#define KEY_AGAIN 129 +#define KEY_PROPS 130 /* AC Properties */ +#define KEY_UNDO 131 /* AC Undo */ +#define KEY_FRONT 132 +#define KEY_COPY 133 /* AC Copy */ +#define KEY_OPEN 134 /* AC Open */ +#define KEY_PASTE 135 /* AC Paste */ +#define KEY_FIND 136 /* AC Search */ +#define KEY_CUT 137 /* AC Cut */ +#define KEY_HELP 138 /* AL Integrated Help Center */ +#define KEY_MENU 139 /* Menu (show menu) */ +#define KEY_CALC 140 /* AL Calculator */ +#define KEY_SETUP 141 +#define KEY_SLEEP 142 /* SC System Sleep */ +#define KEY_WAKEUP 143 /* System Wake Up */ +#define KEY_FILE 144 /* AL Local Machine Browser */ +#define KEY_SENDFILE 145 +#define KEY_DELETEFILE 146 +#define KEY_XFER 147 +#define KEY_PROG1 148 +#define KEY_PROG2 149 +#define KEY_WWW 150 /* AL Internet Browser */ +#define KEY_MSDOS 151 +#define KEY_COFFEE 152 /* AL Terminal Lock/Screensaver */ +#define KEY_SCREENLOCK KEY_COFFEE +#define KEY_ROTATE_DISPLAY 153 /* Display orientation for e.g. tablets */ +#define KEY_DIRECTION KEY_ROTATE_DISPLAY +#define KEY_CYCLEWINDOWS 154 +#define KEY_MAIL 155 +#define KEY_BOOKMARKS 156 /* AC Bookmarks */ +#define KEY_COMPUTER 157 +#define KEY_BACK 158 /* AC Back */ +#define KEY_FORWARD 159 /* AC Forward */ +#define KEY_CLOSECD 160 +#define KEY_EJECTCD 161 +#define KEY_EJECTCLOSECD 162 +#define KEY_NEXTSONG 163 +#define KEY_PLAYPAUSE 164 +#define KEY_PREVIOUSSONG 165 +#define KEY_STOPCD 166 +#define KEY_RECORD 167 +#define KEY_REWIND 168 +#define KEY_PHONE 169 /* Media Select Telephone */ +#define KEY_ISO 170 +#define KEY_CONFIG 171 /* AL Consumer Control Configuration */ +#define KEY_HOMEPAGE 172 /* AC Home */ +#define KEY_REFRESH 173 /* AC Refresh */ +#define KEY_EXIT 174 /* AC Exit */ +#define KEY_MOVE 175 +#define KEY_EDIT 176 +#define KEY_SCROLLUP 177 +#define KEY_SCROLLDOWN 178 +#define KEY_KPLEFTPAREN 179 +#define KEY_KPRIGHTPAREN 180 +#define KEY_NEW 181 /* AC New */ +#define KEY_REDO 182 /* AC Redo/Repeat */ + +#define KEY_F13 183 +#define KEY_F14 184 +#define KEY_F15 185 +#define KEY_F16 186 +#define KEY_F17 187 +#define KEY_F18 188 +#define KEY_F19 189 +#define KEY_F20 190 +#define KEY_F21 191 +#define KEY_F22 192 +#define KEY_F23 193 +#define KEY_F24 194 + +#define KEY_PLAYCD 200 +#define KEY_PAUSECD 201 +#define KEY_PROG3 202 +#define KEY_PROG4 203 +#define KEY_ALL_APPLICATIONS 204 /* AC Desktop Show All Applications */ +#define KEY_DASHBOARD KEY_ALL_APPLICATIONS +#define KEY_SUSPEND 205 +#define KEY_CLOSE 206 /* AC Close */ +#define KEY_PLAY 207 +#define KEY_FASTFORWARD 208 +#define KEY_BASSBOOST 209 +#define KEY_PRINT 210 /* AC Print */ +#define KEY_HP 211 +#define KEY_CAMERA 212 +#define KEY_SOUND 213 +#define KEY_QUESTION 214 +#define KEY_EMAIL 215 +#define KEY_CHAT 216 +#define KEY_SEARCH 217 +#define KEY_CONNECT 218 +#define KEY_FINANCE 219 /* AL Checkbook/Finance */ +#define KEY_SPORT 220 +#define KEY_SHOP 221 +#define KEY_ALTERASE 222 +#define KEY_CANCEL 223 /* AC Cancel */ +#define KEY_BRIGHTNESSDOWN 224 +#define KEY_BRIGHTNESSUP 225 +#define KEY_MEDIA 226 + +#define KEY_SWITCHVIDEOMODE \ + 227 /* Cycle between available video \ +outputs (Monitor/LCD/TV-out/etc) */ +#define KEY_KBDILLUMTOGGLE 228 +#define KEY_KBDILLUMDOWN 229 +#define KEY_KBDILLUMUP 230 + +#define KEY_SEND 231 /* AC Send */ +#define KEY_REPLY 232 /* AC Reply */ +#define KEY_FORWARDMAIL 233 /* AC Forward Msg */ +#define KEY_SAVE 234 /* AC Save */ +#define KEY_DOCUMENTS 235 + +#define KEY_BATTERY 236 + +#define KEY_BLUETOOTH 237 +#define KEY_WLAN 238 +#define KEY_UWB 239 + +#define KEY_UNKNOWN 240 + +#define KEY_VIDEO_NEXT 241 /* drive next video source */ +#define KEY_VIDEO_PREV 242 /* drive previous video source */ +#define KEY_BRIGHTNESS_CYCLE 243 /* brightness up, after max is min */ +#define KEY_BRIGHTNESS_AUTO \ + 244 /* Set Auto Brightness: manual \ +brightness control is off, \ +rely on ambient */ +#define KEY_BRIGHTNESS_ZERO KEY_BRIGHTNESS_AUTO +#define KEY_DISPLAY_OFF 245 /* display device to off state */ + +#define KEY_WWAN 246 /* Wireless WAN (LTE, UMTS, GSM, etc.) */ +#define KEY_WIMAX KEY_WWAN +#define KEY_RFKILL 247 /* Key that controls all radios */ + +#define KEY_MICMUTE 248 /* Mute / unmute the microphone */ + +/* Code 255 is reserved for special needs of AT keyboard driver */ + +#define BTN_MISC 0x100 +#define BTN_0 0x100 +#define BTN_1 0x101 +#define BTN_2 0x102 +#define BTN_3 0x103 +#define BTN_4 0x104 +#define BTN_5 0x105 +#define BTN_6 0x106 +#define BTN_7 0x107 +#define BTN_8 0x108 +#define BTN_9 0x109 + +#define BTN_MOUSE 0x110 +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 +#define BTN_SIDE 0x113 +#define BTN_EXTRA 0x114 +#define BTN_FORWARD 0x115 +#define BTN_BACK 0x116 +#define BTN_TASK 0x117 + +#define BTN_JOYSTICK 0x120 +#define BTN_TRIGGER 0x120 +#define BTN_THUMB 0x121 +#define BTN_THUMB2 0x122 +#define BTN_TOP 0x123 +#define BTN_TOP2 0x124 +#define BTN_PINKIE 0x125 +#define BTN_BASE 0x126 +#define BTN_BASE2 0x127 +#define BTN_BASE3 0x128 +#define BTN_BASE4 0x129 +#define BTN_BASE5 0x12a +#define BTN_BASE6 0x12b +#define BTN_DEAD 0x12f + +#define BTN_GAMEPAD 0x130 +#define BTN_SOUTH 0x130 +#define BTN_A BTN_SOUTH +#define BTN_EAST 0x131 +#define BTN_B BTN_EAST +#define BTN_C 0x132 +#define BTN_NORTH 0x133 +#define BTN_X BTN_NORTH +#define BTN_WEST 0x134 +#define BTN_Y BTN_WEST +#define BTN_Z 0x135 +#define BTN_TL 0x136 +#define BTN_TR 0x137 +#define BTN_TL2 0x138 +#define BTN_TR2 0x139 +#define BTN_SELECT 0x13a +#define BTN_START 0x13b +#define BTN_MODE 0x13c +#define BTN_THUMBL 0x13d +#define BTN_THUMBR 0x13e + +#define BTN_DIGI 0x140 +#define BTN_TOOL_PEN 0x140 +#define BTN_TOOL_RUBBER 0x141 +#define BTN_TOOL_BRUSH 0x142 +#define BTN_TOOL_PENCIL 0x143 +#define BTN_TOOL_AIRBRUSH 0x144 +#define BTN_TOOL_FINGER 0x145 +#define BTN_TOOL_MOUSE 0x146 +#define BTN_TOOL_LENS 0x147 +#define BTN_TOOL_QUINTTAP 0x148 /* Five fingers on trackpad */ +#define BTN_STYLUS3 0x149 +#define BTN_TOUCH 0x14a +#define BTN_STYLUS 0x14b +#define BTN_STYLUS2 0x14c +#define BTN_TOOL_DOUBLETAP 0x14d +#define BTN_TOOL_TRIPLETAP 0x14e +#define BTN_TOOL_QUADTAP 0x14f /* Four fingers on trackpad */ + +#define BTN_WHEEL 0x150 +#define BTN_GEAR_DOWN 0x150 +#define BTN_GEAR_UP 0x151 + +#define KEY_OK 0x160 +#define KEY_SELECT 0x161 +#define KEY_GOTO 0x162 +#define KEY_CLEAR 0x163 +#define KEY_POWER2 0x164 +#define KEY_OPTION 0x165 +#define KEY_INFO 0x166 /* AL OEM Features/Tips/Tutorial */ +#define KEY_TIME 0x167 +#define KEY_VENDOR 0x168 +#define KEY_ARCHIVE 0x169 +#define KEY_PROGRAM 0x16a /* Media Select Program Guide */ +#define KEY_CHANNEL 0x16b +#define KEY_FAVORITES 0x16c +#define KEY_EPG 0x16d +#define KEY_PVR 0x16e /* Media Select Home */ +#define KEY_MHP 0x16f +#define KEY_LANGUAGE 0x170 +#define KEY_TITLE 0x171 +#define KEY_SUBTITLE 0x172 +#define KEY_ANGLE 0x173 +#define KEY_FULL_SCREEN 0x174 /* AC View Toggle */ +#define KEY_ZOOM KEY_FULL_SCREEN +#define KEY_MODE 0x175 +#define KEY_KEYBOARD 0x176 +#define KEY_ASPECT_RATIO 0x177 /* HUTRR37: Aspect */ +#define KEY_SCREEN KEY_ASPECT_RATIO +#define KEY_PC 0x178 /* Media Select Computer */ +#define KEY_TV 0x179 /* Media Select TV */ +#define KEY_TV2 0x17a /* Media Select Cable */ +#define KEY_VCR 0x17b /* Media Select VCR */ +#define KEY_VCR2 0x17c /* VCR Plus */ +#define KEY_SAT 0x17d /* Media Select Satellite */ +#define KEY_SAT2 0x17e +#define KEY_CD 0x17f /* Media Select CD */ +#define KEY_TAPE 0x180 /* Media Select Tape */ +#define KEY_RADIO 0x181 +#define KEY_TUNER 0x182 /* Media Select Tuner */ +#define KEY_PLAYER 0x183 +#define KEY_TEXT 0x184 +#define KEY_DVD 0x185 /* Media Select DVD */ +#define KEY_AUX 0x186 +#define KEY_MP3 0x187 +#define KEY_AUDIO 0x188 /* AL Audio Browser */ +#define KEY_VIDEO 0x189 /* AL Movie Browser */ +#define KEY_DIRECTORY 0x18a +#define KEY_LIST 0x18b +#define KEY_MEMO 0x18c /* Media Select Messages */ +#define KEY_CALENDAR 0x18d +#define KEY_RED 0x18e +#define KEY_GREEN 0x18f +#define KEY_YELLOW 0x190 +#define KEY_BLUE 0x191 +#define KEY_CHANNELUP 0x192 /* Channel Increment */ +#define KEY_CHANNELDOWN 0x193 /* Channel Decrement */ +#define KEY_FIRST 0x194 +#define KEY_LAST 0x195 /* Recall Last */ +#define KEY_AB 0x196 +#define KEY_NEXT 0x197 +#define KEY_RESTART 0x198 +#define KEY_SLOW 0x199 +#define KEY_SHUFFLE 0x19a +#define KEY_BREAK 0x19b +#define KEY_PREVIOUS 0x19c +#define KEY_DIGITS 0x19d +#define KEY_TEEN 0x19e +#define KEY_TWEN 0x19f +#define KEY_VIDEOPHONE 0x1a0 /* Media Select Video Phone */ +#define KEY_GAMES 0x1a1 /* Media Select Games */ +#define KEY_ZOOMIN 0x1a2 /* AC Zoom In */ +#define KEY_ZOOMOUT 0x1a3 /* AC Zoom Out */ +#define KEY_ZOOMRESET 0x1a4 /* AC Zoom */ +#define KEY_WORDPROCESSOR 0x1a5 /* AL Word Processor */ +#define KEY_EDITOR 0x1a6 /* AL Text Editor */ +#define KEY_SPREADSHEET 0x1a7 /* AL Spreadsheet */ +#define KEY_GRAPHICSEDITOR 0x1a8 /* AL Graphics Editor */ +#define KEY_PRESENTATION 0x1a9 /* AL Presentation App */ +#define KEY_DATABASE 0x1aa /* AL Database App */ +#define KEY_NEWS 0x1ab /* AL Newsreader */ +#define KEY_VOICEMAIL 0x1ac /* AL Voicemail */ +#define KEY_ADDRESSBOOK 0x1ad /* AL Contacts/Address Book */ +#define KEY_MESSENGER 0x1ae /* AL Instant Messaging */ +#define KEY_DISPLAYTOGGLE 0x1af /* Turn display (LCD) on and off */ +#define KEY_BRIGHTNESS_TOGGLE KEY_DISPLAYTOGGLE +#define KEY_SPELLCHECK 0x1b0 /* AL Spell Check */ +#define KEY_LOGOFF 0x1b1 /* AL Logoff */ + +#define KEY_DOLLAR 0x1b2 +#define KEY_EURO 0x1b3 + +#define KEY_FRAMEBACK 0x1b4 /* Consumer - transport controls */ +#define KEY_FRAMEFORWARD 0x1b5 +#define KEY_CONTEXT_MENU 0x1b6 /* GenDesc - system context menu */ +#define KEY_MEDIA_REPEAT 0x1b7 /* Consumer - transport control */ +#define KEY_10CHANNELSUP 0x1b8 /* 10 channels up (10+) */ +#define KEY_10CHANNELSDOWN 0x1b9 /* 10 channels down (10-) */ +#define KEY_IMAGES 0x1ba /* AL Image Browser */ +#define KEY_NOTIFICATION_CENTER 0x1bc /* Show/hide the notification center */ +#define KEY_PICKUP_PHONE 0x1bd /* Answer incoming call */ +#define KEY_HANGUP_PHONE 0x1be /* Decline incoming call */ + +#define KEY_DEL_EOL 0x1c0 +#define KEY_DEL_EOS 0x1c1 +#define KEY_INS_LINE 0x1c2 +#define KEY_DEL_LINE 0x1c3 + +#define KEY_FN 0x1d0 +#define KEY_FN_ESC 0x1d1 +#define KEY_FN_F1 0x1d2 +#define KEY_FN_F2 0x1d3 +#define KEY_FN_F3 0x1d4 +#define KEY_FN_F4 0x1d5 +#define KEY_FN_F5 0x1d6 +#define KEY_FN_F6 0x1d7 +#define KEY_FN_F7 0x1d8 +#define KEY_FN_F8 0x1d9 +#define KEY_FN_F9 0x1da +#define KEY_FN_F10 0x1db +#define KEY_FN_F11 0x1dc +#define KEY_FN_F12 0x1dd +#define KEY_FN_1 0x1de +#define KEY_FN_2 0x1df +#define KEY_FN_D 0x1e0 +#define KEY_FN_E 0x1e1 +#define KEY_FN_F 0x1e2 +#define KEY_FN_S 0x1e3 +#define KEY_FN_B 0x1e4 +#define KEY_FN_RIGHT_SHIFT 0x1e5 + +#define KEY_BRL_DOT1 0x1f1 +#define KEY_BRL_DOT2 0x1f2 +#define KEY_BRL_DOT3 0x1f3 +#define KEY_BRL_DOT4 0x1f4 +#define KEY_BRL_DOT5 0x1f5 +#define KEY_BRL_DOT6 0x1f6 +#define KEY_BRL_DOT7 0x1f7 +#define KEY_BRL_DOT8 0x1f8 +#define KEY_BRL_DOT9 0x1f9 +#define KEY_BRL_DOT10 0x1fa + +#define KEY_NUMERIC_0 0x200 /* used by phones, remote controls, */ +#define KEY_NUMERIC_1 0x201 /* and other keypads */ +#define KEY_NUMERIC_2 0x202 +#define KEY_NUMERIC_3 0x203 +#define KEY_NUMERIC_4 0x204 +#define KEY_NUMERIC_5 0x205 +#define KEY_NUMERIC_6 0x206 +#define KEY_NUMERIC_7 0x207 +#define KEY_NUMERIC_8 0x208 +#define KEY_NUMERIC_9 0x209 +#define KEY_NUMERIC_STAR 0x20a +#define KEY_NUMERIC_POUND 0x20b +#define KEY_NUMERIC_A 0x20c /* Phone key A - HUT Telephony 0xb9 */ +#define KEY_NUMERIC_B 0x20d +#define KEY_NUMERIC_C 0x20e +#define KEY_NUMERIC_D 0x20f + +#define KEY_CAMERA_FOCUS 0x210 +#define KEY_WPS_BUTTON 0x211 /* WiFi Protected Setup key */ + +#define KEY_TOUCHPAD_TOGGLE 0x212 /* Request switch touchpad on or off */ +#define KEY_TOUCHPAD_ON 0x213 +#define KEY_TOUCHPAD_OFF 0x214 + +#define KEY_CAMERA_ZOOMIN 0x215 +#define KEY_CAMERA_ZOOMOUT 0x216 +#define KEY_CAMERA_UP 0x217 +#define KEY_CAMERA_DOWN 0x218 +#define KEY_CAMERA_LEFT 0x219 +#define KEY_CAMERA_RIGHT 0x21a + +#define KEY_ATTENDANT_ON 0x21b +#define KEY_ATTENDANT_OFF 0x21c +#define KEY_ATTENDANT_TOGGLE 0x21d /* Attendant call on or off */ +#define KEY_LIGHTS_TOGGLE 0x21e /* Reading light on or off */ + +#define BTN_DPAD_UP 0x220 +#define BTN_DPAD_DOWN 0x221 +#define BTN_DPAD_LEFT 0x222 +#define BTN_DPAD_RIGHT 0x223 + +#define KEY_ALS_TOGGLE 0x230 /* Ambient light sensor */ +#define KEY_ROTATE_LOCK_TOGGLE 0x231 /* Display rotation lock */ +#define KEY_REFRESH_RATE_TOGGLE 0x232 /* Display refresh rate toggle */ + +#define KEY_BUTTONCONFIG 0x240 /* AL Button Configuration */ +#define KEY_TASKMANAGER 0x241 /* AL Task/Project Manager */ +#define KEY_JOURNAL 0x242 /* AL Log/Journal/Timecard */ +#define KEY_CONTROLPANEL 0x243 /* AL Control Panel */ +#define KEY_APPSELECT 0x244 /* AL Select Task/Application */ +#define KEY_SCREENSAVER 0x245 /* AL Screen Saver */ +#define KEY_VOICECOMMAND 0x246 /* Listening Voice Command */ +#define KEY_ASSISTANT 0x247 /* AL Context-aware desktop assistant */ +#define KEY_KBD_LAYOUT_NEXT 0x248 /* AC Next Keyboard Layout Select */ +#define KEY_EMOJI_PICKER 0x249 /* Show/hide emoji picker (HUTRR101) */ +#define KEY_DICTATE \ + 0x24a /* Start or Stop Voice Dictation Session (HUTRR99) \ + */ +#define KEY_CAMERA_ACCESS_ENABLE \ + 0x24b /* Enables programmatic access to camera devices. (HUTRR72) */ +#define KEY_CAMERA_ACCESS_DISABLE \ + 0x24c /* Disables programmatic access to camera devices. (HUTRR72) */ +#define KEY_CAMERA_ACCESS_TOGGLE \ + 0x24d /* Toggles the current state of the camera access control. (HUTRR72) \ + */ +#define KEY_ACCESSIBILITY \ + 0x24e /* Toggles the system bound accessibility UI/command (HUTRR116) */ +#define KEY_DO_NOT_DISTURB \ + 0x24f /* Toggles the system-wide "Do Not Disturb" control (HUTRR94)*/ + +#define KEY_BRIGHTNESS_MIN 0x250 /* Set Brightness to Minimum */ +#define KEY_BRIGHTNESS_MAX 0x251 /* Set Brightness to Maximum */ + +#define KEY_KBDINPUTASSIST_PREV 0x260 +#define KEY_KBDINPUTASSIST_NEXT 0x261 +#define KEY_KBDINPUTASSIST_PREVGROUP 0x262 +#define KEY_KBDINPUTASSIST_NEXTGROUP 0x263 +#define KEY_KBDINPUTASSIST_ACCEPT 0x264 +#define KEY_KBDINPUTASSIST_CANCEL 0x265 + +/* Diagonal movement keys */ +#define KEY_RIGHT_UP 0x266 +#define KEY_RIGHT_DOWN 0x267 +#define KEY_LEFT_UP 0x268 +#define KEY_LEFT_DOWN 0x269 + +#define KEY_ROOT_MENU 0x26a /* Show Device's Root Menu */ +/* Show Top Menu of the Media (e.g. DVD) */ +#define KEY_MEDIA_TOP_MENU 0x26b +#define KEY_NUMERIC_11 0x26c +#define KEY_NUMERIC_12 0x26d +/* + * Toggle Audio Description: refers to an audio service that helps blind and + * visually impaired consumers understand the action in a program. Note: in + * some countries this is referred to as "Video Description". + */ +#define KEY_AUDIO_DESC 0x26e +#define KEY_3D_MODE 0x26f +#define KEY_NEXT_FAVORITE 0x270 +#define KEY_STOP_RECORD 0x271 +#define KEY_PAUSE_RECORD 0x272 +#define KEY_VOD 0x273 /* Video on Demand */ +#define KEY_UNMUTE 0x274 +#define KEY_FASTREVERSE 0x275 +#define KEY_SLOWREVERSE 0x276 +/* + * Control a data application associated with the currently viewed channel, + * e.g. teletext or data broadcast application (MHEG, MHP, HbbTV, etc.) + */ +#define KEY_DATA 0x277 +#define KEY_ONSCREEN_KEYBOARD 0x278 +/* Electronic privacy screen control */ +#define KEY_PRIVACY_SCREEN_TOGGLE 0x279 + +/* Select an area of screen to be copied */ +#define KEY_SELECTIVE_SCREENSHOT 0x27a + +/* Move the focus to the next or previous user controllable element within a UI + * container */ +#define KEY_NEXT_ELEMENT 0x27b +#define KEY_PREVIOUS_ELEMENT 0x27c + +/* Toggle Autopilot engagement */ +#define KEY_AUTOPILOT_ENGAGE_TOGGLE 0x27d + +/* Shortcut Keys */ +#define KEY_MARK_WAYPOINT 0x27e +#define KEY_SOS 0x27f +#define KEY_NAV_CHART 0x280 +#define KEY_FISHING_CHART 0x281 +#define KEY_SINGLE_RANGE_RADAR 0x282 +#define KEY_DUAL_RANGE_RADAR 0x283 +#define KEY_RADAR_OVERLAY 0x284 +#define KEY_TRADITIONAL_SONAR 0x285 +#define KEY_CLEARVU_SONAR 0x286 +#define KEY_SIDEVU_SONAR 0x287 +#define KEY_NAV_INFO 0x288 +#define KEY_BRIGHTNESS_MENU 0x289 + +/* + * Some keyboards have keys which do not have a defined meaning, these keys + * are intended to be programmed / bound to macros by the user. For most + * keyboards with these macro-keys the key-sequence to inject, or action to + * take, is all handled by software on the host side. So from the kernel's + * point of view these are just normal keys. + * + * The KEY_MACRO# codes below are intended for such keys, which may be labeled + * e.g. G1-G18, or S1 - S30. The KEY_MACRO# codes MUST NOT be used for keys + * where the marking on the key does indicate a defined meaning / purpose. + * + * The KEY_MACRO# codes MUST also NOT be used as fallback for when no existing + * KEY_FOO define matches the marking / purpose. In this case a new KEY_FOO + * define MUST be added. + */ +#define KEY_MACRO1 0x290 +#define KEY_MACRO2 0x291 +#define KEY_MACRO3 0x292 +#define KEY_MACRO4 0x293 +#define KEY_MACRO5 0x294 +#define KEY_MACRO6 0x295 +#define KEY_MACRO7 0x296 +#define KEY_MACRO8 0x297 +#define KEY_MACRO9 0x298 +#define KEY_MACRO10 0x299 +#define KEY_MACRO11 0x29a +#define KEY_MACRO12 0x29b +#define KEY_MACRO13 0x29c +#define KEY_MACRO14 0x29d +#define KEY_MACRO15 0x29e +#define KEY_MACRO16 0x29f +#define KEY_MACRO17 0x2a0 +#define KEY_MACRO18 0x2a1 +#define KEY_MACRO19 0x2a2 +#define KEY_MACRO20 0x2a3 +#define KEY_MACRO21 0x2a4 +#define KEY_MACRO22 0x2a5 +#define KEY_MACRO23 0x2a6 +#define KEY_MACRO24 0x2a7 +#define KEY_MACRO25 0x2a8 +#define KEY_MACRO26 0x2a9 +#define KEY_MACRO27 0x2aa +#define KEY_MACRO28 0x2ab +#define KEY_MACRO29 0x2ac +#define KEY_MACRO30 0x2ad + +/* + * Some keyboards with the macro-keys described above have some extra keys + * for controlling the host-side software responsible for the macro handling: + * -A macro recording start/stop key. Note that not all keyboards which emit + * KEY_MACRO_RECORD_START will also emit KEY_MACRO_RECORD_STOP if + * KEY_MACRO_RECORD_STOP is not advertised, then KEY_MACRO_RECORD_START + * should be interpreted as a recording start/stop toggle; + * -Keys for switching between different macro (pre)sets, either a key for + * cycling through the configured presets or keys to directly select a preset. + */ +#define KEY_MACRO_RECORD_START 0x2b0 +#define KEY_MACRO_RECORD_STOP 0x2b1 +#define KEY_MACRO_PRESET_CYCLE 0x2b2 +#define KEY_MACRO_PRESET1 0x2b3 +#define KEY_MACRO_PRESET2 0x2b4 +#define KEY_MACRO_PRESET3 0x2b5 + +/* + * Some keyboards have a buildin LCD panel where the contents are controlled + * by the host. Often these have a number of keys directly below the LCD + * intended for controlling a menu shown on the LCD. These keys often don't + * have any labeling so we just name them KEY_KBD_LCD_MENU# + */ +#define KEY_KBD_LCD_MENU1 0x2b8 +#define KEY_KBD_LCD_MENU2 0x2b9 +#define KEY_KBD_LCD_MENU3 0x2ba +#define KEY_KBD_LCD_MENU4 0x2bb +#define KEY_KBD_LCD_MENU5 0x2bc + +#define BTN_TRIGGER_HAPPY 0x2c0 +#define BTN_TRIGGER_HAPPY1 0x2c0 +#define BTN_TRIGGER_HAPPY2 0x2c1 +#define BTN_TRIGGER_HAPPY3 0x2c2 +#define BTN_TRIGGER_HAPPY4 0x2c3 +#define BTN_TRIGGER_HAPPY5 0x2c4 +#define BTN_TRIGGER_HAPPY6 0x2c5 +#define BTN_TRIGGER_HAPPY7 0x2c6 +#define BTN_TRIGGER_HAPPY8 0x2c7 +#define BTN_TRIGGER_HAPPY9 0x2c8 +#define BTN_TRIGGER_HAPPY10 0x2c9 +#define BTN_TRIGGER_HAPPY11 0x2ca +#define BTN_TRIGGER_HAPPY12 0x2cb +#define BTN_TRIGGER_HAPPY13 0x2cc +#define BTN_TRIGGER_HAPPY14 0x2cd +#define BTN_TRIGGER_HAPPY15 0x2ce +#define BTN_TRIGGER_HAPPY16 0x2cf +#define BTN_TRIGGER_HAPPY17 0x2d0 +#define BTN_TRIGGER_HAPPY18 0x2d1 +#define BTN_TRIGGER_HAPPY19 0x2d2 +#define BTN_TRIGGER_HAPPY20 0x2d3 +#define BTN_TRIGGER_HAPPY21 0x2d4 +#define BTN_TRIGGER_HAPPY22 0x2d5 +#define BTN_TRIGGER_HAPPY23 0x2d6 +#define BTN_TRIGGER_HAPPY24 0x2d7 +#define BTN_TRIGGER_HAPPY25 0x2d8 +#define BTN_TRIGGER_HAPPY26 0x2d9 +#define BTN_TRIGGER_HAPPY27 0x2da +#define BTN_TRIGGER_HAPPY28 0x2db +#define BTN_TRIGGER_HAPPY29 0x2dc +#define BTN_TRIGGER_HAPPY30 0x2dd +#define BTN_TRIGGER_HAPPY31 0x2de +#define BTN_TRIGGER_HAPPY32 0x2df +#define BTN_TRIGGER_HAPPY33 0x2e0 +#define BTN_TRIGGER_HAPPY34 0x2e1 +#define BTN_TRIGGER_HAPPY35 0x2e2 +#define BTN_TRIGGER_HAPPY36 0x2e3 +#define BTN_TRIGGER_HAPPY37 0x2e4 +#define BTN_TRIGGER_HAPPY38 0x2e5 +#define BTN_TRIGGER_HAPPY39 0x2e6 +#define BTN_TRIGGER_HAPPY40 0x2e7 + +/* We avoid low common keys in module aliases so they don't get huge. */ +#define KEY_MIN_INTERESTING KEY_MUTE +#define KEY_MAX 0x2ff +#define KEY_CNT (KEY_MAX + 1) + +/* + * Relative axes + */ + +#define REL_X 0x00 +#define REL_Y 0x01 +#define REL_Z 0x02 +#define REL_RX 0x03 +#define REL_RY 0x04 +#define REL_RZ 0x05 +#define REL_HWHEEL 0x06 +#define REL_DIAL 0x07 +#define REL_WHEEL 0x08 +#define REL_MISC 0x09 +/* + * 0x0a is reserved and should not be used in input drivers. + * It was used by HID as REL_MISC+1 and userspace needs to detect if + * the next REL_* event is correct or is just REL_MISC + n. + * We define here REL_RESERVED so userspace can rely on it and detect + * the situation described above. + */ +#define REL_RESERVED 0x0a +#define REL_WHEEL_HI_RES 0x0b +#define REL_HWHEEL_HI_RES 0x0c +#define REL_MAX 0x0f +#define REL_CNT (REL_MAX + 1) + +/* + * Absolute axes + */ + +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_Z 0x02 +#define ABS_RX 0x03 +#define ABS_RY 0x04 +#define ABS_RZ 0x05 +#define ABS_THROTTLE 0x06 +#define ABS_RUDDER 0x07 +#define ABS_WHEEL 0x08 +#define ABS_GAS 0x09 +#define ABS_BRAKE 0x0a +#define ABS_HAT0X 0x10 +#define ABS_HAT0Y 0x11 +#define ABS_HAT1X 0x12 +#define ABS_HAT1Y 0x13 +#define ABS_HAT2X 0x14 +#define ABS_HAT2Y 0x15 +#define ABS_HAT3X 0x16 +#define ABS_HAT3Y 0x17 +#define ABS_PRESSURE 0x18 +#define ABS_DISTANCE 0x19 +#define ABS_TILT_X 0x1a +#define ABS_TILT_Y 0x1b +#define ABS_TOOL_WIDTH 0x1c + +#define ABS_VOLUME 0x20 +#define ABS_PROFILE 0x21 + +#define ABS_MISC 0x28 + +/* + * 0x2e is reserved and should not be used in input drivers. + * It was used by HID as ABS_MISC+6 and userspace needs to detect if + * the next ABS_* event is correct or is just ABS_MISC + n. + * We define here ABS_RESERVED so userspace can rely on it and detect + * the situation described above. + */ +#define ABS_RESERVED 0x2e + +#define ABS_MT_SLOT 0x2f /* MT slot being modified */ +#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ +#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */ +#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */ +#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */ +#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */ +#define ABS_MT_POSITION_X 0x35 /* Center X touch position */ +#define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */ +#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */ +#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */ +#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */ +#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */ +#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */ +#define ABS_MT_TOOL_X 0x3c /* Center X tool position */ +#define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */ + + +#define ABS_MAX 0x3f +#define ABS_CNT (ABS_MAX + 1) + +/* + * Switch events + */ + +#define SW_LID 0x00 /* set = lid shut */ +#define SW_TABLET_MODE 0x01 /* set = tablet mode */ +#define SW_HEADPHONE_INSERT 0x02 /* set = inserted */ +#define SW_RFKILL_ALL \ + 0x03 /* rfkill master switch, type "any" \ + set = radio enabled */ +#define SW_RADIO SW_RFKILL_ALL /* deprecated */ +#define SW_MICROPHONE_INSERT 0x04 /* set = inserted */ +#define SW_DOCK 0x05 /* set = plugged into dock */ +#define SW_LINEOUT_INSERT 0x06 /* set = inserted */ +#define SW_JACK_PHYSICAL_INSERT 0x07 /* set = mechanical switch set */ +#define SW_VIDEOOUT_INSERT 0x08 /* set = inserted */ +#define SW_CAMERA_LENS_COVER 0x09 /* set = lens covered */ +#define SW_KEYPAD_SLIDE 0x0a /* set = keypad slide out */ +#define SW_FRONT_PROXIMITY 0x0b /* set = front proximity sensor active */ +#define SW_ROTATE_LOCK 0x0c /* set = rotate locked/disabled */ +#define SW_LINEIN_INSERT 0x0d /* set = inserted */ +#define SW_MUTE_DEVICE 0x0e /* set = device disabled */ +#define SW_PEN_INSERTED 0x0f /* set = pen inserted */ +#define SW_MACHINE_COVER 0x10 /* set = cover closed */ +#define SW_MAX 0x10 +#define SW_CNT (SW_MAX + 1) + +/* + * Misc events + */ + +#define MSC_SERIAL 0x00 +#define MSC_PULSELED 0x01 +#define MSC_GESTURE 0x02 +#define MSC_RAW 0x03 +#define MSC_SCAN 0x04 +#define MSC_TIMESTAMP 0x05 +#define MSC_MAX 0x07 +#define MSC_CNT (MSC_MAX + 1) + +/* + * LEDs + */ + +#define LED_NUML 0x00 +#define LED_CAPSL 0x01 +#define LED_SCROLLL 0x02 +#define LED_COMPOSE 0x03 +#define LED_KANA 0x04 +#define LED_SLEEP 0x05 +#define LED_SUSPEND 0x06 +#define LED_MUTE 0x07 +#define LED_MISC 0x08 +#define LED_MAIL 0x09 +#define LED_CHARGING 0x0a +#define LED_MAX 0x0f +#define LED_CNT (LED_MAX + 1) + +/* + * Autorepeat values + */ + +#define REP_DELAY 0x00 +#define REP_PERIOD 0x01 +#define REP_MAX 0x01 +#define REP_CNT (REP_MAX + 1) + +/* + * Sounds + */ + +#define SND_CLICK 0x00 +#define SND_BELL 0x01 +#define SND_TONE 0x02 +#define SND_MAX 0x07 +#define SND_CNT (SND_MAX + 1) + +#endif diff --git a/list.h b/list.h new file mode 100644 index 0000000..ed7663c --- /dev/null +++ b/list.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +#define container_of(ptr, type, member) \ + ((type *) ((void *) ptr - offsetof(type, member))) + +#define list_entry(ptr, type, member) container_of(ptr, type, member) + +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +#define list_prev_entry(pos, member) \ + list_entry((pos)->member.prev, typeof(*(pos)), member) + +#define list_next_entry(pos, member) \ + list_entry((pos)->member.next, typeof(*(pos)), member) + +#define list_entry_is_head(pos, head, member) (&pos->member == (head)) + +#define list_for_each(pos, head) \ + for ((pos) = (head)->next; (pos) != (head); (pos) = (pos)->next) + +#define list_for_each_safe(pos, _next, head) \ + for (pos = (head)->next, _next = (pos)->next; (pos) != (head); \ + (pos) = _next, _next = (pos)->next) + +#define list_for_each_entry(pos, head, member) \ + for (pos = list_first_entry(head, __typeof__(*pos), member); \ + &pos->member != (head); pos = list_next_entry(pos, member)) + +#define LIST_HEAD_INIT(name) \ + { \ + .prev = (&name), .next = (&name) \ + } + +#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) + +struct list_head { + struct list_head *next, *prev; +}; + +static inline void INIT_LIST_HEAD(struct list_head *list) +{ + list->prev = list; + list->next = list; +} + +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +static int list_is_last(const struct list_head *list, + const struct list_head *head) +{ + return list->next == head; +} + +static inline void list_add(struct list_head *new, struct list_head *list) +{ + new->prev = list->prev; + new->next = list; + list->prev->next = new; + list->prev = new; +} + +static inline void list_del(struct list_head *list) +{ + list->next->prev = list->prev; + list->prev->next = list->next; +} + +static void list_del_init(struct list_head *entry) +{ + list_del(entry); + INIT_LIST_HEAD(entry); +} + +static inline void list_move(struct list_head *list, struct list_head *new_head) +{ + list_del(list); + list_add(new_head, list); +} diff --git a/main.c b/main.c index 4a92113..f5d0854 100644 --- a/main.c +++ b/main.c @@ -11,6 +11,7 @@ #include "device.h" #include "riscv.h" #include "riscv_private.h" +#include "window.h" #define PRIV(x) ((emu_state_t *) x->priv) @@ -72,6 +73,40 @@ static void emu_update_vblk_interrupts(vm_t *vm) } #endif +#if SEMU_HAS(VIRTIOGPU) +static void emu_update_vgpu_interrupts(vm_t *vm) +{ + emu_state_t *data = PRIV(vm->hart[0]); + if (data->vgpu.InterruptStatus) + data->plic.active |= IRQ_VGPU_BIT; + else + data->plic.active &= ~IRQ_VGPU_BIT; + plic_update_interrupts(vm, &data->plic); +} +#endif + +#if SEMU_HAS(VIRTIOINPUT) +static void emu_update_vinput_keyboard_interrupts(vm_t *vm) +{ + emu_state_t *data = PRIV(vm->hart[0]); + if (data->vkeyboard.InterruptStatus) + data->plic.active |= IRQ_VINPUT_KEYBOARD_BIT; + else + data->plic.active &= ~IRQ_VINPUT_KEYBOARD_BIT; + plic_update_interrupts(vm, &data->plic); +} + +static void emu_update_vinput_mouse_interrupts(vm_t *vm) +{ + emu_state_t *data = PRIV(vm->hart[0]); + if (data->vmouse.InterruptStatus) + data->plic.active |= IRQ_VINPUT_MOUSE_BIT; + else + data->plic.active &= ~IRQ_VINPUT_MOUSE_BIT; + plic_update_interrupts(vm, &data->plic); +} +#endif + static void emu_update_timer_interrupt(hart_t *hart) { emu_state_t *data = PRIV(hart); @@ -121,6 +156,24 @@ static void mem_load(hart_t *hart, clint_read(hart, &data->clint, addr & 0xFFFFF, width, value); clint_update_interrupts(hart, &data->clint); return; +#if SEMU_HAS(VIRTIOGPU) + case 0x44: /* virtio-gpu */ + virtio_gpu_read(hart, &data->vgpu, addr & 0xFFFFF, width, value); + emu_update_vgpu_interrupts(hart->vm); + return; +#endif +#if SEMU_HAS(VIRTIOINPUT) + case 0x45: /* virtio-input keyboard */ + virtio_input_read(hart, &data->vkeyboard, addr & 0xFFFFF, width, + value); + emu_update_vinput_keyboard_interrupts(hart->vm); + return; + case 0x46: /* virtio-input mouse */ + virtio_input_read(hart, &data->vmouse, addr & 0xFFFFF, width, + value); + emu_update_vinput_mouse_interrupts(hart->vm); + return; +#endif } } vm_set_exception(hart, RV_EXC_LOAD_FAULT, hart->exc_val); @@ -166,6 +219,24 @@ static void mem_store(hart_t *hart, clint_write(hart, &data->clint, addr & 0xFFFFF, width, value); clint_update_interrupts(hart, &data->clint); return; +#if SEMU_HAS(VIRTIOGPU) + case 0x44: /* virtio-gpu */ + virtio_gpu_write(hart, &data->vgpu, addr & 0xFFFFF, width, value); + emu_update_vgpu_interrupts(hart->vm); + return; +#endif +#if SEMU_HAS(VIRTIOINPUT) + case 0x45: /* virtio-input */ + virtio_input_write(hart, &data->vkeyboard, addr & 0xFFFFF, width, + value); + emu_update_vinput_keyboard_interrupts(hart->vm); + return; + case 0x46: /* virtio-input mouse */ + virtio_input_write(hart, &data->vmouse, addr & 0xFFFFF, width, + value); + emu_update_vinput_mouse_interrupts(hart->vm); + return; +#endif } } vm_set_exception(hart, RV_EXC_STORE_FAULT, hart->exc_val); @@ -581,6 +652,21 @@ static int semu_start(int argc, char **argv) emu.vblk.ram = emu.ram; emu.disk = virtio_blk_init(&(emu.vblk), disk_file); #endif +#if SEMU_HAS(VIRTIOINPUT) + emu.vkeyboard.ram = emu.ram; + virtio_input_init(&(emu.vkeyboard)); + + emu.vmouse.ram = emu.ram; + virtio_input_init(&(emu.vmouse)); +#endif +#if SEMU_HAS(VIRTIOGPU) + semu_virgl_init(); + + emu.vgpu.ram = emu.ram; + virtio_gpu_init(&(emu.vgpu)); + virtio_gpu_add_scanout(&(emu.vgpu), 1024, 768); + window_init(); +#endif /* Emulate */ uint32_t peripheral_update_ctr = 0; @@ -603,6 +689,19 @@ static int semu_start(int argc, char **argv) if (emu.vblk.InterruptStatus) emu_update_vblk_interrupts(&vm); #endif + +#if SEMU_HAS(VIRTIOGPU) + if (emu.vgpu.InterruptStatus) + emu_update_vgpu_interrupts(&vm); +#endif + +#if SEMU_HAS(VIRTIOINPUT) + if (emu.vkeyboard.InterruptStatus) + emu_update_vinput_keyboard_interrupts(&vm); + + if (emu.vmouse.InterruptStatus) + emu_update_vinput_mouse_interrupts(&vm); +#endif } emu_update_timer_interrupt(vm.hart[i]); diff --git a/minimal.dts b/minimal.dts index d83bcfc..7bcb01a 100644 --- a/minimal.dts +++ b/minimal.dts @@ -17,7 +17,8 @@ chosen { bootargs = "earlycon console=ttyS0"; stdout-path = "serial0"; - linux,initrd-start = <0x1f700000>; /* @403 MiB (503 * 1024 * 1024) */ + /* Reserve 65MiB for initrd image */ + linux,initrd-start = <0x1be00000>; /* @406 MiB (446 * 1024 * 1024) */ linux,initrd-end = <0x1fefffff>; /* @511 MiB (511 * 1024 * 1024 - 1) */ }; @@ -63,5 +64,27 @@ interrupts = <3>; }; #endif + +#if SEMU_FEATURE_VIRTIOGPU + gpu0: virtio@4400000 { + compatible = "virtio,mmio"; + reg = <0x4400000 0x200>; + interrupts = <4>; + }; +#endif + +#if SEMU_FEATURE_VIRTIOINPUT + keyboard0: virtio@4500000 { + compatible = "virtio,mmio"; + reg = <0x4500000 0x200>; + interrupts = <5>; + }; + + mouse0: virtio@4600000 { + compatible = "virtio,mmio"; + reg = <0x4600000 0x200>; + interrupts = <6>; + }; +#endif }; }; diff --git a/scripts/build-image.sh b/scripts/build-image.sh index 77916b5..c6a2747 100755 --- a/scripts/build-image.sh +++ b/scripts/build-image.sh @@ -38,16 +38,84 @@ function do_buildroot function do_linux { ASSERT git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git -b linux-6.1.y --depth=1 + mkdir -p linux/out cp -f configs/linux.config linux/.config export PATH="$PWD/buildroot/output/host/bin:$PATH" export CROSS_COMPILE=riscv32-buildroot-linux-gnu- export ARCH=riscv + export INSTALL_MOD_PATH="out" pushd linux ASSERT make olddefconfig ASSERT make $PARALLEL + ASSERT make modules_install cp -f arch/riscv/boot/Image ../Image popd } +function do_directfb +{ + export PATH=$PATH:$PWD/buildroot/output/host/bin + export BUILDROOT_OUT=$PWD/buildroot/output/ + mkdir -p directfb + + # Build DirectFB2 + ASSERT git clone https://github.com/directfb2/DirectFB2.git + pushd DirectFB2 + ASSERT wget https://gist.githubusercontent.com/shengwen-tw/098da8c41ba7fbb9162ddaa4ff62b29e/raw/6bcd3adef7d4932fa4f56a18aee9ebc1e1bd0665/riscv-cross-file + ASSERT meson -Ddrmkms=true --cross-file riscv-cross-file build/riscv + ASSERT meson compile -C build/riscv + DESTDIR=$BUILDROOT_OUT/host/riscv32-buildroot-linux-gnu/sysroot meson install -C build/riscv + DESTDIR=../../../directfb meson install -C build/riscv + popd + + # Build DirectFB2 examples + ASSERT git clone https://github.com/directfb2/DirectFB-examples.git + pushd DirectFB-examples/ + ASSERT wget https://gist.githubusercontent.com/shengwen-tw/098da8c41ba7fbb9162ddaa4ff62b29e/raw/6bcd3adef7d4932fa4f56a18aee9ebc1e1bd0665/riscv-cross-file + ASSERT meson --cross-file riscv-cross-file build/riscv + ASSERT meson compile -C build/riscv + DESTDIR=../../../directfb meson install -C build/riscv + popd +} + +function do_directfb_gl +{ + export BUILDROOT_OUT=$PWD/buildroot/output/ + + ASSERT git clone https://github.com/directfb2/DirectFBGL-EGL.git + pushd DirectFBGL-EGL + ASSERT wget https://gist.githubusercontent.com/shengwen-tw/098da8c41ba7fbb9162ddaa4ff62b29e/raw/6bcd3adef7d4932fa4f56a18aee9ebc1e1bd0665/riscv-cross-file + ASSERT meson --cross-file riscv-cross-file build/riscv + ASSERT meson compile -C build/riscv + DESTDIR=$BUILDROOT_OUT/host/riscv32-buildroot-linux-gnu/sysroot meson install -C build/riscv + popd +} + +function do_gles2_driver +{ + ASSERT git clone https://github.com/directfb2/DirectFB2-gles2.git + pushd DirectFB2-gles2 + ASSERT wget https://gist.githubusercontent.com/shengwen-tw/098da8c41ba7fbb9162ddaa4ff62b29e/raw/6bcd3adef7d4932fa4f56a18aee9ebc1e1bd0665/riscv-cross-file + ASSERT meson --cross-file riscv-cross-file build/riscv + ASSERT meson compile -C build/riscv + popd +} + +function do_yagears +{ + ASSERT git clone https://github.com/caramelli/yagears.git + pushd yagears + ASSERT wget https://gist.githubusercontent.com/shengwen-tw/098da8c41ba7fbb9162ddaa4ff62b29e/raw/6bcd3adef7d4932fa4f56a18aee9ebc1e1bd0665/riscv-cross-file + ASSERT meson --cross-file riscv-cross-file build/riscv + ASSERT meson compile -C build/riscv + popd +} + do_buildroot && OK do_linux && OK +do_directfb && OK + +# Subject to change +do_directfb_gl && OK +do_gles2_driver & OK +do_yagears && OK diff --git a/scripts/initrd-image.sh b/scripts/initrd-image.sh new file mode 100755 index 0000000..05d7bdb --- /dev/null +++ b/scripts/initrd-image.sh @@ -0,0 +1,28 @@ +#!/usr/bin/bash + +IMG=ext4.img +KERNEL_VER=$(git -C linux/ tag | sed "s/^v//") +SRC=linux/out/lib/modules/$KERNEL_VER +DEST=rootfs/lib/modules/$KERNEL_VER + +# Add path of kernel modules to load with dependency file, for example: +# FILES='kernel/drivers/gpu/drm/drm.ko +# modules.dep' +FILES='' + +for file in $FILES; do + mkdir -p `dirname $DEST/$file` + cp -f $SRC/$file $DEST/$file +done + +cp -r directfb rootfs +cp target/run.sh rootfs + +# DirectFB-example requires ~60MiB of space +dd if=/dev/zero of=${IMG} bs=4k count=20000 +mkfs.ext4 -F ${IMG} -d rootfs + +rm -rf rootfs + +# show image size +du -h ${IMG} diff --git a/target/run.sh b/target/run.sh new file mode 100755 index 0000000..7ac4e2c --- /dev/null +++ b/target/run.sh @@ -0,0 +1,15 @@ +#!/usr/bin/bash + +# Install kernel modules +if [ -d "/lib/modules/" ]; then + mkdir -p /lib/modules/ + cp -r ./lib/modules/* /lib/modules/ +fi + +# Install DirectFB and examples +cp -r ./usr/local /usr/ +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib +export PATH=$PATH:/usr/local/bin/ + +# Load kernel modules if exist +# modprobe virtio-gpu diff --git a/virtio-gpu.c b/virtio-gpu.c new file mode 100644 index 0000000..cfb9f63 --- /dev/null +++ b/virtio-gpu.c @@ -0,0 +1,1430 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "device.h" +#include "list.h" +#include "riscv.h" +#include "riscv_private.h" +#include "virtio.h" +#include "window.h" + +#define VIRTIO_F_VERSION_1 1 + +#define VIRTIO_GPU_EVENT_DISPLAY (1 << 0) +#define VIRTIO_GPU_F_VIRGL (1 << 0) +#define VIRTIO_GPU_F_EDID (1 << 1) +#define VIRTIO_GPU_FLAG_FENCE (1 << 0) + +#define VGPU_QUEUE_NUM_MAX 1024 +#define VGPU_QUEUE (vgpu->queues[vgpu->QueueSel]) + +#define PRIV(x) ((struct vgpu_scanout_info *) x->priv) + +#define STRIDE_SIZE 4096 + +struct vgpu_scanout_info { + uint32_t width; + uint32_t height; + uint32_t enabled; +}; + +struct vgpu_resource_2d { + /* Public: */ + uint32_t scanout_id; + uint32_t format; + uint32_t width; + uint32_t height; + uint32_t stride; + uint32_t bits_per_pixel; + uint32_t *image; + /* Private: */ + uint32_t resource_id; + size_t page_cnt; + struct iovec *iovec; + struct list_head list; +}; + +PACKED(struct vgpu_config { + uint32_t events_read; + uint32_t events_clear; + uint32_t num_scanouts; + uint32_t num_capsets; +}); + +PACKED(struct vgpu_ctrl_hdr { + uint32_t type; + uint32_t flags; + uint64_t fence_id; + uint32_t ctx_id; + uint8_t ring_idx; + uint8_t padding[3]; +}); + +PACKED(struct vgpu_rect { + uint32_t x; + uint32_t y; + uint32_t width; + uint32_t height; +}); + +PACKED(struct vgpu_resp_disp_info { + struct vgpu_ctrl_hdr hdr; + struct virtio_gpu_display_one { + struct vgpu_rect r; + uint32_t enabled; + uint32_t flags; + } pmodes[VIRTIO_GPU_MAX_SCANOUTS]; +}); + +PACKED(struct vgpu_res_create_2d { + struct vgpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t format; + uint32_t width; + uint32_t height; +}); + +PACKED(struct vgpu_res_unref { + struct vgpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t padding; +}); + +PACKED(struct vgpu_set_scanout { + struct vgpu_ctrl_hdr hdr; + struct vgpu_rect r; + uint32_t scanout_id; + uint32_t resource_id; +}); + +PACKED(struct vgpu_res_flush { + struct vgpu_ctrl_hdr hdr; + struct vgpu_rect r; + uint32_t resource_id; + uint32_t padding; +}); + +PACKED(struct vgpu_trans_to_host_2d { + struct vgpu_ctrl_hdr hdr; + struct vgpu_rect r; + uint64_t offset; + uint32_t resource_id; + uint32_t padding; +}); + +PACKED(struct vgpu_res_attach_backing { + struct vgpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t nr_entries; +}); + +PACKED(struct vgpu_mem_entry { + uint64_t addr; + uint32_t length; + uint32_t padding; +}); + +PACKED(struct vgpu_resp_edid { + struct vgpu_ctrl_hdr hdr; + uint32_t size; + uint32_t padding; + char edid[1024]; +}); + +PACKED(struct vgpu_get_capset_info { + struct vgpu_ctrl_hdr hdr; + uint32_t capset_index; + uint32_t padding; +}); + +PACKED(struct vgpu_resp_capset_info { + struct vgpu_ctrl_hdr hdr; + uint32_t capset_id; + uint32_t capset_max_version; + uint32_t capset_max_size; + uint32_t padding; +}); + +PACKED(struct virtio_gpu_ctx_create { + struct vgpu_ctrl_hdr hdr; + uint32_t nlen; + uint32_t context_init; + char debug_name[64]; +}); + +/* clang-format off */ +PACKED(struct virtio_gpu_ctx_destroy { + struct vgpu_ctrl_hdr hdr; +}); +/* clang-format on */ + +PACKED(struct virtio_gpu_resource_create_3d { + struct vgpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t target; + uint32_t format; + uint32_t bind; + uint32_t width; + uint32_t height; + uint32_t depth; + uint32_t array_size; + uint32_t last_level; + uint32_t nr_samples; + uint32_t flags; + uint32_t padding; +}); + +PACKED(struct virtio_gpu_ctx_resource { + struct vgpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t padding; +}); + +PACKED(struct virtio_gpu_box { + uint32_t x; + uint32_t y; + uint32_t z; + uint32_t w; + uint32_t h; + uint32_t d; +}); + +PACKED(struct virtio_gpu_transfer_host_3d { + struct vgpu_ctrl_hdr hdr; + struct virtio_gpu_box box; + uint64_t offset; + uint32_t resource_id; + uint32_t level; + uint32_t stride; + uint32_t layer_stride; +}); + +PACKED(struct virtio_gpu_cmd_submit { + struct vgpu_ctrl_hdr hdr; + uint32_t size; + uint32_t num_in_fences; +}); + +PACKED(struct virtio_gpu_resp_map_info { + struct vgpu_ctrl_hdr hdr; + uint32_t map_info; + uint32_t padding; +}); + +static struct vgpu_config vgpu_configs; +static LIST_HEAD(vgpu_res_2d_list); + +static inline void *vgpu_mem_host_to_guest(virtio_gpu_state_t *vgpu, + uint32_t addr) +{ + return (void *) ((uintptr_t) vgpu->ram + addr); +} + +static struct vgpu_resource_2d *create_vgpu_resource_2d(int resource_id) +{ + struct vgpu_resource_2d *res = malloc(sizeof(struct vgpu_resource_2d)); + if (!res) + return NULL; + + res->resource_id = resource_id; + list_add(&res->list, &vgpu_res_2d_list); + return res; +} + +static struct vgpu_resource_2d *acquire_vgpu_resource_2d(uint32_t resource_id) +{ + struct vgpu_resource_2d *res_2d; + list_for_each_entry (res_2d, &vgpu_res_2d_list, list) { + if (res_2d->resource_id == resource_id) + return res_2d; + } + + return NULL; +} + +static int destroy_vgpu_resource_2d(uint32_t resource_id) +{ + struct vgpu_resource_2d *res_2d = acquire_vgpu_resource_2d(resource_id); + + /* Failed to find the resource */ + if (!res_2d) + return -1; + + window_lock(resource_id); + + /* Release the resource */ + free(res_2d->image); + list_del(&res_2d->list); + free(res_2d->iovec); + free(res_2d); + + window_unlock(resource_id); + + return 0; +} + +static void virtio_gpu_set_fail(virtio_gpu_state_t *vgpu) +{ + vgpu->Status |= VIRTIO_STATUS__DEVICE_NEEDS_RESET; + if (vgpu->Status & VIRTIO_STATUS__DRIVER_OK) + vgpu->InterruptStatus |= VIRTIO_INT__CONF_CHANGE; +} + +static inline uint32_t vgpu_preprocess(virtio_gpu_state_t *vgpu, uint32_t addr) +{ + if ((addr >= RAM_SIZE) || (addr & 0b11)) + return virtio_gpu_set_fail(vgpu), 0; + + return addr >> 2; +} + +static void virtio_gpu_update_status(virtio_gpu_state_t *vgpu, uint32_t status) +{ + vgpu->Status |= status; + if (status) + return; + + /* Reset */ + uint32_t *ram = vgpu->ram; + void *priv = vgpu->priv; + uint32_t scanout_num = vgpu_configs.num_scanouts; + memset(vgpu->priv, 0, sizeof(*vgpu->priv)); + memset(vgpu, 0, sizeof(*vgpu)); + vgpu->ram = ram; + vgpu->priv = priv; + vgpu_configs.num_scanouts = scanout_num; + + /* Release all 2D resources */ + struct list_head *curr, *next; + list_for_each_safe (curr, next, &vgpu_res_2d_list) { + struct vgpu_resource_2d *res_2d = + list_entry(curr, struct vgpu_resource_2d, list); + + list_del(&res_2d->list); + free(res_2d->image); + free(res_2d->iovec); + free(res_2d); + } +} + +static void virtio_gpu_get_display_info_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Write display infomation */ + struct vgpu_resp_disp_info *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->hdr.type = VIRTIO_GPU_RESP_OK_DISPLAY_INFO; + + int scanout_num = vgpu_configs.num_scanouts; + for (int i = 0; i < scanout_num; i++) { + response->pmodes[i].r.width = PRIV(vgpu)[i].width; + response->pmodes[i].r.height = PRIV(vgpu)[i].height; + response->pmodes[i].enabled = PRIV(vgpu)[i].enabled; + } + + /* Update write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_resource_create_2d_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct vgpu_res_create_2d *request = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Create 2D resource */ + struct vgpu_resource_2d *res_2d = + create_vgpu_resource_2d(request->resource_id); + + if (!res_2d) { + fprintf(stderr, "%s(): Failed to allocate 2D resource\n", __func__); + virtio_gpu_set_fail(vgpu); + return; + } + + /* Select image formats */ + uint32_t bits_per_pixel; + + switch (request->format) { + case VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: + bits_per_pixel = 32; + break; + case VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM: + bits_per_pixel = 32; + break; + case VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM: + bits_per_pixel = 32; + break; + case VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM: + bits_per_pixel = 32; + break; + case VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM: + bits_per_pixel = 32; + break; + case VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM: + bits_per_pixel = 32; + break; + case VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM: + bits_per_pixel = 32; + break; + case VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM: + bits_per_pixel = 32; + break; + default: + fprintf(stderr, "%s(): Unsupported format %d\n", __func__, + request->format); + virtio_gpu_set_fail(vgpu); + return; + } + + uint32_t bytes_per_pixel = bits_per_pixel / 8; + + /* Set 2D resource */ + res_2d->width = request->width; + res_2d->height = request->height; + res_2d->format = request->format; + res_2d->bits_per_pixel = bits_per_pixel; + res_2d->stride = STRIDE_SIZE; + res_2d->image = malloc(bytes_per_pixel * (request->width + res_2d->stride) * + request->height); + + /* Failed to create image buffer */ + if (!res_2d->image) { + fprintf(stderr, "%s(): Failed to allocate image buffer\n", __func__); + virtio_gpu_set_fail(vgpu); + return; + } + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_resource_unref_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct vgpu_res_unref *request = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Destroy 2D resource */ + int result = destroy_vgpu_resource_2d(request->resource_id); + + if (result != 0) { + fprintf(stderr, "%s(): Failed to destroy 2D resource\n", __func__); + virtio_gpu_set_fail(vgpu); + return; + } + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(struct vgpu_ctrl_hdr)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static uint8_t virtio_gpu_generate_edid_checksum(uint8_t *edid, size_t size) +{ + uint8_t sum = 0; + + for (size_t i = 0; i < size; i++) + sum += edid[i]; + + return 0x100 - sum; +} + +static void virtio_gpu_generate_edid(uint8_t *edid, int width_cm, int height_cm) +{ + /* Check: + * "VESA ENHANCED EXTENDED DISPLAY IDENTIFICATION DATA STANDARD" + * (Defines EDID Structure Version 1, Revision 4) + */ + + memset(edid, 0, 128); + + /* EDID header */ + edid[0] = 0x00; + edid[1] = 0xff; + edid[2] = 0xff; + edid[3] = 0xff; + edid[4] = 0xff; + edid[5] = 0xff; + edid[6] = 0xff; + edid[7] = 0x00; + + /* ISA (Industry Standard Architecture) + * Plug and Play Device Identifier (PNPID) */ + char manufacture[3] = {'T', 'W', 'N'}; + + /* Vendor ID uses 2 bytes to store 3 characters, where 'A' starts as 1 */ + uint16_t vendor_id = ((((manufacture[0] - '@') & 0b11111) << 10) | + (((manufacture[1] - '@') & 0b11111) << 5) | + (((manufacture[2] - '@') & 0b11111) << 0)); + /* Convert vendor ID to big-endian order */ + edid[8] = vendor_id >> 8; + edid[9] = vendor_id && 0xff; + + /* Product code (all zeros if unused) */ + memset(&edid[10], 0, 6); + + /* Week of manufacture (1-54) */ + edid[16] = 0; + /* Year of manufacture (starts from 1990) */ + edid[17] = 2023 - 1990; + + /* EDID 1.4 (Version 1, Revision 4) */ + edid[18] = 1; /* Version number */ + edid[19] = 4; /* Revision number */ + + /* Video input definition */ + uint8_t signal_interface = 0b1 << 7; /* digital */ + uint8_t color_bit_depth = 0b010 << 4; /* 8 bits per primary color */ + uint8_t interface_type = 0b101; /* DisplayPort is supported */ + edid[20] = signal_interface | color_bit_depth | interface_type; + + /* Screen size or aspect ratio */ + edid[21] = width_cm; /* Horizontal screen size (1cm - 255cm) */ + edid[22] = height_cm; /* Vertical screen size (1cm - 255cm) */ + + /* Gamma value */ + edid[23] = 1; /* Assigned with the minimum value */ + + /* Feature support */ + uint8_t power_management = 0 << 4; /* standby, suspend and active-off + * modes are not supported */ + uint8_t color_type = 0 << 2; /* ignored as it is for the analog display */ + uint8_t other_flags = 0b110; /* [2]: sRGB as default color space + * [1]: Prefered timing mode with native format + * [0]: Non-continuys frequency */ + edid[24] = power_management | color_type | other_flags; + + /* Established timmings: These are the default timmings defined by the + * VESA. Each bit represents 1 configuration. For now, we enable the + * timming configurations of 1024x768@60Hz only */ + edid[35] = 0b00000000; + edid[36] = 0b00001000; + edid[37] = 0b00000000; + + /* Standard timmings: 16 bytes data start from edid[38] to edid[54] as + * additional timming configurations with 2 bytes for each to define + * the horizontal pixel number, aspect ratio, and refresh rate. */ + + /* Extension block count number */ + edid[126] = 0; /* No other extension blocks are defined */ + + /* Checksum of the first (and the only) extension block */ + edid[127] = virtio_gpu_generate_edid_checksum(edid, 127); +} + +static void virtio_gpu_get_edid_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Generate the display EDID */ + struct vgpu_resp_edid edid = { + .hdr = {.type = VIRTIO_GPU_RESP_OK_EDID}, + .size = 128 /* One EDID extension block only */ + }; + virtio_gpu_generate_edid((uint8_t *) edid.edid, 0, 0); + + /* Write EDID response */ + struct vgpu_resp_edid *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + memcpy(response, &edid, sizeof(*response)); + + /* return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_get_capset_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Write capability set */ + struct vgpu_resp_capset_info *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->hdr.type = VIRTIO_GPU_RESP_OK_CAPSET_INFO; + response->capset_id = VIRTIO_GPU_CAPSET_VIRGL2; + response->capset_max_version = 1; + response->capset_max_size = 0; + + /* Update write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_set_scanout_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct vgpu_set_scanout *request = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Acquire 2D resource */ + struct vgpu_resource_2d *res_2d = + acquire_vgpu_resource_2d(request->resource_id); + + /* Linux's virtio-gpu driver may send scanout command + * even if the resource does not exist */ + if (res_2d) + /* Set scanout ID to proper 2D resource */ + res_2d->scanout_id = request->scanout_id; + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_resource_flush_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct vgpu_res_flush *request = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Acquire 2D resource */ + struct vgpu_resource_2d *res_2d = + acquire_vgpu_resource_2d(request->resource_id); + + /* Trigger display window rendering */ + window_render((struct gpu_resource *) res_2d); + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_transfer_to_host_2d_handler( + virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct vgpu_trans_to_host_2d *req = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Acquire 2D resource */ + struct vgpu_resource_2d *res_2d = + acquire_vgpu_resource_2d(req->resource_id); + + if (!res_2d) { + fprintf(stderr, "%s(): Failed to find 2D resource\n", __func__); + virtio_gpu_set_fail(vgpu); + return; + } + + /* Check image boundary */ + if (req->r.x > res_2d->width || req->r.y > res_2d->height || + req->r.width > res_2d->width || req->r.height > res_2d->height || + req->r.x + req->r.width > res_2d->width || + req->r.y + req->r.height > res_2d->height) { + fprintf(stderr, "%s(): Invalid image size\n", __func__); + virtio_gpu_set_fail(vgpu); + return; + } + + /* Transfer frame data from guest to host */ + uint32_t stride = res_2d->stride; + uint32_t bpp = res_2d->bits_per_pixel / 8; /* Bytes per pixel */ + uint32_t width = + (req->r.width < res_2d->width) ? req->r.width : res_2d->width; + uint32_t height = + (req->r.height < res_2d->height) ? req->r.height : res_2d->height; + void *img_data = (void *) res_2d->image; + + for (uint32_t h = 0; h < height; h++) { + size_t src_offset = req->offset + stride * h; + size_t dest_offset = (req->r.y + h) * stride + (req->r.x * bpp); + void *dest = (void *) ((uintptr_t) img_data + dest_offset); + size_t done = 0; + size_t total = width * bpp; + + for (uint32_t i = 0; i < res_2d->page_cnt; i++) { + /* Skip empty pages */ + if (res_2d->iovec[i].iov_base == 0 || res_2d->iovec[i].iov_len == 0) + continue; + + if (src_offset < res_2d->iovec[i].iov_len) { + /* Source offset is in the image coordinate. The address to + * copy from is the page base address plus with the offset + */ + void *src = (void *) ((uintptr_t) res_2d->iovec[i].iov_base + + src_offset); + + /* Take as much as data of current page can provide */ + size_t remained = total - done; + size_t page_avail = res_2d->iovec[i].iov_len - src_offset; + size_t nbytes = (remained < page_avail) ? remained : page_avail; + + /* Copy to 2D resource buffer */ + memcpy((void *) ((uintptr_t) dest + done), src, nbytes); + + /* If there is still data left to read, but current page is + * exhausted, we need to read from the beginning of the next + * page, where its offset should be 0 */ + src_offset = 0; + + /* Count the total received bytes so far */ + done += nbytes; + + /* Data transfering of current scanline is complete */ + if (done >= total) + break; + } else { + /* Keep substracting until reaching the page */ + src_offset -= res_2d->iovec[i].iov_len; + } + } + } + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + struct vgpu_ctrl_hdr res_no_data = {.type = VIRTIO_GPU_RESP_OK_NODATA}; + memcpy(response, &res_no_data, sizeof(struct vgpu_ctrl_hdr)); + + /* Update write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_resource_attach_backing_handler( + virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct vgpu_res_attach_backing *backing_info = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + struct vgpu_mem_entry *pages = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + /* Acquire 2D resource */ + struct vgpu_resource_2d *res_2d = + acquire_vgpu_resource_2d(backing_info->resource_id); + + /* Dispatch page memories to the 2D resource */ + res_2d->page_cnt = backing_info->nr_entries; + res_2d->iovec = malloc(sizeof(struct iovec) * backing_info->nr_entries); + struct vgpu_mem_entry *mem_entries = (struct vgpu_mem_entry *) pages; + + for (size_t i = 0; i < backing_info->nr_entries; i++) { + /* Attach address and length of i-th page to the 2D resource */ + res_2d->iovec[i].iov_base = + vgpu_mem_host_to_guest(vgpu, mem_entries[i].addr); + res_2d->iovec[i].iov_len = mem_entries[i].length; + + /* Corrupted page address */ + if (!res_2d->iovec[i].iov_base) { + fprintf(stderr, "%s(): Invalid page address\n", __func__); + virtio_gpu_set_fail(vgpu); + return; + } + } + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[2].addr); + + memset(response, 0, sizeof(*response)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_update_cursor_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_move_cursor_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_ctx_create_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct virtio_gpu_ctx_create *request = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Create 3D context with VirGL */ + virgl_renderer_context_create(request->hdr.ctx_id, request->nlen, + request->debug_name); + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_ctx_destroy_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct virtio_gpu_ctx_destroy *request = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Destroy 3D context with VirGL */ + virgl_renderer_context_destroy(request->hdr.ctx_id); + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_attach_resource_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct virtio_gpu_ctx_resource *request = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Attach 3D context to resource with VirGL */ + virgl_renderer_ctx_attach_resource(request->hdr.ctx_id, + request->resource_id); + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_detach_resource_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct virtio_gpu_ctx_resource *request = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Detach 3D context and resource with VirGL */ + virgl_renderer_ctx_detach_resource(request->hdr.ctx_id, + request->resource_id); + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_resource_create_3d_handler( + virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct virtio_gpu_resource_create_3d *request = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Create 3D resource with VirGL */ + struct virgl_renderer_resource_create_args args = { + .handle = request->resource_id, + .target = request->target, + .format = request->format, + .bind = request->bind, + .width = request->width, + .height = request->height, + .depth = request->depth, + .array_size = request->array_size, + .last_level = request->last_level, + .nr_samples = request->nr_samples, + .flags = request->flags, + }; + virgl_renderer_resource_create(&args, NULL, 0); + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + memset(response, 0, sizeof(*response)); + response->type = VIRTIO_GPU_RESP_OK_NODATA; + + /* Return write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_transfer_to_host_3d_handler( + virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct virtio_gpu_transfer_host_3d *req = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Transfer data from target to host with VirGL */ + virgl_renderer_transfer_read_iov(req->resource_id, req->hdr.ctx_id, + req->level, req->stride, req->layer_stride, + (struct virgl_box *) &req->box, + req->offset, NULL, 0); + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + struct vgpu_ctrl_hdr res_no_data = {.type = VIRTIO_GPU_RESP_OK_NODATA}; + memcpy(response, &res_no_data, sizeof(struct vgpu_ctrl_hdr)); + + /* Update write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_transfer_from_host_3d_handler( + virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct virtio_gpu_transfer_host_3d *req = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Transfer data from host to target with VirGL */ + virgl_renderer_transfer_write_iov( + req->resource_id, req->hdr.ctx_id, req->level, req->stride, + req->layer_stride, (struct virgl_box *) &req->box, req->offset, NULL, + 0); + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + struct vgpu_ctrl_hdr res_no_data = {.type = VIRTIO_GPU_RESP_OK_NODATA}; + memcpy(response, &res_no_data, sizeof(struct vgpu_ctrl_hdr)); + + /* Update write length */ + *plen = sizeof(*response); +} + +static void virtio_gpu_cmd_submit_3d_handler(virtio_gpu_state_t *vgpu, + struct virtq_desc *vq_desc, + uint32_t *plen) +{ + /* Read request */ + struct virtio_gpu_cmd_submit *req = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Submit 3D command with VirGL */ + // TODO + void *buffer = NULL; + int ndw = 0; + virgl_renderer_submit_cmd(buffer, req->hdr.ctx_id, ndw); + + /* Write response */ + struct vgpu_ctrl_hdr *response = + vgpu_mem_host_to_guest(vgpu, vq_desc[1].addr); + + struct vgpu_ctrl_hdr res_no_data = {.type = VIRTIO_GPU_RESP_OK_NODATA}; + memcpy(response, &res_no_data, sizeof(struct vgpu_ctrl_hdr)); + + /* Update write length */ + *plen = sizeof(*response); +} + +static int virtio_gpu_desc_handler(virtio_gpu_state_t *vgpu, + const virtio_gpu_queue_t *queue, + uint32_t desc_idx, + uint32_t *plen) +{ + /* virtio-gpu uses 3 virtqueue descriptors at most */ + struct virtq_desc vq_desc[3]; + + /* Collect descriptors */ + for (int i = 0; i < 3; i++) { + /* The size of the `struct virtq_desc` is 4 words */ + uint32_t *desc = &vgpu->ram[queue->QueueDesc + desc_idx * 4]; + + /* Retrieve the fields of current descriptor */ + vq_desc[i].addr = desc[0]; + vq_desc[i].len = desc[2]; + vq_desc[i].flags = desc[3]; + desc_idx = desc[3] >> 16; /* vq_desc[desc_cnt].next */ + + /* Leave the loop if next-flag is not set */ + if (!(vq_desc[i].flags & VIRTIO_DESC_F_NEXT)) + break; + } + + /* Process the header */ + struct vgpu_ctrl_hdr *header = + vgpu_mem_host_to_guest(vgpu, vq_desc[0].addr); + + /* Process the command */ + switch (header->type) { + case VIRTIO_GPU_CMD_GET_DISPLAY_INFO: + virtio_gpu_get_display_info_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: + virtio_gpu_resource_create_2d_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_RESOURCE_UNREF: + virtio_gpu_cmd_resource_unref_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_GET_EDID: + virtio_gpu_get_edid_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_GET_CAPSET_INFO: + virtio_gpu_get_capset_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_SET_SCANOUT: + virtio_gpu_cmd_set_scanout_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_RESOURCE_FLUSH: + virtio_gpu_cmd_resource_flush_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: + virtio_gpu_cmd_transfer_to_host_2d_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING: + virtio_gpu_cmd_resource_attach_backing_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_UPDATE_CURSOR: + virtio_gpu_cmd_update_cursor_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_MOVE_CURSOR: + virtio_gpu_cmd_move_cursor_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_CTX_CREATE: + printf("@@@VIRTIO_GPU_CMD_CTX_CREATE\n"); + virtio_gpu_cmd_ctx_create_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_CTX_DESTROY: + printf("@@@VIRTIO_GPU_CMD_CTX_DESTROY\n"); + virtio_gpu_cmd_ctx_destroy_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE: + printf("@@@VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE\n"); + virtio_gpu_cmd_attach_resource_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE: + printf("@@@VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE\n"); + virtio_gpu_cmd_detach_resource_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_RESOURCE_CREATE_3D: + printf("@@@VIRTIO_GPU_CMD_RESOURCE_CREATE_3D\n"); + virtio_gpu_cmd_resource_create_3d_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D: + printf("@@@VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D\n"); + virtio_gpu_cmd_transfer_to_host_3d_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D: + printf("@@@VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D\n"); + virtio_gpu_cmd_transfer_from_host_3d_handler(vgpu, vq_desc, plen); + break; + case VIRTIO_GPU_CMD_SUBMIT_3D: + printf("@@@VIRTIO_GPU_CMD_SUBMIT_3D\n"); + virtio_gpu_cmd_submit_3d_handler(vgpu, vq_desc, plen); + break; + default: + fprintf(stderr, "%s(): unknown command %d\n", __func__, header->type); + virtio_gpu_set_fail(vgpu); + *plen = 0; + return -1; + } + + return 0; +} + +static void virtio_queue_notify_handler(virtio_gpu_state_t *vgpu, int index) +{ + uint32_t *ram = vgpu->ram; + virtio_gpu_queue_t *queue = &vgpu->queues[index]; + if (vgpu->Status & VIRTIO_STATUS__DEVICE_NEEDS_RESET) + return; + + if (!((vgpu->Status & VIRTIO_STATUS__DRIVER_OK) && queue->ready)) + return virtio_gpu_set_fail(vgpu); + + /* Check for new buffers */ + uint16_t new_avail = ram[queue->QueueAvail] >> 16; + if (new_avail - queue->last_avail > (uint16_t) queue->QueueNum) + return (fprintf(stderr, "%s(): size check failed\n", __func__), + virtio_gpu_set_fail(vgpu)); + + if (queue->last_avail == new_avail) + return; + + /* Process them */ + uint16_t new_used = ram[queue->QueueUsed] >> 16; /* virtq_used.idx (le16) */ + while (queue->last_avail != new_avail) { + /* Obtain the index in the ring buffer */ + uint16_t queue_idx = queue->last_avail % queue->QueueNum; + + /* Since each buffer index occupies 2 bytes but the memory is aligned + * with 4 bytes, and the first element of the available queue is stored + * at ram[queue->QueueAvail + 1], to acquire the buffer index, it + * requires the following array index calculation and bit shifting. + * Check also the `struct virtq_avail` on the spec. + */ + uint16_t buffer_idx = ram[queue->QueueAvail + 1 + queue_idx / 2] >> + (16 * (queue_idx % 2)); + + /* Consume request from the available queue and process the data in the + * descriptor list. + */ + uint32_t len = 0; + int result = virtio_gpu_desc_handler(vgpu, queue, buffer_idx, &len); + if (result != 0) + return virtio_gpu_set_fail(vgpu); + + /* Write used element information (`struct virtq_used_elem`) to the used + * queue */ + uint32_t vq_used_addr = + queue->QueueUsed + 1 + (new_used % queue->QueueNum) * 2; + ram[vq_used_addr] = buffer_idx; /* virtq_used_elem.id (le32) */ + ram[vq_used_addr + 1] = len; /* virtq_used_elem.len (le32) */ + queue->last_avail++; + new_used++; + } + + /* Check le32 len field of `struct virtq_used_elem` on the spec */ + vgpu->ram[queue->QueueUsed] &= MASK(16); /* Reset low 16 bits to zero */ + vgpu->ram[queue->QueueUsed] |= ((uint32_t) new_used) << 16; /* len */ + + /* Send interrupt, unless VIRTQ_AVAIL_F_NO_INTERRUPT is set */ + if (!(ram[queue->QueueAvail] & 1)) + vgpu->InterruptStatus |= VIRTIO_INT__USED_RING; +} + +static bool virtio_gpu_reg_read(virtio_gpu_state_t *vgpu, + uint32_t addr, + uint32_t *value) +{ +#define _(reg) VIRTIO_##reg + switch (addr) { + case _(MagicValue): + *value = 0x74726976; + return true; + case _(Version): + *value = 2; + return true; + case _(DeviceID): + *value = 16; + return true; + case _(VendorID): + *value = VIRTIO_VENDOR_ID; + return true; + case _(DeviceFeatures): + if (vgpu->DeviceFeaturesSel) { /* [63:32] */ + *value = VIRTIO_F_VERSION_1; + } else { /* [31:0] */ + *value = VIRTIO_GPU_F_EDID; +#if SEMU_HAS(VIRGL) + *value |= VIRTIO_GPU_F_VIRGL; +#endif + } + return true; + case _(QueueNumMax): + *value = VGPU_QUEUE_NUM_MAX; + return true; + case _(QueueReady): + *value = VGPU_QUEUE.ready ? 1 : 0; + return true; + case _(InterruptStatus): + *value = vgpu->InterruptStatus; + return true; + case _(Status): + *value = vgpu->Status; + return true; + case _(SHMLenLow): + case _(SHMLenHigh): + /* shared memory is unimplemented */ + *value = -1; + return true; + case _(SHMBaseLow): + *value = 0; + return true; + case _(SHMBaseHigh): + *value = 0; + return true; + case _(ConfigGeneration): + *value = 0; + return true; + default: + /* Invalid address which exceeded the range */ + if (!RANGE_CHECK(addr, _(Config), sizeof(struct vgpu_config))) + return false; + + /* Read configuration from the corresponding register */ + uint32_t offset = (addr - _(Config)) << 2; + switch (offset) { + case offsetof(struct vgpu_config, events_read): { + *value = 0; /* No event is implemented currently */ + return true; + } + case offsetof(struct vgpu_config, num_scanouts): { + *value = vgpu_configs.num_scanouts; + return true; + } + case offsetof(struct vgpu_config, num_capsets): { + *value = 1; /* TODO: Add at least one capset to support VirGl */ + return true; + } + default: + return false; + } + } +#undef _ +} + +static bool virtio_gpu_reg_write(virtio_gpu_state_t *vgpu, + uint32_t addr, + uint32_t value) +{ +#define _(reg) VIRTIO_##reg + switch (addr) { + case _(DeviceFeaturesSel): + vgpu->DeviceFeaturesSel = value; + return true; + case _(DriverFeatures): + vgpu->DriverFeaturesSel == 0 ? (vgpu->DriverFeatures = value) : 0; + return true; + case _(DriverFeaturesSel): + vgpu->DriverFeaturesSel = value; + return true; + case _(QueueSel): + if (value < ARRAY_SIZE(vgpu->queues)) + vgpu->QueueSel = value; + else + virtio_gpu_set_fail(vgpu); + return true; + case _(QueueNum): + if (value > 0 && value <= VGPU_QUEUE_NUM_MAX) + VGPU_QUEUE.QueueNum = value; + else + virtio_gpu_set_fail(vgpu); + return true; + case _(QueueReady): + VGPU_QUEUE.ready = value & 1; + if (value & 1) + VGPU_QUEUE.last_avail = vgpu->ram[VGPU_QUEUE.QueueAvail] >> 16; + return true; + case _(QueueDescLow): + VGPU_QUEUE.QueueDesc = vgpu_preprocess(vgpu, value); + return true; + case _(QueueDescHigh): + if (value) + virtio_gpu_set_fail(vgpu); + return true; + case _(QueueDriverLow): + VGPU_QUEUE.QueueAvail = vgpu_preprocess(vgpu, value); + return true; + case _(QueueDriverHigh): + if (value) + virtio_gpu_set_fail(vgpu); + return true; + case _(QueueDeviceLow): + VGPU_QUEUE.QueueUsed = vgpu_preprocess(vgpu, value); + return true; + case _(QueueDeviceHigh): + if (value) + virtio_gpu_set_fail(vgpu); + return true; + case _(QueueNotify): + if (value < ARRAY_SIZE(vgpu->queues)) + virtio_queue_notify_handler(vgpu, value); + else + virtio_gpu_set_fail(vgpu); + return true; + case _(InterruptACK): + vgpu->InterruptStatus &= ~value; + return true; + case _(Status): + virtio_gpu_update_status(vgpu, value); + return true; + case _(SHMSel): + return true; + default: + /* Invalid address which exceeded the range */ + if (!RANGE_CHECK(addr, _(Config), sizeof(struct vgpu_config))) + return false; + + /* Write configuration to the corresponding register */ + uint32_t offset = (addr - _(Config)) << 2; + switch (offset) { + case offsetof(struct vgpu_config, events_clear): { + /* Ignored, no event is implemented currently */ + return true; + } + default: + return false; + } + } +#undef _ +} + +void virtio_gpu_read(hart_t *vm, + virtio_gpu_state_t *vgpu, + uint32_t addr, + uint8_t width, + uint32_t *value) +{ + switch (width) { + case RV_MEM_LW: + if (!virtio_gpu_reg_read(vgpu, addr >> 2, value)) + vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val); + break; + case RV_MEM_LBU: + case RV_MEM_LB: + case RV_MEM_LHU: + case RV_MEM_LH: + vm_set_exception(vm, RV_EXC_LOAD_MISALIGN, vm->exc_val); + return; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + return; + } +} + +void virtio_gpu_write(hart_t *vm, + virtio_gpu_state_t *vgpu, + uint32_t addr, + uint8_t width, + uint32_t value) +{ + switch (width) { + case RV_MEM_SW: + if (!virtio_gpu_reg_write(vgpu, addr >> 2, value)) + vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val); + break; + case RV_MEM_SB: + case RV_MEM_SH: + vm_set_exception(vm, RV_EXC_STORE_MISALIGN, vm->exc_val); + return; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + return; + } +} + +void virtio_gpu_init(virtio_gpu_state_t *vgpu) +{ + vgpu->priv = + calloc(sizeof(struct vgpu_scanout_info), VIRTIO_GPU_MAX_SCANOUTS); +} + +void semu_virgl_init(void) +{ + struct virgl_renderer_callbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.version = VIRGL_RENDERER_CALLBACKS_VERSION; + + int flags = VIRGL_RENDERER_USE_EGL | VIRGL_RENDERER_THREAD_SYNC; + static int cookie; // FIXME: This is for passing user-defined data + + int ret = virgl_renderer_init(&cookie, flags, &cb); + if (ret) { + fprintf(stderr, "failed to initialize virgl renderer: %s\n", + strerror(ret)); + exit(2); + } +} + +void virtio_gpu_add_scanout(virtio_gpu_state_t *vgpu, + uint32_t width, + uint32_t height) +{ + int scanout_num = vgpu_configs.num_scanouts; + + if (scanout_num >= VIRTIO_GPU_MAX_SCANOUTS) { + fprintf(stderr, "%s(): exceeded scanout maximum number\n", __func__); + exit(2); + } + + PRIV(vgpu)[scanout_num].width = width; + PRIV(vgpu)[scanout_num].height = height; + PRIV(vgpu)[scanout_num].enabled = 1; + + window_add(width, height); + + vgpu_configs.num_scanouts++; +} diff --git a/virtio-input.c b/virtio-input.c new file mode 100644 index 0000000..ca8d4e4 --- /dev/null +++ b/virtio-input.c @@ -0,0 +1,652 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "device.h" +#include "input-event-codes.h" +#include "list.h" +#include "riscv.h" +#include "riscv_private.h" +#include "virtio.h" + +#define BUS_VIRTUAL 0x06 /* Definition from the Linux kernel */ + +#define VINPUT_KEYBOARD_NAME "VirtIO Keyboard" +#define VINPUT_MOUSE_NAME "VirtIO Mouse" + +#define VIRTIO_INPUT_SERIAL "None" + +#define VIRTIO_F_VERSION_1 1 + +#define VINPUT_FEATURES_0 0 +#define VINPUT_FEATURES_1 1 /* VIRTIO_F_VERSION_1 */ + +#define VINPUT_QUEUE_NUM_MAX 1024 +#define VINPUT_QUEUE (vinput->queues[vinput->QueueSel]) + +enum { + VINPUT_KEYBOARD_ID = 0, + VINPUT_MOUSE_ID = 1, + VINPUT_DEV_CNT, +}; + +enum { + EVENTQ = 0, + STATUSQ = 1, +}; + +enum { + VIRTIO_INPUT_REG_SELECT = 0x100, + VIRTIO_INPUT_REG_SUBSEL = 0x101, + VIRTIO_INPUT_REG_SIZE = 0x102, +}; + +enum virtio_input_config_select { + VIRTIO_INPUT_CFG_UNSET = 0x00, + VIRTIO_INPUT_CFG_ID_NAME = 0x01, + VIRTIO_INPUT_CFG_ID_SERIAL = 0x02, + VIRTIO_INPUT_CFG_ID_DEVIDS = 0x03, + VIRTIO_INPUT_CFG_PROP_BITS = 0x10, + VIRTIO_INPUT_CFG_EV_BITS = 0x11, + VIRTIO_INPUT_CFG_ABS_INFO = 0x12, +}; + +PACKED(struct virtio_input_absinfo { + uint32_t min; + uint32_t max; + uint32_t fuzz; + uint32_t flat; + uint32_t res; +}); + +PACKED(struct virtio_input_devids { + uint16_t bustype; + uint16_t vendor; + uint16_t product; + uint16_t version; +}); + +PACKED(struct virtio_input_config { + uint8_t select; + uint8_t subsel; + uint8_t size; + uint8_t reserved[5]; + union { + char string[128]; + uint8_t bitmap[128]; + struct virtio_input_absinfo abs; + struct virtio_input_devids ids; + } u; +}); + +PACKED(struct virtio_input_event { + uint16_t type; + uint16_t code; + uint32_t value; +}); + +struct virio_input_data { + uint32_t ev_notify; + virtio_input_state_t *vinput; + struct virtio_input_config cfg; +}; + +static pthread_mutex_t virtio_input_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t virtio_input_cond = PTHREAD_COND_INITIALIZER; + +static struct virio_input_data vinput_dev[VINPUT_DEV_CNT]; +static int vinput_dev_cnt; + +static char *vinput_dev_name[VINPUT_DEV_CNT] = { + VINPUT_KEYBOARD_NAME, + VINPUT_MOUSE_NAME, +}; + +static void virtio_input_set_fail(virtio_input_state_t *vinput) +{ + vinput->Status |= VIRTIO_STATUS__DEVICE_NEEDS_RESET; + if (vinput->Status & VIRTIO_STATUS__DRIVER_OK) + vinput->InterruptStatus |= VIRTIO_INT__CONF_CHANGE; +} + +static inline uint32_t vinput_preprocess(virtio_input_state_t *vinput, + uint32_t addr) +{ + if ((addr >= RAM_SIZE) || (addr & 0b11)) + return virtio_input_set_fail(vinput), 0; + + return addr >> 2; +} + +static void virtio_input_update_status(virtio_input_state_t *vinput, + uint32_t status) +{ + vinput->Status |= status; + if (status) + return; + + /* Reset */ + uint32_t *ram = vinput->ram; + // void *priv = vinput->priv; /* TODO */ + int id = vinput->id; /* TODO: Store in vinput->priv */ + memset(vinput, 0, sizeof(*vinput)); + vinput->ram = ram; + vinput->id = id; + // vinput->priv = priv; +} + +static void virtio_input_desc_handler(virtio_input_state_t *vinput, + struct virtio_input_event *input_ev, + uint32_t ev_cnt, + virtio_input_queue_t *queue) +{ + uint32_t *desc; + struct virtq_desc vq_desc; + struct virtio_input_event *ev; + + uint32_t *ram = vinput->ram; + uint16_t new_avail = + ram[queue->QueueAvail] >> 16; /* virtq_avail.idx (le16) */ + uint16_t new_used = ram[queue->QueueUsed] >> 16; /* virtq_used.idx (le16) */ + + /* For checking if the event buffer has enough space to write */ + uint32_t end = queue->last_avail + ev_cnt; + uint32_t flattened_avail_idx = new_avail; + + /* Handle if the available index has overflowed and returned to the + * beginning */ + if (new_avail < queue->last_avail) + flattened_avail_idx += UINT16_MAX; + + /* Check if need to wait until the driver supplies new buffers */ + if (flattened_avail_idx < end) + return; + + for (uint32_t i = 0; i < ev_cnt; i++) { + /* Obtain the available ring index */ + uint16_t queue_idx = queue->last_avail % queue->QueueNum; + uint16_t buffer_idx = ram[queue->QueueAvail + 1 + queue_idx / 2] >> + (16 * (queue_idx % 2)); + + desc = &vinput->ram[queue->QueueDesc + buffer_idx * 4]; + vq_desc.addr = desc[0]; + vq_desc.len = desc[2]; + vq_desc.flags = desc[3]; + ev = (struct virtio_input_event *) ((uintptr_t) vinput->ram + + vq_desc.addr); + + desc[3] = 0; + + /* Write event */ + ev->type = input_ev[i].type; + ev->code = input_ev[i].code; + ev->value = input_ev[i].value; + + /* Used ring */ + uint32_t vq_used_addr = + queue->QueueUsed + 1 + (new_used % queue->QueueNum) * 2; + ram[vq_used_addr] = buffer_idx; + ram[vq_used_addr + 1] = sizeof(struct virtio_input_event); + + new_used++; + queue->last_avail++; + } + + /* Reset used ring flag to zero (virtq_used.flags) */ + vinput->ram[queue->QueueUsed] &= MASK(16); + /* Update the used ring pointer (virtq_used.idx) */ + /* TODO: Check if the or-ing is valid or not */ + // vinput->ram[queue->QueueUsed] |= ((uint32_t) new_used) << 16; + uint16_t *used = (uint16_t *) &vinput->ram[queue->QueueUsed]; + used[1] = new_used; + + return; +} + +static void virtio_queue_event_update(int dev_id, + struct virtio_input_event *input_ev, + uint32_t ev_cnt) +{ + virtio_input_state_t *vinput = vinput_dev[dev_id].vinput; + int index = EVENTQ; + + /* Start of the critical section */ + pthread_mutex_lock(&virtio_input_mutex); + + /* Wait until event buffer to be ready */ + while (vinput_dev[vinput->id].ev_notify <= 0) + pthread_cond_wait(&virtio_input_cond, &virtio_input_mutex); + + /* Consume notification count */ + vinput_dev[dev_id].ev_notify--; + + uint32_t *ram = vinput->ram; + virtio_input_queue_t *queue = &vinput->queues[index]; + if (vinput->Status & VIRTIO_STATUS__DEVICE_NEEDS_RESET) + goto success; + + if (!((vinput->Status & VIRTIO_STATUS__DRIVER_OK) && queue->ready)) + goto fail; + + /* Check for new buffers */ + uint16_t new_avail = ram[queue->QueueAvail] >> 16; + if (new_avail - queue->last_avail > (uint16_t) queue->QueueNum) { + fprintf(stderr, "%s(): size check failed\n", __func__); + goto fail; + } + + if (queue->last_avail == new_avail) + goto success; + + virtio_input_desc_handler(vinput, input_ev, ev_cnt, queue); + + /* Send interrupt, unless VIRTQ_AVAIL_F_NO_INTERRUPT is set */ + if (!(ram[queue->QueueAvail] & 1)) + vinput->InterruptStatus |= VIRTIO_INT__USED_RING; + + goto success; + +fail: + virtio_input_set_fail(vinput); + +success: + /* End of the critical section */ + pthread_mutex_unlock(&virtio_input_mutex); +} + +void virtio_input_update_key(uint32_t key, uint32_t state) +{ + struct virtio_input_event input_ev[] = { + {.type = EV_KEY, .code = key, .value = state}, + {.type = EV_SYN, .code = SYN_REPORT, .value = 0}, + }; + + size_t ev_cnt = ARRAY_SIZE(input_ev); + virtio_queue_event_update(VINPUT_KEYBOARD_ID, input_ev, ev_cnt); +} + +void virtio_input_update_mouse_button_state(uint32_t button, bool pressed) +{ + struct virtio_input_event input_ev[] = { + {.type = EV_KEY, .code = button, .value = pressed}, + {.type = EV_SYN, .code = SYN_REPORT, .value = 0}, + }; + + size_t ev_cnt = ARRAY_SIZE(input_ev); + virtio_queue_event_update(VINPUT_MOUSE_ID, input_ev, ev_cnt); +} + +void virtio_input_update_cursor(uint32_t x, uint32_t y) +{ + struct virtio_input_event input_ev[] = { + {.type = EV_ABS, .code = ABS_X, .value = x}, + {.type = EV_ABS, .code = ABS_Y, .value = y}, + {.type = EV_SYN, .code = SYN_REPORT, .value = 0}, + }; + + size_t ev_cnt = ARRAY_SIZE(input_ev); + virtio_queue_event_update(VINPUT_MOUSE_ID, input_ev, ev_cnt); +} + +static void virtio_input_properties(int dev_id) +{ + struct virtio_input_config *cfg = &vinput_dev[dev_id].cfg; + + memset(cfg->u.bitmap, 0, 128); + set_bit(INPUT_PROP_POINTER, (unsigned long *) cfg->u.bitmap); + set_bit(INPUT_PROP_DIRECT, (unsigned long *) cfg->u.bitmap); + cfg->size = 128; +} + +static void virtio_keyboard_support_events(int dev_id, uint8_t event) +{ + struct virtio_input_config *cfg = &vinput_dev[dev_id].cfg; + + memset(cfg->u.bitmap, 0, 128); + + switch (event) { + case EV_KEY: + memset(cfg->u.bitmap, 0xff, 128); + cfg->size = 128; + break; + case EV_MSC: + bitmap_set_bit((unsigned long *) cfg->u.bitmap, REP_DELAY); + bitmap_set_bit((unsigned long *) cfg->u.bitmap, REP_PERIOD); + cfg->size = 128; + break; + default: + cfg->size = 0; + } +} + +static void virtio_mouse_support_events(int dev_id, uint8_t event) +{ + struct virtio_input_config *cfg = &vinput_dev[dev_id].cfg; + + memset(cfg->u.bitmap, 0, 128); + + switch (event) { + case EV_KEY: + bitmap_set_bit((unsigned long *) cfg->u.bitmap, BTN_LEFT); + bitmap_set_bit((unsigned long *) cfg->u.bitmap, BTN_RIGHT); + bitmap_set_bit((unsigned long *) cfg->u.bitmap, BTN_MIDDLE); + cfg->size = 128; + break; + case EV_ABS: + bitmap_set_bit((unsigned long *) cfg->u.bitmap, ABS_X); + bitmap_set_bit((unsigned long *) cfg->u.bitmap, ABS_Y); + cfg->size = 128; + break; + default: + cfg->size = 0; + } +} + +static void virtio_input_support_events(int dev_id, uint8_t event) +{ + switch (dev_id) { + case VINPUT_KEYBOARD_ID: + virtio_keyboard_support_events(dev_id, event); + break; + case VINPUT_MOUSE_ID: + virtio_mouse_support_events(dev_id, event); + break; + } +} + +static void virtio_input_abs_range(int dev_id, uint8_t code) +{ + struct virtio_input_config *cfg = &vinput_dev[dev_id].cfg; + + switch (code) { + case ABS_X: + cfg->u.abs.min = 0; + cfg->u.abs.max = SCREEN_WIDTH; + cfg->u.abs.res = 1; + cfg->size = sizeof(struct virtio_input_absinfo); + break; + case ABS_Y: + cfg->u.abs.min = 0; + cfg->u.abs.max = SCREEN_HEIGHT; + cfg->u.abs.res = 1; + cfg->size = sizeof(struct virtio_input_absinfo); + break; + default: + cfg->size = 0; + } +} + +static bool virtio_input_cfg_read(int dev_id) +{ + uint8_t select = vinput_dev[dev_id].cfg.select; + uint8_t subsel = vinput_dev[dev_id].cfg.subsel; + struct virtio_input_config *cfg = &vinput_dev[dev_id].cfg; + + switch (select) { + case VIRTIO_INPUT_CFG_ID_NAME: + strcpy(cfg->u.string, vinput_dev_name[dev_id]); + cfg->size = strlen(vinput_dev_name[dev_id]); + return true; + case VIRTIO_INPUT_CFG_ID_SERIAL: + strcpy(cfg->u.string, VIRTIO_INPUT_SERIAL); + cfg->size = strlen(VIRTIO_INPUT_SERIAL); + return true; + case VIRTIO_INPUT_CFG_ID_DEVIDS: + cfg->u.ids.bustype = BUS_VIRTUAL; + cfg->u.ids.vendor = 0; + cfg->u.ids.product = 0; + cfg->u.ids.version = 1; + cfg->size = sizeof(struct virtio_input_devids); + return true; + case VIRTIO_INPUT_CFG_PROP_BITS: + virtio_input_properties(dev_id); + return true; + case VIRTIO_INPUT_CFG_EV_BITS: + virtio_input_support_events(dev_id, subsel); + return true; + case VIRTIO_INPUT_CFG_ABS_INFO: + virtio_input_abs_range(dev_id, subsel); + return true; + default: + fprintf(stderr, + "virtio-input: Unknown value written to select register.\n"); + return false; + } +} + +static bool virtio_input_reg_read(virtio_input_state_t *vinput, + uint32_t addr, + uint32_t *value, + size_t size) +{ +#define _(reg) (VIRTIO_##reg << 2) + switch (addr) { + case _(MagicValue): + *value = 0x74726976; + return true; + case _(Version): + *value = 2; + return true; + case _(DeviceID): + *value = 18; + return true; + case _(VendorID): + *value = VIRTIO_VENDOR_ID; + return true; + case _(DeviceFeatures): + *value = vinput->DeviceFeaturesSel == 0 + ? VINPUT_FEATURES_0 + : (vinput->DeviceFeaturesSel == 1 ? VINPUT_FEATURES_1 : 0); + return true; + case _(QueueNumMax): + *value = VINPUT_QUEUE_NUM_MAX; + return true; + case _(QueueReady): + *value = VINPUT_QUEUE.ready ? 1 : 0; + return true; + case _(InterruptStatus): + *value = vinput->InterruptStatus; + return true; + case _(Status): + *value = vinput->Status; + return true; + case _(ConfigGeneration): + *value = 0; + return true; + case VIRTIO_INPUT_REG_SIZE: + if (!virtio_input_cfg_read(vinput->id)) + return false; + *value = vinput_dev[vinput->id].cfg.size; + return true; + default: + /* Invalid address which exceeded the range */ + if (!RANGE_CHECK(addr, _(Config), sizeof(struct virtio_input_config))) + return false; + + /* Read virtio-input specific registers */ + off_t offset = addr - VIRTIO_INPUT_REG_SELECT; + uint8_t *reg = + (uint8_t *) ((uintptr_t) &vinput_dev[vinput->id].cfg + offset); + memcpy(value, reg, size); + + return true; + } +#undef _ +} + +static bool virtio_input_reg_write(virtio_input_state_t *vinput, + uint32_t addr, + uint32_t value) +{ +#define _(reg) (VIRTIO_##reg << 2) + switch (addr) { + case _(DeviceFeaturesSel): + vinput->DeviceFeaturesSel = value; + return true; + case _(DriverFeatures): + vinput->DriverFeaturesSel == 0 ? (vinput->DriverFeatures = value) : 0; + return true; + case _(DriverFeaturesSel): + vinput->DriverFeaturesSel = value; + return true; + case _(QueueSel): + if (value < ARRAY_SIZE(vinput->queues)) + vinput->QueueSel = value; + else + virtio_input_set_fail(vinput); + return true; + case _(QueueNum): + if (value > 0 && value <= VINPUT_QUEUE_NUM_MAX) + VINPUT_QUEUE.QueueNum = value; + else + virtio_input_set_fail(vinput); + return true; + case _(QueueReady): + VINPUT_QUEUE.ready = value & 1; + if (value & 1) + VINPUT_QUEUE.last_avail = + vinput->ram[VINPUT_QUEUE.QueueAvail] >> 16; + return true; + case _(QueueDescLow): + VINPUT_QUEUE.QueueDesc = vinput_preprocess(vinput, value); + return true; + case _(QueueDescHigh): + if (value) + virtio_input_set_fail(vinput); + return true; + case _(QueueDriverLow): + VINPUT_QUEUE.QueueAvail = vinput_preprocess(vinput, value); + return true; + case _(QueueDriverHigh): + if (value) + virtio_input_set_fail(vinput); + return true; + case _(QueueDeviceLow): + VINPUT_QUEUE.QueueUsed = vinput_preprocess(vinput, value); + return true; + case _(QueueDeviceHigh): + if (value) + virtio_input_set_fail(vinput); + return true; + case _(QueueNotify): + if (value < ARRAY_SIZE(vinput->queues)) { + /* Handle event queue only for minimal implementation */ + if (value == EVENTQ) { + vinput_dev[vinput->id].ev_notify++; + pthread_cond_signal(&virtio_input_cond); + } + } else { + virtio_input_set_fail(vinput); + } + return true; + case _(InterruptACK): + vinput->InterruptStatus &= ~value; + return true; + case _(Status): + virtio_input_update_status(vinput, value); + return true; + case _(SHMSel): + return true; + case VIRTIO_INPUT_REG_SELECT: + vinput_dev[vinput->id].cfg.select = value; + return true; + case VIRTIO_INPUT_REG_SUBSEL: + vinput_dev[vinput->id].cfg.subsel = value; + return true; + default: + /* No other writable registers */ + return false; + } +#undef _ +} + +void virtio_input_read(hart_t *vm, + virtio_input_state_t *vinput, + uint32_t addr, + uint8_t width, + uint32_t *value) +{ + pthread_mutex_lock(&virtio_input_mutex); + + /* XXX: 4-byte alignment (i.e., addr >> 2) is removed due to the per + byte accessing */ + switch (width) { + case RV_MEM_LW: + if (!virtio_input_reg_read(vinput, addr, value, 4)) + vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val); + break; + case RV_MEM_LBU: + case RV_MEM_LB: + case RV_MEM_LHU: + case RV_MEM_LH: + /*FIXME: virtio-input driver need to access device config register per + * byte. the following code that derived from other virtio devices' + * implementation will cause kernel panic */ + // vm_set_exception(vm, RV_EXC_LOAD_MISALIGN, vm->exc_val); +#if 1 + // printf("read addr: 0x%x, width: %d\n", addr, width); + if (!virtio_input_reg_read(vinput, addr, value, 1)) + vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val); +#endif + break; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + break; + } + + pthread_mutex_unlock(&virtio_input_mutex); +} + +void virtio_input_write(hart_t *vm, + virtio_input_state_t *vinput, + uint32_t addr, + uint8_t width, + uint32_t value) +{ + pthread_mutex_lock(&virtio_input_mutex); + + /* XXX: 4-byte alignment (i.e., addr >> 2) is removed due to the per + byte accessing */ + switch (width) { + case RV_MEM_SW: + if (!virtio_input_reg_write(vinput, addr, value)) + vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val); + break; + case RV_MEM_SB: + case RV_MEM_SH: + /* FIXME: virtio-input driver need to access device config register per + * byte. the following code that derived from other virtio devices' + * implementation will cause kernel panic */ + // vm_set_exception(vm, RV_EXC_STORE_MISALIGN, vm->exc_val); +#if 1 + // printf("read addr: 0x%x, width: %d\n", addr, width); + if (!virtio_input_reg_write(vinput, addr, value)) + vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val); +#endif + break; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + break; + } + + pthread_mutex_unlock(&virtio_input_mutex); +} + +void virtio_input_init(virtio_input_state_t *vinput) +{ + vinput->id = vinput_dev_cnt; + vinput_dev_cnt++; + + vinput_dev[vinput->id].ev_notify = 0; + vinput_dev[vinput->id].vinput = vinput; +} diff --git a/virtio.h b/virtio.h index b893ef5..b23b414 100644 --- a/virtio.h +++ b/virtio.h @@ -24,6 +24,16 @@ #define VIRTIO_BLK_S_IOERR 1 #define VIRTIO_BLK_S_UNSUPP 2 +#define VIRTIO_GPU_FLAG_FENCE (1 << 0) + +#define VIRTIO_GPU_MAX_SCANOUTS 16 + +#define VIRTIO_GPU_CAPSET_VIRGL 1 +#define VIRTIO_GPU_CAPSET_VIRGL2 2 +#define VIRTIO_GPU_CAPSET_GFXSTREAM 3 +#define VIRTIO_GPU_CAPSET_VENUS 4 +#define VIRTIO_GPU_CAPSET_CROSS_DOMAIN 5 + /* VirtIO MMIO registers */ #define VIRTIO_REG_LIST \ _(MagicValue, 0x000) /* R */ \ @@ -49,6 +59,12 @@ _(QueueDeviceLow, 0x0a0) /* W */ \ _(QueueDeviceHigh, 0x0a4) /* W */ \ _(ConfigGeneration, 0x0fc) /* R */ \ + _(SHMSel, 0x0ac) /* W */ \ + _(SHMLenLow, 0x0b0) /* R */ \ + _(SHMLenHigh, 0x0b4) /* R */ \ + _(SHMBaseLow, 0x0b8) /* R */ \ + _(SHMBaseHigh, 0x0bc) /* R */ \ + _(QueueReset, 0x0c0) /* RW */ \ _(Config, 0x100) /* RW */ enum { @@ -57,6 +73,63 @@ enum { #undef _ }; +enum virtio_gpu_ctrl_type { + /* 2d commands */ + VIRTIO_GPU_CMD_GET_DISPLAY_INFO = 0x0100, + VIRTIO_GPU_CMD_RESOURCE_CREATE_2D, + VIRTIO_GPU_CMD_RESOURCE_UNREF, + VIRTIO_GPU_CMD_SET_SCANOUT, + VIRTIO_GPU_CMD_RESOURCE_FLUSH, + VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D, + VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING, + VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING, + VIRTIO_GPU_CMD_GET_CAPSET_INFO, + VIRTIO_GPU_CMD_GET_CAPSET, + VIRTIO_GPU_CMD_GET_EDID, + + /* 3d commands */ + VIRTIO_GPU_CMD_CTX_CREATE = 0x0200, + VIRTIO_GPU_CMD_CTX_DESTROY, + VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE, + VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE, + VIRTIO_GPU_CMD_RESOURCE_CREATE_3D, + VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D, + VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D, + VIRTIO_GPU_CMD_SUBMIT_3D, + VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB, + VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB, + + /* cursor commands */ + VIRTIO_GPU_CMD_UPDATE_CURSOR = 0x0300, + VIRTIO_GPU_CMD_MOVE_CURSOR, + + /* success responses */ + VIRTIO_GPU_RESP_OK_NODATA = 0x1100, + VIRTIO_GPU_RESP_OK_DISPLAY_INFO, + VIRTIO_GPU_RESP_OK_CAPSET_INFO, + VIRTIO_GPU_RESP_OK_CAPSET, + VIRTIO_GPU_RESP_OK_EDID, + + /* error responses */ + VIRTIO_GPU_RESP_ERR_UNSPEC = 0x1200, + VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY, + VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID, + VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID, + VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID, + VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER, +}; + +enum virtio_gpu_formats { + VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM = 1, + VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM = 2, + VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM = 3, + VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM = 4, + VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM = 67, + VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM = 68, + VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM = 121, + VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM = 134 +}; + struct virtq_desc { uint32_t addr; uint32_t len; diff --git a/window.c b/window.c new file mode 100644 index 0000000..33a93eb --- /dev/null +++ b/window.c @@ -0,0 +1,328 @@ +#include +#include +#include +#include + +#include +#include + +#include "device.h" +#include "input-event-codes.h" +#include "virtio.h" +#include "window.h" + +#define SDL_COND_TIMEOUT 1 /* ms */ + +#define DEF_KEY_MAP(_sdl_key, _linux_key) \ + { \ + .sdl_key = _sdl_key, .linux_key = _linux_key \ + } + +struct key_map_entry { + int sdl_key; + int linux_key; +}; + +struct key_map_entry key_map[] = { + /* Mouse */ + DEF_KEY_MAP(SDL_BUTTON_LEFT, BTN_LEFT), + DEF_KEY_MAP(SDL_BUTTON_RIGHT, BTN_RIGHT), + DEF_KEY_MAP(SDL_BUTTON_MIDDLE, BTN_MIDDLE), + /* Keyboard */ + DEF_KEY_MAP(SDLK_ESCAPE, KEY_ESC), + DEF_KEY_MAP(SDLK_1, KEY_1), + DEF_KEY_MAP(SDLK_2, KEY_2), + DEF_KEY_MAP(SDLK_3, KEY_3), + DEF_KEY_MAP(SDLK_4, KEY_4), + DEF_KEY_MAP(SDLK_5, KEY_5), + DEF_KEY_MAP(SDLK_6, KEY_6), + DEF_KEY_MAP(SDLK_7, KEY_7), + DEF_KEY_MAP(SDLK_8, KEY_8), + DEF_KEY_MAP(SDLK_9, KEY_9), + DEF_KEY_MAP(SDLK_0, KEY_0), + DEF_KEY_MAP(SDLK_MINUS, KEY_MINUS), + DEF_KEY_MAP(SDLK_EQUALS, KEY_EQUAL), + DEF_KEY_MAP(SDLK_BACKSPACE, KEY_BACKSPACE), + DEF_KEY_MAP(SDLK_TAB, KEY_TAB), + DEF_KEY_MAP(SDLK_q, KEY_Q), + DEF_KEY_MAP(SDLK_w, KEY_W), + DEF_KEY_MAP(SDLK_e, KEY_E), + DEF_KEY_MAP(SDLK_r, KEY_R), + DEF_KEY_MAP(SDLK_t, KEY_T), + DEF_KEY_MAP(SDLK_y, KEY_Y), + DEF_KEY_MAP(SDLK_u, KEY_U), + DEF_KEY_MAP(SDLK_i, KEY_I), + DEF_KEY_MAP(SDLK_o, KEY_O), + DEF_KEY_MAP(SDLK_p, KEY_P), + DEF_KEY_MAP(SDLK_LEFTBRACKET, KEY_LEFTBRACE), + DEF_KEY_MAP(SDLK_RIGHTBRACKET, KEY_RIGHTBRACE), + DEF_KEY_MAP(SDLK_RETURN, KEY_ENTER), + DEF_KEY_MAP(SDLK_LCTRL, KEY_LEFTCTRL), + DEF_KEY_MAP(SDLK_a, KEY_A), + DEF_KEY_MAP(SDLK_s, KEY_S), + DEF_KEY_MAP(SDLK_d, KEY_D), + DEF_KEY_MAP(SDLK_f, KEY_F), + DEF_KEY_MAP(SDLK_g, KEY_G), + DEF_KEY_MAP(SDLK_h, KEY_H), + DEF_KEY_MAP(SDLK_j, KEY_J), + DEF_KEY_MAP(SDLK_k, KEY_K), + DEF_KEY_MAP(SDLK_l, KEY_L), + DEF_KEY_MAP(SDLK_SEMICOLON, KEY_SEMICOLON), + DEF_KEY_MAP(SDLK_BACKQUOTE, KEY_GRAVE), + DEF_KEY_MAP(SDLK_LSHIFT, KEY_LEFTSHIFT), + DEF_KEY_MAP(SDLK_BACKSLASH, KEY_BACKSLASH), + DEF_KEY_MAP(SDLK_z, KEY_Z), + DEF_KEY_MAP(SDLK_x, KEY_X), + DEF_KEY_MAP(SDLK_c, KEY_C), + DEF_KEY_MAP(SDLK_v, KEY_V), + DEF_KEY_MAP(SDLK_b, KEY_B), + DEF_KEY_MAP(SDLK_n, KEY_N), + DEF_KEY_MAP(SDLK_m, KEY_M), + DEF_KEY_MAP(SDLK_COMMA, KEY_COMMA), + DEF_KEY_MAP(SDLK_PERIOD, KEY_DOT), + DEF_KEY_MAP(SDLK_SLASH, KEY_SLASH), + DEF_KEY_MAP(SDLK_RSHIFT, KEY_RIGHTSHIFT), + DEF_KEY_MAP(SDLK_LALT, KEY_LEFTALT), + DEF_KEY_MAP(SDLK_SPACE, KEY_SPACE), + DEF_KEY_MAP(SDLK_CAPSLOCK, KEY_CAPSLOCK), + DEF_KEY_MAP(SDLK_F1, KEY_F1), + DEF_KEY_MAP(SDLK_F2, KEY_F2), + DEF_KEY_MAP(SDLK_F3, KEY_F3), + DEF_KEY_MAP(SDLK_F4, KEY_F4), + DEF_KEY_MAP(SDLK_F5, KEY_F5), + DEF_KEY_MAP(SDLK_F6, KEY_F6), + DEF_KEY_MAP(SDLK_F7, KEY_F7), + DEF_KEY_MAP(SDLK_F7, KEY_F8), + DEF_KEY_MAP(SDLK_F9, KEY_F9), + DEF_KEY_MAP(SDLK_F10, KEY_F10), + DEF_KEY_MAP(SDLK_SCROLLLOCK, KEY_SCROLLLOCK), + DEF_KEY_MAP(SDLK_KP_7, KEY_KP7), + DEF_KEY_MAP(SDLK_KP_8, KEY_KP8), + DEF_KEY_MAP(SDLK_KP_9, KEY_KP9), + DEF_KEY_MAP(SDLK_KP_MINUS, KEY_KPMINUS), + DEF_KEY_MAP(SDLK_KP_4, KEY_KP4), + DEF_KEY_MAP(SDLK_KP_5, KEY_KP5), + DEF_KEY_MAP(SDLK_KP_6, KEY_KP6), + DEF_KEY_MAP(SDLK_KP_PLUS, KEY_KPPLUS), + DEF_KEY_MAP(SDLK_KP_1, KEY_KP1), + DEF_KEY_MAP(SDLK_KP_2, KEY_KP2), + DEF_KEY_MAP(SDLK_KP_3, KEY_KP3), + DEF_KEY_MAP(SDLK_KP_0, KEY_KP0), + DEF_KEY_MAP(SDLK_KP_PERIOD, KEY_KPDOT), + DEF_KEY_MAP(SDLK_F11, KEY_F11), + DEF_KEY_MAP(SDLK_F12, KEY_F12), + DEF_KEY_MAP(SDLK_KP_ENTER, KEY_KPENTER), + DEF_KEY_MAP(SDLK_RCTRL, KEY_RIGHTCTRL), + DEF_KEY_MAP(SDLK_RALT, KEY_RIGHTALT), + DEF_KEY_MAP(SDLK_HOME, KEY_HOME), + DEF_KEY_MAP(SDLK_UP, KEY_UP), + DEF_KEY_MAP(SDLK_PAGEUP, KEY_PAGEUP), + DEF_KEY_MAP(SDLK_LEFT, KEY_LEFT), + DEF_KEY_MAP(SDLK_RIGHT, KEY_RIGHT), + DEF_KEY_MAP(SDLK_END, KEY_END), + DEF_KEY_MAP(SDLK_DOWN, KEY_DOWN), + DEF_KEY_MAP(SDLK_PAGEDOWN, KEY_PAGEDOWN), + DEF_KEY_MAP(SDLK_INSERT, KEY_INSERT), + DEF_KEY_MAP(SDLK_DELETE, KEY_DELETE), +}; + +struct display_info { + struct gpu_resource resource; + uint32_t sdl_format; + SDL_mutex *img_mtx; + SDL_cond *img_cond; + SDL_Thread *win_thread; + SDL_Thread *ev_thread; + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Surface *surface; + SDL_Texture *texture; +}; + +static struct display_info displays[VIRTIO_GPU_MAX_SCANOUTS]; +static int display_cnt; + +void window_add(uint32_t width, uint32_t height) +{ + displays[display_cnt].resource.width = width; + displays[display_cnt].resource.height = height; + display_cnt++; +} + +static int sdl_key_to_linux_key(int sdl_key) +{ + unsigned long key_cnt = sizeof(key_map) / sizeof(struct key_map_entry); + for (unsigned long i = 0; i < key_cnt; i++) + if (sdl_key == key_map[i].sdl_key) + return key_map[i].linux_key; + + return -1; +} + +static int event_thread(void *data) +{ + int linux_key; + + while (1) { + SDL_Event e; + if (SDL_PollEvent(&e)) { + switch (e.type) { + case SDL_QUIT: { + exit(0); + } + case SDL_KEYDOWN: { + linux_key = sdl_key_to_linux_key(e.key.keysym.sym); + virtio_input_update_key(linux_key, 1); + break; + } + case SDL_KEYUP: { + linux_key = sdl_key_to_linux_key(e.key.keysym.sym); + virtio_input_update_key(linux_key, 0); + break; + } + case SDL_MOUSEBUTTONDOWN: { + linux_key = sdl_key_to_linux_key(e.button.button); + virtio_input_update_mouse_button_state(linux_key, true); + break; + } + case SDL_MOUSEBUTTONUP: { + linux_key = sdl_key_to_linux_key(e.button.button); + virtio_input_update_mouse_button_state(linux_key, false); + break; + } + case SDL_MOUSEMOTION: { + virtio_input_update_cursor(e.motion.x, e.motion.y); + break; + } + } + } + } +} + +static int window_thread(void *data) +{ + struct display_info *display = (struct display_info *) data; + struct gpu_resource *resource = &display->resource; + + /* Create SDL window */ + display->window = SDL_CreateWindow("semu", SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, resource->width, + resource->height, SDL_WINDOW_SHOWN); + + if (!display->window) { + fprintf(stderr, "%s(): failed to create window\n", __func__); + exit(2); + } + + /* Create SDL render */ + display->renderer = + SDL_CreateRenderer(display->window, -1, SDL_RENDERER_ACCELERATED); + + if (!display->renderer) { + fprintf(stderr, "%s(): failed to create renderer\n", __func__); + exit(2); + } + + /* Render the whole screen with black color */ + SDL_SetRenderDrawColor(display->renderer, 0, 0, 0, 255); + SDL_RenderClear(display->renderer); + SDL_RenderPresent(display->renderer); + + /* Create event handling thread */ + ((struct display_info *) data)->ev_thread = + SDL_CreateThread(event_thread, NULL, data); + + while (1) { + SDL_LockMutex(display->img_mtx); + + /* Wait until the image is arrived */ + while (SDL_CondWaitTimeout(display->img_cond, display->img_mtx, + SDL_COND_TIMEOUT)) + ; + + /* Render image */ + display->surface = SDL_CreateRGBSurfaceWithFormatFrom( + resource->image, resource->width, resource->height, + resource->bits_per_pixel, resource->stride, display->sdl_format); + display->texture = + SDL_CreateTextureFromSurface(display->renderer, display->surface); + SDL_RenderCopy(display->renderer, display->texture, NULL, NULL); + SDL_RenderPresent(display->renderer); + SDL_DestroyTexture(display->texture); + + SDL_UnlockMutex(display->img_mtx); + } +} + +void window_init(void) +{ + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "%s(): failed to initialize SDL\n", __func__); + exit(2); + } + + for (int i = 0; i < display_cnt; i++) { + displays[i].img_mtx = SDL_CreateMutex(); + displays[i].img_cond = SDL_CreateCond(); + + displays[i].win_thread = + SDL_CreateThread(window_thread, NULL, (void *) &displays[i]); + SDL_DetachThread(displays[i].win_thread); + } +} + +void window_lock(uint32_t id) +{ + SDL_LockMutex(displays[id].img_mtx); +} + +void window_unlock(uint32_t id) +{ + SDL_UnlockMutex(displays[id].img_mtx); +} + +static bool virtio_gpu_to_sdl_format(uint32_t virtio_gpu_format, + uint32_t *sdl_format) +{ + switch (virtio_gpu_format) { + case VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: + *sdl_format = SDL_PIXELFORMAT_ARGB8888; + return true; + case VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM: + *sdl_format = SDL_PIXELFORMAT_XRGB8888; + return true; + case VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM: + *sdl_format = SDL_PIXELFORMAT_BGRA8888; + return true; + case VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM: + *sdl_format = SDL_PIXELFORMAT_BGRX8888; + return true; + case VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM: + *sdl_format = SDL_PIXELFORMAT_ABGR8888; + return true; + case VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM: + *sdl_format = SDL_PIXELFORMAT_RGBX8888; + return true; + case VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM: + *sdl_format = SDL_PIXELFORMAT_RGBA8888; + return true; + case VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM: + *sdl_format = SDL_PIXELFORMAT_XBGR8888; + return true; + default: + return false; + } +} + +void window_render(struct gpu_resource *resource) +{ + int id = resource->scanout_id; + + /* Resource update */ + memcpy(&displays[id].resource, resource, sizeof(struct gpu_resource)); + bool legal_format = + virtio_gpu_to_sdl_format(resource->format, &displays[id].sdl_format); + + if (legal_format) + SDL_CondSignal(displays[id].img_cond); +} diff --git a/window.h b/window.h new file mode 100644 index 0000000..c2c53f7 --- /dev/null +++ b/window.h @@ -0,0 +1,20 @@ +#pragma once + +#if SEMU_HAS(VIRTIOGPU) +/* Public interface to the vgpu_resource_2d structure */ +struct gpu_resource { + uint32_t scanout_id; + uint32_t format; + uint32_t width; + uint32_t height; + uint32_t stride; + uint32_t bits_per_pixel; + uint32_t *image; +}; + +void window_init(void); +void window_add(uint32_t width, uint32_t height); +void window_render(struct gpu_resource *resource); +void window_lock(uint32_t id); +void window_unlock(uint32_t id); +#endif