commit b6b7d72717bc0588c8dbf7888dc58faee1903e7c Author: Emile Clark-Boman Date: Tue Jul 22 02:42:34 2025 +1000 Fork http://bellard.org/tinyemu diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..540a05f --- /dev/null +++ b/Changelog @@ -0,0 +1,45 @@ +2019-12-21: + +- added complete JSLinux demo +- RISC-V: added initrd support +- RISC-V: fixed FMIN/FMAX instructions + +2018-09-23: + +- added support for separate RISC-V BIOS and kernel + +2018-09-15: + +- renamed to TinyEMU (temu) +- single executable for all emulated machines + +2018-08-29: + +- compilation fixes + +2017-08-06: + +- added JSON configuration file +- added graphical display with SDL +- added VirtIO input support +- added PCI bus and VirtIO PCI support +- x86: added IDE, PS/2, vmmouse and VGA devices +- added user mode network interface + +2017-06-10: + +- RISCV: avoid unnecessary kernel patches +- x86: accept standard kernel images + +2017-05-25: + +- RISCV: faster emulation (1.4x) +- Support of user level ISA version 2.2 and priviledged architecture + version 1.10 +- added small x86 emulator (x86emu) based on KVM +- modified the fs_net network protocol to match the vfsync protocol +- handle console resize +- JS emulator: + - added scrollbar in terminal + - added file import and export + - added copy/paste support diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt new file mode 100644 index 0000000..3f38cbe --- /dev/null +++ b/MIT-LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 Fabrice Bellard + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..390ae37 --- /dev/null +++ b/Makefile @@ -0,0 +1,135 @@ +# +# TinyEMU +# +# Copyright (c) 2016-2018 Fabrice Bellard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +# if set, network filesystem is enabled. libcurl and libcrypto +# (openssl) must be installed. +CONFIG_FS_NET=y +# SDL support (optional) +CONFIG_SDL=y +# if set, compile the 128 bit emulator. Note: the 128 bit target does +# not compile if gcc does not support the int128 type (32 bit hosts). +CONFIG_INT128=y +# build x86 emulator +CONFIG_X86EMU=y +# win32 build (not usable yet) +#CONFIG_WIN32=y +# user space network redirector +CONFIG_SLIRP=y + +ifdef CONFIG_WIN32 +CROSS_PREFIX=i686-w64-mingw32- +EXE=.exe +else +CROSS_PREFIX= +EXE= +endif +CC=$(CROSS_PREFIX)gcc +STRIP=$(CROSS_PREFIX)strip +CFLAGS=-O2 -Wall -g -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -MMD +CFLAGS+=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(shell cat VERSION)\" +LDFLAGS= + +bindir=/usr/local/bin +INSTALL=install + +PROGS+= temu$(EXE) +ifndef CONFIG_WIN32 +ifdef CONFIG_FS_NET +PROGS+=build_filelist splitimg +endif +endif + +all: $(PROGS) + +EMU_OBJS:=virtio.o pci.o fs.o cutils.o iomem.o simplefb.o \ + json.o machine.o temu.o + +ifdef CONFIG_SLIRP +CFLAGS+=-DCONFIG_SLIRP +EMU_OBJS+=$(addprefix slirp/, bootp.o ip_icmp.o mbuf.o slirp.o tcp_output.o cksum.o ip_input.o misc.o socket.o tcp_subr.o udp.o if.o ip_output.o sbuf.o tcp_input.o tcp_timer.o) +endif + +ifndef CONFIG_WIN32 +EMU_OBJS+=fs_disk.o +EMU_LIBS=-lrt +endif +ifdef CONFIG_FS_NET +CFLAGS+=-DCONFIG_FS_NET +EMU_OBJS+=fs_net.o fs_wget.o fs_utils.o block_net.o +EMU_LIBS+=-lcurl -lcrypto +ifdef CONFIG_WIN32 +EMU_LIBS+=-lwsock32 +endif # CONFIG_WIN32 +endif # CONFIG_FS_NET +ifdef CONFIG_SDL +EMU_LIBS+=-lSDL +EMU_OBJS+=sdl.o +CFLAGS+=-DCONFIG_SDL +ifdef CONFIG_WIN32 +LDFLAGS+=-mwindows +endif +endif + +EMU_OBJS+=riscv_machine.o softfp.o riscv_cpu32.o riscv_cpu64.o +ifdef CONFIG_INT128 +CFLAGS+=-DCONFIG_RISCV_MAX_XLEN=128 +EMU_OBJS+=riscv_cpu128.o +else +CFLAGS+=-DCONFIG_RISCV_MAX_XLEN=64 +endif +ifdef CONFIG_X86EMU +CFLAGS+=-DCONFIG_X86EMU +EMU_OBJS+=x86_cpu.o x86_machine.o ide.o ps2.o vmmouse.o pckbd.o vga.o +endif + +temu$(EXE): $(EMU_OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(EMU_LIBS) + +riscv_cpu32.o: riscv_cpu.c + $(CC) $(CFLAGS) -DMAX_XLEN=32 -c -o $@ $< + +riscv_cpu64.o: riscv_cpu.c + $(CC) $(CFLAGS) -DMAX_XLEN=64 -c -o $@ $< + +riscv_cpu128.o: riscv_cpu.c + $(CC) $(CFLAGS) -DMAX_XLEN=128 -c -o $@ $< + +build_filelist: build_filelist.o fs_utils.o cutils.o + $(CC) $(LDFLAGS) -o $@ $^ -lm + +splitimg: splitimg.o + $(CC) $(LDFLAGS) -o $@ $^ + +install: $(PROGS) + $(STRIP) $(PROGS) + $(INSTALL) -m755 $(PROGS) "$(DESTDIR)$(bindir)" + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +clean: + rm -f *.o *.d *~ $(PROGS) slirp/*.o slirp/*.d slirp/*~ + +-include $(wildcard *.d) +-include $(wildcard slirp/*.d) diff --git a/Makefile.js b/Makefile.js new file mode 100644 index 0000000..3d76f77 --- /dev/null +++ b/Makefile.js @@ -0,0 +1,65 @@ +# +# TinyEMU emulator +# +# Copyright (c) 2016-2018 Fabrice Bellard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +# Build the Javascript version of TinyEMU +EMCC=emcc +EMCFLAGS=-O2 --llvm-opts 2 -Wall -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -MMD -fno-strict-aliasing -DCONFIG_FS_NET +#EMCFLAGS+=-Werror +EMLDFLAGS=-O3 --memory-init-file 0 --closure 0 -s NO_EXIT_RUNTIME=1 -s NO_FILESYSTEM=1 -s "EXPORTED_FUNCTIONS=['_console_queue_char','_vm_start','_fs_import_file','_display_key_event','_display_mouse_event','_display_wheel_event','_net_write_packet','_net_set_carrier']" -s 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall", "cwrap"]' -s BINARYEN_TRAP_MODE=clamp --js-library js/lib.js +EMLDFLAGS_ASMJS:=$(EMLDFLAGS) -s WASM=0 +EMLDFLAGS_WASM:=$(EMLDFLAGS) -s WASM=1 -s TOTAL_MEMORY=67108864 -s ALLOW_MEMORY_GROWTH=1 + +PROGS=js/riscvemu32.js js/riscvemu32-wasm.js js/riscvemu64.js js/riscvemu64-wasm.js + +all: $(PROGS) + +JS_OBJS=jsemu.js.o softfp.js.o virtio.js.o fs.js.o fs_net.js.o fs_wget.js.o fs_utils.js.o simplefb.js.o pci.js.o json.js.o block_net.js.o +JS_OBJS+=iomem.js.o cutils.js.o aes.js.o sha256.js.o + +RISCVEMU64_OBJS=$(JS_OBJS) riscv_cpu64.js.o riscv_machine.js.o machine.js.o +RISCVEMU32_OBJS=$(JS_OBJS) riscv_cpu32.js.o riscv_machine.js.o machine.js.o + +js/riscvemu64.js: $(RISCVEMU64_OBJS) js/lib.js + $(EMCC) $(EMLDFLAGS_ASMJS) -o $@ $(RISCVEMU64_OBJS) + +js/riscvemu32.js: $(RISCVEMU32_OBJS) js/lib.js + $(EMCC) $(EMLDFLAGS_ASMJS) -o $@ $(RISCVEMU32_OBJS) + +js/riscvemu64-wasm.js: $(RISCVEMU64_OBJS) js/lib.js + $(EMCC) $(EMLDFLAGS_WASM) -o $@ $(RISCVEMU64_OBJS) + +js/riscvemu32-wasm.js: $(RISCVEMU32_OBJS) js/lib.js + $(EMCC) $(EMLDFLAGS_WASM) -o $@ $(RISCVEMU32_OBJS) + +riscv_cpu32.js.o: riscv_cpu.c + $(EMCC) $(EMCFLAGS) -DMAX_XLEN=32 -DCONFIG_RISCV_MAX_XLEN=32 -c -o $@ $< + +riscv_cpu64.js.o: riscv_cpu.c + $(EMCC) $(EMCFLAGS) -DMAX_XLEN=64 -DCONFIG_RISCV_MAX_XLEN=64 -c -o $@ $< + + +%.js.o: %.c + $(EMCC) $(EMCFLAGS) -c -o $@ $< + +-include $(wildcard *.d) diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..bd16728 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +2019-12-21 diff --git a/aes.c b/aes.c new file mode 100644 index 0000000..ac8af6d --- /dev/null +++ b/aes.c @@ -0,0 +1,1321 @@ +/** + * + * aes.c - integrated in QEMU by Fabrice Bellard from the OpenSSL project. + */ +/* + * rijndael-alg-fst.c + * + * @version 3.0 (December 2000) + * + * Optimised ANSI C code for the Rijndael cipher (now AES) + * + * @author Vincent Rijmen + * @author Antoon Bosselaers + * @author Paulo Barreto + * + * This code is hereby placed in the public domain. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include "aes.h" + +#ifndef NDEBUG +#define NDEBUG +#endif + +#include + +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint8_t u8; + +#define MAXKC (256/32) +#define MAXKB (256/8) +#define MAXNR 14 + +/* This controls loop-unrolling in aes_core.c */ +#undef FULL_UNROLL +# define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] << 8) ^ ((u32)(pt)[3])) +# define PUTU32(ct, st) { (ct)[0] = (u8)((st) >> 24); (ct)[1] = (u8)((st) >> 16); (ct)[2] = (u8)((st) >> 8); (ct)[3] = (u8)(st); } + +/* +Te0[x] = S [x].[02, 01, 01, 03]; +Te1[x] = S [x].[03, 02, 01, 01]; +Te2[x] = S [x].[01, 03, 02, 01]; +Te3[x] = S [x].[01, 01, 03, 02]; +Te4[x] = S [x].[01, 01, 01, 01]; + +Td0[x] = Si[x].[0e, 09, 0d, 0b]; +Td1[x] = Si[x].[0b, 0e, 09, 0d]; +Td2[x] = Si[x].[0d, 0b, 0e, 09]; +Td3[x] = Si[x].[09, 0d, 0b, 0e]; +Td4[x] = Si[x].[01, 01, 01, 01]; +*/ + +static const u32 Te0[256] = { + 0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU, + 0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U, + 0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU, + 0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU, + 0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U, + 0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU, + 0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU, + 0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU, + 0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU, + 0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU, + 0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U, + 0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU, + 0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU, + 0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U, + 0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU, + 0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU, + 0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU, + 0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU, + 0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU, + 0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U, + 0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU, + 0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU, + 0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU, + 0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU, + 0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U, + 0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U, + 0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U, + 0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U, + 0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU, + 0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U, + 0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U, + 0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU, + 0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU, + 0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U, + 0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U, + 0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U, + 0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU, + 0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U, + 0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU, + 0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U, + 0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU, + 0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U, + 0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U, + 0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU, + 0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U, + 0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U, + 0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U, + 0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U, + 0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U, + 0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U, + 0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U, + 0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U, + 0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU, + 0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U, + 0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U, + 0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U, + 0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U, + 0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U, + 0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U, + 0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU, + 0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U, + 0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U, + 0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U, + 0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU, +}; +static const u32 Te1[256] = { + 0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU, + 0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U, + 0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU, + 0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U, + 0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU, + 0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U, + 0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU, + 0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U, + 0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U, + 0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU, + 0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U, + 0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U, + 0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U, + 0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU, + 0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U, + 0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U, + 0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU, + 0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U, + 0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U, + 0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U, + 0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU, + 0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU, + 0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U, + 0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU, + 0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU, + 0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U, + 0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU, + 0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U, + 0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU, + 0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U, + 0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U, + 0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U, + 0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU, + 0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U, + 0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU, + 0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U, + 0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU, + 0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U, + 0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U, + 0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU, + 0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU, + 0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU, + 0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U, + 0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U, + 0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU, + 0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U, + 0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU, + 0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U, + 0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU, + 0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U, + 0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU, + 0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU, + 0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U, + 0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU, + 0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U, + 0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU, + 0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U, + 0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U, + 0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U, + 0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU, + 0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU, + 0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U, + 0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU, + 0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U, +}; +static const u32 Te2[256] = { + 0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU, + 0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U, + 0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU, + 0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U, + 0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU, + 0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U, + 0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU, + 0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U, + 0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U, + 0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU, + 0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U, + 0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U, + 0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U, + 0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU, + 0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U, + 0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U, + 0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU, + 0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U, + 0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U, + 0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U, + 0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU, + 0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU, + 0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U, + 0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU, + 0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU, + 0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U, + 0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU, + 0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U, + 0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU, + 0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U, + 0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U, + 0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U, + 0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU, + 0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U, + 0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU, + 0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U, + 0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU, + 0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U, + 0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U, + 0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU, + 0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU, + 0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU, + 0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U, + 0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U, + 0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU, + 0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U, + 0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU, + 0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U, + 0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU, + 0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U, + 0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU, + 0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU, + 0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U, + 0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU, + 0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U, + 0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU, + 0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U, + 0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U, + 0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U, + 0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU, + 0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU, + 0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U, + 0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU, + 0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U, +}; +static const u32 Te3[256] = { + + 0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U, + 0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U, + 0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U, + 0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU, + 0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU, + 0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU, + 0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U, + 0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU, + 0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU, + 0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U, + 0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U, + 0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU, + 0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU, + 0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU, + 0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU, + 0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU, + 0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U, + 0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU, + 0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU, + 0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U, + 0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U, + 0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U, + 0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U, + 0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U, + 0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU, + 0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U, + 0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU, + 0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU, + 0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U, + 0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U, + 0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U, + 0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU, + 0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U, + 0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU, + 0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU, + 0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U, + 0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U, + 0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU, + 0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U, + 0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU, + 0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U, + 0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U, + 0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U, + 0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U, + 0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU, + 0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U, + 0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU, + 0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U, + 0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU, + 0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U, + 0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU, + 0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU, + 0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU, + 0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU, + 0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U, + 0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U, + 0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U, + 0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U, + 0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U, + 0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U, + 0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU, + 0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U, + 0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU, + 0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU, +}; +static const u32 Te4[256] = { + 0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU, + 0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U, + 0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU, + 0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U, + 0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU, + 0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U, + 0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU, + 0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U, + 0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U, + 0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU, + 0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U, + 0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U, + 0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U, + 0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU, + 0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U, + 0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U, + 0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU, + 0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U, + 0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U, + 0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U, + 0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU, + 0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU, + 0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U, + 0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU, + 0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU, + 0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U, + 0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU, + 0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U, + 0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU, + 0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U, + 0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U, + 0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U, + 0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU, + 0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U, + 0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU, + 0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U, + 0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU, + 0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U, + 0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U, + 0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU, + 0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU, + 0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU, + 0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U, + 0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U, + 0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU, + 0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U, + 0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU, + 0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U, + 0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU, + 0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U, + 0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU, + 0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU, + 0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U, + 0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU, + 0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U, + 0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU, + 0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U, + 0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U, + 0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U, + 0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU, + 0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU, + 0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U, + 0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU, + 0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U, +}; +static const u32 Td0[256] = { + 0x51f4a750U, 0x7e416553U, 0x1a17a4c3U, 0x3a275e96U, + 0x3bab6bcbU, 0x1f9d45f1U, 0xacfa58abU, 0x4be30393U, + 0x2030fa55U, 0xad766df6U, 0x88cc7691U, 0xf5024c25U, + 0x4fe5d7fcU, 0xc52acbd7U, 0x26354480U, 0xb562a38fU, + 0xdeb15a49U, 0x25ba1b67U, 0x45ea0e98U, 0x5dfec0e1U, + 0xc32f7502U, 0x814cf012U, 0x8d4697a3U, 0x6bd3f9c6U, + 0x038f5fe7U, 0x15929c95U, 0xbf6d7aebU, 0x955259daU, + 0xd4be832dU, 0x587421d3U, 0x49e06929U, 0x8ec9c844U, + 0x75c2896aU, 0xf48e7978U, 0x99583e6bU, 0x27b971ddU, + 0xbee14fb6U, 0xf088ad17U, 0xc920ac66U, 0x7dce3ab4U, + 0x63df4a18U, 0xe51a3182U, 0x97513360U, 0x62537f45U, + 0xb16477e0U, 0xbb6bae84U, 0xfe81a01cU, 0xf9082b94U, + 0x70486858U, 0x8f45fd19U, 0x94de6c87U, 0x527bf8b7U, + 0xab73d323U, 0x724b02e2U, 0xe31f8f57U, 0x6655ab2aU, + 0xb2eb2807U, 0x2fb5c203U, 0x86c57b9aU, 0xd33708a5U, + 0x302887f2U, 0x23bfa5b2U, 0x02036abaU, 0xed16825cU, + 0x8acf1c2bU, 0xa779b492U, 0xf307f2f0U, 0x4e69e2a1U, + 0x65daf4cdU, 0x0605bed5U, 0xd134621fU, 0xc4a6fe8aU, + 0x342e539dU, 0xa2f355a0U, 0x058ae132U, 0xa4f6eb75U, + 0x0b83ec39U, 0x4060efaaU, 0x5e719f06U, 0xbd6e1051U, + 0x3e218af9U, 0x96dd063dU, 0xdd3e05aeU, 0x4de6bd46U, + 0x91548db5U, 0x71c45d05U, 0x0406d46fU, 0x605015ffU, + 0x1998fb24U, 0xd6bde997U, 0x894043ccU, 0x67d99e77U, + 0xb0e842bdU, 0x07898b88U, 0xe7195b38U, 0x79c8eedbU, + 0xa17c0a47U, 0x7c420fe9U, 0xf8841ec9U, 0x00000000U, + 0x09808683U, 0x322bed48U, 0x1e1170acU, 0x6c5a724eU, + 0xfd0efffbU, 0x0f853856U, 0x3daed51eU, 0x362d3927U, + 0x0a0fd964U, 0x685ca621U, 0x9b5b54d1U, 0x24362e3aU, + 0x0c0a67b1U, 0x9357e70fU, 0xb4ee96d2U, 0x1b9b919eU, + 0x80c0c54fU, 0x61dc20a2U, 0x5a774b69U, 0x1c121a16U, + 0xe293ba0aU, 0xc0a02ae5U, 0x3c22e043U, 0x121b171dU, + 0x0e090d0bU, 0xf28bc7adU, 0x2db6a8b9U, 0x141ea9c8U, + 0x57f11985U, 0xaf75074cU, 0xee99ddbbU, 0xa37f60fdU, + 0xf701269fU, 0x5c72f5bcU, 0x44663bc5U, 0x5bfb7e34U, + 0x8b432976U, 0xcb23c6dcU, 0xb6edfc68U, 0xb8e4f163U, + 0xd731dccaU, 0x42638510U, 0x13972240U, 0x84c61120U, + 0x854a247dU, 0xd2bb3df8U, 0xaef93211U, 0xc729a16dU, + 0x1d9e2f4bU, 0xdcb230f3U, 0x0d8652ecU, 0x77c1e3d0U, + 0x2bb3166cU, 0xa970b999U, 0x119448faU, 0x47e96422U, + 0xa8fc8cc4U, 0xa0f03f1aU, 0x567d2cd8U, 0x223390efU, + 0x87494ec7U, 0xd938d1c1U, 0x8ccaa2feU, 0x98d40b36U, + 0xa6f581cfU, 0xa57ade28U, 0xdab78e26U, 0x3fadbfa4U, + 0x2c3a9de4U, 0x5078920dU, 0x6a5fcc9bU, 0x547e4662U, + 0xf68d13c2U, 0x90d8b8e8U, 0x2e39f75eU, 0x82c3aff5U, + 0x9f5d80beU, 0x69d0937cU, 0x6fd52da9U, 0xcf2512b3U, + 0xc8ac993bU, 0x10187da7U, 0xe89c636eU, 0xdb3bbb7bU, + 0xcd267809U, 0x6e5918f4U, 0xec9ab701U, 0x834f9aa8U, + 0xe6956e65U, 0xaaffe67eU, 0x21bccf08U, 0xef15e8e6U, + 0xbae79bd9U, 0x4a6f36ceU, 0xea9f09d4U, 0x29b07cd6U, + 0x31a4b2afU, 0x2a3f2331U, 0xc6a59430U, 0x35a266c0U, + 0x744ebc37U, 0xfc82caa6U, 0xe090d0b0U, 0x33a7d815U, + 0xf104984aU, 0x41ecdaf7U, 0x7fcd500eU, 0x1791f62fU, + 0x764dd68dU, 0x43efb04dU, 0xccaa4d54U, 0xe49604dfU, + 0x9ed1b5e3U, 0x4c6a881bU, 0xc12c1fb8U, 0x4665517fU, + 0x9d5eea04U, 0x018c355dU, 0xfa877473U, 0xfb0b412eU, + 0xb3671d5aU, 0x92dbd252U, 0xe9105633U, 0x6dd64713U, + 0x9ad7618cU, 0x37a10c7aU, 0x59f8148eU, 0xeb133c89U, + 0xcea927eeU, 0xb761c935U, 0xe11ce5edU, 0x7a47b13cU, + 0x9cd2df59U, 0x55f2733fU, 0x1814ce79U, 0x73c737bfU, + 0x53f7cdeaU, 0x5ffdaa5bU, 0xdf3d6f14U, 0x7844db86U, + 0xcaaff381U, 0xb968c43eU, 0x3824342cU, 0xc2a3405fU, + 0x161dc372U, 0xbce2250cU, 0x283c498bU, 0xff0d9541U, + 0x39a80171U, 0x080cb3deU, 0xd8b4e49cU, 0x6456c190U, + 0x7bcb8461U, 0xd532b670U, 0x486c5c74U, 0xd0b85742U, +}; +static const u32 Td1[256] = { + 0x5051f4a7U, 0x537e4165U, 0xc31a17a4U, 0x963a275eU, + 0xcb3bab6bU, 0xf11f9d45U, 0xabacfa58U, 0x934be303U, + 0x552030faU, 0xf6ad766dU, 0x9188cc76U, 0x25f5024cU, + 0xfc4fe5d7U, 0xd7c52acbU, 0x80263544U, 0x8fb562a3U, + 0x49deb15aU, 0x6725ba1bU, 0x9845ea0eU, 0xe15dfec0U, + 0x02c32f75U, 0x12814cf0U, 0xa38d4697U, 0xc66bd3f9U, + 0xe7038f5fU, 0x9515929cU, 0xebbf6d7aU, 0xda955259U, + 0x2dd4be83U, 0xd3587421U, 0x2949e069U, 0x448ec9c8U, + 0x6a75c289U, 0x78f48e79U, 0x6b99583eU, 0xdd27b971U, + 0xb6bee14fU, 0x17f088adU, 0x66c920acU, 0xb47dce3aU, + 0x1863df4aU, 0x82e51a31U, 0x60975133U, 0x4562537fU, + 0xe0b16477U, 0x84bb6baeU, 0x1cfe81a0U, 0x94f9082bU, + 0x58704868U, 0x198f45fdU, 0x8794de6cU, 0xb7527bf8U, + 0x23ab73d3U, 0xe2724b02U, 0x57e31f8fU, 0x2a6655abU, + 0x07b2eb28U, 0x032fb5c2U, 0x9a86c57bU, 0xa5d33708U, + 0xf2302887U, 0xb223bfa5U, 0xba02036aU, 0x5ced1682U, + 0x2b8acf1cU, 0x92a779b4U, 0xf0f307f2U, 0xa14e69e2U, + 0xcd65daf4U, 0xd50605beU, 0x1fd13462U, 0x8ac4a6feU, + 0x9d342e53U, 0xa0a2f355U, 0x32058ae1U, 0x75a4f6ebU, + 0x390b83ecU, 0xaa4060efU, 0x065e719fU, 0x51bd6e10U, + 0xf93e218aU, 0x3d96dd06U, 0xaedd3e05U, 0x464de6bdU, + 0xb591548dU, 0x0571c45dU, 0x6f0406d4U, 0xff605015U, + 0x241998fbU, 0x97d6bde9U, 0xcc894043U, 0x7767d99eU, + 0xbdb0e842U, 0x8807898bU, 0x38e7195bU, 0xdb79c8eeU, + 0x47a17c0aU, 0xe97c420fU, 0xc9f8841eU, 0x00000000U, + 0x83098086U, 0x48322bedU, 0xac1e1170U, 0x4e6c5a72U, + 0xfbfd0effU, 0x560f8538U, 0x1e3daed5U, 0x27362d39U, + 0x640a0fd9U, 0x21685ca6U, 0xd19b5b54U, 0x3a24362eU, + 0xb10c0a67U, 0x0f9357e7U, 0xd2b4ee96U, 0x9e1b9b91U, + 0x4f80c0c5U, 0xa261dc20U, 0x695a774bU, 0x161c121aU, + 0x0ae293baU, 0xe5c0a02aU, 0x433c22e0U, 0x1d121b17U, + 0x0b0e090dU, 0xadf28bc7U, 0xb92db6a8U, 0xc8141ea9U, + 0x8557f119U, 0x4caf7507U, 0xbbee99ddU, 0xfda37f60U, + 0x9ff70126U, 0xbc5c72f5U, 0xc544663bU, 0x345bfb7eU, + 0x768b4329U, 0xdccb23c6U, 0x68b6edfcU, 0x63b8e4f1U, + 0xcad731dcU, 0x10426385U, 0x40139722U, 0x2084c611U, + 0x7d854a24U, 0xf8d2bb3dU, 0x11aef932U, 0x6dc729a1U, + 0x4b1d9e2fU, 0xf3dcb230U, 0xec0d8652U, 0xd077c1e3U, + 0x6c2bb316U, 0x99a970b9U, 0xfa119448U, 0x2247e964U, + 0xc4a8fc8cU, 0x1aa0f03fU, 0xd8567d2cU, 0xef223390U, + 0xc787494eU, 0xc1d938d1U, 0xfe8ccaa2U, 0x3698d40bU, + 0xcfa6f581U, 0x28a57adeU, 0x26dab78eU, 0xa43fadbfU, + 0xe42c3a9dU, 0x0d507892U, 0x9b6a5fccU, 0x62547e46U, + 0xc2f68d13U, 0xe890d8b8U, 0x5e2e39f7U, 0xf582c3afU, + 0xbe9f5d80U, 0x7c69d093U, 0xa96fd52dU, 0xb3cf2512U, + 0x3bc8ac99U, 0xa710187dU, 0x6ee89c63U, 0x7bdb3bbbU, + 0x09cd2678U, 0xf46e5918U, 0x01ec9ab7U, 0xa8834f9aU, + 0x65e6956eU, 0x7eaaffe6U, 0x0821bccfU, 0xe6ef15e8U, + 0xd9bae79bU, 0xce4a6f36U, 0xd4ea9f09U, 0xd629b07cU, + 0xaf31a4b2U, 0x312a3f23U, 0x30c6a594U, 0xc035a266U, + 0x37744ebcU, 0xa6fc82caU, 0xb0e090d0U, 0x1533a7d8U, + 0x4af10498U, 0xf741ecdaU, 0x0e7fcd50U, 0x2f1791f6U, + 0x8d764dd6U, 0x4d43efb0U, 0x54ccaa4dU, 0xdfe49604U, + 0xe39ed1b5U, 0x1b4c6a88U, 0xb8c12c1fU, 0x7f466551U, + 0x049d5eeaU, 0x5d018c35U, 0x73fa8774U, 0x2efb0b41U, + 0x5ab3671dU, 0x5292dbd2U, 0x33e91056U, 0x136dd647U, + 0x8c9ad761U, 0x7a37a10cU, 0x8e59f814U, 0x89eb133cU, + 0xeecea927U, 0x35b761c9U, 0xede11ce5U, 0x3c7a47b1U, + 0x599cd2dfU, 0x3f55f273U, 0x791814ceU, 0xbf73c737U, + 0xea53f7cdU, 0x5b5ffdaaU, 0x14df3d6fU, 0x867844dbU, + 0x81caaff3U, 0x3eb968c4U, 0x2c382434U, 0x5fc2a340U, + 0x72161dc3U, 0x0cbce225U, 0x8b283c49U, 0x41ff0d95U, + 0x7139a801U, 0xde080cb3U, 0x9cd8b4e4U, 0x906456c1U, + 0x617bcb84U, 0x70d532b6U, 0x74486c5cU, 0x42d0b857U, +}; +static const u32 Td2[256] = { + 0xa75051f4U, 0x65537e41U, 0xa4c31a17U, 0x5e963a27U, + 0x6bcb3babU, 0x45f11f9dU, 0x58abacfaU, 0x03934be3U, + 0xfa552030U, 0x6df6ad76U, 0x769188ccU, 0x4c25f502U, + 0xd7fc4fe5U, 0xcbd7c52aU, 0x44802635U, 0xa38fb562U, + 0x5a49deb1U, 0x1b6725baU, 0x0e9845eaU, 0xc0e15dfeU, + 0x7502c32fU, 0xf012814cU, 0x97a38d46U, 0xf9c66bd3U, + 0x5fe7038fU, 0x9c951592U, 0x7aebbf6dU, 0x59da9552U, + 0x832dd4beU, 0x21d35874U, 0x692949e0U, 0xc8448ec9U, + 0x896a75c2U, 0x7978f48eU, 0x3e6b9958U, 0x71dd27b9U, + 0x4fb6bee1U, 0xad17f088U, 0xac66c920U, 0x3ab47dceU, + 0x4a1863dfU, 0x3182e51aU, 0x33609751U, 0x7f456253U, + 0x77e0b164U, 0xae84bb6bU, 0xa01cfe81U, 0x2b94f908U, + 0x68587048U, 0xfd198f45U, 0x6c8794deU, 0xf8b7527bU, + 0xd323ab73U, 0x02e2724bU, 0x8f57e31fU, 0xab2a6655U, + 0x2807b2ebU, 0xc2032fb5U, 0x7b9a86c5U, 0x08a5d337U, + 0x87f23028U, 0xa5b223bfU, 0x6aba0203U, 0x825ced16U, + 0x1c2b8acfU, 0xb492a779U, 0xf2f0f307U, 0xe2a14e69U, + 0xf4cd65daU, 0xbed50605U, 0x621fd134U, 0xfe8ac4a6U, + 0x539d342eU, 0x55a0a2f3U, 0xe132058aU, 0xeb75a4f6U, + 0xec390b83U, 0xefaa4060U, 0x9f065e71U, 0x1051bd6eU, + + 0x8af93e21U, 0x063d96ddU, 0x05aedd3eU, 0xbd464de6U, + 0x8db59154U, 0x5d0571c4U, 0xd46f0406U, 0x15ff6050U, + 0xfb241998U, 0xe997d6bdU, 0x43cc8940U, 0x9e7767d9U, + 0x42bdb0e8U, 0x8b880789U, 0x5b38e719U, 0xeedb79c8U, + 0x0a47a17cU, 0x0fe97c42U, 0x1ec9f884U, 0x00000000U, + 0x86830980U, 0xed48322bU, 0x70ac1e11U, 0x724e6c5aU, + 0xfffbfd0eU, 0x38560f85U, 0xd51e3daeU, 0x3927362dU, + 0xd9640a0fU, 0xa621685cU, 0x54d19b5bU, 0x2e3a2436U, + 0x67b10c0aU, 0xe70f9357U, 0x96d2b4eeU, 0x919e1b9bU, + 0xc54f80c0U, 0x20a261dcU, 0x4b695a77U, 0x1a161c12U, + 0xba0ae293U, 0x2ae5c0a0U, 0xe0433c22U, 0x171d121bU, + 0x0d0b0e09U, 0xc7adf28bU, 0xa8b92db6U, 0xa9c8141eU, + 0x198557f1U, 0x074caf75U, 0xddbbee99U, 0x60fda37fU, + 0x269ff701U, 0xf5bc5c72U, 0x3bc54466U, 0x7e345bfbU, + 0x29768b43U, 0xc6dccb23U, 0xfc68b6edU, 0xf163b8e4U, + 0xdccad731U, 0x85104263U, 0x22401397U, 0x112084c6U, + 0x247d854aU, 0x3df8d2bbU, 0x3211aef9U, 0xa16dc729U, + 0x2f4b1d9eU, 0x30f3dcb2U, 0x52ec0d86U, 0xe3d077c1U, + 0x166c2bb3U, 0xb999a970U, 0x48fa1194U, 0x642247e9U, + 0x8cc4a8fcU, 0x3f1aa0f0U, 0x2cd8567dU, 0x90ef2233U, + 0x4ec78749U, 0xd1c1d938U, 0xa2fe8ccaU, 0x0b3698d4U, + 0x81cfa6f5U, 0xde28a57aU, 0x8e26dab7U, 0xbfa43fadU, + 0x9de42c3aU, 0x920d5078U, 0xcc9b6a5fU, 0x4662547eU, + 0x13c2f68dU, 0xb8e890d8U, 0xf75e2e39U, 0xaff582c3U, + 0x80be9f5dU, 0x937c69d0U, 0x2da96fd5U, 0x12b3cf25U, + 0x993bc8acU, 0x7da71018U, 0x636ee89cU, 0xbb7bdb3bU, + 0x7809cd26U, 0x18f46e59U, 0xb701ec9aU, 0x9aa8834fU, + 0x6e65e695U, 0xe67eaaffU, 0xcf0821bcU, 0xe8e6ef15U, + 0x9bd9bae7U, 0x36ce4a6fU, 0x09d4ea9fU, 0x7cd629b0U, + 0xb2af31a4U, 0x23312a3fU, 0x9430c6a5U, 0x66c035a2U, + 0xbc37744eU, 0xcaa6fc82U, 0xd0b0e090U, 0xd81533a7U, + 0x984af104U, 0xdaf741ecU, 0x500e7fcdU, 0xf62f1791U, + 0xd68d764dU, 0xb04d43efU, 0x4d54ccaaU, 0x04dfe496U, + 0xb5e39ed1U, 0x881b4c6aU, 0x1fb8c12cU, 0x517f4665U, + 0xea049d5eU, 0x355d018cU, 0x7473fa87U, 0x412efb0bU, + 0x1d5ab367U, 0xd25292dbU, 0x5633e910U, 0x47136dd6U, + 0x618c9ad7U, 0x0c7a37a1U, 0x148e59f8U, 0x3c89eb13U, + 0x27eecea9U, 0xc935b761U, 0xe5ede11cU, 0xb13c7a47U, + 0xdf599cd2U, 0x733f55f2U, 0xce791814U, 0x37bf73c7U, + 0xcdea53f7U, 0xaa5b5ffdU, 0x6f14df3dU, 0xdb867844U, + 0xf381caafU, 0xc43eb968U, 0x342c3824U, 0x405fc2a3U, + 0xc372161dU, 0x250cbce2U, 0x498b283cU, 0x9541ff0dU, + 0x017139a8U, 0xb3de080cU, 0xe49cd8b4U, 0xc1906456U, + 0x84617bcbU, 0xb670d532U, 0x5c74486cU, 0x5742d0b8U, +}; +static const u32 Td3[256] = { + 0xf4a75051U, 0x4165537eU, 0x17a4c31aU, 0x275e963aU, + 0xab6bcb3bU, 0x9d45f11fU, 0xfa58abacU, 0xe303934bU, + 0x30fa5520U, 0x766df6adU, 0xcc769188U, 0x024c25f5U, + 0xe5d7fc4fU, 0x2acbd7c5U, 0x35448026U, 0x62a38fb5U, + 0xb15a49deU, 0xba1b6725U, 0xea0e9845U, 0xfec0e15dU, + 0x2f7502c3U, 0x4cf01281U, 0x4697a38dU, 0xd3f9c66bU, + 0x8f5fe703U, 0x929c9515U, 0x6d7aebbfU, 0x5259da95U, + 0xbe832dd4U, 0x7421d358U, 0xe0692949U, 0xc9c8448eU, + 0xc2896a75U, 0x8e7978f4U, 0x583e6b99U, 0xb971dd27U, + 0xe14fb6beU, 0x88ad17f0U, 0x20ac66c9U, 0xce3ab47dU, + 0xdf4a1863U, 0x1a3182e5U, 0x51336097U, 0x537f4562U, + 0x6477e0b1U, 0x6bae84bbU, 0x81a01cfeU, 0x082b94f9U, + 0x48685870U, 0x45fd198fU, 0xde6c8794U, 0x7bf8b752U, + 0x73d323abU, 0x4b02e272U, 0x1f8f57e3U, 0x55ab2a66U, + 0xeb2807b2U, 0xb5c2032fU, 0xc57b9a86U, 0x3708a5d3U, + 0x2887f230U, 0xbfa5b223U, 0x036aba02U, 0x16825cedU, + 0xcf1c2b8aU, 0x79b492a7U, 0x07f2f0f3U, 0x69e2a14eU, + 0xdaf4cd65U, 0x05bed506U, 0x34621fd1U, 0xa6fe8ac4U, + 0x2e539d34U, 0xf355a0a2U, 0x8ae13205U, 0xf6eb75a4U, + 0x83ec390bU, 0x60efaa40U, 0x719f065eU, 0x6e1051bdU, + 0x218af93eU, 0xdd063d96U, 0x3e05aeddU, 0xe6bd464dU, + 0x548db591U, 0xc45d0571U, 0x06d46f04U, 0x5015ff60U, + 0x98fb2419U, 0xbde997d6U, 0x4043cc89U, 0xd99e7767U, + 0xe842bdb0U, 0x898b8807U, 0x195b38e7U, 0xc8eedb79U, + 0x7c0a47a1U, 0x420fe97cU, 0x841ec9f8U, 0x00000000U, + 0x80868309U, 0x2bed4832U, 0x1170ac1eU, 0x5a724e6cU, + 0x0efffbfdU, 0x8538560fU, 0xaed51e3dU, 0x2d392736U, + 0x0fd9640aU, 0x5ca62168U, 0x5b54d19bU, 0x362e3a24U, + 0x0a67b10cU, 0x57e70f93U, 0xee96d2b4U, 0x9b919e1bU, + 0xc0c54f80U, 0xdc20a261U, 0x774b695aU, 0x121a161cU, + 0x93ba0ae2U, 0xa02ae5c0U, 0x22e0433cU, 0x1b171d12U, + 0x090d0b0eU, 0x8bc7adf2U, 0xb6a8b92dU, 0x1ea9c814U, + 0xf1198557U, 0x75074cafU, 0x99ddbbeeU, 0x7f60fda3U, + 0x01269ff7U, 0x72f5bc5cU, 0x663bc544U, 0xfb7e345bU, + 0x4329768bU, 0x23c6dccbU, 0xedfc68b6U, 0xe4f163b8U, + 0x31dccad7U, 0x63851042U, 0x97224013U, 0xc6112084U, + 0x4a247d85U, 0xbb3df8d2U, 0xf93211aeU, 0x29a16dc7U, + 0x9e2f4b1dU, 0xb230f3dcU, 0x8652ec0dU, 0xc1e3d077U, + 0xb3166c2bU, 0x70b999a9U, 0x9448fa11U, 0xe9642247U, + 0xfc8cc4a8U, 0xf03f1aa0U, 0x7d2cd856U, 0x3390ef22U, + 0x494ec787U, 0x38d1c1d9U, 0xcaa2fe8cU, 0xd40b3698U, + 0xf581cfa6U, 0x7ade28a5U, 0xb78e26daU, 0xadbfa43fU, + 0x3a9de42cU, 0x78920d50U, 0x5fcc9b6aU, 0x7e466254U, + 0x8d13c2f6U, 0xd8b8e890U, 0x39f75e2eU, 0xc3aff582U, + 0x5d80be9fU, 0xd0937c69U, 0xd52da96fU, 0x2512b3cfU, + 0xac993bc8U, 0x187da710U, 0x9c636ee8U, 0x3bbb7bdbU, + 0x267809cdU, 0x5918f46eU, 0x9ab701ecU, 0x4f9aa883U, + 0x956e65e6U, 0xffe67eaaU, 0xbccf0821U, 0x15e8e6efU, + 0xe79bd9baU, 0x6f36ce4aU, 0x9f09d4eaU, 0xb07cd629U, + 0xa4b2af31U, 0x3f23312aU, 0xa59430c6U, 0xa266c035U, + 0x4ebc3774U, 0x82caa6fcU, 0x90d0b0e0U, 0xa7d81533U, + 0x04984af1U, 0xecdaf741U, 0xcd500e7fU, 0x91f62f17U, + 0x4dd68d76U, 0xefb04d43U, 0xaa4d54ccU, 0x9604dfe4U, + 0xd1b5e39eU, 0x6a881b4cU, 0x2c1fb8c1U, 0x65517f46U, + 0x5eea049dU, 0x8c355d01U, 0x877473faU, 0x0b412efbU, + 0x671d5ab3U, 0xdbd25292U, 0x105633e9U, 0xd647136dU, + 0xd7618c9aU, 0xa10c7a37U, 0xf8148e59U, 0x133c89ebU, + 0xa927eeceU, 0x61c935b7U, 0x1ce5ede1U, 0x47b13c7aU, + 0xd2df599cU, 0xf2733f55U, 0x14ce7918U, 0xc737bf73U, + 0xf7cdea53U, 0xfdaa5b5fU, 0x3d6f14dfU, 0x44db8678U, + 0xaff381caU, 0x68c43eb9U, 0x24342c38U, 0xa3405fc2U, + 0x1dc37216U, 0xe2250cbcU, 0x3c498b28U, 0x0d9541ffU, + 0xa8017139U, 0x0cb3de08U, 0xb4e49cd8U, 0x56c19064U, + 0xcb84617bU, 0x32b670d5U, 0x6c5c7448U, 0xb85742d0U, +}; +static const u32 Td4[256] = { + 0x52525252U, 0x09090909U, 0x6a6a6a6aU, 0xd5d5d5d5U, + 0x30303030U, 0x36363636U, 0xa5a5a5a5U, 0x38383838U, + 0xbfbfbfbfU, 0x40404040U, 0xa3a3a3a3U, 0x9e9e9e9eU, + 0x81818181U, 0xf3f3f3f3U, 0xd7d7d7d7U, 0xfbfbfbfbU, + 0x7c7c7c7cU, 0xe3e3e3e3U, 0x39393939U, 0x82828282U, + 0x9b9b9b9bU, 0x2f2f2f2fU, 0xffffffffU, 0x87878787U, + 0x34343434U, 0x8e8e8e8eU, 0x43434343U, 0x44444444U, + 0xc4c4c4c4U, 0xdedededeU, 0xe9e9e9e9U, 0xcbcbcbcbU, + 0x54545454U, 0x7b7b7b7bU, 0x94949494U, 0x32323232U, + 0xa6a6a6a6U, 0xc2c2c2c2U, 0x23232323U, 0x3d3d3d3dU, + 0xeeeeeeeeU, 0x4c4c4c4cU, 0x95959595U, 0x0b0b0b0bU, + 0x42424242U, 0xfafafafaU, 0xc3c3c3c3U, 0x4e4e4e4eU, + 0x08080808U, 0x2e2e2e2eU, 0xa1a1a1a1U, 0x66666666U, + 0x28282828U, 0xd9d9d9d9U, 0x24242424U, 0xb2b2b2b2U, + 0x76767676U, 0x5b5b5b5bU, 0xa2a2a2a2U, 0x49494949U, + 0x6d6d6d6dU, 0x8b8b8b8bU, 0xd1d1d1d1U, 0x25252525U, + 0x72727272U, 0xf8f8f8f8U, 0xf6f6f6f6U, 0x64646464U, + 0x86868686U, 0x68686868U, 0x98989898U, 0x16161616U, + 0xd4d4d4d4U, 0xa4a4a4a4U, 0x5c5c5c5cU, 0xccccccccU, + 0x5d5d5d5dU, 0x65656565U, 0xb6b6b6b6U, 0x92929292U, + 0x6c6c6c6cU, 0x70707070U, 0x48484848U, 0x50505050U, + 0xfdfdfdfdU, 0xededededU, 0xb9b9b9b9U, 0xdadadadaU, + 0x5e5e5e5eU, 0x15151515U, 0x46464646U, 0x57575757U, + 0xa7a7a7a7U, 0x8d8d8d8dU, 0x9d9d9d9dU, 0x84848484U, + 0x90909090U, 0xd8d8d8d8U, 0xababababU, 0x00000000U, + 0x8c8c8c8cU, 0xbcbcbcbcU, 0xd3d3d3d3U, 0x0a0a0a0aU, + 0xf7f7f7f7U, 0xe4e4e4e4U, 0x58585858U, 0x05050505U, + 0xb8b8b8b8U, 0xb3b3b3b3U, 0x45454545U, 0x06060606U, + 0xd0d0d0d0U, 0x2c2c2c2cU, 0x1e1e1e1eU, 0x8f8f8f8fU, + 0xcacacacaU, 0x3f3f3f3fU, 0x0f0f0f0fU, 0x02020202U, + 0xc1c1c1c1U, 0xafafafafU, 0xbdbdbdbdU, 0x03030303U, + 0x01010101U, 0x13131313U, 0x8a8a8a8aU, 0x6b6b6b6bU, + 0x3a3a3a3aU, 0x91919191U, 0x11111111U, 0x41414141U, + 0x4f4f4f4fU, 0x67676767U, 0xdcdcdcdcU, 0xeaeaeaeaU, + 0x97979797U, 0xf2f2f2f2U, 0xcfcfcfcfU, 0xcecececeU, + 0xf0f0f0f0U, 0xb4b4b4b4U, 0xe6e6e6e6U, 0x73737373U, + 0x96969696U, 0xacacacacU, 0x74747474U, 0x22222222U, + 0xe7e7e7e7U, 0xadadadadU, 0x35353535U, 0x85858585U, + 0xe2e2e2e2U, 0xf9f9f9f9U, 0x37373737U, 0xe8e8e8e8U, + 0x1c1c1c1cU, 0x75757575U, 0xdfdfdfdfU, 0x6e6e6e6eU, + 0x47474747U, 0xf1f1f1f1U, 0x1a1a1a1aU, 0x71717171U, + 0x1d1d1d1dU, 0x29292929U, 0xc5c5c5c5U, 0x89898989U, + 0x6f6f6f6fU, 0xb7b7b7b7U, 0x62626262U, 0x0e0e0e0eU, + 0xaaaaaaaaU, 0x18181818U, 0xbebebebeU, 0x1b1b1b1bU, + 0xfcfcfcfcU, 0x56565656U, 0x3e3e3e3eU, 0x4b4b4b4bU, + 0xc6c6c6c6U, 0xd2d2d2d2U, 0x79797979U, 0x20202020U, + 0x9a9a9a9aU, 0xdbdbdbdbU, 0xc0c0c0c0U, 0xfefefefeU, + 0x78787878U, 0xcdcdcdcdU, 0x5a5a5a5aU, 0xf4f4f4f4U, + 0x1f1f1f1fU, 0xddddddddU, 0xa8a8a8a8U, 0x33333333U, + 0x88888888U, 0x07070707U, 0xc7c7c7c7U, 0x31313131U, + 0xb1b1b1b1U, 0x12121212U, 0x10101010U, 0x59595959U, + 0x27272727U, 0x80808080U, 0xececececU, 0x5f5f5f5fU, + 0x60606060U, 0x51515151U, 0x7f7f7f7fU, 0xa9a9a9a9U, + 0x19191919U, 0xb5b5b5b5U, 0x4a4a4a4aU, 0x0d0d0d0dU, + 0x2d2d2d2dU, 0xe5e5e5e5U, 0x7a7a7a7aU, 0x9f9f9f9fU, + 0x93939393U, 0xc9c9c9c9U, 0x9c9c9c9cU, 0xefefefefU, + 0xa0a0a0a0U, 0xe0e0e0e0U, 0x3b3b3b3bU, 0x4d4d4d4dU, + 0xaeaeaeaeU, 0x2a2a2a2aU, 0xf5f5f5f5U, 0xb0b0b0b0U, + 0xc8c8c8c8U, 0xebebebebU, 0xbbbbbbbbU, 0x3c3c3c3cU, + 0x83838383U, 0x53535353U, 0x99999999U, 0x61616161U, + 0x17171717U, 0x2b2b2b2bU, 0x04040404U, 0x7e7e7e7eU, + 0xbabababaU, 0x77777777U, 0xd6d6d6d6U, 0x26262626U, + 0xe1e1e1e1U, 0x69696969U, 0x14141414U, 0x63636363U, + 0x55555555U, 0x21212121U, 0x0c0c0c0cU, 0x7d7d7d7dU, +}; +static const u32 rcon[] = { + 0x01000000, 0x02000000, 0x04000000, 0x08000000, + 0x10000000, 0x20000000, 0x40000000, 0x80000000, + 0x1B000000, 0x36000000, /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */ +}; + +/** + * Expand the cipher key into the encryption key schedule. + */ +int AES_set_encrypt_key(const unsigned char *userKey, const int bits, + AES_KEY *key) { + + u32 *rk; + int i = 0; + u32 temp; + + if (!userKey || !key) + return -1; + if (bits != 128 && bits != 192 && bits != 256) + return -2; + + rk = key->rd_key; + + if (bits==128) + key->rounds = 10; + else if (bits==192) + key->rounds = 12; + else + key->rounds = 14; + + rk[0] = GETU32(userKey ); + rk[1] = GETU32(userKey + 4); + rk[2] = GETU32(userKey + 8); + rk[3] = GETU32(userKey + 12); + if (bits == 128) { + while (1) { + temp = rk[3]; + rk[4] = rk[0] ^ + (Te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (Te4[(temp ) & 0xff] & 0x0000ff00) ^ + (Te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[5] = rk[1] ^ rk[4]; + rk[6] = rk[2] ^ rk[5]; + rk[7] = rk[3] ^ rk[6]; + if (++i == 10) { + return 0; + } + rk += 4; + } + } + rk[4] = GETU32(userKey + 16); + rk[5] = GETU32(userKey + 20); + if (bits == 192) { + while (1) { + temp = rk[ 5]; + rk[ 6] = rk[ 0] ^ + (Te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (Te4[(temp ) & 0xff] & 0x0000ff00) ^ + (Te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[ 7] = rk[ 1] ^ rk[ 6]; + rk[ 8] = rk[ 2] ^ rk[ 7]; + rk[ 9] = rk[ 3] ^ rk[ 8]; + if (++i == 8) { + return 0; + } + rk[10] = rk[ 4] ^ rk[ 9]; + rk[11] = rk[ 5] ^ rk[10]; + rk += 6; + } + } + rk[6] = GETU32(userKey + 24); + rk[7] = GETU32(userKey + 28); + if (bits == 256) { + while (1) { + temp = rk[ 7]; + rk[ 8] = rk[ 0] ^ + (Te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (Te4[(temp ) & 0xff] & 0x0000ff00) ^ + (Te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[ 9] = rk[ 1] ^ rk[ 8]; + rk[10] = rk[ 2] ^ rk[ 9]; + rk[11] = rk[ 3] ^ rk[10]; + if (++i == 7) { + return 0; + } + temp = rk[11]; + rk[12] = rk[ 4] ^ + (Te4[(temp >> 24) ] & 0xff000000) ^ + (Te4[(temp >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(temp >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(temp ) & 0xff] & 0x000000ff); + rk[13] = rk[ 5] ^ rk[12]; + rk[14] = rk[ 6] ^ rk[13]; + rk[15] = rk[ 7] ^ rk[14]; + + rk += 8; + } + } + return 0; +} + +/** + * Expand the cipher key into the decryption key schedule. + */ +int AES_set_decrypt_key(const unsigned char *userKey, const int bits, + AES_KEY *key) { + + u32 *rk; + int i, j, status; + u32 temp; + + /* first, start with an encryption schedule */ + status = AES_set_encrypt_key(userKey, bits, key); + if (status < 0) + return status; + + rk = key->rd_key; + + /* invert the order of the round keys: */ + for (i = 0, j = 4*(key->rounds); i < j; i += 4, j -= 4) { + temp = rk[i ]; rk[i ] = rk[j ]; rk[j ] = temp; + temp = rk[i + 1]; rk[i + 1] = rk[j + 1]; rk[j + 1] = temp; + temp = rk[i + 2]; rk[i + 2] = rk[j + 2]; rk[j + 2] = temp; + temp = rk[i + 3]; rk[i + 3] = rk[j + 3]; rk[j + 3] = temp; + } + /* apply the inverse MixColumn transform to all round keys but the first and the last: */ + for (i = 1; i < (key->rounds); i++) { + rk += 4; + rk[0] = + Td0[Te4[(rk[0] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[0] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[0] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[0] ) & 0xff] & 0xff]; + rk[1] = + Td0[Te4[(rk[1] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[1] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[1] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[1] ) & 0xff] & 0xff]; + rk[2] = + Td0[Te4[(rk[2] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[2] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[2] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[2] ) & 0xff] & 0xff]; + rk[3] = + Td0[Te4[(rk[3] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[3] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[3] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[3] ) & 0xff] & 0xff]; + } + return 0; +} + +#ifndef AES_ASM +/* + * Encrypt a single block + * in and out can overlap + */ +void AES_encrypt(const unsigned char *in, unsigned char *out, + const AES_KEY *key) { + + const u32 *rk; + u32 s0, s1, s2, s3, t0, t1, t2, t3; +#ifndef FULL_UNROLL + int r; +#endif /* ?FULL_UNROLL */ + + assert(in && out && key); + rk = key->rd_key; + + /* + * map byte array block to cipher state + * and add initial round key: + */ + s0 = GETU32(in ) ^ rk[0]; + s1 = GETU32(in + 4) ^ rk[1]; + s2 = GETU32(in + 8) ^ rk[2]; + s3 = GETU32(in + 12) ^ rk[3]; +#ifdef FULL_UNROLL + /* round 1: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[ 4]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[ 5]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[ 6]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[ 7]; + /* round 2: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[ 8]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[ 9]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[10]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[11]; + /* round 3: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[12]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[13]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[14]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[15]; + /* round 4: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[16]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[17]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[18]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[19]; + /* round 5: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[20]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[21]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[22]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[23]; + /* round 6: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[24]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[25]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[26]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[27]; + /* round 7: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[28]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[29]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[30]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[31]; + /* round 8: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[32]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[33]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[34]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[35]; + /* round 9: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[36]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[37]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[38]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[39]; + if (key->rounds > 10) { + /* round 10: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[40]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[41]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[42]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[43]; + /* round 11: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[44]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[45]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[46]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[47]; + if (key->rounds > 12) { + /* round 12: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[48]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[49]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[50]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[51]; + /* round 13: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[52]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[53]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[54]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[55]; + } + } + rk += key->rounds << 2; +#else /* !FULL_UNROLL */ + /* + * Nr - 1 full rounds: + */ + r = key->rounds >> 1; + for (;;) { + t0 = + Te0[(s0 >> 24) ] ^ + Te1[(s1 >> 16) & 0xff] ^ + Te2[(s2 >> 8) & 0xff] ^ + Te3[(s3 ) & 0xff] ^ + rk[4]; + t1 = + Te0[(s1 >> 24) ] ^ + Te1[(s2 >> 16) & 0xff] ^ + Te2[(s3 >> 8) & 0xff] ^ + Te3[(s0 ) & 0xff] ^ + rk[5]; + t2 = + Te0[(s2 >> 24) ] ^ + Te1[(s3 >> 16) & 0xff] ^ + Te2[(s0 >> 8) & 0xff] ^ + Te3[(s1 ) & 0xff] ^ + rk[6]; + t3 = + Te0[(s3 >> 24) ] ^ + Te1[(s0 >> 16) & 0xff] ^ + Te2[(s1 >> 8) & 0xff] ^ + Te3[(s2 ) & 0xff] ^ + rk[7]; + + rk += 8; + if (--r == 0) { + break; + } + + s0 = + Te0[(t0 >> 24) ] ^ + Te1[(t1 >> 16) & 0xff] ^ + Te2[(t2 >> 8) & 0xff] ^ + Te3[(t3 ) & 0xff] ^ + rk[0]; + s1 = + Te0[(t1 >> 24) ] ^ + Te1[(t2 >> 16) & 0xff] ^ + Te2[(t3 >> 8) & 0xff] ^ + Te3[(t0 ) & 0xff] ^ + rk[1]; + s2 = + Te0[(t2 >> 24) ] ^ + Te1[(t3 >> 16) & 0xff] ^ + Te2[(t0 >> 8) & 0xff] ^ + Te3[(t1 ) & 0xff] ^ + rk[2]; + s3 = + Te0[(t3 >> 24) ] ^ + Te1[(t0 >> 16) & 0xff] ^ + Te2[(t1 >> 8) & 0xff] ^ + Te3[(t2 ) & 0xff] ^ + rk[3]; + } +#endif /* ?FULL_UNROLL */ + /* + * apply last round and + * map cipher state to byte array block: + */ + s0 = + (Te4[(t0 >> 24) ] & 0xff000000) ^ + (Te4[(t1 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t2 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t3 ) & 0xff] & 0x000000ff) ^ + rk[0]; + PUTU32(out , s0); + s1 = + (Te4[(t1 >> 24) ] & 0xff000000) ^ + (Te4[(t2 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t3 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t0 ) & 0xff] & 0x000000ff) ^ + rk[1]; + PUTU32(out + 4, s1); + s2 = + (Te4[(t2 >> 24) ] & 0xff000000) ^ + (Te4[(t3 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t0 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t1 ) & 0xff] & 0x000000ff) ^ + rk[2]; + PUTU32(out + 8, s2); + s3 = + (Te4[(t3 >> 24) ] & 0xff000000) ^ + (Te4[(t0 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t1 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t2 ) & 0xff] & 0x000000ff) ^ + rk[3]; + PUTU32(out + 12, s3); +} + +/* + * Decrypt a single block + * in and out can overlap + */ +void AES_decrypt(const unsigned char *in, unsigned char *out, + const AES_KEY *key) { + + const u32 *rk; + u32 s0, s1, s2, s3, t0, t1, t2, t3; +#ifndef FULL_UNROLL + int r; +#endif /* ?FULL_UNROLL */ + + assert(in && out && key); + rk = key->rd_key; + + /* + * map byte array block to cipher state + * and add initial round key: + */ + s0 = GETU32(in ) ^ rk[0]; + s1 = GETU32(in + 4) ^ rk[1]; + s2 = GETU32(in + 8) ^ rk[2]; + s3 = GETU32(in + 12) ^ rk[3]; +#ifdef FULL_UNROLL + /* round 1: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[ 4]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[ 5]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[ 6]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[ 7]; + /* round 2: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[ 8]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[ 9]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[10]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[11]; + /* round 3: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[12]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[13]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[14]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[15]; + /* round 4: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[16]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[17]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[18]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[19]; + /* round 5: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[20]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[21]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[22]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[23]; + /* round 6: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[24]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[25]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[26]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[27]; + /* round 7: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[28]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[29]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[30]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[31]; + /* round 8: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[32]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[33]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[34]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[35]; + /* round 9: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[36]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[37]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[38]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[39]; + if (key->rounds > 10) { + /* round 10: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[40]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[41]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[42]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[43]; + /* round 11: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[44]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[45]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[46]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[47]; + if (key->rounds > 12) { + /* round 12: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[48]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[49]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[50]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[51]; + /* round 13: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[52]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[53]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[54]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[55]; + } + } + rk += key->rounds << 2; +#else /* !FULL_UNROLL */ + /* + * Nr - 1 full rounds: + */ + r = key->rounds >> 1; + for (;;) { + t0 = + Td0[(s0 >> 24) ] ^ + Td1[(s3 >> 16) & 0xff] ^ + Td2[(s2 >> 8) & 0xff] ^ + Td3[(s1 ) & 0xff] ^ + rk[4]; + t1 = + Td0[(s1 >> 24) ] ^ + Td1[(s0 >> 16) & 0xff] ^ + Td2[(s3 >> 8) & 0xff] ^ + Td3[(s2 ) & 0xff] ^ + rk[5]; + t2 = + Td0[(s2 >> 24) ] ^ + Td1[(s1 >> 16) & 0xff] ^ + Td2[(s0 >> 8) & 0xff] ^ + Td3[(s3 ) & 0xff] ^ + rk[6]; + t3 = + Td0[(s3 >> 24) ] ^ + Td1[(s2 >> 16) & 0xff] ^ + Td2[(s1 >> 8) & 0xff] ^ + Td3[(s0 ) & 0xff] ^ + rk[7]; + + rk += 8; + if (--r == 0) { + break; + } + + s0 = + Td0[(t0 >> 24) ] ^ + Td1[(t3 >> 16) & 0xff] ^ + Td2[(t2 >> 8) & 0xff] ^ + Td3[(t1 ) & 0xff] ^ + rk[0]; + s1 = + Td0[(t1 >> 24) ] ^ + Td1[(t0 >> 16) & 0xff] ^ + Td2[(t3 >> 8) & 0xff] ^ + Td3[(t2 ) & 0xff] ^ + rk[1]; + s2 = + Td0[(t2 >> 24) ] ^ + Td1[(t1 >> 16) & 0xff] ^ + Td2[(t0 >> 8) & 0xff] ^ + Td3[(t3 ) & 0xff] ^ + rk[2]; + s3 = + Td0[(t3 >> 24) ] ^ + Td1[(t2 >> 16) & 0xff] ^ + Td2[(t1 >> 8) & 0xff] ^ + Td3[(t0 ) & 0xff] ^ + rk[3]; + } +#endif /* ?FULL_UNROLL */ + /* + * apply last round and + * map cipher state to byte array block: + */ + s0 = + (Td4[(t0 >> 24) ] & 0xff000000) ^ + (Td4[(t3 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t2 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t1 ) & 0xff] & 0x000000ff) ^ + rk[0]; + PUTU32(out , s0); + s1 = + (Td4[(t1 >> 24) ] & 0xff000000) ^ + (Td4[(t0 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t3 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t2 ) & 0xff] & 0x000000ff) ^ + rk[1]; + PUTU32(out + 4, s1); + s2 = + (Td4[(t2 >> 24) ] & 0xff000000) ^ + (Td4[(t1 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t0 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t3 ) & 0xff] & 0x000000ff) ^ + rk[2]; + PUTU32(out + 8, s2); + s3 = + (Td4[(t3 >> 24) ] & 0xff000000) ^ + (Td4[(t2 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t1 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t0 ) & 0xff] & 0x000000ff) ^ + rk[3]; + PUTU32(out + 12, s3); +} + +#endif /* AES_ASM */ + +void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, + const unsigned long length, const AES_KEY *key, + unsigned char *ivec, const int enc) +{ + + unsigned long n; + unsigned long len = length; + unsigned char tmp[AES_BLOCK_SIZE]; + + assert(in && out && key && ivec); + + if (enc) { + while (len >= AES_BLOCK_SIZE) { + for(n=0; n < AES_BLOCK_SIZE; ++n) + tmp[n] = in[n] ^ ivec[n]; + AES_encrypt(tmp, out, key); + memcpy(ivec, out, AES_BLOCK_SIZE); + len -= AES_BLOCK_SIZE; + in += AES_BLOCK_SIZE; + out += AES_BLOCK_SIZE; + } + if (len) { + for(n=0; n < len; ++n) + tmp[n] = in[n] ^ ivec[n]; + for(n=len; n < AES_BLOCK_SIZE; ++n) + tmp[n] = ivec[n]; + AES_encrypt(tmp, tmp, key); + memcpy(out, tmp, AES_BLOCK_SIZE); + memcpy(ivec, tmp, AES_BLOCK_SIZE); + } + } else { + while (len >= AES_BLOCK_SIZE) { + memcpy(tmp, in, AES_BLOCK_SIZE); + AES_decrypt(in, out, key); + for(n=0; n < AES_BLOCK_SIZE; ++n) + out[n] ^= ivec[n]; + memcpy(ivec, tmp, AES_BLOCK_SIZE); + len -= AES_BLOCK_SIZE; + in += AES_BLOCK_SIZE; + out += AES_BLOCK_SIZE; + } + if (len) { + memcpy(tmp, in, AES_BLOCK_SIZE); + AES_decrypt(tmp, tmp, key); + for(n=0; n < len; ++n) + out[n] = tmp[n] ^ ivec[n]; + memcpy(ivec, tmp, AES_BLOCK_SIZE); + } + } +} diff --git a/aes.h b/aes.h new file mode 100644 index 0000000..c4d7a1c --- /dev/null +++ b/aes.h @@ -0,0 +1,49 @@ +/* + * OpenSSL compatible AES header + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef AES_H +#define AES_H + +#define AES_MAXNR 14 +#define AES_BLOCK_SIZE 16 + +struct aes_key_st { + uint32_t rd_key[4 *(AES_MAXNR + 1)]; + int rounds; +}; +typedef struct aes_key_st AES_KEY; + +int AES_set_encrypt_key(const unsigned char *userKey, const int bits, + AES_KEY *key); +int AES_set_decrypt_key(const unsigned char *userKey, const int bits, + AES_KEY *key); + +void AES_encrypt(const unsigned char *in, unsigned char *out, + const AES_KEY *key); +void AES_decrypt(const unsigned char *in, unsigned char *out, + const AES_KEY *key); +void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, + const unsigned long length, const AES_KEY *key, + unsigned char *ivec, const int enc); + +#endif diff --git a/block_net.c b/block_net.c new file mode 100644 index 0000000..66794d4 --- /dev/null +++ b/block_net.c @@ -0,0 +1,531 @@ +/* + * HTTP block device + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "virtio.h" +#include "fs_wget.h" +#include "list.h" +#include "fbuf.h" +#include "machine.h" + +typedef enum { + CBLOCK_LOADING, + CBLOCK_LOADED, +} CachedBlockStateEnum; + +typedef struct CachedBlock { + struct list_head link; + struct BlockDeviceHTTP *bf; + unsigned int block_num; + CachedBlockStateEnum state; + FileBuffer fbuf; +} CachedBlock; + +#define BLK_FMT "%sblk%09u.bin" +#define GROUP_FMT "%sgrp%09u.bin" +#define PREFETCH_GROUP_LEN_MAX 32 + +typedef struct { + struct BlockDeviceHTTP *bf; + int group_num; + int n_block_num; + CachedBlock *tab_block[PREFETCH_GROUP_LEN_MAX]; +} PrefetchGroupRequest; + +/* modified data is stored per cluster (smaller than cached blocks to + avoid losing space) */ +typedef struct Cluster { + FileBuffer fbuf; +} Cluster; + +typedef struct BlockDeviceHTTP { + BlockDevice *bs; + int max_cache_size_kb; + char url[1024]; + int prefetch_count; + void (*start_cb)(void *opaque); + void *start_opaque; + + int64_t nb_sectors; + int block_size; /* in sectors, power of two */ + int nb_blocks; + struct list_head cached_blocks; /* list of CachedBlock */ + int n_cached_blocks; + int n_cached_blocks_max; + + /* write support */ + int sectors_per_cluster; /* power of two */ + Cluster **clusters; /* NULL if no written data */ + int n_clusters; + int n_allocated_clusters; + + /* statistics */ + int64_t n_read_sectors; + int64_t n_read_blocks; + int64_t n_write_sectors; + + /* current read request */ + BOOL is_write; + uint64_t sector_num; + int cur_block_num; + int sector_index, sector_count; + BlockDeviceCompletionFunc *cb; + void *opaque; + uint8_t *io_buf; + + /* prefetch */ + int prefetch_group_len; +} BlockDeviceHTTP; + +static void bf_update_block(CachedBlock *b, const uint8_t *data); +static void bf_read_onload(void *opaque, int err, void *data, size_t size); +static void bf_init_onload(void *opaque, int err, void *data, size_t size); +static void bf_prefetch_group_onload(void *opaque, int err, void *data, + size_t size); + +static CachedBlock *bf_find_block(BlockDeviceHTTP *bf, unsigned int block_num) +{ + CachedBlock *b; + struct list_head *el; + + list_for_each(el, &bf->cached_blocks) { + b = list_entry(el, CachedBlock, link); + if (b->block_num == block_num) { + /* move to front */ + if (bf->cached_blocks.next != el) { + list_del(&b->link); + list_add(&b->link, &bf->cached_blocks); + } + return b; + } + } + return NULL; +} + +static void bf_free_block(BlockDeviceHTTP *bf, CachedBlock *b) +{ + bf->n_cached_blocks--; + file_buffer_reset(&b->fbuf); + list_del(&b->link); + free(b); +} + +static CachedBlock *bf_add_block(BlockDeviceHTTP *bf, unsigned int block_num) +{ + CachedBlock *b; + if (bf->n_cached_blocks >= bf->n_cached_blocks_max) { + struct list_head *el, *el1; + /* start by looking at the least unused blocks */ + list_for_each_prev_safe(el, el1, &bf->cached_blocks) { + b = list_entry(el, CachedBlock, link); + if (b->state == CBLOCK_LOADED) { + bf_free_block(bf, b); + if (bf->n_cached_blocks < bf->n_cached_blocks_max) + break; + } + } + } + b = mallocz(sizeof(CachedBlock)); + b->bf = bf; + b->block_num = block_num; + b->state = CBLOCK_LOADING; + file_buffer_init(&b->fbuf); + file_buffer_resize(&b->fbuf, bf->block_size * 512); + list_add(&b->link, &bf->cached_blocks); + bf->n_cached_blocks++; + return b; +} + +static int64_t bf_get_sector_count(BlockDevice *bs) +{ + BlockDeviceHTTP *bf = bs->opaque; + return bf->nb_sectors; +} + +static void bf_start_load_block(BlockDevice *bs, int block_num) +{ + BlockDeviceHTTP *bf = bs->opaque; + char filename[1024]; + CachedBlock *b; + b = bf_add_block(bf, block_num); + bf->n_read_blocks++; + /* make a XHR to read the block */ +#if 0 + printf("%u,\n", block_num); +#endif +#if 0 + printf("load_blk=%d cached=%d read=%d KB (%d KB) write=%d KB (%d KB)\n", + block_num, bf->n_cached_blocks, + (int)(bf->n_read_sectors / 2), + (int)(bf->n_read_blocks * bf->block_size / 2), + (int)(bf->n_write_sectors / 2), + (int)(bf->n_allocated_clusters * bf->sectors_per_cluster / 2)); +#endif + snprintf(filename, sizeof(filename), BLK_FMT, bf->url, block_num); + // printf("wget %s\n", filename); + fs_wget(filename, NULL, NULL, b, bf_read_onload, TRUE); +} + +static void bf_start_load_prefetch_group(BlockDevice *bs, int group_num, + const int *tab_block_num, + int n_block_num) +{ + BlockDeviceHTTP *bf = bs->opaque; + CachedBlock *b; + PrefetchGroupRequest *req; + char filename[1024]; + BOOL req_flag; + int i; + + req_flag = FALSE; + req = malloc(sizeof(*req)); + req->bf = bf; + req->group_num = group_num; + req->n_block_num = n_block_num; + for(i = 0; i < n_block_num; i++) { + b = bf_find_block(bf, tab_block_num[i]); + if (!b) { + b = bf_add_block(bf, tab_block_num[i]); + req_flag = TRUE; + } else { + /* no need to read the block if it is already loading or + loaded */ + b = NULL; + } + req->tab_block[i] = b; + } + + if (req_flag) { + snprintf(filename, sizeof(filename), GROUP_FMT, bf->url, group_num); + // printf("wget %s\n", filename); + fs_wget(filename, NULL, NULL, req, bf_prefetch_group_onload, TRUE); + /* XXX: should add request in a list to free it for clean exit */ + } else { + free(req); + } +} + +static void bf_prefetch_group_onload(void *opaque, int err, void *data, + size_t size) +{ + PrefetchGroupRequest *req = opaque; + BlockDeviceHTTP *bf = req->bf; + CachedBlock *b; + int block_bytes, i; + + if (err < 0) { + fprintf(stderr, "Could not load group %u\n", req->group_num); + exit(1); + } + block_bytes = bf->block_size * 512; + assert(size == block_bytes * req->n_block_num); + for(i = 0; i < req->n_block_num; i++) { + b = req->tab_block[i]; + if (b) { + bf_update_block(b, (const uint8_t *)data + block_bytes * i); + } + } + free(req); +} + +static int bf_rw_async1(BlockDevice *bs, BOOL is_sync) +{ + BlockDeviceHTTP *bf = bs->opaque; + int offset, block_num, n, cluster_num; + CachedBlock *b; + Cluster *c; + + for(;;) { + n = bf->sector_count - bf->sector_index; + if (n == 0) + break; + cluster_num = bf->sector_num / bf->sectors_per_cluster; + c = bf->clusters[cluster_num]; + if (c) { + offset = bf->sector_num % bf->sectors_per_cluster; + n = min_int(n, bf->sectors_per_cluster - offset); + if (bf->is_write) { + file_buffer_write(&c->fbuf, offset * 512, + bf->io_buf + bf->sector_index * 512, n * 512); + } else { + file_buffer_read(&c->fbuf, offset * 512, + bf->io_buf + bf->sector_index * 512, n * 512); + } + bf->sector_index += n; + bf->sector_num += n; + } else { + block_num = bf->sector_num / bf->block_size; + offset = bf->sector_num % bf->block_size; + n = min_int(n, bf->block_size - offset); + bf->cur_block_num = block_num; + + b = bf_find_block(bf, block_num); + if (b) { + if (b->state == CBLOCK_LOADING) { + /* wait until the block is loaded */ + return 1; + } else { + if (bf->is_write) { + int cluster_size, cluster_offset; + uint8_t *buf; + /* allocate a new cluster */ + c = mallocz(sizeof(Cluster)); + cluster_size = bf->sectors_per_cluster * 512; + buf = malloc(cluster_size); + file_buffer_init(&c->fbuf); + file_buffer_resize(&c->fbuf, cluster_size); + bf->clusters[cluster_num] = c; + /* copy the cached block data to the cluster */ + cluster_offset = (cluster_num * bf->sectors_per_cluster) & + (bf->block_size - 1); + file_buffer_read(&b->fbuf, cluster_offset * 512, + buf, cluster_size); + file_buffer_write(&c->fbuf, 0, buf, cluster_size); + free(buf); + bf->n_allocated_clusters++; + continue; /* write to the allocated cluster */ + } else { + file_buffer_read(&b->fbuf, offset * 512, + bf->io_buf + bf->sector_index * 512, n * 512); + } + bf->sector_index += n; + bf->sector_num += n; + } + } else { + bf_start_load_block(bs, block_num); + return 1; + } + bf->cur_block_num = -1; + } + } + + if (!is_sync) { + // printf("end of request\n"); + /* end of request */ + bf->cb(bf->opaque, 0); + } + return 0; +} + +static void bf_update_block(CachedBlock *b, const uint8_t *data) +{ + BlockDeviceHTTP *bf = b->bf; + BlockDevice *bs = bf->bs; + + assert(b->state == CBLOCK_LOADING); + file_buffer_write(&b->fbuf, 0, data, bf->block_size * 512); + b->state = CBLOCK_LOADED; + + /* continue I/O read/write if necessary */ + if (b->block_num == bf->cur_block_num) { + bf_rw_async1(bs, FALSE); + } +} + +static void bf_read_onload(void *opaque, int err, void *data, size_t size) +{ + CachedBlock *b = opaque; + BlockDeviceHTTP *bf = b->bf; + + if (err < 0) { + fprintf(stderr, "Could not load block %u\n", b->block_num); + exit(1); + } + + assert(size == bf->block_size * 512); + bf_update_block(b, data); +} + +static int bf_read_async(BlockDevice *bs, + uint64_t sector_num, uint8_t *buf, int n, + BlockDeviceCompletionFunc *cb, void *opaque) +{ + BlockDeviceHTTP *bf = bs->opaque; + // printf("bf_read_async: sector_num=%" PRId64 " n=%d\n", sector_num, n); + bf->is_write = FALSE; + bf->sector_num = sector_num; + bf->io_buf = buf; + bf->sector_count = n; + bf->sector_index = 0; + bf->cb = cb; + bf->opaque = opaque; + bf->n_read_sectors += n; + return bf_rw_async1(bs, TRUE); +} + +static int bf_write_async(BlockDevice *bs, + uint64_t sector_num, const uint8_t *buf, int n, + BlockDeviceCompletionFunc *cb, void *opaque) +{ + BlockDeviceHTTP *bf = bs->opaque; + // printf("bf_write_async: sector_num=%" PRId64 " n=%d\n", sector_num, n); + bf->is_write = TRUE; + bf->sector_num = sector_num; + bf->io_buf = (uint8_t *)buf; + bf->sector_count = n; + bf->sector_index = 0; + bf->cb = cb; + bf->opaque = opaque; + bf->n_write_sectors += n; + return bf_rw_async1(bs, TRUE); +} + +BlockDevice *block_device_init_http(const char *url, + int max_cache_size_kb, + void (*start_cb)(void *opaque), + void *start_opaque) +{ + BlockDevice *bs; + BlockDeviceHTTP *bf; + char *p; + + bs = mallocz(sizeof(*bs)); + bf = mallocz(sizeof(*bf)); + strcpy(bf->url, url); + /* get the path with the trailing '/' */ + p = strrchr(bf->url, '/'); + if (!p) + p = bf->url; + else + p++; + *p = '\0'; + + init_list_head(&bf->cached_blocks); + bf->max_cache_size_kb = max_cache_size_kb; + bf->start_cb = start_cb; + bf->start_opaque = start_opaque; + bf->bs = bs; + + bs->opaque = bf; + bs->get_sector_count = bf_get_sector_count; + bs->read_async = bf_read_async; + bs->write_async = bf_write_async; + + fs_wget(url, NULL, NULL, bs, bf_init_onload, TRUE); + return bs; +} + +static void bf_init_onload(void *opaque, int err, void *data, size_t size) +{ + BlockDevice *bs = opaque; + BlockDeviceHTTP *bf = bs->opaque; + int block_size_kb, block_num; + JSONValue cfg, array; + + if (err < 0) { + fprintf(stderr, "Could not load block device file (err=%d)\n", -err); + exit(1); + } + + /* parse the disk image info */ + cfg = json_parse_value_len(data, size); + if (json_is_error(cfg)) { + vm_error("error: %s\n", json_get_error(cfg)); + config_error: + json_free(cfg); + exit(1); + } + + if (vm_get_int(cfg, "block_size", &block_size_kb) < 0) + goto config_error; + bf->block_size = block_size_kb * 2; + if (bf->block_size <= 0 || + (bf->block_size & (bf->block_size - 1)) != 0) { + vm_error("invalid block_size\n"); + goto config_error; + } + if (vm_get_int(cfg, "n_block", &bf->nb_blocks) < 0) + goto config_error; + if (bf->nb_blocks <= 0) { + vm_error("invalid n_block\n"); + goto config_error; + } + + bf->nb_sectors = bf->block_size * (uint64_t)bf->nb_blocks; + bf->n_cached_blocks = 0; + bf->n_cached_blocks_max = max_int(1, bf->max_cache_size_kb / block_size_kb); + bf->cur_block_num = -1; /* no request in progress */ + + bf->sectors_per_cluster = 8; /* 4 KB */ + bf->n_clusters = (bf->nb_sectors + bf->sectors_per_cluster - 1) / bf->sectors_per_cluster; + bf->clusters = mallocz(sizeof(bf->clusters[0]) * bf->n_clusters); + + if (vm_get_int_opt(cfg, "prefetch_group_len", + &bf->prefetch_group_len, 1) < 0) + goto config_error; + if (bf->prefetch_group_len > PREFETCH_GROUP_LEN_MAX) { + vm_error("prefetch_group_len is too large"); + goto config_error; + } + + array = json_object_get(cfg, "prefetch"); + if (!json_is_undefined(array)) { + int idx, prefetch_len, l, i; + JSONValue el; + int tab_block_num[PREFETCH_GROUP_LEN_MAX]; + + if (array.type != JSON_ARRAY) { + vm_error("expecting an array\n"); + goto config_error; + } + prefetch_len = array.u.array->len; + idx = 0; + while (idx < prefetch_len) { + l = min_int(prefetch_len - idx, bf->prefetch_group_len); + for(i = 0; i < l; i++) { + el = json_array_get(array, idx + i); + if (el.type != JSON_INT) { + vm_error("expecting an integer\n"); + goto config_error; + } + tab_block_num[i] = el.u.int32; + } + if (l == 1) { + block_num = tab_block_num[0]; + if (!bf_find_block(bf, block_num)) { + bf_start_load_block(bs, block_num); + } + } else { + bf_start_load_prefetch_group(bs, idx / bf->prefetch_group_len, + tab_block_num, l); + } + idx += l; + } + } + json_free(cfg); + + if (bf->start_cb) { + bf->start_cb(bf->start_opaque); + } +} diff --git a/build_filelist.c b/build_filelist.c new file mode 100644 index 0000000..db3c150 --- /dev/null +++ b/build_filelist.c @@ -0,0 +1,311 @@ +/* + * File list builder for RISCVEMU network filesystem + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "fs_utils.h" + +void print_str(FILE *f, const char *str) +{ + const char *s; + int c; + s = str; + while (*s != '\0') { + if (*s <= ' ' || *s > '~') + goto use_quote; + s++; + } + fputs(str, f); + return; + use_quote: + s = str; + fputc('"', f); + while (*s != '\0') { + c = *(uint8_t *)s; + if (c < ' ' || c == 127) { + fprintf(f, "\\x%02x", c); + } else if (c == '\\' || c == '\"') { + fprintf(f, "\\%c", c); + } else { + fputc(c, f); + } + s++; + } + fputc('"', f); +} + +#define COPY_BUF_LEN (1024 * 1024) + +static void copy_file(const char *src_filename, const char *dst_filename) +{ + uint8_t *buf; + FILE *fi, *fo; + int len; + + buf = malloc(COPY_BUF_LEN); + fi = fopen(src_filename, "rb"); + if (!fi) { + perror(src_filename); + exit(1); + } + fo = fopen(dst_filename, "wb"); + if (!fo) { + perror(dst_filename); + exit(1); + } + for(;;) { + len = fread(buf, 1, COPY_BUF_LEN, fi); + if (len == 0) + break; + fwrite(buf, 1, len, fo); + } + fclose(fo); + fclose(fi); +} + +typedef struct { + char *files_path; + uint64_t next_inode_num; + uint64_t fs_size; + uint64_t fs_max_size; + FILE *f; +} ScanState; + +static void add_file_size(ScanState *s, uint64_t size) +{ + s->fs_size += block_align(size, FS_BLOCK_SIZE); + if (s->fs_size > s->fs_max_size) { + fprintf(stderr, "Filesystem Quota exceeded (%" PRId64 " bytes)\n", s->fs_max_size); + exit(1); + } +} + +void scan_dir(ScanState *s, const char *path) +{ + FILE *f = s->f; + DIR *dirp; + struct dirent *de; + const char *name; + struct stat st; + char *path1; + uint32_t mode, v; + + dirp = opendir(path); + if (!dirp) { + perror(path); + exit(1); + } + for(;;) { + de = readdir(dirp); + if (!de) + break; + name = de->d_name; + if (!strcmp(name, ".") || !strcmp(name, "..")) + continue; + path1 = compose_path(path, name); + if (lstat(path1, &st) < 0) { + perror(path1); + exit(1); + } + + mode = st.st_mode & 0xffff; + fprintf(f, "%06o %u %u", + mode, + (int)st.st_uid, + (int)st.st_gid); + if (S_ISCHR(mode) || S_ISBLK(mode)) { + fprintf(f, " %u %u", + (int)major(st.st_rdev), + (int)minor(st.st_rdev)); + } + if (S_ISREG(mode)) { + fprintf(f, " %" PRIu64, st.st_size); + } + /* modification time (at most ms resolution) */ + fprintf(f, " %u", (int)st.st_mtim.tv_sec); + v = st.st_mtim.tv_nsec; + if (v != 0) { + fprintf(f, "."); + while (v != 0) { + fprintf(f, "%u", v / 100000000); + v = (v % 100000000) * 10; + } + } + + fprintf(f, " "); + print_str(f, name); + if (S_ISLNK(mode)) { + char buf[1024]; + int len; + len = readlink(path1, buf, sizeof(buf) - 1); + if (len < 0) { + perror("readlink"); + exit(1); + } + buf[len] = '\0'; + fprintf(f, " "); + print_str(f, buf); + } else if (S_ISREG(mode) && st.st_size > 0) { + char buf1[FILEID_SIZE_MAX], *fname; + FSFileID file_id; + file_id = s->next_inode_num++; + fprintf(f, " %" PRIx64, file_id); + file_id_to_filename(buf1, file_id); + fname = compose_path(s->files_path, buf1); + copy_file(path1, fname); + add_file_size(s, st.st_size); + } + + fprintf(f, "\n"); + if (S_ISDIR(mode)) { + scan_dir(s, path1); + } + free(path1); + } + + closedir(dirp); + fprintf(f, ".\n"); /* end of directory */ +} + +void help(void) +{ + printf("usage: build_filelist [options] source_path dest_path\n" + "\n" + "Options:\n" + "-m size_mb set the max filesystem size in MiB\n"); + exit(1); +} + +#define LOCK_FILENAME "lock" + +int main(int argc, char **argv) +{ + const char *dst_path, *src_path; + ScanState s_s, *s = &s_s; + FILE *f; + char *filename; + FSFileID root_id; + char fname[FILEID_SIZE_MAX]; + struct stat st; + uint64_t first_inode, fs_max_size; + int c; + + first_inode = 1; + fs_max_size = (uint64_t)1 << 30; + for(;;) { + c = getopt(argc, argv, "hi:m:"); + if (c == -1) + break; + switch(c) { + case 'h': + help(); + case 'i': + first_inode = strtoul(optarg, NULL, 0); + break; + case 'm': + fs_max_size = (uint64_t)strtoul(optarg, NULL, 0) << 20; + break; + default: + exit(1); + } + } + + if (optind + 1 >= argc) + help(); + src_path = argv[optind]; + dst_path = argv[optind + 1]; + + mkdir(dst_path, 0755); + + s->files_path = compose_path(dst_path, ROOT_FILENAME); + s->next_inode_num = first_inode; + s->fs_size = 0; + s->fs_max_size = fs_max_size; + + mkdir(s->files_path, 0755); + + root_id = s->next_inode_num++; + file_id_to_filename(fname, root_id); + filename = compose_path(s->files_path, fname); + f = fopen(filename, "wb"); + if (!f) { + perror(filename); + exit(1); + } + fprintf(f, "Version: 1\n"); + fprintf(f, "Revision: 1\n"); + fprintf(f, "\n"); + s->f = f; + scan_dir(s, src_path); + fclose(f); + + /* take into account the filelist size */ + if (stat(filename, &st) < 0) { + perror(filename); + exit(1); + } + add_file_size(s, st.st_size); + + free(filename); + + filename = compose_path(dst_path, HEAD_FILENAME); + f = fopen(filename, "wb"); + if (!f) { + perror(filename); + exit(1); + } + fprintf(f, "Version: 1\n"); + fprintf(f, "Revision: 1\n"); + fprintf(f, "NextFileID: %" PRIx64 "\n", s->next_inode_num); + fprintf(f, "FSFileCount: %" PRIu64 "\n", s->next_inode_num - 1); + fprintf(f, "FSSize: %" PRIu64 "\n", s->fs_size); + fprintf(f, "FSMaxSize: %" PRIu64 "\n", s->fs_max_size); + fprintf(f, "Key:\n"); /* not encrypted */ + fprintf(f, "RootID: %" PRIx64 "\n", root_id); + fclose(f); + free(filename); + + filename = compose_path(dst_path, LOCK_FILENAME); + f = fopen(filename, "wb"); + if (!f) { + perror(filename); + exit(1); + } + fclose(f); + free(filename); + + return 0; +} diff --git a/cutils.c b/cutils.c new file mode 100644 index 0000000..a86f90b --- /dev/null +++ b/cutils.c @@ -0,0 +1,120 @@ +/* + * Misc C utilities + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" + +void *mallocz(size_t size) +{ + void *ptr; + ptr = malloc(size); + if (!ptr) + return NULL; + memset(ptr, 0, size); + return ptr; +} + +void pstrcpy(char *buf, int buf_size, const char *str) +{ + int c; + char *q = buf; + + if (buf_size <= 0) + return; + + for(;;) { + c = *str++; + if (c == 0 || q >= buf + buf_size - 1) + break; + *q++ = c; + } + *q = '\0'; +} + +char *pstrcat(char *buf, int buf_size, const char *s) +{ + int len; + len = strlen(buf); + if (len < buf_size) + pstrcpy(buf + len, buf_size - len, s); + return buf; +} + +int strstart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (*p != *q) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +void dbuf_init(DynBuf *s) +{ + memset(s, 0, sizeof(*s)); +} + +void dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len) +{ + size_t end, new_size; + new_size = end = offset + len; + if (new_size > s->allocated_size) { + new_size = max_int(new_size, s->allocated_size * 3 / 2); + s->buf = realloc(s->buf, new_size); + s->allocated_size = new_size; + } + memcpy(s->buf + offset, data, len); + if (end > s->size) + s->size = end; +} + +void dbuf_putc(DynBuf *s, uint8_t c) +{ + dbuf_write(s, s->size, &c, 1); +} + +void dbuf_putstr(DynBuf *s, const char *str) +{ + dbuf_write(s, s->size, (const uint8_t *)str, strlen(str)); +} + +void dbuf_free(DynBuf *s) +{ + free(s->buf); + memset(s, 0, sizeof(*s)); +} diff --git a/cutils.h b/cutils.h new file mode 100644 index 0000000..689542e --- /dev/null +++ b/cutils.h @@ -0,0 +1,194 @@ +/* + * C utilities + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef CUTILS_H +#define CUTILS_H + +#include + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#define force_inline inline __attribute__((always_inline)) +#define no_inline __attribute__((noinline)) +#define __maybe_unused __attribute__((unused)) + +#define xglue(x, y) x ## y +#define glue(x, y) xglue(x, y) +#define stringify(s) tostring(s) +#define tostring(s) #s + +#ifndef offsetof +#define offsetof(type, field) ((size_t) &((type *)0)->field) +#endif +#define countof(x) (sizeof(x) / sizeof(x[0])) + +#define DLL_PUBLIC __attribute__ ((visibility ("default"))) + +#ifndef _BOOL_defined +#define _BOOL_defined +#undef FALSE +#undef TRUE + +typedef int BOOL; +enum { + FALSE = 0, + TRUE = 1, +}; +#endif + +/* this test works at least with gcc */ +#if defined(__SIZEOF_INT128__) +#define HAVE_INT128 +#endif + +#ifdef HAVE_INT128 +typedef __int128 int128_t; +typedef unsigned __int128 uint128_t; +#endif + +static inline int max_int(int a, int b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int min_int(int a, int b) +{ + if (a < b) + return a; + else + return b; +} + +void *mallocz(size_t size); + +#if defined(_WIN32) +static inline uint32_t bswap_32(uint32_t v) +{ + return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | + ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); +} +#else +#include +#endif + +static inline uint16_t get_le16(const uint8_t *ptr) +{ + return ptr[0] | (ptr[1] << 8); +} + +static inline uint32_t get_le32(const uint8_t *ptr) +{ + return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24); +} + +static inline uint64_t get_le64(const uint8_t *ptr) +{ + return get_le32(ptr) | ((uint64_t)get_le32(ptr + 4) << 32); +} + +static inline void put_le16(uint8_t *ptr, uint16_t v) +{ + ptr[0] = v; + ptr[1] = v >> 8; +} + +static inline void put_le32(uint8_t *ptr, uint32_t v) +{ + ptr[0] = v; + ptr[1] = v >> 8; + ptr[2] = v >> 16; + ptr[3] = v >> 24; +} + +static inline void put_le64(uint8_t *ptr, uint64_t v) +{ + put_le32(ptr, v); + put_le32(ptr + 4, v >> 32); +} + +static inline uint32_t get_be32(const uint8_t *d) +{ + return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]; +} + +static inline void put_be32(uint8_t *d, uint32_t v) +{ + d[0] = v >> 24; + d[1] = v >> 16; + d[2] = v >> 8; + d[3] = v >> 0; +} + +static inline void put_be64(uint8_t *d, uint64_t v) +{ + put_be32(d, v >> 32); + put_be32(d + 4, v); +} + +#ifdef WORDS_BIGENDIAN +static inline uint32_t cpu_to_be32(uint32_t v) +{ + return v; +} +#else +static inline uint32_t cpu_to_be32(uint32_t v) +{ + return bswap_32(v); +} +#endif + +/* XXX: optimize */ +static inline int ctz32(uint32_t a) +{ + int i; + if (a == 0) + return 32; + for(i = 0; i < 32; i++) { + if ((a >> i) & 1) + return i; + } + return 32; +} + + +void *mallocz(size_t size); +void pstrcpy(char *buf, int buf_size, const char *str); +char *pstrcat(char *buf, int buf_size, const char *s); +int strstart(const char *str, const char *val, const char **ptr); + +typedef struct { + uint8_t *buf; + size_t size; + size_t allocated_size; +} DynBuf; + +void dbuf_init(DynBuf *s); +void dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len); +void dbuf_putc(DynBuf *s, uint8_t c); +void dbuf_putstr(DynBuf *s, const char *str); +void dbuf_free(DynBuf *s); + +#endif /* CUTILS_H */ diff --git a/fbuf.h b/fbuf.h new file mode 100644 index 0000000..9736a49 --- /dev/null +++ b/fbuf.h @@ -0,0 +1,22 @@ +#ifndef FBUF_H +#define FBUF_H + +typedef struct { +#if defined(EMSCRIPTEN) + int handle; +#else + uint8_t *data; +#endif + size_t allocated_size; +} FileBuffer; + +void file_buffer_init(FileBuffer *bs); +void file_buffer_reset(FileBuffer *bs); +int file_buffer_resize(FileBuffer *bs, size_t new_size); +void file_buffer_write(FileBuffer *bs, size_t offset, const uint8_t *buf, + size_t size); +void file_buffer_set(FileBuffer *bs, size_t offset, int val, size_t size); +void file_buffer_read(FileBuffer *bs, size_t offset, uint8_t *buf, + size_t size); + +#endif /* FBUF_H */ diff --git a/fs.c b/fs.c new file mode 100644 index 0000000..7f92d82 --- /dev/null +++ b/fs.c @@ -0,0 +1,104 @@ +/* + * Filesystem utilities + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "fs.h" + +FSFile *fs_dup(FSDevice *fs, FSFile *f) +{ + FSQID qid; + fs->fs_walk(fs, &f, &qid, f, 0, NULL); + return f; +} + +FSFile *fs_walk_path1(FSDevice *fs, FSFile *f, const char *path, + char **pname) +{ + const char *p; + char *name; + FSFile *f1; + FSQID qid; + int len, ret; + BOOL is_last, is_first; + + if (path[0] == '/') + path++; + + is_first = TRUE; + for(;;) { + p = strchr(path, '/'); + if (!p) { + name = (char *)path; + if (pname) { + *pname = name; + if (is_first) { + ret = fs->fs_walk(fs, &f, &qid, f, 0, NULL); + if (ret < 0) + f = NULL; + } + return f; + } + is_last = TRUE; + } else { + len = p - path; + name = malloc(len + 1); + memcpy(name, path, len); + name[len] = '\0'; + is_last = FALSE; + } + ret = fs->fs_walk(fs, &f1, &qid, f, 1, &name); + if (!is_last) + free(name); + if (!is_first) + fs->fs_delete(fs, f); + f = f1; + is_first = FALSE; + if (ret <= 0) { + fs->fs_delete(fs, f); + f = NULL; + break; + } else if (is_last) { + break; + } + path = p + 1; + } + return f; +} + +FSFile *fs_walk_path(FSDevice *fs, FSFile *f, const char *path) +{ + return fs_walk_path1(fs, f, path, NULL); +} + +void fs_end(FSDevice *fs) +{ + fs->fs_end(fs); + free(fs); +} diff --git a/fs.h b/fs.h new file mode 100644 index 0000000..4c38e84 --- /dev/null +++ b/fs.h @@ -0,0 +1,211 @@ +/* + * Filesystem abstraction + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* FSQID.type */ +#define P9_QTDIR 0x80 +#define P9_QTAPPEND 0x40 +#define P9_QTEXCL 0x20 +#define P9_QTMOUNT 0x10 +#define P9_QTAUTH 0x08 +#define P9_QTTMP 0x04 +#define P9_QTSYMLINK 0x02 +#define P9_QTLINK 0x01 +#define P9_QTFILE 0x00 + +/* mode bits */ +#define P9_S_IRWXUGO 0x01FF +#define P9_S_ISVTX 0x0200 +#define P9_S_ISGID 0x0400 +#define P9_S_ISUID 0x0800 + +#define P9_S_IFMT 0xF000 +#define P9_S_IFIFO 0x1000 +#define P9_S_IFCHR 0x2000 +#define P9_S_IFDIR 0x4000 +#define P9_S_IFBLK 0x6000 +#define P9_S_IFREG 0x8000 +#define P9_S_IFLNK 0xA000 +#define P9_S_IFSOCK 0xC000 + +/* flags for lopen()/lcreate() */ +#define P9_O_RDONLY 0x00000000 +#define P9_O_WRONLY 0x00000001 +#define P9_O_RDWR 0x00000002 +#define P9_O_NOACCESS 0x00000003 +#define P9_O_CREAT 0x00000040 +#define P9_O_EXCL 0x00000080 +#define P9_O_NOCTTY 0x00000100 +#define P9_O_TRUNC 0x00000200 +#define P9_O_APPEND 0x00000400 +#define P9_O_NONBLOCK 0x00000800 +#define P9_O_DSYNC 0x00001000 +#define P9_O_FASYNC 0x00002000 +#define P9_O_DIRECT 0x00004000 +#define P9_O_LARGEFILE 0x00008000 +#define P9_O_DIRECTORY 0x00010000 +#define P9_O_NOFOLLOW 0x00020000 +#define P9_O_NOATIME 0x00040000 +#define P9_O_CLOEXEC 0x00080000 +#define P9_O_SYNC 0x00100000 + +/* for fs_setattr() */ +#define P9_SETATTR_MODE 0x00000001 +#define P9_SETATTR_UID 0x00000002 +#define P9_SETATTR_GID 0x00000004 +#define P9_SETATTR_SIZE 0x00000008 +#define P9_SETATTR_ATIME 0x00000010 +#define P9_SETATTR_MTIME 0x00000020 +#define P9_SETATTR_CTIME 0x00000040 +#define P9_SETATTR_ATIME_SET 0x00000080 +#define P9_SETATTR_MTIME_SET 0x00000100 + +#define P9_EPERM 1 +#define P9_ENOENT 2 +#define P9_EIO 5 +#define P9_EEXIST 17 +#define P9_ENOTDIR 20 +#define P9_EINVAL 22 +#define P9_ENOSPC 28 +#define P9_ENOTEMPTY 39 +#define P9_EPROTO 71 +#define P9_ENOTSUP 524 + +typedef struct FSDevice FSDevice; +typedef struct FSFile FSFile; + +typedef struct { + uint32_t f_bsize; + uint64_t f_blocks; + uint64_t f_bfree; + uint64_t f_bavail; + uint64_t f_files; + uint64_t f_ffree; +} FSStatFS; + +typedef struct { + uint8_t type; /* P9_IFx */ + uint32_t version; + uint64_t path; +} FSQID; + +typedef struct { + FSQID qid; + uint32_t st_mode; + uint32_t st_uid; + uint32_t st_gid; + uint64_t st_nlink; + uint64_t st_rdev; + uint64_t st_size; + uint64_t st_blksize; + uint64_t st_blocks; + uint64_t st_atime_sec; + uint32_t st_atime_nsec; + uint64_t st_mtime_sec; + uint32_t st_mtime_nsec; + uint64_t st_ctime_sec; + uint32_t st_ctime_nsec; +} FSStat; + +#define P9_LOCK_TYPE_RDLCK 0 +#define P9_LOCK_TYPE_WRLCK 1 +#define P9_LOCK_TYPE_UNLCK 2 + +#define P9_LOCK_FLAGS_BLOCK 1 +#define P9_LOCK_FLAGS_RECLAIM 2 + +#define P9_LOCK_SUCCESS 0 +#define P9_LOCK_BLOCKED 1 +#define P9_LOCK_ERROR 2 +#define P9_LOCK_GRACE 3 + +#define FSCMD_NAME ".fscmd" + +typedef struct { + uint8_t type; + uint32_t flags; + uint64_t start; + uint64_t length; + uint32_t proc_id; + char *client_id; +} FSLock; + +typedef void FSOpenCompletionFunc(FSDevice *fs, FSQID *qid, int err, + void *opaque); + +struct FSDevice { + void (*fs_end)(FSDevice *s); + void (*fs_delete)(FSDevice *s, FSFile *f); + void (*fs_statfs)(FSDevice *fs, FSStatFS *st); + int (*fs_attach)(FSDevice *fs, FSFile **pf, FSQID *qid, uint32_t uid, + const char *uname, const char *aname); + int (*fs_walk)(FSDevice *fs, FSFile **pf, FSQID *qids, + FSFile *f, int n, char **names); + int (*fs_mkdir)(FSDevice *fs, FSQID *qid, FSFile *f, + const char *name, uint32_t mode, uint32_t gid); + int (*fs_open)(FSDevice *fs, FSQID *qid, FSFile *f, uint32_t flags, + FSOpenCompletionFunc *cb, void *opaque); + int (*fs_create)(FSDevice *fs, FSQID *qid, FSFile *f, const char *name, + uint32_t flags, uint32_t mode, uint32_t gid); + int (*fs_stat)(FSDevice *fs, FSFile *f, FSStat *st); + int (*fs_setattr)(FSDevice *fs, FSFile *f, uint32_t mask, + uint32_t mode, uint32_t uid, uint32_t gid, + uint64_t size, uint64_t atime_sec, uint64_t atime_nsec, + uint64_t mtime_sec, uint64_t mtime_nsec); + void (*fs_close)(FSDevice *fs, FSFile *f); + int (*fs_readdir)(FSDevice *fs, FSFile *f, uint64_t offset, + uint8_t *buf, int count); + int (*fs_read)(FSDevice *fs, FSFile *f, uint64_t offset, + uint8_t *buf, int count); + int (*fs_write)(FSDevice *fs, FSFile *f, uint64_t offset, + const uint8_t *buf, int count); + int (*fs_link)(FSDevice *fs, FSFile *df, FSFile *f, const char *name); + int (*fs_symlink)(FSDevice *fs, FSQID *qid, + FSFile *f, const char *name, const char *symgt, uint32_t gid); + int (*fs_mknod)(FSDevice *fs, FSQID *qid, + FSFile *f, const char *name, uint32_t mode, uint32_t major, + uint32_t minor, uint32_t gid); + int (*fs_readlink)(FSDevice *fs, char *buf, int buf_size, FSFile *f); + int (*fs_renameat)(FSDevice *fs, FSFile *f, const char *name, + FSFile *new_f, const char *new_name); + int (*fs_unlinkat)(FSDevice *fs, FSFile *f, const char *name); + int (*fs_lock)(FSDevice *fs, FSFile *f, const FSLock *lock); + int (*fs_getlock)(FSDevice *fs, FSFile *f, FSLock *lock); +}; + +FSDevice *fs_disk_init(const char *root_path); +FSDevice *fs_mem_init(void); +FSDevice *fs_net_init(const char *url, void (*start)(void *opaque), void *opaque); +void fs_net_set_pwd(FSDevice *fs, const char *pwd); +#ifdef EMSCRIPTEN +void fs_import_file(const char *filename, uint8_t *buf, int buf_len); +#endif +void fs_export_file(const char *filename, + const uint8_t *buf, int buf_len); +void fs_end(FSDevice *fs); +void fs_dump_cache_load(FSDevice *fs1, const char *filename); + +FSFile *fs_dup(FSDevice *fs, FSFile *f); +FSFile *fs_walk_path1(FSDevice *fs, FSFile *f, const char *path, + char **pname); +FSFile *fs_walk_path(FSDevice *fs, FSFile *f, const char *path); diff --git a/fs_disk.c b/fs_disk.c new file mode 100644 index 0000000..bf96c89 --- /dev/null +++ b/fs_disk.c @@ -0,0 +1,659 @@ +/* + * Filesystem on disk + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "list.h" +#include "fs.h" + +typedef struct { + FSDevice common; + char *root_path; +} FSDeviceDisk; + +static void fs_close(FSDevice *fs, FSFile *f); + +struct FSFile { + uint32_t uid; + char *path; /* complete path */ + BOOL is_opened; + BOOL is_dir; + union { + int fd; + DIR *dirp; + } u; +}; + +static void fs_delete(FSDevice *fs, FSFile *f) +{ + if (f->is_opened) + fs_close(fs, f); + free(f->path); + free(f); +} + +/* warning: path belong to fid_create() */ +static FSFile *fid_create(FSDevice *s1, char *path, uint32_t uid) +{ + FSFile *f; + f = mallocz(sizeof(*f)); + f->path = path; + f->uid = uid; + return f; +} + + +static int errno_table[][2] = { + { P9_EPERM, EPERM }, + { P9_ENOENT, ENOENT }, + { P9_EIO, EIO }, + { P9_EEXIST, EEXIST }, + { P9_EINVAL, EINVAL }, + { P9_ENOSPC, ENOSPC }, + { P9_ENOTEMPTY, ENOTEMPTY }, + { P9_EPROTO, EPROTO }, + { P9_ENOTSUP, ENOTSUP }, +}; + +static int errno_to_p9(int err) +{ + int i; + if (err == 0) + return 0; + for(i = 0; i < countof(errno_table); i++) { + if (err == errno_table[i][1]) + return errno_table[i][0]; + } + return P9_EINVAL; +} + +static int open_flags[][2] = { + { P9_O_CREAT, O_CREAT }, + { P9_O_EXCL, O_EXCL }, + // { P9_O_NOCTTY, O_NOCTTY }, + { P9_O_TRUNC, O_TRUNC }, + { P9_O_APPEND, O_APPEND }, + { P9_O_NONBLOCK, O_NONBLOCK }, + { P9_O_DSYNC, O_DSYNC }, + // { P9_O_FASYNC, O_FASYNC }, + // { P9_O_DIRECT, O_DIRECT }, + // { P9_O_LARGEFILE, O_LARGEFILE }, + // { P9_O_DIRECTORY, O_DIRECTORY }, + { P9_O_NOFOLLOW, O_NOFOLLOW }, + // { P9_O_NOATIME, O_NOATIME }, + // { P9_O_CLOEXEC, O_CLOEXEC }, + { P9_O_SYNC, O_SYNC }, +}; + +static int p9_flags_to_host(int flags) +{ + int ret, i; + + ret = (flags & P9_O_NOACCESS); + for(i = 0; i < countof(open_flags); i++) { + if (flags & open_flags[i][0]) + ret |= open_flags[i][1]; + } + return ret; +} + +static void stat_to_qid(FSQID *qid, const struct stat *st) +{ + if (S_ISDIR(st->st_mode)) + qid->type = P9_QTDIR; + else if (S_ISLNK(st->st_mode)) + qid->type = P9_QTSYMLINK; + else + qid->type = P9_QTFILE; + qid->version = 0; /* no caching on client */ + qid->path = st->st_ino; +} + +static void fs_statfs(FSDevice *fs1, FSStatFS *st) +{ + FSDeviceDisk *fs = (FSDeviceDisk *)fs1; + struct statfs st1; + statfs(fs->root_path, &st1); + st->f_bsize = st1.f_bsize; + st->f_blocks = st1.f_blocks; + st->f_bfree = st1.f_bfree; + st->f_bavail = st1.f_bavail; + st->f_files = st1.f_files; + st->f_ffree = st1.f_ffree; +} + +static char *compose_path(const char *path, const char *name) +{ + int path_len, name_len; + char *d; + + path_len = strlen(path); + name_len = strlen(name); + d = malloc(path_len + 1 + name_len + 1); + memcpy(d, path, path_len); + d[path_len] = '/'; + memcpy(d + path_len + 1, name, name_len + 1); + return d; +} + +static int fs_attach(FSDevice *fs1, FSFile **pf, + FSQID *qid, uint32_t uid, + const char *uname, const char *aname) +{ + FSDeviceDisk *fs = (FSDeviceDisk *)fs1; + struct stat st; + FSFile *f; + + if (lstat(fs->root_path, &st) != 0) { + *pf = NULL; + return -errno_to_p9(errno); + } + f = fid_create(fs1, strdup(fs->root_path), uid); + stat_to_qid(qid, &st); + *pf = f; + return 0; +} + +static int fs_walk(FSDevice *fs, FSFile **pf, FSQID *qids, + FSFile *f, int n, char **names) +{ + char *path, *path1; + struct stat st; + int i; + + path = strdup(f->path); + for(i = 0; i < n; i++) { + path1 = compose_path(path, names[i]); + if (lstat(path1, &st) != 0) { + free(path1); + break; + } + free(path); + path = path1; + stat_to_qid(&qids[i], &st); + } + *pf = fid_create(fs, path, f->uid); + return i; +} + + +static int fs_mkdir(FSDevice *fs, FSQID *qid, FSFile *f, + const char *name, uint32_t mode, uint32_t gid) +{ + char *path; + struct stat st; + + path = compose_path(f->path, name); + if (mkdir(path, mode) < 0) { + free(path); + return -errno_to_p9(errno); + } + if (lstat(path, &st) != 0) { + free(path); + return -errno_to_p9(errno); + } + free(path); + stat_to_qid(qid, &st); + return 0; +} + +static int fs_open(FSDevice *fs, FSQID *qid, FSFile *f, uint32_t flags, + FSOpenCompletionFunc *cb, void *opaque) +{ + struct stat st; + fs_close(fs, f); + + if (stat(f->path, &st) != 0) + return -errno_to_p9(errno); + stat_to_qid(qid, &st); + + if (flags & P9_O_DIRECTORY) { + DIR *dirp; + dirp = opendir(f->path); + if (!dirp) + return -errno_to_p9(errno); + f->is_opened = TRUE; + f->is_dir = TRUE; + f->u.dirp = dirp; + } else { + int fd; + fd = open(f->path, p9_flags_to_host(flags) & ~O_CREAT); + if (fd < 0) + return -errno_to_p9(errno); + f->is_opened = TRUE; + f->is_dir = FALSE; + f->u.fd = fd; + } + return 0; +} + +static int fs_create(FSDevice *fs, FSQID *qid, FSFile *f, const char *name, + uint32_t flags, uint32_t mode, uint32_t gid) +{ + struct stat st; + char *path; + int ret, fd; + + fs_close(fs, f); + + path = compose_path(f->path, name); + fd = open(path, p9_flags_to_host(flags) | O_CREAT, mode); + if (fd < 0) { + free(path); + return -errno_to_p9(errno); + } + ret = lstat(path, &st); + if (ret != 0) { + free(path); + close(fd); + return -errno_to_p9(errno); + } + free(f->path); + f->path = path; + f->is_opened = TRUE; + f->is_dir = FALSE; + f->u.fd = fd; + stat_to_qid(qid, &st); + return 0; +} + +static int fs_readdir(FSDevice *fs, FSFile *f, uint64_t offset, + uint8_t *buf, int count) +{ + struct dirent *de; + int len, pos, name_len, type, d_type; + + if (!f->is_opened || !f->is_dir) + return -P9_EPROTO; + if (offset == 0) + rewinddir(f->u.dirp); + else + seekdir(f->u.dirp, offset); + pos = 0; + for(;;) { + de = readdir(f->u.dirp); + if (de == NULL) + break; + name_len = strlen(de->d_name); + len = 13 + 8 + 1 + 2 + name_len; + if ((pos + len) > count) + break; + offset = telldir(f->u.dirp); + d_type = de->d_type; + if (d_type == DT_UNKNOWN) { + char *path; + struct stat st; + path = compose_path(f->path, de->d_name); + if (lstat(path, &st) == 0) { + d_type = st.st_mode >> 12; + } else { + d_type = DT_REG; /* default */ + } + free(path); + } + if (d_type == DT_DIR) + type = P9_QTDIR; + else if (d_type == DT_LNK) + type = P9_QTSYMLINK; + else + type = P9_QTFILE; + buf[pos++] = type; + put_le32(buf + pos, 0); /* version */ + pos += 4; + put_le64(buf + pos, de->d_ino); + pos += 8; + put_le64(buf + pos, offset); + pos += 8; + buf[pos++] = d_type; + put_le16(buf + pos, name_len); + pos += 2; + memcpy(buf + pos, de->d_name, name_len); + pos += name_len; + } + return pos; +} + +static int fs_read(FSDevice *fs, FSFile *f, uint64_t offset, + uint8_t *buf, int count) +{ + int ret; + + if (!f->is_opened || f->is_dir) + return -P9_EPROTO; + ret = pread(f->u.fd, buf, count, offset); + if (ret < 0) + return -errno_to_p9(errno); + else + return ret; +} + +static int fs_write(FSDevice *fs, FSFile *f, uint64_t offset, + const uint8_t *buf, int count) +{ + int ret; + + if (!f->is_opened || f->is_dir) + return -P9_EPROTO; + ret = pwrite(f->u.fd, buf, count, offset); + if (ret < 0) + return -errno_to_p9(errno); + else + return ret; +} + +static void fs_close(FSDevice *fs, FSFile *f) +{ + if (!f->is_opened) + return; + if (f->is_dir) + closedir(f->u.dirp); + else + close(f->u.fd); + f->is_opened = FALSE; +} + +static int fs_stat(FSDevice *fs, FSFile *f, FSStat *st) +{ + struct stat st1; + + if (lstat(f->path, &st1) != 0) + return -P9_ENOENT; + stat_to_qid(&st->qid, &st1); + st->st_mode = st1.st_mode; + st->st_uid = st1.st_uid; + st->st_gid = st1.st_gid; + st->st_nlink = st1.st_nlink; + st->st_rdev = st1.st_rdev; + st->st_size = st1.st_size; + st->st_blksize = st1.st_blksize; + st->st_blocks = st1.st_blocks; + st->st_atime_sec = st1.st_atim.tv_sec; + st->st_atime_nsec = st1.st_atim.tv_nsec; + st->st_mtime_sec = st1.st_mtim.tv_sec; + st->st_mtime_nsec = st1.st_mtim.tv_nsec; + st->st_ctime_sec = st1.st_ctim.tv_sec; + st->st_ctime_nsec = st1.st_ctim.tv_nsec; + return 0; +} + +static int fs_setattr(FSDevice *fs, FSFile *f, uint32_t mask, + uint32_t mode, uint32_t uid, uint32_t gid, + uint64_t size, uint64_t atime_sec, uint64_t atime_nsec, + uint64_t mtime_sec, uint64_t mtime_nsec) +{ + BOOL ctime_updated = FALSE; + + if (mask & (P9_SETATTR_UID | P9_SETATTR_GID)) { + if (lchown(f->path, (mask & P9_SETATTR_UID) ? uid : -1, + (mask & P9_SETATTR_GID) ? gid : -1) < 0) + return -errno_to_p9(errno); + ctime_updated = TRUE; + } + /* must be done after uid change for suid */ + if (mask & P9_SETATTR_MODE) { + if (chmod(f->path, mode) < 0) + return -errno_to_p9(errno); + ctime_updated = TRUE; + } + if (mask & P9_SETATTR_SIZE) { + if (truncate(f->path, size) < 0) + return -errno_to_p9(errno); + ctime_updated = TRUE; + } + if (mask & (P9_SETATTR_ATIME | P9_SETATTR_MTIME)) { + struct timespec ts[2]; + if (mask & P9_SETATTR_ATIME) { + if (mask & P9_SETATTR_ATIME_SET) { + ts[0].tv_sec = atime_sec; + ts[0].tv_nsec = atime_nsec; + } else { + ts[0].tv_sec = 0; + ts[0].tv_nsec = UTIME_NOW; + } + } else { + ts[0].tv_sec = 0; + ts[0].tv_nsec = UTIME_OMIT; + } + if (mask & P9_SETATTR_MTIME) { + if (mask & P9_SETATTR_MTIME_SET) { + ts[1].tv_sec = mtime_sec; + ts[1].tv_nsec = mtime_nsec; + } else { + ts[1].tv_sec = 0; + ts[1].tv_nsec = UTIME_NOW; + } + } else { + ts[1].tv_sec = 0; + ts[1].tv_nsec = UTIME_OMIT; + } + if (utimensat(AT_FDCWD, f->path, ts, AT_SYMLINK_NOFOLLOW) < 0) + return -errno_to_p9(errno); + ctime_updated = TRUE; + } + if ((mask & P9_SETATTR_CTIME) && !ctime_updated) { + if (lchown(f->path, -1, -1) < 0) + return -errno_to_p9(errno); + } + return 0; +} + +static int fs_link(FSDevice *fs, FSFile *df, FSFile *f, const char *name) +{ + char *path; + + path = compose_path(df->path, name); + if (link(f->path, path) < 0) { + free(path); + return -errno_to_p9(errno); + } + free(path); + return 0; +} + +static int fs_symlink(FSDevice *fs, FSQID *qid, + FSFile *f, const char *name, const char *symgt, uint32_t gid) +{ + char *path; + struct stat st; + + path = compose_path(f->path, name); + if (symlink(symgt, path) < 0) { + free(path); + return -errno_to_p9(errno); + } + if (lstat(path, &st) != 0) { + free(path); + return -errno_to_p9(errno); + } + free(path); + stat_to_qid(qid, &st); + return 0; +} + +static int fs_mknod(FSDevice *fs, FSQID *qid, + FSFile *f, const char *name, uint32_t mode, uint32_t major, + uint32_t minor, uint32_t gid) +{ + char *path; + struct stat st; + + path = compose_path(f->path, name); + if (mknod(path, mode, makedev(major, minor)) < 0) { + free(path); + return -errno_to_p9(errno); + } + if (lstat(path, &st) != 0) { + free(path); + return -errno_to_p9(errno); + } + free(path); + stat_to_qid(qid, &st); + return 0; +} + +static int fs_readlink(FSDevice *fs, char *buf, int buf_size, FSFile *f) +{ + int ret; + ret = readlink(f->path, buf, buf_size - 1); + if (ret < 0) + return -errno_to_p9(errno); + buf[ret] = '\0'; + return 0; +} + +static int fs_renameat(FSDevice *fs, FSFile *f, const char *name, + FSFile *new_f, const char *new_name) +{ + char *path, *new_path; + int ret; + + path = compose_path(f->path, name); + new_path = compose_path(new_f->path, new_name); + ret = rename(path, new_path); + free(path); + free(new_path); + if (ret < 0) + return -errno_to_p9(errno); + return 0; +} + +static int fs_unlinkat(FSDevice *fs, FSFile *f, const char *name) +{ + char *path; + int ret; + + path = compose_path(f->path, name); + ret = remove(path); + free(path); + if (ret < 0) + return -errno_to_p9(errno); + return 0; + +} + +static int fs_lock(FSDevice *fs, FSFile *f, const FSLock *lock) +{ + int ret; + struct flock fl; + + /* XXX: lock directories too */ + if (!f->is_opened || f->is_dir) + return -P9_EPROTO; + + fl.l_type = lock->type; + fl.l_whence = SEEK_SET; + fl.l_start = lock->start; + fl.l_len = lock->length; + + ret = fcntl(f->u.fd, F_SETLK, &fl); + if (ret == 0) { + ret = P9_LOCK_SUCCESS; + } else if (errno == EAGAIN || errno == EACCES) { + ret = P9_LOCK_BLOCKED; + } else { + ret = -errno_to_p9(errno); + } + return ret; +} + +static int fs_getlock(FSDevice *fs, FSFile *f, FSLock *lock) +{ + int ret; + struct flock fl; + + /* XXX: lock directories too */ + if (!f->is_opened || f->is_dir) + return -P9_EPROTO; + + fl.l_type = lock->type; + fl.l_whence = SEEK_SET; + fl.l_start = lock->start; + fl.l_len = lock->length; + + ret = fcntl(f->u.fd, F_GETLK, &fl); + if (ret < 0) { + ret = -errno_to_p9(errno); + } else { + lock->type = fl.l_type; + lock->start = fl.l_start; + lock->length = fl.l_len; + } + return ret; +} + +static void fs_disk_end(FSDevice *fs1) +{ + FSDeviceDisk *fs = (FSDeviceDisk *)fs1; + free(fs->root_path); +} + +FSDevice *fs_disk_init(const char *root_path) +{ + FSDeviceDisk *fs; + struct stat st; + + lstat(root_path, &st); + if (!S_ISDIR(st.st_mode)) + return NULL; + + fs = mallocz(sizeof(*fs)); + + fs->common.fs_end = fs_disk_end; + fs->common.fs_delete = fs_delete; + fs->common.fs_statfs = fs_statfs; + fs->common.fs_attach = fs_attach; + fs->common.fs_walk = fs_walk; + fs->common.fs_mkdir = fs_mkdir; + fs->common.fs_open = fs_open; + fs->common.fs_create = fs_create; + fs->common.fs_stat = fs_stat; + fs->common.fs_setattr = fs_setattr; + fs->common.fs_close = fs_close; + fs->common.fs_readdir = fs_readdir; + fs->common.fs_read = fs_read; + fs->common.fs_write = fs_write; + fs->common.fs_link = fs_link; + fs->common.fs_symlink = fs_symlink; + fs->common.fs_mknod = fs_mknod; + fs->common.fs_readlink = fs_readlink; + fs->common.fs_renameat = fs_renameat; + fs->common.fs_unlinkat = fs_unlinkat; + fs->common.fs_lock = fs_lock; + fs->common.fs_getlock = fs_getlock; + + fs->root_path = strdup(root_path); + return (FSDevice *)fs; +} diff --git a/fs_net.c b/fs_net.c new file mode 100644 index 0000000..c7c7484 --- /dev/null +++ b/fs_net.c @@ -0,0 +1,2910 @@ +/* + * Networked Filesystem using HTTP + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "list.h" +#include "fs.h" +#include "fs_utils.h" +#include "fs_wget.h" +#include "fbuf.h" + +#if defined(EMSCRIPTEN) +#include +#endif + +/* + TODO: + - implement fs_lock/fs_getlock + - update fs_size with links ? + - limit fs_size in dirent creation + - limit filename length +*/ + +//#define DEBUG_CACHE +#if !defined(EMSCRIPTEN) +#define DUMP_CACHE_LOAD +#endif + +#if defined(EMSCRIPTEN) +#define DEFAULT_INODE_CACHE_SIZE (64 * 1024 * 1024) +#else +#define DEFAULT_INODE_CACHE_SIZE (256 * 1024 * 1024) +#endif + +typedef enum { + FT_FIFO = 1, + FT_CHR = 2, + FT_DIR = 4, + FT_BLK = 6, + FT_REG = 8, + FT_LNK = 10, + FT_SOCK = 12, +} FSINodeTypeEnum; + +typedef enum { + REG_STATE_LOCAL, /* local content */ + REG_STATE_UNLOADED, /* content not loaded */ + REG_STATE_LOADING, /* content is being loaded */ + REG_STATE_LOADED, /* loaded, not modified, stored in cached_inode_list */ +} FSINodeRegStateEnum; + +typedef struct FSBaseURL { + struct list_head link; + int ref_count; + char *base_url_id; + char *url; + char *user; + char *password; + BOOL encrypted; + AES_KEY aes_state; +} FSBaseURL; + +typedef struct FSINode { + struct list_head link; + uint64_t inode_num; /* inode number */ + int32_t refcount; + int32_t open_count; + FSINodeTypeEnum type; + uint32_t mode; + uint32_t uid; + uint32_t gid; + uint32_t mtime_sec; + uint32_t ctime_sec; + uint32_t mtime_nsec; + uint32_t ctime_nsec; + union { + struct { + FSINodeRegStateEnum state; + size_t size; /* real file size */ + FileBuffer fbuf; + FSBaseURL *base_url; + FSFileID file_id; /* network file ID */ + struct list_head link; + struct FSOpenInfo *open_info; /* used in LOADING state */ + BOOL is_fscmd; +#ifdef DUMP_CACHE_LOAD + char *filename; +#endif + } reg; + struct { + struct list_head de_list; /* list of FSDirEntry */ + int size; + } dir; + struct { + uint32_t major; + uint32_t minor; + } dev; + struct { + char *name; + } symlink; + } u; +} FSINode; + +typedef struct { + struct list_head link; + FSINode *inode; + uint8_t mark; /* temporary use only */ + char name[0]; +} FSDirEntry; + +typedef enum { + FS_CMD_XHR, + FS_CMD_PBKDF2, +} FSCMDRequestEnum; + +#define FS_CMD_REPLY_LEN_MAX 64 + +typedef struct { + FSCMDRequestEnum type; + struct CmdXHRState *xhr_state; + int reply_len; + uint8_t reply_buf[FS_CMD_REPLY_LEN_MAX]; +} FSCMDRequest; + +struct FSFile { + uint32_t uid; + FSINode *inode; + BOOL is_opened; + uint32_t open_flags; + FSCMDRequest *req; +}; + +typedef struct { + struct list_head link; + BOOL is_archive; + const char *name; +} PreloadFile; + +typedef struct { + struct list_head link; + FSFileID file_id; + struct list_head file_list; /* list of PreloadFile.link */ +} PreloadEntry; + +typedef struct { + struct list_head link; + FSFileID file_id; + uint64_t size; + const char *name; +} PreloadArchiveFile; + +typedef struct { + struct list_head link; + const char *name; + struct list_head file_list; /* list of PreloadArchiveFile.link */ +} PreloadArchive; + +typedef struct FSDeviceMem { + FSDevice common; + + struct list_head inode_list; /* list of FSINode */ + int64_t inode_count; /* current number of inodes */ + uint64_t inode_limit; + int64_t fs_blocks; + uint64_t fs_max_blocks; + uint64_t inode_num_alloc; + int block_size_log2; + uint32_t block_size; /* for stat/statfs */ + FSINode *root_inode; + struct list_head inode_cache_list; /* list of FSINode.u.reg.link */ + int64_t inode_cache_size; + int64_t inode_cache_size_limit; + struct list_head preload_list; /* list of PreloadEntry.link */ + struct list_head preload_archive_list; /* list of PreloadArchive.link */ + /* network */ + struct list_head base_url_list; /* list of FSBaseURL.link */ + char *import_dir; +#ifdef DUMP_CACHE_LOAD + BOOL dump_cache_load; + BOOL dump_started; + char *dump_preload_dir; + FILE *dump_preload_file; + FILE *dump_preload_archive_file; + + char *dump_archive_name; + uint64_t dump_archive_size; + FILE *dump_archive_file; + + int dump_archive_num; + struct list_head dump_preload_list; /* list of PreloadFile.link */ + struct list_head dump_exclude_list; /* list of PreloadFile.link */ +#endif +} FSDeviceMem; + +typedef enum { + FS_OPEN_WGET_REG, + FS_OPEN_WGET_ARCHIVE, + FS_OPEN_WGET_ARCHIVE_FILE, +} FSOpenWgetEnum; + +typedef struct FSOpenInfo { + FSDevice *fs; + FSOpenWgetEnum open_type; + + /* used for FS_OPEN_WGET_REG, FS_OPEN_WGET_ARCHIVE */ + XHRState *xhr; + FSINode *n; + DecryptFileState *dec_state; + size_t cur_pos; + + struct list_head archive_link; /* FS_OPEN_WGET_ARCHIVE_FILE */ + uint64_t archive_offset; /* FS_OPEN_WGET_ARCHIVE_FILE */ + struct list_head archive_file_list; /* FS_OPEN_WGET_ARCHIVE */ + + /* the following is set in case there is a fs_open callback */ + FSFile *f; + FSOpenCompletionFunc *cb; + void *opaque; +} FSOpenInfo; + +static void fs_close(FSDevice *fs, FSFile *f); +static void inode_decref(FSDevice *fs1, FSINode *n); +static int fs_cmd_write(FSDevice *fs, FSFile *f, uint64_t offset, + const uint8_t *buf, int buf_len); +static int fs_cmd_read(FSDevice *fs, FSFile *f, uint64_t offset, + uint8_t *buf, int buf_len); +static int fs_truncate(FSDevice *fs1, FSINode *n, uint64_t size); +static void fs_open_end(FSOpenInfo *oi); +static void fs_base_url_decref(FSDevice *fs, FSBaseURL *bu); +static FSBaseURL *fs_net_set_base_url(FSDevice *fs1, + const char *base_url_id, + const char *url, + const char *user, const char *password, + AES_KEY *aes_state); +static void fs_cmd_close(FSDevice *fs, FSFile *f); +static void fs_error_archive(FSOpenInfo *oi); +#ifdef DUMP_CACHE_LOAD +static void dump_loaded_file(FSDevice *fs1, FSINode *n); +#endif + +#if !defined(EMSCRIPTEN) +/* file buffer (the content of the buffer can be stored elsewhere) */ +void file_buffer_init(FileBuffer *bs) +{ + bs->data = NULL; + bs->allocated_size = 0; +} + +void file_buffer_reset(FileBuffer *bs) +{ + free(bs->data); + file_buffer_init(bs); +} + +int file_buffer_resize(FileBuffer *bs, size_t new_size) +{ + uint8_t *new_data; + new_data = realloc(bs->data, new_size); + if (!new_data && new_size != 0) + return -1; + bs->data = new_data; + bs->allocated_size = new_size; + return 0; +} + +void file_buffer_write(FileBuffer *bs, size_t offset, const uint8_t *buf, + size_t size) +{ + memcpy(bs->data + offset, buf, size); +} + +void file_buffer_set(FileBuffer *bs, size_t offset, int val, size_t size) +{ + memset(bs->data + offset, val, size); +} + +void file_buffer_read(FileBuffer *bs, size_t offset, uint8_t *buf, + size_t size) +{ + memcpy(buf, bs->data + offset, size); +} +#endif + +static int64_t to_blocks(FSDeviceMem *fs, uint64_t size) +{ + return (size + fs->block_size - 1) >> fs->block_size_log2; +} + +static FSINode *inode_incref(FSDevice *fs, FSINode *n) +{ + n->refcount++; + return n; +} + +static FSINode *inode_inc_open(FSDevice *fs, FSINode *n) +{ + n->open_count++; + return n; +} + +static void inode_free(FSDevice *fs1, FSINode *n) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + + // printf("inode_free=%" PRId64 "\n", n->inode_num); + assert(n->refcount == 0); + assert(n->open_count == 0); + switch(n->type) { + case FT_REG: + fs->fs_blocks -= to_blocks(fs, n->u.reg.size); + assert(fs->fs_blocks >= 0); + file_buffer_reset(&n->u.reg.fbuf); +#ifdef DUMP_CACHE_LOAD + free(n->u.reg.filename); +#endif + switch(n->u.reg.state) { + case REG_STATE_LOADED: + list_del(&n->u.reg.link); + fs->inode_cache_size -= n->u.reg.size; + assert(fs->inode_cache_size >= 0); + fs_base_url_decref(fs1, n->u.reg.base_url); + break; + case REG_STATE_LOADING: + { + FSOpenInfo *oi = n->u.reg.open_info; + if (oi->xhr) + fs_wget_free(oi->xhr); + if (oi->open_type == FS_OPEN_WGET_ARCHIVE) { + fs_error_archive(oi); + } + fs_open_end(oi); + fs_base_url_decref(fs1, n->u.reg.base_url); + } + break; + case REG_STATE_UNLOADED: + fs_base_url_decref(fs1, n->u.reg.base_url); + break; + case REG_STATE_LOCAL: + break; + default: + abort(); + } + break; + case FT_LNK: + free(n->u.symlink.name); + break; + case FT_DIR: + assert(list_empty(&n->u.dir.de_list)); + break; + default: + break; + } + list_del(&n->link); + free(n); + fs->inode_count--; + assert(fs->inode_count >= 0); +} + +static void inode_decref(FSDevice *fs1, FSINode *n) +{ + assert(n->refcount >= 1); + if (--n->refcount <= 0 && n->open_count <= 0) { + inode_free(fs1, n); + } +} + +static void inode_dec_open(FSDevice *fs1, FSINode *n) +{ + assert(n->open_count >= 1); + if (--n->open_count <= 0 && n->refcount <= 0) { + inode_free(fs1, n); + } +} + +static void inode_update_mtime(FSDevice *fs, FSINode *n) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + n->mtime_sec = tv.tv_sec; + n->mtime_nsec = tv.tv_usec * 1000; +} + +static FSINode *inode_new(FSDevice *fs1, FSINodeTypeEnum type, + uint32_t mode, uint32_t uid, uint32_t gid) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + FSINode *n; + + n = mallocz(sizeof(*n)); + n->refcount = 1; + n->open_count = 0; + n->inode_num = fs->inode_num_alloc; + fs->inode_num_alloc++; + n->type = type; + n->mode = mode & 0xfff; + n->uid = uid; + n->gid = gid; + + switch(type) { + case FT_REG: + file_buffer_init(&n->u.reg.fbuf); + break; + case FT_DIR: + init_list_head(&n->u.dir.de_list); + break; + default: + break; + } + + list_add(&n->link, &fs->inode_list); + fs->inode_count++; + + inode_update_mtime(fs1, n); + n->ctime_sec = n->mtime_sec; + n->ctime_nsec = n->mtime_nsec; + + return n; +} + +/* warning: the refcount of 'n1' is not incremented by this function */ +/* XXX: test FS max size */ +static FSDirEntry *inode_dir_add(FSDevice *fs1, FSINode *n, const char *name, + FSINode *n1) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + FSDirEntry *de; + int name_len, dirent_size, new_size; + assert(n->type == FT_DIR); + + name_len = strlen(name); + de = mallocz(sizeof(*de) + name_len + 1); + de->inode = n1; + memcpy(de->name, name, name_len + 1); + dirent_size = sizeof(*de) + name_len + 1; + new_size = n->u.dir.size + dirent_size; + fs->fs_blocks += to_blocks(fs, new_size) - to_blocks(fs, n->u.dir.size); + n->u.dir.size = new_size; + list_add_tail(&de->link, &n->u.dir.de_list); + return de; +} + +static FSDirEntry *inode_search(FSINode *n, const char *name) +{ + struct list_head *el; + FSDirEntry *de; + + if (n->type != FT_DIR) + return NULL; + + list_for_each(el, &n->u.dir.de_list) { + de = list_entry(el, FSDirEntry, link); + if (!strcmp(de->name, name)) + return de; + } + return NULL; +} + +static FSINode *inode_search_path1(FSDevice *fs, FSINode *n, const char *path) +{ + char name[1024]; + const char *p, *p1; + int len; + FSDirEntry *de; + + p = path; + if (*p == '/') + p++; + if (*p == '\0') + return n; + for(;;) { + p1 = strchr(p, '/'); + if (!p1) { + len = strlen(p); + } else { + len = p1 - p; + p1++; + } + if (len > sizeof(name) - 1) + return NULL; + memcpy(name, p, len); + name[len] = '\0'; + if (n->type != FT_DIR) + return NULL; + de = inode_search(n, name); + if (!de) + return NULL; + n = de->inode; + p = p1; + if (!p) + break; + } + return n; +} + +static FSINode *inode_search_path(FSDevice *fs1, const char *path) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + if (!fs1) + return NULL; + return inode_search_path1(fs1, fs->root_inode, path); +} + +static BOOL is_empty_dir(FSDevice *fs, FSINode *n) +{ + struct list_head *el; + FSDirEntry *de; + + list_for_each(el, &n->u.dir.de_list) { + de = list_entry(el, FSDirEntry, link); + if (strcmp(de->name, ".") != 0 && + strcmp(de->name, "..") != 0) + return FALSE; + } + return TRUE; +} + +static void inode_dirent_delete_no_decref(FSDevice *fs1, FSINode *n, FSDirEntry *de) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + int dirent_size, new_size; + dirent_size = sizeof(*de) + strlen(de->name) + 1; + + new_size = n->u.dir.size - dirent_size; + fs->fs_blocks += to_blocks(fs, new_size) - to_blocks(fs, n->u.dir.size); + n->u.dir.size = new_size; + assert(n->u.dir.size >= 0); + assert(fs->fs_blocks >= 0); + list_del(&de->link); + free(de); +} + +static void inode_dirent_delete(FSDevice *fs, FSINode *n, FSDirEntry *de) +{ + FSINode *n1; + n1 = de->inode; + inode_dirent_delete_no_decref(fs, n, de); + inode_decref(fs, n1); +} + +static void flush_dir(FSDevice *fs, FSINode *n) +{ + struct list_head *el, *el1; + FSDirEntry *de; + list_for_each_safe(el, el1, &n->u.dir.de_list) { + de = list_entry(el, FSDirEntry, link); + inode_dirent_delete(fs, n, de); + } + assert(n->u.dir.size == 0); +} + +static void fs_delete(FSDevice *fs, FSFile *f) +{ + fs_close(fs, f); + inode_dec_open(fs, f->inode); + free(f); +} + +static FSFile *fid_create(FSDevice *fs1, FSINode *n, uint32_t uid) +{ + FSFile *f; + + f = mallocz(sizeof(*f)); + f->inode = inode_inc_open(fs1, n); + f->uid = uid; + return f; +} + +static void inode_to_qid(FSQID *qid, FSINode *n) +{ + if (n->type == FT_DIR) + qid->type = P9_QTDIR; + else if (n->type == FT_LNK) + qid->type = P9_QTSYMLINK; + else + qid->type = P9_QTFILE; + qid->version = 0; /* no caching on client */ + qid->path = n->inode_num; +} + +static void fs_statfs(FSDevice *fs1, FSStatFS *st) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + st->f_bsize = 1024; + st->f_blocks = fs->fs_max_blocks << + (fs->block_size_log2 - 10); + st->f_bfree = (fs->fs_max_blocks - fs->fs_blocks) << + (fs->block_size_log2 - 10); + st->f_bavail = st->f_bfree; + st->f_files = fs->inode_limit; + st->f_ffree = fs->inode_limit - fs->inode_count; +} + +static int fs_attach(FSDevice *fs1, FSFile **pf, FSQID *qid, uint32_t uid, + const char *uname, const char *aname) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + + *pf = fid_create(fs1, fs->root_inode, uid); + inode_to_qid(qid, fs->root_inode); + return 0; +} + +static int fs_walk(FSDevice *fs, FSFile **pf, FSQID *qids, + FSFile *f, int count, char **names) +{ + int i; + FSINode *n; + FSDirEntry *de; + + n = f->inode; + for(i = 0; i < count; i++) { + de = inode_search(n, names[i]); + if (!de) + break; + n = de->inode; + inode_to_qid(&qids[i], n); + } + *pf = fid_create(fs, n, f->uid); + return i; +} + +static int fs_mkdir(FSDevice *fs, FSQID *qid, FSFile *f, + const char *name, uint32_t mode, uint32_t gid) +{ + FSINode *n, *n1; + + n = f->inode; + if (n->type != FT_DIR) + return -P9_ENOTDIR; + if (inode_search(n, name)) + return -P9_EEXIST; + n1 = inode_new(fs, FT_DIR, mode, f->uid, gid); + inode_dir_add(fs, n1, ".", inode_incref(fs, n1)); + inode_dir_add(fs, n1, "..", inode_incref(fs, n)); + inode_dir_add(fs, n, name, n1); + inode_to_qid(qid, n1); + return 0; +} + +/* remove elements in the cache considering that 'added_size' will be + added */ +static void fs_trim_cache(FSDevice *fs1, int64_t added_size) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + struct list_head *el, *el1; + FSINode *n; + + if ((fs->inode_cache_size + added_size) <= fs->inode_cache_size_limit) + return; + list_for_each_prev_safe(el, el1, &fs->inode_cache_list) { + n = list_entry(el, FSINode, u.reg.link); + assert(n->u.reg.state == REG_STATE_LOADED); + /* cannot remove open files */ + // printf("open_count=%d\n", n->open_count); + if (n->open_count != 0) + continue; +#ifdef DEBUG_CACHE + printf("fs_trim_cache: remove '%s' size=%ld\n", + n->u.reg.filename, (long)n->u.reg.size); +#endif + file_buffer_reset(&n->u.reg.fbuf); + n->u.reg.state = REG_STATE_UNLOADED; + list_del(&n->u.reg.link); + fs->inode_cache_size -= n->u.reg.size; + assert(fs->inode_cache_size >= 0); + if ((fs->inode_cache_size + added_size) <= fs->inode_cache_size_limit) + break; + } +} + +static void fs_open_end(FSOpenInfo *oi) +{ + if (oi->open_type == FS_OPEN_WGET_ARCHIVE_FILE) { + list_del(&oi->archive_link); + } + if (oi->dec_state) + decrypt_file_end(oi->dec_state); + free(oi); +} + +static int fs_open_write_cb(void *opaque, const uint8_t *data, size_t size) +{ + FSOpenInfo *oi = opaque; + size_t len; + FSINode *n = oi->n; + + /* we ignore extraneous data */ + len = n->u.reg.size - oi->cur_pos; + if (size < len) + len = size; + file_buffer_write(&n->u.reg.fbuf, oi->cur_pos, data, len); + oi->cur_pos += len; + return 0; +} + +static void fs_wget_set_loaded(FSINode *n) +{ + FSOpenInfo *oi; + FSDeviceMem *fs; + FSFile *f; + FSQID qid; + + assert(n->u.reg.state == REG_STATE_LOADING); + oi = n->u.reg.open_info; + fs = (FSDeviceMem *)oi->fs; + n->u.reg.state = REG_STATE_LOADED; + list_add(&n->u.reg.link, &fs->inode_cache_list); + fs->inode_cache_size += n->u.reg.size; + + if (oi->cb) { + f = oi->f; + f->is_opened = TRUE; + inode_to_qid(&qid, n); + oi->cb(oi->fs, &qid, 0, oi->opaque); + } + fs_open_end(oi); +} + +static void fs_wget_set_error(FSINode *n) +{ + FSOpenInfo *oi; + assert(n->u.reg.state == REG_STATE_LOADING); + oi = n->u.reg.open_info; + n->u.reg.state = REG_STATE_UNLOADED; + file_buffer_reset(&n->u.reg.fbuf); + if (oi->cb) { + oi->cb(oi->fs, NULL, -P9_EIO, oi->opaque); + } + fs_open_end(oi); +} + +static void fs_read_archive(FSOpenInfo *oi) +{ + FSINode *n = oi->n; + uint64_t pos, pos1, l; + uint8_t buf[1024]; + FSINode *n1; + FSOpenInfo *oi1; + struct list_head *el, *el1; + + list_for_each_safe(el, el1, &oi->archive_file_list) { + oi1 = list_entry(el, FSOpenInfo, archive_link); + n1 = oi1->n; + /* copy the archive data to the file */ + pos = oi1->archive_offset; + pos1 = 0; + while (pos1 < n1->u.reg.size) { + l = n1->u.reg.size - pos1; + if (l > sizeof(buf)) + l = sizeof(buf); + file_buffer_read(&n->u.reg.fbuf, pos, buf, l); + file_buffer_write(&n1->u.reg.fbuf, pos1, buf, l); + pos += l; + pos1 += l; + } + fs_wget_set_loaded(n1); + } +} + +static void fs_error_archive(FSOpenInfo *oi) +{ + FSOpenInfo *oi1; + struct list_head *el, *el1; + + list_for_each_safe(el, el1, &oi->archive_file_list) { + oi1 = list_entry(el, FSOpenInfo, archive_link); + fs_wget_set_error(oi1->n); + } +} + +static void fs_open_cb(void *opaque, int err, void *data, size_t size) +{ + FSOpenInfo *oi = opaque; + FSINode *n = oi->n; + + // printf("open_cb: err=%d size=%ld\n", err, size); + if (err < 0) { + error: + if (oi->open_type == FS_OPEN_WGET_ARCHIVE) + fs_error_archive(oi); + fs_wget_set_error(n); + } else { + if (oi->dec_state) { + if (decrypt_file(oi->dec_state, data, size) < 0) + goto error; + if (err == 0) { + if (decrypt_file_flush(oi->dec_state) < 0) + goto error; + } + } else { + fs_open_write_cb(oi, data, size); + } + + if (err == 0) { + /* end of transfer */ + if (oi->cur_pos != n->u.reg.size) + goto error; +#ifdef DUMP_CACHE_LOAD + dump_loaded_file(oi->fs, n); +#endif + if (oi->open_type == FS_OPEN_WGET_ARCHIVE) + fs_read_archive(oi); + fs_wget_set_loaded(n); + } + } +} + + +static int fs_open_wget(FSDevice *fs1, FSINode *n, FSOpenWgetEnum open_type) +{ + char *url; + FSOpenInfo *oi; + char fname[FILEID_SIZE_MAX]; + FSBaseURL *bu; + + assert(n->u.reg.state == REG_STATE_UNLOADED); + + fs_trim_cache(fs1, n->u.reg.size); + + if (file_buffer_resize(&n->u.reg.fbuf, n->u.reg.size) < 0) + return -P9_EIO; + n->u.reg.state = REG_STATE_LOADING; + oi = mallocz(sizeof(*oi)); + oi->cur_pos = 0; + oi->fs = fs1; + oi->n = n; + oi->open_type = open_type; + if (open_type != FS_OPEN_WGET_ARCHIVE_FILE) { + if (open_type == FS_OPEN_WGET_ARCHIVE) + init_list_head(&oi->archive_file_list); + file_id_to_filename(fname, n->u.reg.file_id); + bu = n->u.reg.base_url; + url = compose_path(bu->url, fname); + if (bu->encrypted) { + oi->dec_state = decrypt_file_init(&bu->aes_state, fs_open_write_cb, oi); + } + oi->xhr = fs_wget(url, bu->user, bu->password, oi, fs_open_cb, FALSE); + } + n->u.reg.open_info = oi; + return 0; +} + + +static void fs_preload_file(FSDevice *fs1, const char *filename) +{ + FSINode *n; + + n = inode_search_path(fs1, filename); + if (n && n->type == FT_REG && n->u.reg.state == REG_STATE_UNLOADED) { +#if defined(DEBUG_CACHE) + printf("preload: %s\n", filename); +#endif + fs_open_wget(fs1, n, FS_OPEN_WGET_REG); + } +} + +static PreloadArchive *find_preload_archive(FSDeviceMem *fs, + const char *filename) +{ + PreloadArchive *pa; + struct list_head *el; + list_for_each(el, &fs->preload_archive_list) { + pa = list_entry(el, PreloadArchive, link); + if (!strcmp(pa->name, filename)) + return pa; + } + return NULL; +} + +static void fs_preload_archive(FSDevice *fs1, const char *filename) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + PreloadArchive *pa; + PreloadArchiveFile *paf; + struct list_head *el; + FSINode *n, *n1; + uint64_t offset; + BOOL has_unloaded; + + pa = find_preload_archive(fs, filename); + if (!pa) + return; +#if defined(DEBUG_CACHE) + printf("preload archive: %s\n", filename); +#endif + n = inode_search_path(fs1, filename); + if (n && n->type == FT_REG && n->u.reg.state == REG_STATE_UNLOADED) { + /* if all the files are loaded, no need to load the archive */ + offset = 0; + has_unloaded = FALSE; + list_for_each(el, &pa->file_list) { + paf = list_entry(el, PreloadArchiveFile, link); + n1 = inode_search_path(fs1, paf->name); + if (n1 && n1->type == FT_REG && + n1->u.reg.state == REG_STATE_UNLOADED) { + has_unloaded = TRUE; + } + offset += paf->size; + } + if (!has_unloaded) { +#if defined(DEBUG_CACHE) + printf("archive files already loaded\n"); +#endif + return; + } + /* check archive size consistency */ + if (offset != n->u.reg.size) { +#if defined(DEBUG_CACHE) + printf(" inconsistent archive size: %" PRId64 " %" PRId64 "\n", + offset, n->u.reg.size); +#endif + goto load_fallback; + } + + /* start loading the archive */ + fs_open_wget(fs1, n, FS_OPEN_WGET_ARCHIVE); + + /* indicate that all the archive files are being loaded. Also + check consistency of size and file id */ + offset = 0; + list_for_each(el, &pa->file_list) { + paf = list_entry(el, PreloadArchiveFile, link); + n1 = inode_search_path(fs1, paf->name); + if (n1 && n1->type == FT_REG && + n1->u.reg.state == REG_STATE_UNLOADED) { + if (n1->u.reg.size == paf->size && + n1->u.reg.file_id == paf->file_id) { + fs_open_wget(fs1, n1, FS_OPEN_WGET_ARCHIVE_FILE); + list_add_tail(&n1->u.reg.open_info->archive_link, + &n->u.reg.open_info->archive_file_list); + n1->u.reg.open_info->archive_offset = offset; + } else { +#if defined(DEBUG_CACHE) + printf(" inconsistent archive file: %s\n", paf->name); +#endif + /* fallback to file preload */ + fs_preload_file(fs1, paf->name); + } + } + offset += paf->size; + } + } else { + load_fallback: + /* if the archive is already loaded or not loaded, we load the + files separately (XXX: not optimal if the archive is + already loaded, but it should not happen often) */ + list_for_each(el, &pa->file_list) { + paf = list_entry(el, PreloadArchiveFile, link); + fs_preload_file(fs1, paf->name); + } + } +} + +static void fs_preload_files(FSDevice *fs1, FSFileID file_id) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + struct list_head *el; + PreloadEntry *pe; + PreloadFile *pf; + + list_for_each(el, &fs->preload_list) { + pe = list_entry(el, PreloadEntry, link); + if (pe->file_id == file_id) + goto found; + } + return; + found: + list_for_each(el, &pe->file_list) { + pf = list_entry(el, PreloadFile, link); + if (pf->is_archive) + fs_preload_archive(fs1, pf->name); + else + fs_preload_file(fs1, pf->name); + } +} + +/* return < 0 if error, 0 if OK, 1 if asynchronous completion */ +/* XXX: we don't support several simultaneous asynchronous open on the + same inode */ +static int fs_open(FSDevice *fs1, FSQID *qid, FSFile *f, uint32_t flags, + FSOpenCompletionFunc *cb, void *opaque) +{ + FSINode *n = f->inode; + FSDeviceMem *fs = (FSDeviceMem *)fs1; + int ret; + + fs_close(fs1, f); + + if (flags & P9_O_DIRECTORY) { + if (n->type != FT_DIR) + return -P9_ENOTDIR; + } else { + if (n->type != FT_REG && n->type != FT_DIR) + return -P9_EINVAL; /* XXX */ + } + f->open_flags = flags; + if (n->type == FT_REG) { + if ((flags & P9_O_TRUNC) && (flags & P9_O_NOACCESS) != P9_O_RDONLY) { + fs_truncate(fs1, n, 0); + } + + switch(n->u.reg.state) { + case REG_STATE_UNLOADED: + { + FSOpenInfo *oi; + /* need to load the file */ + fs_preload_files(fs1, n->u.reg.file_id); + /* The state can be modified by the fs_preload_files */ + if (n->u.reg.state == REG_STATE_LOADING) + goto handle_loading; + ret = fs_open_wget(fs1, n, FS_OPEN_WGET_REG); + if (ret) + return ret; + oi = n->u.reg.open_info; + oi->f = f; + oi->cb = cb; + oi->opaque = opaque; + return 1; /* completion callback will be called later */ + } + break; + case REG_STATE_LOADING: + handle_loading: + { + FSOpenInfo *oi; + /* we only handle the case where the file is being preloaded */ + oi = n->u.reg.open_info; + if (oi->cb) + return -P9_EIO; + oi = n->u.reg.open_info; + oi->f = f; + oi->cb = cb; + oi->opaque = opaque; + return 1; /* completion callback will be called later */ + } + break; + case REG_STATE_LOCAL: + goto do_open; + case REG_STATE_LOADED: + /* move to front */ + list_del(&n->u.reg.link); + list_add(&n->u.reg.link, &fs->inode_cache_list); + goto do_open; + default: + abort(); + } + } else { + do_open: + f->is_opened = TRUE; + inode_to_qid(qid, n); + return 0; + } +} + +static int fs_create(FSDevice *fs, FSQID *qid, FSFile *f, const char *name, + uint32_t flags, uint32_t mode, uint32_t gid) +{ + FSINode *n1, *n = f->inode; + + if (n->type != FT_DIR) + return -P9_ENOTDIR; + if (inode_search(n, name)) { + /* XXX: support it, but Linux does not seem to use this case */ + return -P9_EEXIST; + } else { + fs_close(fs, f); + + n1 = inode_new(fs, FT_REG, mode, f->uid, gid); + inode_dir_add(fs, n, name, n1); + + inode_dec_open(fs, f->inode); + f->inode = inode_inc_open(fs, n1); + f->is_opened = TRUE; + f->open_flags = flags; + inode_to_qid(qid, n1); + return 0; + } +} + +static int fs_readdir(FSDevice *fs, FSFile *f, uint64_t offset1, + uint8_t *buf, int count) +{ + FSINode *n1, *n = f->inode; + int len, pos, name_len, type; + struct list_head *el; + FSDirEntry *de; + uint64_t offset; + + if (!f->is_opened || n->type != FT_DIR) + return -P9_EPROTO; + + el = n->u.dir.de_list.next; + offset = 0; + while (offset < offset1) { + if (el == &n->u.dir.de_list) + return 0; /* no more entries */ + offset++; + el = el->next; + } + + pos = 0; + for(;;) { + if (el == &n->u.dir.de_list) + break; + de = list_entry(el, FSDirEntry, link); + name_len = strlen(de->name); + len = 13 + 8 + 1 + 2 + name_len; + if ((pos + len) > count) + break; + offset++; + n1 = de->inode; + if (n1->type == FT_DIR) + type = P9_QTDIR; + else if (n1->type == FT_LNK) + type = P9_QTSYMLINK; + else + type = P9_QTFILE; + buf[pos++] = type; + put_le32(buf + pos, 0); /* version */ + pos += 4; + put_le64(buf + pos, n1->inode_num); + pos += 8; + put_le64(buf + pos, offset); + pos += 8; + buf[pos++] = n1->type; + put_le16(buf + pos, name_len); + pos += 2; + memcpy(buf + pos, de->name, name_len); + pos += name_len; + el = el->next; + } + return pos; +} + +static int fs_read(FSDevice *fs, FSFile *f, uint64_t offset, + uint8_t *buf, int count) +{ + FSINode *n = f->inode; + uint64_t count1; + + if (!f->is_opened) + return -P9_EPROTO; + if (n->type != FT_REG) + return -P9_EIO; + if ((f->open_flags & P9_O_NOACCESS) == P9_O_WRONLY) + return -P9_EIO; + if (n->u.reg.is_fscmd) + return fs_cmd_read(fs, f, offset, buf, count); + if (offset >= n->u.reg.size) + return 0; + count1 = n->u.reg.size - offset; + if (count1 < count) + count = count1; + file_buffer_read(&n->u.reg.fbuf, offset, buf, count); + return count; +} + +static int fs_truncate(FSDevice *fs1, FSINode *n, uint64_t size) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + intptr_t diff, diff_blocks; + size_t new_allocated_size; + + if (n->type != FT_REG) + return -P9_EINVAL; + if (size > UINTPTR_MAX) + return -P9_ENOSPC; + diff = size - n->u.reg.size; + if (diff == 0) + return 0; + diff_blocks = to_blocks(fs, size) - to_blocks(fs, n->u.reg.size); + /* currently cannot resize while loading */ + switch(n->u.reg.state) { + case REG_STATE_LOADING: + return -P9_EIO; + case REG_STATE_UNLOADED: + if (size == 0) { + /* now local content */ + n->u.reg.state = REG_STATE_LOCAL; + } + break; + case REG_STATE_LOADED: + case REG_STATE_LOCAL: + if (diff > 0) { + if ((fs->fs_blocks + diff_blocks) > fs->fs_max_blocks) + return -P9_ENOSPC; + if (size > n->u.reg.fbuf.allocated_size) { + new_allocated_size = n->u.reg.fbuf.allocated_size * 5 / 4; + if (size > new_allocated_size) + new_allocated_size = size; + if (file_buffer_resize(&n->u.reg.fbuf, new_allocated_size) < 0) + return -P9_ENOSPC; + } + file_buffer_set(&n->u.reg.fbuf, n->u.reg.size, 0, diff); + } else { + new_allocated_size = n->u.reg.fbuf.allocated_size * 4 / 5; + if (size <= new_allocated_size) { + if (file_buffer_resize(&n->u.reg.fbuf, new_allocated_size) < 0) + return -P9_ENOSPC; + } + } + /* file is modified, so it is now local */ + if (n->u.reg.state == REG_STATE_LOADED) { + list_del(&n->u.reg.link); + fs->inode_cache_size -= n->u.reg.size; + assert(fs->inode_cache_size >= 0); + n->u.reg.state = REG_STATE_LOCAL; + } + break; + default: + abort(); + } + fs->fs_blocks += diff_blocks; + assert(fs->fs_blocks >= 0); + n->u.reg.size = size; + return 0; +} + +static int fs_write(FSDevice *fs1, FSFile *f, uint64_t offset, + const uint8_t *buf, int count) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + FSINode *n = f->inode; + uint64_t end; + int err; + + if (!f->is_opened) + return -P9_EPROTO; + if (n->type != FT_REG) + return -P9_EIO; + if ((f->open_flags & P9_O_NOACCESS) == P9_O_RDONLY) + return -P9_EIO; + if (count == 0) + return 0; + if (n->u.reg.is_fscmd) { + return fs_cmd_write(fs1, f, offset, buf, count); + } + end = offset + count; + if (end > n->u.reg.size) { + err = fs_truncate(fs1, n, end); + if (err) + return err; + } + inode_update_mtime(fs1, n); + /* file is modified, so it is now local */ + if (n->u.reg.state == REG_STATE_LOADED) { + list_del(&n->u.reg.link); + fs->inode_cache_size -= n->u.reg.size; + assert(fs->inode_cache_size >= 0); + n->u.reg.state = REG_STATE_LOCAL; + } + file_buffer_write(&n->u.reg.fbuf, offset, buf, count); + return count; +} + +static void fs_close(FSDevice *fs, FSFile *f) +{ + if (f->is_opened) { + f->is_opened = FALSE; + } + if (f->req) + fs_cmd_close(fs, f); +} + +static int fs_stat(FSDevice *fs1, FSFile *f, FSStat *st) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + FSINode *n = f->inode; + + inode_to_qid(&st->qid, n); + st->st_mode = n->mode | (n->type << 12); + st->st_uid = n->uid; + st->st_gid = n->gid; + st->st_nlink = n->refcount; + if (n->type == FT_BLK || n->type == FT_CHR) { + /* XXX: check */ + st->st_rdev = (n->u.dev.major << 8) | n->u.dev.minor; + } else { + st->st_rdev = 0; + } + st->st_blksize = fs->block_size; + if (n->type == FT_REG) { + st->st_size = n->u.reg.size; + } else if (n->type == FT_LNK) { + st->st_size = strlen(n->u.symlink.name); + } else if (n->type == FT_DIR) { + st->st_size = n->u.dir.size; + } else { + st->st_size = 0; + } + /* in 512 byte blocks */ + st->st_blocks = to_blocks(fs, st->st_size) << (fs->block_size_log2 - 9); + + /* Note: atime is not supported */ + st->st_atime_sec = n->mtime_sec; + st->st_atime_nsec = n->mtime_nsec; + st->st_mtime_sec = n->mtime_sec; + st->st_mtime_nsec = n->mtime_nsec; + st->st_ctime_sec = n->ctime_sec; + st->st_ctime_nsec = n->ctime_nsec; + return 0; +} + +static int fs_setattr(FSDevice *fs1, FSFile *f, uint32_t mask, + uint32_t mode, uint32_t uid, uint32_t gid, + uint64_t size, uint64_t atime_sec, uint64_t atime_nsec, + uint64_t mtime_sec, uint64_t mtime_nsec) +{ + FSINode *n = f->inode; + int ret; + + if (mask & P9_SETATTR_MODE) { + n->mode = mode; + } + if (mask & P9_SETATTR_UID) { + n->uid = uid; + } + if (mask & P9_SETATTR_GID) { + n->gid = gid; + } + if (mask & P9_SETATTR_SIZE) { + ret = fs_truncate(fs1, n, size); + if (ret) + return ret; + } + if (mask & P9_SETATTR_MTIME) { + if (mask & P9_SETATTR_MTIME_SET) { + n->mtime_sec = mtime_sec; + n->mtime_nsec = mtime_nsec; + } else { + inode_update_mtime(fs1, n); + } + } + if (mask & P9_SETATTR_CTIME) { + struct timeval tv; + gettimeofday(&tv, NULL); + n->ctime_sec = tv.tv_sec; + n->ctime_nsec = tv.tv_usec * 1000; + } + return 0; +} + +static int fs_link(FSDevice *fs, FSFile *df, FSFile *f, const char *name) +{ + FSINode *n = df->inode; + + if (f->inode->type == FT_DIR) + return -P9_EPERM; + if (inode_search(n, name)) + return -P9_EEXIST; + inode_dir_add(fs, n, name, inode_incref(fs, f->inode)); + return 0; +} + +static int fs_symlink(FSDevice *fs, FSQID *qid, + FSFile *f, const char *name, const char *symgt, uint32_t gid) +{ + FSINode *n1, *n = f->inode; + + if (inode_search(n, name)) + return -P9_EEXIST; + + n1 = inode_new(fs, FT_LNK, 0777, f->uid, gid); + n1->u.symlink.name = strdup(symgt); + inode_dir_add(fs, n, name, n1); + inode_to_qid(qid, n1); + return 0; +} + +static int fs_mknod(FSDevice *fs, FSQID *qid, + FSFile *f, const char *name, uint32_t mode, uint32_t major, + uint32_t minor, uint32_t gid) +{ + int type; + FSINode *n1, *n = f->inode; + + type = (mode & P9_S_IFMT) >> 12; + /* XXX: add FT_DIR support */ + if (type != FT_FIFO && type != FT_CHR && type != FT_BLK && + type != FT_REG && type != FT_SOCK) + return -P9_EINVAL; + if (inode_search(n, name)) + return -P9_EEXIST; + n1 = inode_new(fs, type, mode, f->uid, gid); + if (type == FT_CHR || type == FT_BLK) { + n1->u.dev.major = major; + n1->u.dev.minor = minor; + } + inode_dir_add(fs, n, name, n1); + inode_to_qid(qid, n1); + return 0; +} + +static int fs_readlink(FSDevice *fs, char *buf, int buf_size, FSFile *f) +{ + FSINode *n = f->inode; + int len; + if (n->type != FT_LNK) + return -P9_EIO; + len = min_int(strlen(n->u.symlink.name), buf_size - 1); + memcpy(buf, n->u.symlink.name, len); + buf[len] = '\0'; + return 0; +} + +static int fs_renameat(FSDevice *fs, FSFile *f, const char *name, + FSFile *new_f, const char *new_name) +{ + FSDirEntry *de, *de1; + FSINode *n1; + + de = inode_search(f->inode, name); + if (!de) + return -P9_ENOENT; + de1 = inode_search(new_f->inode, new_name); + n1 = NULL; + if (de1) { + n1 = de1->inode; + if (n1->type == FT_DIR) + return -P9_EEXIST; /* XXX: handle the case */ + inode_dirent_delete_no_decref(fs, new_f->inode, de1); + } + inode_dir_add(fs, new_f->inode, new_name, inode_incref(fs, de->inode)); + inode_dirent_delete(fs, f->inode, de); + if (n1) + inode_decref(fs, n1); + return 0; +} + +static int fs_unlinkat(FSDevice *fs, FSFile *f, const char *name) +{ + FSDirEntry *de; + FSINode *n; + + if (!strcmp(name, ".") || !strcmp(name, "..")) + return -P9_ENOENT; + de = inode_search(f->inode, name); + if (!de) + return -P9_ENOENT; + n = de->inode; + if (n->type == FT_DIR) { + if (!is_empty_dir(fs, n)) + return -P9_ENOTEMPTY; + flush_dir(fs, n); + } + inode_dirent_delete(fs, f->inode, de); + return 0; +} + +static int fs_lock(FSDevice *fs, FSFile *f, const FSLock *lock) +{ + FSINode *n = f->inode; + if (!f->is_opened) + return -P9_EPROTO; + if (n->type != FT_REG) + return -P9_EIO; + /* XXX: implement it */ + return P9_LOCK_SUCCESS; +} + +static int fs_getlock(FSDevice *fs, FSFile *f, FSLock *lock) +{ + FSINode *n = f->inode; + if (!f->is_opened) + return -P9_EPROTO; + if (n->type != FT_REG) + return -P9_EIO; + /* XXX: implement it */ + return 0; +} + +/* XXX: only used with file lists, so not all the data is released */ +static void fs_mem_end(FSDevice *fs1) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + struct list_head *el, *el1, *el2, *el3; + FSINode *n; + FSDirEntry *de; + + list_for_each_safe(el, el1, &fs->inode_list) { + n = list_entry(el, FSINode, link); + n->refcount = 0; + if (n->type == FT_DIR) { + list_for_each_safe(el2, el3, &n->u.dir.de_list) { + de = list_entry(el2, FSDirEntry, link); + list_del(&de->link); + free(de); + } + init_list_head(&n->u.dir.de_list); + } + inode_free(fs1, n); + } + assert(list_empty(&fs->inode_cache_list)); + free(fs->import_dir); +} + +FSDevice *fs_mem_init(void) +{ + FSDeviceMem *fs; + FSDevice *fs1; + FSINode *n; + + fs = mallocz(sizeof(*fs)); + fs1 = &fs->common; + + fs->common.fs_end = fs_mem_end; + fs->common.fs_delete = fs_delete; + fs->common.fs_statfs = fs_statfs; + fs->common.fs_attach = fs_attach; + fs->common.fs_walk = fs_walk; + fs->common.fs_mkdir = fs_mkdir; + fs->common.fs_open = fs_open; + fs->common.fs_create = fs_create; + fs->common.fs_stat = fs_stat; + fs->common.fs_setattr = fs_setattr; + fs->common.fs_close = fs_close; + fs->common.fs_readdir = fs_readdir; + fs->common.fs_read = fs_read; + fs->common.fs_write = fs_write; + fs->common.fs_link = fs_link; + fs->common.fs_symlink = fs_symlink; + fs->common.fs_mknod = fs_mknod; + fs->common.fs_readlink = fs_readlink; + fs->common.fs_renameat = fs_renameat; + fs->common.fs_unlinkat = fs_unlinkat; + fs->common.fs_lock = fs_lock; + fs->common.fs_getlock = fs_getlock; + + init_list_head(&fs->inode_list); + fs->inode_num_alloc = 1; + fs->block_size_log2 = FS_BLOCK_SIZE_LOG2; + fs->block_size = 1 << fs->block_size_log2; + fs->inode_limit = 1 << 20; /* arbitrary */ + fs->fs_max_blocks = 1 << (30 - fs->block_size_log2); /* arbitrary */ + + init_list_head(&fs->inode_cache_list); + fs->inode_cache_size_limit = DEFAULT_INODE_CACHE_SIZE; + + init_list_head(&fs->preload_list); + init_list_head(&fs->preload_archive_list); + + init_list_head(&fs->base_url_list); + + /* create the root inode */ + n = inode_new(fs1, FT_DIR, 0777, 0, 0); + inode_dir_add(fs1, n, ".", inode_incref(fs1, n)); + inode_dir_add(fs1, n, "..", inode_incref(fs1, n)); + fs->root_inode = n; + + return (FSDevice *)fs; +} + +static BOOL fs_is_net(FSDevice *fs) +{ + return (fs->fs_end == fs_mem_end); +} + +static FSBaseURL *fs_find_base_url(FSDevice *fs1, + const char *base_url_id) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + struct list_head *el; + FSBaseURL *bu; + + list_for_each(el, &fs->base_url_list) { + bu = list_entry(el, FSBaseURL, link); + if (!strcmp(bu->base_url_id, base_url_id)) + return bu; + } + return NULL; +} + +static void fs_base_url_decref(FSDevice *fs, FSBaseURL *bu) +{ + assert(bu->ref_count >= 1); + if (--bu->ref_count == 0) { + free(bu->base_url_id); + free(bu->url); + free(bu->user); + free(bu->password); + list_del(&bu->link); + free(bu); + } +} + +static FSBaseURL *fs_net_set_base_url(FSDevice *fs1, + const char *base_url_id, + const char *url, + const char *user, const char *password, + AES_KEY *aes_state) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + FSBaseURL *bu; + + assert(fs_is_net(fs1)); + bu = fs_find_base_url(fs1, base_url_id); + if (!bu) { + bu = mallocz(sizeof(*bu)); + bu->base_url_id = strdup(base_url_id); + bu->ref_count = 1; + list_add_tail(&bu->link, &fs->base_url_list); + } else { + free(bu->url); + free(bu->user); + free(bu->password); + } + + bu->url = strdup(url); + if (user) + bu->user = strdup(user); + else + bu->user = NULL; + if (password) + bu->password = strdup(password); + else + bu->password = NULL; + if (aes_state) { + bu->encrypted = TRUE; + bu->aes_state = *aes_state; + } else { + bu->encrypted = FALSE; + } + return bu; +} + +static int fs_net_reset_base_url(FSDevice *fs1, + const char *base_url_id) +{ + FSBaseURL *bu; + + assert(fs_is_net(fs1)); + bu = fs_find_base_url(fs1, base_url_id); + if (!bu) + return -P9_ENOENT; + fs_base_url_decref(fs1, bu); + return 0; +} + +static void fs_net_set_fs_max_size(FSDevice *fs1, uint64_t fs_max_size) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + + assert(fs_is_net(fs1)); + fs->fs_max_blocks = to_blocks(fs, fs_max_size); +} + +static int fs_net_set_url(FSDevice *fs1, FSINode *n, + const char *base_url_id, FSFileID file_id, uint64_t size) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + FSBaseURL *bu; + + assert(fs_is_net(fs1)); + + bu = fs_find_base_url(fs1, base_url_id); + if (!bu) + return -P9_ENOENT; + + /* XXX: could accept more state */ + if (n->type != FT_REG || + n->u.reg.state != REG_STATE_LOCAL || + n->u.reg.fbuf.allocated_size != 0) + return -P9_EIO; + + if (size > 0) { + n->u.reg.state = REG_STATE_UNLOADED; + n->u.reg.base_url = bu; + bu->ref_count++; + n->u.reg.size = size; + fs->fs_blocks += to_blocks(fs, size); + n->u.reg.file_id = file_id; + } + return 0; +} + +#ifdef DUMP_CACHE_LOAD + +#include "json.h" + +#define ARCHIVE_SIZE_MAX (4 << 20) + +static void fs_dump_add_file(struct list_head *head, const char *name) +{ + PreloadFile *pf; + pf = mallocz(sizeof(*pf)); + pf->name = strdup(name); + list_add_tail(&pf->link, head); +} + +static PreloadFile *fs_dump_find_file(struct list_head *head, const char *name) +{ + PreloadFile *pf; + struct list_head *el; + list_for_each(el, head) { + pf = list_entry(el, PreloadFile, link); + if (!strcmp(pf->name, name)) + return pf; + } + return NULL; +} + +static void dump_close_archive(FSDevice *fs1) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + if (fs->dump_archive_file) { + fclose(fs->dump_archive_file); + } + fs->dump_archive_file = NULL; + fs->dump_archive_size = 0; +} + +static void dump_loaded_file(FSDevice *fs1, FSINode *n) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + char filename[1024]; + const char *fname, *p; + + if (!fs->dump_cache_load || !n->u.reg.filename) + return; + fname = n->u.reg.filename; + + if (fs_dump_find_file(&fs->dump_preload_list, fname)) { + dump_close_archive(fs1); + p = strrchr(fname, '/'); + if (!p) + p = fname; + else + p++; + free(fs->dump_archive_name); + fs->dump_archive_name = strdup(p); + fs->dump_started = TRUE; + fs->dump_archive_num = 0; + + fprintf(fs->dump_preload_file, "\n%s :\n", fname); + } + if (!fs->dump_started) + return; + + if (!fs->dump_archive_file) { + snprintf(filename, sizeof(filename), "%s/%s%d", + fs->dump_preload_dir, fs->dump_archive_name, + fs->dump_archive_num); + fs->dump_archive_file = fopen(filename, "wb"); + if (!fs->dump_archive_file) { + perror(filename); + exit(1); + } + fprintf(fs->dump_preload_archive_file, "\n@.preload2/%s%d :\n", + fs->dump_archive_name, fs->dump_archive_num); + fprintf(fs->dump_preload_file, " @.preload2/%s%d\n", + fs->dump_archive_name, fs->dump_archive_num); + fflush(fs->dump_preload_file); + fs->dump_archive_num++; + } + + if (n->u.reg.size >= ARCHIVE_SIZE_MAX) { + /* exclude large files from archive */ + /* add indicative size */ + fprintf(fs->dump_preload_file, " %s %" PRId64 "\n", + fname, n->u.reg.size); + fflush(fs->dump_preload_file); + } else { + fprintf(fs->dump_preload_archive_file, " %s %" PRId64 " %" PRIx64 "\n", + n->u.reg.filename, n->u.reg.size, n->u.reg.file_id); + fflush(fs->dump_preload_archive_file); + fwrite(n->u.reg.fbuf.data, 1, n->u.reg.size, fs->dump_archive_file); + fflush(fs->dump_archive_file); + fs->dump_archive_size += n->u.reg.size; + if (fs->dump_archive_size >= ARCHIVE_SIZE_MAX) { + dump_close_archive(fs1); + } + } +} + +static JSONValue json_load(const char *filename) +{ + FILE *f; + JSONValue val; + size_t size; + char *buf; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + buf = malloc(size + 1); + fread(buf, 1, size, f); + fclose(f); + val = json_parse_value_len(buf, size); + free(buf); + return val; +} + +void fs_dump_cache_load(FSDevice *fs1, const char *cfg_filename) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + JSONValue cfg, val, array; + char *fname; + const char *preload_dir, *name; + int i; + + if (!fs_is_net(fs1)) + return; + cfg = json_load(cfg_filename); + if (json_is_error(cfg)) { + fprintf(stderr, "%s\n", json_get_error(cfg)); + exit(1); + } + + val = json_object_get(cfg, "preload_dir"); + if (json_is_undefined(cfg)) { + config_error: + exit(1); + } + preload_dir = json_get_str(val); + if (!preload_dir) { + fprintf(stderr, "expecting preload_filename\n"); + goto config_error; + } + fs->dump_preload_dir = strdup(preload_dir); + + init_list_head(&fs->dump_preload_list); + init_list_head(&fs->dump_exclude_list); + + array = json_object_get(cfg, "preload"); + if (array.type != JSON_ARRAY) { + fprintf(stderr, "expecting preload array\n"); + goto config_error; + } + for(i = 0; i < array.u.array->len; i++) { + val = json_array_get(array, i); + name = json_get_str(val); + if (!name) { + fprintf(stderr, "expecting a string\n"); + goto config_error; + } + fs_dump_add_file(&fs->dump_preload_list, name); + } + json_free(cfg); + + fname = compose_path(fs->dump_preload_dir, "preload.txt"); + fs->dump_preload_file = fopen(fname, "w"); + if (!fs->dump_preload_file) { + perror(fname); + exit(1); + } + free(fname); + + fname = compose_path(fs->dump_preload_dir, "preload_archive.txt"); + fs->dump_preload_archive_file = fopen(fname, "w"); + if (!fs->dump_preload_archive_file) { + perror(fname); + exit(1); + } + free(fname); + + fs->dump_cache_load = TRUE; +} +#else +void fs_dump_cache_load(FSDevice *fs1, const char *cfg_filename) +{ +} +#endif + +/***********************************************/ +/* file list processing */ + +static int filelist_load_rec(FSDevice *fs1, const char **pp, FSINode *dir, + const char *path) +{ + // FSDeviceMem *fs = (FSDeviceMem *)fs1; + char fname[1024], lname[1024]; + int ret; + const char *p; + FSINodeTypeEnum type; + uint32_t mode, uid, gid; + uint64_t size; + FSINode *n; + + p = *pp; + for(;;) { + /* skip comments or empty lines */ + if (*p == '\0') + break; + if (*p == '#') { + skip_line(&p); + continue; + } + /* end of directory */ + if (*p == '.') { + p++; + skip_line(&p); + break; + } + if (parse_uint32_base(&mode, &p, 8) < 0) { + fprintf(stderr, "invalid mode\n"); + return -1; + } + type = mode >> 12; + mode &= 0xfff; + + if (parse_uint32(&uid, &p) < 0) { + fprintf(stderr, "invalid uid\n"); + return -1; + } + + if (parse_uint32(&gid, &p) < 0) { + fprintf(stderr, "invalid gid\n"); + return -1; + } + + n = inode_new(fs1, type, mode, uid, gid); + + size = 0; + switch(type) { + case FT_CHR: + case FT_BLK: + if (parse_uint32(&n->u.dev.major, &p) < 0) { + fprintf(stderr, "invalid major\n"); + return -1; + } + if (parse_uint32(&n->u.dev.minor, &p) < 0) { + fprintf(stderr, "invalid minor\n"); + return -1; + } + break; + case FT_REG: + if (parse_uint64(&size, &p) < 0) { + fprintf(stderr, "invalid size\n"); + return -1; + } + break; + case FT_DIR: + inode_dir_add(fs1, n, ".", inode_incref(fs1, n)); + inode_dir_add(fs1, n, "..", inode_incref(fs1, dir)); + break; + default: + break; + } + + /* modification time */ + if (parse_time(&n->mtime_sec, &n->mtime_nsec, &p) < 0) { + fprintf(stderr, "invalid mtime\n"); + return -1; + } + + if (parse_fname(fname, sizeof(fname), &p) < 0) { + fprintf(stderr, "invalid filename\n"); + return -1; + } + inode_dir_add(fs1, dir, fname, n); + + if (type == FT_LNK) { + if (parse_fname(lname, sizeof(lname), &p) < 0) { + fprintf(stderr, "invalid symlink name\n"); + return -1; + } + n->u.symlink.name = strdup(lname); + } else if (type == FT_REG && size > 0) { + FSFileID file_id; + if (parse_file_id(&file_id, &p) < 0) { + fprintf(stderr, "invalid file id\n"); + return -1; + } + fs_net_set_url(fs1, n, "/", file_id, size); +#ifdef DUMP_CACHE_LOAD + { + FSDeviceMem *fs = (FSDeviceMem *)fs1; + if (fs->dump_cache_load +#ifdef DEBUG_CACHE + || 1 +#endif + ) { + n->u.reg.filename = compose_path(path, fname); + } else { + n->u.reg.filename = NULL; + } + } +#endif + } + + skip_line(&p); + + if (type == FT_DIR) { + char *path1; + path1 = compose_path(path, fname); + ret = filelist_load_rec(fs1, &p, n, path1); + free(path1); + if (ret) + return ret; + } + } + *pp = p; + return 0; +} + +static int filelist_load(FSDevice *fs1, const char *str) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + int ret; + const char *p; + + if (parse_tag_version(str) != 1) + return -1; + p = skip_header(str); + if (!p) + return -1; + ret = filelist_load_rec(fs1, &p, fs->root_inode, ""); + return ret; +} + +/************************************************************/ +/* FS init from network */ + +static void __attribute__((format(printf, 1, 2))) fatal_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "Error: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(1); +} + +static void fs_create_cmd(FSDevice *fs) +{ + FSFile *root_fd; + FSQID qid; + FSINode *n; + + assert(!fs->fs_attach(fs, &root_fd, &qid, 0, "", "")); + assert(!fs->fs_create(fs, &qid, root_fd, FSCMD_NAME, P9_O_RDWR | P9_O_TRUNC, + 0666, 0)); + n = root_fd->inode; + n->u.reg.is_fscmd = TRUE; + fs->fs_delete(fs, root_fd); +} + +typedef struct { + FSDevice *fs; + char *url; + void (*start_cb)(void *opaque); + void *start_opaque; + + FSFile *root_fd; + FSFile *fd; + int file_index; + +} FSNetInitState; + +static void fs_initial_sync(FSDevice *fs, + const char *url, void (*start_cb)(void *opaque), + void *start_opaque); +static void head_loaded(FSDevice *fs, FSFile *f, int64_t size, void *opaque); +static void filelist_loaded(FSDevice *fs, FSFile *f, int64_t size, void *opaque); +static void kernel_load_cb(FSDevice *fs, FSQID *qid, int err, + void *opaque); +static int preload_parse(FSDevice *fs, const char *fname, BOOL is_new); + +#ifdef EMSCRIPTEN +static FSDevice *fs_import_fs; +#endif + +#define DEFAULT_IMPORT_FILE_PATH "/tmp" + +FSDevice *fs_net_init(const char *url, void (*start_cb)(void *opaque), + void *start_opaque) +{ + FSDevice *fs; + FSDeviceMem *fs1; + + fs_wget_init(); + + fs = fs_mem_init(); +#ifdef EMSCRIPTEN + if (!fs_import_fs) + fs_import_fs = fs; +#endif + fs1 = (FSDeviceMem *)fs; + fs1->import_dir = strdup(DEFAULT_IMPORT_FILE_PATH); + + fs_create_cmd(fs); + + if (url) { + fs_initial_sync(fs, url, start_cb, start_opaque); + } + return fs; +} + +static void fs_initial_sync(FSDevice *fs, + const char *url, void (*start_cb)(void *opaque), + void *start_opaque) +{ + FSNetInitState *s; + FSFile *head_fd; + FSQID qid; + char *head_url; + char buf[128]; + struct timeval tv; + + s = mallocz(sizeof(*s)); + s->fs = fs; + s->url = strdup(url); + s->start_cb = start_cb; + s->start_opaque = start_opaque; + assert(!fs->fs_attach(fs, &s->root_fd, &qid, 0, "", "")); + + /* avoid using cached version */ + gettimeofday(&tv, NULL); + snprintf(buf, sizeof(buf), HEAD_FILENAME "?nocache=%" PRId64, + (int64_t)tv.tv_sec * 1000000 + tv.tv_usec); + head_url = compose_url(s->url, buf); + head_fd = fs_dup(fs, s->root_fd); + assert(!fs->fs_create(fs, &qid, head_fd, ".head", + P9_O_RDWR | P9_O_TRUNC, 0644, 0)); + fs_wget_file2(fs, head_fd, head_url, NULL, NULL, NULL, 0, + head_loaded, s, NULL); + free(head_url); +} + +static void head_loaded(FSDevice *fs, FSFile *f, int64_t size, void *opaque) +{ + FSNetInitState *s = opaque; + char *buf, *root_url, *url; + char fname[FILEID_SIZE_MAX]; + FSFileID root_id; + FSFile *new_filelist_fd; + FSQID qid; + uint64_t fs_max_size; + + if (size < 0) + fatal_error("could not load 'head' file (HTTP error=%d)", -(int)size); + + buf = malloc(size + 1); + fs->fs_read(fs, f, 0, (uint8_t *)buf, size); + buf[size] = '\0'; + fs->fs_delete(fs, f); + fs->fs_unlinkat(fs, s->root_fd, ".head"); + + if (parse_tag_version(buf) != 1) + fatal_error("invalid head version"); + + if (parse_tag_file_id(&root_id, buf, "RootID") < 0) + fatal_error("expected RootID tag"); + + if (parse_tag_uint64(&fs_max_size, buf, "FSMaxSize") == 0 && + fs_max_size >= ((uint64_t)1 << 20)) { + fs_net_set_fs_max_size(fs, fs_max_size); + } + + /* set the Root URL in the filesystem */ + root_url = compose_url(s->url, ROOT_FILENAME); + fs_net_set_base_url(fs, "/", root_url, NULL, NULL, NULL); + + new_filelist_fd = fs_dup(fs, s->root_fd); + assert(!fs->fs_create(fs, &qid, new_filelist_fd, ".filelist.txt", + P9_O_RDWR | P9_O_TRUNC, 0644, 0)); + + file_id_to_filename(fname, root_id); + url = compose_url(root_url, fname); + fs_wget_file2(fs, new_filelist_fd, url, NULL, NULL, NULL, 0, + filelist_loaded, s, NULL); + free(root_url); + free(url); +} + +static void filelist_loaded(FSDevice *fs, FSFile *f, int64_t size, void *opaque) +{ + FSNetInitState *s = opaque; + uint8_t *buf; + + if (size < 0) + fatal_error("could not load file list (HTTP error=%d)", -(int)size); + + buf = malloc(size + 1); + fs->fs_read(fs, f, 0, buf, size); + buf[size] = '\0'; + fs->fs_delete(fs, f); + fs->fs_unlinkat(fs, s->root_fd, ".filelist.txt"); + + if (filelist_load(fs, (char *)buf) != 0) + fatal_error("error while parsing file list"); + + /* try to load the kernel and the preload file */ + s->file_index = 0; + kernel_load_cb(fs, NULL, 0, s); +} + + +#define FILE_LOAD_COUNT 2 + +static const char *kernel_file_list[FILE_LOAD_COUNT] = { + ".preload", + ".preload2/preload.txt", +}; + +static void kernel_load_cb(FSDevice *fs, FSQID *qid1, int err, + void *opaque) +{ + FSNetInitState *s = opaque; + FSQID qid; + +#ifdef DUMP_CACHE_LOAD + /* disable preloading if dumping cache load */ + if (((FSDeviceMem *)fs)->dump_cache_load) + return; +#endif + + if (s->fd) { + fs->fs_delete(fs, s->fd); + s->fd = NULL; + } + + if (s->file_index >= FILE_LOAD_COUNT) { + /* all files are loaded */ + if (preload_parse(fs, ".preload2/preload.txt", TRUE) < 0) { + preload_parse(fs, ".preload", FALSE); + } + fs->fs_delete(fs, s->root_fd); + if (s->start_cb) + s->start_cb(s->start_opaque); + free(s); + } else { + s->fd = fs_walk_path(fs, s->root_fd, kernel_file_list[s->file_index++]); + if (!s->fd) + goto done; + err = fs->fs_open(fs, &qid, s->fd, P9_O_RDONLY, kernel_load_cb, s); + if (err <= 0) { + done: + kernel_load_cb(fs, NULL, 0, s); + } + } +} + +static void preload_parse_str_old(FSDevice *fs1, const char *p) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + char fname[1024]; + PreloadEntry *pe; + PreloadFile *pf; + FSINode *n; + + for(;;) { + while (isspace_nolf(*p)) + p++; + if (*p == '\n') { + p++; + continue; + } + if (*p == '\0') + break; + if (parse_fname(fname, sizeof(fname), &p) < 0) { + fprintf(stderr, "invalid filename\n"); + return; + } + // printf("preload file='%s\n", fname); + n = inode_search_path(fs1, fname); + if (!n || n->type != FT_REG || n->u.reg.state == REG_STATE_LOCAL) { + fprintf(stderr, "invalid preload file: '%s'\n", fname); + while (*p != '\n' && *p != '\0') + p++; + } else { + pe = mallocz(sizeof(*pe)); + pe->file_id = n->u.reg.file_id; + init_list_head(&pe->file_list); + list_add_tail(&pe->link, &fs->preload_list); + for(;;) { + while (isspace_nolf(*p)) + p++; + if (*p == '\0' || *p == '\n') + break; + if (parse_fname(fname, sizeof(fname), &p) < 0) { + fprintf(stderr, "invalid filename\n"); + return; + } + // printf(" adding '%s'\n", fname); + pf = mallocz(sizeof(*pf)); + pf->name = strdup(fname); + list_add_tail(&pf->link, &pe->file_list); + } + } + } +} + +static void preload_parse_str(FSDevice *fs1, const char *p) +{ + FSDeviceMem *fs = (FSDeviceMem *)fs1; + PreloadEntry *pe; + PreloadArchive *pa; + FSINode *n; + BOOL is_archive; + char fname[1024]; + + pe = NULL; + pa = NULL; + for(;;) { + while (isspace_nolf(*p)) + p++; + if (*p == '\n') { + pe = NULL; + p++; + continue; + } + if (*p == '#') + continue; /* comment */ + if (*p == '\0') + break; + + is_archive = FALSE; + if (*p == '@') { + is_archive = TRUE; + p++; + } + if (parse_fname(fname, sizeof(fname), &p) < 0) { + fprintf(stderr, "invalid filename\n"); + return; + } + while (isspace_nolf(*p)) + p++; + if (*p == ':') { + p++; + // printf("preload file='%s' archive=%d\n", fname, is_archive); + n = inode_search_path(fs1, fname); + pe = NULL; + pa = NULL; + if (!n || n->type != FT_REG || n->u.reg.state == REG_STATE_LOCAL) { + fprintf(stderr, "invalid preload file: '%s'\n", fname); + while (*p != '\n' && *p != '\0') + p++; + } else if (is_archive) { + pa = mallocz(sizeof(*pa)); + pa->name = strdup(fname); + init_list_head(&pa->file_list); + list_add_tail(&pa->link, &fs->preload_archive_list); + } else { + pe = mallocz(sizeof(*pe)); + pe->file_id = n->u.reg.file_id; + init_list_head(&pe->file_list); + list_add_tail(&pe->link, &fs->preload_list); + } + } else { + if (!pe && !pa) { + fprintf(stderr, "filename without target: %s\n", fname); + return; + } + if (pa) { + PreloadArchiveFile *paf; + FSFileID file_id; + uint64_t size; + + if (parse_uint64(&size, &p) < 0) { + fprintf(stderr, "invalid size\n"); + return; + } + + if (parse_file_id(&file_id, &p) < 0) { + fprintf(stderr, "invalid file id\n"); + return; + } + + paf = mallocz(sizeof(*paf)); + paf->name = strdup(fname); + paf->file_id = file_id; + paf->size = size; + list_add_tail(&paf->link, &pa->file_list); + } else { + PreloadFile *pf; + pf = mallocz(sizeof(*pf)); + pf->name = strdup(fname); + pf->is_archive = is_archive; + list_add_tail(&pf->link, &pe->file_list); + } + } + /* skip the rest of the line */ + while (*p != '\n' && *p != '\0') + p++; + if (*p == '\n') + p++; + } +} + +static int preload_parse(FSDevice *fs, const char *fname, BOOL is_new) +{ + FSINode *n; + char *buf; + size_t size; + + n = inode_search_path(fs, fname); + if (!n || n->type != FT_REG || n->u.reg.state != REG_STATE_LOADED) + return -1; + /* transform to zero terminated string */ + size = n->u.reg.size; + buf = malloc(size + 1); + file_buffer_read(&n->u.reg.fbuf, 0, (uint8_t *)buf, size); + buf[size] = '\0'; + if (is_new) + preload_parse_str(fs, buf); + else + preload_parse_str_old(fs, buf); + free(buf); + return 0; +} + + + +/************************************************************/ +/* FS user interface */ + +typedef struct CmdXHRState { + FSFile *req_fd; + FSFile *root_fd; + FSFile *fd; + FSFile *post_fd; + AES_KEY aes_state; +} CmdXHRState; + +static void fs_cmd_xhr_on_load(FSDevice *fs, FSFile *f, int64_t size, + void *opaque); + +static int parse_hex_buf(uint8_t *buf, int buf_size, const char **pp) +{ + char buf1[1024]; + int len; + + if (parse_fname(buf1, sizeof(buf1), pp) < 0) + return -1; + len = strlen(buf1); + if ((len & 1) != 0) + return -1; + len >>= 1; + if (len > buf_size) + return -1; + if (decode_hex(buf, buf1, len) < 0) + return -1; + return len; +} + +static int fs_cmd_xhr(FSDevice *fs, FSFile *f, + const char *p, uint32_t uid, uint32_t gid) +{ + char url[1024], post_filename[1024], filename[1024]; + char user_buf[128], *user; + char password_buf[128], *password; + FSQID qid; + FSFile *fd, *root_fd, *post_fd; + uint64_t post_data_len; + int err, aes_key_len; + CmdXHRState *s; + char *name; + AES_KEY *paes_state; + uint8_t aes_key[FS_KEY_LEN]; + uint32_t flags; + FSCMDRequest *req; + + /* a request is already done or in progress */ + if (f->req != NULL) + return -P9_EIO; + + if (parse_fname(url, sizeof(url), &p) < 0) + goto fail; + if (parse_fname(user_buf, sizeof(user_buf), &p) < 0) + goto fail; + if (parse_fname(password_buf, sizeof(password_buf), &p) < 0) + goto fail; + if (parse_fname(post_filename, sizeof(post_filename), &p) < 0) + goto fail; + if (parse_fname(filename, sizeof(filename), &p) < 0) + goto fail; + aes_key_len = parse_hex_buf(aes_key, FS_KEY_LEN, &p); + if (aes_key_len < 0) + goto fail; + if (parse_uint32(&flags, &p) < 0) + goto fail; + if (aes_key_len != 0 && aes_key_len != FS_KEY_LEN) + goto fail; + + if (user_buf[0] != '\0') + user = user_buf; + else + user = NULL; + if (password_buf[0] != '\0') + password = password_buf; + else + password = NULL; + + // printf("url='%s' '%s' '%s' filename='%s'\n", url, user, password, filename); + assert(!fs->fs_attach(fs, &root_fd, &qid, uid, "", "")); + post_fd = NULL; + + fd = fs_walk_path1(fs, root_fd, filename, &name); + if (!fd) { + err = -P9_ENOENT; + goto fail1; + } + /* XXX: until fs_create is fixed */ + fs->fs_unlinkat(fs, fd, name); + + err = fs->fs_create(fs, &qid, fd, name, + P9_O_RDWR | P9_O_TRUNC, 0600, gid); + if (err < 0) { + goto fail1; + } + + if (post_filename[0] != '\0') { + FSINode *n; + + post_fd = fs_walk_path(fs, root_fd, post_filename); + if (!post_fd) { + err = -P9_ENOENT; + goto fail1; + } + err = fs->fs_open(fs, &qid, post_fd, P9_O_RDONLY, NULL, NULL); + if (err < 0) + goto fail1; + n = post_fd->inode; + assert(n->type == FT_REG && n->u.reg.state == REG_STATE_LOCAL); + post_data_len = n->u.reg.size; + } else { + post_data_len = 0; + } + + s = mallocz(sizeof(*s)); + s->root_fd = root_fd; + s->fd = fd; + s->post_fd = post_fd; + if (aes_key_len != 0) { + AES_set_decrypt_key(aes_key, FS_KEY_LEN * 8, &s->aes_state); + paes_state = &s->aes_state; + } else { + paes_state = NULL; + } + + req = mallocz(sizeof(*req)); + req->type = FS_CMD_XHR; + req->reply_len = 0; + req->xhr_state = s; + s->req_fd = f; + f->req = req; + + fs_wget_file2(fs, fd, url, user, password, post_fd, post_data_len, + fs_cmd_xhr_on_load, s, paes_state); + return 0; + fail1: + if (fd) + fs->fs_delete(fs, fd); + if (post_fd) + fs->fs_delete(fs, post_fd); + fs->fs_delete(fs, root_fd); + return err; + fail: + return -P9_EIO; +} + +static void fs_cmd_xhr_on_load(FSDevice *fs, FSFile *f, int64_t size, + void *opaque) +{ + CmdXHRState *s = opaque; + FSCMDRequest *req; + int ret; + + // printf("fs_cmd_xhr_on_load: size=%d\n", (int)size); + + if (s->fd) + fs->fs_delete(fs, s->fd); + if (s->post_fd) + fs->fs_delete(fs, s->post_fd); + fs->fs_delete(fs, s->root_fd); + + if (s->req_fd) { + req = s->req_fd->req; + if (size < 0) { + ret = size; + } else { + ret = 0; + } + put_le32(req->reply_buf, ret); + req->reply_len = sizeof(ret); + req->xhr_state = NULL; + } + free(s); +} + +static int fs_cmd_set_base_url(FSDevice *fs, const char *p) +{ + // FSDeviceMem *fs1 = (FSDeviceMem *)fs; + char url[1024], base_url_id[1024]; + char user_buf[128], *user; + char password_buf[128], *password; + AES_KEY aes_state, *paes_state; + uint8_t aes_key[FS_KEY_LEN]; + int aes_key_len; + + if (parse_fname(base_url_id, sizeof(base_url_id), &p) < 0) + goto fail; + if (parse_fname(url, sizeof(url), &p) < 0) + goto fail; + if (parse_fname(user_buf, sizeof(user_buf), &p) < 0) + goto fail; + if (parse_fname(password_buf, sizeof(password_buf), &p) < 0) + goto fail; + aes_key_len = parse_hex_buf(aes_key, FS_KEY_LEN, &p); + if (aes_key_len < 0) + goto fail; + + if (user_buf[0] != '\0') + user = user_buf; + else + user = NULL; + if (password_buf[0] != '\0') + password = password_buf; + else + password = NULL; + + if (aes_key_len != 0) { + if (aes_key_len != FS_KEY_LEN) + goto fail; + AES_set_decrypt_key(aes_key, FS_KEY_LEN * 8, &aes_state); + paes_state = &aes_state; + } else { + paes_state = NULL; + } + + fs_net_set_base_url(fs, base_url_id, url, user, password, + paes_state); + return 0; + fail: + return -P9_EINVAL; +} + +static int fs_cmd_reset_base_url(FSDevice *fs, const char *p) +{ + char base_url_id[1024]; + + if (parse_fname(base_url_id, sizeof(base_url_id), &p) < 0) + goto fail; + fs_net_reset_base_url(fs, base_url_id); + return 0; + fail: + return -P9_EINVAL; +} + +static int fs_cmd_set_url(FSDevice *fs, const char *p) +{ + char base_url_id[1024]; + char filename[1024]; + FSFileID file_id; + uint64_t size; + FSINode *n; + + if (parse_fname(filename, sizeof(filename), &p) < 0) + goto fail; + if (parse_fname(base_url_id, sizeof(base_url_id), &p) < 0) + goto fail; + if (parse_file_id(&file_id, &p) < 0) + goto fail; + if (parse_uint64(&size, &p) < 0) + goto fail; + + n = inode_search_path(fs, filename); + if (!n) { + return -P9_ENOENT; + } + return fs_net_set_url(fs, n, base_url_id, file_id, size); + fail: + return -P9_EINVAL; +} + +static int fs_cmd_export_file(FSDevice *fs, const char *p) +{ + char filename[1024]; + FSINode *n; + const char *name; + uint8_t *buf; + + if (parse_fname(filename, sizeof(filename), &p) < 0) + goto fail; + n = inode_search_path(fs, filename); + if (!n) + return -P9_ENOENT; + if (n->type != FT_REG || + (n->u.reg.state != REG_STATE_LOCAL && + n->u.reg.state != REG_STATE_LOADED)) + goto fail; + name = strrchr(filename, '/'); + if (name) + name++; + else + name = filename; + /* XXX: pass the buffer to JS to avoid the allocation */ + buf = malloc(n->u.reg.size); + file_buffer_read(&n->u.reg.fbuf, 0, buf, n->u.reg.size); + fs_export_file(name, buf, n->u.reg.size); + free(buf); + return 0; + fail: + return -P9_EIO; +} + +/* PBKDF2 crypto acceleration */ +static int fs_cmd_pbkdf2(FSDevice *fs, FSFile *f, const char *p) +{ + uint8_t pwd[1024]; + uint8_t salt[128]; + uint32_t iter, key_len; + int pwd_len, salt_len; + FSCMDRequest *req; + + /* a request is already done or in progress */ + if (f->req != NULL) + return -P9_EIO; + + pwd_len = parse_hex_buf(pwd, sizeof(pwd), &p); + if (pwd_len < 0) + goto fail; + salt_len = parse_hex_buf(salt, sizeof(salt), &p); + if (pwd_len < 0) + goto fail; + if (parse_uint32(&iter, &p) < 0) + goto fail; + if (parse_uint32(&key_len, &p) < 0) + goto fail; + if (key_len > FS_CMD_REPLY_LEN_MAX || + key_len == 0) + goto fail; + req = mallocz(sizeof(*req)); + req->type = FS_CMD_PBKDF2; + req->reply_len = key_len; + pbkdf2_hmac_sha256(pwd, pwd_len, salt, salt_len, iter, key_len, + req->reply_buf); + f->req = req; + return 0; + fail: + return -P9_EINVAL; +} + +static int fs_cmd_set_import_dir(FSDevice *fs, FSFile *f, const char *p) +{ + FSDeviceMem *fs1 = (FSDeviceMem *)fs; + char filename[1024]; + + if (parse_fname(filename, sizeof(filename), &p) < 0) + return -P9_EINVAL; + free(fs1->import_dir); + fs1->import_dir = strdup(filename); + return 0; +} + +static int fs_cmd_write(FSDevice *fs, FSFile *f, uint64_t offset, + const uint8_t *buf, int buf_len) +{ + char *buf1; + const char *p; + char cmd[64]; + int err; + + /* transform into a string */ + buf1 = malloc(buf_len + 1); + memcpy(buf1, buf, buf_len); + buf1[buf_len] = '\0'; + + err = 0; + p = buf1; + if (parse_fname(cmd, sizeof(cmd), &p) < 0) + goto fail; + if (!strcmp(cmd, "xhr")) { + err = fs_cmd_xhr(fs, f, p, f->uid, 0); + } else if (!strcmp(cmd, "set_base_url")) { + err = fs_cmd_set_base_url(fs, p); + } else if (!strcmp(cmd, "reset_base_url")) { + err = fs_cmd_reset_base_url(fs, p); + } else if (!strcmp(cmd, "set_url")) { + err = fs_cmd_set_url(fs, p); + } else if (!strcmp(cmd, "export_file")) { + err = fs_cmd_export_file(fs, p); + } else if (!strcmp(cmd, "pbkdf2")) { + err = fs_cmd_pbkdf2(fs, f, p); + } else if (!strcmp(cmd, "set_import_dir")) { + err = fs_cmd_set_import_dir(fs, f, p); + } else { + printf("unknown command: '%s'\n", cmd); + fail: + err = -P9_EIO; + } + free(buf1); + if (err == 0) + return buf_len; + else + return err; +} + +static int fs_cmd_read(FSDevice *fs, FSFile *f, uint64_t offset, + uint8_t *buf, int buf_len) +{ + FSCMDRequest *req; + int l; + + req = f->req; + if (!req) + return -P9_EIO; + l = min_int(req->reply_len, buf_len); + memcpy(buf, req->reply_buf, l); + return l; +} + +static void fs_cmd_close(FSDevice *fs, FSFile *f) +{ + FSCMDRequest *req; + req = f->req; + + if (req) { + if (req->xhr_state) { + req->xhr_state->req_fd = NULL; + } + free(req); + f->req = NULL; + } +} + +/* Create a .fscmd_pwd file to avoid passing the password thru the + Linux command line */ +void fs_net_set_pwd(FSDevice *fs, const char *pwd) +{ + FSFile *root_fd; + FSQID qid; + + assert(fs_is_net(fs)); + + assert(!fs->fs_attach(fs, &root_fd, &qid, 0, "", "")); + assert(!fs->fs_create(fs, &qid, root_fd, ".fscmd_pwd", P9_O_RDWR | P9_O_TRUNC, + 0600, 0)); + fs->fs_write(fs, root_fd, 0, (uint8_t *)pwd, strlen(pwd)); + fs->fs_delete(fs, root_fd); +} + +/* external file import */ + +#ifdef EMSCRIPTEN + +void fs_import_file(const char *filename, uint8_t *buf, int buf_len) +{ + FSDevice *fs; + FSDeviceMem *fs1; + FSFile *fd, *root_fd; + FSQID qid; + + // printf("importing file: %s len=%d\n", filename, buf_len); + fs = fs_import_fs; + if (!fs) { + free(buf); + return; + } + + assert(!fs->fs_attach(fs, &root_fd, &qid, 1000, "", "")); + fs1 = (FSDeviceMem *)fs; + fd = fs_walk_path(fs, root_fd, fs1->import_dir); + if (!fd) + goto fail; + fs_unlinkat(fs, root_fd, filename); + if (fs->fs_create(fs, &qid, fd, filename, P9_O_RDWR | P9_O_TRUNC, + 0600, 0) < 0) + goto fail; + fs->fs_write(fs, fd, 0, buf, buf_len); + fail: + if (fd) + fs->fs_delete(fs, fd); + if (root_fd) + fs->fs_delete(fs, root_fd); + free(buf); +} + +#else + +void fs_export_file(const char *filename, + const uint8_t *buf, int buf_len) +{ +} + +#endif diff --git a/fs_utils.c b/fs_utils.c new file mode 100644 index 0000000..37f399d --- /dev/null +++ b/fs_utils.c @@ -0,0 +1,370 @@ +/* + * Misc FS utilities + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "list.h" +#include "fs_utils.h" + +/* last byte is the version */ +const uint8_t encrypted_file_magic[4] = { 0xfb, 0xa2, 0xe9, 0x01 }; + +char *compose_path(const char *path, const char *name) +{ + int path_len, name_len; + char *d, *q; + + if (path[0] == '\0') { + d = strdup(name); + } else { + path_len = strlen(path); + name_len = strlen(name); + d = malloc(path_len + 1 + name_len + 1); + q = d; + memcpy(q, path, path_len); + q += path_len; + if (path[path_len - 1] != '/') + *q++ = '/'; + memcpy(q, name, name_len + 1); + } + return d; +} + +char *compose_url(const char *base_url, const char *name) +{ + if (strchr(name, ':')) { + return strdup(name); + } else { + return compose_path(base_url, name); + } +} + +void skip_line(const char **pp) +{ + const char *p; + p = *pp; + while (*p != '\n' && *p != '\0') + p++; + if (*p == '\n') + p++; + *pp = p; +} + +char *quoted_str(const char *str) +{ + const char *s; + char *q; + int c; + char *buf; + + if (str[0] == '\0') + goto use_quote; + s = str; + while (*s != '\0') { + if (*s <= ' ' || *s > '~') + goto use_quote; + s++; + } + return strdup(str); + use_quote: + buf = malloc(strlen(str) * 4 + 2 + 1); + q = buf; + s = str; + *q++ = '"'; + while (*s != '\0') { + c = *(uint8_t *)s; + if (c < ' ' || c == 127) { + q += sprintf(q, "\\x%02x", c); + } else if (c == '\\' || c == '\"') { + q += sprintf(q, "\\%c", c); + } else { + *q++ = c; + } + s++; + } + *q++ = '"'; + *q = '\0'; + return buf; +} + +int parse_fname(char *buf, int buf_size, const char **pp) +{ + const char *p; + char *q; + int c, h; + + p = *pp; + while (isspace_nolf(*p)) + p++; + if (*p == '\0') + return -1; + q = buf; + if (*p == '"') { + p++; + for(;;) { + c = *p++; + if (c == '\0' || c == '\n') { + return -1; + } else if (c == '\"') { + break; + } else if (c == '\\') { + c = *p++; + switch(c) { + case '\'': + case '\"': + case '\\': + goto add_char; + case 'n': + c = '\n'; + goto add_char; + case 'r': + c = '\r'; + goto add_char; + case 't': + c = '\t'; + goto add_char; + case 'x': + h = from_hex(*p++); + if (h < 0) + return -1; + c = h << 4; + h = from_hex(*p++); + if (h < 0) + return -1; + c |= h; + goto add_char; + default: + return -1; + } + } else { + add_char: + if (q >= buf + buf_size - 1) + return -1; + *q++ = c; + } + } + } else { + while (!isspace_nolf(*p) && *p != '\0' && *p != '\n') { + if (q >= buf + buf_size - 1) + return -1; + *q++ = *p++; + } + } + *q = '\0'; + *pp = p; + return 0; +} + +int parse_uint32_base(uint32_t *pval, const char **pp, int base) +{ + const char *p, *p1; + p = *pp; + while (isspace_nolf(*p)) + p++; + *pval = strtoul(p, (char **)&p1, base); + if (p1 == p) + return -1; + *pp = p1; + return 0; +} + +int parse_uint64_base(uint64_t *pval, const char **pp, int base) +{ + const char *p, *p1; + p = *pp; + while (isspace_nolf(*p)) + p++; + *pval = strtoull(p, (char **)&p1, base); + if (p1 == p) + return -1; + *pp = p1; + return 0; +} + +int parse_uint64(uint64_t *pval, const char **pp) +{ + return parse_uint64_base(pval, pp, 0); +} + +int parse_uint32(uint32_t *pval, const char **pp) +{ + return parse_uint32_base(pval, pp, 0); +} + +int parse_time(uint32_t *psec, uint32_t *pnsec, const char **pp) +{ + const char *p; + uint32_t v, m; + p = *pp; + if (parse_uint32(psec, &p) < 0) + return -1; + v = 0; + if (*p == '.') { + p++; + /* XXX: inefficient */ + m = 1000000000; + v = 0; + while (*p >= '0' && *p <= '9') { + m /= 10; + v += (*p - '0') * m; + p++; + } + } + *pnsec = v; + *pp = p; + return 0; +} + +int parse_file_id(FSFileID *pval, const char **pp) +{ + return parse_uint64_base(pval, pp, 16); +} + +char *file_id_to_filename(char *buf, FSFileID file_id) +{ + sprintf(buf, "%016" PRIx64, file_id); + return buf; +} + +void encode_hex(char *str, const uint8_t *buf, int len) +{ + int i; + for(i = 0; i < len; i++) + sprintf(str + 2 * i, "%02x", buf[i]); +} + +int decode_hex(uint8_t *buf, const char *str, int len) +{ + int h0, h1, i; + + for(i = 0; i < len; i++) { + h0 = from_hex(str[2 * i]); + if (h0 < 0) + return -1; + h1 = from_hex(str[2 * i + 1]); + if (h1 < 0) + return -1; + buf[i] = (h0 << 4) | h1; + } + return 0; +} + +/* return NULL if no end of header found */ +const char *skip_header(const char *p) +{ + p = strstr(p, "\n\n"); + if (!p) + return NULL; + return p + 2; +} + +/* return 0 if OK, < 0 if error */ +int parse_tag(char *buf, int buf_size, const char *str, const char *tag) +{ + char tagname[128], *q; + const char *p, *p1; + int len; + + p = str; + for(;;) { + if (*p == '\0' || *p == '\n') + break; + q = tagname; + while (*p != ':' && *p != '\n' && *p != '\0') { + if ((q - tagname) < sizeof(tagname) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + if (*p != ':') + return -1; + p++; + while (isspace_nolf(*p)) + p++; + p1 = p; + p = strchr(p, '\n'); + if (!p) + len = strlen(p1); + else + len = p - p1; + if (!strcmp(tagname, tag)) { + if (len > buf_size - 1) + len = buf_size - 1; + memcpy(buf, p1, len); + buf[len] = '\0'; + return 0; + } + if (!p) + break; + else + p++; + } + return -1; +} + +int parse_tag_uint64(uint64_t *pval, const char *str, const char *tag) +{ + char buf[64]; + const char *p; + if (parse_tag(buf, sizeof(buf), str, tag)) + return -1; + p = buf; + return parse_uint64(pval, &p); +} + +int parse_tag_file_id(FSFileID *pval, const char *str, const char *tag) +{ + char buf[64]; + const char *p; + if (parse_tag(buf, sizeof(buf), str, tag)) + return -1; + p = buf; + return parse_uint64_base(pval, &p, 16); +} + +int parse_tag_version(const char *str) +{ + uint64_t version; + if (parse_tag_uint64(&version, str, "Version")) + return -1; + return version; +} + +BOOL is_url(const char *path) +{ + return (strstart(path, "http:", NULL) || + strstart(path, "https:", NULL) || + strstart(path, "file:", NULL)); +} diff --git a/fs_utils.h b/fs_utils.h new file mode 100644 index 0000000..4e4b9cd --- /dev/null +++ b/fs_utils.h @@ -0,0 +1,95 @@ +/* + * Misc FS utilities + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#define HEAD_FILENAME "head" +#define ROOT_FILENAME "files" + +#define FILEID_SIZE_MAX 32 + +#define FS_KEY_LEN 16 + +/* default block size to determine the total filesytem size */ +#define FS_BLOCK_SIZE_LOG2 12 +#define FS_BLOCK_SIZE (1 << FS_BLOCK_SIZE_LOG2) + +typedef enum { + FS_ERR_OK = 0, + FS_ERR_GENERIC = -1, + FS_ERR_SYNTAX = -2, + FS_ERR_REVISION = -3, + FS_ERR_FILE_ID = -4, + FS_ERR_IO = -5, + FS_ERR_NOENT = -6, + FS_ERR_COUNTERS = -7, + FS_ERR_QUOTA = -8, + FS_ERR_PROTOCOL_VERSION = -9, + FS_ERR_HEAD = -10, +} FSCommitErrorCode; + +typedef uint64_t FSFileID; + +static inline BOOL isspace_nolf(int c) +{ + return (c == ' ' || c == '\t'); +} + +static inline int from_hex(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else + return -1; +} + +static inline uint64_t block_align(uint64_t val, uint64_t align) +{ + return (val + align - 1) & ~(align - 1); +} + +void pstrcpy(char *buf, int buf_size, const char *str); +char *pstrcat(char *buf, int buf_size, const char *s); +char *compose_path(const char *path, const char *name); +char *compose_url(const char *base_url, const char *name); +void skip_line(const char **pp); +char *quoted_str(const char *str); +int parse_fname(char *buf, int buf_size, const char **pp); +int parse_uint32_base(uint32_t *pval, const char **pp, int base); +int parse_uint64_base(uint64_t *pval, const char **pp, int base); +int parse_uint64(uint64_t *pval, const char **pp); +int parse_uint32(uint32_t *pval, const char **pp); +int parse_time(uint32_t *psec, uint32_t *pnsec, const char **pp); +int parse_file_id(FSFileID *pval, const char **pp); +char *file_id_to_filename(char *buf, FSFileID file_id); +void encode_hex(char *str, const uint8_t *buf, int len); +int decode_hex(uint8_t *buf, const char *str, int len); +BOOL is_url(const char *path); + +const char *skip_header(const char *p); +int parse_tag(char *buf, int buf_size, const char *str, const char *tag); +int parse_tag_uint64(uint64_t *pval, const char *str, const char *tag); +int parse_tag_file_id(FSFileID *pval, const char *str, const char *tag); +int parse_tag_version(const char *str); diff --git a/fs_wget.c b/fs_wget.c new file mode 100644 index 0000000..b4857b0 --- /dev/null +++ b/fs_wget.c @@ -0,0 +1,625 @@ +/* + * HTTP file download + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "list.h" +#include "fs.h" +#include "fs_utils.h" +#include "fs_wget.h" + +#if defined(EMSCRIPTEN) +#include +#else +#include +#endif + +/***********************************************/ +/* HTTP get */ + +#ifdef EMSCRIPTEN + +struct XHRState { + void *opaque; + WGetWriteCallback *cb; +}; + +static int downloading_count; + +void fs_wget_init(void) +{ +} + +extern void fs_wget_update_downloading(int flag); + +static void fs_wget_update_downloading_count(int incr) +{ + int prev_state, state; + prev_state = (downloading_count > 0); + downloading_count += incr; + state = (downloading_count > 0); + if (prev_state != state) + fs_wget_update_downloading(state); +} + +static void fs_wget_onerror(unsigned int handle, void *opaque, int status, + const char *status_text) +{ + XHRState *s = opaque; + if (status <= 0) + status = -404; /* HTTP not found error */ + else + status = -status; + fs_wget_update_downloading_count(-1); + if (s->cb) + s->cb(s->opaque, status, NULL, 0); +} + +static void fs_wget_onload(unsigned int handle, + void *opaque, void *data, unsigned int size) +{ + XHRState *s = opaque; + fs_wget_update_downloading_count(-1); + if (s->cb) + s->cb(s->opaque, 0, data, size); +} + +extern int emscripten_async_wget3_data(const char* url, const char* requesttype, const char *user, const char *password, const uint8_t *post_data, int post_data_len, void *arg, int free, em_async_wget2_data_onload_func onload, em_async_wget2_data_onerror_func onerror, em_async_wget2_data_onprogress_func onprogress); + +XHRState *fs_wget2(const char *url, const char *user, const char *password, + WGetReadCallback *read_cb, uint64_t post_data_len, + void *opaque, WGetWriteCallback *cb, BOOL single_write) +{ + XHRState *s; + const char *request; + uint8_t *post_data; + + s = mallocz(sizeof(*s)); + s->opaque = opaque; + s->cb = cb; + + if (post_data_len != 0) { + request = "POST"; + post_data = malloc(post_data_len); + read_cb(opaque, post_data, post_data_len); + } else { + request = "GET"; + post_data = NULL; + } + fs_wget_update_downloading_count(1); + + emscripten_async_wget3_data(url, request, user, password, + post_data, post_data_len, s, 1, fs_wget_onload, + fs_wget_onerror, NULL); + if (post_data_len != 0) + free(post_data); + return s; +} + +void fs_wget_free(XHRState *s) +{ + s->cb = NULL; + s->opaque = NULL; +} + +#else + +struct XHRState { + struct list_head link; + CURL *eh; + void *opaque; + WGetWriteCallback *write_cb; + WGetReadCallback *read_cb; + + BOOL single_write; + DynBuf dbuf; /* used if single_write */ +}; + +typedef struct { + struct list_head link; + int64_t timeout; + void (*cb)(void *opaque); + void *opaque; +} AsyncCallState; + +static CURLM *curl_multi_ctx; +static struct list_head xhr_list; /* list of XHRState.link */ + +void fs_wget_init(void) +{ + if (curl_multi_ctx) + return; + curl_global_init(CURL_GLOBAL_ALL); + curl_multi_ctx = curl_multi_init(); + init_list_head(&xhr_list); +} + +void fs_wget_end(void) +{ + curl_multi_cleanup(curl_multi_ctx); + curl_global_cleanup(); +} + +static size_t fs_wget_write_cb(char *ptr, size_t size, size_t nmemb, + void *userdata) +{ + XHRState *s = userdata; + size *= nmemb; + + if (s->single_write) { + dbuf_write(&s->dbuf, s->dbuf.size, (void *)ptr, size); + } else { + s->write_cb(s->opaque, 1, ptr, size); + } + return size; +} + +static size_t fs_wget_read_cb(char *ptr, size_t size, size_t nmemb, + void *userdata) +{ + XHRState *s = userdata; + size *= nmemb; + return s->read_cb(s->opaque, ptr, size); +} + +XHRState *fs_wget2(const char *url, const char *user, const char *password, + WGetReadCallback *read_cb, uint64_t post_data_len, + void *opaque, WGetWriteCallback *write_cb, BOOL single_write) +{ + XHRState *s; + s = mallocz(sizeof(*s)); + s->eh = curl_easy_init(); + s->opaque = opaque; + s->write_cb = write_cb; + s->read_cb = read_cb; + s->single_write = single_write; + dbuf_init(&s->dbuf); + + curl_easy_setopt(s->eh, CURLOPT_PRIVATE, s); + curl_easy_setopt(s->eh, CURLOPT_WRITEDATA, s); + curl_easy_setopt(s->eh, CURLOPT_WRITEFUNCTION, fs_wget_write_cb); + curl_easy_setopt(s->eh, CURLOPT_HEADER, 0); + curl_easy_setopt(s->eh, CURLOPT_URL, url); + curl_easy_setopt(s->eh, CURLOPT_VERBOSE, 0L); + curl_easy_setopt(s->eh, CURLOPT_ACCEPT_ENCODING, ""); + if (user) { + curl_easy_setopt(s->eh, CURLOPT_USERNAME, user); + curl_easy_setopt(s->eh, CURLOPT_PASSWORD, password); + } + if (post_data_len != 0) { + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, + "Content-Type: application/octet-stream"); + curl_easy_setopt(s->eh, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(s->eh, CURLOPT_POST, 1L); + curl_easy_setopt(s->eh, CURLOPT_POSTFIELDSIZE_LARGE, + (curl_off_t)post_data_len); + curl_easy_setopt(s->eh, CURLOPT_READDATA, s); + curl_easy_setopt(s->eh, CURLOPT_READFUNCTION, fs_wget_read_cb); + } + curl_multi_add_handle(curl_multi_ctx, s->eh); + list_add_tail(&s->link, &xhr_list); + return s; +} + +void fs_wget_free(XHRState *s) +{ + dbuf_free(&s->dbuf); + curl_easy_cleanup(s->eh); + list_del(&s->link); + free(s); +} + +/* timeout is in ms */ +void fs_net_set_fdset(int *pfd_max, fd_set *rfds, fd_set *wfds, fd_set *efds, + int *ptimeout) +{ + long timeout; + int n, fd_max; + CURLMsg *msg; + + if (!curl_multi_ctx) + return; + + curl_multi_perform(curl_multi_ctx, &n); + + for(;;) { + msg = curl_multi_info_read(curl_multi_ctx, &n); + if (!msg) + break; + if (msg->msg == CURLMSG_DONE) { + XHRState *s; + long http_code; + + curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char **)&s); + curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, + &http_code); + /* signal the end of the transfer or error */ + if (http_code == 200) { + if (s->single_write) { + s->write_cb(s->opaque, 0, s->dbuf.buf, s->dbuf.size); + } else { + s->write_cb(s->opaque, 0, NULL, 0); + } + } else { + s->write_cb(s->opaque, -http_code, NULL, 0); + } + curl_multi_remove_handle(curl_multi_ctx, s->eh); + curl_easy_cleanup(s->eh); + dbuf_free(&s->dbuf); + list_del(&s->link); + free(s); + } + } + + curl_multi_fdset(curl_multi_ctx, rfds, wfds, efds, &fd_max); + *pfd_max = max_int(*pfd_max, fd_max); + curl_multi_timeout(curl_multi_ctx, &timeout); + if (timeout >= 0) + *ptimeout = min_int(*ptimeout, timeout); +} + +void fs_net_event_loop(FSNetEventLoopCompletionFunc *cb, void *opaque) +{ + fd_set rfds, wfds, efds; + int timeout, fd_max; + struct timeval tv; + + if (!curl_multi_ctx) + return; + + for(;;) { + fd_max = -1; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + timeout = 10000; + fs_net_set_fdset(&fd_max, &rfds, &wfds, &efds, &timeout); + if (cb) { + if (cb(opaque)) + break; + } else { + if (list_empty(&xhr_list)) + break; + } + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + select(fd_max + 1, &rfds, &wfds, &efds, &tv); + } +} + +#endif /* !EMSCRIPTEN */ + +XHRState *fs_wget(const char *url, const char *user, const char *password, + void *opaque, WGetWriteCallback *cb, BOOL single_write) +{ + return fs_wget2(url, user, password, NULL, 0, opaque, cb, single_write); +} + +/***********************************************/ +/* file decryption */ + +#define ENCRYPTED_FILE_HEADER_SIZE (4 + AES_BLOCK_SIZE) + +#define DEC_BUF_SIZE (256 * AES_BLOCK_SIZE) + +struct DecryptFileState { + DecryptFileCB *write_cb; + void *opaque; + int dec_state; + int dec_buf_pos; + AES_KEY *aes_state; + uint8_t iv[AES_BLOCK_SIZE]; + uint8_t dec_buf[DEC_BUF_SIZE]; +}; + +DecryptFileState *decrypt_file_init(AES_KEY *aes_state, + DecryptFileCB *write_cb, + void *opaque) +{ + DecryptFileState *s; + s = mallocz(sizeof(*s)); + s->write_cb = write_cb; + s->opaque = opaque; + s->aes_state = aes_state; + return s; +} + +int decrypt_file(DecryptFileState *s, const uint8_t *data, + size_t size) +{ + int l, len, ret; + + while (size != 0) { + switch(s->dec_state) { + case 0: + l = min_int(size, ENCRYPTED_FILE_HEADER_SIZE - s->dec_buf_pos); + memcpy(s->dec_buf + s->dec_buf_pos, data, l); + s->dec_buf_pos += l; + if (s->dec_buf_pos >= ENCRYPTED_FILE_HEADER_SIZE) { + if (memcmp(s->dec_buf, encrypted_file_magic, 4) != 0) + return -1; + memcpy(s->iv, s->dec_buf + 4, AES_BLOCK_SIZE); + s->dec_state = 1; + s->dec_buf_pos = 0; + } + break; + case 1: + l = min_int(size, DEC_BUF_SIZE - s->dec_buf_pos); + memcpy(s->dec_buf + s->dec_buf_pos, data, l); + s->dec_buf_pos += l; + if (s->dec_buf_pos >= DEC_BUF_SIZE) { + /* keep one block in case it is the padding */ + len = s->dec_buf_pos - AES_BLOCK_SIZE; + AES_cbc_encrypt(s->dec_buf, s->dec_buf, len, + s->aes_state, s->iv, FALSE); + ret = s->write_cb(s->opaque, s->dec_buf, len); + if (ret < 0) + return ret; + memcpy(s->dec_buf, s->dec_buf + s->dec_buf_pos - AES_BLOCK_SIZE, + AES_BLOCK_SIZE); + s->dec_buf_pos = AES_BLOCK_SIZE; + } + break; + default: + abort(); + } + data += l; + size -= l; + } + return 0; +} + +/* write last blocks */ +int decrypt_file_flush(DecryptFileState *s) +{ + int len, pad_len, ret; + + if (s->dec_state != 1) + return -1; + len = s->dec_buf_pos; + if (len == 0 || + (len % AES_BLOCK_SIZE) != 0) + return -1; + AES_cbc_encrypt(s->dec_buf, s->dec_buf, len, + s->aes_state, s->iv, FALSE); + pad_len = s->dec_buf[s->dec_buf_pos - 1]; + if (pad_len < 1 || pad_len > AES_BLOCK_SIZE) + return -1; + len -= pad_len; + if (len != 0) { + ret = s->write_cb(s->opaque, s->dec_buf, len); + if (ret < 0) + return ret; + } + return 0; +} + +void decrypt_file_end(DecryptFileState *s) +{ + free(s); +} + +/* XHR file */ + +typedef struct { + FSDevice *fs; + FSFile *f; + int64_t pos; + FSWGetFileCB *cb; + void *opaque; + FSFile *posted_file; + int64_t read_pos; + DecryptFileState *dec_state; +} FSWGetFileState; + +static int fs_wget_file_write_cb(void *opaque, const uint8_t *data, + size_t size) +{ + FSWGetFileState *s = opaque; + FSDevice *fs = s->fs; + int ret; + + ret = fs->fs_write(fs, s->f, s->pos, data, size); + if (ret < 0) + return ret; + s->pos += ret; + return ret; +} + +static void fs_wget_file_on_load(void *opaque, int err, void *data, size_t size) +{ + FSWGetFileState *s = opaque; + FSDevice *fs = s->fs; + int ret; + int64_t ret_size; + + // printf("err=%d size=%ld\n", err, size); + if (err < 0) { + ret_size = err; + goto done; + } else { + if (s->dec_state) { + ret = decrypt_file(s->dec_state, data, size); + if (ret >= 0 && err == 0) { + /* handle the end of file */ + decrypt_file_flush(s->dec_state); + } + } else { + ret = fs_wget_file_write_cb(s, data, size); + } + if (ret < 0) { + ret_size = ret; + goto done; + } else if (err == 0) { + /* end of transfer */ + ret_size = s->pos; + done: + s->cb(fs, s->f, ret_size, s->opaque); + if (s->dec_state) + decrypt_file_end(s->dec_state); + free(s); + } + } +} + +static size_t fs_wget_file_read_cb(void *opaque, void *data, size_t size) +{ + FSWGetFileState *s = opaque; + FSDevice *fs = s->fs; + int ret; + + if (!s->posted_file) + return 0; + ret = fs->fs_read(fs, s->posted_file, s->read_pos, data, size); + if (ret < 0) + return 0; + s->read_pos += ret; + return ret; +} + +void fs_wget_file2(FSDevice *fs, FSFile *f, const char *url, + const char *user, const char *password, + FSFile *posted_file, uint64_t post_data_len, + FSWGetFileCB *cb, void *opaque, + AES_KEY *aes_state) +{ + FSWGetFileState *s; + s = mallocz(sizeof(*s)); + s->fs = fs; + s->f = f; + s->pos = 0; + s->cb = cb; + s->opaque = opaque; + s->posted_file = posted_file; + s->read_pos = 0; + if (aes_state) { + s->dec_state = decrypt_file_init(aes_state, fs_wget_file_write_cb, s); + } + + fs_wget2(url, user, password, fs_wget_file_read_cb, post_data_len, + s, fs_wget_file_on_load, FALSE); +} + +/***********************************************/ +/* PBKDF2 */ + +#ifdef USE_BUILTIN_CRYPTO + +#define HMAC_BLOCK_SIZE 64 + +typedef struct { + SHA256_CTX ctx; + uint8_t K[HMAC_BLOCK_SIZE + SHA256_DIGEST_LENGTH]; +} HMAC_SHA256_CTX; + +void hmac_sha256_init(HMAC_SHA256_CTX *s, const uint8_t *key, int key_len) +{ + int i, l; + + if (key_len > HMAC_BLOCK_SIZE) { + SHA256(key, key_len, s->K); + l = SHA256_DIGEST_LENGTH; + } else { + memcpy(s->K, key, key_len); + l = key_len; + } + memset(s->K + l, 0, HMAC_BLOCK_SIZE - l); + for(i = 0; i < HMAC_BLOCK_SIZE; i++) + s->K[i] ^= 0x36; + SHA256_Init(&s->ctx); + SHA256_Update(&s->ctx, s->K, HMAC_BLOCK_SIZE); +} + +void hmac_sha256_update(HMAC_SHA256_CTX *s, const uint8_t *buf, int len) +{ + SHA256_Update(&s->ctx, buf, len); +} + +/* out has a length of SHA256_DIGEST_LENGTH */ +void hmac_sha256_final(HMAC_SHA256_CTX *s, uint8_t *out) +{ + int i; + + SHA256_Final(s->K + HMAC_BLOCK_SIZE, &s->ctx); + for(i = 0; i < HMAC_BLOCK_SIZE; i++) + s->K[i] ^= (0x36 ^ 0x5c); + SHA256(s->K, HMAC_BLOCK_SIZE + SHA256_DIGEST_LENGTH, out); +} + +#define SALT_LEN_MAX 32 + +void pbkdf2_hmac_sha256(const uint8_t *pwd, int pwd_len, + const uint8_t *salt, int salt_len, + int iter, int key_len, uint8_t *out) +{ + uint8_t F[SHA256_DIGEST_LENGTH], U[SALT_LEN_MAX + 4]; + HMAC_SHA256_CTX ctx; + int it, U_len, j, l; + uint32_t i; + + assert(salt_len <= SALT_LEN_MAX); + i = 1; + while (key_len > 0) { + memset(F, 0, SHA256_DIGEST_LENGTH); + memcpy(U, salt, salt_len); + U[salt_len] = i >> 24; + U[salt_len + 1] = i >> 16; + U[salt_len + 2] = i >> 8; + U[salt_len + 3] = i; + U_len = salt_len + 4; + for(it = 0; it < iter; it++) { + hmac_sha256_init(&ctx, pwd, pwd_len); + hmac_sha256_update(&ctx, U, U_len); + hmac_sha256_final(&ctx, U); + for(j = 0; j < SHA256_DIGEST_LENGTH; j++) + F[j] ^= U[j]; + U_len = SHA256_DIGEST_LENGTH; + } + l = min_int(key_len, SHA256_DIGEST_LENGTH); + memcpy(out, F, l); + out += l; + key_len -= l; + i++; + } +} + +#else + +void pbkdf2_hmac_sha256(const uint8_t *pwd, int pwd_len, + const uint8_t *salt, int salt_len, + int iter, int key_len, uint8_t *out) +{ + PKCS5_PBKDF2_HMAC((const char *)pwd, pwd_len, salt, salt_len, + iter, EVP_sha256(), key_len, out); +} + +#endif /* !USE_BUILTIN_CRYPTO */ diff --git a/fs_wget.h b/fs_wget.h new file mode 100644 index 0000000..35b6a4b --- /dev/null +++ b/fs_wget.h @@ -0,0 +1,93 @@ +/* + * HTTP file download + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#if defined(EMSCRIPTEN) +#define USE_BUILTIN_CRYPTO +#endif + +#ifdef USE_BUILTIN_CRYPTO +#include "aes.h" +#include "sha256.h" +#else +#include +#include +#include +#endif +#ifdef _WIN32 +#include +#endif + +#define LOG() printf("%s:%d\n", __func__, __LINE__) + +/* XHR */ + +/* err < 0: error (no data provided) + err = 0: end of transfer (data can be provided too) + err = 1: data chunk +*/ +typedef void WGetWriteCallback(void *opaque, int err, void *data, size_t size); +typedef size_t WGetReadCallback(void *opaque, void *data, size_t size); +typedef struct XHRState XHRState; + +XHRState *fs_wget(const char *url, const char *user, const char *password, + void *opaque, WGetWriteCallback *cb, BOOL single_write); +void fs_wget_free(XHRState *s); + +void fs_wget_init(void); +void fs_wget_end(void); + +#ifndef EMSCRIPTEN +typedef BOOL FSNetEventLoopCompletionFunc(void *opaque); +void fs_net_set_fdset(int *pfd_max, fd_set *rfds, fd_set *wfds, fd_set *efds, + int *ptimeout); +void fs_net_event_loop(FSNetEventLoopCompletionFunc *cb, void *opaque); +#endif + +/* crypto */ + +extern const uint8_t encrypted_file_magic[4]; + +typedef int DecryptFileCB(void *opaque, const uint8_t *data, size_t len); +typedef struct DecryptFileState DecryptFileState; + +DecryptFileState *decrypt_file_init(AES_KEY *aes_state, + DecryptFileCB *write_cb, + void *opaque); +int decrypt_file(DecryptFileState *s, const uint8_t *data, + size_t size); +int decrypt_file_flush(DecryptFileState *s); +void decrypt_file_end(DecryptFileState *s); + +void pbkdf2_hmac_sha256(const uint8_t *pwd, int pwd_len, + const uint8_t *salt, int salt_len, + int iter, int key_len, uint8_t *out); + +/* XHR file */ + +typedef void FSWGetFileCB(FSDevice *fs, FSFile *f, int64_t size, void *opaque); + +void fs_wget_file2(FSDevice *fs, FSFile *f, const char *url, + const char *user, const char *password, + FSFile *posted_file, uint64_t post_data_len, + FSWGetFileCB *cb, void *opaque, + AES_KEY *aes_state); diff --git a/ide.c b/ide.c new file mode 100644 index 0000000..4952965 --- /dev/null +++ b/ide.c @@ -0,0 +1,835 @@ +/* + * IDE emulation + * + * Copyright (c) 2003-2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "ide.h" + +//#define DEBUG_IDE + +/* Bits of HD_STATUS */ +#define ERR_STAT 0x01 +#define INDEX_STAT 0x02 +#define ECC_STAT 0x04 /* Corrected error */ +#define DRQ_STAT 0x08 +#define SEEK_STAT 0x10 +#define SRV_STAT 0x10 +#define WRERR_STAT 0x20 +#define READY_STAT 0x40 +#define BUSY_STAT 0x80 + +/* Bits for HD_ERROR */ +#define MARK_ERR 0x01 /* Bad address mark */ +#define TRK0_ERR 0x02 /* couldn't find track 0 */ +#define ABRT_ERR 0x04 /* Command aborted */ +#define MCR_ERR 0x08 /* media change request */ +#define ID_ERR 0x10 /* ID field not found */ +#define MC_ERR 0x20 /* media changed */ +#define ECC_ERR 0x40 /* Uncorrectable ECC error */ +#define BBD_ERR 0x80 /* pre-EIDE meaning: block marked bad */ +#define ICRC_ERR 0x80 /* new meaning: CRC error during transfer */ + +/* Bits of HD_NSECTOR */ +#define CD 0x01 +#define IO 0x02 +#define REL 0x04 +#define TAG_MASK 0xf8 + +#define IDE_CMD_RESET 0x04 +#define IDE_CMD_DISABLE_IRQ 0x02 + +/* ATA/ATAPI Commands pre T13 Spec */ +#define WIN_NOP 0x00 +/* + * 0x01->0x02 Reserved + */ +#define CFA_REQ_EXT_ERROR_CODE 0x03 /* CFA Request Extended Error Code */ +/* + * 0x04->0x07 Reserved + */ +#define WIN_SRST 0x08 /* ATAPI soft reset command */ +#define WIN_DEVICE_RESET 0x08 +/* + * 0x09->0x0F Reserved + */ +#define WIN_RECAL 0x10 +#define WIN_RESTORE WIN_RECAL +/* + * 0x10->0x1F Reserved + */ +#define WIN_READ 0x20 /* 28-Bit */ +#define WIN_READ_ONCE 0x21 /* 28-Bit without retries */ +#define WIN_READ_LONG 0x22 /* 28-Bit */ +#define WIN_READ_LONG_ONCE 0x23 /* 28-Bit without retries */ +#define WIN_READ_EXT 0x24 /* 48-Bit */ +#define WIN_READDMA_EXT 0x25 /* 48-Bit */ +#define WIN_READDMA_QUEUED_EXT 0x26 /* 48-Bit */ +#define WIN_READ_NATIVE_MAX_EXT 0x27 /* 48-Bit */ +/* + * 0x28 + */ +#define WIN_MULTREAD_EXT 0x29 /* 48-Bit */ +/* + * 0x2A->0x2F Reserved + */ +#define WIN_WRITE 0x30 /* 28-Bit */ +#define WIN_WRITE_ONCE 0x31 /* 28-Bit without retries */ +#define WIN_WRITE_LONG 0x32 /* 28-Bit */ +#define WIN_WRITE_LONG_ONCE 0x33 /* 28-Bit without retries */ +#define WIN_WRITE_EXT 0x34 /* 48-Bit */ +#define WIN_WRITEDMA_EXT 0x35 /* 48-Bit */ +#define WIN_WRITEDMA_QUEUED_EXT 0x36 /* 48-Bit */ +#define WIN_SET_MAX_EXT 0x37 /* 48-Bit */ +#define CFA_WRITE_SECT_WO_ERASE 0x38 /* CFA Write Sectors without erase */ +#define WIN_MULTWRITE_EXT 0x39 /* 48-Bit */ +/* + * 0x3A->0x3B Reserved + */ +#define WIN_WRITE_VERIFY 0x3C /* 28-Bit */ +/* + * 0x3D->0x3F Reserved + */ +#define WIN_VERIFY 0x40 /* 28-Bit - Read Verify Sectors */ +#define WIN_VERIFY_ONCE 0x41 /* 28-Bit - without retries */ +#define WIN_VERIFY_EXT 0x42 /* 48-Bit */ +/* + * 0x43->0x4F Reserved + */ +#define WIN_FORMAT 0x50 +/* + * 0x51->0x5F Reserved + */ +#define WIN_INIT 0x60 +/* + * 0x61->0x5F Reserved + */ +#define WIN_SEEK 0x70 /* 0x70-0x7F Reserved */ +#define CFA_TRANSLATE_SECTOR 0x87 /* CFA Translate Sector */ +#define WIN_DIAGNOSE 0x90 +#define WIN_SPECIFY 0x91 /* set drive geometry translation */ +#define WIN_DOWNLOAD_MICROCODE 0x92 +#define WIN_STANDBYNOW2 0x94 +#define WIN_STANDBY2 0x96 +#define WIN_SETIDLE2 0x97 +#define WIN_CHECKPOWERMODE2 0x98 +#define WIN_SLEEPNOW2 0x99 +/* + * 0x9A VENDOR + */ +#define WIN_PACKETCMD 0xA0 /* Send a packet command. */ +#define WIN_PIDENTIFY 0xA1 /* identify ATAPI device */ +#define WIN_QUEUED_SERVICE 0xA2 +#define WIN_SMART 0xB0 /* self-monitoring and reporting */ +#define CFA_ERASE_SECTORS 0xC0 +#define WIN_MULTREAD 0xC4 /* read sectors using multiple mode*/ +#define WIN_MULTWRITE 0xC5 /* write sectors using multiple mode */ +#define WIN_SETMULT 0xC6 /* enable/disable multiple mode */ +#define WIN_READDMA_QUEUED 0xC7 /* read sectors using Queued DMA transfers */ +#define WIN_READDMA 0xC8 /* read sectors using DMA transfers */ +#define WIN_READDMA_ONCE 0xC9 /* 28-Bit - without retries */ +#define WIN_WRITEDMA 0xCA /* write sectors using DMA transfers */ +#define WIN_WRITEDMA_ONCE 0xCB /* 28-Bit - without retries */ +#define WIN_WRITEDMA_QUEUED 0xCC /* write sectors using Queued DMA transfers */ +#define CFA_WRITE_MULTI_WO_ERASE 0xCD /* CFA Write multiple without erase */ +#define WIN_GETMEDIASTATUS 0xDA +#define WIN_ACKMEDIACHANGE 0xDB /* ATA-1, ATA-2 vendor */ +#define WIN_POSTBOOT 0xDC +#define WIN_PREBOOT 0xDD +#define WIN_DOORLOCK 0xDE /* lock door on removable drives */ +#define WIN_DOORUNLOCK 0xDF /* unlock door on removable drives */ +#define WIN_STANDBYNOW1 0xE0 +#define WIN_IDLEIMMEDIATE 0xE1 /* force drive to become "ready" */ +#define WIN_STANDBY 0xE2 /* Set device in Standby Mode */ +#define WIN_SETIDLE1 0xE3 +#define WIN_READ_BUFFER 0xE4 /* force read only 1 sector */ +#define WIN_CHECKPOWERMODE1 0xE5 +#define WIN_SLEEPNOW1 0xE6 +#define WIN_FLUSH_CACHE 0xE7 +#define WIN_WRITE_BUFFER 0xE8 /* force write only 1 sector */ +#define WIN_WRITE_SAME 0xE9 /* read ata-2 to use */ + /* SET_FEATURES 0x22 or 0xDD */ +#define WIN_FLUSH_CACHE_EXT 0xEA /* 48-Bit */ +#define WIN_IDENTIFY 0xEC /* ask drive to identify itself */ +#define WIN_MEDIAEJECT 0xED +#define WIN_IDENTIFY_DMA 0xEE /* same as WIN_IDENTIFY, but DMA */ +#define WIN_SETFEATURES 0xEF /* set special drive features */ +#define EXABYTE_ENABLE_NEST 0xF0 +#define WIN_SECURITY_SET_PASS 0xF1 +#define WIN_SECURITY_UNLOCK 0xF2 +#define WIN_SECURITY_ERASE_PREPARE 0xF3 +#define WIN_SECURITY_ERASE_UNIT 0xF4 +#define WIN_SECURITY_FREEZE_LOCK 0xF5 +#define WIN_SECURITY_DISABLE 0xF6 +#define WIN_READ_NATIVE_MAX 0xF8 /* return the native maximum address */ +#define WIN_SET_MAX 0xF9 +#define DISABLE_SEAGATE 0xFB + +#define MAX_MULT_SECTORS 128 + +typedef struct IDEState IDEState; + +typedef void EndTransferFunc(IDEState *); + +struct IDEState { + IDEIFState *ide_if; + BlockDevice *bs; + int cylinders, heads, sectors; + int mult_sectors; + int64_t nb_sectors; + + /* ide regs */ + uint8_t feature; + uint8_t error; + uint16_t nsector; /* 0 is 256 to ease computations */ + uint8_t sector; + uint8_t lcyl; + uint8_t hcyl; + uint8_t select; + uint8_t status; + + int io_nb_sectors; + int req_nb_sectors; + EndTransferFunc *end_transfer_func; + + int data_index; + int data_end; + uint8_t io_buffer[MAX_MULT_SECTORS*512 + 4]; +}; + +struct IDEIFState { + IRQSignal *irq; + IDEState *cur_drive; + IDEState *drives[2]; + /* 0x3f6 command */ + uint8_t cmd; +}; + +static void ide_sector_read_cb(void *opaque, int ret); +static void ide_sector_read_cb_end(IDEState *s); +static void ide_sector_write_cb2(void *opaque, int ret); + +static void padstr(char *str, const char *src, int len) +{ + int i, v; + for(i = 0; i < len; i++) { + if (*src) + v = *src++; + else + v = ' '; + *(char *)((long)str ^ 1) = v; + str++; + } +} + +/* little endian assume */ +static void stw(uint16_t *buf, int v) +{ + *buf = v; +} + +static void ide_identify(IDEState *s) +{ + uint16_t *tab; + uint32_t oldsize; + + tab = (uint16_t *)s->io_buffer; + + memset(tab, 0, 512 * 2); + + stw(tab + 0, 0x0040); + stw(tab + 1, s->cylinders); + stw(tab + 3, s->heads); + stw(tab + 4, 512 * s->sectors); /* sectors */ + stw(tab + 5, 512); /* sector size */ + stw(tab + 6, s->sectors); + stw(tab + 20, 3); /* buffer type */ + stw(tab + 21, 512); /* cache size in sectors */ + stw(tab + 22, 4); /* ecc bytes */ + padstr((char *)(tab + 27), "RISCVEMU HARDDISK", 40); + stw(tab + 47, 0x8000 | MAX_MULT_SECTORS); + stw(tab + 48, 0); /* dword I/O */ + stw(tab + 49, 1 << 9); /* LBA supported, no DMA */ + stw(tab + 51, 0x200); /* PIO transfer cycle */ + stw(tab + 52, 0x200); /* DMA transfer cycle */ + stw(tab + 54, s->cylinders); + stw(tab + 55, s->heads); + stw(tab + 56, s->sectors); + oldsize = s->cylinders * s->heads * s->sectors; + stw(tab + 57, oldsize); + stw(tab + 58, oldsize >> 16); + if (s->mult_sectors) + stw(tab + 59, 0x100 | s->mult_sectors); + stw(tab + 60, s->nb_sectors); + stw(tab + 61, s->nb_sectors >> 16); + stw(tab + 80, (1 << 1) | (1 << 2)); + stw(tab + 82, (1 << 14)); + stw(tab + 83, (1 << 14)); + stw(tab + 84, (1 << 14)); + stw(tab + 85, (1 << 14)); + stw(tab + 86, 0); + stw(tab + 87, (1 << 14)); +} + +static void ide_set_signature(IDEState *s) +{ + s->select &= 0xf0; + s->nsector = 1; + s->sector = 1; + s->lcyl = 0; + s->hcyl = 0; +} + +static void ide_abort_command(IDEState *s) +{ + s->status = READY_STAT | ERR_STAT; + s->error = ABRT_ERR; +} + +static void ide_set_irq(IDEState *s) +{ + IDEIFState *ide_if = s->ide_if; + if (!(ide_if->cmd & IDE_CMD_DISABLE_IRQ)) { + set_irq(ide_if->irq, 1); + } +} + +/* prepare data transfer and tell what to do after */ +static void ide_transfer_start(IDEState *s, int size, + EndTransferFunc *end_transfer_func) +{ + s->end_transfer_func = end_transfer_func; + s->data_index = 0; + s->data_end = size; +} + +static void ide_transfer_stop(IDEState *s) +{ + s->end_transfer_func = ide_transfer_stop; + s->data_index = 0; + s->data_end = 0; +} + +static int64_t ide_get_sector(IDEState *s) +{ + int64_t sector_num; + if (s->select & 0x40) { + /* lba */ + sector_num = ((s->select & 0x0f) << 24) | (s->hcyl << 16) | + (s->lcyl << 8) | s->sector; + } else { + sector_num = ((s->hcyl << 8) | s->lcyl) * + s->heads * s->sectors + + (s->select & 0x0f) * s->sectors + (s->sector - 1); + } + return sector_num; +} + +static void ide_set_sector(IDEState *s, int64_t sector_num) +{ + unsigned int cyl, r; + if (s->select & 0x40) { + s->select = (s->select & 0xf0) | ((sector_num >> 24) & 0x0f); + s->hcyl = (sector_num >> 16) & 0xff; + s->lcyl = (sector_num >> 8) & 0xff; + s->sector = sector_num & 0xff; + } else { + cyl = sector_num / (s->heads * s->sectors); + r = sector_num % (s->heads * s->sectors); + s->hcyl = (cyl >> 8) & 0xff; + s->lcyl = cyl & 0xff; + s->select = (s->select & 0xf0) | ((r / s->sectors) & 0x0f); + s->sector = (r % s->sectors) + 1; + } +} + +static void ide_sector_read(IDEState *s) +{ + int64_t sector_num; + int ret, n; + + sector_num = ide_get_sector(s); + n = s->nsector; + if (n == 0) + n = 256; + if (n > s->req_nb_sectors) + n = s->req_nb_sectors; +#if defined(DEBUG_IDE) + printf("read sector=%" PRId64 " count=%d\n", sector_num, n); +#endif + s->io_nb_sectors = n; + ret = s->bs->read_async(s->bs, sector_num, s->io_buffer, n, + ide_sector_read_cb, s); + if (ret < 0) { + /* error */ + ide_abort_command(s); + ide_set_irq(s); + } else if (ret == 0) { + /* synchronous case (needed for performance) */ + ide_sector_read_cb(s, 0); + } else { + /* async case */ + s->status = READY_STAT | SEEK_STAT | BUSY_STAT; + s->error = 0; /* not needed by IDE spec, but needed by Windows */ + } +} + +static void ide_sector_read_cb(void *opaque, int ret) +{ + IDEState *s = opaque; + int n; + EndTransferFunc *func; + + n = s->io_nb_sectors; + ide_set_sector(s, ide_get_sector(s) + n); + s->nsector = (s->nsector - n) & 0xff; + if (s->nsector == 0) + func = ide_sector_read_cb_end; + else + func = ide_sector_read; + ide_transfer_start(s, 512 * n, func); + ide_set_irq(s); + s->status = READY_STAT | SEEK_STAT | DRQ_STAT; + s->error = 0; /* not needed by IDE spec, but needed by Windows */ +} + +static void ide_sector_read_cb_end(IDEState *s) +{ + /* no more sector to read from disk */ + s->status = READY_STAT | SEEK_STAT; + s->error = 0; /* not needed by IDE spec, but needed by Windows */ + ide_transfer_stop(s); +} + +static void ide_sector_write_cb1(IDEState *s) +{ + int64_t sector_num; + int ret; + + ide_transfer_stop(s); + sector_num = ide_get_sector(s); +#if defined(DEBUG_IDE) + printf("write sector=%" PRId64 " count=%d\n", + sector_num, s->io_nb_sectors); +#endif + ret = s->bs->write_async(s->bs, sector_num, s->io_buffer, s->io_nb_sectors, + ide_sector_write_cb2, s); + if (ret < 0) { + /* error */ + ide_abort_command(s); + ide_set_irq(s); + } else if (ret == 0) { + /* synchronous case (needed for performance) */ + ide_sector_write_cb2(s, 0); + } else { + /* async case */ + s->status = READY_STAT | SEEK_STAT | BUSY_STAT; + } +} + +static void ide_sector_write_cb2(void *opaque, int ret) +{ + IDEState *s = opaque; + int n; + + n = s->io_nb_sectors; + ide_set_sector(s, ide_get_sector(s) + n); + s->nsector = (s->nsector - n) & 0xff; + if (s->nsector == 0) { + /* no more sectors to write */ + s->status = READY_STAT | SEEK_STAT; + } else { + n = s->nsector; + if (n > s->req_nb_sectors) + n = s->req_nb_sectors; + s->io_nb_sectors = n; + ide_transfer_start(s, 512 * n, ide_sector_write_cb1); + s->status = READY_STAT | SEEK_STAT | DRQ_STAT; + } + ide_set_irq(s); +} + +static void ide_sector_write(IDEState *s) +{ + int n; + n = s->nsector; + if (n == 0) + n = 256; + if (n > s->req_nb_sectors) + n = s->req_nb_sectors; + s->io_nb_sectors = n; + ide_transfer_start(s, 512 * n, ide_sector_write_cb1); + s->status = READY_STAT | SEEK_STAT | DRQ_STAT; +} + +static void ide_identify_cb(IDEState *s) +{ + ide_transfer_stop(s); + s->status = READY_STAT; +} + +static void ide_exec_cmd(IDEState *s, int val) +{ +#if defined(DEBUG_IDE) + printf("ide: exec_cmd=0x%02x\n", val); +#endif + switch(val) { + case WIN_IDENTIFY: + ide_identify(s); + s->status = READY_STAT | SEEK_STAT | DRQ_STAT; + ide_transfer_start(s, 512, ide_identify_cb); + ide_set_irq(s); + break; + case WIN_SPECIFY: + case WIN_RECAL: + s->error = 0; + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s); + break; + case WIN_SETMULT: + if (s->nsector > MAX_MULT_SECTORS || + (s->nsector & (s->nsector - 1)) != 0) { + ide_abort_command(s); + } else { + s->mult_sectors = s->nsector; +#if defined(DEBUG_IDE) + printf("ide: setmult=%d\n", s->mult_sectors); +#endif + s->status = READY_STAT; + } + ide_set_irq(s); + break; + case WIN_READ: + case WIN_READ_ONCE: + s->req_nb_sectors = 1; + ide_sector_read(s); + break; + case WIN_WRITE: + case WIN_WRITE_ONCE: + s->req_nb_sectors = 1; + ide_sector_write(s); + break; + case WIN_MULTREAD: + if (!s->mult_sectors) { + ide_abort_command(s); + ide_set_irq(s); + } else { + s->req_nb_sectors = s->mult_sectors; + ide_sector_read(s); + } + break; + case WIN_MULTWRITE: + if (!s->mult_sectors) { + ide_abort_command(s); + ide_set_irq(s); + } else { + s->req_nb_sectors = s->mult_sectors; + ide_sector_write(s); + } + break; + case WIN_READ_NATIVE_MAX: + ide_set_sector(s, s->nb_sectors - 1); + s->status = READY_STAT; + ide_set_irq(s); + break; + default: + ide_abort_command(s); + ide_set_irq(s); + break; + } +} + +static void ide_ioport_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ + IDEIFState *s1 = opaque; + IDEState *s = s1->cur_drive; + int addr = offset + 1; + +#ifdef DEBUG_IDE + printf("ide: write addr=0x%02x val=0x%02x\n", addr, val); +#endif + switch(addr) { + case 0: + break; + case 1: + if (s) { + s->feature = val; + } + break; + case 2: + if (s) { + s->nsector = val; + } + break; + case 3: + if (s) { + s->sector = val; + } + break; + case 4: + if (s) { + s->lcyl = val; + } + break; + case 5: + if (s) { + s->hcyl = val; + } + break; + case 6: + /* select drive */ + s = s1->cur_drive = s1->drives[(val >> 4) & 1]; + if (s) { + s->select = val; + } + break; + default: + case 7: + /* command */ + if (s) { + ide_exec_cmd(s, val); + } + break; + } +} + +static uint32_t ide_ioport_read(void *opaque, uint32_t offset, int size_log2) +{ + IDEIFState *s1 = opaque; + IDEState *s = s1->cur_drive; + int ret, addr = offset + 1; + + if (!s) { + ret = 0x00; + } else { + switch(addr) { + case 0: + ret = 0xff; + break; + case 1: + ret = s->error; + break; + case 2: + ret = s->nsector; + break; + case 3: + ret = s->sector; + break; + case 4: + ret = s->lcyl; + break; + case 5: + ret = s->hcyl; + break; + case 6: + ret = s->select; + break; + default: + case 7: + ret = s->status; + set_irq(s1->irq, 0); + break; + } + } +#ifdef DEBUG_IDE + printf("ide: read addr=0x%02x val=0x%02x\n", addr, ret); +#endif + return ret; +} + +static uint32_t ide_status_read(void *opaque, uint32_t offset, int size_log2) +{ + IDEIFState *s1 = opaque; + IDEState *s = s1->cur_drive; + int ret; + + if (s) { + ret = s->status; + } else { + ret = 0; + } +#ifdef DEBUG_IDE + printf("ide: read status=0x%02x\n", ret); +#endif + return ret; +} + +static void ide_cmd_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ + IDEIFState *s1 = opaque; + IDEState *s; + int i; + +#ifdef DEBUG_IDE + printf("ide: cmd write=0x%02x\n", val); +#endif + if (!(s1->cmd & IDE_CMD_RESET) && (val & IDE_CMD_RESET)) { + /* low to high */ + for(i = 0; i < 2; i++) { + s = s1->drives[i]; + if (s) { + s->status = BUSY_STAT | SEEK_STAT; + s->error = 0x01; + } + } + } else if ((s1->cmd & IDE_CMD_RESET) && !(val & IDE_CMD_RESET)) { + /* high to low */ + for(i = 0; i < 2; i++) { + s = s1->drives[i]; + if (s) { + s->status = READY_STAT | SEEK_STAT; + ide_set_signature(s); + } + } + } + s1->cmd = val; +} + +static void ide_data_writew(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ + IDEIFState *s1 = opaque; + IDEState *s = s1->cur_drive; + int p; + uint8_t *tab; + + if (!s) + return; + p = s->data_index; + tab = s->io_buffer; + tab[p] = val & 0xff; + tab[p + 1] = (val >> 8) & 0xff; + p += 2; + s->data_index = p; + if (p >= s->data_end) + s->end_transfer_func(s); +} + +static uint32_t ide_data_readw(void *opaque, uint32_t offset, int size_log2) +{ + IDEIFState *s1 = opaque; + IDEState *s = s1->cur_drive; + int p, ret; + uint8_t *tab; + + if (!s) { + ret = 0; + } else { + p = s->data_index; + tab = s->io_buffer; + ret = tab[p] | (tab[p + 1] << 8); + p += 2; + s->data_index = p; + if (p >= s->data_end) + s->end_transfer_func(s); + } + return ret; +} + +static IDEState *ide_drive_init(IDEIFState *ide_if, BlockDevice *bs) +{ + IDEState *s; + uint32_t cylinders; + uint64_t nb_sectors; + + s = malloc(sizeof(*s)); + memset(s, 0, sizeof(*s)); + + s->ide_if = ide_if; + s->bs = bs; + + nb_sectors = s->bs->get_sector_count(s->bs); + cylinders = nb_sectors / (16 * 63); + if (cylinders > 16383) + cylinders = 16383; + else if (cylinders < 2) + cylinders = 2; + s->cylinders = cylinders; + s->heads = 16; + s->sectors = 63; + s->nb_sectors = nb_sectors; + + s->mult_sectors = MAX_MULT_SECTORS; + /* ide regs */ + s->feature = 0; + s->error = 0; + s->nsector = 0; + s->sector = 0; + s->lcyl = 0; + s->hcyl = 0; + s->select = 0xa0; + s->status = READY_STAT | SEEK_STAT; + + /* init I/O buffer */ + s->data_index = 0; + s->data_end = 0; + s->end_transfer_func = ide_transfer_stop; + + s->req_nb_sectors = 0; /* temp for read/write */ + s->io_nb_sectors = 0; /* temp for read/write */ + return s; +} + +IDEIFState *ide_init(PhysMemoryMap *port_map, uint32_t addr, uint32_t addr2, + IRQSignal *irq, BlockDevice **tab_bs) +{ + int i; + IDEIFState *s; + + s = malloc(sizeof(IDEIFState)); + memset(s, 0, sizeof(*s)); + + s->irq = irq; + s->cmd = 0; + + cpu_register_device(port_map, addr, 1, s, ide_data_readw, ide_data_writew, + DEVIO_SIZE16); + cpu_register_device(port_map, addr + 1, 7, s, ide_ioport_read, ide_ioport_write, + DEVIO_SIZE8); + if (addr2) { + cpu_register_device(port_map, addr2, 1, s, ide_status_read, ide_cmd_write, + DEVIO_SIZE8); + } + + for(i = 0; i < 2; i++) { + if (tab_bs[i]) + s->drives[i] = ide_drive_init(s, tab_bs[i]); + } + s->cur_drive = s->drives[0]; + return s; +} + +/* dummy PCI device for the IDE */ +PCIDevice *piix3_ide_init(PCIBus *pci_bus, int devfn) +{ + PCIDevice *d; + d = pci_register_device(pci_bus, "PIIX3 IDE", devfn, 0x8086, 0x7010, 0x00, 0x0101); + pci_device_set_config8(d, 0x09, 0x00); /* ISA IDE ports, no DMA */ + return d; +} diff --git a/ide.h b/ide.h new file mode 100644 index 0000000..257a6a0 --- /dev/null +++ b/ide.h @@ -0,0 +1,32 @@ +/* + * IDE emulation + * + * Copyright (c) 2003-2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "virtio.h" +#include "iomem.h" +#include "pci.h" + +typedef struct IDEIFState IDEIFState; + +IDEIFState *ide_init(PhysMemoryMap *port_map, uint32_t addr, uint32_t addr2, + IRQSignal *irq, BlockDevice **tab_bs); +PCIDevice *piix3_ide_init(PCIBus *pci_bus, int devfn); diff --git a/iomem.c b/iomem.c new file mode 100644 index 0000000..c63ada0 --- /dev/null +++ b/iomem.c @@ -0,0 +1,264 @@ +/* + * IO memory handling + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "iomem.h" + +static PhysMemoryRange *default_register_ram(PhysMemoryMap *s, uint64_t addr, + uint64_t size, int devram_flags); +static void default_free_ram(PhysMemoryMap *s, PhysMemoryRange *pr); +static const uint32_t *default_get_dirty_bits(PhysMemoryMap *map, PhysMemoryRange *pr); +static void default_set_addr(PhysMemoryMap *map, + PhysMemoryRange *pr, uint64_t addr, BOOL enabled); + +PhysMemoryMap *phys_mem_map_init(void) +{ + PhysMemoryMap *s; + s = mallocz(sizeof(*s)); + s->register_ram = default_register_ram; + s->free_ram = default_free_ram; + s->get_dirty_bits = default_get_dirty_bits; + s->set_ram_addr = default_set_addr; + return s; +} + +void phys_mem_map_end(PhysMemoryMap *s) +{ + int i; + PhysMemoryRange *pr; + + for(i = 0; i < s->n_phys_mem_range; i++) { + pr = &s->phys_mem_range[i]; + if (pr->is_ram) { + s->free_ram(s, pr); + } + } + free(s); +} + +/* return NULL if not found */ +/* XXX: optimize */ +PhysMemoryRange *get_phys_mem_range(PhysMemoryMap *s, uint64_t paddr) +{ + PhysMemoryRange *pr; + int i; + for(i = 0; i < s->n_phys_mem_range; i++) { + pr = &s->phys_mem_range[i]; + if (paddr >= pr->addr && paddr < pr->addr + pr->size) + return pr; + } + return NULL; +} + +PhysMemoryRange *register_ram_entry(PhysMemoryMap *s, uint64_t addr, + uint64_t size, int devram_flags) +{ + PhysMemoryRange *pr; + + assert(s->n_phys_mem_range < PHYS_MEM_RANGE_MAX); + assert((size & (DEVRAM_PAGE_SIZE - 1)) == 0 && size != 0); + pr = &s->phys_mem_range[s->n_phys_mem_range++]; + pr->map = s; + pr->is_ram = TRUE; + pr->devram_flags = devram_flags & ~DEVRAM_FLAG_DISABLED; + pr->addr = addr; + pr->org_size = size; + if (devram_flags & DEVRAM_FLAG_DISABLED) + pr->size = 0; + else + pr->size = pr->org_size; + pr->phys_mem = NULL; + pr->dirty_bits = NULL; + return pr; +} + +static PhysMemoryRange *default_register_ram(PhysMemoryMap *s, uint64_t addr, + uint64_t size, int devram_flags) +{ + PhysMemoryRange *pr; + + pr = register_ram_entry(s, addr, size, devram_flags); + + pr->phys_mem = mallocz(size); + if (!pr->phys_mem) { + fprintf(stderr, "Could not allocate VM memory\n"); + exit(1); + } + + if (devram_flags & DEVRAM_FLAG_DIRTY_BITS) { + size_t nb_pages; + int i; + nb_pages = size >> DEVRAM_PAGE_SIZE_LOG2; + pr->dirty_bits_size = ((nb_pages + 31) / 32) * sizeof(uint32_t); + pr->dirty_bits_index = 0; + for(i = 0; i < 2; i++) { + pr->dirty_bits_tab[i] = mallocz(pr->dirty_bits_size); + } + pr->dirty_bits = pr->dirty_bits_tab[pr->dirty_bits_index]; + } + return pr; +} + +/* return a pointer to the bitmap of dirty bits and reset them */ +static const uint32_t *default_get_dirty_bits(PhysMemoryMap *map, + PhysMemoryRange *pr) +{ + uint32_t *dirty_bits; + BOOL has_dirty_bits; + size_t n, i; + + dirty_bits = pr->dirty_bits; + + has_dirty_bits = FALSE; + n = pr->dirty_bits_size / sizeof(uint32_t); + for(i = 0; i < n; i++) { + if (dirty_bits[i] != 0) { + has_dirty_bits = TRUE; + break; + } + } + if (has_dirty_bits && pr->size != 0) { + /* invalidate the corresponding CPU write TLBs */ + map->flush_tlb_write_range(map->opaque, pr->phys_mem, pr->org_size); + } + + pr->dirty_bits_index ^= 1; + pr->dirty_bits = pr->dirty_bits_tab[pr->dirty_bits_index]; + memset(pr->dirty_bits, 0, pr->dirty_bits_size); + return dirty_bits; +} + +/* reset the dirty bit of one page at 'offset' inside 'pr' */ +void phys_mem_reset_dirty_bit(PhysMemoryRange *pr, size_t offset) +{ + size_t page_index; + uint32_t mask, *dirty_bits_ptr; + PhysMemoryMap *map; + if (pr->dirty_bits) { + page_index = offset >> DEVRAM_PAGE_SIZE_LOG2; + mask = 1 << (page_index & 0x1f); + dirty_bits_ptr = pr->dirty_bits + (page_index >> 5); + if (*dirty_bits_ptr & mask) { + *dirty_bits_ptr &= ~mask; + /* invalidate the corresponding CPU write TLBs */ + map = pr->map; + map->flush_tlb_write_range(map->opaque, + pr->phys_mem + (offset & ~(DEVRAM_PAGE_SIZE - 1)), + DEVRAM_PAGE_SIZE); + } + } +} + +static void default_free_ram(PhysMemoryMap *s, PhysMemoryRange *pr) +{ + free(pr->phys_mem); +} + +PhysMemoryRange *cpu_register_device(PhysMemoryMap *s, uint64_t addr, + uint64_t size, void *opaque, + DeviceReadFunc *read_func, DeviceWriteFunc *write_func, + int devio_flags) +{ + PhysMemoryRange *pr; + assert(s->n_phys_mem_range < PHYS_MEM_RANGE_MAX); + assert(size <= 0xffffffff); + pr = &s->phys_mem_range[s->n_phys_mem_range++]; + pr->map = s; + pr->addr = addr; + pr->org_size = size; + if (devio_flags & DEVIO_DISABLED) + pr->size = 0; + else + pr->size = pr->org_size; + pr->is_ram = FALSE; + pr->opaque = opaque; + pr->read_func = read_func; + pr->write_func = write_func; + pr->devio_flags = devio_flags; + return pr; +} + +static void default_set_addr(PhysMemoryMap *map, + PhysMemoryRange *pr, uint64_t addr, BOOL enabled) +{ + if (enabled) { + if (pr->size == 0 || pr->addr != addr) { + /* enable or move mapping */ + if (pr->is_ram) { + map->flush_tlb_write_range(map->opaque, + pr->phys_mem, pr->org_size); + } + pr->addr = addr; + pr->size = pr->org_size; + } + } else { + if (pr->size != 0) { + /* disable mapping */ + if (pr->is_ram) { + map->flush_tlb_write_range(map->opaque, + pr->phys_mem, pr->org_size); + } + pr->addr = 0; + pr->size = 0; + } + } +} + +void phys_mem_set_addr(PhysMemoryRange *pr, uint64_t addr, BOOL enabled) +{ + PhysMemoryMap *map = pr->map; + if (!pr->is_ram) { + default_set_addr(map, pr, addr, enabled); + } else { + return map->set_ram_addr(map, pr, addr, enabled); + } +} + +/* return NULL if no valid RAM page. The access can only be done in the page */ +uint8_t *phys_mem_get_ram_ptr(PhysMemoryMap *map, uint64_t paddr, BOOL is_rw) +{ + PhysMemoryRange *pr = get_phys_mem_range(map, paddr); + uintptr_t offset; + if (!pr || !pr->is_ram) + return NULL; + offset = paddr - pr->addr; + if (is_rw) + phys_mem_set_dirty_bit(pr, offset); + return pr->phys_mem + (uintptr_t)offset; +} + +/* IRQ support */ + +void irq_init(IRQSignal *irq, SetIRQFunc *set_irq, void *opaque, int irq_num) +{ + irq->set_irq = set_irq; + irq->opaque = opaque; + irq->irq_num = irq_num; +} diff --git a/iomem.h b/iomem.h new file mode 100644 index 0000000..cc561b4 --- /dev/null +++ b/iomem.h @@ -0,0 +1,148 @@ +/* + * IO memory handling + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef IOMEM_H +#define IOMEM_H + +typedef void DeviceWriteFunc(void *opaque, uint32_t offset, + uint32_t val, int size_log2); +typedef uint32_t DeviceReadFunc(void *opaque, uint32_t offset, int size_log2); + +#define DEVIO_SIZE8 (1 << 0) +#define DEVIO_SIZE16 (1 << 1) +#define DEVIO_SIZE32 (1 << 2) +/* not supported, could add specific 64 bit callbacks when needed */ +//#define DEVIO_SIZE64 (1 << 3) +#define DEVIO_DISABLED (1 << 4) + +#define DEVRAM_FLAG_ROM (1 << 0) /* not writable */ +#define DEVRAM_FLAG_DIRTY_BITS (1 << 1) /* maintain dirty bits */ +#define DEVRAM_FLAG_DISABLED (1 << 2) /* allocated but not mapped */ +#define DEVRAM_PAGE_SIZE_LOG2 12 +#define DEVRAM_PAGE_SIZE (1 << DEVRAM_PAGE_SIZE_LOG2) + +typedef struct PhysMemoryMap PhysMemoryMap; + +typedef struct { + PhysMemoryMap *map; + uint64_t addr; + uint64_t org_size; /* original size */ + uint64_t size; /* =org_size or 0 if the mapping is disabled */ + BOOL is_ram; + /* the following is used for RAM access */ + int devram_flags; + uint8_t *phys_mem; + int dirty_bits_size; /* in bytes */ + uint32_t *dirty_bits; /* NULL if not used */ + uint32_t *dirty_bits_tab[2]; + int dirty_bits_index; /* 0-1 */ + /* the following is used for I/O access */ + void *opaque; + DeviceReadFunc *read_func; + DeviceWriteFunc *write_func; + int devio_flags; +} PhysMemoryRange; + +#define PHYS_MEM_RANGE_MAX 32 + +struct PhysMemoryMap { + int n_phys_mem_range; + PhysMemoryRange phys_mem_range[PHYS_MEM_RANGE_MAX]; + PhysMemoryRange *(*register_ram)(PhysMemoryMap *s, uint64_t addr, + uint64_t size, int devram_flags); + void (*free_ram)(PhysMemoryMap *s, PhysMemoryRange *pr); + const uint32_t *(*get_dirty_bits)(PhysMemoryMap *s, PhysMemoryRange *pr); + void (*set_ram_addr)(PhysMemoryMap *s, PhysMemoryRange *pr, uint64_t addr, + BOOL enabled); + void *opaque; + void (*flush_tlb_write_range)(void *opaque, uint8_t *ram_addr, + size_t ram_size); +}; + + +PhysMemoryMap *phys_mem_map_init(void); +void phys_mem_map_end(PhysMemoryMap *s); +PhysMemoryRange *register_ram_entry(PhysMemoryMap *s, uint64_t addr, + uint64_t size, int devram_flags); +static inline PhysMemoryRange *cpu_register_ram(PhysMemoryMap *s, uint64_t addr, + uint64_t size, int devram_flags) +{ + return s->register_ram(s, addr, size, devram_flags); +} +PhysMemoryRange *cpu_register_device(PhysMemoryMap *s, uint64_t addr, + uint64_t size, void *opaque, + DeviceReadFunc *read_func, DeviceWriteFunc *write_func, + int devio_flags); +PhysMemoryRange *get_phys_mem_range(PhysMemoryMap *s, uint64_t paddr); +void phys_mem_set_addr(PhysMemoryRange *pr, uint64_t addr, BOOL enabled); + +static inline const uint32_t *phys_mem_get_dirty_bits(PhysMemoryRange *pr) +{ + PhysMemoryMap *map = pr->map; + return map->get_dirty_bits(map, pr); +} + +static inline void phys_mem_set_dirty_bit(PhysMemoryRange *pr, size_t offset) +{ + size_t page_index; + uint32_t mask, *dirty_bits_ptr; + if (pr->dirty_bits) { + page_index = offset >> DEVRAM_PAGE_SIZE_LOG2; + mask = 1 << (page_index & 0x1f); + dirty_bits_ptr = pr->dirty_bits + (page_index >> 5); + *dirty_bits_ptr |= mask; + } +} + +static inline BOOL phys_mem_is_dirty_bit(PhysMemoryRange *pr, size_t offset) +{ + size_t page_index; + uint32_t *dirty_bits_ptr; + if (!pr->dirty_bits) + return TRUE; + page_index = offset >> DEVRAM_PAGE_SIZE_LOG2; + dirty_bits_ptr = pr->dirty_bits + (page_index >> 5); + return (*dirty_bits_ptr >> (page_index & 0x1f)) & 1; +} + +void phys_mem_reset_dirty_bit(PhysMemoryRange *pr, size_t offset); +uint8_t *phys_mem_get_ram_ptr(PhysMemoryMap *map, uint64_t paddr, BOOL is_rw); + +/* IRQ support */ + +typedef void SetIRQFunc(void *opaque, int irq_num, int level); + +typedef struct { + SetIRQFunc *set_irq; + void *opaque; + int irq_num; +} IRQSignal; + +void irq_init(IRQSignal *irq, SetIRQFunc *set_irq, void *opaque, int irq_num); + +static inline void set_irq(IRQSignal *irq, int level) +{ + irq->set_irq(irq->opaque, irq->irq_num, level); +} + +#endif /* IOMEM_H */ diff --git a/js/lib.js b/js/lib.js new file mode 100644 index 0000000..5dbf2b2 --- /dev/null +++ b/js/lib.js @@ -0,0 +1,280 @@ +/* + * JS emulator library + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +mergeInto(LibraryManager.library, { + console_write: function(opaque, buf, len) + { + var str; + /* Note: we really send byte values. It would be up to the + * terminal to support UTF-8 */ + str = String.fromCharCode.apply(String, HEAPU8.subarray(buf, buf + len)); + term.write(str); + }, + + console_get_size: function(pw, ph) + { + var w, h, r; + r = term.getSize(); + HEAPU32[pw >> 2] = r[0]; + HEAPU32[ph >> 2] = r[1]; + }, + + fs_export_file: function(filename, buf, buf_len) + { + var _filename = Pointer_stringify(filename); +// console.log("exporting " + _filename); + var data = HEAPU8.subarray(buf, buf + buf_len); + var file = new Blob([data], { type: "application/octet-stream" }); + var url = URL.createObjectURL(file); + var a = document.createElement("a"); + a.href = url; + a.setAttribute("download", _filename); + a.innerHTML = "downloading"; + document.body.appendChild(a); + /* click on the link and remove it */ + setTimeout(function() { + a.click(); + document.body.removeChild(a); + }, 50); + }, + + emscripten_async_wget3_data: function(url, request, user, password, post_data, post_data_len, arg, free, onload, onerror, onprogress) { + var _url = Pointer_stringify(url); + var _request = Pointer_stringify(request); + var _user; + var _password; + + var http = new XMLHttpRequest(); + + if (user) + _user = Pointer_stringify(user); + else + _user = null; + if (password) + _password = Pointer_stringify(password); + else + _password = null; + + http.open(_request, _url, true); + http.responseType = 'arraybuffer'; + if (_user) { + http.setRequestHeader("Authorization", "Basic " + btoa(_user + ':' + _password)); + } + + var handle = Browser.getNextWgetRequestHandle(); + + // LOAD + http.onload = function http_onload(e) { + if (http.status == 200 || _url.substr(0,4).toLowerCase() != "http") { + var byteArray = new Uint8Array(http.response); + var buffer = _malloc(byteArray.length); + HEAPU8.set(byteArray, buffer); + if (onload) Runtime.dynCall('viiii', onload, [handle, arg, buffer, byteArray.length]); + if (free) _free(buffer); + } else { + if (onerror) Runtime.dynCall('viiii', onerror, [handle, arg, http.status, http.statusText]); + } + delete Browser.wgetRequests[handle]; + }; + + // ERROR + http.onerror = function http_onerror(e) { + if (onerror) { + Runtime.dynCall('viiii', onerror, [handle, arg, http.status, http.statusText]); + } + delete Browser.wgetRequests[handle]; + }; + + // PROGRESS + http.onprogress = function http_onprogress(e) { + if (onprogress) Runtime.dynCall('viiii', onprogress, [handle, arg, e.loaded, e.lengthComputable || e.lengthComputable === undefined ? e.total : 0]); + }; + + // ABORT + http.onabort = function http_onabort(e) { + delete Browser.wgetRequests[handle]; + }; + + // Useful because the browser can limit the number of redirection + try { + if (http.channel instanceof Ci.nsIHttpChannel) + http.channel.redirectionLimit = 0; + } catch (ex) { /* whatever */ } + + if (_request == "POST") { + var _post_data = HEAPU8.subarray(post_data, post_data + post_data_len); + //Send the proper header information along with the request + http.setRequestHeader("Content-type", "application/octet-stream"); + http.setRequestHeader("Content-length", post_data_len); + http.setRequestHeader("Connection", "close"); + http.send(_post_data); + } else { + http.send(null); + } + + Browser.wgetRequests[handle] = http; + + return handle; + }, + + fs_wget_update_downloading: function (flag) + { + update_downloading(Boolean(flag)); + }, + + fb_refresh: function(opaque, data, x, y, w, h, stride) + { + var i, j, v, src, image_data, dst_pos, display, dst_pos1, image_stride; + + display = graphic_display; + /* current x = 0 and w = width for all refreshes */ +// console.log("fb_refresh: x=" + x + " y=" + y + " w=" + w + " h=" + h); + image_data = display.image.data; + image_stride = display.width * 4; + dst_pos1 = (y * display.width + x) * 4; + for(i = 0; i < h; i = (i + 1) | 0) { + src = data; + dst_pos = dst_pos1; + for(j = 0; j < w; j = (j + 1) | 0) { + v = HEAPU32[src >> 2]; + image_data[dst_pos] = (v >> 16) & 0xff; + image_data[dst_pos + 1] = (v >> 8) & 0xff; + image_data[dst_pos + 2] = v & 0xff; + image_data[dst_pos + 3] = 0xff; /* XXX: do it once */ + src = (src + 4) | 0; + dst_pos = (dst_pos + 4) | 0; + } + data = (data + stride) | 0; + dst_pos1 = (dst_pos1 + image_stride) | 0; + } + display.ctx.putImageData(display.image, 0, 0, x, y, w, h); + }, + + net_recv_packet: function(bs, buf, buf_len) + { + if (net_state) { + net_state.recv_packet(HEAPU8.subarray(buf, buf + buf_len)); + } + }, + + /* file buffer API */ + file_buffer_get_new_handle: function() + { + var h; + if (typeof Browser.fbuf_table == "undefined") { + Browser.fbuf_table = new Object(); + Browser.fbuf_next_handle = 1; + } + for(;;) { + h = Browser.fbuf_next_handle; + Browser.fbuf_next_handle++; + if (Browser.fbuf_next_handle == 0x80000000) + Browser.fbuf_next_handle = 1; + if (typeof Browser.fbuf_table[h] == "undefined") { +// console.log("new handle=" + h); + return h; + } + } + }, + + file_buffer_init: function(bs) + { + var h; + HEAPU32[bs >> 2] = 0; + HEAPU32[(bs + 4) >> 2] = 0; + }, + + file_buffer_resize__deps: ['file_buffer_get_new_handle'], + file_buffer_resize: function(bs, new_size) + { + var h, size, new_data, size1, i, data; + h = HEAPU32[bs >> 2]; + size = HEAPU32[(bs + 4) >> 2]; + if (new_size == 0) { + if (h != 0) { + delete Browser.fbuf_table[h]; + h = 0; + } + } else if (size == 0) { + h = _file_buffer_get_new_handle(); + new_data = new Uint8Array(new_size); + Browser.fbuf_table[h] = new_data; + } else if (size != new_size) { + data = Browser.fbuf_table[h]; + new_data = new Uint8Array(new_size); + if (new_size > size) { + new_data.set(data, 0); + } else { + for(i = 0; i < new_size; i = (i + 1) | 0) + new_data[i] = data[i]; + } + Browser.fbuf_table[h] = new_data; + } + HEAPU32[bs >> 2] = h; + HEAPU32[(bs + 4) >> 2] = new_size; + return 0; + }, + + file_buffer_reset: function(bs) + { + _file_buffer_resize(bs, 0); + _file_buffer_init(bs); + }, + + file_buffer_write: function(bs, offset, buf, size) + { + var h, data, i; + h = HEAPU32[bs >> 2]; + if (h) { + data = Browser.fbuf_table[h]; + for(i = 0; i < size; i = (i + 1) | 0) { + data[offset + i] = HEAPU8[buf + i]; + } + } + }, + + file_buffer_read: function(bs, offset, buf, size) + { + var h, data, i; + h = HEAPU32[bs >> 2]; + if (h) { + data = Browser.fbuf_table[h]; + for(i = 0; i < size; i = (i + 1) | 0) { + HEAPU8[buf + i] = data[offset + i]; + } + } + }, + + file_buffer_set: function(bs, offset, val, size) + { + var h, data, i; + h = HEAPU32[bs >> 2]; + if (h) { + data = Browser.fbuf_table[h]; + for(i = 0; i < size; i = (i + 1) | 0) { + data[offset + i] = val; + } + } + }, + +}); diff --git a/jsemu.c b/jsemu.c new file mode 100644 index 0000000..3641db4 --- /dev/null +++ b/jsemu.c @@ -0,0 +1,349 @@ +/* + * JS emulator main + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "iomem.h" +#include "virtio.h" +#include "machine.h" +#include "list.h" +#include "fbuf.h" + +void virt_machine_run(void *opaque); + +/* provided in lib.js */ +extern void console_write(void *opaque, const uint8_t *buf, int len); +extern void console_get_size(int *pw, int *ph); +extern void fb_refresh(void *opaque, void *data, + int x, int y, int w, int h, int stride); +extern void net_recv_packet(EthernetDevice *bs, + const uint8_t *buf, int len); + +static uint8_t console_fifo[1024]; +static int console_fifo_windex; +static int console_fifo_rindex; +static int console_fifo_count; +static BOOL console_resize_pending; + +static int global_width; +static int global_height; +static VirtMachine *global_vm; +static BOOL global_carrier_state; + +static int console_read(void *opaque, uint8_t *buf, int len) +{ + int out_len, l; + len = min_int(len, console_fifo_count); + console_fifo_count -= len; + out_len = 0; + while (len != 0) { + l = min_int(len, sizeof(console_fifo) - console_fifo_rindex); + memcpy(buf + out_len, console_fifo + console_fifo_rindex, l); + len -= l; + out_len += l; + console_fifo_rindex += l; + if (console_fifo_rindex == sizeof(console_fifo)) + console_fifo_rindex = 0; + } + return out_len; +} + +/* called from JS */ +void console_queue_char(int c) +{ + if (console_fifo_count < sizeof(console_fifo)) { + console_fifo[console_fifo_windex] = c; + if (++console_fifo_windex == sizeof(console_fifo)) + console_fifo_windex = 0; + console_fifo_count++; + } +} + +/* called from JS */ +void display_key_event(int is_down, int key_code) +{ + if (global_vm) { + vm_send_key_event(global_vm, is_down, key_code); + } +} + +/* called from JS */ +static int mouse_last_x, mouse_last_y, mouse_last_buttons; + +void display_mouse_event(int dx, int dy, int buttons) +{ + if (global_vm) { + if (vm_mouse_is_absolute(global_vm) || 1) { + dx = min_int(dx, global_width - 1); + dy = min_int(dy, global_height - 1); + dx = (dx * VIRTIO_INPUT_ABS_SCALE) / global_width; + dy = (dy * VIRTIO_INPUT_ABS_SCALE) / global_height; + } else { + /* relative mouse is not supported */ + dx = 0; + dy = 0; + } + mouse_last_x = dx; + mouse_last_y = dy; + mouse_last_buttons = buttons; + vm_send_mouse_event(global_vm, dx, dy, 0, buttons); + } +} + +/* called from JS */ +void display_wheel_event(int dz) +{ + if (global_vm) { + vm_send_mouse_event(global_vm, mouse_last_x, mouse_last_y, dz, + mouse_last_buttons); + } +} + +/* called from JS */ +void net_write_packet(const uint8_t *buf, int buf_len) +{ + EthernetDevice *net = global_vm->net; + if (net) { + net->device_write_packet(net, buf, buf_len); + } +} + +/* called from JS */ +void net_set_carrier(BOOL carrier_state) +{ + EthernetDevice *net; + global_carrier_state = carrier_state; + if (global_vm && global_vm->net) { + net = global_vm->net; + net->device_set_carrier(net, carrier_state); + } +} + +static void fb_refresh1(FBDevice *fb_dev, void *opaque, + int x, int y, int w, int h) +{ + int stride = fb_dev->stride; + fb_refresh(opaque, fb_dev->fb_data + y * stride + x * 4, x, y, w, h, + stride); +} + +static CharacterDevice *console_init(void) +{ + CharacterDevice *dev; + console_resize_pending = TRUE; + dev = mallocz(sizeof(*dev)); + dev->write_data = console_write; + dev->read_data = console_read; + return dev; +} + +typedef struct { + VirtMachineParams *p; + int ram_size; + char *cmdline; + BOOL has_network; + char *pwd; +} VMStartState; + +static void init_vm(void *arg); +static void init_vm_fs(void *arg); +static void init_vm_drive(void *arg); + +void vm_start(const char *url, int ram_size, const char *cmdline, + const char *pwd, int width, int height, BOOL has_network) +{ + VMStartState *s; + + s = mallocz(sizeof(*s)); + s->ram_size = ram_size; + s->cmdline = strdup(cmdline); + if (pwd) + s->pwd = strdup(pwd); + global_width = width; + global_height = height; + s->has_network = has_network; + s->p = mallocz(sizeof(VirtMachineParams)); + virt_machine_set_defaults(s->p); + virt_machine_load_config_file(s->p, url, init_vm_fs, s); +} + +static void init_vm_fs(void *arg) +{ + VMStartState *s = arg; + VirtMachineParams *p = s->p; + + if (p->fs_count > 0) { + assert(p->fs_count == 1); + p->tab_fs[0].fs_dev = fs_net_init(p->tab_fs[0].filename, + init_vm_drive, s); + if (s->pwd) { + fs_net_set_pwd(p->tab_fs[0].fs_dev, s->pwd); + } + } else { + init_vm_drive(s); + } +} + +static void init_vm_drive(void *arg) +{ + VMStartState *s = arg; + VirtMachineParams *p = s->p; + + if (p->drive_count > 0) { + assert(p->drive_count == 1); + p->tab_drive[0].block_dev = + block_device_init_http(p->tab_drive[0].filename, + 131072, + init_vm, s); + } else { + init_vm(s); + } +} + +static void init_vm(void *arg) +{ + VMStartState *s = arg; + VirtMachine *m; + VirtMachineParams *p = s->p; + int i; + + p->rtc_real_time = TRUE; + p->ram_size = s->ram_size << 20; + if (s->cmdline && s->cmdline[0] != '\0') { + vm_add_cmdline(s->p, s->cmdline); + } + + if (global_width > 0 && global_height > 0) { + /* enable graphic output if needed */ + if (!p->display_device) + p->display_device = strdup("simplefb"); + p->width = global_width; + p->height = global_height; + } else { + p->console = console_init(); + } + + if (p->eth_count > 0 && !s->has_network) { + /* remove the interfaces */ + for(i = 0; i < p->eth_count; i++) { + free(p->tab_eth[i].ifname); + free(p->tab_eth[i].driver); + } + p->eth_count = 0; + } + + if (p->eth_count > 0) { + EthernetDevice *net; + int i; + assert(p->eth_count == 1); + net = mallocz(sizeof(EthernetDevice)); + net->mac_addr[0] = 0x02; + for(i = 1; i < 6; i++) + net->mac_addr[i] = (int)(emscripten_random() * 256); + net->write_packet = net_recv_packet; + net->opaque = NULL; + p->tab_eth[0].net = net; + } + + m = virt_machine_init(p); + global_vm = m; + + virt_machine_free_config(s->p); + + if (m->net) { + m->net->device_set_carrier(m->net, global_carrier_state); + } + + free(s->p); + free(s->cmdline); + if (s->pwd) { + memset(s->pwd, 0, strlen(s->pwd)); + free(s->pwd); + } + free(s); + + emscripten_async_call(virt_machine_run, m, 0); +} + +/* need to be long enough to hide the non zero delay of setTimeout(_, 0) */ +#define MAX_EXEC_TOTAL_CYCLE 3000000 +#define MAX_EXEC_CYCLE 200000 + +#define MAX_SLEEP_TIME 10 /* in ms */ + +void virt_machine_run(void *opaque) +{ + VirtMachine *m = opaque; + int delay, i; + FBDevice *fb_dev; + + if (m->console_dev && virtio_console_can_write_data(m->console_dev)) { + uint8_t buf[128]; + int ret, len; + len = virtio_console_get_write_len(m->console_dev); + len = min_int(len, sizeof(buf)); + ret = m->console->read_data(m->console->opaque, buf, len); + if (ret > 0) + virtio_console_write_data(m->console_dev, buf, ret); + if (console_resize_pending) { + int w, h; + console_get_size(&w, &h); + virtio_console_resize_event(m->console_dev, w, h); + console_resize_pending = FALSE; + } + } + + fb_dev = m->fb_dev; + if (fb_dev) { + /* refresh the display */ + fb_dev->refresh(fb_dev, fb_refresh1, NULL); + } + + i = 0; + for(;;) { + /* wait for an event: the only asynchronous event is the RTC timer */ + delay = virt_machine_get_sleep_duration(m, MAX_SLEEP_TIME); + if (delay != 0 || i >= MAX_EXEC_TOTAL_CYCLE / MAX_EXEC_CYCLE) + break; + virt_machine_interp(m, MAX_EXEC_CYCLE); + i++; + } + + if (delay == 0) { + emscripten_async_call(virt_machine_run, m, 0); + } else { + emscripten_async_call(virt_machine_run, m, MAX_SLEEP_TIME); + } +} + diff --git a/json.c b/json.c new file mode 100644 index 0000000..d1ebe82 --- /dev/null +++ b/json.c @@ -0,0 +1,464 @@ +/* + * Pseudo JSON parser + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "json.h" +#include "fs_utils.h" + +static JSONValue parse_string(const char **pp) +{ + char buf[4096], *q; + const char *p; + int c, h; + + q = buf; + p = *pp; + p++; + for(;;) { + c = *p++; + if (c == '\0' || c == '\n') { + return json_error_new("unterminated string"); + } else if (c == '\"') { + break; + } else if (c == '\\') { + c = *p++; + switch(c) { + case '\'': + case '\"': + case '\\': + goto add_char; + case 'n': + c = '\n'; + goto add_char; + case 'r': + c = '\r'; + goto add_char; + case 't': + c = '\t'; + goto add_char; + case 'x': + h = from_hex(*p++); + if (h < 0) + return json_error_new("invalig hex digit"); + c = h << 4; + h = from_hex(*p++); + if (h < 0) + return json_error_new("invalig hex digit"); + c |= h; + goto add_char; + default: + return json_error_new("unknown escape code"); + } + } else { + add_char: + if (q >= buf + sizeof(buf) - 1) + return json_error_new("string too long"); + *q++ = c; + } + } + *q = '\0'; + *pp = p; + return json_string_new(buf); +} + +static JSONProperty *json_object_get2(JSONObject *obj, const char *name) +{ + JSONProperty *f; + int i; + for(i = 0; i < obj->len; i++) { + f = &obj->props[i]; + if (!strcmp(f->name.u.str->data, name)) + return f; + } + return NULL; +} + +JSONValue json_object_get(JSONValue val, const char *name) +{ + JSONProperty *f; + JSONObject *obj; + + if (val.type != JSON_OBJ) + return json_undefined_new(); + obj = val.u.obj; + f = json_object_get2(obj, name); + if (!f) + return json_undefined_new(); + return f->value; +} + +int json_object_set(JSONValue val, const char *name, JSONValue prop_val) +{ + JSONObject *obj; + JSONProperty *f; + int new_size; + + if (val.type != JSON_OBJ) + return -1; + obj = val.u.obj; + f = json_object_get2(obj, name); + if (f) { + json_free(f->value); + f->value = prop_val; + } else { + if (obj->len >= obj->size) { + new_size = max_int(obj->len + 1, obj->size * 3 / 2); + obj->props = realloc(obj->props, new_size * sizeof(JSONProperty)); + obj->size = new_size; + } + f = &obj->props[obj->len++]; + f->name = json_string_new(name); + f->value = prop_val; + } + return 0; +} + +JSONValue json_array_get(JSONValue val, unsigned int idx) +{ + JSONArray *array; + + if (val.type != JSON_ARRAY) + return json_undefined_new(); + array = val.u.array; + if (idx < array->len) { + return array->tab[idx]; + } else { + return json_undefined_new(); + } +} + +int json_array_set(JSONValue val, unsigned int idx, JSONValue prop_val) +{ + JSONArray *array; + int new_size; + + if (val.type != JSON_ARRAY) + return -1; + array = val.u.array; + if (idx < array->len) { + json_free(array->tab[idx]); + array->tab[idx] = prop_val; + } else if (idx == array->len) { + if (array->len >= array->size) { + new_size = max_int(array->len + 1, array->size * 3 / 2); + array->tab = realloc(array->tab, new_size * sizeof(JSONValue)); + array->size = new_size; + } + array->tab[array->len++] = prop_val; + } else { + return -1; + } + return 0; +} + +const char *json_get_str(JSONValue val) +{ + if (val.type != JSON_STR) + return NULL; + return val.u.str->data; +} + +const char *json_get_error(JSONValue val) +{ + if (val.type != JSON_EXCEPTION) + return NULL; + return val.u.str->data; +} + +JSONValue json_string_new2(const char *str, int len) +{ + JSONValue val; + JSONString *str1; + + str1 = malloc(sizeof(JSONString) + len + 1); + str1->len = len; + memcpy(str1->data, str, len + 1); + val.type = JSON_STR; + val.u.str = str1; + return val; +} + +JSONValue json_string_new(const char *str) +{ + return json_string_new2(str, strlen(str)); +} + +JSONValue __attribute__((format(printf, 1, 2))) json_error_new(const char *fmt, ...) +{ + JSONValue val; + va_list ap; + char buf[256]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + val = json_string_new(buf); + val.type = JSON_EXCEPTION; + return val; +} + +JSONValue json_object_new(void) +{ + JSONValue val; + JSONObject *obj; + obj = mallocz(sizeof(JSONObject)); + val.type = JSON_OBJ; + val.u.obj = obj; + return val; +} + +JSONValue json_array_new(void) +{ + JSONValue val; + JSONArray *array; + array = mallocz(sizeof(JSONArray)); + val.type = JSON_ARRAY; + val.u.array = array; + return val; +} + +void json_free(JSONValue val) +{ + switch(val.type) { + case JSON_STR: + case JSON_EXCEPTION: + free(val.u.str); + break; + case JSON_INT: + case JSON_BOOL: + case JSON_NULL: + case JSON_UNDEFINED: + break; + case JSON_ARRAY: + { + JSONArray *array = val.u.array; + int i; + + for(i = 0; i < array->len; i++) { + json_free(array->tab[i]); + } + free(array); + } + break; + case JSON_OBJ: + { + JSONObject *obj = val.u.obj; + JSONProperty *f; + int i; + + for(i = 0; i < obj->len; i++) { + f = &obj->props[i]; + json_free(f->name); + json_free(f->value); + } + free(obj); + } + break; + default: + abort(); + } +} + +static void skip_spaces(const char **pp) +{ + const char *p; + p = *pp; + for(;;) { + if (isspace(*p)) { + p++; + } else if (p[0] == '/' && p[1] == '/') { + p += 2; + while (*p != '\0' && *p != '\n') + p++; + } else if (p[0] == '/' && p[1] == '*') { + p += 2; + while (*p != '\0' && (p[0] != '*' || p[1] != '/')) + p++; + if (*p != '\0') + p += 2; + } else { + break; + } + } + *pp = p; +} + +static inline BOOL is_ident_first(int c) +{ + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '_' || c == '$'; +} + +static int parse_ident(char *buf, int buf_size, const char **pp) +{ + char *q; + const char *p; + p = *pp; + q = buf; + *q++ = *p++; /* first char is already tested */ + while (is_ident_first(*p) || isdigit(*p)) { + if ((q - buf) >= buf_size - 1) + return -1; + *q++ = *p++; + } + *pp = p; + *q = '\0'; + return 0; +} + +JSONValue json_parse_value2(const char **pp) +{ + char buf[128]; + const char *p; + JSONValue val, val1, tag; + + p = *pp; + skip_spaces(&p); + if (*p == '\0') { + return json_error_new("unexpected end of file"); + } + if (isdigit(*p)) { + val = json_int32_new(strtol(p, (char **)&p, 0)); + } else if (*p == '"') { + val = parse_string(&p); + } else if (*p == '{') { + p++; + val = json_object_new(); + for(;;) { + skip_spaces(&p); + if (*p == '}') { + p++; + break; + } + if (*p == '"') { + tag = parse_string(&p); + if (json_is_error(tag)) + return tag; + } else if (is_ident_first(*p)) { + if (parse_ident(buf, sizeof(buf), &p) < 0) + goto invalid_prop; + tag = json_string_new(buf); + } else { + goto invalid_prop; + } + // printf("property: %s\n", json_get_str(tag)); + if (tag.u.str->len == 0) { + invalid_prop: + return json_error_new("Invalid property name"); + } + skip_spaces(&p); + if (*p != ':') { + return json_error_new("':' expected"); + } + p++; + + val1 = json_parse_value2(&p); + json_object_set(val, tag.u.str->data, val1); + + skip_spaces(&p); + if (*p == ',') { + p++; + } else if (*p != '}') { + return json_error_new("expecting ',' or '}'"); + } + } + } else if (*p == '[') { + int idx; + + p++; + val = json_array_new(); + idx = 0; + for(;;) { + skip_spaces(&p); + if (*p == ']') { + p++; + break; + } + val1 = json_parse_value2(&p); + json_array_set(val, idx++, val1); + + skip_spaces(&p); + if (*p == ',') { + p++; + } else if (*p != ']') { + return json_error_new("expecting ',' or ']'"); + } + } + } else if (is_ident_first(*p)) { + if (parse_ident(buf, sizeof(buf), &p) < 0) + goto unknown_id; + if (!strcmp(buf, "null")) { + val = json_null_new(); + } else if (!strcmp(buf, "true")) { + val = json_bool_new(TRUE); + } else if (!strcmp(buf, "false")) { + val = json_bool_new(FALSE); + } else { + unknown_id: + return json_error_new("unknown identifier: '%s'", buf); + } + } else { + return json_error_new("unexpected character"); + } + *pp = p; + return val; +} + +JSONValue json_parse_value(const char *p) +{ + JSONValue val; + val = json_parse_value2(&p); + if (json_is_error(val)) + return val; + skip_spaces(&p); + if (*p != '\0') { + json_free(val); + return json_error_new("unexpected characters at the end"); + } + return val; +} + +JSONValue json_parse_value_len(const char *p, int len) +{ + char *str; + JSONValue val; + str = malloc(len + 1); + memcpy(str, p, len); + str[len] = '\0'; + val = json_parse_value(str); + free(str); + return val; +} diff --git a/json.h b/json.h new file mode 100644 index 0000000..148d050 --- /dev/null +++ b/json.h @@ -0,0 +1,132 @@ +/* + * Pseudo JSON parser + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef JSON_H +#define JSON_H + +typedef enum { + JSON_STR, + JSON_INT, + JSON_OBJ, + JSON_ARRAY, + JSON_BOOL, + JSON_NULL, + JSON_UNDEFINED, + JSON_EXCEPTION, +} JSONTypeEnum; + +typedef struct { + int len; + char data[0]; +} JSONString; + +typedef struct JSONValue { + JSONTypeEnum type; + union { + JSONString *str; + int int32; + BOOL b; + struct JSONObject *obj; + struct JSONArray *array; + } u; +} JSONValue; + +typedef struct JSONProperty { + JSONValue name; + JSONValue value; +} JSONProperty; + +typedef struct JSONObject { + int len; + int size; + JSONProperty *props; +} JSONObject; + +typedef struct JSONArray { + int len; + int size; + JSONValue *tab; +} JSONArray; + +JSONValue json_string_new2(const char *str, int len); +JSONValue json_string_new(const char *str); +JSONValue __attribute__((format(printf, 1, 2))) json_error_new(const char *fmt, ...); +void json_free(JSONValue val); + +JSONValue json_object_new(void); +JSONValue json_object_get(JSONValue val, const char *name); +int json_object_set(JSONValue val, const char *name, JSONValue prop_val); + +JSONValue json_array_new(void); +JSONValue json_array_get(JSONValue val, unsigned int idx); +int json_array_set(JSONValue val, unsigned int idx, JSONValue prop_val); + +static inline BOOL json_is_error(JSONValue val) +{ + return val.type == JSON_EXCEPTION; +} + +static inline BOOL json_is_undefined(JSONValue val) +{ + return val.type == JSON_UNDEFINED; +} + +static inline JSONValue json_undefined_new(void) +{ + JSONValue val; + val.type = JSON_UNDEFINED; + val.u.int32 = 0; + return val; +} + +static inline JSONValue json_null_new(void) +{ + JSONValue val; + val.type = JSON_NULL; + val.u.int32 = 0; + return val; +} + +static inline JSONValue json_int32_new(int v) +{ + JSONValue val; + val.type = JSON_INT; + val.u.int32 = v; + return val; +} + +static inline JSONValue json_bool_new(BOOL v) +{ + JSONValue val; + val.type = JSON_BOOL; + val.u.b = v; + return val; +} + +const char *json_get_str(JSONValue val); +const char *json_get_error(JSONValue val); + +JSONValue json_parse_value(const char *p); +JSONValue json_parse_value_len(const char *p, int len); + +#endif /* JSON_H */ diff --git a/list.h b/list.h new file mode 100644 index 0000000..3fbea04 --- /dev/null +++ b/list.h @@ -0,0 +1,94 @@ +/* + * Linux klist like system + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef LIST_H +#define LIST_H + +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +/* return the pointer of type 'type *' containing 'el' as field 'member' */ +#define list_entry(el, type, member) \ + ((type *)((uint8_t *)(el) - offsetof(type, member))) + +static inline void init_list_head(struct list_head *head) +{ + head->prev = head; + head->next = head; +} + +/* insert 'el' between 'prev' and 'next' */ +static inline void __list_add(struct list_head *el, + struct list_head *prev, struct list_head *next) +{ + prev->next = el; + el->prev = prev; + el->next = next; + next->prev = el; +} + +/* add 'el' at the head of the list 'head' (= after element head) */ +static inline void list_add(struct list_head *el, struct list_head *head) +{ + __list_add(el, head, head->next); +} + +/* add 'el' at the end of the list 'head' (= before element head) */ +static inline void list_add_tail(struct list_head *el, struct list_head *head) +{ + __list_add(el, head->prev, head); +} + +static inline void list_del(struct list_head *el) +{ + struct list_head *prev, *next; + prev = el->prev; + next = el->next; + prev->next = next; + next->prev = prev; + el->prev = NULL; /* fail safe */ + el->next = NULL; /* fail safe */ +} + +static inline int list_empty(struct list_head *el) +{ + return el->next == el; +} + +#define list_for_each(el, head) \ + for(el = (head)->next; el != (head); el = el->next) + +#define list_for_each_safe(el, el1, head) \ + for(el = (head)->next, el1 = el->next; el != (head); \ + el = el1, el1 = el->next) + +#define list_for_each_prev(el, head) \ + for(el = (head)->prev; el != (head); el = el->prev) + +#define list_for_each_prev_safe(el, el1, head) \ + for(el = (head)->prev, el1 = el->prev; el != (head); \ + el = el1, el1 = el->prev) + +#endif /* LIST_H */ diff --git a/machine.c b/machine.c new file mode 100644 index 0000000..09c7940 --- /dev/null +++ b/machine.c @@ -0,0 +1,640 @@ +/* + * VM utilities + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "iomem.h" +#include "virtio.h" +#include "machine.h" +#include "fs_utils.h" +#ifdef CONFIG_FS_NET +#include "fs_wget.h" +#endif + +void __attribute__((format(printf, 1, 2))) vm_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); +#ifdef EMSCRIPTEN + vprintf(fmt, ap); +#else + vfprintf(stderr, fmt, ap); +#endif + va_end(ap); +} + +int vm_get_int(JSONValue obj, const char *name, int *pval) +{ + JSONValue val; + val = json_object_get(obj, name); + if (json_is_undefined(val)) { + vm_error("expecting '%s' property\n", name); + return -1; + } + if (val.type != JSON_INT) { + vm_error("%s: integer expected\n", name); + return -1; + } + *pval = val.u.int32; + return 0; +} + +int vm_get_int_opt(JSONValue obj, const char *name, int *pval, int def_val) +{ + JSONValue val; + val = json_object_get(obj, name); + if (json_is_undefined(val)) { + *pval = def_val; + return 0; + } + if (val.type != JSON_INT) { + vm_error("%s: integer expected\n", name); + return -1; + } + *pval = val.u.int32; + return 0; +} + +static int vm_get_str2(JSONValue obj, const char *name, const char **pstr, + BOOL is_opt) +{ + JSONValue val; + val = json_object_get(obj, name); + if (json_is_undefined(val)) { + if (is_opt) { + *pstr = NULL; + return 0; + } else { + vm_error("expecting '%s' property\n", name); + return -1; + } + } + if (val.type != JSON_STR) { + vm_error("%s: string expected\n", name); + return -1; + } + *pstr = val.u.str->data; + return 0; +} + +static int vm_get_str(JSONValue obj, const char *name, const char **pstr) +{ + return vm_get_str2(obj, name, pstr, FALSE); +} + +static int vm_get_str_opt(JSONValue obj, const char *name, const char **pstr) +{ + return vm_get_str2(obj, name, pstr, TRUE); +} + +static char *strdup_null(const char *str) +{ + if (!str) + return NULL; + else + return strdup(str); +} + +/* currently only for "TZ" */ +static char *cmdline_subst(const char *cmdline) +{ + DynBuf dbuf; + const char *p; + char var_name[32], *q, buf[32]; + + dbuf_init(&dbuf); + p = cmdline; + while (*p != '\0') { + if (p[0] == '$' && p[1] == '{') { + p += 2; + q = var_name; + while (*p != '\0' && *p != '}') { + if ((q - var_name) < sizeof(var_name) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + if (*p == '}') + p++; + if (!strcmp(var_name, "TZ")) { + time_t ti; + struct tm tm; + int n, sg; + /* get the offset to UTC */ + time(&ti); + localtime_r(&ti, &tm); + n = tm.tm_gmtoff / 60; + sg = '-'; + if (n < 0) { + sg = '+'; + n = -n; + } + snprintf(buf, sizeof(buf), "UTC%c%02d:%02d", + sg, n / 60, n % 60); + dbuf_putstr(&dbuf, buf); + } + } else { + dbuf_putc(&dbuf, *p++); + } + } + dbuf_putc(&dbuf, 0); + return (char *)dbuf.buf; +} + +static BOOL find_name(const char *name, const char *name_list) +{ + size_t len; + const char *p, *r; + + p = name_list; + for(;;) { + r = strchr(p, ','); + if (!r) { + if (!strcmp(name, p)) + return TRUE; + break; + } else { + len = r - p; + if (len == strlen(name) && !memcmp(name, p, len)) + return TRUE; + p = r + 1; + } + } + return FALSE; +} + +static const VirtMachineClass *virt_machine_list[] = { +#if defined(EMSCRIPTEN) + /* only a single machine in the EMSCRIPTEN target */ +#ifndef CONFIG_X86EMU + &riscv_machine_class, +#endif +#else + &riscv_machine_class, +#endif /* !EMSCRIPTEN */ +#ifdef CONFIG_X86EMU + &pc_machine_class, +#endif + NULL, +}; + +static const VirtMachineClass *virt_machine_find_class(const char *machine_name) +{ + const VirtMachineClass *vmc, **pvmc; + + for(pvmc = virt_machine_list; *pvmc != NULL; pvmc++) { + vmc = *pvmc; + if (find_name(machine_name, vmc->machine_names)) + return vmc; + } + return NULL; +} + +static int virt_machine_parse_config(VirtMachineParams *p, + char *config_file_str, int len) +{ + int version, val; + const char *tag_name, *str; + char buf1[256]; + JSONValue cfg, obj, el; + + cfg = json_parse_value_len(config_file_str, len); + if (json_is_error(cfg)) { + vm_error("error: %s\n", json_get_error(cfg)); + json_free(cfg); + return -1; + } + + if (vm_get_int(cfg, "version", &version) < 0) + goto tag_fail; + if (version != VM_CONFIG_VERSION) { + if (version > VM_CONFIG_VERSION) { + vm_error("The emulator is too old to run this VM: please upgrade\n"); + return -1; + } else { + vm_error("The VM configuration file is too old for this emulator version: please upgrade the VM configuration file\n"); + return -1; + } + } + + if (vm_get_str(cfg, "machine", &str) < 0) + goto tag_fail; + p->machine_name = strdup(str); + p->vmc = virt_machine_find_class(p->machine_name); + if (!p->vmc) { + vm_error("Unknown machine name: %s\n", p->machine_name); + goto tag_fail; + } + p->vmc->virt_machine_set_defaults(p); + + tag_name = "memory_size"; + if (vm_get_int(cfg, tag_name, &val) < 0) + goto tag_fail; + p->ram_size = (uint64_t)val << 20; + + tag_name = "bios"; + if (vm_get_str_opt(cfg, tag_name, &str) < 0) + goto tag_fail; + if (str) { + p->files[VM_FILE_BIOS].filename = strdup(str); + } + + tag_name = "kernel"; + if (vm_get_str_opt(cfg, tag_name, &str) < 0) + goto tag_fail; + if (str) { + p->files[VM_FILE_KERNEL].filename = strdup(str); + } + + tag_name = "initrd"; + if (vm_get_str_opt(cfg, tag_name, &str) < 0) + goto tag_fail; + if (str) { + p->files[VM_FILE_INITRD].filename = strdup(str); + } + + if (vm_get_str_opt(cfg, "cmdline", &str) < 0) + goto tag_fail; + if (str) { + p->cmdline = cmdline_subst(str); + } + + for(;;) { + snprintf(buf1, sizeof(buf1), "drive%d", p->drive_count); + obj = json_object_get(cfg, buf1); + if (json_is_undefined(obj)) + break; + if (p->drive_count >= MAX_DRIVE_DEVICE) { + vm_error("Too many drives\n"); + return -1; + } + if (vm_get_str(obj, "file", &str) < 0) + goto tag_fail; + p->tab_drive[p->drive_count].filename = strdup(str); + if (vm_get_str_opt(obj, "device", &str) < 0) + goto tag_fail; + p->tab_drive[p->drive_count].device = strdup_null(str); + p->drive_count++; + } + + for(;;) { + snprintf(buf1, sizeof(buf1), "fs%d", p->fs_count); + obj = json_object_get(cfg, buf1); + if (json_is_undefined(obj)) + break; + if (p->fs_count >= MAX_DRIVE_DEVICE) { + vm_error("Too many filesystems\n"); + return -1; + } + if (vm_get_str(obj, "file", &str) < 0) + goto tag_fail; + p->tab_fs[p->fs_count].filename = strdup(str); + if (vm_get_str_opt(obj, "tag", &str) < 0) + goto tag_fail; + if (!str) { + if (p->fs_count == 0) + strcpy(buf1, "/dev/root"); + else + snprintf(buf1, sizeof(buf1), "/dev/root%d", p->fs_count); + str = buf1; + } + p->tab_fs[p->fs_count].tag = strdup(str); + p->fs_count++; + } + + for(;;) { + snprintf(buf1, sizeof(buf1), "eth%d", p->eth_count); + obj = json_object_get(cfg, buf1); + if (json_is_undefined(obj)) + break; + if (p->eth_count >= MAX_ETH_DEVICE) { + vm_error("Too many ethernet interfaces\n"); + return -1; + } + if (vm_get_str(obj, "driver", &str) < 0) + goto tag_fail; + p->tab_eth[p->eth_count].driver = strdup(str); + if (!strcmp(str, "tap")) { + if (vm_get_str(obj, "ifname", &str) < 0) + goto tag_fail; + p->tab_eth[p->eth_count].ifname = strdup(str); + } + p->eth_count++; + } + + p->display_device = NULL; + obj = json_object_get(cfg, "display0"); + if (!json_is_undefined(obj)) { + if (vm_get_str(obj, "device", &str) < 0) + goto tag_fail; + p->display_device = strdup(str); + if (vm_get_int(obj, "width", &p->width) < 0) + goto tag_fail; + if (vm_get_int(obj, "height", &p->height) < 0) + goto tag_fail; + if (vm_get_str_opt(obj, "vga_bios", &str) < 0) + goto tag_fail; + if (str) { + p->files[VM_FILE_VGA_BIOS].filename = strdup(str); + } + } + + if (vm_get_str_opt(cfg, "input_device", &str) < 0) + goto tag_fail; + p->input_device = strdup_null(str); + + if (vm_get_str_opt(cfg, "accel", &str) < 0) + goto tag_fail; + if (str) { + if (!strcmp(str, "none")) { + p->accel_enable = FALSE; + } else if (!strcmp(str, "auto")) { + p->accel_enable = TRUE; + } else { + vm_error("unsupported 'accel' config: %s\n", str); + return -1; + } + } + + tag_name = "rtc_local_time"; + el = json_object_get(cfg, tag_name); + if (!json_is_undefined(el)) { + if (el.type != JSON_BOOL) { + vm_error("%s: boolean expected\n", tag_name); + goto tag_fail; + } + p->rtc_local_time = el.u.b; + } + + json_free(cfg); + return 0; + tag_fail: + json_free(cfg); + return -1; +} + +typedef void FSLoadFileCB(void *opaque, uint8_t *buf, int buf_len); + +typedef struct { + VirtMachineParams *vm_params; + void (*start_cb)(void *opaque); + void *opaque; + + FSLoadFileCB *file_load_cb; + void *file_load_opaque; + int file_index; +} VMConfigLoadState; + +static void config_file_loaded(void *opaque, uint8_t *buf, int buf_len); +static void config_additional_file_load(VMConfigLoadState *s); +static void config_additional_file_load_cb(void *opaque, + uint8_t *buf, int buf_len); + +/* XXX: win32, URL */ +char *get_file_path(const char *base_filename, const char *filename) +{ + int len, len1; + char *fname, *p; + + if (!base_filename) + goto done; + if (strchr(filename, ':')) + goto done; /* full URL */ + if (filename[0] == '/') + goto done; + p = strrchr(base_filename, '/'); + if (!p) { + done: + return strdup(filename); + } + len = p + 1 - base_filename; + len1 = strlen(filename); + fname = malloc(len + len1 + 1); + memcpy(fname, base_filename, len); + memcpy(fname + len, filename, len1 + 1); + return fname; +} + + +#ifdef EMSCRIPTEN +static int load_file(uint8_t **pbuf, const char *filename) +{ + abort(); +} +#else +/* return -1 if error. */ +static int load_file(uint8_t **pbuf, const char *filename) +{ + FILE *f; + int size; + uint8_t *buf; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + buf = malloc(size); + if (fread(buf, 1, size, f) != size) { + fprintf(stderr, "%s: read error\n", filename); + exit(1); + } + fclose(f); + *pbuf = buf; + return size; +} +#endif + +#ifdef CONFIG_FS_NET +static void config_load_file_cb(void *opaque, int err, void *data, size_t size) +{ + VMConfigLoadState *s = opaque; + + // printf("err=%d data=%p size=%ld\n", err, data, size); + if (err < 0) { + vm_error("Error %d while loading file\n", -err); + exit(1); + } + s->file_load_cb(s->file_load_opaque, data, size); +} +#endif + +static void config_load_file(VMConfigLoadState *s, const char *filename, + FSLoadFileCB *cb, void *opaque) +{ + // printf("loading %s\n", filename); +#ifdef CONFIG_FS_NET + if (is_url(filename)) { + s->file_load_cb = cb; + s->file_load_opaque = opaque; + fs_wget(filename, NULL, NULL, s, config_load_file_cb, TRUE); + } else +#endif + { + uint8_t *buf; + int size; + size = load_file(&buf, filename); + cb(opaque, buf, size); + free(buf); + } +} + +void virt_machine_load_config_file(VirtMachineParams *p, + const char *filename, + void (*start_cb)(void *opaque), + void *opaque) +{ + VMConfigLoadState *s; + + s = mallocz(sizeof(*s)); + s->vm_params = p; + s->start_cb = start_cb; + s->opaque = opaque; + p->cfg_filename = strdup(filename); + + config_load_file(s, filename, config_file_loaded, s); +} + +static void config_file_loaded(void *opaque, uint8_t *buf, int buf_len) +{ + VMConfigLoadState *s = opaque; + VirtMachineParams *p = s->vm_params; + + if (virt_machine_parse_config(p, (char *)buf, buf_len) < 0) + exit(1); + + /* load the additional files */ + s->file_index = 0; + config_additional_file_load(s); +} + +static void config_additional_file_load(VMConfigLoadState *s) +{ + VirtMachineParams *p = s->vm_params; + while (s->file_index < VM_FILE_COUNT && + p->files[s->file_index].filename == NULL) { + s->file_index++; + } + if (s->file_index == VM_FILE_COUNT) { + if (s->start_cb) + s->start_cb(s->opaque); + free(s); + } else { + char *fname; + + fname = get_file_path(p->cfg_filename, + p->files[s->file_index].filename); + config_load_file(s, fname, + config_additional_file_load_cb, s); + free(fname); + } +} + +static void config_additional_file_load_cb(void *opaque, + uint8_t *buf, int buf_len) +{ + VMConfigLoadState *s = opaque; + VirtMachineParams *p = s->vm_params; + + p->files[s->file_index].buf = malloc(buf_len); + memcpy(p->files[s->file_index].buf, buf, buf_len); + p->files[s->file_index].len = buf_len; + + /* load the next files */ + s->file_index++; + config_additional_file_load(s); +} + +void vm_add_cmdline(VirtMachineParams *p, const char *cmdline) +{ + char *new_cmdline, *old_cmdline; + if (cmdline[0] == '!') { + new_cmdline = strdup(cmdline + 1); + } else { + old_cmdline = p->cmdline; + if (!old_cmdline) + old_cmdline = ""; + new_cmdline = malloc(strlen(old_cmdline) + 1 + strlen(cmdline) + 1); + strcpy(new_cmdline, old_cmdline); + strcat(new_cmdline, " "); + strcat(new_cmdline, cmdline); + } + free(p->cmdline); + p->cmdline = new_cmdline; +} + +void virt_machine_free_config(VirtMachineParams *p) +{ + int i; + + free(p->machine_name); + free(p->cmdline); + for(i = 0; i < VM_FILE_COUNT; i++) { + free(p->files[i].filename); + free(p->files[i].buf); + } + for(i = 0; i < p->drive_count; i++) { + free(p->tab_drive[i].filename); + free(p->tab_drive[i].device); + } + for(i = 0; i < p->fs_count; i++) { + free(p->tab_fs[i].filename); + free(p->tab_fs[i].tag); + } + for(i = 0; i < p->eth_count; i++) { + free(p->tab_eth[i].driver); + free(p->tab_eth[i].ifname); + } + free(p->input_device); + free(p->display_device); + free(p->cfg_filename); +} + +VirtMachine *virt_machine_init(const VirtMachineParams *p) +{ + const VirtMachineClass *vmc = p->vmc; + return vmc->virt_machine_init(p); +} + +void virt_machine_set_defaults(VirtMachineParams *p) +{ + memset(p, 0, sizeof(*p)); +} + +void virt_machine_end(VirtMachine *s) +{ + s->vmc->virt_machine_end(s); +} diff --git a/machine.h b/machine.h new file mode 100644 index 0000000..76217ee --- /dev/null +++ b/machine.h @@ -0,0 +1,196 @@ +/* + * VM definitions + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "json.h" + +typedef struct FBDevice FBDevice; + +typedef void SimpleFBDrawFunc(FBDevice *fb_dev, void *opaque, + int x, int y, int w, int h); + +struct FBDevice { + /* the following is set by the device */ + int width; + int height; + int stride; /* current stride in bytes */ + uint8_t *fb_data; /* current pointer to the pixel data */ + int fb_size; /* frame buffer memory size (info only) */ + void *device_opaque; + void (*refresh)(struct FBDevice *fb_dev, + SimpleFBDrawFunc *redraw_func, void *opaque); +}; + +#define MAX_DRIVE_DEVICE 4 +#define MAX_FS_DEVICE 4 +#define MAX_ETH_DEVICE 1 + +#define VM_CONFIG_VERSION 1 + +typedef enum { + VM_FILE_BIOS, + VM_FILE_VGA_BIOS, + VM_FILE_KERNEL, + VM_FILE_INITRD, + + VM_FILE_COUNT, +} VMFileTypeEnum; + +typedef struct { + char *filename; + uint8_t *buf; + int len; +} VMFileEntry; + +typedef struct { + char *device; + char *filename; + BlockDevice *block_dev; +} VMDriveEntry; + +typedef struct { + char *device; + char *tag; /* 9p mount tag */ + char *filename; + FSDevice *fs_dev; +} VMFSEntry; + +typedef struct { + char *driver; + char *ifname; + EthernetDevice *net; +} VMEthEntry; + +typedef struct VirtMachineClass VirtMachineClass; + +typedef struct { + char *cfg_filename; + const VirtMachineClass *vmc; + char *machine_name; + uint64_t ram_size; + BOOL rtc_real_time; + BOOL rtc_local_time; + char *display_device; /* NULL means no display */ + int width, height; /* graphic width & height */ + CharacterDevice *console; + VMDriveEntry tab_drive[MAX_DRIVE_DEVICE]; + int drive_count; + VMFSEntry tab_fs[MAX_FS_DEVICE]; + int fs_count; + VMEthEntry tab_eth[MAX_ETH_DEVICE]; + int eth_count; + + char *cmdline; /* bios or kernel command line */ + BOOL accel_enable; /* enable acceleration (KVM) */ + char *input_device; /* NULL means no input */ + + /* kernel, bios and other auxiliary files */ + VMFileEntry files[VM_FILE_COUNT]; +} VirtMachineParams; + +typedef struct VirtMachine { + const VirtMachineClass *vmc; + /* network */ + EthernetDevice *net; + /* console */ + VIRTIODevice *console_dev; + CharacterDevice *console; + /* graphics */ + FBDevice *fb_dev; +} VirtMachine; + +struct VirtMachineClass { + const char *machine_names; + void (*virt_machine_set_defaults)(VirtMachineParams *p); + VirtMachine *(*virt_machine_init)(const VirtMachineParams *p); + void (*virt_machine_end)(VirtMachine *s); + int (*virt_machine_get_sleep_duration)(VirtMachine *s, int delay); + void (*virt_machine_interp)(VirtMachine *s, int max_exec_cycle); + BOOL (*vm_mouse_is_absolute)(VirtMachine *s); + void (*vm_send_mouse_event)(VirtMachine *s1, int dx, int dy, int dz, + unsigned int buttons); + void (*vm_send_key_event)(VirtMachine *s1, BOOL is_down, uint16_t key_code); +}; + +extern const VirtMachineClass riscv_machine_class; +extern const VirtMachineClass pc_machine_class; + +void __attribute__((format(printf, 1, 2))) vm_error(const char *fmt, ...); +int vm_get_int(JSONValue obj, const char *name, int *pval); +int vm_get_int_opt(JSONValue obj, const char *name, int *pval, int def_val); + +void virt_machine_set_defaults(VirtMachineParams *p); +void virt_machine_load_config_file(VirtMachineParams *p, + const char *filename, + void (*start_cb)(void *opaque), + void *opaque); +void vm_add_cmdline(VirtMachineParams *p, const char *cmdline); +char *get_file_path(const char *base_filename, const char *filename); +void virt_machine_free_config(VirtMachineParams *p); +VirtMachine *virt_machine_init(const VirtMachineParams *p); +void virt_machine_end(VirtMachine *s); +static inline int virt_machine_get_sleep_duration(VirtMachine *s, int delay) +{ + return s->vmc->virt_machine_get_sleep_duration(s, delay); +} +static inline void virt_machine_interp(VirtMachine *s, int max_exec_cycle) +{ + s->vmc->virt_machine_interp(s, max_exec_cycle); +} +static inline BOOL vm_mouse_is_absolute(VirtMachine *s) +{ + return s->vmc->vm_mouse_is_absolute(s); +} +static inline void vm_send_mouse_event(VirtMachine *s1, int dx, int dy, int dz, + unsigned int buttons) +{ + s1->vmc->vm_send_mouse_event(s1, dx, dy, dz, buttons); +} +static inline void vm_send_key_event(VirtMachine *s1, BOOL is_down, uint16_t key_code) +{ + s1->vmc->vm_send_key_event(s1, is_down, key_code); +} + +/* gui */ +void sdl_refresh(VirtMachine *m); +void sdl_init(int width, int height); + +/* simplefb.c */ +typedef struct SimpleFBState SimpleFBState; +SimpleFBState *simplefb_init(PhysMemoryMap *map, uint64_t phys_addr, + FBDevice *fb_dev, int width, int height); +void simplefb_refresh(FBDevice *fb_dev, + SimpleFBDrawFunc *redraw_func, void *opaque, + PhysMemoryRange *mem_range, + int fb_page_count); + +/* vga.c */ +typedef struct VGAState VGAState; +VGAState *pci_vga_init(PCIBus *bus, FBDevice *fb_dev, + int width, int height, + const uint8_t *vga_rom_buf, int vga_rom_size); + +/* block_net.c */ +BlockDevice *block_device_init_http(const char *url, + int max_cache_size_kb, + void (*start_cb)(void *opaque), + void *start_opaque); diff --git a/netinit.sh b/netinit.sh new file mode 100755 index 0000000..c2dadf9 --- /dev/null +++ b/netinit.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# +# RISCVEMU Ethernet bridge and NAT configuration (run with sudo) +# +# Copyright (c) 2017 Fabrice Bellard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +# host network interface connected to Internet (change it) +internet_ifname="enp0s20f0u1" + +# setup bridge interface +ip link add br0 type bridge +# create and add tap0 interface to bridge +ip tuntap add dev tap0 mode tap user $USER +ip link set tap0 master br0 + +ip link set dev br0 up +ip link set dev tap0 up +ifconfig br0 192.168.3.1 + +# setup NAT to access to Internet +echo 1 > /proc/sys/net/ipv4/ip_forward +# delete forwarding reject rule if present +#iptables -D FORWARD 1 +iptables -t nat -A POSTROUTING -o $internet_ifname -j MASQUERADE diff --git a/pci.c b/pci.c new file mode 100644 index 0000000..d37b737 --- /dev/null +++ b/pci.c @@ -0,0 +1,588 @@ +/* + * Simple PCI bus driver + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "pci.h" + +//#define DEBUG_CONFIG + +typedef struct { + uint32_t size; /* 0 means no mapping defined */ + uint8_t type; + uint8_t enabled; /* true if mapping is enabled */ + void *opaque; + PCIBarSetFunc *bar_set; +} PCIIORegion; + +struct PCIDevice { + PCIBus *bus; + uint8_t devfn; + IRQSignal irq[4]; + uint8_t config[256]; + uint8_t next_cap_offset; /* offset of the next capability */ + char *name; /* for debug only */ + PCIIORegion io_regions[PCI_NUM_REGIONS]; +}; + +struct PCIBus { + int bus_num; + PCIDevice *device[256]; + PhysMemoryMap *mem_map; + PhysMemoryMap *port_map; + uint32_t irq_state[4][8]; /* one bit per device */ + IRQSignal irq[4]; +}; + +static int bus_map_irq(PCIDevice *d, int irq_num) +{ + int slot_addend; + slot_addend = (d->devfn >> 3) - 1; + return (irq_num + slot_addend) & 3; +} + +static void pci_device_set_irq(void *opaque, int irq_num, int level) +{ + PCIDevice *d = opaque; + PCIBus *b = d->bus; + uint32_t mask; + int i, irq_level; + + // printf("%s: pci_device_seq_irq: %d %d\n", d->name, irq_num, level); + irq_num = bus_map_irq(d, irq_num); + mask = 1 << (d->devfn & 0x1f); + if (level) + b->irq_state[irq_num][d->devfn >> 5] |= mask; + else + b->irq_state[irq_num][d->devfn >> 5] &= ~mask; + + /* compute the IRQ state */ + mask = 0; + for(i = 0; i < 8; i++) + mask |= b->irq_state[irq_num][i]; + irq_level = (mask != 0); + set_irq(&b->irq[irq_num], irq_level); +} + +static int devfn_alloc(PCIBus *b) +{ + int devfn; + for(devfn = 0; devfn < 256; devfn += 8) { + if (!b->device[devfn]) + return devfn; + } + return -1; +} + +/* devfn < 0 means to allocate it */ +PCIDevice *pci_register_device(PCIBus *b, const char *name, int devfn, + uint16_t vendor_id, uint16_t device_id, + uint8_t revision, uint16_t class_id) +{ + PCIDevice *d; + int i; + + if (devfn < 0) { + devfn = devfn_alloc(b); + if (devfn < 0) + return NULL; + } + if (b->device[devfn]) + return NULL; + + d = mallocz(sizeof(PCIDevice)); + d->bus = b; + d->name = strdup(name); + d->devfn = devfn; + + put_le16(d->config + 0x00, vendor_id); + put_le16(d->config + 0x02, device_id); + d->config[0x08] = revision; + put_le16(d->config + 0x0a, class_id); + d->config[0x0e] = 0x00; /* header type */ + d->next_cap_offset = 0x40; + + for(i = 0; i < 4; i++) + irq_init(&d->irq[i], pci_device_set_irq, d, i); + b->device[devfn] = d; + + return d; +} + +IRQSignal *pci_device_get_irq(PCIDevice *d, unsigned int irq_num) +{ + assert(irq_num < 4); + return &d->irq[irq_num]; +} + +static uint32_t pci_device_config_read(PCIDevice *d, uint32_t addr, + int size_log2) +{ + uint32_t val; + switch(size_log2) { + case 0: + val = *(uint8_t *)(d->config + addr); + break; + case 1: + /* Note: may be unaligned */ + if (addr <= 0xfe) + val = get_le16(d->config + addr); + else + val = *(uint8_t *)(d->config + addr); + break; + case 2: + /* always aligned */ + val = get_le32(d->config + addr); + break; + default: + abort(); + } +#ifdef DEBUG_CONFIG + printf("pci_config_read: dev=%s addr=0x%02x val=0x%x s=%d\n", + d->name, addr, val, 1 << size_log2); +#endif + return val; +} + +PhysMemoryMap *pci_device_get_mem_map(PCIDevice *d) +{ + return d->bus->mem_map; +} + +PhysMemoryMap *pci_device_get_port_map(PCIDevice *d) +{ + return d->bus->port_map; +} + +void pci_register_bar(PCIDevice *d, unsigned int bar_num, + uint32_t size, int type, + void *opaque, PCIBarSetFunc *bar_set) +{ + PCIIORegion *r; + uint32_t val, config_addr; + + assert(bar_num < PCI_NUM_REGIONS); + assert((size & (size - 1)) == 0); /* power of two */ + assert(size >= 4); + r = &d->io_regions[bar_num]; + assert(r->size == 0); + r->size = size; + r->type = type; + r->enabled = FALSE; + r->opaque = opaque; + r->bar_set = bar_set; + /* set the config value */ + val = 0; + if (bar_num == PCI_ROM_SLOT) { + config_addr = 0x30; + } else { + val |= r->type; + config_addr = 0x10 + 4 * bar_num; + } + put_le32(&d->config[config_addr], val); +} + +static void pci_update_mappings(PCIDevice *d) +{ + int cmd, i, offset; + uint32_t new_addr; + BOOL new_enabled; + PCIIORegion *r; + + cmd = get_le16(&d->config[PCI_COMMAND]); + + for(i = 0; i < PCI_NUM_REGIONS; i++) { + r = &d->io_regions[i]; + if (i == PCI_ROM_SLOT) { + offset = 0x30; + } else { + offset = 0x10 + i * 4; + } + new_addr = get_le32(&d->config[offset]); + new_enabled = FALSE; + if (r->size != 0) { + if ((r->type & PCI_ADDRESS_SPACE_IO) && + (cmd & PCI_COMMAND_IO)) { + new_enabled = TRUE; + } else { + if (cmd & PCI_COMMAND_MEMORY) { + if (i == PCI_ROM_SLOT) { + new_enabled = (new_addr & 1); + } else { + new_enabled = TRUE; + } + } + } + } + if (new_enabled) { + /* new address */ + new_addr = get_le32(&d->config[offset]) & ~(r->size - 1); + r->bar_set(r->opaque, i, new_addr, TRUE); + r->enabled = TRUE; + } else if (r->enabled) { + r->bar_set(r->opaque, i, 0, FALSE); + r->enabled = FALSE; + } + } +} + +/* return != 0 if write is not handled */ +static int pci_write_bar(PCIDevice *d, uint32_t addr, + uint32_t val) +{ + PCIIORegion *r; + int reg; + + if (addr == 0x30) + reg = PCI_ROM_SLOT; + else + reg = (addr - 0x10) >> 2; + // printf("%s: write bar addr=%x data=%x\n", d->name, addr, val); + r = &d->io_regions[reg]; + if (r->size == 0) + return -1; + if (reg == PCI_ROM_SLOT) { + val = val & ((~(r->size - 1)) | 1); + } else { + val = (val & ~(r->size - 1)) | r->type; + } + put_le32(d->config + addr, val); + pci_update_mappings(d); + return 0; +} + +static void pci_device_config_write8(PCIDevice *d, uint32_t addr, + uint32_t data) +{ + int can_write; + + if (addr == PCI_STATUS || addr == (PCI_STATUS + 1)) { + /* write 1 reset bits */ + d->config[addr] &= ~data; + return; + } + + switch(d->config[0x0e]) { + case 0x00: + case 0x80: + switch(addr) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0e: + case 0x10 ... 0x27: /* base */ + case 0x30 ... 0x33: /* rom */ + case 0x3d: + can_write = 0; + break; + default: + can_write = 1; + break; + } + break; + default: + case 0x01: + switch(addr) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0e: + case 0x38 ... 0x3b: /* rom */ + case 0x3d: + can_write = 0; + break; + default: + can_write = 1; + break; + } + break; + } + if (can_write) + d->config[addr] = data; +} + + +static void pci_device_config_write(PCIDevice *d, uint32_t addr, + uint32_t data, int size_log2) +{ + int size, i; + uint32_t addr1; + +#ifdef DEBUG_CONFIG + printf("pci_config_write: dev=%s addr=0x%02x val=0x%x s=%d\n", + d->name, addr, data, 1 << size_log2); +#endif + if (size_log2 == 2 && + ((addr >= 0x10 && addr < 0x10 + 4 * 6) || + addr == 0x30)) { + if (pci_write_bar(d, addr, data) == 0) + return; + } + size = 1 << size_log2; + for(i = 0; i < size; i++) { + addr1 = addr + i; + if (addr1 <= 0xff) { + pci_device_config_write8(d, addr1, (data >> (i * 8)) & 0xff); + } + } + if (PCI_COMMAND >= addr && PCI_COMMAND < addr + size) { + pci_update_mappings(d); + } +} + + +static void pci_data_write(PCIBus *s, uint32_t addr, + uint32_t data, int size_log2) +{ + PCIDevice *d; + int bus_num, devfn, config_addr; + + bus_num = (addr >> 16) & 0xff; + if (bus_num != s->bus_num) + return; + devfn = (addr >> 8) & 0xff; + d = s->device[devfn]; + if (!d) + return; + config_addr = addr & 0xff; + pci_device_config_write(d, config_addr, data, size_log2); +} + +static const uint32_t val_ones[3] = { 0xff, 0xffff, 0xffffffff }; + +static uint32_t pci_data_read(PCIBus *s, uint32_t addr, int size_log2) +{ + PCIDevice *d; + int bus_num, devfn, config_addr; + + bus_num = (addr >> 16) & 0xff; + if (bus_num != s->bus_num) + return val_ones[size_log2]; + devfn = (addr >> 8) & 0xff; + d = s->device[devfn]; + if (!d) + return val_ones[size_log2]; + config_addr = addr & 0xff; + return pci_device_config_read(d, config_addr, size_log2); +} + +/* warning: only valid for one DEVIO page. Return NULL if no memory at + the given address */ +uint8_t *pci_device_get_dma_ptr(PCIDevice *d, uint64_t addr, BOOL is_rw) +{ + return phys_mem_get_ram_ptr(d->bus->mem_map, addr, is_rw); +} + +void pci_device_set_config8(PCIDevice *d, uint8_t addr, uint8_t val) +{ + d->config[addr] = val; +} + +void pci_device_set_config16(PCIDevice *d, uint8_t addr, uint16_t val) +{ + put_le16(&d->config[addr], val); +} + +int pci_device_get_devfn(PCIDevice *d) +{ + return d->devfn; +} + +/* return the offset of the capability or < 0 if error. */ +int pci_add_capability(PCIDevice *d, const uint8_t *buf, int size) +{ + int offset; + + offset = d->next_cap_offset; + if ((offset + size) > 256) + return -1; + d->next_cap_offset += size; + d->config[PCI_STATUS] |= PCI_STATUS_CAP_LIST; + memcpy(d->config + offset, buf, size); + d->config[offset + 1] = d->config[PCI_CAPABILITY_LIST]; + d->config[PCI_CAPABILITY_LIST] = offset; + return offset; +} + +/* i440FX host bridge */ + +struct I440FXState { + PCIBus *pci_bus; + PCIDevice *pci_dev; + PCIDevice *piix3_dev; + uint32_t config_reg; + uint8_t pic_irq_state[16]; + IRQSignal *pic_irqs; /* 16 irqs */ +}; + +static void i440fx_write_addr(void *opaque, uint32_t offset, + uint32_t data, int size_log2) +{ + I440FXState *s = opaque; + s->config_reg = data; +} + +static uint32_t i440fx_read_addr(void *opaque, uint32_t offset, int size_log2) +{ + I440FXState *s = opaque; + return s->config_reg; +} + +static void i440fx_write_data(void *opaque, uint32_t offset, + uint32_t data, int size_log2) +{ + I440FXState *s = opaque; + if (s->config_reg & 0x80000000) { + if (size_log2 == 2) { + /* it is simpler to assume 32 bit config accesses are + always aligned */ + pci_data_write(s->pci_bus, s->config_reg & ~3, data, size_log2); + } else { + pci_data_write(s->pci_bus, s->config_reg | offset, data, size_log2); + } + } +} + +static uint32_t i440fx_read_data(void *opaque, uint32_t offset, int size_log2) +{ + I440FXState *s = opaque; + if (!(s->config_reg & 0x80000000)) + return val_ones[size_log2]; + if (size_log2 == 2) { + /* it is simpler to assume 32 bit config accesses are + always aligned */ + return pci_data_read(s->pci_bus, s->config_reg & ~3, size_log2); + } else { + return pci_data_read(s->pci_bus, s->config_reg | offset, size_log2); + } +} + +static void i440fx_set_irq(void *opaque, int irq_num, int irq_level) +{ + I440FXState *s = opaque; + PCIDevice *hd = s->piix3_dev; + int pic_irq; + + /* map to the PIC irq (different IRQs can be mapped to the same + PIC irq) */ + hd->config[0x60 + irq_num] &= ~0x80; + pic_irq = hd->config[0x60 + irq_num]; + if (pic_irq < 16) { + if (irq_level) + s->pic_irq_state[pic_irq] |= 1 << irq_num; + else + s->pic_irq_state[pic_irq] &= ~(1 << irq_num); + set_irq(&s->pic_irqs[pic_irq], (s->pic_irq_state[pic_irq] != 0)); + } +} + +I440FXState *i440fx_init(PCIBus **pbus, int *ppiix3_devfn, + PhysMemoryMap *mem_map, PhysMemoryMap *port_map, + IRQSignal *pic_irqs) +{ + I440FXState *s; + PCIBus *b; + PCIDevice *d; + int i; + + s = mallocz(sizeof(*s)); + + b = mallocz(sizeof(PCIBus)); + b->bus_num = 0; + b->mem_map = mem_map; + b->port_map = port_map; + + s->pic_irqs = pic_irqs; + for(i = 0; i < 4; i++) { + irq_init(&b->irq[i], i440fx_set_irq, s, i); + } + + cpu_register_device(port_map, 0xcf8, 1, s, i440fx_read_addr, i440fx_write_addr, + DEVIO_SIZE32); + cpu_register_device(port_map, 0xcfc, 4, s, i440fx_read_data, i440fx_write_data, + DEVIO_SIZE8 | DEVIO_SIZE16 | DEVIO_SIZE32); + d = pci_register_device(b, "i440FX", 0, 0x8086, 0x1237, 0x02, 0x0600); + put_le16(&d->config[PCI_SUBSYSTEM_VENDOR_ID], 0x1af4); /* Red Hat, Inc. */ + put_le16(&d->config[PCI_SUBSYSTEM_ID], 0x1100); /* QEMU virtual machine */ + + s->pci_dev = d; + s->pci_bus = b; + + s->piix3_dev = pci_register_device(b, "PIIX3", 8, 0x8086, 0x7000, + 0x00, 0x0601); + pci_device_set_config8(s->piix3_dev, 0x0e, 0x80); /* header type */ + + *pbus = b; + *ppiix3_devfn = s->piix3_dev->devfn; + return s; +} + +/* in case no BIOS is used, map the interrupts. */ +void i440fx_map_interrupts(I440FXState *s, uint8_t *elcr, + const uint8_t *pci_irqs) +{ + PCIBus *b = s->pci_bus; + PCIDevice *d, *hd; + int irq_num, pic_irq, devfn, i; + + /* set a default PCI IRQ mapping to PIC IRQs */ + hd = s->piix3_dev; + + elcr[0] = 0; + elcr[1] = 0; + for(i = 0; i < 4; i++) { + irq_num = pci_irqs[i]; + hd->config[0x60 + i] = irq_num; + elcr[irq_num >> 3] |= (1 << (irq_num & 7)); + } + + for(devfn = 0; devfn < 256; devfn++) { + d = b->device[devfn]; + if (!d) + continue; + if (d->config[PCI_INTERRUPT_PIN]) { + irq_num = 0; + irq_num = bus_map_irq(d, irq_num); + pic_irq = hd->config[0x60 + irq_num]; + if (pic_irq < 16) { + d->config[PCI_INTERRUPT_LINE] = pic_irq; + } + } + } +} diff --git a/pci.h b/pci.h new file mode 100644 index 0000000..39d6efe --- /dev/null +++ b/pci.h @@ -0,0 +1,81 @@ +/* + * Simple PCI bus driver + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef PCI_H +#define PCI_H + +#include "iomem.h" + +typedef struct PCIBus PCIBus; +typedef struct PCIDevice PCIDevice; + +/* bar type */ +#define PCI_ADDRESS_SPACE_MEM 0x00 +#define PCI_ADDRESS_SPACE_IO 0x01 +#define PCI_ADDRESS_SPACE_MEM_PREFETCH 0x08 + +#define PCI_ROM_SLOT 6 +#define PCI_NUM_REGIONS 7 + +/* PCI config addresses */ +#define PCI_VENDOR_ID 0x00 /* 16 bits */ +#define PCI_DEVICE_ID 0x02 /* 16 bits */ +#define PCI_COMMAND 0x04 /* 16 bits */ +#define PCI_COMMAND_IO (1 << 0) +#define PCI_COMMAND_MEMORY (1 << 1) +#define PCI_STATUS 0x06 /* 16 bits */ +#define PCI_STATUS_CAP_LIST (1 << 4) +#define PCI_CLASS_PROG 0x09 +#define PCI_SUBSYSTEM_VENDOR_ID 0x2c /* 16 bits */ +#define PCI_SUBSYSTEM_ID 0x2e /* 16 bits */ +#define PCI_CAPABILITY_LIST 0x34 /* 8 bits */ +#define PCI_INTERRUPT_LINE 0x3c /* 8 bits */ +#define PCI_INTERRUPT_PIN 0x3d /* 8 bits */ + +typedef void PCIBarSetFunc(void *opaque, int bar_num, uint32_t addr, + BOOL enabled); + +PCIDevice *pci_register_device(PCIBus *b, const char *name, int devfn, + uint16_t vendor_id, uint16_t device_id, + uint8_t revision, uint16_t class_id); +PhysMemoryMap *pci_device_get_mem_map(PCIDevice *d); +PhysMemoryMap *pci_device_get_port_map(PCIDevice *d); +void pci_register_bar(PCIDevice *d, unsigned int bar_num, + uint32_t size, int type, + void *opaque, PCIBarSetFunc *bar_set); +IRQSignal *pci_device_get_irq(PCIDevice *d, unsigned int irq_num); +uint8_t *pci_device_get_dma_ptr(PCIDevice *d, uint64_t addr, BOOL is_rw); +void pci_device_set_config8(PCIDevice *d, uint8_t addr, uint8_t val); +void pci_device_set_config16(PCIDevice *d, uint8_t addr, uint16_t val); +int pci_device_get_devfn(PCIDevice *d); +int pci_add_capability(PCIDevice *d, const uint8_t *buf, int size); + +typedef struct I440FXState I440FXState; + +I440FXState *i440fx_init(PCIBus **pbus, int *ppiix3_devfn, + PhysMemoryMap *mem_map, PhysMemoryMap *port_map, + IRQSignal *pic_irqs); +void i440fx_map_interrupts(I440FXState *s, uint8_t *elcr, + const uint8_t *pci_irqs); + +#endif /* PCI_H */ diff --git a/pckbd.c b/pckbd.c new file mode 100644 index 0000000..e1c73d7 --- /dev/null +++ b/pckbd.c @@ -0,0 +1,342 @@ +/* + * QEMU PC keyboard emulation + * + * Copyright (c) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "iomem.h" +#include "ps2.h" +#include "virtio.h" +#include "machine.h" + +/* debug PC keyboard */ +//#define DEBUG_KBD + +/* debug PC keyboard : only mouse */ +//#define DEBUG_MOUSE + +/* Keyboard Controller Commands */ +#define KBD_CCMD_READ_MODE 0x20 /* Read mode bits */ +#define KBD_CCMD_WRITE_MODE 0x60 /* Write mode bits */ +#define KBD_CCMD_GET_VERSION 0xA1 /* Get controller version */ +#define KBD_CCMD_MOUSE_DISABLE 0xA7 /* Disable mouse interface */ +#define KBD_CCMD_MOUSE_ENABLE 0xA8 /* Enable mouse interface */ +#define KBD_CCMD_TEST_MOUSE 0xA9 /* Mouse interface test */ +#define KBD_CCMD_SELF_TEST 0xAA /* Controller self test */ +#define KBD_CCMD_KBD_TEST 0xAB /* Keyboard interface test */ +#define KBD_CCMD_KBD_DISABLE 0xAD /* Keyboard interface disable */ +#define KBD_CCMD_KBD_ENABLE 0xAE /* Keyboard interface enable */ +#define KBD_CCMD_READ_INPORT 0xC0 /* read input port */ +#define KBD_CCMD_READ_OUTPORT 0xD0 /* read output port */ +#define KBD_CCMD_WRITE_OUTPORT 0xD1 /* write output port */ +#define KBD_CCMD_WRITE_OBUF 0xD2 +#define KBD_CCMD_WRITE_AUX_OBUF 0xD3 /* Write to output buffer as if + initiated by the auxiliary device */ +#define KBD_CCMD_WRITE_MOUSE 0xD4 /* Write the following byte to the mouse */ +#define KBD_CCMD_DISABLE_A20 0xDD /* HP vectra only ? */ +#define KBD_CCMD_ENABLE_A20 0xDF /* HP vectra only ? */ +#define KBD_CCMD_RESET 0xFE + +/* Status Register Bits */ +#define KBD_STAT_OBF 0x01 /* Keyboard output buffer full */ +#define KBD_STAT_IBF 0x02 /* Keyboard input buffer full */ +#define KBD_STAT_SELFTEST 0x04 /* Self test successful */ +#define KBD_STAT_CMD 0x08 /* Last write was a command write (0=data) */ +#define KBD_STAT_UNLOCKED 0x10 /* Zero if keyboard locked */ +#define KBD_STAT_MOUSE_OBF 0x20 /* Mouse output buffer full */ +#define KBD_STAT_GTO 0x40 /* General receive/xmit timeout */ +#define KBD_STAT_PERR 0x80 /* Parity error */ + +/* Controller Mode Register Bits */ +#define KBD_MODE_KBD_INT 0x01 /* Keyboard data generate IRQ1 */ +#define KBD_MODE_MOUSE_INT 0x02 /* Mouse data generate IRQ12 */ +#define KBD_MODE_SYS 0x04 /* The system flag (?) */ +#define KBD_MODE_NO_KEYLOCK 0x08 /* The keylock doesn't affect the keyboard if set */ +#define KBD_MODE_DISABLE_KBD 0x10 /* Disable keyboard interface */ +#define KBD_MODE_DISABLE_MOUSE 0x20 /* Disable mouse interface */ +#define KBD_MODE_KCC 0x40 /* Scan code conversion to PC format */ +#define KBD_MODE_RFU 0x80 + +#define KBD_PENDING_KBD 1 +#define KBD_PENDING_AUX 2 + +struct KBDState { + uint8_t write_cmd; /* if non zero, write data to port 60 is expected */ + uint8_t status; + uint8_t mode; + /* Bitmask of devices with data available. */ + uint8_t pending; + PS2KbdState *kbd; + PS2MouseState *mouse; + + IRQSignal *irq_kbd; + IRQSignal *irq_mouse; +}; + +static void qemu_system_reset_request(void) +{ + printf("system_reset_request\n"); + exit(1); + /* XXX */ +} + +static void ioport_set_a20(int val) +{ +} + +static int ioport_get_a20(void) +{ + return 1; +} + +/* update irq and KBD_STAT_[MOUSE_]OBF */ +/* XXX: not generating the irqs if KBD_MODE_DISABLE_KBD is set may be + incorrect, but it avoids having to simulate exact delays */ +static void kbd_update_irq(KBDState *s) +{ + int irq_kbd_level, irq_mouse_level; + + irq_kbd_level = 0; + irq_mouse_level = 0; + s->status &= ~(KBD_STAT_OBF | KBD_STAT_MOUSE_OBF); + if (s->pending) { + s->status |= KBD_STAT_OBF; + /* kbd data takes priority over aux data. */ + if (s->pending == KBD_PENDING_AUX) { + s->status |= KBD_STAT_MOUSE_OBF; + if (s->mode & KBD_MODE_MOUSE_INT) + irq_mouse_level = 1; + } else { + if ((s->mode & KBD_MODE_KBD_INT) && + !(s->mode & KBD_MODE_DISABLE_KBD)) + irq_kbd_level = 1; + } + } + set_irq(s->irq_kbd, irq_kbd_level); + set_irq(s->irq_mouse, irq_mouse_level); +} + +static void kbd_update_kbd_irq(void *opaque, int level) +{ + KBDState *s = (KBDState *)opaque; + + if (level) + s->pending |= KBD_PENDING_KBD; + else + s->pending &= ~KBD_PENDING_KBD; + kbd_update_irq(s); +} + +static void kbd_update_aux_irq(void *opaque, int level) +{ + KBDState *s = (KBDState *)opaque; + + if (level) + s->pending |= KBD_PENDING_AUX; + else + s->pending &= ~KBD_PENDING_AUX; + kbd_update_irq(s); +} + +static uint32_t kbd_read_status(void *opaque, uint32_t addr, int size_log2) +{ + KBDState *s = opaque; + int val; + val = s->status; +#if defined(DEBUG_KBD) + printf("kbd: read status=0x%02x\n", val); +#endif + return val; +} + +static void kbd_queue(KBDState *s, int b, int aux) +{ + if (aux) + ps2_queue(s->mouse, b); + else + ps2_queue(s->kbd, b); +} + +static void kbd_write_command(void *opaque, uint32_t addr, uint32_t val, + int size_log2) +{ + KBDState *s = opaque; + +#if defined(DEBUG_KBD) + printf("kbd: write cmd=0x%02x\n", val); +#endif + switch(val) { + case KBD_CCMD_READ_MODE: + kbd_queue(s, s->mode, 1); + break; + case KBD_CCMD_WRITE_MODE: + case KBD_CCMD_WRITE_OBUF: + case KBD_CCMD_WRITE_AUX_OBUF: + case KBD_CCMD_WRITE_MOUSE: + case KBD_CCMD_WRITE_OUTPORT: + s->write_cmd = val; + break; + case KBD_CCMD_MOUSE_DISABLE: + s->mode |= KBD_MODE_DISABLE_MOUSE; + break; + case KBD_CCMD_MOUSE_ENABLE: + s->mode &= ~KBD_MODE_DISABLE_MOUSE; + break; + case KBD_CCMD_TEST_MOUSE: + kbd_queue(s, 0x00, 0); + break; + case KBD_CCMD_SELF_TEST: + s->status |= KBD_STAT_SELFTEST; + kbd_queue(s, 0x55, 0); + break; + case KBD_CCMD_KBD_TEST: + kbd_queue(s, 0x00, 0); + break; + case KBD_CCMD_KBD_DISABLE: + s->mode |= KBD_MODE_DISABLE_KBD; + kbd_update_irq(s); + break; + case KBD_CCMD_KBD_ENABLE: + s->mode &= ~KBD_MODE_DISABLE_KBD; + kbd_update_irq(s); + break; + case KBD_CCMD_READ_INPORT: + kbd_queue(s, 0x00, 0); + break; + case KBD_CCMD_READ_OUTPORT: + /* XXX: check that */ + val = 0x01 | (ioport_get_a20() << 1); + if (s->status & KBD_STAT_OBF) + val |= 0x10; + if (s->status & KBD_STAT_MOUSE_OBF) + val |= 0x20; + kbd_queue(s, val, 0); + break; + case KBD_CCMD_ENABLE_A20: + ioport_set_a20(1); + break; + case KBD_CCMD_DISABLE_A20: + ioport_set_a20(0); + break; + case KBD_CCMD_RESET: + qemu_system_reset_request(); + break; + case 0xff: + /* ignore that - I don't know what is its use */ + break; + default: + fprintf(stderr, "qemu: unsupported keyboard cmd=0x%02x\n", val); + break; + } +} + +static uint32_t kbd_read_data(void *opaque, uint32_t addr, int size_log2) +{ + KBDState *s = opaque; + uint32_t val; + if (s->pending == KBD_PENDING_AUX) + val = ps2_read_data(s->mouse); + else + val = ps2_read_data(s->kbd); +#ifdef DEBUG_KBD + printf("kbd: read data=0x%02x\n", val); +#endif + return val; +} + +static void kbd_write_data(void *opaque, uint32_t addr, uint32_t val, int size_log2) +{ + KBDState *s = opaque; + +#ifdef DEBUG_KBD + printf("kbd: write data=0x%02x\n", val); +#endif + + switch(s->write_cmd) { + case 0: + ps2_write_keyboard(s->kbd, val); + break; + case KBD_CCMD_WRITE_MODE: + s->mode = val; + ps2_keyboard_set_translation(s->kbd, (s->mode & KBD_MODE_KCC) != 0); + /* ??? */ + kbd_update_irq(s); + break; + case KBD_CCMD_WRITE_OBUF: + kbd_queue(s, val, 0); + break; + case KBD_CCMD_WRITE_AUX_OBUF: + kbd_queue(s, val, 1); + break; + case KBD_CCMD_WRITE_OUTPORT: + ioport_set_a20((val >> 1) & 1); + if (!(val & 1)) { + qemu_system_reset_request(); + } + break; + case KBD_CCMD_WRITE_MOUSE: + ps2_write_mouse(s->mouse, val); + break; + default: + break; + } + s->write_cmd = 0; +} + +static void kbd_reset(void *opaque) +{ + KBDState *s = opaque; + + s->mode = KBD_MODE_KBD_INT | KBD_MODE_MOUSE_INT; + s->status = KBD_STAT_CMD | KBD_STAT_UNLOCKED; +} + +KBDState *i8042_init(PS2KbdState **pkbd, + PS2MouseState **pmouse, + PhysMemoryMap *port_map, + IRQSignal *kbd_irq, IRQSignal *mouse_irq, uint32_t io_base) +{ + KBDState *s; + + s = mallocz(sizeof(*s)); + + s->irq_kbd = kbd_irq; + s->irq_mouse = mouse_irq; + + kbd_reset(s); + cpu_register_device(port_map, io_base, 1, s, kbd_read_data, kbd_write_data, + DEVIO_SIZE8); + cpu_register_device(port_map, io_base + 4, 1, s, kbd_read_status, kbd_write_command, + DEVIO_SIZE8); + + s->kbd = ps2_kbd_init(kbd_update_kbd_irq, s); + s->mouse = ps2_mouse_init(kbd_update_aux_irq, s); + + *pkbd = s->kbd; + *pmouse = s->mouse; + return s; +} diff --git a/ps2.c b/ps2.c new file mode 100644 index 0000000..c787568 --- /dev/null +++ b/ps2.c @@ -0,0 +1,489 @@ +/* + * QEMU PS/2 keyboard/mouse emulation + * + * Copyright (c) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "iomem.h" +#include "ps2.h" + +/* debug PC keyboard */ +//#define DEBUG_KBD + +/* debug PC keyboard : only mouse */ +//#define DEBUG_MOUSE + +/* Keyboard Commands */ +#define KBD_CMD_SET_LEDS 0xED /* Set keyboard leds */ +#define KBD_CMD_ECHO 0xEE +#define KBD_CMD_GET_ID 0xF2 /* get keyboard ID */ +#define KBD_CMD_SET_RATE 0xF3 /* Set typematic rate */ +#define KBD_CMD_ENABLE 0xF4 /* Enable scanning */ +#define KBD_CMD_RESET_DISABLE 0xF5 /* reset and disable scanning */ +#define KBD_CMD_RESET_ENABLE 0xF6 /* reset and enable scanning */ +#define KBD_CMD_RESET 0xFF /* Reset */ + +/* Keyboard Replies */ +#define KBD_REPLY_POR 0xAA /* Power on reset */ +#define KBD_REPLY_ACK 0xFA /* Command ACK */ +#define KBD_REPLY_RESEND 0xFE /* Command NACK, send the cmd again */ + +/* Mouse Commands */ +#define AUX_SET_SCALE11 0xE6 /* Set 1:1 scaling */ +#define AUX_SET_SCALE21 0xE7 /* Set 2:1 scaling */ +#define AUX_SET_RES 0xE8 /* Set resolution */ +#define AUX_GET_SCALE 0xE9 /* Get scaling factor */ +#define AUX_SET_STREAM 0xEA /* Set stream mode */ +#define AUX_POLL 0xEB /* Poll */ +#define AUX_RESET_WRAP 0xEC /* Reset wrap mode */ +#define AUX_SET_WRAP 0xEE /* Set wrap mode */ +#define AUX_SET_REMOTE 0xF0 /* Set remote mode */ +#define AUX_GET_TYPE 0xF2 /* Get type */ +#define AUX_SET_SAMPLE 0xF3 /* Set sample rate */ +#define AUX_ENABLE_DEV 0xF4 /* Enable aux device */ +#define AUX_DISABLE_DEV 0xF5 /* Disable aux device */ +#define AUX_SET_DEFAULT 0xF6 +#define AUX_RESET 0xFF /* Reset aux device */ +#define AUX_ACK 0xFA /* Command byte ACK. */ + +#define MOUSE_STATUS_REMOTE 0x40 +#define MOUSE_STATUS_ENABLED 0x20 +#define MOUSE_STATUS_SCALE21 0x10 + +#define PS2_QUEUE_SIZE 256 + +typedef struct { + uint8_t data[PS2_QUEUE_SIZE]; + int rptr, wptr, count; +} PS2Queue; + +typedef struct { + PS2Queue queue; + int32_t write_cmd; + void (*update_irq)(void *, int); + void *update_arg; +} PS2State; + +struct PS2KbdState { + PS2State common; + int scan_enabled; + /* Qemu uses translated PC scancodes internally. To avoid multiple + conversions we do the translation (if any) in the PS/2 emulation + not the keyboard controller. */ + int translate; +}; + +struct PS2MouseState { + PS2State common; + uint8_t mouse_status; + uint8_t mouse_resolution; + uint8_t mouse_sample_rate; + uint8_t mouse_wrap; + uint8_t mouse_type; /* 0 = PS2, 3 = IMPS/2, 4 = IMEX */ + uint8_t mouse_detect_state; + int mouse_dx; /* current values, needed for 'poll' mode */ + int mouse_dy; + int mouse_dz; + uint8_t mouse_buttons; +}; + +void ps2_queue(void *opaque, int b) +{ + PS2State *s = (PS2State *)opaque; + PS2Queue *q = &s->queue; + + if (q->count >= PS2_QUEUE_SIZE) + return; + q->data[q->wptr] = b; + if (++q->wptr == PS2_QUEUE_SIZE) + q->wptr = 0; + q->count++; + s->update_irq(s->update_arg, 1); +} + +#define INPUT_MAKE_KEY_MIN 96 +#define INPUT_MAKE_KEY_MAX 127 + +static const uint8_t linux_input_to_keycode_set1[INPUT_MAKE_KEY_MAX - INPUT_MAKE_KEY_MIN + 1] = { + 0x1c, 0x1d, 0x35, 0x00, 0x38, 0x00, 0x47, 0x48, + 0x49, 0x4b, 0x4d, 0x4f, 0x50, 0x51, 0x52, 0x53, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x5c, 0x5d, +}; + +/* keycode is a Linux input layer keycode. We only support the PS/2 + keycode set 1 */ +void ps2_put_keycode(PS2KbdState *s, BOOL is_down, int keycode) +{ + if (keycode >= INPUT_MAKE_KEY_MIN) { + if (keycode > INPUT_MAKE_KEY_MAX) + return; + keycode = linux_input_to_keycode_set1[keycode - INPUT_MAKE_KEY_MIN]; + if (keycode == 0) + return; + ps2_queue(&s->common, 0xe0); + } + ps2_queue(&s->common, keycode | ((!is_down) << 7)); +} + +uint32_t ps2_read_data(void *opaque) +{ + PS2State *s = (PS2State *)opaque; + PS2Queue *q; + int val, index; + + q = &s->queue; + if (q->count == 0) { + /* NOTE: if no data left, we return the last keyboard one + (needed for EMM386) */ + /* XXX: need a timer to do things correctly */ + index = q->rptr - 1; + if (index < 0) + index = PS2_QUEUE_SIZE - 1; + val = q->data[index]; + } else { + val = q->data[q->rptr]; + if (++q->rptr == PS2_QUEUE_SIZE) + q->rptr = 0; + q->count--; + /* reading deasserts IRQ */ + s->update_irq(s->update_arg, 0); + /* reassert IRQs if data left */ + s->update_irq(s->update_arg, q->count != 0); + } + return val; +} + +static void ps2_reset_keyboard(PS2KbdState *s) +{ + s->scan_enabled = 1; +} + +void ps2_write_keyboard(void *opaque, int val) +{ + PS2KbdState *s = (PS2KbdState *)opaque; + + switch(s->common.write_cmd) { + default: + case -1: + switch(val) { + case 0x00: + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + case 0x05: + ps2_queue(&s->common, KBD_REPLY_RESEND); + break; + case KBD_CMD_GET_ID: + ps2_queue(&s->common, KBD_REPLY_ACK); + ps2_queue(&s->common, 0xab); + ps2_queue(&s->common, 0x83); + break; + case KBD_CMD_ECHO: + ps2_queue(&s->common, KBD_CMD_ECHO); + break; + case KBD_CMD_ENABLE: + s->scan_enabled = 1; + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_SET_LEDS: + case KBD_CMD_SET_RATE: + s->common.write_cmd = val; + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_RESET_DISABLE: + ps2_reset_keyboard(s); + s->scan_enabled = 0; + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_RESET_ENABLE: + ps2_reset_keyboard(s); + s->scan_enabled = 1; + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_RESET: + ps2_reset_keyboard(s); + ps2_queue(&s->common, KBD_REPLY_ACK); + ps2_queue(&s->common, KBD_REPLY_POR); + break; + default: + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + } + break; + case KBD_CMD_SET_LEDS: + ps2_queue(&s->common, KBD_REPLY_ACK); + s->common.write_cmd = -1; + break; + case KBD_CMD_SET_RATE: + ps2_queue(&s->common, KBD_REPLY_ACK); + s->common.write_cmd = -1; + break; + } +} + +/* Set the scancode translation mode. + 0 = raw scancodes. + 1 = translated scancodes (used by qemu internally). */ + +void ps2_keyboard_set_translation(void *opaque, int mode) +{ + PS2KbdState *s = (PS2KbdState *)opaque; + s->translate = mode; +} + +static void ps2_mouse_send_packet(PS2MouseState *s) +{ + unsigned int b; + int dx1, dy1, dz1; + + dx1 = s->mouse_dx; + dy1 = s->mouse_dy; + dz1 = s->mouse_dz; + /* XXX: increase range to 8 bits ? */ + if (dx1 > 127) + dx1 = 127; + else if (dx1 < -127) + dx1 = -127; + if (dy1 > 127) + dy1 = 127; + else if (dy1 < -127) + dy1 = -127; + b = 0x08 | ((dx1 < 0) << 4) | ((dy1 < 0) << 5) | (s->mouse_buttons & 0x07); + ps2_queue(&s->common, b); + ps2_queue(&s->common, dx1 & 0xff); + ps2_queue(&s->common, dy1 & 0xff); + /* extra byte for IMPS/2 or IMEX */ + switch(s->mouse_type) { + default: + break; + case 3: + if (dz1 > 127) + dz1 = 127; + else if (dz1 < -127) + dz1 = -127; + ps2_queue(&s->common, dz1 & 0xff); + break; + case 4: + if (dz1 > 7) + dz1 = 7; + else if (dz1 < -7) + dz1 = -7; + b = (dz1 & 0x0f) | ((s->mouse_buttons & 0x18) << 1); + ps2_queue(&s->common, b); + break; + } + + /* update deltas */ + s->mouse_dx -= dx1; + s->mouse_dy -= dy1; + s->mouse_dz -= dz1; +} + +void ps2_mouse_event(PS2MouseState *s, + int dx, int dy, int dz, int buttons_state) +{ + /* check if deltas are recorded when disabled */ + if (!(s->mouse_status & MOUSE_STATUS_ENABLED)) + return; + + s->mouse_dx += dx; + s->mouse_dy -= dy; + s->mouse_dz += dz; + /* XXX: SDL sometimes generates nul events: we delete them */ + if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0 && + s->mouse_buttons == buttons_state) + return; + s->mouse_buttons = buttons_state; + + if (!(s->mouse_status & MOUSE_STATUS_REMOTE) && + (s->common.queue.count < (PS2_QUEUE_SIZE - 16))) { + for(;;) { + /* if not remote, send event. Multiple events are sent if + too big deltas */ + ps2_mouse_send_packet(s); + if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0) + break; + } + } +} + +void ps2_write_mouse(void *opaque, int val) +{ + PS2MouseState *s = (PS2MouseState *)opaque; +#ifdef DEBUG_MOUSE + printf("kbd: write mouse 0x%02x\n", val); +#endif + switch(s->common.write_cmd) { + default: + case -1: + /* mouse command */ + if (s->mouse_wrap) { + if (val == AUX_RESET_WRAP) { + s->mouse_wrap = 0; + ps2_queue(&s->common, AUX_ACK); + return; + } else if (val != AUX_RESET) { + ps2_queue(&s->common, val); + return; + } + } + switch(val) { + case AUX_SET_SCALE11: + s->mouse_status &= ~MOUSE_STATUS_SCALE21; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_SET_SCALE21: + s->mouse_status |= MOUSE_STATUS_SCALE21; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_SET_STREAM: + s->mouse_status &= ~MOUSE_STATUS_REMOTE; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_SET_WRAP: + s->mouse_wrap = 1; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_SET_REMOTE: + s->mouse_status |= MOUSE_STATUS_REMOTE; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_GET_TYPE: + ps2_queue(&s->common, AUX_ACK); + ps2_queue(&s->common, s->mouse_type); + break; + case AUX_SET_RES: + case AUX_SET_SAMPLE: + s->common.write_cmd = val; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_GET_SCALE: + ps2_queue(&s->common, AUX_ACK); + ps2_queue(&s->common, s->mouse_status); + ps2_queue(&s->common, s->mouse_resolution); + ps2_queue(&s->common, s->mouse_sample_rate); + break; + case AUX_POLL: + ps2_queue(&s->common, AUX_ACK); + ps2_mouse_send_packet(s); + break; + case AUX_ENABLE_DEV: + s->mouse_status |= MOUSE_STATUS_ENABLED; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_DISABLE_DEV: + s->mouse_status &= ~MOUSE_STATUS_ENABLED; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_SET_DEFAULT: + s->mouse_sample_rate = 100; + s->mouse_resolution = 2; + s->mouse_status = 0; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_RESET: + s->mouse_sample_rate = 100; + s->mouse_resolution = 2; + s->mouse_status = 0; + s->mouse_type = 0; + ps2_queue(&s->common, AUX_ACK); + ps2_queue(&s->common, 0xaa); + ps2_queue(&s->common, s->mouse_type); + break; + default: + break; + } + break; + case AUX_SET_SAMPLE: + s->mouse_sample_rate = val; + /* detect IMPS/2 or IMEX */ + switch(s->mouse_detect_state) { + default: + case 0: + if (val == 200) + s->mouse_detect_state = 1; + break; + case 1: + if (val == 100) + s->mouse_detect_state = 2; + else if (val == 200) + s->mouse_detect_state = 3; + else + s->mouse_detect_state = 0; + break; + case 2: + if (val == 80) + s->mouse_type = 3; /* IMPS/2 */ + s->mouse_detect_state = 0; + break; + case 3: + if (val == 80) + s->mouse_type = 4; /* IMEX */ + s->mouse_detect_state = 0; + break; + } + ps2_queue(&s->common, AUX_ACK); + s->common.write_cmd = -1; + break; + case AUX_SET_RES: + s->mouse_resolution = val; + ps2_queue(&s->common, AUX_ACK); + s->common.write_cmd = -1; + break; + } +} + +static void ps2_reset(void *opaque) +{ + PS2State *s = (PS2State *)opaque; + PS2Queue *q; + s->write_cmd = -1; + q = &s->queue; + q->rptr = 0; + q->wptr = 0; + q->count = 0; +} + +PS2KbdState *ps2_kbd_init(void (*update_irq)(void *, int), void *update_arg) +{ + PS2KbdState *s = (PS2KbdState *)mallocz(sizeof(PS2KbdState)); + + s->common.update_irq = update_irq; + s->common.update_arg = update_arg; + ps2_reset(&s->common); + return s; +} + +PS2MouseState *ps2_mouse_init(void (*update_irq)(void *, int), void *update_arg) +{ + PS2MouseState *s = (PS2MouseState *)mallocz(sizeof(PS2MouseState)); + + s->common.update_irq = update_irq; + s->common.update_arg = update_arg; + ps2_reset(&s->common); + return s; +} diff --git a/ps2.h b/ps2.h new file mode 100644 index 0000000..47bcfe9 --- /dev/null +++ b/ps2.h @@ -0,0 +1,34 @@ +/* ps2.c */ +typedef struct PS2MouseState PS2MouseState; +typedef struct PS2KbdState PS2KbdState; + +PS2KbdState *ps2_kbd_init(void (*update_irq)(void *, int), void *update_arg); +PS2MouseState *ps2_mouse_init(void (*update_irq)(void *, int), void *update_arg); +void ps2_write_mouse(void *, int val); +void ps2_write_keyboard(void *, int val); +uint32_t ps2_read_data(void *); +void ps2_queue(void *, int b); +void ps2_keyboard_set_translation(void *opaque, int mode); + +void ps2_put_keycode(PS2KbdState *s, BOOL is_down, int keycode); +void ps2_mouse_event(PS2MouseState *s, + int dx, int dy, int dz, int buttons_state); + +/* vmmouse.c */ +typedef struct VMMouseState VMMouseState; + +VMMouseState *vmmouse_init(PS2MouseState *ps2_mouse); +BOOL vmmouse_is_absolute(VMMouseState *s); +void vmmouse_send_mouse_event(VMMouseState *s, int x, int y, int dz, + int buttons); +void vmmouse_handler(VMMouseState *s, uint32_t *regs); + +/* pckbd.c */ + +typedef struct KBDState KBDState; + +KBDState *i8042_init(PS2KbdState **pkbd, + PS2MouseState **pmouse, + PhysMemoryMap *port_map, + IRQSignal *kbd_irq, IRQSignal *mouse_irq, + uint32_t io_base); diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..ca72f7d --- /dev/null +++ b/readme.txt @@ -0,0 +1,204 @@ +TinyEMU System Emulator by Fabrice Bellard +========================================== + +1) Features +----------- + +- RISC-V system emulator supporting the RV128IMAFDQC base ISA (user + level ISA version 2.2, priviledged architecture version 1.10) + including: + + - 32/64/128 bit integer registers + - 32/64/128 bit floating point instructions + - Compressed instructions + - dynamic XLEN change + +- x86 system emulator based on KVM + +- VirtIO console, network, block device, input and 9P filesystem + +- Graphical display with SDL + +- JSON configuration file + +- Remote HTTP block device and filesystem + +- small code, easy to modify, no external dependancies + +- Javascript demo version + +2) Installation +--------------- + +- The libraries libcurl, OpenSSL and SDL should be installed. On a Fedora + system you can do it with: + + sudo dnf install openssl-devel libcurl-devel SDL-devel + + It is possible to compile the programs without these libraries by + commenting CONFIG_FS_NET and/or CONFIG_SDL in the Makefile. + +- Edit the Makefile to disable the 128 bit target if you compile on a + 32 bit host (for the 128 bit RISCV target the compiler must support + the __int128 C extension). + +- Use 'make' to compile the binaries. + +- You can optionally install the program to '/usr/local/bin' with: + + make install + +3) Usage +-------- + +3.1 Quick examples +------------------ + +- Use the VM images available from https://bellard.org/jslinux (no + need to download them): + + Terminal: + + ./temu https://bellard.org/jslinux/buildroot-riscv64.cfg + + Graphical (with SDL): + + ./temu https://bellard.org/jslinux/buildroot-x86-xwin.cfg + + ./temu https://bellard.org/jslinux/win2k.cfg + +- Download the example RISC-V Linux image + (diskimage-linux-riscv-yyyy-mm-dd.tar.gz) and use it: + + ./temu root-riscv64.cfg + + ./temu rv128test/rv128test.cfg + +- Access to your local hard disk (/tmp directory) in the guest: + + ./temu root_9p-riscv64.cfg + +then type: +mount -t 9p /dev/root /mnt + +in the guest. The content of the host '/tmp' directory is visible in '/mnt'. + +3.2 Invocation +-------------- + +usage: temu [options] config_file +options are: +-m ram_size set the RAM size in MB +-rw allow write access to the disk image (default=snapshot) +-ctrlc the C-c key stops the emulator instead of being sent to the + emulated software +-append cmdline append cmdline to the kernel command line +-no-accel disable VM acceleration (KVM, x86 machine only) + +Console keys: +Press C-a x to exit the emulator, C-a h to get some help. + +3.3 Network usage +----------------- + +The easiest way is to use the "user" mode network driver. No specific +configuration is necessary. + +TinyEMU also supports a "tap" network driver to redirect the network +traffic from a VirtIO network adapter. + +You can look at the netinit.sh script to create the tap network +interface and to redirect the virtual traffic to Internet thru a +NAT. The exact configuration may depend on the Linux distribution and +local firewall configuration. + +The VM configuration file must include: + +eth0: { driver: "tap", ifname: "tap0" } + +and configure the network in the guest system with: + +ifconfig eth0 192.168.3.2 +route add -net 0.0.0.0 gw 192.168.3.1 eth0 + +3.4 Network filesystem +---------------------- + +TinyEMU supports the VirtIO 9P filesystem to access local or remote +filesystems. For remote filesystems, it does HTTP requests to download +the files. The protocol is compatible with the vfsync utility. In the +"mount" command, "/dev/rootN" must be used as device name where N is +the index of the filesystem. When N=0 it is omitted. + +The build_filelist tool builds the file list from a root directory. A +simple web server is enough to serve the files. + +The '.preload' file gives a list of files to preload when opening a +given file. + +3.5 Network block device +------------------------ + +TinyEMU supports an HTTP block device. The disk image is split into +small files. Use the 'splitimg' utility to generate images. The URL of +the JSON blk.txt file must be provided as disk image filename. + +4) Technical notes +------------------ + +4.1) 128 bit support + +The RISC-V specification does not define all the instruction encodings +for the 128 bit integer and floating point operations. The missing +ones were interpolated from the 32 and 64 ones. + +Unfortunately there is no RISC-V 128 bit toolchain nor OS now +(volunteers for the Linux port ?), so rv128test.bin may be the first +128 bit code for RISC-V ! + +4.2) Floating point emulation + +The floating point emulation is bit exact and supports all the +specified instructions for 32, 64 and 128 bit floating point +numbers. It uses the new SoftFP library. + +4.3) HTIF console + +The standard HTIF console uses registers at variable addresses which +are deduced by loading specific ELF symbols. TinyEMU does not rely on +an ELF loader, so it is much simpler to use registers at fixed +addresses (0x40008000). A small modification was made in the +"riscv-pk" boot loader to support it. The HTIF console is only used to +display boot messages and to power off the virtual system. The OS +should use the VirtIO console. + +4.4) Javascript version + +The Javascript version (JSLinux) can be compiled with Makefile.js and +emscripten. A complete precompiled and preconfigured demo is available +in the jslinux-yyyy-mm-dd.tar.gz archive (read the readme.txt file +inside the archive). + +4.5) x86 emulator + +A small x86 emulator is included. It is not really an emulator because +it uses the Linux KVM API to run the x86 code at near native +performance. The x86 emulator uses the same set of VirtIO devices as +the RISCV emulator and is able to run many operating systems. + +The x86 emulator accepts a Linux kernel image (bzImage). No BIOS image +is necessary. + +The x86 emulator comes from my JS/Linux project (2011) which was one +of the first emulator running Linux fully implemented in +Javascript. It is provided to allow easy access to the x86 images +hosted at https://bellard.org/jslinux . + + +5) License / Credits +-------------------- + +TinyEMU is released under the MIT license. If there is no explicit +license in a file, the license from MIT-LICENSE.txt applies. + +The SLIRP library has its own license (two clause BSD license). diff --git a/riscv_cpu.c b/riscv_cpu.c new file mode 100644 index 0000000..ba4fa4c --- /dev/null +++ b/riscv_cpu.c @@ -0,0 +1,1377 @@ +/* + * RISCV CPU emulator + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "iomem.h" +#include "riscv_cpu.h" + +#ifndef MAX_XLEN +#error MAX_XLEN must be defined +#endif +#ifndef CONFIG_RISCV_MAX_XLEN +#error CONFIG_RISCV_MAX_XLEN must be defined +#endif + +//#define DUMP_INVALID_MEM_ACCESS +//#define DUMP_MMU_EXCEPTIONS +//#define DUMP_INTERRUPTS +//#define DUMP_INVALID_CSR +//#define DUMP_EXCEPTIONS +//#define DUMP_CSR +//#define CONFIG_LOGFILE + +#include "riscv_cpu_priv.h" + +#if FLEN > 0 +#include "softfp.h" +#endif + +#ifdef USE_GLOBAL_STATE +static RISCVCPUState riscv_cpu_global_state; +#endif +#ifdef USE_GLOBAL_VARIABLES +#define code_ptr s->__code_ptr +#define code_end s->__code_end +#define code_to_pc_addend s->__code_to_pc_addend +#endif + +#ifdef CONFIG_LOGFILE +static FILE *log_file; + +static void log_vprintf(const char *fmt, va_list ap) +{ + if (!log_file) + log_file = fopen("/tmp/riscemu.log", "wb"); + vfprintf(log_file, fmt, ap); +} +#else +static void log_vprintf(const char *fmt, va_list ap) +{ + vprintf(fmt, ap); +} +#endif + +static void __attribute__((format(printf, 1, 2), unused)) log_printf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + log_vprintf(fmt, ap); + va_end(ap); +} + +#if MAX_XLEN == 128 +static void fprint_target_ulong(FILE *f, target_ulong a) +{ + fprintf(f, "%016" PRIx64 "%016" PRIx64, (uint64_t)(a >> 64), (uint64_t)a); +} +#else +static void fprint_target_ulong(FILE *f, target_ulong a) +{ + fprintf(f, "%" PR_target_ulong, a); +} +#endif + +static void print_target_ulong(target_ulong a) +{ + fprint_target_ulong(stdout, a); +} + +static char *reg_name[32] = { +"zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", +"s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5", +"a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", +"s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6" +}; + +static void dump_regs(RISCVCPUState *s) +{ + int i, cols; + const char priv_str[4] = "USHM"; + cols = 256 / MAX_XLEN; + printf("pc ="); + print_target_ulong(s->pc); + printf(" "); + for(i = 1; i < 32; i++) { + printf("%-3s=", reg_name[i]); + print_target_ulong(s->reg[i]); + if ((i & (cols - 1)) == (cols - 1)) + printf("\n"); + else + printf(" "); + } + printf("priv=%c", priv_str[s->priv]); + printf(" mstatus="); + print_target_ulong(s->mstatus); + printf(" cycles=%" PRId64, s->insn_counter); + printf("\n"); +#if 1 + printf(" mideleg="); + print_target_ulong(s->mideleg); + printf(" mie="); + print_target_ulong(s->mie); + printf(" mip="); + print_target_ulong(s->mip); + printf("\n"); +#endif +} + +static __attribute__((unused)) void cpu_abort(RISCVCPUState *s) +{ + dump_regs(s); + abort(); +} + +/* addr must be aligned. Only RAM accesses are supported */ +#define PHYS_MEM_READ_WRITE(size, uint_type) \ +static __maybe_unused inline void phys_write_u ## size(RISCVCPUState *s, target_ulong addr,\ + uint_type val) \ +{\ + PhysMemoryRange *pr = get_phys_mem_range(s->mem_map, addr);\ + if (!pr || !pr->is_ram)\ + return;\ + *(uint_type *)(pr->phys_mem + \ + (uintptr_t)(addr - pr->addr)) = val;\ +}\ +\ +static __maybe_unused inline uint_type phys_read_u ## size(RISCVCPUState *s, target_ulong addr) \ +{\ + PhysMemoryRange *pr = get_phys_mem_range(s->mem_map, addr);\ + if (!pr || !pr->is_ram)\ + return 0;\ + return *(uint_type *)(pr->phys_mem + \ + (uintptr_t)(addr - pr->addr)); \ +} + +PHYS_MEM_READ_WRITE(8, uint8_t) +PHYS_MEM_READ_WRITE(32, uint32_t) +PHYS_MEM_READ_WRITE(64, uint64_t) + +#define PTE_V_MASK (1 << 0) +#define PTE_U_MASK (1 << 4) +#define PTE_A_MASK (1 << 6) +#define PTE_D_MASK (1 << 7) + +#define ACCESS_READ 0 +#define ACCESS_WRITE 1 +#define ACCESS_CODE 2 + +/* access = 0: read, 1 = write, 2 = code. Set the exception_pending + field if necessary. return 0 if OK, -1 if translation error */ +static int get_phys_addr(RISCVCPUState *s, + target_ulong *ppaddr, target_ulong vaddr, + int access) +{ + int mode, levels, pte_bits, pte_idx, pte_mask, pte_size_log2, xwr, priv; + int need_write, vaddr_shift, i, pte_addr_bits; + target_ulong pte_addr, pte, vaddr_mask, paddr; + + if ((s->mstatus & MSTATUS_MPRV) && access != ACCESS_CODE) { + /* use previous priviledge */ + priv = (s->mstatus >> MSTATUS_MPP_SHIFT) & 3; + } else { + priv = s->priv; + } + + if (priv == PRV_M) { + if (s->cur_xlen < MAX_XLEN) { + /* truncate virtual address */ + *ppaddr = vaddr & (((target_ulong)1 << s->cur_xlen) - 1); + } else { + *ppaddr = vaddr; + } + return 0; + } +#if MAX_XLEN == 32 + /* 32 bits */ + mode = s->satp >> 31; + if (mode == 0) { + /* bare: no translation */ + *ppaddr = vaddr; + return 0; + } else { + /* sv32 */ + levels = 2; + pte_size_log2 = 2; + pte_addr_bits = 22; + } +#else + mode = (s->satp >> 60) & 0xf; + if (mode == 0) { + /* bare: no translation */ + *ppaddr = vaddr; + return 0; + } else { + /* sv39/sv48 */ + levels = mode - 8 + 3; + pte_size_log2 = 3; + vaddr_shift = MAX_XLEN - (PG_SHIFT + levels * 9); + if ((((target_long)vaddr << vaddr_shift) >> vaddr_shift) != vaddr) + return -1; + pte_addr_bits = 44; + } +#endif + pte_addr = (s->satp & (((target_ulong)1 << pte_addr_bits) - 1)) << PG_SHIFT; + pte_bits = 12 - pte_size_log2; + pte_mask = (1 << pte_bits) - 1; + for(i = 0; i < levels; i++) { + vaddr_shift = PG_SHIFT + pte_bits * (levels - 1 - i); + pte_idx = (vaddr >> vaddr_shift) & pte_mask; + pte_addr += pte_idx << pte_size_log2; + if (pte_size_log2 == 2) + pte = phys_read_u32(s, pte_addr); + else + pte = phys_read_u64(s, pte_addr); + //printf("pte=0x%08" PRIx64 "\n", pte); + if (!(pte & PTE_V_MASK)) + return -1; /* invalid PTE */ + paddr = (pte >> 10) << PG_SHIFT; + xwr = (pte >> 1) & 7; + if (xwr != 0) { + if (xwr == 2 || xwr == 6) + return -1; + /* priviledge check */ + if (priv == PRV_S) { + if ((pte & PTE_U_MASK) && !(s->mstatus & MSTATUS_SUM)) + return -1; + } else { + if (!(pte & PTE_U_MASK)) + return -1; + } + /* protection check */ + /* MXR allows read access to execute-only pages */ + if (s->mstatus & MSTATUS_MXR) + xwr |= (xwr >> 2); + + if (((xwr >> access) & 1) == 0) + return -1; + need_write = !(pte & PTE_A_MASK) || + (!(pte & PTE_D_MASK) && access == ACCESS_WRITE); + pte |= PTE_A_MASK; + if (access == ACCESS_WRITE) + pte |= PTE_D_MASK; + if (need_write) { + if (pte_size_log2 == 2) + phys_write_u32(s, pte_addr, pte); + else + phys_write_u64(s, pte_addr, pte); + } + vaddr_mask = ((target_ulong)1 << vaddr_shift) - 1; + *ppaddr = (vaddr & vaddr_mask) | (paddr & ~vaddr_mask); + return 0; + } else { + pte_addr = paddr; + } + } + return -1; +} + +/* return 0 if OK, != 0 if exception */ +int target_read_slow(RISCVCPUState *s, mem_uint_t *pval, + target_ulong addr, int size_log2) +{ + int size, tlb_idx, err, al; + target_ulong paddr, offset; + uint8_t *ptr; + PhysMemoryRange *pr; + mem_uint_t ret; + + /* first handle unaligned accesses */ + size = 1 << size_log2; + al = addr & (size - 1); + if (al != 0) { + switch(size_log2) { + case 1: + { + uint8_t v0, v1; + err = target_read_u8(s, &v0, addr); + if (err) + return err; + err = target_read_u8(s, &v1, addr + 1); + if (err) + return err; + ret = v0 | (v1 << 8); + } + break; + case 2: + { + uint32_t v0, v1; + addr -= al; + err = target_read_u32(s, &v0, addr); + if (err) + return err; + err = target_read_u32(s, &v1, addr + 4); + if (err) + return err; + ret = (v0 >> (al * 8)) | (v1 << (32 - al * 8)); + } + break; +#if MLEN >= 64 + case 3: + { + uint64_t v0, v1; + addr -= al; + err = target_read_u64(s, &v0, addr); + if (err) + return err; + err = target_read_u64(s, &v1, addr + 8); + if (err) + return err; + ret = (v0 >> (al * 8)) | (v1 << (64 - al * 8)); + } + break; +#endif +#if MLEN >= 128 + case 4: + { + uint128_t v0, v1; + addr -= al; + err = target_read_u128(s, &v0, addr); + if (err) + return err; + err = target_read_u128(s, &v1, addr + 16); + if (err) + return err; + ret = (v0 >> (al * 8)) | (v1 << (128 - al * 8)); + } + break; +#endif + default: + abort(); + } + } else { + if (get_phys_addr(s, &paddr, addr, ACCESS_READ)) { + s->pending_tval = addr; + s->pending_exception = CAUSE_LOAD_PAGE_FAULT; + return -1; + } + pr = get_phys_mem_range(s->mem_map, paddr); + if (!pr) { +#ifdef DUMP_INVALID_MEM_ACCESS + printf("target_read_slow: invalid physical address 0x"); + print_target_ulong(paddr); + printf("\n"); +#endif + return 0; + } else if (pr->is_ram) { + tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); + ptr = pr->phys_mem + (uintptr_t)(paddr - pr->addr); + s->tlb_read[tlb_idx].vaddr = addr & ~PG_MASK; + s->tlb_read[tlb_idx].mem_addend = (uintptr_t)ptr - addr; + switch(size_log2) { + case 0: + ret = *(uint8_t *)ptr; + break; + case 1: + ret = *(uint16_t *)ptr; + break; + case 2: + ret = *(uint32_t *)ptr; + break; +#if MLEN >= 64 + case 3: + ret = *(uint64_t *)ptr; + break; +#endif +#if MLEN >= 128 + case 4: + ret = *(uint128_t *)ptr; + break; +#endif + default: + abort(); + } + } else { + offset = paddr - pr->addr; + if (((pr->devio_flags >> size_log2) & 1) != 0) { + ret = pr->read_func(pr->opaque, offset, size_log2); + } +#if MLEN >= 64 + else if ((pr->devio_flags & DEVIO_SIZE32) && size_log2 == 3) { + /* emulate 64 bit access */ + ret = pr->read_func(pr->opaque, offset, 2); + ret |= (uint64_t)pr->read_func(pr->opaque, offset + 4, 2) << 32; + + } +#endif + else { +#ifdef DUMP_INVALID_MEM_ACCESS + printf("unsupported device read access: addr=0x"); + print_target_ulong(paddr); + printf(" width=%d bits\n", 1 << (3 + size_log2)); +#endif + ret = 0; + } + } + } + *pval = ret; + return 0; +} + +/* return 0 if OK, != 0 if exception */ +int target_write_slow(RISCVCPUState *s, target_ulong addr, + mem_uint_t val, int size_log2) +{ + int size, i, tlb_idx, err; + target_ulong paddr, offset; + uint8_t *ptr; + PhysMemoryRange *pr; + + /* first handle unaligned accesses */ + size = 1 << size_log2; + if ((addr & (size - 1)) != 0) { + /* XXX: should avoid modifying the memory in case of exception */ + for(i = 0; i < size; i++) { + err = target_write_u8(s, addr + i, (val >> (8 * i)) & 0xff); + if (err) + return err; + } + } else { + if (get_phys_addr(s, &paddr, addr, ACCESS_WRITE)) { + s->pending_tval = addr; + s->pending_exception = CAUSE_STORE_PAGE_FAULT; + return -1; + } + pr = get_phys_mem_range(s->mem_map, paddr); + if (!pr) { +#ifdef DUMP_INVALID_MEM_ACCESS + printf("target_write_slow: invalid physical address 0x"); + print_target_ulong(paddr); + printf("\n"); +#endif + } else if (pr->is_ram) { + phys_mem_set_dirty_bit(pr, paddr - pr->addr); + tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); + ptr = pr->phys_mem + (uintptr_t)(paddr - pr->addr); + s->tlb_write[tlb_idx].vaddr = addr & ~PG_MASK; + s->tlb_write[tlb_idx].mem_addend = (uintptr_t)ptr - addr; + switch(size_log2) { + case 0: + *(uint8_t *)ptr = val; + break; + case 1: + *(uint16_t *)ptr = val; + break; + case 2: + *(uint32_t *)ptr = val; + break; +#if MLEN >= 64 + case 3: + *(uint64_t *)ptr = val; + break; +#endif +#if MLEN >= 128 + case 4: + *(uint128_t *)ptr = val; + break; +#endif + default: + abort(); + } + } else { + offset = paddr - pr->addr; + if (((pr->devio_flags >> size_log2) & 1) != 0) { + pr->write_func(pr->opaque, offset, val, size_log2); + } +#if MLEN >= 64 + else if ((pr->devio_flags & DEVIO_SIZE32) && size_log2 == 3) { + /* emulate 64 bit access */ + pr->write_func(pr->opaque, offset, + val & 0xffffffff, 2); + pr->write_func(pr->opaque, offset + 4, + (val >> 32) & 0xffffffff, 2); + } +#endif + else { +#ifdef DUMP_INVALID_MEM_ACCESS + printf("unsupported device write access: addr=0x"); + print_target_ulong(paddr); + printf(" width=%d bits\n", 1 << (3 + size_log2)); +#endif + } + } + } + return 0; +} + +struct __attribute__((packed)) unaligned_u32 { + uint32_t u32; +}; + +/* unaligned access at an address known to be a multiple of 2 */ +static uint32_t get_insn32(uint8_t *ptr) +{ +#if defined(EMSCRIPTEN) + return ((uint16_t *)ptr)[0] | (((uint16_t *)ptr)[1] << 16); +#else + return ((struct unaligned_u32 *)ptr)->u32; +#endif +} + +/* return 0 if OK, != 0 if exception */ +static no_inline __exception int target_read_insn_slow(RISCVCPUState *s, + uint8_t **pptr, + target_ulong addr) +{ + int tlb_idx; + target_ulong paddr; + uint8_t *ptr; + PhysMemoryRange *pr; + + if (get_phys_addr(s, &paddr, addr, ACCESS_CODE)) { + s->pending_tval = addr; + s->pending_exception = CAUSE_FETCH_PAGE_FAULT; + return -1; + } + pr = get_phys_mem_range(s->mem_map, paddr); + if (!pr || !pr->is_ram) { + /* XXX: we only access to execute code from RAM */ + s->pending_tval = addr; + s->pending_exception = CAUSE_FAULT_FETCH; + return -1; + } + tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); + ptr = pr->phys_mem + (uintptr_t)(paddr - pr->addr); + s->tlb_code[tlb_idx].vaddr = addr & ~PG_MASK; + s->tlb_code[tlb_idx].mem_addend = (uintptr_t)ptr - addr; + *pptr = ptr; + return 0; +} + +/* addr must be aligned */ +static inline __exception int target_read_insn_u16(RISCVCPUState *s, uint16_t *pinsn, + target_ulong addr) +{ + uint32_t tlb_idx; + uint8_t *ptr; + + tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); + if (likely(s->tlb_code[tlb_idx].vaddr == (addr & ~PG_MASK))) { + ptr = (uint8_t *)(s->tlb_code[tlb_idx].mem_addend + + (uintptr_t)addr); + } else { + if (target_read_insn_slow(s, &ptr, addr)) + return -1; + } + *pinsn = *(uint16_t *)ptr; + return 0; +} + +static void tlb_init(RISCVCPUState *s) +{ + int i; + + for(i = 0; i < TLB_SIZE; i++) { + s->tlb_read[i].vaddr = -1; + s->tlb_write[i].vaddr = -1; + s->tlb_code[i].vaddr = -1; + } +} + +static void tlb_flush_all(RISCVCPUState *s) +{ + tlb_init(s); +} + +static void tlb_flush_vaddr(RISCVCPUState *s, target_ulong vaddr) +{ + tlb_flush_all(s); +} + +/* XXX: inefficient but not critical as long as it is seldom used */ +static void glue(riscv_cpu_flush_tlb_write_range_ram, + MAX_XLEN)(RISCVCPUState *s, + uint8_t *ram_ptr, size_t ram_size) +{ + uint8_t *ptr, *ram_end; + int i; + + ram_end = ram_ptr + ram_size; + for(i = 0; i < TLB_SIZE; i++) { + if (s->tlb_write[i].vaddr != -1) { + ptr = (uint8_t *)(s->tlb_write[i].mem_addend + + (uintptr_t)s->tlb_write[i].vaddr); + if (ptr >= ram_ptr && ptr < ram_end) { + s->tlb_write[i].vaddr = -1; + } + } + } +} + + +#define SSTATUS_MASK0 (MSTATUS_UIE | MSTATUS_SIE | \ + MSTATUS_UPIE | MSTATUS_SPIE | \ + MSTATUS_SPP | \ + MSTATUS_FS | MSTATUS_XS | \ + MSTATUS_SUM | MSTATUS_MXR) +#if MAX_XLEN >= 64 +#define SSTATUS_MASK (SSTATUS_MASK0 | MSTATUS_UXL_MASK) +#else +#define SSTATUS_MASK SSTATUS_MASK0 +#endif + + +#define MSTATUS_MASK (MSTATUS_UIE | MSTATUS_SIE | MSTATUS_MIE | \ + MSTATUS_UPIE | MSTATUS_SPIE | MSTATUS_MPIE | \ + MSTATUS_SPP | MSTATUS_MPP | \ + MSTATUS_FS | \ + MSTATUS_MPRV | MSTATUS_SUM | MSTATUS_MXR) + +/* cycle and insn counters */ +#define COUNTEREN_MASK ((1 << 0) | (1 << 2)) + +/* return the complete mstatus with the SD bit */ +static target_ulong get_mstatus(RISCVCPUState *s, target_ulong mask) +{ + target_ulong val; + BOOL sd; + val = s->mstatus | (s->fs << MSTATUS_FS_SHIFT); + val &= mask; + sd = ((val & MSTATUS_FS) == MSTATUS_FS) | + ((val & MSTATUS_XS) == MSTATUS_XS); + if (sd) + val |= (target_ulong)1 << (s->cur_xlen - 1); + return val; +} + +static int get_base_from_xlen(int xlen) +{ + if (xlen == 32) + return 1; + else if (xlen == 64) + return 2; + else + return 3; +} + +static void set_mstatus(RISCVCPUState *s, target_ulong val) +{ + target_ulong mod, mask; + + /* flush the TLBs if change of MMU config */ + mod = s->mstatus ^ val; + if ((mod & (MSTATUS_MPRV | MSTATUS_SUM | MSTATUS_MXR)) != 0 || + ((s->mstatus & MSTATUS_MPRV) && (mod & MSTATUS_MPP) != 0)) { + tlb_flush_all(s); + } + s->fs = (val >> MSTATUS_FS_SHIFT) & 3; + + mask = MSTATUS_MASK & ~MSTATUS_FS; +#if MAX_XLEN >= 64 + { + int uxl, sxl; + uxl = (val >> MSTATUS_UXL_SHIFT) & 3; + if (uxl >= 1 && uxl <= get_base_from_xlen(MAX_XLEN)) + mask |= MSTATUS_UXL_MASK; + sxl = (val >> MSTATUS_UXL_SHIFT) & 3; + if (sxl >= 1 && sxl <= get_base_from_xlen(MAX_XLEN)) + mask |= MSTATUS_SXL_MASK; + } +#endif + s->mstatus = (s->mstatus & ~mask) | (val & mask); +} + +/* return -1 if invalid CSR. 0 if OK. 'will_write' indicate that the + csr will be written after (used for CSR access check) */ +static int csr_read(RISCVCPUState *s, target_ulong *pval, uint32_t csr, + BOOL will_write) +{ + target_ulong val; + + if (((csr & 0xc00) == 0xc00) && will_write) + return -1; /* read-only CSR */ + if (s->priv < ((csr >> 8) & 3)) + return -1; /* not enough priviledge */ + + switch(csr) { +#if FLEN > 0 + case 0x001: /* fflags */ + if (s->fs == 0) + return -1; + val = s->fflags; + break; + case 0x002: /* frm */ + if (s->fs == 0) + return -1; + val = s->frm; + break; + case 0x003: + if (s->fs == 0) + return -1; + val = s->fflags | (s->frm << 5); + break; +#endif + case 0xc00: /* ucycle */ + case 0xc02: /* uinstret */ + { + uint32_t counteren; + if (s->priv < PRV_M) { + if (s->priv < PRV_S) + counteren = s->scounteren; + else + counteren = s->mcounteren; + if (((counteren >> (csr & 0x1f)) & 1) == 0) + goto invalid_csr; + } + } + val = (int64_t)s->insn_counter; + break; + case 0xc80: /* mcycleh */ + case 0xc82: /* minstreth */ + if (s->cur_xlen != 32) + goto invalid_csr; + { + uint32_t counteren; + if (s->priv < PRV_M) { + if (s->priv < PRV_S) + counteren = s->scounteren; + else + counteren = s->mcounteren; + if (((counteren >> (csr & 0x1f)) & 1) == 0) + goto invalid_csr; + } + } + val = s->insn_counter >> 32; + break; + + case 0x100: + val = get_mstatus(s, SSTATUS_MASK); + break; + case 0x104: /* sie */ + val = s->mie & s->mideleg; + break; + case 0x105: + val = s->stvec; + break; + case 0x106: + val = s->scounteren; + break; + case 0x140: + val = s->sscratch; + break; + case 0x141: + val = s->sepc; + break; + case 0x142: + val = s->scause; + break; + case 0x143: + val = s->stval; + break; + case 0x144: /* sip */ + val = s->mip & s->mideleg; + break; + case 0x180: + val = s->satp; + break; + case 0x300: + val = get_mstatus(s, (target_ulong)-1); + break; + case 0x301: + val = s->misa; + val |= (target_ulong)s->mxl << (s->cur_xlen - 2); + break; + case 0x302: + val = s->medeleg; + break; + case 0x303: + val = s->mideleg; + break; + case 0x304: + val = s->mie; + break; + case 0x305: + val = s->mtvec; + break; + case 0x306: + val = s->mcounteren; + break; + case 0x340: + val = s->mscratch; + break; + case 0x341: + val = s->mepc; + break; + case 0x342: + val = s->mcause; + break; + case 0x343: + val = s->mtval; + break; + case 0x344: + val = s->mip; + break; + case 0xb00: /* mcycle */ + case 0xb02: /* minstret */ + val = (int64_t)s->insn_counter; + break; + case 0xb80: /* mcycleh */ + case 0xb82: /* minstreth */ + if (s->cur_xlen != 32) + goto invalid_csr; + val = s->insn_counter >> 32; + break; + case 0xf14: + val = s->mhartid; + break; + default: + invalid_csr: +#ifdef DUMP_INVALID_CSR + /* the 'time' counter is usually emulated */ + if (csr != 0xc01 && csr != 0xc81) { + printf("csr_read: invalid CSR=0x%x\n", csr); + } +#endif + *pval = 0; + return -1; + } + *pval = val; + return 0; +} + +#if FLEN > 0 +static void set_frm(RISCVCPUState *s, unsigned int val) +{ + if (val >= 5) + val = 0; + s->frm = val; +} + +/* return -1 if invalid roundind mode */ +static int get_insn_rm(RISCVCPUState *s, unsigned int rm) +{ + if (rm == 7) + return s->frm; + if (rm >= 5) + return -1; + else + return rm; +} +#endif + +/* return -1 if invalid CSR, 0 if OK, 1 if the interpreter loop must be + exited (e.g. XLEN was modified), 2 if TLBs have been flushed. */ +static int csr_write(RISCVCPUState *s, uint32_t csr, target_ulong val) +{ + target_ulong mask; + +#if defined(DUMP_CSR) + printf("csr_write: csr=0x%03x val=0x", csr); + print_target_ulong(val); + printf("\n"); +#endif + switch(csr) { +#if FLEN > 0 + case 0x001: /* fflags */ + s->fflags = val & 0x1f; + s->fs = 3; + break; + case 0x002: /* frm */ + set_frm(s, val & 7); + s->fs = 3; + break; + case 0x003: /* fcsr */ + set_frm(s, (val >> 5) & 7); + s->fflags = val & 0x1f; + s->fs = 3; + break; +#endif + case 0x100: /* sstatus */ + set_mstatus(s, (s->mstatus & ~SSTATUS_MASK) | (val & SSTATUS_MASK)); + break; + case 0x104: /* sie */ + mask = s->mideleg; + s->mie = (s->mie & ~mask) | (val & mask); + break; + case 0x105: + s->stvec = val & ~3; + break; + case 0x106: + s->scounteren = val & COUNTEREN_MASK; + break; + case 0x140: + s->sscratch = val; + break; + case 0x141: + s->sepc = val & ~1; + break; + case 0x142: + s->scause = val; + break; + case 0x143: + s->stval = val; + break; + case 0x144: /* sip */ + mask = s->mideleg; + s->mip = (s->mip & ~mask) | (val & mask); + break; + case 0x180: + /* no ASID implemented */ +#if MAX_XLEN == 32 + { + int new_mode; + new_mode = (val >> 31) & 1; + s->satp = (val & (((target_ulong)1 << 22) - 1)) | + (new_mode << 31); + } +#else + { + int mode, new_mode; + mode = s->satp >> 60; + new_mode = (val >> 60) & 0xf; + if (new_mode == 0 || (new_mode >= 8 && new_mode <= 9)) + mode = new_mode; + s->satp = (val & (((uint64_t)1 << 44) - 1)) | + ((uint64_t)mode << 60); + } +#endif + tlb_flush_all(s); + return 2; + + case 0x300: + set_mstatus(s, val); + break; + case 0x301: /* misa */ +#if MAX_XLEN >= 64 + { + int new_mxl; + new_mxl = (val >> (s->cur_xlen - 2)) & 3; + if (new_mxl >= 1 && new_mxl <= get_base_from_xlen(MAX_XLEN)) { + /* Note: misa is only modified in M level, so cur_xlen + = 2^(mxl + 4) */ + if (s->mxl != new_mxl) { + s->mxl = new_mxl; + s->cur_xlen = 1 << (new_mxl + 4); + return 1; + } + } + } +#endif + break; + case 0x302: + mask = (1 << (CAUSE_STORE_PAGE_FAULT + 1)) - 1; + s->medeleg = (s->medeleg & ~mask) | (val & mask); + break; + case 0x303: + mask = MIP_SSIP | MIP_STIP | MIP_SEIP; + s->mideleg = (s->mideleg & ~mask) | (val & mask); + break; + case 0x304: + mask = MIP_MSIP | MIP_MTIP | MIP_SSIP | MIP_STIP | MIP_SEIP; + s->mie = (s->mie & ~mask) | (val & mask); + break; + case 0x305: + s->mtvec = val & ~3; + break; + case 0x306: + s->mcounteren = val & COUNTEREN_MASK; + break; + case 0x340: + s->mscratch = val; + break; + case 0x341: + s->mepc = val & ~1; + break; + case 0x342: + s->mcause = val; + break; + case 0x343: + s->mtval = val; + break; + case 0x344: + mask = MIP_SSIP | MIP_STIP; + s->mip = (s->mip & ~mask) | (val & mask); + break; + default: +#ifdef DUMP_INVALID_CSR + printf("csr_write: invalid CSR=0x%x\n", csr); +#endif + return -1; + } + return 0; +} + +static void set_priv(RISCVCPUState *s, int priv) +{ + if (s->priv != priv) { + tlb_flush_all(s); +#if MAX_XLEN >= 64 + /* change the current xlen */ + { + int mxl; + if (priv == PRV_S) + mxl = (s->mstatus >> MSTATUS_SXL_SHIFT) & 3; + else if (priv == PRV_U) + mxl = (s->mstatus >> MSTATUS_UXL_SHIFT) & 3; + else + mxl = s->mxl; + s->cur_xlen = 1 << (4 + mxl); + } +#endif + s->priv = priv; + } +} + +static void raise_exception2(RISCVCPUState *s, uint32_t cause, + target_ulong tval) +{ + BOOL deleg; + target_ulong causel; + +#if defined(DUMP_EXCEPTIONS) || defined(DUMP_MMU_EXCEPTIONS) || defined(DUMP_INTERRUPTS) + { + int flag; + flag = 0; +#ifdef DUMP_MMU_EXCEPTIONS + if (cause == CAUSE_FAULT_FETCH || + cause == CAUSE_FAULT_LOAD || + cause == CAUSE_FAULT_STORE || + cause == CAUSE_FETCH_PAGE_FAULT || + cause == CAUSE_LOAD_PAGE_FAULT || + cause == CAUSE_STORE_PAGE_FAULT) + flag = 1; +#endif +#ifdef DUMP_INTERRUPTS + flag |= (cause & CAUSE_INTERRUPT) != 0; +#endif +#ifdef DUMP_EXCEPTIONS + flag = 1; + flag = (cause & CAUSE_INTERRUPT) == 0; + if (cause == CAUSE_SUPERVISOR_ECALL || cause == CAUSE_ILLEGAL_INSTRUCTION) + flag = 0; +#endif + if (flag) { + log_printf("raise_exception: cause=0x%08x tval=0x", cause); +#ifdef CONFIG_LOGFILE + fprint_target_ulong(log_file, tval); +#else + print_target_ulong(tval); +#endif + log_printf("\n"); + dump_regs(s); + } + } +#endif + + if (s->priv <= PRV_S) { + /* delegate the exception to the supervisor priviledge */ + if (cause & CAUSE_INTERRUPT) + deleg = (s->mideleg >> (cause & (MAX_XLEN - 1))) & 1; + else + deleg = (s->medeleg >> cause) & 1; + } else { + deleg = 0; + } + + causel = cause & 0x7fffffff; + if (cause & CAUSE_INTERRUPT) + causel |= (target_ulong)1 << (s->cur_xlen - 1); + + if (deleg) { + s->scause = causel; + s->sepc = s->pc; + s->stval = tval; + s->mstatus = (s->mstatus & ~MSTATUS_SPIE) | + (((s->mstatus >> s->priv) & 1) << MSTATUS_SPIE_SHIFT); + s->mstatus = (s->mstatus & ~MSTATUS_SPP) | + (s->priv << MSTATUS_SPP_SHIFT); + s->mstatus &= ~MSTATUS_SIE; + set_priv(s, PRV_S); + s->pc = s->stvec; + } else { + s->mcause = causel; + s->mepc = s->pc; + s->mtval = tval; + s->mstatus = (s->mstatus & ~MSTATUS_MPIE) | + (((s->mstatus >> s->priv) & 1) << MSTATUS_MPIE_SHIFT); + s->mstatus = (s->mstatus & ~MSTATUS_MPP) | + (s->priv << MSTATUS_MPP_SHIFT); + s->mstatus &= ~MSTATUS_MIE; + set_priv(s, PRV_M); + s->pc = s->mtvec; + } +} + +static void raise_exception(RISCVCPUState *s, uint32_t cause) +{ + raise_exception2(s, cause, 0); +} + +static void handle_sret(RISCVCPUState *s) +{ + int spp, spie; + spp = (s->mstatus >> MSTATUS_SPP_SHIFT) & 1; + /* set the IE state to previous IE state */ + spie = (s->mstatus >> MSTATUS_SPIE_SHIFT) & 1; + s->mstatus = (s->mstatus & ~(1 << spp)) | + (spie << spp); + /* set SPIE to 1 */ + s->mstatus |= MSTATUS_SPIE; + /* set SPP to U */ + s->mstatus &= ~MSTATUS_SPP; + set_priv(s, spp); + s->pc = s->sepc; +} + +static void handle_mret(RISCVCPUState *s) +{ + int mpp, mpie; + mpp = (s->mstatus >> MSTATUS_MPP_SHIFT) & 3; + /* set the IE state to previous IE state */ + mpie = (s->mstatus >> MSTATUS_MPIE_SHIFT) & 1; + s->mstatus = (s->mstatus & ~(1 << mpp)) | + (mpie << mpp); + /* set MPIE to 1 */ + s->mstatus |= MSTATUS_MPIE; + /* set MPP to U */ + s->mstatus &= ~MSTATUS_MPP; + set_priv(s, mpp); + s->pc = s->mepc; +} + +static inline uint32_t get_pending_irq_mask(RISCVCPUState *s) +{ + uint32_t pending_ints, enabled_ints; + + pending_ints = s->mip & s->mie; + if (pending_ints == 0) + return 0; + + enabled_ints = 0; + switch(s->priv) { + case PRV_M: + if (s->mstatus & MSTATUS_MIE) + enabled_ints = ~s->mideleg; + break; + case PRV_S: + enabled_ints = ~s->mideleg; + if (s->mstatus & MSTATUS_SIE) + enabled_ints |= s->mideleg; + break; + default: + case PRV_U: + enabled_ints = -1; + break; + } + return pending_ints & enabled_ints; +} + +static __exception int raise_interrupt(RISCVCPUState *s) +{ + uint32_t mask; + int irq_num; + + mask = get_pending_irq_mask(s); + if (mask == 0) + return 0; + irq_num = ctz32(mask); + raise_exception(s, irq_num | CAUSE_INTERRUPT); + return -1; +} + +static inline int32_t sext(int32_t val, int n) +{ + return (val << (32 - n)) >> (32 - n); +} + +static inline uint32_t get_field1(uint32_t val, int src_pos, + int dst_pos, int dst_pos_max) +{ + int mask; + assert(dst_pos_max >= dst_pos); + mask = ((1 << (dst_pos_max - dst_pos + 1)) - 1) << dst_pos; + if (dst_pos >= src_pos) + return (val << (dst_pos - src_pos)) & mask; + else + return (val >> (src_pos - dst_pos)) & mask; +} + +#define XLEN 32 +#include "riscv_cpu_template.h" + +#if MAX_XLEN >= 64 +#define XLEN 64 +#include "riscv_cpu_template.h" +#endif + +#if MAX_XLEN >= 128 +#define XLEN 128 +#include "riscv_cpu_template.h" +#endif + +static void glue(riscv_cpu_interp, MAX_XLEN)(RISCVCPUState *s, int n_cycles) +{ +#ifdef USE_GLOBAL_STATE + s = &riscv_cpu_global_state; +#endif + uint64_t timeout; + + timeout = s->insn_counter + n_cycles; + while (!s->power_down_flag && + (int)(timeout - s->insn_counter) > 0) { + n_cycles = timeout - s->insn_counter; + switch(s->cur_xlen) { + case 32: + riscv_cpu_interp_x32(s, n_cycles); + break; +#if MAX_XLEN >= 64 + case 64: + riscv_cpu_interp_x64(s, n_cycles); + break; +#endif +#if MAX_XLEN >= 128 + case 128: + riscv_cpu_interp_x128(s, n_cycles); + break; +#endif + default: + abort(); + } + } +} + +/* Note: the value is not accurate when called in riscv_cpu_interp() */ +static uint64_t glue(riscv_cpu_get_cycles, MAX_XLEN)(RISCVCPUState *s) +{ + return s->insn_counter; +} + +static void glue(riscv_cpu_set_mip, MAX_XLEN)(RISCVCPUState *s, uint32_t mask) +{ + s->mip |= mask; + /* exit from power down if an interrupt is pending */ + if (s->power_down_flag && (s->mip & s->mie) != 0) + s->power_down_flag = FALSE; +} + +static void glue(riscv_cpu_reset_mip, MAX_XLEN)(RISCVCPUState *s, uint32_t mask) +{ + s->mip &= ~mask; +} + +static uint32_t glue(riscv_cpu_get_mip, MAX_XLEN)(RISCVCPUState *s) +{ + return s->mip; +} + +static BOOL glue(riscv_cpu_get_power_down, MAX_XLEN)(RISCVCPUState *s) +{ + return s->power_down_flag; +} + +static RISCVCPUState *glue(riscv_cpu_init, MAX_XLEN)(PhysMemoryMap *mem_map) +{ + RISCVCPUState *s; + +#ifdef USE_GLOBAL_STATE + s = &riscv_cpu_global_state; +#else + s = mallocz(sizeof(*s)); +#endif + s->common.class_ptr = &glue(riscv_cpu_class, MAX_XLEN); + s->mem_map = mem_map; + s->pc = 0x1000; + s->priv = PRV_M; + s->cur_xlen = MAX_XLEN; + s->mxl = get_base_from_xlen(MAX_XLEN); + s->mstatus = ((uint64_t)s->mxl << MSTATUS_UXL_SHIFT) | + ((uint64_t)s->mxl << MSTATUS_SXL_SHIFT); + s->misa |= MCPUID_SUPER | MCPUID_USER | MCPUID_I | MCPUID_M | MCPUID_A; +#if FLEN >= 32 + s->misa |= MCPUID_F; +#endif +#if FLEN >= 64 + s->misa |= MCPUID_D; +#endif +#if FLEN >= 128 + s->misa |= MCPUID_Q; +#endif +#ifdef CONFIG_EXT_C + s->misa |= MCPUID_C; +#endif + tlb_init(s); + return s; +} + +static void glue(riscv_cpu_end, MAX_XLEN)(RISCVCPUState *s) +{ +#ifdef USE_GLOBAL_STATE + free(s); +#endif +} + +static uint32_t glue(riscv_cpu_get_misa, MAX_XLEN)(RISCVCPUState *s) +{ + return s->misa; +} + +const RISCVCPUClass glue(riscv_cpu_class, MAX_XLEN) = { + glue(riscv_cpu_init, MAX_XLEN), + glue(riscv_cpu_end, MAX_XLEN), + glue(riscv_cpu_interp, MAX_XLEN), + glue(riscv_cpu_get_cycles, MAX_XLEN), + glue(riscv_cpu_set_mip, MAX_XLEN), + glue(riscv_cpu_reset_mip, MAX_XLEN), + glue(riscv_cpu_get_mip, MAX_XLEN), + glue(riscv_cpu_get_power_down, MAX_XLEN), + glue(riscv_cpu_get_misa, MAX_XLEN), + glue(riscv_cpu_flush_tlb_write_range_ram, MAX_XLEN), +}; + +#if CONFIG_RISCV_MAX_XLEN == MAX_XLEN +RISCVCPUState *riscv_cpu_init(PhysMemoryMap *mem_map, int max_xlen) +{ + const RISCVCPUClass *c; + switch(max_xlen) { + /* with emscripten we compile a single CPU */ +#if defined(EMSCRIPTEN) + case MAX_XLEN: + c = &glue(riscv_cpu_class, MAX_XLEN); + break; +#else + case 32: + c = &riscv_cpu_class32; + break; + case 64: + c = &riscv_cpu_class64; + break; +#if CONFIG_RISCV_MAX_XLEN == 128 + case 128: + c = &riscv_cpu_class128; + break; +#endif +#endif /* !EMSCRIPTEN */ + default: + return NULL; + } + return c->riscv_cpu_init(mem_map); +} +#endif /* CONFIG_RISCV_MAX_XLEN == MAX_XLEN */ + diff --git a/riscv_cpu.h b/riscv_cpu.h new file mode 100644 index 0000000..6fb8b37 --- /dev/null +++ b/riscv_cpu.h @@ -0,0 +1,118 @@ +/* + * RISCV CPU emulator + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef RISCV_CPU_H +#define RISCV_CPU_H + +#include +#include "cutils.h" +#include "iomem.h" + +#define MIP_USIP (1 << 0) +#define MIP_SSIP (1 << 1) +#define MIP_HSIP (1 << 2) +#define MIP_MSIP (1 << 3) +#define MIP_UTIP (1 << 4) +#define MIP_STIP (1 << 5) +#define MIP_HTIP (1 << 6) +#define MIP_MTIP (1 << 7) +#define MIP_UEIP (1 << 8) +#define MIP_SEIP (1 << 9) +#define MIP_HEIP (1 << 10) +#define MIP_MEIP (1 << 11) + +typedef struct RISCVCPUState RISCVCPUState; + +typedef struct { + RISCVCPUState *(*riscv_cpu_init)(PhysMemoryMap *mem_map); + void (*riscv_cpu_end)(RISCVCPUState *s); + void (*riscv_cpu_interp)(RISCVCPUState *s, int n_cycles); + uint64_t (*riscv_cpu_get_cycles)(RISCVCPUState *s); + void (*riscv_cpu_set_mip)(RISCVCPUState *s, uint32_t mask); + void (*riscv_cpu_reset_mip)(RISCVCPUState *s, uint32_t mask); + uint32_t (*riscv_cpu_get_mip)(RISCVCPUState *s); + BOOL (*riscv_cpu_get_power_down)(RISCVCPUState *s); + uint32_t (*riscv_cpu_get_misa)(RISCVCPUState *s); + void (*riscv_cpu_flush_tlb_write_range_ram)(RISCVCPUState *s, + uint8_t *ram_ptr, size_t ram_size); +} RISCVCPUClass; + +typedef struct { + const RISCVCPUClass *class_ptr; +} RISCVCPUCommonState; + +int riscv_cpu_get_max_xlen(void); + +extern const RISCVCPUClass riscv_cpu_class32; +extern const RISCVCPUClass riscv_cpu_class64; +extern const RISCVCPUClass riscv_cpu_class128; + +RISCVCPUState *riscv_cpu_init(PhysMemoryMap *mem_map, int max_xlen); +static inline void riscv_cpu_end(RISCVCPUState *s) +{ + const RISCVCPUClass *c = ((RISCVCPUCommonState *)s)->class_ptr; + c->riscv_cpu_end(s); +} +static inline void riscv_cpu_interp(RISCVCPUState *s, int n_cycles) +{ + const RISCVCPUClass *c = ((RISCVCPUCommonState *)s)->class_ptr; + c->riscv_cpu_interp(s, n_cycles); +} +static inline uint64_t riscv_cpu_get_cycles(RISCVCPUState *s) +{ + const RISCVCPUClass *c = ((RISCVCPUCommonState *)s)->class_ptr; + return c->riscv_cpu_get_cycles(s); +} +static inline void riscv_cpu_set_mip(RISCVCPUState *s, uint32_t mask) +{ + const RISCVCPUClass *c = ((RISCVCPUCommonState *)s)->class_ptr; + c->riscv_cpu_set_mip(s, mask); +} +static inline void riscv_cpu_reset_mip(RISCVCPUState *s, uint32_t mask) +{ + const RISCVCPUClass *c = ((RISCVCPUCommonState *)s)->class_ptr; + c->riscv_cpu_reset_mip(s, mask); +} +static inline uint32_t riscv_cpu_get_mip(RISCVCPUState *s) +{ + const RISCVCPUClass *c = ((RISCVCPUCommonState *)s)->class_ptr; + return c->riscv_cpu_get_mip(s); +} +static inline BOOL riscv_cpu_get_power_down(RISCVCPUState *s) +{ + const RISCVCPUClass *c = ((RISCVCPUCommonState *)s)->class_ptr; + return c->riscv_cpu_get_power_down(s); +} +static inline uint32_t riscv_cpu_get_misa(RISCVCPUState *s) +{ + const RISCVCPUClass *c = ((RISCVCPUCommonState *)s)->class_ptr; + return c->riscv_cpu_get_misa(s); +} +static inline void riscv_cpu_flush_tlb_write_range_ram(RISCVCPUState *s, + uint8_t *ram_ptr, size_t ram_size) +{ + const RISCVCPUClass *c = ((RISCVCPUCommonState *)s)->class_ptr; + c->riscv_cpu_flush_tlb_write_range_ram(s, ram_ptr, ram_size); +} + +#endif /* RISCV_CPU_H */ diff --git a/riscv_cpu_fp_template.h b/riscv_cpu_fp_template.h new file mode 100644 index 0000000..88d4037 --- /dev/null +++ b/riscv_cpu_fp_template.h @@ -0,0 +1,304 @@ +/* + * RISCV emulator + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#if F_SIZE == 32 +#define OPID 0 +#define F_HIGH F32_HIGH +#elif F_SIZE == 64 +#define OPID 1 +#define F_HIGH F64_HIGH +#elif F_SIZE == 128 +#define OPID 3 +#define F_HIGH 0 +#else +#error unsupported F_SIZE +#endif + +#define FSIGN_MASK glue(FSIGN_MASK, F_SIZE) + + case (0x00 << 2) | OPID: + rm = get_insn_rm(s, rm); + if (rm < 0) + goto illegal_insn; + s->fp_reg[rd] = glue(add_sf, F_SIZE)(s->fp_reg[rs1], + s->fp_reg[rs2], + rm, &s->fflags) | F_HIGH; + s->fs = 3; + break; + case (0x01 << 2) | OPID: + rm = get_insn_rm(s, rm); + if (rm < 0) + goto illegal_insn; + s->fp_reg[rd] = glue(sub_sf, F_SIZE)(s->fp_reg[rs1], + s->fp_reg[rs2], + rm, &s->fflags) | F_HIGH; + s->fs = 3; + break; + case (0x02 << 2) | OPID: + rm = get_insn_rm(s, rm); + if (rm < 0) + goto illegal_insn; + s->fp_reg[rd] = glue(mul_sf, F_SIZE)(s->fp_reg[rs1], + s->fp_reg[rs2], + rm, &s->fflags) | F_HIGH; + s->fs = 3; + break; + case (0x03 << 2) | OPID: + rm = get_insn_rm(s, rm); + if (rm < 0) + goto illegal_insn; + s->fp_reg[rd] = glue(div_sf, F_SIZE)(s->fp_reg[rs1], + s->fp_reg[rs2], + rm, &s->fflags) | F_HIGH; + s->fs = 3; + break; + case (0x0b << 2) | OPID: + rm = get_insn_rm(s, rm); + if (rm < 0 || rs2 != 0) + goto illegal_insn; + s->fp_reg[rd] = glue(sqrt_sf, F_SIZE)(s->fp_reg[rs1], + rm, &s->fflags) | F_HIGH; + s->fs = 3; + break; + case (0x04 << 2) | OPID: + switch(rm) { + case 0: /* fsgnj */ + s->fp_reg[rd] = (s->fp_reg[rs1] & ~FSIGN_MASK) | + (s->fp_reg[rs2] & FSIGN_MASK); + break; + case 1: /* fsgnjn */ + s->fp_reg[rd] = (s->fp_reg[rs1] & ~FSIGN_MASK) | + ((s->fp_reg[rs2] & FSIGN_MASK) ^ FSIGN_MASK); + break; + case 2: /* fsgnjx */ + s->fp_reg[rd] = s->fp_reg[rs1] ^ + (s->fp_reg[rs2] & FSIGN_MASK); + break; + default: + goto illegal_insn; + } + s->fs = 3; + break; + case (0x05 << 2) | OPID: + switch(rm) { + case 0: /* fmin */ + s->fp_reg[rd] = glue(min_sf, F_SIZE)(s->fp_reg[rs1], + s->fp_reg[rs2], + &s->fflags, + FMINMAX_IEEE754_201X) | F_HIGH; + break; + case 1: /* fmax */ + s->fp_reg[rd] = glue(max_sf, F_SIZE)(s->fp_reg[rs1], + s->fp_reg[rs2], + &s->fflags, + FMINMAX_IEEE754_201X) | F_HIGH; + break; + default: + goto illegal_insn; + } + s->fs = 3; + break; + case (0x18 << 2) | OPID: + rm = get_insn_rm(s, rm); + if (rm < 0) + goto illegal_insn; + switch(rs2) { + case 0: /* fcvt.w.[sdq] */ + val = (int32_t)glue(glue(cvt_sf, F_SIZE), _i32)(s->fp_reg[rs1], rm, + &s->fflags); + break; + case 1: /* fcvt.wu.[sdq] */ + val = (int32_t)glue(glue(cvt_sf, F_SIZE), _u32)(s->fp_reg[rs1], rm, + &s->fflags); + break; +#if XLEN >= 64 + case 2: /* fcvt.l.[sdq] */ + val = (int64_t)glue(glue(cvt_sf, F_SIZE), _i64)(s->fp_reg[rs1], rm, + &s->fflags); + break; + case 3: /* fcvt.lu.[sdq] */ + val = (int64_t)glue(glue(cvt_sf, F_SIZE), _u64)(s->fp_reg[rs1], rm, + &s->fflags); + break; +#endif +#if XLEN >= 128 + /* XXX: the index is not defined in the spec */ + case 4: /* fcvt.t.[sdq] */ + val = glue(glue(cvt_sf, F_SIZE), _i128)(s->fp_reg[rs1], rm, + &s->fflags); + break; + case 5: /* fcvt.tu.[sdq] */ + val = glue(glue(cvt_sf, F_SIZE), _u128)(s->fp_reg[rs1], rm, + &s->fflags); + break; +#endif + default: + goto illegal_insn; + } + if (rd != 0) + s->reg[rd] = val; + break; + case (0x14 << 2) | OPID: + switch(rm) { + case 0: /* fle */ + val = glue(le_sf, F_SIZE)(s->fp_reg[rs1], s->fp_reg[rs2], + &s->fflags); + break; + case 1: /* flt */ + val = glue(lt_sf, F_SIZE)(s->fp_reg[rs1], s->fp_reg[rs2], + &s->fflags); + break; + case 2: /* feq */ + val = glue(eq_quiet_sf, F_SIZE)(s->fp_reg[rs1], s->fp_reg[rs2], + &s->fflags); + break; + default: + goto illegal_insn; + } + if (rd != 0) + s->reg[rd] = val; + break; + case (0x1a << 2) | OPID: + rm = get_insn_rm(s, rm); + if (rm < 0) + goto illegal_insn; + switch(rs2) { + case 0: /* fcvt.[sdq].w */ + s->fp_reg[rd] = glue(cvt_i32_sf, F_SIZE)(s->reg[rs1], rm, + &s->fflags) | F_HIGH; + break; + case 1: /* fcvt.[sdq].wu */ + s->fp_reg[rd] = glue(cvt_u32_sf, F_SIZE)(s->reg[rs1], rm, + &s->fflags) | F_HIGH; + break; +#if XLEN >= 64 + case 2: /* fcvt.[sdq].l */ + s->fp_reg[rd] = glue(cvt_i64_sf, F_SIZE)(s->reg[rs1], rm, + &s->fflags) | F_HIGH; + break; + case 3: /* fcvt.[sdq].lu */ + s->fp_reg[rd] = glue(cvt_u64_sf, F_SIZE)(s->reg[rs1], rm, + &s->fflags) | F_HIGH; + break; +#endif +#if XLEN >= 128 + /* XXX: the index is not defined in the spec */ + case 4: /* fcvt.[sdq].t */ + s->fp_reg[rd] = glue(cvt_i128_sf, F_SIZE)(s->reg[rs1], rm, + &s->fflags) | F_HIGH; + break; + case 5: /* fcvt.[sdq].tu */ + s->fp_reg[rd] = glue(cvt_u128_sf, F_SIZE)(s->reg[rs1], rm, + &s->fflags) | F_HIGH; + break; +#endif + default: + goto illegal_insn; + } + s->fs = 3; + break; + + case (0x08 << 2) | OPID: + rm = get_insn_rm(s, rm); + if (rm < 0) + goto illegal_insn; + switch(rs2) { +#if F_SIZE == 32 && FLEN >= 64 + case 1: /* cvt.s.d */ + s->fp_reg[rd] = cvt_sf64_sf32(s->fp_reg[rs1], rm, &s->fflags) | F32_HIGH; + break; +#if FLEN >= 128 + case 3: /* cvt.s.q */ + s->fp_reg[rd] = cvt_sf128_sf32(s->fp_reg[rs1], rm, &s->fflags) | F32_HIGH; + break; +#endif +#endif /* F_SIZE == 32 */ +#if F_SIZE == 64 + case 0: /* cvt.d.s */ + s->fp_reg[rd] = cvt_sf32_sf64(s->fp_reg[rs1], &s->fflags) | F64_HIGH; + break; +#if FLEN >= 128 + case 1: /* cvt.d.q */ + s->fp_reg[rd] = cvt_sf128_sf64(s->fp_reg[rs1], rm, &s->fflags) | F64_HIGH; + break; +#endif +#endif /* F_SIZE == 64 */ +#if F_SIZE == 128 + case 0: /* cvt.q.s */ + s->fp_reg[rd] = cvt_sf32_sf128(s->fp_reg[rs1], &s->fflags); + break; + case 1: /* cvt.q.d */ + s->fp_reg[rd] = cvt_sf64_sf128(s->fp_reg[rs1], &s->fflags); + break; +#endif /* F_SIZE == 128 */ + + default: + goto illegal_insn; + } + s->fs = 3; + break; + + case (0x1c << 2) | OPID: + if (rs2 != 0) + goto illegal_insn; + switch(rm) { +#if F_SIZE <= XLEN + case 0: /* fmv.x.s */ +#if F_SIZE == 32 + val = (int32_t)s->fp_reg[rs1]; +#elif F_SIZE == 64 + val = (int64_t)s->fp_reg[rs1]; +#else + val = (int128_t)s->fp_reg[rs1]; +#endif + break; +#endif /* F_SIZE <= XLEN */ + case 1: /* fclass */ + val = glue(fclass_sf, F_SIZE)(s->fp_reg[rs1]); + break; + default: + goto illegal_insn; + } + if (rd != 0) + s->reg[rd] = val; + break; + +#if F_SIZE <= XLEN + case (0x1e << 2) | OPID: /* fmv.s.x */ + if (rs2 != 0 || rm != 0) + goto illegal_insn; +#if F_SIZE == 32 + s->fp_reg[rd] = (int32_t)s->reg[rs1]; +#elif F_SIZE == 64 + s->fp_reg[rd] = (int64_t)s->reg[rs1]; +#else + s->fp_reg[rd] = (int128_t)s->reg[rs1]; +#endif + s->fs = 3; + break; +#endif /* F_SIZE <= XLEN */ + +#undef F_SIZE +#undef F_HIGH +#undef OPID +#undef FSIGN_MASK diff --git a/riscv_cpu_priv.h b/riscv_cpu_priv.h new file mode 100644 index 0000000..983d8b8 --- /dev/null +++ b/riscv_cpu_priv.h @@ -0,0 +1,293 @@ +/* + * RISCV CPU emulator private definitions + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef RISCV_CPU_PRIV_H +#define RISCV_CPU_PRIV_H + +#include "riscv_cpu.h" +#include "cutils.h" + +#define __exception __attribute__((warn_unused_result)) + +#ifndef FLEN +#if MAX_XLEN == 128 +#define FLEN 128 +#else +#define FLEN 64 +#endif +#endif /* !FLEN */ + +#define CONFIG_EXT_C /* compressed instructions */ + +#if defined(EMSCRIPTEN) +#define USE_GLOBAL_STATE +/* use local variables slows down the generated JS code */ +#define USE_GLOBAL_VARIABLES +#endif + +#if MAX_XLEN == 32 +typedef uint32_t target_ulong; +typedef int32_t target_long; +#define PR_target_ulong "08x" +#elif MAX_XLEN == 64 +typedef uint64_t target_ulong; +typedef int64_t target_long; +#define PR_target_ulong "016" PRIx64 +#elif MAX_XLEN == 128 +typedef uint128_t target_ulong; +typedef int128_t target_long; +#define PR_target_ulong "016" PRIx64 /* XXX */ +#else +#error unsupported MAX_XLEN +#endif + +/* FLEN is the floating point register width */ +#if FLEN > 0 +#if FLEN == 32 +typedef uint32_t fp_uint; +#define F32_HIGH 0 +#elif FLEN == 64 +typedef uint64_t fp_uint; +#define F32_HIGH ((fp_uint)-1 << 32) +#define F64_HIGH 0 +#elif FLEN == 128 +typedef uint128_t fp_uint; +#define F32_HIGH ((fp_uint)-1 << 32) +#define F64_HIGH ((fp_uint)-1 << 64) +#else +#error unsupported FLEN +#endif +#endif + +/* MLEN is the maximum memory access width */ +#if MAX_XLEN <= 32 && FLEN <= 32 +#define MLEN 32 +#elif MAX_XLEN <= 64 && FLEN <= 64 +#define MLEN 64 +#else +#define MLEN 128 +#endif + +#if MLEN == 32 +typedef uint32_t mem_uint_t; +#elif MLEN == 64 +typedef uint64_t mem_uint_t; +#elif MLEN == 128 +typedef uint128_t mem_uint_t; +#else +#unsupported MLEN +#endif + +#define TLB_SIZE 256 + +#define CAUSE_MISALIGNED_FETCH 0x0 +#define CAUSE_FAULT_FETCH 0x1 +#define CAUSE_ILLEGAL_INSTRUCTION 0x2 +#define CAUSE_BREAKPOINT 0x3 +#define CAUSE_MISALIGNED_LOAD 0x4 +#define CAUSE_FAULT_LOAD 0x5 +#define CAUSE_MISALIGNED_STORE 0x6 +#define CAUSE_FAULT_STORE 0x7 +#define CAUSE_USER_ECALL 0x8 +#define CAUSE_SUPERVISOR_ECALL 0x9 +#define CAUSE_HYPERVISOR_ECALL 0xa +#define CAUSE_MACHINE_ECALL 0xb +#define CAUSE_FETCH_PAGE_FAULT 0xc +#define CAUSE_LOAD_PAGE_FAULT 0xd +#define CAUSE_STORE_PAGE_FAULT 0xf + +/* Note: converted to correct bit position at runtime */ +#define CAUSE_INTERRUPT ((uint32_t)1 << 31) + +#define PRV_U 0 +#define PRV_S 1 +#define PRV_H 2 +#define PRV_M 3 + +/* misa CSR */ +#define MCPUID_SUPER (1 << ('S' - 'A')) +#define MCPUID_USER (1 << ('U' - 'A')) +#define MCPUID_I (1 << ('I' - 'A')) +#define MCPUID_M (1 << ('M' - 'A')) +#define MCPUID_A (1 << ('A' - 'A')) +#define MCPUID_F (1 << ('F' - 'A')) +#define MCPUID_D (1 << ('D' - 'A')) +#define MCPUID_Q (1 << ('Q' - 'A')) +#define MCPUID_C (1 << ('C' - 'A')) + +/* mstatus CSR */ + +#define MSTATUS_SPIE_SHIFT 5 +#define MSTATUS_MPIE_SHIFT 7 +#define MSTATUS_SPP_SHIFT 8 +#define MSTATUS_MPP_SHIFT 11 +#define MSTATUS_FS_SHIFT 13 +#define MSTATUS_UXL_SHIFT 32 +#define MSTATUS_SXL_SHIFT 34 + +#define MSTATUS_UIE (1 << 0) +#define MSTATUS_SIE (1 << 1) +#define MSTATUS_HIE (1 << 2) +#define MSTATUS_MIE (1 << 3) +#define MSTATUS_UPIE (1 << 4) +#define MSTATUS_SPIE (1 << MSTATUS_SPIE_SHIFT) +#define MSTATUS_HPIE (1 << 6) +#define MSTATUS_MPIE (1 << MSTATUS_MPIE_SHIFT) +#define MSTATUS_SPP (1 << MSTATUS_SPP_SHIFT) +#define MSTATUS_HPP (3 << 9) +#define MSTATUS_MPP (3 << MSTATUS_MPP_SHIFT) +#define MSTATUS_FS (3 << MSTATUS_FS_SHIFT) +#define MSTATUS_XS (3 << 15) +#define MSTATUS_MPRV (1 << 17) +#define MSTATUS_SUM (1 << 18) +#define MSTATUS_MXR (1 << 19) +//#define MSTATUS_TVM (1 << 20) +//#define MSTATUS_TW (1 << 21) +//#define MSTATUS_TSR (1 << 22) +#define MSTATUS_UXL_MASK ((uint64_t)3 << MSTATUS_UXL_SHIFT) +#define MSTATUS_SXL_MASK ((uint64_t)3 << MSTATUS_SXL_SHIFT) + +#define PG_SHIFT 12 +#define PG_MASK ((1 << PG_SHIFT) - 1) + +typedef struct { + target_ulong vaddr; + uintptr_t mem_addend; +} TLBEntry; + +struct RISCVCPUState { + RISCVCPUCommonState common; /* must be first */ + + target_ulong pc; + target_ulong reg[32]; + +#ifdef USE_GLOBAL_VARIABLES + /* faster to use global variables with emscripten */ + uint8_t *__code_ptr, *__code_end; + target_ulong __code_to_pc_addend; +#endif + +#if FLEN > 0 + fp_uint fp_reg[32]; + uint32_t fflags; + uint8_t frm; +#endif + + uint8_t cur_xlen; /* current XLEN value, <= MAX_XLEN */ + uint8_t priv; /* see PRV_x */ + uint8_t fs; /* MSTATUS_FS value */ + uint8_t mxl; /* MXL field in MISA register */ + + int32_t n_cycles; /* only used inside the CPU loop */ + uint64_t insn_counter; + BOOL power_down_flag; + int pending_exception; /* used during MMU exception handling */ + target_ulong pending_tval; + + /* CSRs */ + target_ulong mstatus; + target_ulong mtvec; + target_ulong mscratch; + target_ulong mepc; + target_ulong mcause; + target_ulong mtval; + target_ulong mhartid; /* ro */ + uint32_t misa; + uint32_t mie; + uint32_t mip; + uint32_t medeleg; + uint32_t mideleg; + uint32_t mcounteren; + + target_ulong stvec; + target_ulong sscratch; + target_ulong sepc; + target_ulong scause; + target_ulong stval; +#if MAX_XLEN == 32 + uint32_t satp; +#else + uint64_t satp; /* currently 64 bit physical addresses max */ +#endif + uint32_t scounteren; + + target_ulong load_res; /* for atomic LR/SC */ + + PhysMemoryMap *mem_map; + + TLBEntry tlb_read[TLB_SIZE]; + TLBEntry tlb_write[TLB_SIZE]; + TLBEntry tlb_code[TLB_SIZE]; +}; + +#define target_read_slow glue(glue(riscv, MAX_XLEN), _read_slow) +#define target_write_slow glue(glue(riscv, MAX_XLEN), _write_slow) + +DLL_PUBLIC int target_read_slow(RISCVCPUState *s, mem_uint_t *pval, + target_ulong addr, int size_log2); +DLL_PUBLIC int target_write_slow(RISCVCPUState *s, target_ulong addr, + mem_uint_t val, int size_log2); + +/* return 0 if OK, != 0 if exception */ +#define TARGET_READ_WRITE(size, uint_type, size_log2) \ +static inline __exception int target_read_u ## size(RISCVCPUState *s, uint_type *pval, target_ulong addr) \ +{\ + uint32_t tlb_idx;\ + tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1);\ + if (likely(s->tlb_read[tlb_idx].vaddr == (addr & ~(PG_MASK & ~((size / 8) - 1))))) { \ + *pval = *(uint_type *)(s->tlb_read[tlb_idx].mem_addend + (uintptr_t)addr);\ + } else {\ + mem_uint_t val;\ + int ret;\ + ret = target_read_slow(s, &val, addr, size_log2);\ + if (ret)\ + return ret;\ + *pval = val;\ + }\ + return 0;\ +}\ +\ +static inline __exception int target_write_u ## size(RISCVCPUState *s, target_ulong addr,\ + uint_type val) \ +{\ + uint32_t tlb_idx;\ + tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1);\ + if (likely(s->tlb_write[tlb_idx].vaddr == (addr & ~(PG_MASK & ~((size / 8) - 1))))) { \ + *(uint_type *)(s->tlb_write[tlb_idx].mem_addend + (uintptr_t)addr) = val;\ + return 0;\ + } else {\ + return target_write_slow(s, addr, val, size_log2);\ + }\ +} + +TARGET_READ_WRITE(8, uint8_t, 0) +TARGET_READ_WRITE(16, uint16_t, 1) +TARGET_READ_WRITE(32, uint32_t, 2) +#if MLEN >= 64 +TARGET_READ_WRITE(64, uint64_t, 3) +#endif +#if MLEN >= 128 +TARGET_READ_WRITE(128, uint128_t, 4) +#endif + +#endif /* RISCV_CPU_PRIV_H */ diff --git a/riscv_cpu_template.h b/riscv_cpu_template.h new file mode 100644 index 0000000..5092062 --- /dev/null +++ b/riscv_cpu_template.h @@ -0,0 +1,1739 @@ +/* + * RISCV emulator + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#if XLEN == 32 +#define uintx_t uint32_t +#define intx_t int32_t +#elif XLEN == 64 +#define uintx_t uint64_t +#define intx_t int64_t +#elif XLEN == 128 +#define uintx_t uint128_t +#define intx_t int128_t +#else +#error unsupported XLEN +#endif + +static inline intx_t glue(div, XLEN)(intx_t a, intx_t b) +{ + if (b == 0) { + return -1; + } else if (a == ((intx_t)1 << (XLEN - 1)) && b == -1) { + return a; + } else { + return a / b; + } +} + +static inline uintx_t glue(divu, XLEN)(uintx_t a, uintx_t b) +{ + if (b == 0) { + return -1; + } else { + return a / b; + } +} + +static inline intx_t glue(rem, XLEN)(intx_t a, intx_t b) +{ + if (b == 0) { + return a; + } else if (a == ((intx_t)1 << (XLEN - 1)) && b == -1) { + return 0; + } else { + return a % b; + } +} + +static inline uintx_t glue(remu, XLEN)(uintx_t a, uintx_t b) +{ + if (b == 0) { + return a; + } else { + return a % b; + } +} + +#if XLEN == 32 + +static inline uint32_t mulh32(int32_t a, int32_t b) +{ + return ((int64_t)a * (int64_t)b) >> 32; +} + +static inline uint32_t mulhsu32(int32_t a, uint32_t b) +{ + return ((int64_t)a * (int64_t)b) >> 32; +} + +static inline uint32_t mulhu32(uint32_t a, uint32_t b) +{ + return ((int64_t)a * (int64_t)b) >> 32; +} + +#elif XLEN == 64 && defined(HAVE_INT128) + +static inline uint64_t mulh64(int64_t a, int64_t b) +{ + return ((int128_t)a * (int128_t)b) >> 64; +} + +static inline uint64_t mulhsu64(int64_t a, uint64_t b) +{ + return ((int128_t)a * (int128_t)b) >> 64; +} + +static inline uint64_t mulhu64(uint64_t a, uint64_t b) +{ + return ((int128_t)a * (int128_t)b) >> 64; +} + +#else + +#if XLEN == 64 +#define UHALF uint32_t +#define UHALF_LEN 32 +#elif XLEN == 128 +#define UHALF uint64_t +#define UHALF_LEN 64 +#else +#error unsupported XLEN +#endif + +static uintx_t glue(mulhu, XLEN)(uintx_t a, uintx_t b) +{ + UHALF a0, a1, b0, b1, r2, r3; + uintx_t r00, r01, r10, r11, c; + a0 = a; + a1 = a >> UHALF_LEN; + b0 = b; + b1 = b >> UHALF_LEN; + + r00 = (uintx_t)a0 * (uintx_t)b0; + r01 = (uintx_t)a0 * (uintx_t)b1; + r10 = (uintx_t)a1 * (uintx_t)b0; + r11 = (uintx_t)a1 * (uintx_t)b1; + + // r0 = r00; + c = (r00 >> UHALF_LEN) + (UHALF)r01 + (UHALF)r10; + // r1 = c; + c = (c >> UHALF_LEN) + (r01 >> UHALF_LEN) + (r10 >> UHALF_LEN) + (UHALF)r11; + r2 = c; + r3 = (c >> UHALF_LEN) + (r11 >> UHALF_LEN); + + // *plow = ((uintx_t)r1 << UHALF_LEN) | r0; + return ((uintx_t)r3 << UHALF_LEN) | r2; +} + +#undef UHALF + +static inline uintx_t glue(mulh, XLEN)(intx_t a, intx_t b) +{ + uintx_t r1; + r1 = glue(mulhu, XLEN)(a, b); + if (a < 0) + r1 -= a; + if (b < 0) + r1 -= b; + return r1; +} + +static inline uintx_t glue(mulhsu, XLEN)(intx_t a, uintx_t b) +{ + uintx_t r1; + r1 = glue(mulhu, XLEN)(a, b); + if (a < 0) + r1 -= a; + return r1; +} + +#endif + +#define DUP2(F, n) F(n) F(n+1) +#define DUP4(F, n) DUP2(F, n) DUP2(F, n + 2) +#define DUP8(F, n) DUP4(F, n) DUP4(F, n + 4) +#define DUP16(F, n) DUP8(F, n) DUP8(F, n + 8) +#define DUP32(F, n) DUP16(F, n) DUP16(F, n + 16) + +#define C_QUADRANT(n) \ + case n+(0 << 2): case n+(1 << 2): case n+(2 << 2): case n+(3 << 2): \ + case n+(4 << 2): case n+(5 << 2): case n+(6 << 2): case n+(7 << 2): \ + case n+(8 << 2): case n+(9 << 2): case n+(10 << 2): case n+(11 << 2): \ + case n+(12 << 2): case n+(13 << 2): case n+(14 << 2): case n+(15 << 2): \ + case n+(16 << 2): case n+(17 << 2): case n+(18 << 2): case n+(19 << 2): \ + case n+(20 << 2): case n+(21 << 2): case n+(22 << 2): case n+(23 << 2): \ + case n+(24 << 2): case n+(25 << 2): case n+(26 << 2): case n+(27 << 2): \ + case n+(28 << 2): case n+(29 << 2): case n+(30 << 2): case n+(31 << 2): + +#define GET_PC() (target_ulong)((uintptr_t)code_ptr + code_to_pc_addend) +#define GET_INSN_COUNTER() (insn_counter_addend - s->n_cycles) + +#define C_NEXT_INSN code_ptr += 2; break +#define NEXT_INSN code_ptr += 4; break +#define JUMP_INSN do { \ + code_ptr = NULL; \ + code_end = NULL; \ + code_to_pc_addend = s->pc; \ + goto jump_insn; \ + } while (0) + +static void no_inline glue(riscv_cpu_interp_x, XLEN)(RISCVCPUState *s, + int n_cycles1) +{ + uint32_t opcode, insn, rd, rs1, rs2, funct3; + int32_t imm, cond, err; + target_ulong addr, val, val2; +#ifndef USE_GLOBAL_VARIABLES + uint8_t *code_ptr, *code_end; + target_ulong code_to_pc_addend; +#endif + uint64_t insn_counter_addend; +#if FLEN > 0 + uint32_t rs3; + int32_t rm; +#endif + + if (n_cycles1 == 0) + return; + insn_counter_addend = s->insn_counter + n_cycles1; + s->n_cycles = n_cycles1; + + /* check pending interrupts */ + if (unlikely((s->mip & s->mie) != 0)) { + if (raise_interrupt(s)) { + s->n_cycles--; + goto done_interp; + } + } + + s->pending_exception = -1; + /* Note: we assume NULL is represented as a zero number */ + code_ptr = NULL; + code_end = NULL; + code_to_pc_addend = s->pc; + + /* we use a single execution loop to keep a simple control flow + for emscripten */ + for(;;) { + if (unlikely(code_ptr >= code_end)) { + uint32_t tlb_idx; + uint16_t insn_high; + target_ulong addr; + uint8_t *ptr; + + s->pc = GET_PC(); + /* we test n_cycles only between blocks so that timer + interrupts only happen between the blocks. It is + important to reduce the translated code size. */ + if (unlikely(s->n_cycles <= 0)) + goto the_end; + + /* check pending interrupts */ + if (unlikely((s->mip & s->mie) != 0)) { + if (raise_interrupt(s)) { + s->n_cycles--; + goto the_end; + } + } + + addr = s->pc; + tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); + if (likely(s->tlb_code[tlb_idx].vaddr == (addr & ~PG_MASK))) { + /* TLB match */ + ptr = (uint8_t *)(s->tlb_code[tlb_idx].mem_addend + + (uintptr_t)addr); + } else { + if (unlikely(target_read_insn_slow(s, &ptr, addr))) + goto mmu_exception; + } + code_ptr = ptr; + code_end = ptr + (PG_MASK - 1 - (addr & PG_MASK)); + code_to_pc_addend = addr - (uintptr_t)code_ptr; + if (unlikely(code_ptr >= code_end)) { + /* instruction is potentially half way between two + pages ? */ + insn = *(uint16_t *)code_ptr; + if ((insn & 3) == 3) { + /* instruction is half way between two pages */ + if (unlikely(target_read_insn_u16(s, &insn_high, addr + 2))) + goto mmu_exception; + insn |= insn_high << 16; + } + } else { + insn = get_insn32(code_ptr); + } + } else { + /* fast path */ + insn = get_insn32(code_ptr); + } + s->n_cycles--; +#if 0 + if (1) { +#ifdef CONFIG_LOGFILE + log_printf("pc=0x"); fprint_target_ulong(log_file, GET_PC()); log_printf(" insn=%08x\n", insn); + fflush(log_file); +#else + printf("pc=0x"); print_target_ulong(GET_PC()); printf(" insn=%08x\n", insn); + // dump_regs(s); +#endif + } +#endif + opcode = insn & 0x7f; + rd = (insn >> 7) & 0x1f; + rs1 = (insn >> 15) & 0x1f; + rs2 = (insn >> 20) & 0x1f; + switch(opcode) { +#ifdef CONFIG_EXT_C + C_QUADRANT(0) + funct3 = (insn >> 13) & 7; + rd = ((insn >> 2) & 7) | 8; + switch(funct3) { + case 0: /* c.addi4spn */ + imm = get_field1(insn, 11, 4, 5) | + get_field1(insn, 7, 6, 9) | + get_field1(insn, 6, 2, 2) | + get_field1(insn, 5, 3, 3); + if (imm == 0) + goto illegal_insn; + s->reg[rd] = (intx_t)(s->reg[2] + imm); + break; +#if XLEN >= 128 + case 1: /* c.lq */ + imm = get_field1(insn, 11, 4, 5) | + get_field1(insn, 10, 8, 8) | + get_field1(insn, 5, 6, 7); + rs1 = ((insn >> 7) & 7) | 8; + addr = (intx_t)(s->reg[rs1] + imm); + if (target_read_u128(s, &val, addr)) + goto mmu_exception; + s->reg[rd] = val; + break; +#elif FLEN >= 64 + case 1: /* c.fld */ + { + uint64_t rval; + if (s->fs == 0) + goto illegal_insn; + imm = get_field1(insn, 10, 3, 5) | + get_field1(insn, 5, 6, 7); + rs1 = ((insn >> 7) & 7) | 8; + addr = (intx_t)(s->reg[rs1] + imm); + if (target_read_u64(s, &rval, addr)) + goto mmu_exception; + s->fp_reg[rd] = rval | F64_HIGH; + s->fs = 3; + } + break; +#endif + case 2: /* c.lw */ + { + uint32_t rval; + imm = get_field1(insn, 10, 3, 5) | + get_field1(insn, 6, 2, 2) | + get_field1(insn, 5, 6, 6); + rs1 = ((insn >> 7) & 7) | 8; + addr = (intx_t)(s->reg[rs1] + imm); + if (target_read_u32(s, &rval, addr)) + goto mmu_exception; + s->reg[rd] = (int32_t)rval; + } + break; +#if XLEN >= 64 + case 3: /* c.ld */ + { + uint64_t rval; + imm = get_field1(insn, 10, 3, 5) | + get_field1(insn, 5, 6, 7); + rs1 = ((insn >> 7) & 7) | 8; + addr = (intx_t)(s->reg[rs1] + imm); + if (target_read_u64(s, &rval, addr)) + goto mmu_exception; + s->reg[rd] = (int64_t)rval; + } + break; +#elif FLEN >= 32 + case 3: /* c.flw */ + { + uint32_t rval; + if (s->fs == 0) + goto illegal_insn; + imm = get_field1(insn, 10, 3, 5) | + get_field1(insn, 6, 2, 2) | + get_field1(insn, 5, 6, 6); + rs1 = ((insn >> 7) & 7) | 8; + addr = (intx_t)(s->reg[rs1] + imm); + if (target_read_u32(s, &rval, addr)) + goto mmu_exception; + s->fp_reg[rd] = rval | F32_HIGH; + s->fs = 3; + } + break; +#endif +#if XLEN >= 128 + case 5: /* c.sq */ + imm = get_field1(insn, 11, 4, 5) | + get_field1(insn, 10, 8, 8) | + get_field1(insn, 5, 6, 7); + rs1 = ((insn >> 7) & 7) | 8; + addr = (intx_t)(s->reg[rs1] + imm); + val = s->reg[rd]; + if (target_write_u128(s, addr, val)) + goto mmu_exception; + break; +#elif FLEN >= 64 + case 5: /* c.fsd */ + if (s->fs == 0) + goto illegal_insn; + imm = get_field1(insn, 10, 3, 5) | + get_field1(insn, 5, 6, 7); + rs1 = ((insn >> 7) & 7) | 8; + addr = (intx_t)(s->reg[rs1] + imm); + if (target_write_u64(s, addr, s->fp_reg[rd])) + goto mmu_exception; + break; +#endif + case 6: /* c.sw */ + imm = get_field1(insn, 10, 3, 5) | + get_field1(insn, 6, 2, 2) | + get_field1(insn, 5, 6, 6); + rs1 = ((insn >> 7) & 7) | 8; + addr = (intx_t)(s->reg[rs1] + imm); + val = s->reg[rd]; + if (target_write_u32(s, addr, val)) + goto mmu_exception; + break; +#if XLEN >= 64 + case 7: /* c.sd */ + imm = get_field1(insn, 10, 3, 5) | + get_field1(insn, 5, 6, 7); + rs1 = ((insn >> 7) & 7) | 8; + addr = (intx_t)(s->reg[rs1] + imm); + val = s->reg[rd]; + if (target_write_u64(s, addr, val)) + goto mmu_exception; + break; +#elif FLEN >= 32 + case 7: /* c.fsw */ + if (s->fs == 0) + goto illegal_insn; + imm = get_field1(insn, 10, 3, 5) | + get_field1(insn, 6, 2, 2) | + get_field1(insn, 5, 6, 6); + rs1 = ((insn >> 7) & 7) | 8; + addr = (intx_t)(s->reg[rs1] + imm); + if (target_write_u32(s, addr, s->fp_reg[rd])) + goto mmu_exception; + break; +#endif + default: + goto illegal_insn; + } + C_NEXT_INSN; + C_QUADRANT(1) + funct3 = (insn >> 13) & 7; + switch(funct3) { + case 0: /* c.addi/c.nop */ + if (rd != 0) { + imm = sext(get_field1(insn, 12, 5, 5) | + get_field1(insn, 2, 0, 4), 6); + s->reg[rd] = (intx_t)(s->reg[rd] + imm); + } + break; +#if XLEN == 32 + case 1: /* c.jal */ + imm = sext(get_field1(insn, 12, 11, 11) | + get_field1(insn, 11, 4, 4) | + get_field1(insn, 9, 8, 9) | + get_field1(insn, 8, 10, 10) | + get_field1(insn, 7, 6, 6) | + get_field1(insn, 6, 7, 7) | + get_field1(insn, 3, 1, 3) | + get_field1(insn, 2, 5, 5), 12); + s->reg[1] = GET_PC() + 2; + s->pc = (intx_t)(GET_PC() + imm); + JUMP_INSN; +#else + case 1: /* c.addiw */ + if (rd != 0) { + imm = sext(get_field1(insn, 12, 5, 5) | + get_field1(insn, 2, 0, 4), 6); + s->reg[rd] = (int32_t)(s->reg[rd] + imm); + } + break; +#endif + case 2: /* c.li */ + if (rd != 0) { + imm = sext(get_field1(insn, 12, 5, 5) | + get_field1(insn, 2, 0, 4), 6); + s->reg[rd] = imm; + } + break; + case 3: + if (rd == 2) { + /* c.addi16sp */ + imm = sext(get_field1(insn, 12, 9, 9) | + get_field1(insn, 6, 4, 4) | + get_field1(insn, 5, 6, 6) | + get_field1(insn, 3, 7, 8) | + get_field1(insn, 2, 5, 5), 10); + if (imm == 0) + goto illegal_insn; + s->reg[2] = (intx_t)(s->reg[2] + imm); + } else if (rd != 0) { + /* c.lui */ + imm = sext(get_field1(insn, 12, 17, 17) | + get_field1(insn, 2, 12, 16), 18); + s->reg[rd] = imm; + } + break; + case 4: + funct3 = (insn >> 10) & 3; + rd = ((insn >> 7) & 7) | 8; + switch(funct3) { + case 0: /* c.srli */ + case 1: /* c.srai */ + imm = get_field1(insn, 12, 5, 5) | + get_field1(insn, 2, 0, 4); +#if XLEN == 32 + if (imm & 0x20) + goto illegal_insn; +#elif XLEN == 128 + if (imm == 0) + imm = 64; + else if (imm >= 32) + imm = 128 - imm; +#endif + if (funct3 == 0) + s->reg[rd] = (intx_t)((uintx_t)s->reg[rd] >> imm); + else + s->reg[rd] = (intx_t)s->reg[rd] >> imm; + + break; + case 2: /* c.andi */ + imm = sext(get_field1(insn, 12, 5, 5) | + get_field1(insn, 2, 0, 4), 6); + s->reg[rd] &= imm; + break; + case 3: + rs2 = ((insn >> 2) & 7) | 8; + funct3 = ((insn >> 5) & 3) | ((insn >> (12 - 2)) & 4); + switch(funct3) { + case 0: /* c.sub */ + s->reg[rd] = (intx_t)(s->reg[rd] - s->reg[rs2]); + break; + case 1: /* c.xor */ + s->reg[rd] = s->reg[rd] ^ s->reg[rs2]; + break; + case 2: /* c.or */ + s->reg[rd] = s->reg[rd] | s->reg[rs2]; + break; + case 3: /* c.and */ + s->reg[rd] = s->reg[rd] & s->reg[rs2]; + break; +#if XLEN >= 64 + case 4: /* c.subw */ + s->reg[rd] = (int32_t)(s->reg[rd] - s->reg[rs2]); + break; + case 5: /* c.addw */ + s->reg[rd] = (int32_t)(s->reg[rd] + s->reg[rs2]); + break; +#endif + default: + goto illegal_insn; + } + break; + } + break; + case 5: /* c.j */ + imm = sext(get_field1(insn, 12, 11, 11) | + get_field1(insn, 11, 4, 4) | + get_field1(insn, 9, 8, 9) | + get_field1(insn, 8, 10, 10) | + get_field1(insn, 7, 6, 6) | + get_field1(insn, 6, 7, 7) | + get_field1(insn, 3, 1, 3) | + get_field1(insn, 2, 5, 5), 12); + s->pc = (intx_t)(GET_PC() + imm); + JUMP_INSN; + case 6: /* c.beqz */ + rs1 = ((insn >> 7) & 7) | 8; + imm = sext(get_field1(insn, 12, 8, 8) | + get_field1(insn, 10, 3, 4) | + get_field1(insn, 5, 6, 7) | + get_field1(insn, 3, 1, 2) | + get_field1(insn, 2, 5, 5), 9); + if (s->reg[rs1] == 0) { + s->pc = (intx_t)(GET_PC() + imm); + JUMP_INSN; + } + break; + case 7: /* c.bnez */ + rs1 = ((insn >> 7) & 7) | 8; + imm = sext(get_field1(insn, 12, 8, 8) | + get_field1(insn, 10, 3, 4) | + get_field1(insn, 5, 6, 7) | + get_field1(insn, 3, 1, 2) | + get_field1(insn, 2, 5, 5), 9); + if (s->reg[rs1] != 0) { + s->pc = (intx_t)(GET_PC() + imm); + JUMP_INSN; + } + break; + default: + goto illegal_insn; + } + C_NEXT_INSN; + C_QUADRANT(2) + funct3 = (insn >> 13) & 7; + rs2 = (insn >> 2) & 0x1f; + switch(funct3) { + case 0: /* c.slli */ + imm = get_field1(insn, 12, 5, 5) | rs2; +#if XLEN == 32 + if (imm & 0x20) + goto illegal_insn; +#elif XLEN == 128 + if (imm == 0) + imm = 64; +#endif + if (rd != 0) + s->reg[rd] = (intx_t)(s->reg[rd] << imm); + break; +#if XLEN == 128 + case 1: /* c.lqsp */ + imm = get_field1(insn, 12, 5, 5) | + (rs2 & (1 << 4)) | + get_field1(insn, 2, 6, 9); + addr = (intx_t)(s->reg[2] + imm); + if (target_read_u128(s, &val, addr)) + goto mmu_exception; + if (rd != 0) + s->reg[rd] = val; + break; +#elif FLEN >= 64 + case 1: /* c.fldsp */ + { + uint64_t rval; + if (s->fs == 0) + goto illegal_insn; + imm = get_field1(insn, 12, 5, 5) | + (rs2 & (3 << 3)) | + get_field1(insn, 2, 6, 8); + addr = (intx_t)(s->reg[2] + imm); + if (target_read_u64(s, &rval, addr)) + goto mmu_exception; + s->fp_reg[rd] = rval | F64_HIGH; + s->fs = 3; + } + break; +#endif + case 2: /* c.lwsp */ + { + uint32_t rval; + imm = get_field1(insn, 12, 5, 5) | + (rs2 & (7 << 2)) | + get_field1(insn, 2, 6, 7); + addr = (intx_t)(s->reg[2] + imm); + if (target_read_u32(s, &rval, addr)) + goto mmu_exception; + if (rd != 0) + s->reg[rd] = (int32_t)rval; + } + break; +#if XLEN >= 64 + case 3: /* c.ldsp */ + { + uint64_t rval; + imm = get_field1(insn, 12, 5, 5) | + (rs2 & (3 << 3)) | + get_field1(insn, 2, 6, 8); + addr = (intx_t)(s->reg[2] + imm); + if (target_read_u64(s, &rval, addr)) + goto mmu_exception; + if (rd != 0) + s->reg[rd] = (int64_t)rval; + } + break; +#elif FLEN >= 32 + case 3: /* c.flwsp */ + { + uint32_t rval; + if (s->fs == 0) + goto illegal_insn; + imm = get_field1(insn, 12, 5, 5) | + (rs2 & (7 << 2)) | + get_field1(insn, 2, 6, 7); + addr = (intx_t)(s->reg[2] + imm); + if (target_read_u32(s, &rval, addr)) + goto mmu_exception; + s->fp_reg[rd] = rval | F32_HIGH; + s->fs = 3; + } + break; +#endif + case 4: + if (((insn >> 12) & 1) == 0) { + if (rs2 == 0) { + /* c.jr */ + if (rd == 0) + goto illegal_insn; + s->pc = s->reg[rd] & ~1; + JUMP_INSN; + } else { + /* c.mv */ + if (rd != 0) + s->reg[rd] = s->reg[rs2]; + } + } else { + if (rs2 == 0) { + if (rd == 0) { + /* c.ebreak */ + s->pending_exception = CAUSE_BREAKPOINT; + goto exception; + } else { + /* c.jalr */ + val = GET_PC() + 2; + s->pc = s->reg[rd] & ~1; + s->reg[1] = val; + JUMP_INSN; + } + } else { + if (rd != 0) { + s->reg[rd] = (intx_t)(s->reg[rd] + s->reg[rs2]); + } + } + } + break; +#if XLEN == 128 + case 5: /* c.sqsp */ + imm = get_field1(insn, 10, 3, 5) | + get_field1(insn, 7, 6, 8); + addr = (intx_t)(s->reg[2] + imm); + if (target_write_u128(s, addr, s->reg[rs2])) + goto mmu_exception; + break; +#elif FLEN >= 64 + case 5: /* c.fsdsp */ + if (s->fs == 0) + goto illegal_insn; + imm = get_field1(insn, 10, 3, 5) | + get_field1(insn, 7, 6, 8); + addr = (intx_t)(s->reg[2] + imm); + if (target_write_u64(s, addr, s->fp_reg[rs2])) + goto mmu_exception; + break; +#endif + case 6: /* c.swsp */ + imm = get_field1(insn, 9, 2, 5) | + get_field1(insn, 7, 6, 7); + addr = (intx_t)(s->reg[2] + imm); + if (target_write_u32(s, addr, s->reg[rs2])) + goto mmu_exception; + break; +#if XLEN >= 64 + case 7: /* c.sdsp */ + imm = get_field1(insn, 10, 3, 5) | + get_field1(insn, 7, 6, 8); + addr = (intx_t)(s->reg[2] + imm); + if (target_write_u64(s, addr, s->reg[rs2])) + goto mmu_exception; + break; +#elif FLEN >= 32 + case 7: /* c.swsp */ + if (s->fs == 0) + goto illegal_insn; + imm = get_field1(insn, 9, 2, 5) | + get_field1(insn, 7, 6, 7); + addr = (intx_t)(s->reg[2] + imm); + if (target_write_u32(s, addr, s->fp_reg[rs2])) + goto mmu_exception; + break; +#endif + default: + goto illegal_insn; + } + C_NEXT_INSN; +#endif /* CONFIG_EXT_C */ + + case 0x37: /* lui */ + if (rd != 0) + s->reg[rd] = (int32_t)(insn & 0xfffff000); + NEXT_INSN; + case 0x17: /* auipc */ + if (rd != 0) + s->reg[rd] = (intx_t)(GET_PC() + (int32_t)(insn & 0xfffff000)); + NEXT_INSN; + case 0x6f: /* jal */ + imm = ((insn >> (31 - 20)) & (1 << 20)) | + ((insn >> (21 - 1)) & 0x7fe) | + ((insn >> (20 - 11)) & (1 << 11)) | + (insn & 0xff000); + imm = (imm << 11) >> 11; + if (rd != 0) + s->reg[rd] = GET_PC() + 4; + s->pc = (intx_t)(GET_PC() + imm); + JUMP_INSN; + case 0x67: /* jalr */ + imm = (int32_t)insn >> 20; + val = GET_PC() + 4; + s->pc = (intx_t)(s->reg[rs1] + imm) & ~1; + if (rd != 0) + s->reg[rd] = val; + JUMP_INSN; + case 0x63: + funct3 = (insn >> 12) & 7; + switch(funct3 >> 1) { + case 0: /* beq/bne */ + cond = (s->reg[rs1] == s->reg[rs2]); + break; + case 2: /* blt/bge */ + cond = ((target_long)s->reg[rs1] < (target_long)s->reg[rs2]); + break; + case 3: /* bltu/bgeu */ + cond = (s->reg[rs1] < s->reg[rs2]); + break; + default: + goto illegal_insn; + } + cond ^= (funct3 & 1); + if (cond) { + imm = ((insn >> (31 - 12)) & (1 << 12)) | + ((insn >> (25 - 5)) & 0x7e0) | + ((insn >> (8 - 1)) & 0x1e) | + ((insn << (11 - 7)) & (1 << 11)); + imm = (imm << 19) >> 19; + s->pc = (intx_t)(GET_PC() + imm); + JUMP_INSN; + } + NEXT_INSN; + case 0x03: /* load */ + funct3 = (insn >> 12) & 7; + imm = (int32_t)insn >> 20; + addr = s->reg[rs1] + imm; + switch(funct3) { + case 0: /* lb */ + { + uint8_t rval; + if (target_read_u8(s, &rval, addr)) + goto mmu_exception; + val = (int8_t)rval; + } + break; + case 1: /* lh */ + { + uint16_t rval; + if (target_read_u16(s, &rval, addr)) + goto mmu_exception; + val = (int16_t)rval; + } + break; + case 2: /* lw */ + { + uint32_t rval; + if (target_read_u32(s, &rval, addr)) + goto mmu_exception; + val = (int32_t)rval; + } + break; + case 4: /* lbu */ + { + uint8_t rval; + if (target_read_u8(s, &rval, addr)) + goto mmu_exception; + val = rval; + } + break; + case 5: /* lhu */ + { + uint16_t rval; + if (target_read_u16(s, &rval, addr)) + goto mmu_exception; + val = rval; + } + break; +#if XLEN >= 64 + case 3: /* ld */ + { + uint64_t rval; + if (target_read_u64(s, &rval, addr)) + goto mmu_exception; + val = (int64_t)rval; + } + break; + case 6: /* lwu */ + { + uint32_t rval; + if (target_read_u32(s, &rval, addr)) + goto mmu_exception; + val = rval; + } + break; +#endif +#if XLEN >= 128 + case 7: /* ldu */ + { + uint64_t rval; + if (target_read_u64(s, &rval, addr)) + goto mmu_exception; + val = rval; + } + break; +#endif + default: + goto illegal_insn; + } + if (rd != 0) + s->reg[rd] = val; + NEXT_INSN; + case 0x23: /* store */ + funct3 = (insn >> 12) & 7; + imm = rd | ((insn >> (25 - 5)) & 0xfe0); + imm = (imm << 20) >> 20; + addr = s->reg[rs1] + imm; + val = s->reg[rs2]; + switch(funct3) { + case 0: /* sb */ + if (target_write_u8(s, addr, val)) + goto mmu_exception; + break; + case 1: /* sh */ + if (target_write_u16(s, addr, val)) + goto mmu_exception; + break; + case 2: /* sw */ + if (target_write_u32(s, addr, val)) + goto mmu_exception; + break; +#if XLEN >= 64 + case 3: /* sd */ + if (target_write_u64(s, addr, val)) + goto mmu_exception; + break; +#endif +#if XLEN >= 128 + case 4: /* sq */ + if (target_write_u128(s, addr, val)) + goto mmu_exception; + break; +#endif + default: + goto illegal_insn; + } + NEXT_INSN; + case 0x13: + funct3 = (insn >> 12) & 7; + imm = (int32_t)insn >> 20; + switch(funct3) { + case 0: /* addi */ + val = (intx_t)(s->reg[rs1] + imm); + break; + case 1: /* slli */ + if ((imm & ~(XLEN - 1)) != 0) + goto illegal_insn; + val = (intx_t)(s->reg[rs1] << (imm & (XLEN - 1))); + break; + case 2: /* slti */ + val = (target_long)s->reg[rs1] < (target_long)imm; + break; + case 3: /* sltiu */ + val = s->reg[rs1] < (target_ulong)imm; + break; + case 4: /* xori */ + val = s->reg[rs1] ^ imm; + break; + case 5: /* srli/srai */ + if ((imm & ~((XLEN - 1) | 0x400)) != 0) + goto illegal_insn; + if (imm & 0x400) + val = (intx_t)s->reg[rs1] >> (imm & (XLEN - 1)); + else + val = (intx_t)((uintx_t)s->reg[rs1] >> (imm & (XLEN - 1))); + break; + case 6: /* ori */ + val = s->reg[rs1] | imm; + break; + default: + case 7: /* andi */ + val = s->reg[rs1] & imm; + break; + } + if (rd != 0) + s->reg[rd] = val; + NEXT_INSN; +#if XLEN >= 64 + case 0x1b:/* OP-IMM-32 */ + funct3 = (insn >> 12) & 7; + imm = (int32_t)insn >> 20; + val = s->reg[rs1]; + switch(funct3) { + case 0: /* addiw */ + val = (int32_t)(val + imm); + break; + case 1: /* slliw */ + if ((imm & ~31) != 0) + goto illegal_insn; + val = (int32_t)(val << (imm & 31)); + break; + case 5: /* srliw/sraiw */ + if ((imm & ~(31 | 0x400)) != 0) + goto illegal_insn; + if (imm & 0x400) + val = (int32_t)val >> (imm & 31); + else + val = (int32_t)((uint32_t)val >> (imm & 31)); + break; + default: + goto illegal_insn; + } + if (rd != 0) + s->reg[rd] = val; + NEXT_INSN; +#endif +#if XLEN >= 128 + case 0x5b: /* OP-IMM-64 */ + funct3 = (insn >> 12) & 7; + imm = (int32_t)insn >> 20; + val = s->reg[rs1]; + switch(funct3) { + case 0: /* addid */ + val = (int64_t)(val + imm); + break; + case 1: /* sllid */ + if ((imm & ~63) != 0) + goto illegal_insn; + val = (int64_t)(val << (imm & 63)); + break; + case 5: /* srlid/sraid */ + if ((imm & ~(63 | 0x400)) != 0) + goto illegal_insn; + if (imm & 0x400) + val = (int64_t)val >> (imm & 63); + else + val = (int64_t)((uint64_t)val >> (imm & 63)); + break; + default: + goto illegal_insn; + } + if (rd != 0) + s->reg[rd] = val; + NEXT_INSN; +#endif + case 0x33: + imm = insn >> 25; + val = s->reg[rs1]; + val2 = s->reg[rs2]; + if (imm == 1) { + funct3 = (insn >> 12) & 7; + switch(funct3) { + case 0: /* mul */ + val = (intx_t)((intx_t)val * (intx_t)val2); + break; + case 1: /* mulh */ + val = (intx_t)glue(mulh, XLEN)(val, val2); + break; + case 2:/* mulhsu */ + val = (intx_t)glue(mulhsu, XLEN)(val, val2); + break; + case 3:/* mulhu */ + val = (intx_t)glue(mulhu, XLEN)(val, val2); + break; + case 4:/* div */ + val = glue(div, XLEN)(val, val2); + break; + case 5:/* divu */ + val = (intx_t)glue(divu, XLEN)(val, val2); + break; + case 6:/* rem */ + val = glue(rem, XLEN)(val, val2); + break; + case 7:/* remu */ + val = (intx_t)glue(remu, XLEN)(val, val2); + break; + default: + goto illegal_insn; + } + } else { + if (imm & ~0x20) + goto illegal_insn; + funct3 = ((insn >> 12) & 7) | ((insn >> (30 - 3)) & (1 << 3)); + switch(funct3) { + case 0: /* add */ + val = (intx_t)(val + val2); + break; + case 0 | 8: /* sub */ + val = (intx_t)(val - val2); + break; + case 1: /* sll */ + val = (intx_t)(val << (val2 & (XLEN - 1))); + break; + case 2: /* slt */ + val = (target_long)val < (target_long)val2; + break; + case 3: /* sltu */ + val = val < val2; + break; + case 4: /* xor */ + val = val ^ val2; + break; + case 5: /* srl */ + val = (intx_t)((uintx_t)val >> (val2 & (XLEN - 1))); + break; + case 5 | 8: /* sra */ + val = (intx_t)val >> (val2 & (XLEN - 1)); + break; + case 6: /* or */ + val = val | val2; + break; + case 7: /* and */ + val = val & val2; + break; + default: + goto illegal_insn; + } + } + if (rd != 0) + s->reg[rd] = val; + NEXT_INSN; +#if XLEN >= 64 + case 0x3b: /* OP-32 */ + imm = insn >> 25; + val = s->reg[rs1]; + val2 = s->reg[rs2]; + if (imm == 1) { + funct3 = (insn >> 12) & 7; + switch(funct3) { + case 0: /* mulw */ + val = (int32_t)((int32_t)val * (int32_t)val2); + break; + case 4:/* divw */ + val = div32(val, val2); + break; + case 5:/* divuw */ + val = (int32_t)divu32(val, val2); + break; + case 6:/* remw */ + val = rem32(val, val2); + break; + case 7:/* remuw */ + val = (int32_t)remu32(val, val2); + break; + default: + goto illegal_insn; + } + } else { + if (imm & ~0x20) + goto illegal_insn; + funct3 = ((insn >> 12) & 7) | ((insn >> (30 - 3)) & (1 << 3)); + switch(funct3) { + case 0: /* addw */ + val = (int32_t)(val + val2); + break; + case 0 | 8: /* subw */ + val = (int32_t)(val - val2); + break; + case 1: /* sllw */ + val = (int32_t)((uint32_t)val << (val2 & 31)); + break; + case 5: /* srlw */ + val = (int32_t)((uint32_t)val >> (val2 & 31)); + break; + case 5 | 8: /* sraw */ + val = (int32_t)val >> (val2 & 31); + break; + default: + goto illegal_insn; + } + } + if (rd != 0) + s->reg[rd] = val; + NEXT_INSN; +#endif +#if XLEN >= 128 + case 0x7b: /* OP-64 */ + imm = insn >> 25; + val = s->reg[rs1]; + val2 = s->reg[rs2]; + if (imm == 1) { + funct3 = (insn >> 12) & 7; + switch(funct3) { + case 0: /* muld */ + val = (int64_t)((int64_t)val * (int64_t)val2); + break; + case 4:/* divd */ + val = div64(val, val2); + break; + case 5:/* divud */ + val = (int64_t)divu64(val, val2); + break; + case 6:/* remd */ + val = rem64(val, val2); + break; + case 7:/* remud */ + val = (int64_t)remu64(val, val2); + break; + default: + goto illegal_insn; + } + } else { + if (imm & ~0x20) + goto illegal_insn; + funct3 = ((insn >> 12) & 7) | ((insn >> (30 - 3)) & (1 << 3)); + switch(funct3) { + case 0: /* addd */ + val = (int64_t)(val + val2); + break; + case 0 | 8: /* subd */ + val = (int64_t)(val - val2); + break; + case 1: /* slld */ + val = (int64_t)((uint64_t)val << (val2 & 63)); + break; + case 5: /* srld */ + val = (int64_t)((uint64_t)val >> (val2 & 63)); + break; + case 5 | 8: /* srad */ + val = (int64_t)val >> (val2 & 63); + break; + default: + goto illegal_insn; + } + } + if (rd != 0) + s->reg[rd] = val; + NEXT_INSN; +#endif + case 0x73: + funct3 = (insn >> 12) & 7; + imm = insn >> 20; + if (funct3 & 4) + val = rs1; + else + val = s->reg[rs1]; + funct3 &= 3; + switch(funct3) { + case 1: /* csrrw */ + s->insn_counter = GET_INSN_COUNTER(); + if (csr_read(s, &val2, imm, TRUE)) + goto illegal_insn; + val2 = (intx_t)val2; + err = csr_write(s, imm, val); + if (err < 0) + goto illegal_insn; + if (rd != 0) + s->reg[rd] = val2; + if (err > 0) { + s->pc = GET_PC() + 4; + if (err == 2) + JUMP_INSN; + else + goto done_interp; + } + break; + case 2: /* csrrs */ + case 3: /* csrrc */ + s->insn_counter = GET_INSN_COUNTER(); + if (csr_read(s, &val2, imm, (rs1 != 0))) + goto illegal_insn; + val2 = (intx_t)val2; + if (rs1 != 0) { + if (funct3 == 2) + val = val2 | val; + else + val = val2 & ~val; + err = csr_write(s, imm, val); + if (err < 0) + goto illegal_insn; + } else { + err = 0; + } + if (rd != 0) + s->reg[rd] = val2; + if (err > 0) { + s->pc = GET_PC() + 4; + if (err == 2) + JUMP_INSN; + else + goto done_interp; + } + break; + case 0: + switch(imm) { + case 0x000: /* ecall */ + if (insn & 0x000fff80) + goto illegal_insn; + s->pending_exception = CAUSE_USER_ECALL + s->priv; + goto exception; + case 0x001: /* ebreak */ + if (insn & 0x000fff80) + goto illegal_insn; + s->pending_exception = CAUSE_BREAKPOINT; + goto exception; + case 0x102: /* sret */ + { + if (insn & 0x000fff80) + goto illegal_insn; + if (s->priv < PRV_S) + goto illegal_insn; + s->pc = GET_PC(); + handle_sret(s); + goto done_interp; + } + break; + case 0x302: /* mret */ + { + if (insn & 0x000fff80) + goto illegal_insn; + if (s->priv < PRV_M) + goto illegal_insn; + s->pc = GET_PC(); + handle_mret(s); + goto done_interp; + } + break; + case 0x105: /* wfi */ + if (insn & 0x00007f80) + goto illegal_insn; + if (s->priv == PRV_U) + goto illegal_insn; + /* go to power down if no enabled interrupts are + pending */ + if ((s->mip & s->mie) == 0) { + s->power_down_flag = TRUE; + s->pc = GET_PC() + 4; + goto done_interp; + } + break; + default: + if ((imm >> 5) == 0x09) { + /* sfence.vma */ + if (insn & 0x00007f80) + goto illegal_insn; + if (s->priv == PRV_U) + goto illegal_insn; + if (rs1 == 0) { + tlb_flush_all(s); + } else { + tlb_flush_vaddr(s, s->reg[rs1]); + } + /* the current code TLB may have been flushed */ + s->pc = GET_PC() + 4; + JUMP_INSN; + } else { + goto illegal_insn; + } + break; + } + break; + default: + goto illegal_insn; + } + NEXT_INSN; + case 0x0f: /* misc-mem */ + funct3 = (insn >> 12) & 7; + switch(funct3) { + case 0: /* fence */ + if (insn & 0xf00fff80) + goto illegal_insn; + break; + case 1: /* fence.i */ + if (insn != 0x0000100f) + goto illegal_insn; + break; +#if XLEN >= 128 + case 2: /* lq */ + imm = (int32_t)insn >> 20; + addr = s->reg[rs1] + imm; + if (target_read_u128(s, &val, addr)) + goto mmu_exception; + if (rd != 0) + s->reg[rd] = val; + break; +#endif + default: + goto illegal_insn; + } + NEXT_INSN; + case 0x2f: + funct3 = (insn >> 12) & 7; +#define OP_A(size) \ + { \ + uint ## size ##_t rval; \ + \ + addr = s->reg[rs1]; \ + funct3 = insn >> 27; \ + switch(funct3) { \ + case 2: /* lr.w */ \ + if (rs2 != 0) \ + goto illegal_insn; \ + if (target_read_u ## size(s, &rval, addr)) \ + goto mmu_exception; \ + val = (int## size ## _t)rval; \ + s->load_res = addr; \ + break; \ + case 3: /* sc.w */ \ + if (s->load_res == addr) { \ + if (target_write_u ## size(s, addr, s->reg[rs2])) \ + goto mmu_exception; \ + val = 0; \ + } else { \ + val = 1; \ + } \ + break; \ + case 1: /* amiswap.w */ \ + case 0: /* amoadd.w */ \ + case 4: /* amoxor.w */ \ + case 0xc: /* amoand.w */ \ + case 0x8: /* amoor.w */ \ + case 0x10: /* amomin.w */ \ + case 0x14: /* amomax.w */ \ + case 0x18: /* amominu.w */ \ + case 0x1c: /* amomaxu.w */ \ + if (target_read_u ## size(s, &rval, addr)) \ + goto mmu_exception; \ + val = (int## size ## _t)rval; \ + val2 = s->reg[rs2]; \ + switch(funct3) { \ + case 1: /* amiswap.w */ \ + break; \ + case 0: /* amoadd.w */ \ + val2 = (int## size ## _t)(val + val2); \ + break; \ + case 4: /* amoxor.w */ \ + val2 = (int## size ## _t)(val ^ val2); \ + break; \ + case 0xc: /* amoand.w */ \ + val2 = (int## size ## _t)(val & val2); \ + break; \ + case 0x8: /* amoor.w */ \ + val2 = (int## size ## _t)(val | val2); \ + break; \ + case 0x10: /* amomin.w */ \ + if ((int## size ## _t)val < (int## size ## _t)val2) \ + val2 = (int## size ## _t)val; \ + break; \ + case 0x14: /* amomax.w */ \ + if ((int## size ## _t)val > (int## size ## _t)val2) \ + val2 = (int## size ## _t)val; \ + break; \ + case 0x18: /* amominu.w */ \ + if ((uint## size ## _t)val < (uint## size ## _t)val2) \ + val2 = (int## size ## _t)val; \ + break; \ + case 0x1c: /* amomaxu.w */ \ + if ((uint## size ## _t)val > (uint## size ## _t)val2) \ + val2 = (int## size ## _t)val; \ + break; \ + default: \ + goto illegal_insn; \ + } \ + if (target_write_u ## size(s, addr, val2)) \ + goto mmu_exception; \ + break; \ + default: \ + goto illegal_insn; \ + } \ + } + + switch(funct3) { + case 2: + OP_A(32); + break; +#if XLEN >= 64 + case 3: + OP_A(64); + break; +#endif +#if XLEN >= 128 + case 4: + OP_A(128); + break; +#endif + default: + goto illegal_insn; + } + if (rd != 0) + s->reg[rd] = val; + NEXT_INSN; +#if FLEN > 0 + /* FPU */ + case 0x07: /* fp load */ + if (s->fs == 0) + goto illegal_insn; + funct3 = (insn >> 12) & 7; + imm = (int32_t)insn >> 20; + addr = s->reg[rs1] + imm; + switch(funct3) { + case 2: /* flw */ + { + uint32_t rval; + if (target_read_u32(s, &rval, addr)) + goto mmu_exception; + s->fp_reg[rd] = rval | F32_HIGH; + } + break; +#if FLEN >= 64 + case 3: /* fld */ + { + uint64_t rval; + if (target_read_u64(s, &rval, addr)) + goto mmu_exception; + s->fp_reg[rd] = rval | F64_HIGH; + } + break; +#endif +#if FLEN >= 128 + case 4: /* flq */ + { + uint128_t rval; + if (target_read_u128(s, &rval, addr)) + goto mmu_exception; + s->fp_reg[rd] = rval; + } + break; +#endif + default: + goto illegal_insn; + } + s->fs = 3; + NEXT_INSN; + case 0x27: /* fp store */ + if (s->fs == 0) + goto illegal_insn; + funct3 = (insn >> 12) & 7; + imm = rd | ((insn >> (25 - 5)) & 0xfe0); + imm = (imm << 20) >> 20; + addr = s->reg[rs1] + imm; + switch(funct3) { + case 2: /* fsw */ + if (target_write_u32(s, addr, s->fp_reg[rs2])) + goto mmu_exception; + break; +#if FLEN >= 64 + case 3: /* fsd */ + if (target_write_u64(s, addr, s->fp_reg[rs2])) + goto mmu_exception; + break; +#endif +#if FLEN >= 128 + case 4: /* fsq */ + if (target_write_u128(s, addr, s->fp_reg[rs2])) + goto mmu_exception; + break; +#endif + default: + goto illegal_insn; + } + NEXT_INSN; + case 0x43: /* fmadd */ + if (s->fs == 0) + goto illegal_insn; + funct3 = (insn >> 25) & 3; + rs3 = insn >> 27; + rm = get_insn_rm(s, (insn >> 12) & 7); + if (rm < 0) + goto illegal_insn; + switch(funct3) { + case 0: + s->fp_reg[rd] = fma_sf32(s->fp_reg[rs1], s->fp_reg[rs2], + s->fp_reg[rs3], rm, &s->fflags) | F32_HIGH; + break; +#if FLEN >= 64 + case 1: + s->fp_reg[rd] = fma_sf64(s->fp_reg[rs1], s->fp_reg[rs2], + s->fp_reg[rs3], rm, &s->fflags) | F64_HIGH; + break; +#endif +#if FLEN >= 128 + case 3: + s->fp_reg[rd] = fma_sf128(s->fp_reg[rs1], s->fp_reg[rs2], + s->fp_reg[rs3], rm, &s->fflags); + break; +#endif + default: + goto illegal_insn; + } + s->fs = 3; + NEXT_INSN; + case 0x47: /* fmsub */ + if (s->fs == 0) + goto illegal_insn; + funct3 = (insn >> 25) & 3; + rs3 = insn >> 27; + rm = get_insn_rm(s, (insn >> 12) & 7); + if (rm < 0) + goto illegal_insn; + switch(funct3) { + case 0: + s->fp_reg[rd] = fma_sf32(s->fp_reg[rs1], + s->fp_reg[rs2], + s->fp_reg[rs3] ^ FSIGN_MASK32, + rm, &s->fflags) | F32_HIGH; + break; +#if FLEN >= 64 + case 1: + s->fp_reg[rd] = fma_sf64(s->fp_reg[rs1], + s->fp_reg[rs2], + s->fp_reg[rs3] ^ FSIGN_MASK64, + rm, &s->fflags) | F64_HIGH; + break; +#endif +#if FLEN >= 128 + case 3: + s->fp_reg[rd] = fma_sf128(s->fp_reg[rs1], + s->fp_reg[rs2], + s->fp_reg[rs3] ^ FSIGN_MASK128, + rm, &s->fflags); + break; +#endif + default: + goto illegal_insn; + } + s->fs = 3; + NEXT_INSN; + case 0x4b: /* fnmsub */ + if (s->fs == 0) + goto illegal_insn; + funct3 = (insn >> 25) & 3; + rs3 = insn >> 27; + rm = get_insn_rm(s, (insn >> 12) & 7); + if (rm < 0) + goto illegal_insn; + switch(funct3) { + case 0: + s->fp_reg[rd] = fma_sf32(s->fp_reg[rs1] ^ FSIGN_MASK32, + s->fp_reg[rs2], + s->fp_reg[rs3], + rm, &s->fflags) | F32_HIGH; + break; +#if FLEN >= 64 + case 1: + s->fp_reg[rd] = fma_sf64(s->fp_reg[rs1] ^ FSIGN_MASK64, + s->fp_reg[rs2], + s->fp_reg[rs3], + rm, &s->fflags) | F64_HIGH; + break; +#endif +#if FLEN >= 128 + case 3: + s->fp_reg[rd] = fma_sf128(s->fp_reg[rs1] ^ FSIGN_MASK128, + s->fp_reg[rs2], + s->fp_reg[rs3], + rm, &s->fflags); + break; +#endif + default: + goto illegal_insn; + } + s->fs = 3; + NEXT_INSN; + case 0x4f: /* fnmadd */ + if (s->fs == 0) + goto illegal_insn; + funct3 = (insn >> 25) & 3; + rs3 = insn >> 27; + rm = get_insn_rm(s, (insn >> 12) & 7); + if (rm < 0) + goto illegal_insn; + switch(funct3) { + case 0: + s->fp_reg[rd] = fma_sf32(s->fp_reg[rs1] ^ FSIGN_MASK32, + s->fp_reg[rs2], + s->fp_reg[rs3] ^ FSIGN_MASK32, + rm, &s->fflags) | F32_HIGH; + break; +#if FLEN >= 64 + case 1: + s->fp_reg[rd] = fma_sf64(s->fp_reg[rs1] ^ FSIGN_MASK64, + s->fp_reg[rs2], + s->fp_reg[rs3] ^ FSIGN_MASK64, + rm, &s->fflags) | F64_HIGH; + break; +#endif +#if FLEN >= 128 + case 3: + s->fp_reg[rd] = fma_sf128(s->fp_reg[rs1] ^ FSIGN_MASK128, + s->fp_reg[rs2], + s->fp_reg[rs3] ^ FSIGN_MASK128, + rm, &s->fflags); + break; +#endif + default: + goto illegal_insn; + } + s->fs = 3; + NEXT_INSN; + case 0x53: + if (s->fs == 0) + goto illegal_insn; + imm = insn >> 25; + rm = (insn >> 12) & 7; + switch(imm) { + +#define F_SIZE 32 +#include "riscv_cpu_fp_template.h" +#if FLEN >= 64 +#define F_SIZE 64 +#include "riscv_cpu_fp_template.h" +#endif +#if FLEN >= 128 +#define F_SIZE 128 +#include "riscv_cpu_fp_template.h" +#endif + + default: + goto illegal_insn; + } + NEXT_INSN; +#endif + default: + goto illegal_insn; + } + /* update PC for next instruction */ + jump_insn: ; + } /* end of main loop */ + illegal_insn: + s->pending_exception = CAUSE_ILLEGAL_INSTRUCTION; + s->pending_tval = insn; + mmu_exception: + exception: + s->pc = GET_PC(); + if (s->pending_exception >= 0) { + /* Note: the idea is that one exception counts for one cycle. */ + s->n_cycles--; + raise_exception2(s, s->pending_exception, s->pending_tval); + } + /* we exit because XLEN may have changed */ + done_interp: +the_end: + s->insn_counter = GET_INSN_COUNTER(); +#if 0 + printf("done interp %lx int=%x mstatus=%lx prv=%d\n", + (uint64_t)s->insn_counter, s->mip & s->mie, (uint64_t)s->mstatus, + s->priv); +#endif +} + +#undef uintx_t +#undef intx_t +#undef XLEN +#undef OP_A diff --git a/riscv_machine.c b/riscv_machine.c new file mode 100644 index 0000000..a7149fd --- /dev/null +++ b/riscv_machine.c @@ -0,0 +1,1053 @@ +/* + * RISCV machine + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "iomem.h" +#include "riscv_cpu.h" +#include "virtio.h" +#include "machine.h" + +/* RISCV machine */ + +typedef struct RISCVMachine { + VirtMachine common; + PhysMemoryMap *mem_map; + int max_xlen; + RISCVCPUState *cpu_state; + uint64_t ram_size; + /* RTC */ + BOOL rtc_real_time; + uint64_t rtc_start_time; + uint64_t timecmp; + /* PLIC */ + uint32_t plic_pending_irq, plic_served_irq; + IRQSignal plic_irq[32]; /* IRQ 0 is not used */ + /* HTIF */ + uint64_t htif_tohost, htif_fromhost; + + VIRTIODevice *keyboard_dev; + VIRTIODevice *mouse_dev; + + int virtio_count; +} RISCVMachine; + +#define LOW_RAM_SIZE 0x00010000 /* 64KB */ +#define RAM_BASE_ADDR 0x80000000 +#define CLINT_BASE_ADDR 0x02000000 +#define CLINT_SIZE 0x000c0000 +#define HTIF_BASE_ADDR 0x40008000 +#define IDE_BASE_ADDR 0x40009000 +#define VIRTIO_BASE_ADDR 0x40010000 +#define VIRTIO_SIZE 0x1000 +#define VIRTIO_IRQ 1 +#define PLIC_BASE_ADDR 0x40100000 +#define PLIC_SIZE 0x00400000 +#define FRAMEBUFFER_BASE_ADDR 0x41000000 + +#define RTC_FREQ 10000000 +#define RTC_FREQ_DIV 16 /* arbitrary, relative to CPU freq to have a + 10 MHz frequency */ + +static uint64_t rtc_get_real_time(RISCVMachine *s) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * RTC_FREQ + + (ts.tv_nsec / (1000000000 / RTC_FREQ)); +} + +static uint64_t rtc_get_time(RISCVMachine *m) +{ + uint64_t val; + if (m->rtc_real_time) { + val = rtc_get_real_time(m) - m->rtc_start_time; + } else { + val = riscv_cpu_get_cycles(m->cpu_state) / RTC_FREQ_DIV; + } + // printf("rtc_time=%" PRId64 "\n", val); + return val; +} + +static uint32_t htif_read(void *opaque, uint32_t offset, + int size_log2) +{ + RISCVMachine *s = opaque; + uint32_t val; + + assert(size_log2 == 2); + switch(offset) { + case 0: + val = s->htif_tohost; + break; + case 4: + val = s->htif_tohost >> 32; + break; + case 8: + val = s->htif_fromhost; + break; + case 12: + val = s->htif_fromhost >> 32; + break; + default: + val = 0; + break; + } + return val; +} + +static void htif_handle_cmd(RISCVMachine *s) +{ + uint32_t device, cmd; + + device = s->htif_tohost >> 56; + cmd = (s->htif_tohost >> 48) & 0xff; + if (s->htif_tohost == 1) { + /* shuthost */ + printf("\nPower off.\n"); + exit(0); + } else if (device == 1 && cmd == 1) { + uint8_t buf[1]; + buf[0] = s->htif_tohost & 0xff; + s->common.console->write_data(s->common.console->opaque, buf, 1); + s->htif_tohost = 0; + s->htif_fromhost = ((uint64_t)device << 56) | ((uint64_t)cmd << 48); + } else if (device == 1 && cmd == 0) { + /* request keyboard interrupt */ + s->htif_tohost = 0; + } else { + printf("HTIF: unsupported tohost=0x%016" PRIx64 "\n", s->htif_tohost); + } +} + +static void htif_write(void *opaque, uint32_t offset, uint32_t val, + int size_log2) +{ + RISCVMachine *s = opaque; + + assert(size_log2 == 2); + switch(offset) { + case 0: + s->htif_tohost = (s->htif_tohost & ~0xffffffff) | val; + break; + case 4: + s->htif_tohost = (s->htif_tohost & 0xffffffff) | ((uint64_t)val << 32); + htif_handle_cmd(s); + break; + case 8: + s->htif_fromhost = (s->htif_fromhost & ~0xffffffff) | val; + break; + case 12: + s->htif_fromhost = (s->htif_fromhost & 0xffffffff) | + (uint64_t)val << 32; + break; + default: + break; + } +} + +#if 0 +static void htif_poll(RISCVMachine *s) +{ + uint8_t buf[1]; + int ret; + + if (s->htif_fromhost == 0) { + ret = s->console->read_data(s->console->opaque, buf, 1); + if (ret == 1) { + s->htif_fromhost = ((uint64_t)1 << 56) | ((uint64_t)0 << 48) | + buf[0]; + } + } +} +#endif + +static uint32_t clint_read(void *opaque, uint32_t offset, int size_log2) +{ + RISCVMachine *m = opaque; + uint32_t val; + + assert(size_log2 == 2); + switch(offset) { + case 0xbff8: + val = rtc_get_time(m); + break; + case 0xbffc: + val = rtc_get_time(m) >> 32; + break; + case 0x4000: + val = m->timecmp; + break; + case 0x4004: + val = m->timecmp >> 32; + break; + default: + val = 0; + break; + } + return val; +} + +static void clint_write(void *opaque, uint32_t offset, uint32_t val, + int size_log2) +{ + RISCVMachine *m = opaque; + + assert(size_log2 == 2); + switch(offset) { + case 0x4000: + m->timecmp = (m->timecmp & ~0xffffffff) | val; + riscv_cpu_reset_mip(m->cpu_state, MIP_MTIP); + break; + case 0x4004: + m->timecmp = (m->timecmp & 0xffffffff) | ((uint64_t)val << 32); + riscv_cpu_reset_mip(m->cpu_state, MIP_MTIP); + break; + default: + break; + } +} + +static void plic_update_mip(RISCVMachine *s) +{ + RISCVCPUState *cpu = s->cpu_state; + uint32_t mask; + mask = s->plic_pending_irq & ~s->plic_served_irq; + if (mask) { + riscv_cpu_set_mip(cpu, MIP_MEIP | MIP_SEIP); + } else { + riscv_cpu_reset_mip(cpu, MIP_MEIP | MIP_SEIP); + } +} + +#define PLIC_HART_BASE 0x200000 +#define PLIC_HART_SIZE 0x1000 + +static uint32_t plic_read(void *opaque, uint32_t offset, int size_log2) +{ + RISCVMachine *s = opaque; + uint32_t val, mask; + int i; + assert(size_log2 == 2); + switch(offset) { + case PLIC_HART_BASE: + val = 0; + break; + case PLIC_HART_BASE + 4: + mask = s->plic_pending_irq & ~s->plic_served_irq; + if (mask != 0) { + i = ctz32(mask); + s->plic_served_irq |= 1 << i; + plic_update_mip(s); + val = i + 1; + } else { + val = 0; + } + break; + default: + val = 0; + break; + } + return val; +} + +static void plic_write(void *opaque, uint32_t offset, uint32_t val, + int size_log2) +{ + RISCVMachine *s = opaque; + + assert(size_log2 == 2); + switch(offset) { + case PLIC_HART_BASE + 4: + val--; + if (val < 32) { + s->plic_served_irq &= ~(1 << val); + plic_update_mip(s); + } + break; + default: + break; + } +} + +static void plic_set_irq(void *opaque, int irq_num, int state) +{ + RISCVMachine *s = opaque; + uint32_t mask; + + mask = 1 << (irq_num - 1); + if (state) + s->plic_pending_irq |= mask; + else + s->plic_pending_irq &= ~mask; + plic_update_mip(s); +} + +static uint8_t *get_ram_ptr(RISCVMachine *s, uint64_t paddr, BOOL is_rw) +{ + return phys_mem_get_ram_ptr(s->mem_map, paddr, is_rw); +} + +/* FDT machine description */ + +#define FDT_MAGIC 0xd00dfeed +#define FDT_VERSION 17 + +struct fdt_header { + uint32_t magic; + uint32_t totalsize; + uint32_t off_dt_struct; + uint32_t off_dt_strings; + uint32_t off_mem_rsvmap; + uint32_t version; + uint32_t last_comp_version; /* <= 17 */ + uint32_t boot_cpuid_phys; + uint32_t size_dt_strings; + uint32_t size_dt_struct; +}; + +struct fdt_reserve_entry { + uint64_t address; + uint64_t size; +}; + +#define FDT_BEGIN_NODE 1 +#define FDT_END_NODE 2 +#define FDT_PROP 3 +#define FDT_NOP 4 +#define FDT_END 9 + +typedef struct { + uint32_t *tab; + int tab_len; + int tab_size; + int open_node_count; + + char *string_table; + int string_table_len; + int string_table_size; +} FDTState; + +static FDTState *fdt_init(void) +{ + FDTState *s; + s = mallocz(sizeof(*s)); + return s; +} + +static void fdt_alloc_len(FDTState *s, int len) +{ + int new_size; + if (unlikely(len > s->tab_size)) { + new_size = max_int(len, s->tab_size * 3 / 2); + s->tab = realloc(s->tab, new_size * sizeof(uint32_t)); + s->tab_size = new_size; + } +} + +static void fdt_put32(FDTState *s, int v) +{ + fdt_alloc_len(s, s->tab_len + 1); + s->tab[s->tab_len++] = cpu_to_be32(v); +} + +/* the data is zero padded */ +static void fdt_put_data(FDTState *s, const uint8_t *data, int len) +{ + int len1; + + len1 = (len + 3) / 4; + fdt_alloc_len(s, s->tab_len + len1); + memcpy(s->tab + s->tab_len, data, len); + memset((uint8_t *)(s->tab + s->tab_len) + len, 0, -len & 3); + s->tab_len += len1; +} + +static void fdt_begin_node(FDTState *s, const char *name) +{ + fdt_put32(s, FDT_BEGIN_NODE); + fdt_put_data(s, (uint8_t *)name, strlen(name) + 1); + s->open_node_count++; +} + +static void fdt_begin_node_num(FDTState *s, const char *name, uint64_t n) +{ + char buf[256]; + snprintf(buf, sizeof(buf), "%s@%" PRIx64, name, n); + fdt_begin_node(s, buf); +} + +static void fdt_end_node(FDTState *s) +{ + fdt_put32(s, FDT_END_NODE); + s->open_node_count--; +} + +static int fdt_get_string_offset(FDTState *s, const char *name) +{ + int pos, new_size, name_size, new_len; + + pos = 0; + while (pos < s->string_table_len) { + if (!strcmp(s->string_table + pos, name)) + return pos; + pos += strlen(s->string_table + pos) + 1; + } + /* add a new string */ + name_size = strlen(name) + 1; + new_len = s->string_table_len + name_size; + if (new_len > s->string_table_size) { + new_size = max_int(new_len, s->string_table_size * 3 / 2); + s->string_table = realloc(s->string_table, new_size); + s->string_table_size = new_size; + } + pos = s->string_table_len; + memcpy(s->string_table + pos, name, name_size); + s->string_table_len = new_len; + return pos; +} + +static void fdt_prop(FDTState *s, const char *prop_name, + const void *data, int data_len) +{ + fdt_put32(s, FDT_PROP); + fdt_put32(s, data_len); + fdt_put32(s, fdt_get_string_offset(s, prop_name)); + fdt_put_data(s, data, data_len); +} + +static void fdt_prop_tab_u32(FDTState *s, const char *prop_name, + uint32_t *tab, int tab_len) +{ + int i; + fdt_put32(s, FDT_PROP); + fdt_put32(s, tab_len * sizeof(uint32_t)); + fdt_put32(s, fdt_get_string_offset(s, prop_name)); + for(i = 0; i < tab_len; i++) + fdt_put32(s, tab[i]); +} + +static void fdt_prop_u32(FDTState *s, const char *prop_name, uint32_t val) +{ + fdt_prop_tab_u32(s, prop_name, &val, 1); +} + +static void fdt_prop_tab_u64(FDTState *s, const char *prop_name, + uint64_t v0) +{ + uint32_t tab[2]; + tab[0] = v0 >> 32; + tab[1] = v0; + fdt_prop_tab_u32(s, prop_name, tab, 2); +} + +static void fdt_prop_tab_u64_2(FDTState *s, const char *prop_name, + uint64_t v0, uint64_t v1) +{ + uint32_t tab[4]; + tab[0] = v0 >> 32; + tab[1] = v0; + tab[2] = v1 >> 32; + tab[3] = v1; + fdt_prop_tab_u32(s, prop_name, tab, 4); +} + +static void fdt_prop_str(FDTState *s, const char *prop_name, + const char *str) +{ + fdt_prop(s, prop_name, str, strlen(str) + 1); +} + +/* NULL terminated string list */ +static void fdt_prop_tab_str(FDTState *s, const char *prop_name, + ...) +{ + va_list ap; + int size, str_size; + char *ptr, *tab; + + va_start(ap, prop_name); + size = 0; + for(;;) { + ptr = va_arg(ap, char *); + if (!ptr) + break; + str_size = strlen(ptr) + 1; + size += str_size; + } + va_end(ap); + + tab = malloc(size); + va_start(ap, prop_name); + size = 0; + for(;;) { + ptr = va_arg(ap, char *); + if (!ptr) + break; + str_size = strlen(ptr) + 1; + memcpy(tab + size, ptr, str_size); + size += str_size; + } + va_end(ap); + + fdt_prop(s, prop_name, tab, size); + free(tab); +} + +/* write the FDT to 'dst1'. return the FDT size in bytes */ +int fdt_output(FDTState *s, uint8_t *dst) +{ + struct fdt_header *h; + struct fdt_reserve_entry *re; + int dt_struct_size; + int dt_strings_size; + int pos; + + assert(s->open_node_count == 0); + + fdt_put32(s, FDT_END); + + dt_struct_size = s->tab_len * sizeof(uint32_t); + dt_strings_size = s->string_table_len; + + h = (struct fdt_header *)dst; + h->magic = cpu_to_be32(FDT_MAGIC); + h->version = cpu_to_be32(FDT_VERSION); + h->last_comp_version = cpu_to_be32(16); + h->boot_cpuid_phys = cpu_to_be32(0); + h->size_dt_strings = cpu_to_be32(dt_strings_size); + h->size_dt_struct = cpu_to_be32(dt_struct_size); + + pos = sizeof(struct fdt_header); + + h->off_dt_struct = cpu_to_be32(pos); + memcpy(dst + pos, s->tab, dt_struct_size); + pos += dt_struct_size; + + /* align to 8 */ + while ((pos & 7) != 0) { + dst[pos++] = 0; + } + h->off_mem_rsvmap = cpu_to_be32(pos); + re = (struct fdt_reserve_entry *)(dst + pos); + re->address = 0; /* no reserved entry */ + re->size = 0; + pos += sizeof(struct fdt_reserve_entry); + + h->off_dt_strings = cpu_to_be32(pos); + memcpy(dst + pos, s->string_table, dt_strings_size); + pos += dt_strings_size; + + /* align to 8, just in case */ + while ((pos & 7) != 0) { + dst[pos++] = 0; + } + + h->totalsize = cpu_to_be32(pos); + return pos; +} + +void fdt_end(FDTState *s) +{ + free(s->tab); + free(s->string_table); + free(s); +} + +static int riscv_build_fdt(RISCVMachine *m, uint8_t *dst, + uint64_t kernel_start, uint64_t kernel_size, + uint64_t initrd_start, uint64_t initrd_size, + const char *cmd_line) +{ + FDTState *s; + int size, max_xlen, i, cur_phandle, intc_phandle, plic_phandle; + char isa_string[128], *q; + uint32_t misa; + uint32_t tab[4]; + FBDevice *fb_dev; + + s = fdt_init(); + + cur_phandle = 1; + + fdt_begin_node(s, ""); + fdt_prop_u32(s, "#address-cells", 2); + fdt_prop_u32(s, "#size-cells", 2); + fdt_prop_str(s, "compatible", "ucbbar,riscvemu-bar_dev"); + fdt_prop_str(s, "model", "ucbbar,riscvemu-bare"); + + /* CPU list */ + fdt_begin_node(s, "cpus"); + fdt_prop_u32(s, "#address-cells", 1); + fdt_prop_u32(s, "#size-cells", 0); + fdt_prop_u32(s, "timebase-frequency", RTC_FREQ); + + /* cpu */ + fdt_begin_node_num(s, "cpu", 0); + fdt_prop_str(s, "device_type", "cpu"); + fdt_prop_u32(s, "reg", 0); + fdt_prop_str(s, "status", "okay"); + fdt_prop_str(s, "compatible", "riscv"); + + max_xlen = m->max_xlen; + misa = riscv_cpu_get_misa(m->cpu_state); + q = isa_string; + q += snprintf(isa_string, sizeof(isa_string), "rv%d", max_xlen); + for(i = 0; i < 26; i++) { + if (misa & (1 << i)) + *q++ = 'a' + i; + } + *q = '\0'; + fdt_prop_str(s, "riscv,isa", isa_string); + + fdt_prop_str(s, "mmu-type", max_xlen <= 32 ? "riscv,sv32" : "riscv,sv48"); + fdt_prop_u32(s, "clock-frequency", 2000000000); + + fdt_begin_node(s, "interrupt-controller"); + fdt_prop_u32(s, "#interrupt-cells", 1); + fdt_prop(s, "interrupt-controller", NULL, 0); + fdt_prop_str(s, "compatible", "riscv,cpu-intc"); + intc_phandle = cur_phandle++; + fdt_prop_u32(s, "phandle", intc_phandle); + fdt_end_node(s); /* interrupt-controller */ + + fdt_end_node(s); /* cpu */ + + fdt_end_node(s); /* cpus */ + + fdt_begin_node_num(s, "memory", RAM_BASE_ADDR); + fdt_prop_str(s, "device_type", "memory"); + tab[0] = (uint64_t)RAM_BASE_ADDR >> 32; + tab[1] = RAM_BASE_ADDR; + tab[2] = m->ram_size >> 32; + tab[3] = m->ram_size; + fdt_prop_tab_u32(s, "reg", tab, 4); + + fdt_end_node(s); /* memory */ + + fdt_begin_node(s, "htif"); + fdt_prop_str(s, "compatible", "ucb,htif0"); + fdt_end_node(s); /* htif */ + + fdt_begin_node(s, "soc"); + fdt_prop_u32(s, "#address-cells", 2); + fdt_prop_u32(s, "#size-cells", 2); + fdt_prop_tab_str(s, "compatible", + "ucbbar,riscvemu-bar-soc", "simple-bus", NULL); + fdt_prop(s, "ranges", NULL, 0); + + fdt_begin_node_num(s, "clint", CLINT_BASE_ADDR); + fdt_prop_str(s, "compatible", "riscv,clint0"); + + tab[0] = intc_phandle; + tab[1] = 3; /* M IPI irq */ + tab[2] = intc_phandle; + tab[3] = 7; /* M timer irq */ + fdt_prop_tab_u32(s, "interrupts-extended", tab, 4); + + fdt_prop_tab_u64_2(s, "reg", CLINT_BASE_ADDR, CLINT_SIZE); + + fdt_end_node(s); /* clint */ + + fdt_begin_node_num(s, "plic", PLIC_BASE_ADDR); + fdt_prop_u32(s, "#interrupt-cells", 1); + fdt_prop(s, "interrupt-controller", NULL, 0); + fdt_prop_str(s, "compatible", "riscv,plic0"); + fdt_prop_u32(s, "riscv,ndev", 31); + fdt_prop_tab_u64_2(s, "reg", PLIC_BASE_ADDR, PLIC_SIZE); + + tab[0] = intc_phandle; + tab[1] = 9; /* S ext irq */ + tab[2] = intc_phandle; + tab[3] = 11; /* M ext irq */ + fdt_prop_tab_u32(s, "interrupts-extended", tab, 4); + + plic_phandle = cur_phandle++; + fdt_prop_u32(s, "phandle", plic_phandle); + + fdt_end_node(s); /* plic */ + + for(i = 0; i < m->virtio_count; i++) { + fdt_begin_node_num(s, "virtio", VIRTIO_BASE_ADDR + i * VIRTIO_SIZE); + fdt_prop_str(s, "compatible", "virtio,mmio"); + fdt_prop_tab_u64_2(s, "reg", VIRTIO_BASE_ADDR + i * VIRTIO_SIZE, + VIRTIO_SIZE); + tab[0] = plic_phandle; + tab[1] = VIRTIO_IRQ + i; + fdt_prop_tab_u32(s, "interrupts-extended", tab, 2); + fdt_end_node(s); /* virtio */ + } + + fb_dev = m->common.fb_dev; + if (fb_dev) { + fdt_begin_node_num(s, "framebuffer", FRAMEBUFFER_BASE_ADDR); + fdt_prop_str(s, "compatible", "simple-framebuffer"); + fdt_prop_tab_u64_2(s, "reg", FRAMEBUFFER_BASE_ADDR, fb_dev->fb_size); + fdt_prop_u32(s, "width", fb_dev->width); + fdt_prop_u32(s, "height", fb_dev->height); + fdt_prop_u32(s, "stride", fb_dev->stride); + fdt_prop_str(s, "format", "a8r8g8b8"); + fdt_end_node(s); /* framebuffer */ + } + + fdt_end_node(s); /* soc */ + + fdt_begin_node(s, "chosen"); + fdt_prop_str(s, "bootargs", cmd_line ? cmd_line : ""); + if (kernel_size > 0) { + fdt_prop_tab_u64(s, "riscv,kernel-start", kernel_start); + fdt_prop_tab_u64(s, "riscv,kernel-end", kernel_start + kernel_size); + } + if (initrd_size > 0) { + fdt_prop_tab_u64(s, "linux,initrd-start", initrd_start); + fdt_prop_tab_u64(s, "linux,initrd-end", initrd_start + initrd_size); + } + + + fdt_end_node(s); /* chosen */ + + fdt_end_node(s); /* / */ + + size = fdt_output(s, dst); +#if 0 + { + FILE *f; + f = fopen("/tmp/riscvemu.dtb", "wb"); + fwrite(dst, 1, size, f); + fclose(f); + } +#endif + fdt_end(s); + return size; +} + +static void copy_bios(RISCVMachine *s, const uint8_t *buf, int buf_len, + const uint8_t *kernel_buf, int kernel_buf_len, + const uint8_t *initrd_buf, int initrd_buf_len, + const char *cmd_line) +{ + uint32_t fdt_addr, align, kernel_base, initrd_base; + uint8_t *ram_ptr; + uint32_t *q; + + if (buf_len > s->ram_size) { + vm_error("BIOS too big\n"); + exit(1); + } + + ram_ptr = get_ram_ptr(s, RAM_BASE_ADDR, TRUE); + memcpy(ram_ptr, buf, buf_len); + + kernel_base = 0; + if (kernel_buf_len > 0) { + /* copy the kernel if present */ + if (s->max_xlen == 32) + align = 4 << 20; /* 4 MB page align */ + else + align = 2 << 20; /* 2 MB page align */ + kernel_base = (buf_len + align - 1) & ~(align - 1); + memcpy(ram_ptr + kernel_base, kernel_buf, kernel_buf_len); + if (kernel_buf_len + kernel_base > s->ram_size) { + vm_error("kernel too big"); + exit(1); + } + } + + initrd_base = 0; + if (initrd_buf_len > 0) { + /* same allocation as QEMU */ + initrd_base = s->ram_size / 2; + if (initrd_base > (128 << 20)) + initrd_base = 128 << 20; + memcpy(ram_ptr + initrd_base, initrd_buf, initrd_buf_len); + if (initrd_buf_len + initrd_base > s->ram_size) { + vm_error("initrd too big"); + exit(1); + } + } + + ram_ptr = get_ram_ptr(s, 0, TRUE); + + fdt_addr = 0x1000 + 8 * 8; + + riscv_build_fdt(s, ram_ptr + fdt_addr, + RAM_BASE_ADDR + kernel_base, kernel_buf_len, + RAM_BASE_ADDR + initrd_base, initrd_buf_len, + cmd_line); + + /* jump_addr = 0x80000000 */ + + q = (uint32_t *)(ram_ptr + 0x1000); + q[0] = 0x297 + 0x80000000 - 0x1000; /* auipc t0, jump_addr */ + q[1] = 0x597; /* auipc a1, dtb */ + q[2] = 0x58593 + ((fdt_addr - 4) << 20); /* addi a1, a1, dtb */ + q[3] = 0xf1402573; /* csrr a0, mhartid */ + q[4] = 0x00028067; /* jalr zero, t0, jump_addr */ +} + +static void riscv_flush_tlb_write_range(void *opaque, uint8_t *ram_addr, + size_t ram_size) +{ + RISCVMachine *s = opaque; + riscv_cpu_flush_tlb_write_range_ram(s->cpu_state, ram_addr, ram_size); +} + +static void riscv_machine_set_defaults(VirtMachineParams *p) +{ +} + +static VirtMachine *riscv_machine_init(const VirtMachineParams *p) +{ + RISCVMachine *s; + VIRTIODevice *blk_dev; + int irq_num, i, max_xlen, ram_flags; + VIRTIOBusDef vbus_s, *vbus = &vbus_s; + + + if (!strcmp(p->machine_name, "riscv32")) { + max_xlen = 32; + } else if (!strcmp(p->machine_name, "riscv64")) { + max_xlen = 64; + } else if (!strcmp(p->machine_name, "riscv128")) { + max_xlen = 128; + } else { + vm_error("unsupported machine: %s\n", p->machine_name); + return NULL; + } + + s = mallocz(sizeof(*s)); + s->common.vmc = p->vmc; + s->ram_size = p->ram_size; + s->max_xlen = max_xlen; + s->mem_map = phys_mem_map_init(); + /* needed to handle the RAM dirty bits */ + s->mem_map->opaque = s; + s->mem_map->flush_tlb_write_range = riscv_flush_tlb_write_range; + + s->cpu_state = riscv_cpu_init(s->mem_map, max_xlen); + if (!s->cpu_state) { + vm_error("unsupported max_xlen=%d\n", max_xlen); + /* XXX: should free resources */ + return NULL; + } + /* RAM */ + ram_flags = 0; + cpu_register_ram(s->mem_map, RAM_BASE_ADDR, p->ram_size, ram_flags); + cpu_register_ram(s->mem_map, 0x00000000, LOW_RAM_SIZE, 0); + s->rtc_real_time = p->rtc_real_time; + if (p->rtc_real_time) { + s->rtc_start_time = rtc_get_real_time(s); + } + + cpu_register_device(s->mem_map, CLINT_BASE_ADDR, CLINT_SIZE, s, + clint_read, clint_write, DEVIO_SIZE32); + cpu_register_device(s->mem_map, PLIC_BASE_ADDR, PLIC_SIZE, s, + plic_read, plic_write, DEVIO_SIZE32); + for(i = 1; i < 32; i++) { + irq_init(&s->plic_irq[i], plic_set_irq, s, i); + } + + cpu_register_device(s->mem_map, HTIF_BASE_ADDR, 16, + s, htif_read, htif_write, DEVIO_SIZE32); + s->common.console = p->console; + + memset(vbus, 0, sizeof(*vbus)); + vbus->mem_map = s->mem_map; + vbus->addr = VIRTIO_BASE_ADDR; + irq_num = VIRTIO_IRQ; + + /* virtio console */ + if (p->console) { + vbus->irq = &s->plic_irq[irq_num]; + s->common.console_dev = virtio_console_init(vbus, p->console); + vbus->addr += VIRTIO_SIZE; + irq_num++; + s->virtio_count++; + } + + /* virtio net device */ + for(i = 0; i < p->eth_count; i++) { + vbus->irq = &s->plic_irq[irq_num]; + virtio_net_init(vbus, p->tab_eth[i].net); + s->common.net = p->tab_eth[i].net; + vbus->addr += VIRTIO_SIZE; + irq_num++; + s->virtio_count++; + } + + /* virtio block device */ + for(i = 0; i < p->drive_count; i++) { + vbus->irq = &s->plic_irq[irq_num]; + blk_dev = virtio_block_init(vbus, p->tab_drive[i].block_dev); + (void)blk_dev; + vbus->addr += VIRTIO_SIZE; + irq_num++; + s->virtio_count++; + } + + /* virtio filesystem */ + for(i = 0; i < p->fs_count; i++) { + VIRTIODevice *fs_dev; + vbus->irq = &s->plic_irq[irq_num]; + fs_dev = virtio_9p_init(vbus, p->tab_fs[i].fs_dev, + p->tab_fs[i].tag); + (void)fs_dev; + // virtio_set_debug(fs_dev, VIRTIO_DEBUG_9P); + vbus->addr += VIRTIO_SIZE; + irq_num++; + s->virtio_count++; + } + + if (p->display_device) { + FBDevice *fb_dev; + fb_dev = mallocz(sizeof(*fb_dev)); + s->common.fb_dev = fb_dev; + if (!strcmp(p->display_device, "simplefb")) { + simplefb_init(s->mem_map, + FRAMEBUFFER_BASE_ADDR, + fb_dev, + p->width, p->height); + + } else { + vm_error("unsupported display device: %s\n", p->display_device); + exit(1); + } + } + + if (p->input_device) { + if (!strcmp(p->input_device, "virtio")) { + vbus->irq = &s->plic_irq[irq_num]; + s->keyboard_dev = virtio_input_init(vbus, + VIRTIO_INPUT_TYPE_KEYBOARD); + vbus->addr += VIRTIO_SIZE; + irq_num++; + s->virtio_count++; + + vbus->irq = &s->plic_irq[irq_num]; + s->mouse_dev = virtio_input_init(vbus, + VIRTIO_INPUT_TYPE_TABLET); + vbus->addr += VIRTIO_SIZE; + irq_num++; + s->virtio_count++; + } else { + vm_error("unsupported input device: %s\n", p->input_device); + exit(1); + } + } + + if (!p->files[VM_FILE_BIOS].buf) { + vm_error("No bios found"); + } + + copy_bios(s, p->files[VM_FILE_BIOS].buf, p->files[VM_FILE_BIOS].len, + p->files[VM_FILE_KERNEL].buf, p->files[VM_FILE_KERNEL].len, + p->files[VM_FILE_INITRD].buf, p->files[VM_FILE_INITRD].len, + p->cmdline); + + return (VirtMachine *)s; +} + +static void riscv_machine_end(VirtMachine *s1) +{ + RISCVMachine *s = (RISCVMachine *)s1; + /* XXX: stop all */ + riscv_cpu_end(s->cpu_state); + phys_mem_map_end(s->mem_map); + free(s); +} + +/* in ms */ +static int riscv_machine_get_sleep_duration(VirtMachine *s1, int delay) +{ + RISCVMachine *m = (RISCVMachine *)s1; + RISCVCPUState *s = m->cpu_state; + int64_t delay1; + + /* wait for an event: the only asynchronous event is the RTC timer */ + if (!(riscv_cpu_get_mip(s) & MIP_MTIP)) { + delay1 = m->timecmp - rtc_get_time(m); + if (delay1 <= 0) { + riscv_cpu_set_mip(s, MIP_MTIP); + delay = 0; + } else { + /* convert delay to ms */ + delay1 = delay1 / (RTC_FREQ / 1000); + if (delay1 < delay) + delay = delay1; + } + } + if (!riscv_cpu_get_power_down(s)) + delay = 0; + return delay; +} + +static void riscv_machine_interp(VirtMachine *s1, int max_exec_cycle) +{ + RISCVMachine *s = (RISCVMachine *)s1; + riscv_cpu_interp(s->cpu_state, max_exec_cycle); +} + +static void riscv_vm_send_key_event(VirtMachine *s1, BOOL is_down, + uint16_t key_code) +{ + RISCVMachine *s = (RISCVMachine *)s1; + if (s->keyboard_dev) { + virtio_input_send_key_event(s->keyboard_dev, is_down, key_code); + } +} + +static BOOL riscv_vm_mouse_is_absolute(VirtMachine *s) +{ + return TRUE; +} + +static void riscv_vm_send_mouse_event(VirtMachine *s1, int dx, int dy, int dz, + unsigned int buttons) +{ + RISCVMachine *s = (RISCVMachine *)s1; + if (s->mouse_dev) { + virtio_input_send_mouse_event(s->mouse_dev, dx, dy, dz, buttons); + } +} + +const VirtMachineClass riscv_machine_class = { + "riscv32,riscv64,riscv128", + riscv_machine_set_defaults, + riscv_machine_init, + riscv_machine_end, + riscv_machine_get_sleep_duration, + riscv_machine_interp, + riscv_vm_mouse_is_absolute, + riscv_vm_send_mouse_event, + riscv_vm_send_key_event, +}; diff --git a/sdl.c b/sdl.c new file mode 100644 index 0000000..c2afeba --- /dev/null +++ b/sdl.c @@ -0,0 +1,275 @@ +/* + * SDL display driver + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cutils.h" +#include "virtio.h" +#include "machine.h" + +#define KEYCODE_MAX 127 + +static SDL_Surface *screen; +static SDL_Surface *fb_surface; +static int screen_width, screen_height, fb_width, fb_height, fb_stride; +static SDL_Cursor *sdl_cursor_hidden; +static uint8_t key_pressed[KEYCODE_MAX + 1]; + +static void sdl_update_fb_surface(FBDevice *fb_dev) +{ + if (!fb_surface) + goto force_alloc; + if (fb_width != fb_dev->width || + fb_height != fb_dev->height || + fb_stride != fb_dev->stride) { + force_alloc: + if (fb_surface != NULL) + SDL_FreeSurface(fb_surface); + fb_width = fb_dev->width; + fb_height = fb_dev->height; + fb_stride = fb_dev->stride; + fb_surface = SDL_CreateRGBSurfaceFrom(fb_dev->fb_data, + fb_dev->width, fb_dev->height, + 32, fb_dev->stride, + 0x00ff0000, + 0x0000ff00, + 0x000000ff, + 0x00000000); + if (!fb_surface) { + fprintf(stderr, "Could not create SDL framebuffer surface\n"); + exit(1); + } + } +} + +static void sdl_update(FBDevice *fb_dev, void *opaque, + int x, int y, int w, int h) +{ + SDL_Rect r; + // printf("sdl_update: %d %d %d %d\n", x, y, w, h); + r.x = x; + r.y = y; + r.w = w; + r.h = h; + SDL_BlitSurface(fb_surface, &r, screen, &r); + SDL_UpdateRect(screen, r.x, r.y, r.w, r.h); +} + +#if defined(_WIN32) + +static int sdl_get_keycode(const SDL_KeyboardEvent *ev) +{ + return ev->keysym.scancode; +} + +#else + +/* we assume Xorg is used with a PC keyboard. Return 0 if no keycode found. */ +static int sdl_get_keycode(const SDL_KeyboardEvent *ev) +{ + int keycode; + keycode = ev->keysym.scancode; + if (keycode < 9) { + keycode = 0; + } else if (keycode < 127 + 8) { + keycode -= 8; + } else { + keycode = 0; + } + return keycode; +} + +#endif + +/* release all pressed keys */ +static void sdl_reset_keys(VirtMachine *m) +{ + int i; + + for(i = 1; i <= KEYCODE_MAX; i++) { + if (key_pressed[i]) { + vm_send_key_event(m, FALSE, i); + key_pressed[i] = FALSE; + } + } +} + +static void sdl_handle_key_event(const SDL_KeyboardEvent *ev, VirtMachine *m) +{ + int keycode, keypress; + + keycode = sdl_get_keycode(ev); + if (keycode) { + if (keycode == 0x3a || keycode ==0x45) { + /* SDL does not generate key up for numlock & caps lock */ + vm_send_key_event(m, TRUE, keycode); + vm_send_key_event(m, FALSE, keycode); + } else { + keypress = (ev->type == SDL_KEYDOWN); + if (keycode <= KEYCODE_MAX) + key_pressed[keycode] = keypress; + vm_send_key_event(m, keypress, keycode); + } + } else if (ev->type == SDL_KEYUP) { + /* workaround to reset the keyboard state (used when changing + desktop with ctrl-alt-x on Linux) */ + sdl_reset_keys(m); + } +} + +static void sdl_send_mouse_event(VirtMachine *m, int x1, int y1, + int dz, int state, BOOL is_absolute) +{ + int buttons, x, y; + + buttons = 0; + if (state & SDL_BUTTON(SDL_BUTTON_LEFT)) + buttons |= (1 << 0); + if (state & SDL_BUTTON(SDL_BUTTON_RIGHT)) + buttons |= (1 << 1); + if (state & SDL_BUTTON(SDL_BUTTON_MIDDLE)) + buttons |= (1 << 2); + if (is_absolute) { + x = (x1 * 32768) / screen_width; + y = (y1 * 32768) / screen_height; + } else { + x = x1; + y = y1; + } + vm_send_mouse_event(m, x, y, dz, buttons); +} + +static void sdl_handle_mouse_motion_event(const SDL_Event *ev, VirtMachine *m) +{ + BOOL is_absolute = vm_mouse_is_absolute(m); + int x, y; + if (is_absolute) { + x = ev->motion.x; + y = ev->motion.y; + } else { + x = ev->motion.xrel; + y = ev->motion.yrel; + } + sdl_send_mouse_event(m, x, y, 0, ev->motion.state, is_absolute); +} + +static void sdl_handle_mouse_button_event(const SDL_Event *ev, VirtMachine *m) +{ + BOOL is_absolute = vm_mouse_is_absolute(m); + int state, dz; + + dz = 0; + if (ev->type == SDL_MOUSEBUTTONDOWN) { + if (ev->button.button == SDL_BUTTON_WHEELUP) { + dz = 1; + } else if (ev->button.button == SDL_BUTTON_WHEELDOWN) { + dz = -1; + } + } + + state = SDL_GetMouseState(NULL, NULL); + /* just in case */ + if (ev->type == SDL_MOUSEBUTTONDOWN) + state |= SDL_BUTTON(ev->button.button); + else + state &= ~SDL_BUTTON(ev->button.button); + + if (is_absolute) { + sdl_send_mouse_event(m, ev->button.x, ev->button.y, + dz, state, is_absolute); + } else { + sdl_send_mouse_event(m, 0, 0, dz, state, is_absolute); + } +} + +void sdl_refresh(VirtMachine *m) +{ + SDL_Event ev_s, *ev = &ev_s; + + if (!m->fb_dev) + return; + + sdl_update_fb_surface(m->fb_dev); + + m->fb_dev->refresh(m->fb_dev, sdl_update, NULL); + + while (SDL_PollEvent(ev)) { + switch (ev->type) { + case SDL_KEYDOWN: + case SDL_KEYUP: + sdl_handle_key_event(&ev->key, m); + break; + case SDL_MOUSEMOTION: + sdl_handle_mouse_motion_event(ev, m); + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + sdl_handle_mouse_button_event(ev, m); + break; + case SDL_QUIT: + exit(0); + } + } +} + +static void sdl_hide_cursor(void) +{ + uint8_t data = 0; + sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0); + SDL_ShowCursor(1); + SDL_SetCursor(sdl_cursor_hidden); +} + +void sdl_init(int width, int height) +{ + int flags; + + screen_width = width; + screen_height = height; + + if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE)) { + fprintf(stderr, "Could not initialize SDL - exiting\n"); + exit(1); + } + + flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL; + screen = SDL_SetVideoMode(width, height, 0, flags); + if (!screen || !screen->pixels) { + fprintf(stderr, "Could not open SDL display\n"); + exit(1); + } + + SDL_WM_SetCaption("TinyEMU", "TinyEMU"); + + sdl_hide_cursor(); +} + diff --git a/sha256.c b/sha256.c new file mode 100644 index 0000000..97bfbda --- /dev/null +++ b/sha256.c @@ -0,0 +1,341 @@ +/* LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tomstdenis@gmail.com, http://libtom.org + */ +#include +#include +#include "cutils.h" +#include "sha256.h" + +#define LOAD32H(a, b) a = get_be32(b) +#define STORE32H(a, b) put_be32(b, a) +#define STORE64H(a, b) put_be64(b, a) +#define RORc(x, y) ( ((((uint32_t)(x)&0xFFFFFFFFUL)>>(uint32_t)((y)&31)) | ((uint32_t)(x)<<(uint32_t)(32-((y)&31)))) & 0xFFFFFFFFUL) + +#if defined(CONFIG_EMBUE) +#define LTC_SMALL_CODE +#endif + +/** + @file sha256.c + LTC_SHA256 by Tom St Denis +*/ + +#ifdef LTC_SMALL_CODE +/* the K array */ +static const uint32_t K[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, + 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL, + 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, + 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL, + 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, + 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, + 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, + 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL, + 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, + 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL +}; +#endif + +/* Various logical functions */ +#define Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Maj(x,y,z) (((x | y) & z) | (x & y)) +#define S(x, n) RORc((x),(n)) +#define R(x, n) (((x)&0xFFFFFFFFUL)>>(n)) +#define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22)) +#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25)) +#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3)) +#define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10)) + +/* compress 512-bits */ +static void sha256_compress(SHA256_CTX *s, unsigned char *buf) +{ + uint32_t S[8], W[64], t0, t1; +#ifdef LTC_SMALL_CODE + uint32_t t; +#endif + int i; + + /* copy state into S */ + for (i = 0; i < 8; i++) { + S[i] = s->state[i]; + } + + /* copy the state into 512-bits into W[0..15] */ + for (i = 0; i < 16; i++) { + LOAD32H(W[i], buf + (4*i)); + } + + /* fill W[16..63] */ + for (i = 16; i < 64; i++) { + W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; + } + + /* Compress */ +#ifdef LTC_SMALL_CODE +#define RND(a,b,c,d,e,f,g,h,i) \ + t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ + t1 = Sigma0(a) + Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; + + for (i = 0; i < 64; ++i) { + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i); + t = S[7]; S[7] = S[6]; S[6] = S[5]; S[5] = S[4]; + S[4] = S[3]; S[3] = S[2]; S[2] = S[1]; S[1] = S[0]; S[0] = t; + } +#else +#define RND(a,b,c,d,e,f,g,h,i,ki) \ + t0 = h + Sigma1(e) + Ch(e, f, g) + ki + W[i]; \ + t1 = Sigma0(a) + Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; + + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],0,0x428a2f98); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],1,0x71374491); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],2,0xb5c0fbcf); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],3,0xe9b5dba5); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],4,0x3956c25b); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],5,0x59f111f1); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],6,0x923f82a4); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],7,0xab1c5ed5); + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],8,0xd807aa98); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],9,0x12835b01); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],10,0x243185be); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],11,0x550c7dc3); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],12,0x72be5d74); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],13,0x80deb1fe); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],14,0x9bdc06a7); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],15,0xc19bf174); + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],16,0xe49b69c1); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],17,0xefbe4786); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],18,0x0fc19dc6); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],19,0x240ca1cc); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],20,0x2de92c6f); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],21,0x4a7484aa); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],22,0x5cb0a9dc); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],23,0x76f988da); + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],24,0x983e5152); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],25,0xa831c66d); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],26,0xb00327c8); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],27,0xbf597fc7); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],28,0xc6e00bf3); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],29,0xd5a79147); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],30,0x06ca6351); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],31,0x14292967); + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],32,0x27b70a85); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],33,0x2e1b2138); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],34,0x4d2c6dfc); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],35,0x53380d13); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],36,0x650a7354); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],37,0x766a0abb); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],38,0x81c2c92e); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],39,0x92722c85); + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],40,0xa2bfe8a1); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],41,0xa81a664b); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],42,0xc24b8b70); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],43,0xc76c51a3); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],44,0xd192e819); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],45,0xd6990624); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],46,0xf40e3585); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],47,0x106aa070); + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],48,0x19a4c116); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],49,0x1e376c08); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],50,0x2748774c); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],51,0x34b0bcb5); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],52,0x391c0cb3); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],53,0x4ed8aa4a); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],54,0x5b9cca4f); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],55,0x682e6ff3); + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],56,0x748f82ee); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],57,0x78a5636f); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],58,0x84c87814); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],59,0x8cc70208); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],60,0x90befffa); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],61,0xa4506ceb); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],62,0xbef9a3f7); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],63,0xc67178f2); + +#undef RND + +#endif + + /* feedback */ + for (i = 0; i < 8; i++) { + s->state[i] = s->state[i] + S[i]; + } +} + +#ifdef LTC_CLEAN_STACK +static int sha256_compress(hash_state * md, unsigned char *buf) +{ + int err; + err = _sha256_compress(md, buf); + burn_stack(sizeof(uint32_t) * 74); + return err; +} +#endif + +/** + Initialize the hash state + @param md The hash state you wish to initialize + @return CRYPT_OK if successful +*/ +void SHA256_Init(SHA256_CTX *s) +{ + s->curlen = 0; + s->length = 0; + s->state[0] = 0x6A09E667UL; + s->state[1] = 0xBB67AE85UL; + s->state[2] = 0x3C6EF372UL; + s->state[3] = 0xA54FF53AUL; + s->state[4] = 0x510E527FUL; + s->state[5] = 0x9B05688CUL; + s->state[6] = 0x1F83D9ABUL; + s->state[7] = 0x5BE0CD19UL; +} + +void SHA256_Update(SHA256_CTX *s, const uint8_t *in, unsigned long inlen) +{ + unsigned long n; + + if (s->curlen > sizeof(s->buf)) { + abort(); + } + if ((s->length + inlen) < s->length) { + abort(); + } + while (inlen > 0) { + if (s->curlen == 0 && inlen >= 64) { + sha256_compress(s, (unsigned char *)in); + s->length += 64 * 8; + in += 64; + inlen -= 64; + } else { + n = min_int(inlen, 64 - s->curlen); + memcpy(s->buf + s->curlen, in, (size_t)n); + s->curlen += n; + in += n; + inlen -= n; + if (s->curlen == 64) { + sha256_compress(s, s->buf); + s->length += 8*64; + s->curlen = 0; + } + } + } } + +/** + Terminate the hash to get the digest + @param md The hash state + @param out [out] The destination of the hash (32 bytes) + @return CRYPT_OK if successful +*/ +void SHA256_Final(uint8_t *out, SHA256_CTX *s) +{ + int i; + + if (s->curlen >= sizeof(s->buf)) { + abort(); + } + + + /* increase the length of the message */ + s->length += s->curlen * 8; + + /* append the '1' bit */ + s->buf[s->curlen++] = (unsigned char)0x80; + + /* if the length is currently above 56 bytes we append zeros + * then compress. Then we can fall back to padding zeros and length + * encoding like normal. + */ + if (s->curlen > 56) { + while (s->curlen < 64) { + s->buf[s->curlen++] = (unsigned char)0; + } + sha256_compress(s, s->buf); + s->curlen = 0; + } + + /* pad upto 56 bytes of zeroes */ + while (s->curlen < 56) { + s->buf[s->curlen++] = (unsigned char)0; + } + + /* store length */ + STORE64H(s->length, s->buf+56); + sha256_compress(s, s->buf); + + /* copy output */ + for (i = 0; i < 8; i++) { + STORE32H(s->state[i], out+(4*i)); + } +#ifdef LTC_CLEAN_STACK + zeromem(md, sizeof(hash_state)); +#endif +} + +void SHA256(const uint8_t *buf, int buf_len, uint8_t *out) +{ + SHA256_CTX ctx; + + SHA256_Init(&ctx); + SHA256_Update(&ctx, buf, buf_len); + SHA256_Final(out, &ctx); +} + +#if 0 +/** + Self-test the hash + @return CRYPT_OK if successful, CRYPT_NOP if self-tests have been disabled +*/ +int sha256_test(void) +{ + #ifndef LTC_TEST + return CRYPT_NOP; + #else + static const struct { + char *msg; + unsigned char hash[32]; + } tests[] = { + { "abc", + { 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad } + }, + { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + { 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, + 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39, + 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, + 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1 } + }, + }; + + int i; + unsigned char tmp[32]; + hash_state md; + + for (i = 0; i < (int)(sizeof(tests) / sizeof(tests[0])); i++) { + sha256_init(&md); + sha256_process(&md, (unsigned char*)tests[i].msg, (unsigned long)strlen(tests[i].msg)); + sha256_done(&md, tmp); + if (XMEMCMP(tmp, tests[i].hash, 32) != 0) { + return CRYPT_FAIL_TESTVECTOR; + } + } + return CRYPT_OK; + #endif +} + +#endif diff --git a/sha256.h b/sha256.h new file mode 100644 index 0000000..b43115f --- /dev/null +++ b/sha256.h @@ -0,0 +1,40 @@ +/* + * OpenSSL compatible SHA256 header + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef SHA256_H +#define SHA256_H + +#define SHA256_DIGEST_LENGTH 32 + +typedef struct { + uint64_t length; + uint32_t state[8], curlen; + uint8_t buf[64]; +} SHA256_CTX; + +void SHA256_Init(SHA256_CTX *s); +void SHA256_Update(SHA256_CTX *s, const uint8_t *in, unsigned long inlen); +void SHA256_Final(uint8_t *out, SHA256_CTX *s); +void SHA256(const uint8_t *buf, int buf_len, uint8_t *out); + +#endif /* SHA256_H */ diff --git a/simplefb.c b/simplefb.c new file mode 100644 index 0000000..5664cea --- /dev/null +++ b/simplefb.c @@ -0,0 +1,127 @@ +/* + * Simple frame buffer + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "iomem.h" +#include "virtio.h" +#include "machine.h" + +//#define DEBUG_VBE + +#define FB_ALLOC_ALIGN 65536 + +struct SimpleFBState { + FBDevice *fb_dev; + int fb_page_count; + PhysMemoryRange *mem_range; +}; + +#define MAX_MERGE_DISTANCE 3 + +void simplefb_refresh(FBDevice *fb_dev, + SimpleFBDrawFunc *redraw_func, void *opaque, + PhysMemoryRange *mem_range, + int fb_page_count) +{ + const uint32_t *dirty_bits; + uint32_t dirty_val; + int y0, y1, page_y0, page_y1, byte_pos, page_index, bit_pos; + + dirty_bits = phys_mem_get_dirty_bits(mem_range); + + page_index = 0; + y0 = y1 = 0; + while (page_index < fb_page_count) { + dirty_val = dirty_bits[page_index >> 5]; + if (dirty_val != 0) { + bit_pos = 0; + while (dirty_val != 0) { + while (((dirty_val >> bit_pos) & 1) == 0) + bit_pos++; + dirty_val &= ~(1 << bit_pos); + + byte_pos = (page_index + bit_pos) * DEVRAM_PAGE_SIZE; + page_y0 = byte_pos / fb_dev->stride; + page_y1 = ((byte_pos + DEVRAM_PAGE_SIZE - 1) / fb_dev->stride) + 1; + page_y1 = min_int(page_y1, fb_dev->height); + if (y0 == y1) { + y0 = page_y0; + y1 = page_y1; + } else if (page_y0 <= (y1 + MAX_MERGE_DISTANCE)) { + /* union with current region */ + y1 = page_y1; + } else { + /* flush */ + redraw_func(fb_dev, opaque, + 0, y0, fb_dev->width, y1 - y0); + y0 = page_y0; + y1 = page_y1; + } + } + } + page_index += 32; + } + + if (y0 != y1) { + redraw_func(fb_dev, opaque, + 0, y0, fb_dev->width, y1 - y0); + } +} + +static void simplefb_refresh1(FBDevice *fb_dev, + SimpleFBDrawFunc *redraw_func, void *opaque) +{ + SimpleFBState *s = fb_dev->device_opaque; + simplefb_refresh(fb_dev, redraw_func, opaque, s->mem_range, + s->fb_page_count); +} + +SimpleFBState *simplefb_init(PhysMemoryMap *map, uint64_t phys_addr, + FBDevice *fb_dev, int width, int height) +{ + SimpleFBState *s; + + s = mallocz(sizeof(*s)); + s->fb_dev = fb_dev; + + fb_dev->width = width; + fb_dev->height = height; + fb_dev->stride = width * 4; + fb_dev->fb_size = (height * fb_dev->stride + FB_ALLOC_ALIGN - 1) & ~(FB_ALLOC_ALIGN - 1); + s->fb_page_count = fb_dev->fb_size >> DEVRAM_PAGE_SIZE_LOG2; + + s->mem_range = cpu_register_ram(map, phys_addr, fb_dev->fb_size, + DEVRAM_FLAG_DIRTY_BITS); + + fb_dev->fb_data = s->mem_range->phys_mem; + fb_dev->device_opaque = s; + fb_dev->refresh = simplefb_refresh1; + return s; +} diff --git a/slirp/bootp.c b/slirp/bootp.c new file mode 100644 index 0000000..3e5f457 --- /dev/null +++ b/slirp/bootp.c @@ -0,0 +1,314 @@ +/* + * QEMU BOOTP/DHCP server + * + * Copyright (c) 2004 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "slirp.h" + +/* XXX: only DHCP is supported */ + +#define LEASE_TIME (24 * 3600) + +static const uint8_t rfc1533_cookie[] = { RFC1533_COOKIE }; + +#ifdef DEBUG +#define DPRINTF(fmt, ...) \ +do if (slirp_debug & DBG_CALL) { fprintf(dfd, fmt, ## __VA_ARGS__); fflush(dfd); } while (0) +#else +#define DPRINTF(fmt, ...) do{}while(0) +#endif + +static BOOTPClient *get_new_addr(Slirp *slirp, struct in_addr *paddr, + const uint8_t *macaddr) +{ + BOOTPClient *bc; + int i; + + for(i = 0; i < NB_BOOTP_CLIENTS; i++) { + bc = &slirp->bootp_clients[i]; + if (!bc->allocated || !memcmp(macaddr, bc->macaddr, 6)) + goto found; + } + return NULL; + found: + bc = &slirp->bootp_clients[i]; + bc->allocated = 1; + paddr->s_addr = slirp->vdhcp_startaddr.s_addr + htonl(i); + return bc; +} + +static BOOTPClient *request_addr(Slirp *slirp, const struct in_addr *paddr, + const uint8_t *macaddr) +{ + uint32_t req_addr = ntohl(paddr->s_addr); + uint32_t dhcp_addr = ntohl(slirp->vdhcp_startaddr.s_addr); + BOOTPClient *bc; + + if (req_addr >= dhcp_addr && + req_addr < (dhcp_addr + NB_BOOTP_CLIENTS)) { + bc = &slirp->bootp_clients[req_addr - dhcp_addr]; + if (!bc->allocated || !memcmp(macaddr, bc->macaddr, 6)) { + bc->allocated = 1; + return bc; + } + } + return NULL; +} + +static BOOTPClient *find_addr(Slirp *slirp, struct in_addr *paddr, + const uint8_t *macaddr) +{ + BOOTPClient *bc; + int i; + + for(i = 0; i < NB_BOOTP_CLIENTS; i++) { + if (!memcmp(macaddr, slirp->bootp_clients[i].macaddr, 6)) + goto found; + } + return NULL; + found: + bc = &slirp->bootp_clients[i]; + bc->allocated = 1; + paddr->s_addr = slirp->vdhcp_startaddr.s_addr + htonl(i); + return bc; +} + +static void dhcp_decode(const struct bootp_t *bp, int *pmsg_type, + struct in_addr *preq_addr) +{ + const uint8_t *p, *p_end; + int len, tag; + + *pmsg_type = 0; + preq_addr->s_addr = htonl(0L); + + p = bp->bp_vend; + p_end = p + DHCP_OPT_LEN; + if (memcmp(p, rfc1533_cookie, 4) != 0) + return; + p += 4; + while (p < p_end) { + tag = p[0]; + if (tag == RFC1533_PAD) { + p++; + } else if (tag == RFC1533_END) { + break; + } else { + p++; + if (p >= p_end) + break; + len = *p++; + DPRINTF("dhcp: tag=%d len=%d\n", tag, len); + + switch(tag) { + case RFC2132_MSG_TYPE: + if (len >= 1) + *pmsg_type = p[0]; + break; + case RFC2132_REQ_ADDR: + if (len >= 4) { + memcpy(&(preq_addr->s_addr), p, 4); + } + break; + default: + break; + } + p += len; + } + } + if (*pmsg_type == DHCPREQUEST && preq_addr->s_addr == htonl(0L) && + bp->bp_ciaddr.s_addr) { + memcpy(&(preq_addr->s_addr), &bp->bp_ciaddr, 4); + } +} + +static void bootp_reply(Slirp *slirp, const struct bootp_t *bp) +{ + BOOTPClient *bc = NULL; + struct mbuf *m; + struct bootp_t *rbp; + struct sockaddr_in saddr, daddr; + struct in_addr preq_addr; + int dhcp_msg_type, val; + uint8_t *q; + + /* extract exact DHCP msg type */ + dhcp_decode(bp, &dhcp_msg_type, &preq_addr); + DPRINTF("bootp packet op=%d msgtype=%d", bp->bp_op, dhcp_msg_type); + if (preq_addr.s_addr != htonl(0L)) + DPRINTF(" req_addr=%08x\n", ntohl(preq_addr.s_addr)); + else + DPRINTF("\n"); + + if (dhcp_msg_type == 0) + dhcp_msg_type = DHCPREQUEST; /* Force reply for old BOOTP clients */ + + if (dhcp_msg_type != DHCPDISCOVER && + dhcp_msg_type != DHCPREQUEST) + return; + /* XXX: this is a hack to get the client mac address */ + memcpy(slirp->client_ethaddr, bp->bp_hwaddr, 6); + + m = m_get(slirp); + if (!m) { + return; + } + m->m_data += IF_MAXLINKHDR; + rbp = (struct bootp_t *)m->m_data; + m->m_data += sizeof(struct udpiphdr); + memset(rbp, 0, sizeof(struct bootp_t)); + + if (dhcp_msg_type == DHCPDISCOVER) { + if (preq_addr.s_addr != htonl(0L)) { + bc = request_addr(slirp, &preq_addr, slirp->client_ethaddr); + if (bc) { + daddr.sin_addr = preq_addr; + } + } + if (!bc) { + new_addr: + bc = get_new_addr(slirp, &daddr.sin_addr, slirp->client_ethaddr); + if (!bc) { + DPRINTF("no address left\n"); + return; + } + } + memcpy(bc->macaddr, slirp->client_ethaddr, 6); + } else if (preq_addr.s_addr != htonl(0L)) { + bc = request_addr(slirp, &preq_addr, slirp->client_ethaddr); + if (bc) { + daddr.sin_addr = preq_addr; + memcpy(bc->macaddr, slirp->client_ethaddr, 6); + } else { + daddr.sin_addr.s_addr = 0; + } + } else { + bc = find_addr(slirp, &daddr.sin_addr, bp->bp_hwaddr); + if (!bc) { + /* if never assigned, behaves as if it was already + assigned (windows fix because it remembers its address) */ + goto new_addr; + } + } + + saddr.sin_addr = slirp->vhost_addr; + saddr.sin_port = htons(BOOTP_SERVER); + + daddr.sin_port = htons(BOOTP_CLIENT); + + rbp->bp_op = BOOTP_REPLY; + rbp->bp_xid = bp->bp_xid; + rbp->bp_htype = 1; + rbp->bp_hlen = 6; + memcpy(rbp->bp_hwaddr, bp->bp_hwaddr, 6); + + rbp->bp_yiaddr = daddr.sin_addr; /* Client IP address */ + rbp->bp_siaddr = saddr.sin_addr; /* Server IP address */ + + q = rbp->bp_vend; + memcpy(q, rfc1533_cookie, 4); + q += 4; + + if (bc) { + DPRINTF("%s addr=%08x\n", + (dhcp_msg_type == DHCPDISCOVER) ? "offered" : "ack'ed", + ntohl(daddr.sin_addr.s_addr)); + + if (dhcp_msg_type == DHCPDISCOVER) { + *q++ = RFC2132_MSG_TYPE; + *q++ = 1; + *q++ = DHCPOFFER; + } else /* DHCPREQUEST */ { + *q++ = RFC2132_MSG_TYPE; + *q++ = 1; + *q++ = DHCPACK; + } + + if (slirp->bootp_filename) + snprintf((char *)rbp->bp_file, sizeof(rbp->bp_file), "%s", + slirp->bootp_filename); + + *q++ = RFC2132_SRV_ID; + *q++ = 4; + memcpy(q, &saddr.sin_addr, 4); + q += 4; + + *q++ = RFC1533_NETMASK; + *q++ = 4; + memcpy(q, &slirp->vnetwork_mask, 4); + q += 4; + + if (!slirp->restricted) { + *q++ = RFC1533_GATEWAY; + *q++ = 4; + memcpy(q, &saddr.sin_addr, 4); + q += 4; + + *q++ = RFC1533_DNS; + *q++ = 4; + memcpy(q, &slirp->vnameserver_addr, 4); + q += 4; + } + + *q++ = RFC2132_LEASE_TIME; + *q++ = 4; + val = htonl(LEASE_TIME); + memcpy(q, &val, 4); + q += 4; + + if (*slirp->client_hostname) { + val = strlen(slirp->client_hostname); + *q++ = RFC1533_HOSTNAME; + *q++ = val; + memcpy(q, slirp->client_hostname, val); + q += val; + } + } else { + static const char nak_msg[] = "requested address not available"; + + DPRINTF("nak'ed addr=%08x\n", ntohl(preq_addr->s_addr)); + + *q++ = RFC2132_MSG_TYPE; + *q++ = 1; + *q++ = DHCPNAK; + + *q++ = RFC2132_MESSAGE; + *q++ = sizeof(nak_msg) - 1; + memcpy(q, nak_msg, sizeof(nak_msg) - 1); + q += sizeof(nak_msg) - 1; + } + *q = RFC1533_END; + + daddr.sin_addr.s_addr = 0xffffffffu; + + m->m_len = sizeof(struct bootp_t) - + sizeof(struct ip) - sizeof(struct udphdr); + udp_output2(NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); +} + +void bootp_input(struct mbuf *m) +{ + struct bootp_t *bp = mtod(m, struct bootp_t *); + + if (bp->bp_op == BOOTP_REQUEST) { + bootp_reply(m->slirp, bp); + } +} diff --git a/slirp/bootp.h b/slirp/bootp.h new file mode 100644 index 0000000..30c30ab --- /dev/null +++ b/slirp/bootp.h @@ -0,0 +1,122 @@ +/* bootp/dhcp defines */ + +#define BOOTP_SERVER 67 +#define BOOTP_CLIENT 68 + +#define BOOTP_REQUEST 1 +#define BOOTP_REPLY 2 + +#define RFC1533_COOKIE 99, 130, 83, 99 +#define RFC1533_PAD 0 +#define RFC1533_NETMASK 1 +#define RFC1533_TIMEOFFSET 2 +#define RFC1533_GATEWAY 3 +#define RFC1533_TIMESERVER 4 +#define RFC1533_IEN116NS 5 +#define RFC1533_DNS 6 +#define RFC1533_LOGSERVER 7 +#define RFC1533_COOKIESERVER 8 +#define RFC1533_LPRSERVER 9 +#define RFC1533_IMPRESSSERVER 10 +#define RFC1533_RESOURCESERVER 11 +#define RFC1533_HOSTNAME 12 +#define RFC1533_BOOTFILESIZE 13 +#define RFC1533_MERITDUMPFILE 14 +#define RFC1533_DOMAINNAME 15 +#define RFC1533_SWAPSERVER 16 +#define RFC1533_ROOTPATH 17 +#define RFC1533_EXTENSIONPATH 18 +#define RFC1533_IPFORWARDING 19 +#define RFC1533_IPSOURCEROUTING 20 +#define RFC1533_IPPOLICYFILTER 21 +#define RFC1533_IPMAXREASSEMBLY 22 +#define RFC1533_IPTTL 23 +#define RFC1533_IPMTU 24 +#define RFC1533_IPMTUPLATEAU 25 +#define RFC1533_INTMTU 26 +#define RFC1533_INTLOCALSUBNETS 27 +#define RFC1533_INTBROADCAST 28 +#define RFC1533_INTICMPDISCOVER 29 +#define RFC1533_INTICMPRESPOND 30 +#define RFC1533_INTROUTEDISCOVER 31 +#define RFC1533_INTROUTESOLICIT 32 +#define RFC1533_INTSTATICROUTES 33 +#define RFC1533_LLTRAILERENCAP 34 +#define RFC1533_LLARPCACHETMO 35 +#define RFC1533_LLETHERNETENCAP 36 +#define RFC1533_TCPTTL 37 +#define RFC1533_TCPKEEPALIVETMO 38 +#define RFC1533_TCPKEEPALIVEGB 39 +#define RFC1533_NISDOMAIN 40 +#define RFC1533_NISSERVER 41 +#define RFC1533_NTPSERVER 42 +#define RFC1533_VENDOR 43 +#define RFC1533_NBNS 44 +#define RFC1533_NBDD 45 +#define RFC1533_NBNT 46 +#define RFC1533_NBSCOPE 47 +#define RFC1533_XFS 48 +#define RFC1533_XDM 49 + +#define RFC2132_REQ_ADDR 50 +#define RFC2132_LEASE_TIME 51 +#define RFC2132_MSG_TYPE 53 +#define RFC2132_SRV_ID 54 +#define RFC2132_PARAM_LIST 55 +#define RFC2132_MESSAGE 56 +#define RFC2132_MAX_SIZE 57 +#define RFC2132_RENEWAL_TIME 58 +#define RFC2132_REBIND_TIME 59 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPACK 5 +#define DHCPNAK 6 + +#define RFC1533_VENDOR_MAJOR 0 +#define RFC1533_VENDOR_MINOR 0 + +#define RFC1533_VENDOR_MAGIC 128 +#define RFC1533_VENDOR_ADDPARM 129 +#define RFC1533_VENDOR_ETHDEV 130 +#define RFC1533_VENDOR_HOWTO 132 +#define RFC1533_VENDOR_MNUOPTS 160 +#define RFC1533_VENDOR_SELECTION 176 +#define RFC1533_VENDOR_MOTD 184 +#define RFC1533_VENDOR_NUMOFMOTD 8 +#define RFC1533_VENDOR_IMG 192 +#define RFC1533_VENDOR_NUMOFIMG 16 + +#define RFC1533_END 255 +#define BOOTP_VENDOR_LEN 64 +#define DHCP_OPT_LEN 312 + +struct bootp_t { + struct ip ip; + struct udphdr udp; + uint8_t bp_op; + uint8_t bp_htype; + uint8_t bp_hlen; + uint8_t bp_hops; + uint32_t bp_xid; + uint16_t bp_secs; + uint16_t unused; + struct in_addr bp_ciaddr; + struct in_addr bp_yiaddr; + struct in_addr bp_siaddr; + struct in_addr bp_giaddr; + uint8_t bp_hwaddr[16]; + uint8_t bp_sname[64]; + uint8_t bp_file[128]; + uint8_t bp_vend[DHCP_OPT_LEN]; +}; + +typedef struct { + uint16_t allocated; + uint8_t macaddr[6]; +} BOOTPClient; + +#define NB_BOOTP_CLIENTS 16 + +void bootp_input(struct mbuf *m); diff --git a/slirp/cksum.c b/slirp/cksum.c new file mode 100644 index 0000000..5aa9060 --- /dev/null +++ b/slirp/cksum.c @@ -0,0 +1,139 @@ +/* + * Copyright (c) 1988, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)in_cksum.c 8.1 (Berkeley) 6/10/93 + * in_cksum.c,v 1.2 1994/08/02 07:48:16 davidg Exp + */ + +#include "slirp.h" + +/* + * Checksum routine for Internet Protocol family headers (Portable Version). + * + * This routine is very heavily used in the network + * code and should be modified for each CPU to be as fast as possible. + * + * XXX Since we will never span more than 1 mbuf, we can optimise this + */ + +#define ADDCARRY(x) (x > 65535 ? x -= 65535 : x) +#define REDUCE {l_util.l = sum; sum = l_util.s[0] + l_util.s[1]; \ + (void)ADDCARRY(sum);} + +int cksum(struct mbuf *m, int len) +{ + register uint16_t *w; + register int sum = 0; + register int mlen = 0; + int byte_swapped = 0; + + union { + uint8_t c[2]; + uint16_t s; + } s_util; + union { + uint16_t s[2]; + uint32_t l; + } l_util; + + if (m->m_len == 0) + goto cont; + w = mtod(m, uint16_t *); + + mlen = m->m_len; + + if (len < mlen) + mlen = len; +#ifdef DEBUG + len -= mlen; +#endif + /* + * Force to even boundary. + */ + if ((1 & (long) w) && (mlen > 0)) { + REDUCE; + sum <<= 8; + s_util.c[0] = *(uint8_t *)w; + w = (uint16_t *)((int8_t *)w + 1); + mlen--; + byte_swapped = 1; + } + /* + * Unroll the loop to make overhead from + * branches &c small. + */ + while ((mlen -= 32) >= 0) { + sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3]; + sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7]; + sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11]; + sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15]; + w += 16; + } + mlen += 32; + while ((mlen -= 8) >= 0) { + sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3]; + w += 4; + } + mlen += 8; + if (mlen == 0 && byte_swapped == 0) + goto cont; + REDUCE; + while ((mlen -= 2) >= 0) { + sum += *w++; + } + + if (byte_swapped) { + REDUCE; + sum <<= 8; + if (mlen == -1) { + s_util.c[1] = *(uint8_t *)w; + sum += s_util.s; + mlen = 0; + } else + + mlen = -1; + } else if (mlen == -1) + s_util.c[0] = *(uint8_t *)w; + +cont: +#ifdef DEBUG + if (len) { + DEBUG_ERROR((dfd, "cksum: out of data\n")); + DEBUG_ERROR((dfd, " len = %d\n", len)); + } +#endif + if (mlen == -1) { + /* The last mbuf has odd # of bytes. Follow the + standard (the odd byte may be shifted left by 8 bits + or not as determined by endian-ness of the machine) */ + s_util.c[1] = 0; + sum += s_util.s; + } + REDUCE; + return (~sum & 0xffff); +} diff --git a/slirp/debug.h b/slirp/debug.h new file mode 100644 index 0000000..6cfa61e --- /dev/null +++ b/slirp/debug.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +//#define DEBUG 1 + +#ifdef DEBUG + +#define DBG_CALL 0x1 +#define DBG_MISC 0x2 +#define DBG_ERROR 0x4 + +#define dfd stderr + +extern int slirp_debug; + +#define DEBUG_CALL(x) if (slirp_debug & DBG_CALL) { fprintf(dfd, "%s...\n", x); fflush(dfd); } +#define DEBUG_ARG(x, y) if (slirp_debug & DBG_CALL) { fputc(' ', dfd); fprintf(dfd, x, y); fputc('\n', dfd); fflush(dfd); } +#define DEBUG_ARGS(x) if (slirp_debug & DBG_CALL) { fprintf x ; fflush(dfd); } +#define DEBUG_MISC(x) if (slirp_debug & DBG_MISC) { fprintf x ; fflush(dfd); } +#define DEBUG_ERROR(x) if (slirp_debug & DBG_ERROR) {fprintf x ; fflush(dfd); } + +#else + +#define DEBUG_CALL(x) +#define DEBUG_ARG(x, y) +#define DEBUG_ARGS(x) +#define DEBUG_MISC(x) +#define DEBUG_ERROR(x) + +#endif diff --git a/slirp/if.c b/slirp/if.c new file mode 100644 index 0000000..e6d114a --- /dev/null +++ b/slirp/if.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" + +#define ifs_init(ifm) ((ifm)->ifs_next = (ifm)->ifs_prev = (ifm)) + +static void +ifs_insque(struct mbuf *ifm, struct mbuf *ifmhead) +{ + ifm->ifs_next = ifmhead->ifs_next; + ifmhead->ifs_next = ifm; + ifm->ifs_prev = ifmhead; + ifm->ifs_next->ifs_prev = ifm; +} + +static void +ifs_remque(struct mbuf *ifm) +{ + ifm->ifs_prev->ifs_next = ifm->ifs_next; + ifm->ifs_next->ifs_prev = ifm->ifs_prev; +} + +void +if_init(Slirp *slirp) +{ + slirp->if_fastq.ifq_next = slirp->if_fastq.ifq_prev = &slirp->if_fastq; + slirp->if_batchq.ifq_next = slirp->if_batchq.ifq_prev = &slirp->if_batchq; + slirp->next_m = &slirp->if_batchq; +} + +/* + * if_output: Queue packet into an output queue. + * There are 2 output queue's, if_fastq and if_batchq. + * Each output queue is a doubly linked list of double linked lists + * of mbufs, each list belonging to one "session" (socket). This + * way, we can output packets fairly by sending one packet from each + * session, instead of all the packets from one session, then all packets + * from the next session, etc. Packets on the if_fastq get absolute + * priority, but if one session hogs the link, it gets "downgraded" + * to the batchq until it runs out of packets, then it'll return + * to the fastq (eg. if the user does an ls -alR in a telnet session, + * it'll temporarily get downgraded to the batchq) + */ +void +if_output(struct socket *so, struct mbuf *ifm) +{ + Slirp *slirp = ifm->slirp; + struct mbuf *ifq; + int on_fastq = 1; + + DEBUG_CALL("if_output"); + DEBUG_ARG("so = %lx", (long)so); + DEBUG_ARG("ifm = %lx", (long)ifm); + + /* + * First remove the mbuf from m_usedlist, + * since we're gonna use m_next and m_prev ourselves + * XXX Shouldn't need this, gotta change dtom() etc. + */ + if (ifm->m_flags & M_USEDLIST) { + remque(ifm); + ifm->m_flags &= ~M_USEDLIST; + } + + /* + * See if there's already a batchq list for this session. + * This can include an interactive session, which should go on fastq, + * but gets too greedy... hence it'll be downgraded from fastq to batchq. + * We mustn't put this packet back on the fastq (or we'll send it out of order) + * XXX add cache here? + */ + for (ifq = slirp->if_batchq.ifq_prev; ifq != &slirp->if_batchq; + ifq = ifq->ifq_prev) { + if (so == ifq->ifq_so) { + /* A match! */ + ifm->ifq_so = so; + ifs_insque(ifm, ifq->ifs_prev); + goto diddit; + } + } + + /* No match, check which queue to put it on */ + if (so && (so->so_iptos & IPTOS_LOWDELAY)) { + ifq = slirp->if_fastq.ifq_prev; + on_fastq = 1; + /* + * Check if this packet is a part of the last + * packet's session + */ + if (ifq->ifq_so == so) { + ifm->ifq_so = so; + ifs_insque(ifm, ifq->ifs_prev); + goto diddit; + } + } else + ifq = slirp->if_batchq.ifq_prev; + + /* Create a new doubly linked list for this session */ + ifm->ifq_so = so; + ifs_init(ifm); + insque(ifm, ifq); + +diddit: + slirp->if_queued++; + + if (so) { + /* Update *_queued */ + so->so_queued++; + so->so_nqueued++; + /* + * Check if the interactive session should be downgraded to + * the batchq. A session is downgraded if it has queued 6 + * packets without pausing, and at least 3 of those packets + * have been sent over the link + * (XXX These are arbitrary numbers, probably not optimal..) + */ + if (on_fastq && ((so->so_nqueued >= 6) && + (so->so_nqueued - so->so_queued) >= 3)) { + + /* Remove from current queue... */ + remque(ifm->ifs_next); + + /* ...And insert in the new. That'll teach ya! */ + insque(ifm->ifs_next, &slirp->if_batchq); + } + } + +#ifndef FULL_BOLT + /* + * This prevents us from malloc()ing too many mbufs + */ + if_start(ifm->slirp); +#endif +} + +/* + * Send a packet + * We choose a packet based on it's position in the output queues; + * If there are packets on the fastq, they are sent FIFO, before + * everything else. Otherwise we choose the first packet from the + * batchq and send it. the next packet chosen will be from the session + * after this one, then the session after that one, and so on.. So, + * for example, if there are 3 ftp session's fighting for bandwidth, + * one packet will be sent from the first session, then one packet + * from the second session, then one packet from the third, then back + * to the first, etc. etc. + */ +void +if_start(Slirp *slirp) +{ + struct mbuf *ifm, *ifqt; + + DEBUG_CALL("if_start"); + + if (slirp->if_queued == 0) + return; /* Nothing to do */ + + again: + /* check if we can really output */ + if (!slirp_can_output(slirp->opaque)) + return; + + /* + * See which queue to get next packet from + * If there's something in the fastq, select it immediately + */ + if (slirp->if_fastq.ifq_next != &slirp->if_fastq) { + ifm = slirp->if_fastq.ifq_next; + } else { + /* Nothing on fastq, see if next_m is valid */ + if (slirp->next_m != &slirp->if_batchq) + ifm = slirp->next_m; + else + ifm = slirp->if_batchq.ifq_next; + + /* Set which packet to send on next iteration */ + slirp->next_m = ifm->ifq_next; + } + /* Remove it from the queue */ + ifqt = ifm->ifq_prev; + remque(ifm); + slirp->if_queued--; + + /* If there are more packets for this session, re-queue them */ + if (ifm->ifs_next != /* ifm->ifs_prev != */ ifm) { + insque(ifm->ifs_next, ifqt); + ifs_remque(ifm); + } + + /* Update so_queued */ + if (ifm->ifq_so) { + if (--ifm->ifq_so->so_queued == 0) + /* If there's no more queued, reset nqueued */ + ifm->ifq_so->so_nqueued = 0; + } + + /* Encapsulate the packet for sending */ + if_encap(slirp, (uint8_t *)ifm->m_data, ifm->m_len); + + m_free(ifm); + + if (slirp->if_queued) + goto again; +} diff --git a/slirp/if.h b/slirp/if.h new file mode 100644 index 0000000..2dac1c7 --- /dev/null +++ b/slirp/if.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#ifndef _IF_H_ +#define _IF_H_ + +#define IF_COMPRESS 0x01 /* We want compression */ +#define IF_NOCOMPRESS 0x02 /* Do not do compression */ +#define IF_AUTOCOMP 0x04 /* Autodetect (default) */ +#define IF_NOCIDCOMP 0x08 /* CID compression */ + +#define IF_MTU 1500 +#define IF_MRU 1500 +#define IF_COMP IF_AUTOCOMP /* Flags for compression */ + +/* 2 for alignment, 14 for ethernet, 40 for TCP/IP */ +#define IF_MAXLINKHDR (2 + 14 + 40) + +#define ifs_init(ifm) ((ifm)->ifs_next = (ifm)->ifs_prev = (ifm)) + +#endif diff --git a/slirp/ip.h b/slirp/ip.h new file mode 100644 index 0000000..48ea38e --- /dev/null +++ b/slirp/ip.h @@ -0,0 +1,253 @@ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip.h 8.1 (Berkeley) 6/10/93 + * ip.h,v 1.3 1994/08/21 05:27:30 paul Exp + */ + +#ifndef _IP_H_ +#define _IP_H_ + +#ifdef HOST_WORDS_BIGENDIAN +# ifndef NTOHL +# define NTOHL(d) +# endif +# ifndef NTOHS +# define NTOHS(d) +# endif +# ifndef HTONL +# define HTONL(d) +# endif +# ifndef HTONS +# define HTONS(d) +# endif +#else +# ifndef NTOHL +# define NTOHL(d) ((d) = ntohl((d))) +# endif +# ifndef NTOHS +# define NTOHS(d) ((d) = ntohs((uint16_t)(d))) +# endif +# ifndef HTONL +# define HTONL(d) ((d) = htonl((d))) +# endif +# ifndef HTONS +# define HTONS(d) ((d) = htons((uint16_t)(d))) +# endif +#endif + +typedef uint32_t n_long; /* long as received from the net */ + +/* + * Definitions for internet protocol version 4. + * Per RFC 791, September 1981. + */ +#define IPVERSION 4 + +/* + * Structure of an internet header, naked of options. + */ +struct ip { +#ifdef HOST_WORDS_BIGENDIAN + u_int ip_v:4, /* version */ + ip_hl:4; /* header length */ +#else + u_int ip_hl:4, /* header length */ + ip_v:4; /* version */ +#endif + uint8_t ip_tos; /* type of service */ + uint16_t ip_len; /* total length */ + uint16_t ip_id; /* identification */ + uint16_t ip_off; /* fragment offset field */ +#define IP_DF 0x4000 /* don't fragment flag */ +#define IP_MF 0x2000 /* more fragments flag */ +#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ + uint8_t ip_ttl; /* time to live */ + uint8_t ip_p; /* protocol */ + uint16_t ip_sum; /* checksum */ + struct in_addr ip_src,ip_dst; /* source and dest address */ +} __attribute__((packed)); + +#define IP_MAXPACKET 65535 /* maximum packet size */ + +/* + * Definitions for IP type of service (ip_tos) + */ +#define IPTOS_LOWDELAY 0x10 +#define IPTOS_THROUGHPUT 0x08 +#define IPTOS_RELIABILITY 0x04 + +/* + * Definitions for options. + */ +#define IPOPT_COPIED(o) ((o)&0x80) +#define IPOPT_CLASS(o) ((o)&0x60) +#define IPOPT_NUMBER(o) ((o)&0x1f) + +#define IPOPT_CONTROL 0x00 +#define IPOPT_RESERVED1 0x20 +#define IPOPT_DEBMEAS 0x40 +#define IPOPT_RESERVED2 0x60 + +#define IPOPT_EOL 0 /* end of option list */ +#define IPOPT_NOP 1 /* no operation */ + +#define IPOPT_RR 7 /* record packet route */ +#define IPOPT_TS 68 /* timestamp */ +#define IPOPT_SECURITY 130 /* provide s,c,h,tcc */ +#define IPOPT_LSRR 131 /* loose source route */ +#define IPOPT_SATID 136 /* satnet id */ +#define IPOPT_SSRR 137 /* strict source route */ + +/* + * Offsets to fields in options other than EOL and NOP. + */ +#define IPOPT_OPTVAL 0 /* option ID */ +#define IPOPT_OLEN 1 /* option length */ +#define IPOPT_OFFSET 2 /* offset within option */ +#define IPOPT_MINOFF 4 /* min value of above */ + +/* + * Time stamp option structure. + */ +struct ip_timestamp { + uint8_t ipt_code; /* IPOPT_TS */ + uint8_t ipt_len; /* size of structure (variable) */ + uint8_t ipt_ptr; /* index of current entry */ +#ifdef HOST_WORDS_BIGENDIAN + u_int ipt_oflw:4, /* overflow counter */ + ipt_flg:4; /* flags, see below */ +#else + u_int ipt_flg:4, /* flags, see below */ + ipt_oflw:4; /* overflow counter */ +#endif + union ipt_timestamp { + n_long ipt_time[1]; + struct ipt_ta { + struct in_addr ipt_addr; + n_long ipt_time; + } ipt_ta[1]; + } ipt_timestamp; +} __attribute__((packed)); + +/* flag bits for ipt_flg */ +#define IPOPT_TS_TSONLY 0 /* timestamps only */ +#define IPOPT_TS_TSANDADDR 1 /* timestamps and addresses */ +#define IPOPT_TS_PRESPEC 3 /* specified modules only */ + +/* bits for security (not byte swapped) */ +#define IPOPT_SECUR_UNCLASS 0x0000 +#define IPOPT_SECUR_CONFID 0xf135 +#define IPOPT_SECUR_EFTO 0x789a +#define IPOPT_SECUR_MMMM 0xbc4d +#define IPOPT_SECUR_RESTR 0xaf13 +#define IPOPT_SECUR_SECRET 0xd788 +#define IPOPT_SECUR_TOPSECRET 0x6bc5 + +/* + * Internet implementation parameters. + */ +#define MAXTTL 255 /* maximum time to live (seconds) */ +#define IPDEFTTL 64 /* default ttl, from RFC 1340 */ +#define IPFRAGTTL 60 /* time to live for frags, slowhz */ +#define IPTTLDEC 1 /* subtracted when forwarding */ + +#define IP_MSS 576 /* default maximum segment size */ + +#if SIZEOF_CHAR_P == 4 +struct mbuf_ptr { + struct mbuf *mptr; + uint32_t dummy; +} __attribute__((packed)); +#else +struct mbuf_ptr { + struct mbuf *mptr; +} __attribute__((packed)); +#endif +struct qlink { + void *next, *prev; +}; + +/* + * Overlay for ip header used by other protocols (tcp, udp). + */ +struct ipovly { + struct mbuf_ptr ih_mbuf; /* backpointer to mbuf */ + uint8_t ih_x1; /* (unused) */ + uint8_t ih_pr; /* protocol */ + uint16_t ih_len; /* protocol length */ + struct in_addr ih_src; /* source internet address */ + struct in_addr ih_dst; /* destination internet address */ +} __attribute__((packed)); + +/* + * Ip reassembly queue structure. Each fragment + * being reassembled is attached to one of these structures. + * They are timed out after ipq_ttl drops to 0, and may also + * be reclaimed if memory becomes tight. + * size 28 bytes + */ +struct ipq { + struct qlink frag_link; /* to ip headers of fragments */ + struct qlink ip_link; /* to other reass headers */ + uint8_t ipq_ttl; /* time for reass q to live */ + uint8_t ipq_p; /* protocol of this fragment */ + uint16_t ipq_id; /* sequence id for reassembly */ + struct in_addr ipq_src,ipq_dst; +} __attribute__((packed)); + +/* + * Ip header, when holding a fragment. + * + * Note: ipf_link must be at same offset as frag_link above + */ +struct ipasfrag { + struct qlink ipf_link; + struct ip ipf_ip; +} __attribute__((packed)); + +#define ipf_off ipf_ip.ip_off +#define ipf_tos ipf_ip.ip_tos +#define ipf_len ipf_ip.ip_len +#define ipf_next ipf_link.next +#define ipf_prev ipf_link.prev + +/* + * Structure stored in mbuf in inpcb.ip_options + * and passed to ip_output when ip options are in use. + * The actual length of the options (including ipopt_dst) + * is in m_len. + */ +#define MAX_IPOPTLEN 40 + +struct ipoption { + struct in_addr ipopt_dst; /* first-hop dst if source routed */ + int8_t ipopt_list[MAX_IPOPTLEN]; /* options proper */ +} __attribute__((packed)); + +#endif diff --git a/slirp/ip_icmp.c b/slirp/ip_icmp.c new file mode 100644 index 0000000..751a8e2 --- /dev/null +++ b/slirp/ip_icmp.c @@ -0,0 +1,351 @@ +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_icmp.c 8.2 (Berkeley) 1/4/94 + * ip_icmp.c,v 1.7 1995/05/30 08:09:42 rgrimes Exp + */ + +#include "slirp.h" +#include "ip_icmp.h" + +/* The message sent when emulating PING */ +/* Be nice and tell them it's just a pseudo-ping packet */ +static const char icmp_ping_msg[] = "This is a pseudo-PING packet used by Slirp to emulate ICMP ECHO-REQUEST packets.\n"; + +/* list of actions for icmp_error() on RX of an icmp message */ +static const int icmp_flush[19] = { +/* ECHO REPLY (0) */ 0, + 1, + 1, +/* DEST UNREACH (3) */ 1, +/* SOURCE QUENCH (4)*/ 1, +/* REDIRECT (5) */ 1, + 1, + 1, +/* ECHO (8) */ 0, +/* ROUTERADVERT (9) */ 1, +/* ROUTERSOLICIT (10) */ 1, +/* TIME EXCEEDED (11) */ 1, +/* PARAMETER PROBLEM (12) */ 1, +/* TIMESTAMP (13) */ 0, +/* TIMESTAMP REPLY (14) */ 0, +/* INFO (15) */ 0, +/* INFO REPLY (16) */ 0, +/* ADDR MASK (17) */ 0, +/* ADDR MASK REPLY (18) */ 0 +}; + +/* + * Process a received ICMP message. + */ +void +icmp_input(struct mbuf *m, int hlen) +{ + register struct icmp *icp; + register struct ip *ip=mtod(m, struct ip *); + int icmplen=ip->ip_len; + Slirp *slirp = m->slirp; + + DEBUG_CALL("icmp_input"); + DEBUG_ARG("m = %lx", (long )m); + DEBUG_ARG("m_len = %d", m->m_len); + + /* + * Locate icmp structure in mbuf, and check + * that its not corrupted and of at least minimum length. + */ + if (icmplen < ICMP_MINLEN) { /* min 8 bytes payload */ + freeit: + m_freem(m); + goto end_error; + } + + m->m_len -= hlen; + m->m_data += hlen; + icp = mtod(m, struct icmp *); + if (cksum(m, icmplen)) { + goto freeit; + } + m->m_len += hlen; + m->m_data -= hlen; + + DEBUG_ARG("icmp_type = %d", icp->icmp_type); + switch (icp->icmp_type) { + case ICMP_ECHO: + icp->icmp_type = ICMP_ECHOREPLY; + ip->ip_len += hlen; /* since ip_input subtracts this */ + if (ip->ip_dst.s_addr == slirp->vhost_addr.s_addr) { + icmp_reflect(m); + } else { + struct socket *so; + struct sockaddr_in addr; + if ((so = socreate(slirp)) == NULL) goto freeit; + if(udp_attach(so) == -1) { + DEBUG_MISC((dfd,"icmp_input udp_attach errno = %d-%s\n", + errno,strerror(errno))); + sofree(so); + m_free(m); + goto end_error; + } + so->so_m = m; + so->so_faddr = ip->ip_dst; + so->so_fport = htons(7); + so->so_laddr = ip->ip_src; + so->so_lport = htons(9); + so->so_iptos = ip->ip_tos; + so->so_type = IPPROTO_ICMP; + so->so_state = SS_ISFCONNECTED; + + /* Send the packet */ + addr.sin_family = AF_INET; + if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + /* It's an alias */ + if (so->so_faddr.s_addr == slirp->vnameserver_addr.s_addr) { + if (get_dns_addr(&addr.sin_addr) < 0) + addr.sin_addr = loopback_addr; + } else { + addr.sin_addr = loopback_addr; + } + } else { + addr.sin_addr = so->so_faddr; + } + addr.sin_port = so->so_fport; + if(sendto(so->s, icmp_ping_msg, strlen(icmp_ping_msg), 0, + (struct sockaddr *)&addr, sizeof(addr)) == -1) { + DEBUG_MISC((dfd,"icmp_input udp sendto tx errno = %d-%s\n", + errno,strerror(errno))); + icmp_error(m, ICMP_UNREACH,ICMP_UNREACH_NET, 0,strerror(errno)); + udp_detach(so); + } + } /* if ip->ip_dst.s_addr == alias_addr.s_addr */ + break; + case ICMP_UNREACH: + /* XXX? report error? close socket? */ + case ICMP_TIMXCEED: + case ICMP_PARAMPROB: + case ICMP_SOURCEQUENCH: + case ICMP_TSTAMP: + case ICMP_MASKREQ: + case ICMP_REDIRECT: + m_freem(m); + break; + + default: + m_freem(m); + } /* swith */ + +end_error: + /* m is m_free()'d xor put in a socket xor or given to ip_send */ + return; +} + + +/* + * Send an ICMP message in response to a situation + * + * RFC 1122: 3.2.2 MUST send at least the IP header and 8 bytes of header. MAY send more (we do). + * MUST NOT change this header information. + * MUST NOT reply to a multicast/broadcast IP address. + * MUST NOT reply to a multicast/broadcast MAC address. + * MUST reply to only the first fragment. + */ +/* + * Send ICMP_UNREACH back to the source regarding msrc. + * mbuf *msrc is used as a template, but is NOT m_free()'d. + * It is reported as the bad ip packet. The header should + * be fully correct and in host byte order. + * ICMP fragmentation is illegal. All machines must accept 576 bytes in one + * packet. The maximum payload is 576-20(ip hdr)-8(icmp hdr)=548 + */ + +#define ICMP_MAXDATALEN (IP_MSS-28) +void +icmp_error(struct mbuf *msrc, u_char type, u_char code, int minsize, + const char *message) +{ + unsigned hlen, shlen, s_ip_len; + register struct ip *ip; + register struct icmp *icp; + register struct mbuf *m; + + DEBUG_CALL("icmp_error"); + DEBUG_ARG("msrc = %lx", (long )msrc); + DEBUG_ARG("msrc_len = %d", msrc->m_len); + + if(type!=ICMP_UNREACH && type!=ICMP_TIMXCEED) goto end_error; + + /* check msrc */ + if(!msrc) goto end_error; + ip = mtod(msrc, struct ip *); +#ifdef DEBUG + { char bufa[20], bufb[20]; + strcpy(bufa, inet_ntoa(ip->ip_src)); + strcpy(bufb, inet_ntoa(ip->ip_dst)); + DEBUG_MISC((dfd, " %.16s to %.16s\n", bufa, bufb)); + } +#endif + if(ip->ip_off & IP_OFFMASK) goto end_error; /* Only reply to fragment 0 */ + + shlen=ip->ip_hl << 2; + s_ip_len=ip->ip_len; + if(ip->ip_p == IPPROTO_ICMP) { + icp = (struct icmp *)((char *)ip + shlen); + /* + * Assume any unknown ICMP type is an error. This isn't + * specified by the RFC, but think about it.. + */ + if(icp->icmp_type>18 || icmp_flush[icp->icmp_type]) goto end_error; + } + + /* make a copy */ + m = m_get(msrc->slirp); + if (!m) { + goto end_error; + } + + { int new_m_size; + new_m_size=sizeof(struct ip )+ICMP_MINLEN+msrc->m_len+ICMP_MAXDATALEN; + if(new_m_size>m->m_size) m_inc(m, new_m_size); + } + memcpy(m->m_data, msrc->m_data, msrc->m_len); + m->m_len = msrc->m_len; /* copy msrc to m */ + + /* make the header of the reply packet */ + ip = mtod(m, struct ip *); + hlen= sizeof(struct ip ); /* no options in reply */ + + /* fill in icmp */ + m->m_data += hlen; + m->m_len -= hlen; + + icp = mtod(m, struct icmp *); + + if(minsize) s_ip_len=shlen+ICMP_MINLEN; /* return header+8b only */ + else if(s_ip_len>ICMP_MAXDATALEN) /* maximum size */ + s_ip_len=ICMP_MAXDATALEN; + + m->m_len=ICMP_MINLEN+s_ip_len; /* 8 bytes ICMP header */ + + /* min. size = 8+sizeof(struct ip)+8 */ + + icp->icmp_type = type; + icp->icmp_code = code; + icp->icmp_id = 0; + icp->icmp_seq = 0; + + memcpy(&icp->icmp_ip, msrc->m_data, s_ip_len); /* report the ip packet */ + HTONS(icp->icmp_ip.ip_len); + HTONS(icp->icmp_ip.ip_id); + HTONS(icp->icmp_ip.ip_off); + +#ifdef DEBUG + if(message) { /* DEBUG : append message to ICMP packet */ + int message_len; + char *cpnt; + message_len=strlen(message); + if(message_len>ICMP_MAXDATALEN) message_len=ICMP_MAXDATALEN; + cpnt=(char *)m->m_data+m->m_len; + memcpy(cpnt, message, message_len); + m->m_len+=message_len; + } +#endif + + icp->icmp_cksum = 0; + icp->icmp_cksum = cksum(m, m->m_len); + + m->m_data -= hlen; + m->m_len += hlen; + + /* fill in ip */ + ip->ip_hl = hlen >> 2; + ip->ip_len = m->m_len; + + ip->ip_tos=((ip->ip_tos & 0x1E) | 0xC0); /* high priority for errors */ + + ip->ip_ttl = MAXTTL; + ip->ip_p = IPPROTO_ICMP; + ip->ip_dst = ip->ip_src; /* ip adresses */ + ip->ip_src = m->slirp->vhost_addr; + + (void ) ip_output((struct socket *)NULL, m); + +end_error: + return; +} +#undef ICMP_MAXDATALEN + +/* + * Reflect the ip packet back to the source + */ +void +icmp_reflect(struct mbuf *m) +{ + register struct ip *ip = mtod(m, struct ip *); + int hlen = ip->ip_hl << 2; + int optlen = hlen - sizeof(struct ip ); + register struct icmp *icp; + + /* + * Send an icmp packet back to the ip level, + * after supplying a checksum. + */ + m->m_data += hlen; + m->m_len -= hlen; + icp = mtod(m, struct icmp *); + + icp->icmp_cksum = 0; + icp->icmp_cksum = cksum(m, ip->ip_len - hlen); + + m->m_data -= hlen; + m->m_len += hlen; + + /* fill in ip */ + if (optlen > 0) { + /* + * Strip out original options by copying rest of first + * mbuf's data back, and adjust the IP length. + */ + memmove((caddr_t)(ip + 1), (caddr_t)ip + hlen, + (unsigned )(m->m_len - hlen)); + hlen -= optlen; + ip->ip_hl = hlen >> 2; + ip->ip_len -= optlen; + m->m_len -= optlen; + } + + ip->ip_ttl = MAXTTL; + { /* swap */ + struct in_addr icmp_dst; + icmp_dst = ip->ip_dst; + ip->ip_dst = ip->ip_src; + ip->ip_src = icmp_dst; + } + + (void ) ip_output((struct socket *)NULL, m); +} diff --git a/slirp/ip_icmp.h b/slirp/ip_icmp.h new file mode 100644 index 0000000..2692822 --- /dev/null +++ b/slirp/ip_icmp.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_icmp.h 8.1 (Berkeley) 6/10/93 + * ip_icmp.h,v 1.4 1995/05/30 08:09:43 rgrimes Exp + */ + +#ifndef _NETINET_IP_ICMP_H_ +#define _NETINET_IP_ICMP_H_ + +/* + * Interface Control Message Protocol Definitions. + * Per RFC 792, September 1981. + */ + +typedef uint32_t n_time; + +/* + * Structure of an icmp header. + */ +struct icmp { + u_char icmp_type; /* type of message, see below */ + u_char icmp_code; /* type sub code */ + u_short icmp_cksum; /* ones complement cksum of struct */ + union { + u_char ih_pptr; /* ICMP_PARAMPROB */ + struct in_addr ih_gwaddr; /* ICMP_REDIRECT */ + struct ih_idseq { + u_short icd_id; + u_short icd_seq; + } ih_idseq; + int ih_void; + + /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */ + struct ih_pmtu { + u_short ipm_void; + u_short ipm_nextmtu; + } ih_pmtu; + } icmp_hun; +#define icmp_pptr icmp_hun.ih_pptr +#define icmp_gwaddr icmp_hun.ih_gwaddr +#define icmp_id icmp_hun.ih_idseq.icd_id +#define icmp_seq icmp_hun.ih_idseq.icd_seq +#define icmp_void icmp_hun.ih_void +#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void +#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu + union { + struct id_ts { + n_time its_otime; + n_time its_rtime; + n_time its_ttime; + } id_ts; + struct id_ip { + struct ip idi_ip; + /* options and then 64 bits of data */ + } id_ip; + uint32_t id_mask; + char id_data[1]; + } icmp_dun; +#define icmp_otime icmp_dun.id_ts.its_otime +#define icmp_rtime icmp_dun.id_ts.its_rtime +#define icmp_ttime icmp_dun.id_ts.its_ttime +#define icmp_ip icmp_dun.id_ip.idi_ip +#define icmp_mask icmp_dun.id_mask +#define icmp_data icmp_dun.id_data +}; + +/* + * Lower bounds on packet lengths for various types. + * For the error advice packets must first insure that the + * packet is large enought to contain the returned ip header. + * Only then can we do the check to see if 64 bits of packet + * data have been returned, since we need to check the returned + * ip header length. + */ +#define ICMP_MINLEN 8 /* abs minimum */ +#define ICMP_TSLEN (8 + 3 * sizeof (n_time)) /* timestamp */ +#define ICMP_MASKLEN 12 /* address mask */ +#define ICMP_ADVLENMIN (8 + sizeof (struct ip) + 8) /* min */ +#define ICMP_ADVLEN(p) (8 + ((p)->icmp_ip.ip_hl << 2) + 8) + /* N.B.: must separately check that ip_hl >= 5 */ + +/* + * Definition of type and code field values. + */ +#define ICMP_ECHOREPLY 0 /* echo reply */ +#define ICMP_UNREACH 3 /* dest unreachable, codes: */ +#define ICMP_UNREACH_NET 0 /* bad net */ +#define ICMP_UNREACH_HOST 1 /* bad host */ +#define ICMP_UNREACH_PROTOCOL 2 /* bad protocol */ +#define ICMP_UNREACH_PORT 3 /* bad port */ +#define ICMP_UNREACH_NEEDFRAG 4 /* IP_DF caused drop */ +#define ICMP_UNREACH_SRCFAIL 5 /* src route failed */ +#define ICMP_UNREACH_NET_UNKNOWN 6 /* unknown net */ +#define ICMP_UNREACH_HOST_UNKNOWN 7 /* unknown host */ +#define ICMP_UNREACH_ISOLATED 8 /* src host isolated */ +#define ICMP_UNREACH_NET_PROHIB 9 /* prohibited access */ +#define ICMP_UNREACH_HOST_PROHIB 10 /* ditto */ +#define ICMP_UNREACH_TOSNET 11 /* bad tos for net */ +#define ICMP_UNREACH_TOSHOST 12 /* bad tos for host */ +#define ICMP_SOURCEQUENCH 4 /* packet lost, slow down */ +#define ICMP_REDIRECT 5 /* shorter route, codes: */ +#define ICMP_REDIRECT_NET 0 /* for network */ +#define ICMP_REDIRECT_HOST 1 /* for host */ +#define ICMP_REDIRECT_TOSNET 2 /* for tos and net */ +#define ICMP_REDIRECT_TOSHOST 3 /* for tos and host */ +#define ICMP_ECHO 8 /* echo service */ +#define ICMP_ROUTERADVERT 9 /* router advertisement */ +#define ICMP_ROUTERSOLICIT 10 /* router solicitation */ +#define ICMP_TIMXCEED 11 /* time exceeded, code: */ +#define ICMP_TIMXCEED_INTRANS 0 /* ttl==0 in transit */ +#define ICMP_TIMXCEED_REASS 1 /* ttl==0 in reass */ +#define ICMP_PARAMPROB 12 /* ip header bad */ +#define ICMP_PARAMPROB_OPTABSENT 1 /* req. opt. absent */ +#define ICMP_TSTAMP 13 /* timestamp request */ +#define ICMP_TSTAMPREPLY 14 /* timestamp reply */ +#define ICMP_IREQ 15 /* information request */ +#define ICMP_IREQREPLY 16 /* information reply */ +#define ICMP_MASKREQ 17 /* address mask request */ +#define ICMP_MASKREPLY 18 /* address mask reply */ + +#define ICMP_MAXTYPE 18 + +#define ICMP_INFOTYPE(type) \ + ((type) == ICMP_ECHOREPLY || (type) == ICMP_ECHO || \ + (type) == ICMP_ROUTERADVERT || (type) == ICMP_ROUTERSOLICIT || \ + (type) == ICMP_TSTAMP || (type) == ICMP_TSTAMPREPLY || \ + (type) == ICMP_IREQ || (type) == ICMP_IREQREPLY || \ + (type) == ICMP_MASKREQ || (type) == ICMP_MASKREPLY) + +void icmp_input(struct mbuf *, int); +void icmp_error(struct mbuf *msrc, u_char type, u_char code, int minsize, + const char *message); +void icmp_reflect(struct mbuf *); + +#endif diff --git a/slirp/ip_input.c b/slirp/ip_input.c new file mode 100644 index 0000000..50ab951 --- /dev/null +++ b/slirp/ip_input.c @@ -0,0 +1,685 @@ +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_input.c 8.2 (Berkeley) 1/4/94 + * ip_input.c,v 1.11 1994/11/16 10:17:08 jkh Exp + */ + +/* + * Changes and additions relating to SLiRP are + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" +#include "ip_icmp.h" + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +static struct ip *ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp); +static void ip_freef(Slirp *slirp, struct ipq *fp); +static void ip_enq(register struct ipasfrag *p, + register struct ipasfrag *prev); +static void ip_deq(register struct ipasfrag *p); + +/* + * IP initialization: fill in IP protocol switch table. + * All protocols not implemented in kernel go to raw IP protocol handler. + */ +void +ip_init(Slirp *slirp) +{ + slirp->ipq.ip_link.next = slirp->ipq.ip_link.prev = &slirp->ipq.ip_link; + udp_init(slirp); + tcp_init(slirp); +} + +/* + * Ip input routine. Checksum and byte swap header. If fragmented + * try to reassemble. Process options. Pass to next level. + */ +void +ip_input(struct mbuf *m) +{ + Slirp *slirp = m->slirp; + register struct ip *ip; + int hlen; + + DEBUG_CALL("ip_input"); + DEBUG_ARG("m = %lx", (long)m); + DEBUG_ARG("m_len = %d", m->m_len); + + if (m->m_len < sizeof (struct ip)) { + return; + } + + ip = mtod(m, struct ip *); + + if (ip->ip_v != IPVERSION) { + goto bad; + } + + hlen = ip->ip_hl << 2; + if (hlenm->m_len) {/* min header length */ + goto bad; /* or packet too short */ + } + + /* keep ip header intact for ICMP reply + * ip->ip_sum = cksum(m, hlen); + * if (ip->ip_sum) { + */ + if(cksum(m,hlen)) { + goto bad; + } + + /* + * Convert fields to host representation. + */ + NTOHS(ip->ip_len); + if (ip->ip_len < hlen) { + goto bad; + } + NTOHS(ip->ip_id); + NTOHS(ip->ip_off); + + /* + * Check that the amount of data in the buffers + * is as at least much as the IP header would have us expect. + * Trim mbufs if longer than we expect. + * Drop packet if shorter than we expect. + */ + if (m->m_len < ip->ip_len) { + goto bad; + } + + if (slirp->restricted) { + if ((ip->ip_dst.s_addr & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + if (ip->ip_dst.s_addr == 0xffffffff && ip->ip_p != IPPROTO_UDP) + goto bad; + } else { + uint32_t inv_mask = ~slirp->vnetwork_mask.s_addr; + struct ex_list *ex_ptr; + + if ((ip->ip_dst.s_addr & inv_mask) == inv_mask) { + goto bad; + } + for (ex_ptr = slirp->exec_list; ex_ptr; ex_ptr = ex_ptr->ex_next) + if (ex_ptr->ex_addr.s_addr == ip->ip_dst.s_addr) + break; + + if (!ex_ptr) + goto bad; + } + } + + /* Should drop packet if mbuf too long? hmmm... */ + if (m->m_len > ip->ip_len) + m_adj(m, ip->ip_len - m->m_len); + + /* check ip_ttl for a correct ICMP reply */ + if(ip->ip_ttl==0) { + icmp_error(m, ICMP_TIMXCEED,ICMP_TIMXCEED_INTRANS, 0,"ttl"); + goto bad; + } + + /* + * If offset or IP_MF are set, must reassemble. + * Otherwise, nothing need be done. + * (We could look in the reassembly queue to see + * if the packet was previously fragmented, + * but it's not worth the time; just let them time out.) + * + * XXX This should fail, don't fragment yet + */ + if (ip->ip_off &~ IP_DF) { + register struct ipq *fp; + struct qlink *l; + /* + * Look for queue of fragments + * of this datagram. + */ + for (l = slirp->ipq.ip_link.next; l != &slirp->ipq.ip_link; + l = l->next) { + fp = container_of(l, struct ipq, ip_link); + if (ip->ip_id == fp->ipq_id && + ip->ip_src.s_addr == fp->ipq_src.s_addr && + ip->ip_dst.s_addr == fp->ipq_dst.s_addr && + ip->ip_p == fp->ipq_p) + goto found; + } + fp = NULL; + found: + + /* + * Adjust ip_len to not reflect header, + * set ip_mff if more fragments are expected, + * convert offset of this to bytes. + */ + ip->ip_len -= hlen; + if (ip->ip_off & IP_MF) + ip->ip_tos |= 1; + else + ip->ip_tos &= ~1; + + ip->ip_off <<= 3; + + /* + * If datagram marked as having more fragments + * or if this is not the first fragment, + * attempt reassembly; if it succeeds, proceed. + */ + if (ip->ip_tos & 1 || ip->ip_off) { + ip = ip_reass(slirp, ip, fp); + if (ip == NULL) + return; + m = dtom(slirp, ip); + } else + if (fp) + ip_freef(slirp, fp); + + } else + ip->ip_len -= hlen; + + /* + * Switch out to protocol's input routine. + */ + switch (ip->ip_p) { + case IPPROTO_TCP: + tcp_input(m, hlen, (struct socket *)NULL); + break; + case IPPROTO_UDP: + udp_input(m, hlen); + break; + case IPPROTO_ICMP: + icmp_input(m, hlen); + break; + default: + m_free(m); + } + return; +bad: + m_freem(m); + return; +} + +#define iptofrag(P) ((struct ipasfrag *)(((char*)(P)) - sizeof(struct qlink))) +#define fragtoip(P) ((struct ip*)(((char*)(P)) + sizeof(struct qlink))) +/* + * Take incoming datagram fragment and try to + * reassemble it into whole datagram. If a chain for + * reassembly of this datagram already exists, then it + * is given as fp; otherwise have to make a chain. + */ +static struct ip * +ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp) +{ + register struct mbuf *m = dtom(slirp, ip); + register struct ipasfrag *q; + int hlen = ip->ip_hl << 2; + int i, next; + + DEBUG_CALL("ip_reass"); + DEBUG_ARG("ip = %lx", (long)ip); + DEBUG_ARG("fp = %lx", (long)fp); + DEBUG_ARG("m = %lx", (long)m); + + /* + * Presence of header sizes in mbufs + * would confuse code below. + * Fragment m_data is concatenated. + */ + m->m_data += hlen; + m->m_len -= hlen; + + /* + * If first fragment to arrive, create a reassembly queue. + */ + if (fp == NULL) { + struct mbuf *t = m_get(slirp); + + if (t == NULL) { + goto dropfrag; + } + fp = mtod(t, struct ipq *); + insque(&fp->ip_link, &slirp->ipq.ip_link); + fp->ipq_ttl = IPFRAGTTL; + fp->ipq_p = ip->ip_p; + fp->ipq_id = ip->ip_id; + fp->frag_link.next = fp->frag_link.prev = &fp->frag_link; + fp->ipq_src = ip->ip_src; + fp->ipq_dst = ip->ip_dst; + q = (struct ipasfrag *)fp; + goto insert; + } + + /* + * Find a segment which begins after this one does. + */ + for (q = fp->frag_link.next; q != (struct ipasfrag *)&fp->frag_link; + q = q->ipf_next) + if (q->ipf_off > ip->ip_off) + break; + + /* + * If there is a preceding segment, it may provide some of + * our data already. If so, drop the data from the incoming + * segment. If it provides all of our data, drop us. + */ + if (q->ipf_prev != &fp->frag_link) { + struct ipasfrag *pq = q->ipf_prev; + i = pq->ipf_off + pq->ipf_len - ip->ip_off; + if (i > 0) { + if (i >= ip->ip_len) + goto dropfrag; + m_adj(dtom(slirp, ip), i); + ip->ip_off += i; + ip->ip_len -= i; + } + } + + /* + * While we overlap succeeding segments trim them or, + * if they are completely covered, dequeue them. + */ + while (q != (struct ipasfrag*)&fp->frag_link && + ip->ip_off + ip->ip_len > q->ipf_off) { + i = (ip->ip_off + ip->ip_len) - q->ipf_off; + if (i < q->ipf_len) { + q->ipf_len -= i; + q->ipf_off += i; + m_adj(dtom(slirp, q), i); + break; + } + q = q->ipf_next; + m_freem(dtom(slirp, q->ipf_prev)); + ip_deq(q->ipf_prev); + } + +insert: + /* + * Stick new segment in its place; + * check for complete reassembly. + */ + ip_enq(iptofrag(ip), q->ipf_prev); + next = 0; + for (q = fp->frag_link.next; q != (struct ipasfrag*)&fp->frag_link; + q = q->ipf_next) { + if (q->ipf_off != next) + return NULL; + next += q->ipf_len; + } + if (((struct ipasfrag *)(q->ipf_prev))->ipf_tos & 1) + return NULL; + + /* + * Reassembly is complete; concatenate fragments. + */ + q = fp->frag_link.next; + m = dtom(slirp, q); + + q = (struct ipasfrag *) q->ipf_next; + while (q != (struct ipasfrag*)&fp->frag_link) { + struct mbuf *t = dtom(slirp, q); + q = (struct ipasfrag *) q->ipf_next; + m_cat(m, t); + } + + /* + * Create header for new ip packet by + * modifying header of first packet; + * dequeue and discard fragment reassembly header. + * Make header visible. + */ + q = fp->frag_link.next; + + /* + * If the fragments concatenated to an mbuf that's + * bigger than the total size of the fragment, then and + * m_ext buffer was alloced. But fp->ipq_next points to + * the old buffer (in the mbuf), so we must point ip + * into the new buffer. + */ + if (m->m_flags & M_EXT) { + int delta = (char *)q - m->m_dat; + q = (struct ipasfrag *)(m->m_ext + delta); + } + + ip = fragtoip(q); + ip->ip_len = next; + ip->ip_tos &= ~1; + ip->ip_src = fp->ipq_src; + ip->ip_dst = fp->ipq_dst; + remque(&fp->ip_link); + (void) m_free(dtom(slirp, fp)); + m->m_len += (ip->ip_hl << 2); + m->m_data -= (ip->ip_hl << 2); + + return ip; + +dropfrag: + m_freem(m); + return NULL; +} + +/* + * Free a fragment reassembly header and all + * associated datagrams. + */ +static void +ip_freef(Slirp *slirp, struct ipq *fp) +{ + register struct ipasfrag *q, *p; + + for (q = fp->frag_link.next; q != (struct ipasfrag*)&fp->frag_link; q = p) { + p = q->ipf_next; + ip_deq(q); + m_freem(dtom(slirp, q)); + } + remque(&fp->ip_link); + (void) m_free(dtom(slirp, fp)); +} + +/* + * Put an ip fragment on a reassembly chain. + * Like insque, but pointers in middle of structure. + */ +static void +ip_enq(register struct ipasfrag *p, register struct ipasfrag *prev) +{ + DEBUG_CALL("ip_enq"); + DEBUG_ARG("prev = %lx", (long)prev); + p->ipf_prev = prev; + p->ipf_next = prev->ipf_next; + ((struct ipasfrag *)(prev->ipf_next))->ipf_prev = p; + prev->ipf_next = p; +} + +/* + * To ip_enq as remque is to insque. + */ +static void +ip_deq(register struct ipasfrag *p) +{ + ((struct ipasfrag *)(p->ipf_prev))->ipf_next = p->ipf_next; + ((struct ipasfrag *)(p->ipf_next))->ipf_prev = p->ipf_prev; +} + +/* + * IP timer processing; + * if a timer expires on a reassembly + * queue, discard it. + */ +void +ip_slowtimo(Slirp *slirp) +{ + struct qlink *l; + + DEBUG_CALL("ip_slowtimo"); + + l = slirp->ipq.ip_link.next; + + if (l == NULL) + return; + + while (l != &slirp->ipq.ip_link) { + struct ipq *fp = container_of(l, struct ipq, ip_link); + l = l->next; + if (--fp->ipq_ttl == 0) { + ip_freef(slirp, fp); + } + } +} + +/* + * Do option processing on a datagram, + * possibly discarding it if bad options are encountered, + * or forwarding it if source-routed. + * Returns 1 if packet has been forwarded/freed, + * 0 if the packet should be processed further. + */ + +#ifdef notdef + +int +ip_dooptions(m) + struct mbuf *m; +{ + register struct ip *ip = mtod(m, struct ip *); + register u_char *cp; + register struct ip_timestamp *ipt; + register struct in_ifaddr *ia; + int opt, optlen, cnt, off, code, type, forward = 0; + struct in_addr *sin, dst; +typedef uint32_t n_time; + n_time ntime; + + dst = ip->ip_dst; + cp = (u_char *)(ip + 1); + cnt = (ip->ip_hl << 2) - sizeof (struct ip); + for (; cnt > 0; cnt -= optlen, cp += optlen) { + opt = cp[IPOPT_OPTVAL]; + if (opt == IPOPT_EOL) + break; + if (opt == IPOPT_NOP) + optlen = 1; + else { + optlen = cp[IPOPT_OLEN]; + if (optlen <= 0 || optlen > cnt) { + code = &cp[IPOPT_OLEN] - (u_char *)ip; + goto bad; + } + } + switch (opt) { + + default: + break; + + /* + * Source routing with record. + * Find interface with current destination address. + * If none on this machine then drop if strictly routed, + * or do nothing if loosely routed. + * Record interface address and bring up next address + * component. If strictly routed make sure next + * address is on directly accessible net. + */ + case IPOPT_LSRR: + case IPOPT_SSRR: + if ((off = cp[IPOPT_OFFSET]) < IPOPT_MINOFF) { + code = &cp[IPOPT_OFFSET] - (u_char *)ip; + goto bad; + } + ipaddr.sin_addr = ip->ip_dst; + ia = (struct in_ifaddr *) + ifa_ifwithaddr((struct sockaddr *)&ipaddr); + if (ia == 0) { + if (opt == IPOPT_SSRR) { + type = ICMP_UNREACH; + code = ICMP_UNREACH_SRCFAIL; + goto bad; + } + /* + * Loose routing, and not at next destination + * yet; nothing to do except forward. + */ + break; + } + off--; / * 0 origin * / + if (off > optlen - sizeof(struct in_addr)) { + /* + * End of source route. Should be for us. + */ + save_rte(cp, ip->ip_src); + break; + } + /* + * locate outgoing interface + */ + bcopy((caddr_t)(cp + off), (caddr_t)&ipaddr.sin_addr, + sizeof(ipaddr.sin_addr)); + if (opt == IPOPT_SSRR) { +#define INA struct in_ifaddr * +#define SA struct sockaddr * + if ((ia = (INA)ifa_ifwithdstaddr((SA)&ipaddr)) == 0) + ia = (INA)ifa_ifwithnet((SA)&ipaddr); + } else + ia = ip_rtaddr(ipaddr.sin_addr); + if (ia == 0) { + type = ICMP_UNREACH; + code = ICMP_UNREACH_SRCFAIL; + goto bad; + } + ip->ip_dst = ipaddr.sin_addr; + bcopy((caddr_t)&(IA_SIN(ia)->sin_addr), + (caddr_t)(cp + off), sizeof(struct in_addr)); + cp[IPOPT_OFFSET] += sizeof(struct in_addr); + /* + * Let ip_intr's mcast routing check handle mcast pkts + */ + forward = !IN_MULTICAST(ntohl(ip->ip_dst.s_addr)); + break; + + case IPOPT_RR: + if ((off = cp[IPOPT_OFFSET]) < IPOPT_MINOFF) { + code = &cp[IPOPT_OFFSET] - (u_char *)ip; + goto bad; + } + /* + * If no space remains, ignore. + */ + off--; * 0 origin * + if (off > optlen - sizeof(struct in_addr)) + break; + bcopy((caddr_t)(&ip->ip_dst), (caddr_t)&ipaddr.sin_addr, + sizeof(ipaddr.sin_addr)); + /* + * locate outgoing interface; if we're the destination, + * use the incoming interface (should be same). + */ + if ((ia = (INA)ifa_ifwithaddr((SA)&ipaddr)) == 0 && + (ia = ip_rtaddr(ipaddr.sin_addr)) == 0) { + type = ICMP_UNREACH; + code = ICMP_UNREACH_HOST; + goto bad; + } + bcopy((caddr_t)&(IA_SIN(ia)->sin_addr), + (caddr_t)(cp + off), sizeof(struct in_addr)); + cp[IPOPT_OFFSET] += sizeof(struct in_addr); + break; + + case IPOPT_TS: + code = cp - (u_char *)ip; + ipt = (struct ip_timestamp *)cp; + if (ipt->ipt_len < 5) + goto bad; + if (ipt->ipt_ptr > ipt->ipt_len - sizeof (int32_t)) { + if (++ipt->ipt_oflw == 0) + goto bad; + break; + } + sin = (struct in_addr *)(cp + ipt->ipt_ptr - 1); + switch (ipt->ipt_flg) { + + case IPOPT_TS_TSONLY: + break; + + case IPOPT_TS_TSANDADDR: + if (ipt->ipt_ptr + sizeof(n_time) + + sizeof(struct in_addr) > ipt->ipt_len) + goto bad; + ipaddr.sin_addr = dst; + ia = (INA)ifaof_ i f p foraddr((SA)&ipaddr, + m->m_pkthdr.rcvif); + if (ia == 0) + continue; + bcopy((caddr_t)&IA_SIN(ia)->sin_addr, + (caddr_t)sin, sizeof(struct in_addr)); + ipt->ipt_ptr += sizeof(struct in_addr); + break; + + case IPOPT_TS_PRESPEC: + if (ipt->ipt_ptr + sizeof(n_time) + + sizeof(struct in_addr) > ipt->ipt_len) + goto bad; + bcopy((caddr_t)sin, (caddr_t)&ipaddr.sin_addr, + sizeof(struct in_addr)); + if (ifa_ifwithaddr((SA)&ipaddr) == 0) + continue; + ipt->ipt_ptr += sizeof(struct in_addr); + break; + + default: + goto bad; + } + ntime = iptime(); + bcopy((caddr_t)&ntime, (caddr_t)cp + ipt->ipt_ptr - 1, + sizeof(n_time)); + ipt->ipt_ptr += sizeof(n_time); + } + } + if (forward) { + ip_forward(m, 1); + return (1); + } + return (0); +bad: + icmp_error(m, type, code, 0, 0); + + return (1); +} + +#endif /* notdef */ + +/* + * Strip out IP options, at higher + * level protocol in the kernel. + * Second argument is buffer to which options + * will be moved, and return value is their length. + * (XXX) should be deleted; last arg currently ignored. + */ +void +ip_stripoptions(register struct mbuf *m, struct mbuf *mopt) +{ + register int i; + struct ip *ip = mtod(m, struct ip *); + register caddr_t opts; + int olen; + + olen = (ip->ip_hl<<2) - sizeof (struct ip); + opts = (caddr_t)(ip + 1); + i = m->m_len - (sizeof (struct ip) + olen); + memcpy(opts, opts + olen, (unsigned)i); + m->m_len -= olen; + + ip->ip_hl = sizeof(struct ip) >> 2; +} diff --git a/slirp/ip_output.c b/slirp/ip_output.c new file mode 100644 index 0000000..657c9af --- /dev/null +++ b/slirp/ip_output.c @@ -0,0 +1,172 @@ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_output.c 8.3 (Berkeley) 1/21/94 + * ip_output.c,v 1.9 1994/11/16 10:17:10 jkh Exp + */ + +/* + * Changes and additions relating to SLiRP are + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" + +/* Number of packets queued before we start sending + * (to prevent allocing too many mbufs) */ +#define IF_THRESH 10 + +/* + * IP output. The packet in mbuf chain m contains a skeletal IP + * header (with len, off, ttl, proto, tos, src, dst). + * The mbuf chain containing the packet will be freed. + * The mbuf opt, if present, will not be freed. + */ +int +ip_output(struct socket *so, struct mbuf *m0) +{ + Slirp *slirp = m0->slirp; + register struct ip *ip; + register struct mbuf *m = m0; + register int hlen = sizeof(struct ip ); + int len, off, error = 0; + + DEBUG_CALL("ip_output"); + DEBUG_ARG("so = %lx", (long)so); + DEBUG_ARG("m0 = %lx", (long)m0); + + ip = mtod(m, struct ip *); + /* + * Fill in IP header. + */ + ip->ip_v = IPVERSION; + ip->ip_off &= IP_DF; + ip->ip_id = htons(slirp->ip_id++); + ip->ip_hl = hlen >> 2; + + /* + * If small enough for interface, can just send directly. + */ + if ((uint16_t)ip->ip_len <= IF_MTU) { + ip->ip_len = htons((uint16_t)ip->ip_len); + ip->ip_off = htons((uint16_t)ip->ip_off); + ip->ip_sum = 0; + ip->ip_sum = cksum(m, hlen); + + if_output(so, m); + goto done; + } + + /* + * Too large for interface; fragment if possible. + * Must be able to put at least 8 bytes per fragment. + */ + if (ip->ip_off & IP_DF) { + error = -1; + goto bad; + } + + len = (IF_MTU - hlen) &~ 7; /* ip databytes per packet */ + if (len < 8) { + error = -1; + goto bad; + } + + { + int mhlen, firstlen = len; + struct mbuf **mnext = &m->m_nextpkt; + + /* + * Loop through length of segment after first fragment, + * make new header and copy data of each part and link onto chain. + */ + m0 = m; + mhlen = sizeof (struct ip); + for (off = hlen + len; off < (uint16_t)ip->ip_len; off += len) { + register struct ip *mhip; + m = m_get(slirp); + if (m == NULL) { + error = -1; + goto sendorfree; + } + m->m_data += IF_MAXLINKHDR; + mhip = mtod(m, struct ip *); + *mhip = *ip; + + m->m_len = mhlen; + mhip->ip_off = ((off - hlen) >> 3) + (ip->ip_off & ~IP_MF); + if (ip->ip_off & IP_MF) + mhip->ip_off |= IP_MF; + if (off + len >= (uint16_t)ip->ip_len) + len = (uint16_t)ip->ip_len - off; + else + mhip->ip_off |= IP_MF; + mhip->ip_len = htons((uint16_t)(len + mhlen)); + + if (m_copy(m, m0, off, len) < 0) { + error = -1; + goto sendorfree; + } + + mhip->ip_off = htons((uint16_t)mhip->ip_off); + mhip->ip_sum = 0; + mhip->ip_sum = cksum(m, mhlen); + *mnext = m; + mnext = &m->m_nextpkt; + } + /* + * Update first fragment by trimming what's been copied out + * and updating header, then send each fragment (in order). + */ + m = m0; + m_adj(m, hlen + firstlen - (uint16_t)ip->ip_len); + ip->ip_len = htons((uint16_t)m->m_len); + ip->ip_off = htons((uint16_t)(ip->ip_off | IP_MF)); + ip->ip_sum = 0; + ip->ip_sum = cksum(m, hlen); +sendorfree: + for (m = m0; m; m = m0) { + m0 = m->m_nextpkt; + m->m_nextpkt = NULL; + if (error == 0) + if_output(so, m); + else + m_freem(m); + } + } + +done: + return (error); + +bad: + m_freem(m0); + goto done; +} diff --git a/slirp/libslirp.h b/slirp/libslirp.h new file mode 100644 index 0000000..574852b --- /dev/null +++ b/slirp/libslirp.h @@ -0,0 +1,56 @@ +#ifndef _LIBSLIRP_H +#define _LIBSLIRP_H + +#ifdef CONFIG_SLIRP + +#include + +struct Slirp; +typedef struct Slirp Slirp; + +int get_dns_addr(struct in_addr *pdns_addr); + +Slirp *slirp_init(int restricted, struct in_addr vnetwork, + struct in_addr vnetmask, struct in_addr vhost, + const char *vhostname, const char *tftp_path, + const char *bootfile, struct in_addr vdhcp_start, + struct in_addr vnameserver, void *opaque); +void slirp_cleanup(Slirp *slirp); + +void slirp_select_fill(Slirp *slirp, int *pnfds, + fd_set *readfds, fd_set *writefds, fd_set *xfds); + +void slirp_select_poll(Slirp *slirp, + fd_set *readfds, fd_set *writefds, fd_set *xfds, + int select_error); + +void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len); + +/* you must provide the following functions: */ +int slirp_can_output(void *opaque); +void slirp_output(void *opaque, const uint8_t *pkt, int pkt_len); + +int slirp_add_hostfwd(Slirp *slirp, int is_udp, + struct in_addr host_addr, int host_port, + struct in_addr guest_addr, int guest_port); +int slirp_remove_hostfwd(Slirp *slirp, int is_udp, + struct in_addr host_addr, int host_port); +int slirp_add_exec(Slirp *slirp, int do_pty, const void *args, + struct in_addr *guest_addr, int guest_port); + +void slirp_socket_recv(Slirp *slirp, struct in_addr guest_addr, + int guest_port, const uint8_t *buf, int size); +size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr guest_addr, + int guest_port); +int slirp_get_time_ms(void); + +#else /* !CONFIG_SLIRP */ + +static inline void slirp_select_fill(int *pnfds, fd_set *readfds, + fd_set *writefds, fd_set *xfds) { } + +static inline void slirp_select_poll(fd_set *readfds, fd_set *writefds, + fd_set *xfds, int select_error) { } +#endif /* !CONFIG_SLIRP */ + +#endif diff --git a/slirp/main.h b/slirp/main.h new file mode 100644 index 0000000..0dd8d81 --- /dev/null +++ b/slirp/main.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#ifdef HAVE_SYS_SELECT_H +#include +#endif + +#define TOWRITEMAX 512 + +extern int slirp_socket; +extern int slirp_socket_unit; +extern int slirp_socket_port; +extern uint32_t slirp_socket_addr; +extern char *slirp_socket_passwd; +extern int ctty_closed; + +/* + * Get the difference in 2 times from updtim() + * Allow for wraparound times, "just in case" + * x is the greater of the 2 (current time) and y is + * what it's being compared against. + */ +#define TIME_DIFF(x,y) (x)-(y) < 0 ? ~0-(y)+(x) : (x)-(y) + +extern char *slirp_tty; +extern char *exec_shell; +extern u_int curtime; +extern fd_set *global_readfds, *global_writefds, *global_xfds; +extern struct in_addr loopback_addr; +extern char *username; +extern char *socket_path; +extern int towrite_max; +extern int ppp_exit; +extern int tcp_keepintvl; + +#define PROTO_SLIP 0x1 +#ifdef USE_PPP +#define PROTO_PPP 0x2 +#endif + +void if_encap(Slirp *slirp, const uint8_t *ip_data, int ip_data_len); +ssize_t slirp_send(struct socket *so, const void *buf, size_t len, int flags); diff --git a/slirp/mbuf.c b/slirp/mbuf.c new file mode 100644 index 0000000..6c115ee --- /dev/null +++ b/slirp/mbuf.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) 1995 Danny Gasparovski + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +/* + * mbuf's in SLiRP are much simpler than the real mbufs in + * FreeBSD. They are fixed size, determined by the MTU, + * so that one whole packet can fit. Mbuf's cannot be + * chained together. If there's more data than the mbuf + * could hold, an external malloced buffer is pointed to + * by m_ext (and the data pointers) and M_EXT is set in + * the flags + */ + +#include "slirp.h" + +#define MBUF_THRESH 30 + +/* + * Find a nice value for msize + * XXX if_maxlinkhdr already in mtu + */ +#define SLIRP_MSIZE (IF_MTU + IF_MAXLINKHDR + offsetof(struct mbuf, m_dat) + 6) + +void +m_init(Slirp *slirp) +{ + slirp->m_freelist.m_next = slirp->m_freelist.m_prev = &slirp->m_freelist; + slirp->m_usedlist.m_next = slirp->m_usedlist.m_prev = &slirp->m_usedlist; +} + +/* + * Get an mbuf from the free list, if there are none + * malloc one + * + * Because fragmentation can occur if we alloc new mbufs and + * free old mbufs, we mark all mbufs above mbuf_thresh as M_DOFREE, + * which tells m_free to actually free() it + */ +struct mbuf * +m_get(Slirp *slirp) +{ + register struct mbuf *m; + int flags = 0; + + DEBUG_CALL("m_get"); + + if (slirp->m_freelist.m_next == &slirp->m_freelist) { + m = (struct mbuf *)malloc(SLIRP_MSIZE); + if (m == NULL) goto end_error; + slirp->mbuf_alloced++; + if (slirp->mbuf_alloced > MBUF_THRESH) + flags = M_DOFREE; + m->slirp = slirp; + } else { + m = slirp->m_freelist.m_next; + remque(m); + } + + /* Insert it in the used list */ + insque(m,&slirp->m_usedlist); + m->m_flags = (flags | M_USEDLIST); + + /* Initialise it */ + m->m_size = SLIRP_MSIZE - offsetof(struct mbuf, m_dat); + m->m_data = m->m_dat; + m->m_len = 0; + m->m_nextpkt = NULL; + m->m_prevpkt = NULL; +end_error: + DEBUG_ARG("m = %lx", (long )m); + return m; +} + +void +m_free(struct mbuf *m) +{ + + DEBUG_CALL("m_free"); + DEBUG_ARG("m = %lx", (long )m); + + if(m) { + /* Remove from m_usedlist */ + if (m->m_flags & M_USEDLIST) + remque(m); + + /* If it's M_EXT, free() it */ + if (m->m_flags & M_EXT) + free(m->m_ext); + + /* + * Either free() it or put it on the free list + */ + if (m->m_flags & M_DOFREE) { + m->slirp->mbuf_alloced--; + free(m); + } else if ((m->m_flags & M_FREELIST) == 0) { + insque(m,&m->slirp->m_freelist); + m->m_flags = M_FREELIST; /* Clobber other flags */ + } + } /* if(m) */ +} + +/* + * Copy data from one mbuf to the end of + * the other.. if result is too big for one mbuf, malloc() + * an M_EXT data segment + */ +void +m_cat(struct mbuf *m, struct mbuf *n) +{ + /* + * If there's no room, realloc + */ + if (M_FREEROOM(m) < n->m_len) + m_inc(m,m->m_size+MINCSIZE); + + memcpy(m->m_data+m->m_len, n->m_data, n->m_len); + m->m_len += n->m_len; + + m_free(n); +} + + +/* make m size bytes large */ +void +m_inc(struct mbuf *m, int size) +{ + int datasize; + + /* some compiles throw up on gotos. This one we can fake. */ + if(m->m_size>size) return; + + if (m->m_flags & M_EXT) { + datasize = m->m_data - m->m_ext; + m->m_ext = (char *)realloc(m->m_ext,size); + m->m_data = m->m_ext + datasize; + } else { + char *dat; + datasize = m->m_data - m->m_dat; + dat = (char *)malloc(size); + memcpy(dat, m->m_dat, m->m_size); + + m->m_ext = dat; + m->m_data = m->m_ext + datasize; + m->m_flags |= M_EXT; + } + + m->m_size = size; + +} + + + +void +m_adj(struct mbuf *m, int len) +{ + if (m == NULL) + return; + if (len >= 0) { + /* Trim from head */ + m->m_data += len; + m->m_len -= len; + } else { + /* Trim from tail */ + len = -len; + m->m_len -= len; + } +} + + +/* + * Copy len bytes from m, starting off bytes into n + */ +int +m_copy(struct mbuf *n, struct mbuf *m, int off, int len) +{ + if (len > M_FREEROOM(n)) + return -1; + + memcpy((n->m_data + n->m_len), (m->m_data + off), len); + n->m_len += len; + return 0; +} + + +/* + * Given a pointer into an mbuf, return the mbuf + * XXX This is a kludge, I should eliminate the need for it + * Fortunately, it's not used often + */ +struct mbuf * +dtom(Slirp *slirp, void *dat) +{ + struct mbuf *m; + + DEBUG_CALL("dtom"); + DEBUG_ARG("dat = %lx", (long )dat); + + /* bug corrected for M_EXT buffers */ + for (m = slirp->m_usedlist.m_next; m != &slirp->m_usedlist; + m = m->m_next) { + if (m->m_flags & M_EXT) { + if( (char *)dat>=m->m_ext && (char *)dat<(m->m_ext + m->m_size) ) + return m; + } else { + if( (char *)dat >= m->m_dat && (char *)dat<(m->m_dat + m->m_size) ) + return m; + } + } + + DEBUG_ERROR((dfd, "dtom failed")); + + return (struct mbuf *)0; +} diff --git a/slirp/mbuf.h b/slirp/mbuf.h new file mode 100644 index 0000000..97729e2 --- /dev/null +++ b/slirp/mbuf.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)mbuf.h 8.3 (Berkeley) 1/21/94 + * mbuf.h,v 1.9 1994/11/14 13:54:20 bde Exp + */ + +#ifndef _MBUF_H_ +#define _MBUF_H_ + +#define m_freem m_free + + +#define MINCSIZE 4096 /* Amount to increase mbuf if too small */ + +/* + * Macros for type conversion + * mtod(m,t) - convert mbuf pointer to data pointer of correct type + */ +#define mtod(m,t) ((t)(m)->m_data) + +/* XXX About mbufs for slirp: + * Only one mbuf is ever used in a chain, for each "cell" of data. + * m_nextpkt points to the next packet, if fragmented. + * If the data is too large, the M_EXT is used, and a larger block + * is alloced. Therefore, m_free[m] must check for M_EXT and if set + * free the m_ext. This is inefficient memory-wise, but who cares. + */ + +/* XXX should union some of these! */ +/* header at beginning of each mbuf: */ +struct m_hdr { + struct mbuf *mh_next; /* Linked list of mbufs */ + struct mbuf *mh_prev; + struct mbuf *mh_nextpkt; /* Next packet in queue/record */ + struct mbuf *mh_prevpkt; /* Flags aren't used in the output queue */ + int mh_flags; /* Misc flags */ + + int mh_size; /* Size of data */ + struct socket *mh_so; + + caddr_t mh_data; /* Location of data */ + int mh_len; /* Amount of data in this mbuf */ +}; + +/* + * How much room is in the mbuf, from m_data to the end of the mbuf + */ +#define M_ROOM(m) ((m->m_flags & M_EXT)? \ + (((m)->m_ext + (m)->m_size) - (m)->m_data) \ + : \ + (((m)->m_dat + (m)->m_size) - (m)->m_data)) + +/* + * How much free room there is + */ +#define M_FREEROOM(m) (M_ROOM(m) - (m)->m_len) +#define M_TRAILINGSPACE M_FREEROOM + +struct mbuf { + struct m_hdr m_hdr; + Slirp *slirp; + union M_dat { + char m_dat_[1]; /* ANSI don't like 0 sized arrays */ + char *m_ext_; + } M_dat; +}; + +#define m_next m_hdr.mh_next +#define m_prev m_hdr.mh_prev +#define m_nextpkt m_hdr.mh_nextpkt +#define m_prevpkt m_hdr.mh_prevpkt +#define m_flags m_hdr.mh_flags +#define m_len m_hdr.mh_len +#define m_data m_hdr.mh_data +#define m_size m_hdr.mh_size +#define m_dat M_dat.m_dat_ +#define m_ext M_dat.m_ext_ +#define m_so m_hdr.mh_so + +#define ifq_prev m_prev +#define ifq_next m_next +#define ifs_prev m_prevpkt +#define ifs_next m_nextpkt +#define ifq_so m_so + +#define M_EXT 0x01 /* m_ext points to more (malloced) data */ +#define M_FREELIST 0x02 /* mbuf is on free list */ +#define M_USEDLIST 0x04 /* XXX mbuf is on used list (for dtom()) */ +#define M_DOFREE 0x08 /* when m_free is called on the mbuf, free() + * it rather than putting it on the free list */ + +void m_init(Slirp *); +struct mbuf * m_get(Slirp *); +void m_free(struct mbuf *); +void m_cat(register struct mbuf *, register struct mbuf *); +void m_inc(struct mbuf *, int); +void m_adj(struct mbuf *, int); +int m_copy(struct mbuf *, struct mbuf *, int, int); +struct mbuf * dtom(Slirp *, void *); + +#endif diff --git a/slirp/misc.c b/slirp/misc.c new file mode 100644 index 0000000..7a79cdb --- /dev/null +++ b/slirp/misc.c @@ -0,0 +1,403 @@ +/* + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" + +#ifdef DEBUG +int slirp_debug = DBG_CALL|DBG_MISC|DBG_ERROR; +#endif + +struct quehead { + struct quehead *qh_link; + struct quehead *qh_rlink; +}; + +inline void +insque(void *a, void *b) +{ + register struct quehead *element = (struct quehead *) a; + register struct quehead *head = (struct quehead *) b; + element->qh_link = head->qh_link; + head->qh_link = (struct quehead *)element; + element->qh_rlink = (struct quehead *)head; + ((struct quehead *)(element->qh_link))->qh_rlink + = (struct quehead *)element; +} + +inline void +remque(void *a) +{ + register struct quehead *element = (struct quehead *) a; + ((struct quehead *)(element->qh_link))->qh_rlink = element->qh_rlink; + ((struct quehead *)(element->qh_rlink))->qh_link = element->qh_link; + element->qh_rlink = NULL; +} + +int add_exec(struct ex_list **ex_ptr, int do_pty, char *exec, + struct in_addr addr, int port) +{ + struct ex_list *tmp_ptr; + + /* First, check if the port is "bound" */ + for (tmp_ptr = *ex_ptr; tmp_ptr; tmp_ptr = tmp_ptr->ex_next) { + if (port == tmp_ptr->ex_fport && + addr.s_addr == tmp_ptr->ex_addr.s_addr) + return -1; + } + + tmp_ptr = *ex_ptr; + *ex_ptr = (struct ex_list *)malloc(sizeof(struct ex_list)); + (*ex_ptr)->ex_fport = port; + (*ex_ptr)->ex_addr = addr; + (*ex_ptr)->ex_pty = do_pty; + (*ex_ptr)->ex_exec = (do_pty == 3) ? exec : strdup(exec); + (*ex_ptr)->ex_next = tmp_ptr; + return 0; +} + +#ifndef HAVE_STRERROR + +/* + * For systems with no strerror + */ + +extern int sys_nerr; +extern char *sys_errlist[]; + +char * +strerror(error) + int error; +{ + if (error < sys_nerr) + return sys_errlist[error]; + else + return "Unknown error."; +} + +#endif + +int os_socket(int domain, int type, int protocol) +{ + return socket(domain, type, protocol); +} + +uint32_t os_get_time_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000 + + (ts.tv_nsec / 1000000); +} + + +#if 1 + +int +fork_exec(struct socket *so, const char *ex, int do_pty) +{ + /* not implemented */ + return 0; +} + +#else + +/* + * XXX This is ugly + * We create and bind a socket, then fork off to another + * process, which connects to this socket, after which we + * exec the wanted program. If something (strange) happens, + * the accept() call could block us forever. + * + * do_pty = 0 Fork/exec inetd style + * do_pty = 1 Fork/exec using slirp.telnetd + * do_ptr = 2 Fork/exec using pty + */ +int +fork_exec(struct socket *so, const char *ex, int do_pty) +{ + int s; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int opt; + int master = -1; + const char *argv[256]; + /* don't want to clobber the original */ + char *bptr; + const char *curarg; + int c, i, ret; + + DEBUG_CALL("fork_exec"); + DEBUG_ARG("so = %lx", (long)so); + DEBUG_ARG("ex = %lx", (long)ex); + DEBUG_ARG("do_pty = %lx", (long)do_pty); + + if (do_pty == 2) { + return 0; + } else { + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = INADDR_ANY; + + if ((s = os_socket(AF_INET, SOCK_STREAM, 0)) < 0 || + bind(s, (struct sockaddr *)&addr, addrlen) < 0 || + listen(s, 1) < 0) { + lprint("Error: inet socket: %s\n", strerror(errno)); + closesocket(s); + + return 0; + } + } + + switch(fork()) { + case -1: + lprint("Error: fork failed: %s\n", strerror(errno)); + close(s); + if (do_pty == 2) + close(master); + return 0; + + case 0: + /* Set the DISPLAY */ + if (do_pty == 2) { + (void) close(master); +#ifdef TIOCSCTTY /* XXXXX */ + (void) setsid(); + ioctl(s, TIOCSCTTY, (char *)NULL); +#endif + } else { + getsockname(s, (struct sockaddr *)&addr, &addrlen); + close(s); + /* + * Connect to the socket + * XXX If any of these fail, we're in trouble! + */ + s = os_socket(AF_INET, SOCK_STREAM, 0); + addr.sin_addr = loopback_addr; + do { + ret = connect(s, (struct sockaddr *)&addr, addrlen); + } while (ret < 0 && errno == EINTR); + } + + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + for (s = getdtablesize() - 1; s >= 3; s--) + close(s); + + i = 0; + bptr = qemu_strdup(ex); /* No need to free() this */ + if (do_pty == 1) { + /* Setup "slirp.telnetd -x" */ + argv[i++] = "slirp.telnetd"; + argv[i++] = "-x"; + argv[i++] = bptr; + } else + do { + /* Change the string into argv[] */ + curarg = bptr; + while (*bptr != ' ' && *bptr != (char)0) + bptr++; + c = *bptr; + *bptr++ = (char)0; + argv[i++] = strdup(curarg); + } while (c); + + argv[i] = NULL; + execvp(argv[0], (char **)argv); + + /* Ooops, failed, let's tell the user why */ + fprintf(stderr, "Error: execvp of %s failed: %s\n", + argv[0], strerror(errno)); + close(0); close(1); close(2); /* XXX */ + exit(1); + + default: + if (do_pty == 2) { + close(s); + so->s = master; + } else { + /* + * XXX this could block us... + * XXX Should set a timer here, and if accept() doesn't + * return after X seconds, declare it a failure + * The only reason this will block forever is if socket() + * of connect() fail in the child process + */ + do { + so->s = accept(s, (struct sockaddr *)&addr, &addrlen); + } while (so->s < 0 && errno == EINTR); + closesocket(s); + opt = 1; + setsockopt(so->s,SOL_SOCKET,SO_REUSEADDR,(char *)&opt,sizeof(int)); + opt = 1; + setsockopt(so->s,SOL_SOCKET,SO_OOBINLINE,(char *)&opt,sizeof(int)); + } + fd_nonblock(so->s); + + /* Append the telnet options now */ + if (so->so_m != NULL && do_pty == 1) { + sbappend(so, so->so_m); + so->so_m = NULL; + } + + return 1; + } +} +#endif + +#ifndef HAVE_STRDUP +char * +strdup(str) + const char *str; +{ + char *bptr; + + bptr = (char *)malloc(strlen(str)+1); + strcpy(bptr, str); + + return bptr; +} +#endif + +void lprint(const char *format, ...) +{ + va_list args; + + va_start(args, format); + vprintf(format, args); + va_end(args); +} + +/* + * Set fd blocking and non-blocking + */ + +void +fd_nonblock(int fd) +{ +#ifdef FIONBIO +#ifdef _WIN32 + unsigned long opt = 1; +#else + int opt = 1; +#endif + + ioctlsocket(fd, FIONBIO, &opt); +#else + int opt; + + opt = fcntl(fd, F_GETFL, 0); + opt |= O_NONBLOCK; + fcntl(fd, F_SETFL, opt); +#endif +} + +void +fd_block(int fd) +{ +#ifdef FIONBIO +#ifdef _WIN32 + unsigned long opt = 0; +#else + int opt = 0; +#endif + + ioctlsocket(fd, FIONBIO, &opt); +#else + int opt; + + opt = fcntl(fd, F_GETFL, 0); + opt &= ~O_NONBLOCK; + fcntl(fd, F_SETFL, opt); +#endif +} + +#if 0 +void slirp_connection_info(Slirp *slirp, Monitor *mon) +{ + const char * const tcpstates[] = { + [TCPS_CLOSED] = "CLOSED", + [TCPS_LISTEN] = "LISTEN", + [TCPS_SYN_SENT] = "SYN_SENT", + [TCPS_SYN_RECEIVED] = "SYN_RCVD", + [TCPS_ESTABLISHED] = "ESTABLISHED", + [TCPS_CLOSE_WAIT] = "CLOSE_WAIT", + [TCPS_FIN_WAIT_1] = "FIN_WAIT_1", + [TCPS_CLOSING] = "CLOSING", + [TCPS_LAST_ACK] = "LAST_ACK", + [TCPS_FIN_WAIT_2] = "FIN_WAIT_2", + [TCPS_TIME_WAIT] = "TIME_WAIT", + }; + struct in_addr dst_addr; + struct sockaddr_in src; + socklen_t src_len; + uint16_t dst_port; + struct socket *so; + const char *state; + char buf[20]; + int n; + + monitor_printf(mon, " Protocol[State] FD Source Address Port " + "Dest. Address Port RecvQ SendQ\n"); + + for (so = slirp->tcb.so_next; so != &slirp->tcb; so = so->so_next) { + if (so->so_state & SS_HOSTFWD) { + state = "HOST_FORWARD"; + } else if (so->so_tcpcb) { + state = tcpstates[so->so_tcpcb->t_state]; + } else { + state = "NONE"; + } + if (so->so_state & (SS_HOSTFWD | SS_INCOMING)) { + src_len = sizeof(src); + getsockname(so->s, (struct sockaddr *)&src, &src_len); + dst_addr = so->so_laddr; + dst_port = so->so_lport; + } else { + src.sin_addr = so->so_laddr; + src.sin_port = so->so_lport; + dst_addr = so->so_faddr; + dst_port = so->so_fport; + } + n = snprintf(buf, sizeof(buf), " TCP[%s]", state); + memset(&buf[n], ' ', 19 - n); + buf[19] = 0; + monitor_printf(mon, "%s %3d %15s %5d ", buf, so->s, + src.sin_addr.s_addr ? inet_ntoa(src.sin_addr) : "*", + ntohs(src.sin_port)); + monitor_printf(mon, "%15s %5d %5d %5d\n", + inet_ntoa(dst_addr), ntohs(dst_port), + so->so_rcv.sb_cc, so->so_snd.sb_cc); + } + + for (so = slirp->udb.so_next; so != &slirp->udb; so = so->so_next) { + if (so->so_state & SS_HOSTFWD) { + n = snprintf(buf, sizeof(buf), " UDP[HOST_FORWARD]"); + src_len = sizeof(src); + getsockname(so->s, (struct sockaddr *)&src, &src_len); + dst_addr = so->so_laddr; + dst_port = so->so_lport; + } else { + n = snprintf(buf, sizeof(buf), " UDP[%d sec]", + (so->so_expire - curtime) / 1000); + src.sin_addr = so->so_laddr; + src.sin_port = so->so_lport; + dst_addr = so->so_faddr; + dst_port = so->so_fport; + } + memset(&buf[n], ' ', 19 - n); + buf[19] = 0; + monitor_printf(mon, "%s %3d %15s %5d ", buf, so->s, + src.sin_addr.s_addr ? inet_ntoa(src.sin_addr) : "*", + ntohs(src.sin_port)); + monitor_printf(mon, "%15s %5d %5d %5d\n", + inet_ntoa(dst_addr), ntohs(dst_port), + so->so_rcv.sb_cc, so->so_snd.sb_cc); + } +} +#endif diff --git a/slirp/misc.h b/slirp/misc.h new file mode 100644 index 0000000..5c307c9 --- /dev/null +++ b/slirp/misc.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#ifndef _MISC_H_ +#define _MISC_H_ + +struct ex_list { + int ex_pty; /* Do we want a pty? */ + struct in_addr ex_addr; /* Server address */ + int ex_fport; /* Port to telnet to */ + const char *ex_exec; /* Command line of what to exec */ + struct ex_list *ex_next; +}; + +#ifndef HAVE_STRDUP +char *strdup(const char *); +#endif + +void do_wait(int); + +#define EMU_NONE 0x0 + +/* TCP emulations */ +#define EMU_CTL 0x1 +#define EMU_FTP 0x2 +#define EMU_KSH 0x3 +#define EMU_IRC 0x4 +#define EMU_REALAUDIO 0x5 +#define EMU_RLOGIN 0x6 +#define EMU_IDENT 0x7 +#define EMU_RSH 0x8 + +#define EMU_NOCONNECT 0x10 /* Don't connect */ + +struct tos_t { + uint16_t lport; + uint16_t fport; + uint8_t tos; + uint8_t emu; +}; + +struct emu_t { + uint16_t lport; + uint16_t fport; + uint8_t tos; + uint8_t emu; + struct emu_t *next; +}; + +extern int x_port, x_server, x_display; + +int show_x(char *, struct socket *); +void redir_x(uint32_t, int, int, int); +void slirp_insque(void *, void *); +void slirp_remque(void *); +int add_exec(struct ex_list **, int, char *, struct in_addr, int); +int slirp_openpty(int *, int *); +int fork_exec(struct socket *so, const char *ex, int do_pty); +void snooze_hup(int); +void snooze(void); +void relay(int); +void add_emu(char *); +void fd_nonblock(int); +void fd_block(int); +int rsh_exec(struct socket *, struct socket *, char *, char *, char *); +int os_socket(int domain, int type, int protocol); +uint32_t os_get_time_ms(void); + +#endif diff --git a/slirp/sbuf.c b/slirp/sbuf.c new file mode 100644 index 0000000..ac20f21 --- /dev/null +++ b/slirp/sbuf.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" + +static void sbappendsb(struct sbuf *sb, struct mbuf *m); + +void +sbfree(struct sbuf *sb) +{ + free(sb->sb_data); +} + +void +sbdrop(struct sbuf *sb, int num) +{ + /* + * We can only drop how much we have + * This should never succeed + */ + if(num > sb->sb_cc) + num = sb->sb_cc; + sb->sb_cc -= num; + sb->sb_rptr += num; + if(sb->sb_rptr >= sb->sb_data + sb->sb_datalen) + sb->sb_rptr -= sb->sb_datalen; + +} + +void +sbreserve(struct sbuf *sb, int size) +{ + if (sb->sb_data) { + /* Already alloced, realloc if necessary */ + if (sb->sb_datalen != size) { + sb->sb_wptr = sb->sb_rptr = sb->sb_data = (char *)realloc(sb->sb_data, size); + sb->sb_cc = 0; + if (sb->sb_wptr) + sb->sb_datalen = size; + else + sb->sb_datalen = 0; + } + } else { + sb->sb_wptr = sb->sb_rptr = sb->sb_data = (char *)malloc(size); + sb->sb_cc = 0; + if (sb->sb_wptr) + sb->sb_datalen = size; + else + sb->sb_datalen = 0; + } +} + +/* + * Try and write() to the socket, whatever doesn't get written + * append to the buffer... for a host with a fast net connection, + * this prevents an unnecessary copy of the data + * (the socket is non-blocking, so we won't hang) + */ +void +sbappend(struct socket *so, struct mbuf *m) +{ + int ret = 0; + + DEBUG_CALL("sbappend"); + DEBUG_ARG("so = %lx", (long)so); + DEBUG_ARG("m = %lx", (long)m); + DEBUG_ARG("m->m_len = %d", m->m_len); + + /* Shouldn't happen, but... e.g. foreign host closes connection */ + if (m->m_len <= 0) { + m_free(m); + return; + } + + /* + * If there is urgent data, call sosendoob + * if not all was sent, sowrite will take care of the rest + * (The rest of this function is just an optimisation) + */ + if (so->so_urgc) { + sbappendsb(&so->so_rcv, m); + m_free(m); + sosendoob(so); + return; + } + + /* + * We only write if there's nothing in the buffer, + * ottherwise it'll arrive out of order, and hence corrupt + */ + if (!so->so_rcv.sb_cc) + ret = slirp_send(so, m->m_data, m->m_len, 0); + + if (ret <= 0) { + /* + * Nothing was written + * It's possible that the socket has closed, but + * we don't need to check because if it has closed, + * it will be detected in the normal way by soread() + */ + sbappendsb(&so->so_rcv, m); + } else if (ret != m->m_len) { + /* + * Something was written, but not everything.. + * sbappendsb the rest + */ + m->m_len -= ret; + m->m_data += ret; + sbappendsb(&so->so_rcv, m); + } /* else */ + /* Whatever happened, we free the mbuf */ + m_free(m); +} + +/* + * Copy the data from m into sb + * The caller is responsible to make sure there's enough room + */ +static void +sbappendsb(struct sbuf *sb, struct mbuf *m) +{ + int len, n, nn; + + len = m->m_len; + + if (sb->sb_wptr < sb->sb_rptr) { + n = sb->sb_rptr - sb->sb_wptr; + if (n > len) n = len; + memcpy(sb->sb_wptr, m->m_data, n); + } else { + /* Do the right edge first */ + n = sb->sb_data + sb->sb_datalen - sb->sb_wptr; + if (n > len) n = len; + memcpy(sb->sb_wptr, m->m_data, n); + len -= n; + if (len) { + /* Now the left edge */ + nn = sb->sb_rptr - sb->sb_data; + if (nn > len) nn = len; + memcpy(sb->sb_data,m->m_data+n,nn); + n += nn; + } + } + + sb->sb_cc += n; + sb->sb_wptr += n; + if (sb->sb_wptr >= sb->sb_data + sb->sb_datalen) + sb->sb_wptr -= sb->sb_datalen; +} + +/* + * Copy data from sbuf to a normal, straight buffer + * Don't update the sbuf rptr, this will be + * done in sbdrop when the data is acked + */ +void +sbcopy(struct sbuf *sb, int off, int len, char *to) +{ + char *from; + + from = sb->sb_rptr + off; + if (from >= sb->sb_data + sb->sb_datalen) + from -= sb->sb_datalen; + + if (from < sb->sb_wptr) { + if (len > sb->sb_cc) len = sb->sb_cc; + memcpy(to,from,len); + } else { + /* re-use off */ + off = (sb->sb_data + sb->sb_datalen) - from; + if (off > len) off = len; + memcpy(to,from,off); + len -= off; + if (len) + memcpy(to+off,sb->sb_data,len); + } +} diff --git a/slirp/sbuf.h b/slirp/sbuf.h new file mode 100644 index 0000000..4f22e7c --- /dev/null +++ b/slirp/sbuf.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#ifndef _SBUF_H_ +#define _SBUF_H_ + +#define sbflush(sb) sbdrop((sb),(sb)->sb_cc) +#define sbspace(sb) ((sb)->sb_datalen - (sb)->sb_cc) + +struct sbuf { + u_int sb_cc; /* actual chars in buffer */ + u_int sb_datalen; /* Length of data */ + char *sb_wptr; /* write pointer. points to where the next + * bytes should be written in the sbuf */ + char *sb_rptr; /* read pointer. points to where the next + * byte should be read from the sbuf */ + char *sb_data; /* Actual data */ +}; + +void sbfree(struct sbuf *); +void sbdrop(struct sbuf *, int); +void sbreserve(struct sbuf *, int); +void sbappend(struct socket *, struct mbuf *); +void sbcopy(struct sbuf *, int, int, char *); + +#endif diff --git a/slirp/slirp.c b/slirp/slirp.c new file mode 100644 index 0000000..db8c117 --- /dev/null +++ b/slirp/slirp.c @@ -0,0 +1,828 @@ +/* + * libslirp glue + * + * Copyright (c) 2004-2008 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "slirp.h" + +/* host loopback address */ +struct in_addr loopback_addr; + +/* emulated hosts use the MAC addr 52:55:IP:IP:IP:IP */ +static const uint8_t special_ethaddr[6] = { + 0x52, 0x55, 0x00, 0x00, 0x00, 0x00 +}; + +static const uint8_t zero_ethaddr[6] = { 0, 0, 0, 0, 0, 0 }; + +/* XXX: suppress those select globals */ +fd_set *global_readfds, *global_writefds, *global_xfds; + +u_int curtime; +static u_int time_fasttimo, last_slowtimo; +static int do_slowtimo; + +static struct in_addr dns_addr; +static u_int dns_addr_time; + +#ifdef _WIN32 + +int get_dns_addr(struct in_addr *pdns_addr) +{ + FIXED_INFO *FixedInfo=NULL; + ULONG BufLen; + DWORD ret; + IP_ADDR_STRING *pIPAddr; + struct in_addr tmp_addr; + + if (dns_addr.s_addr != 0 && (curtime - dns_addr_time) < 1000) { + *pdns_addr = dns_addr; + return 0; + } + + FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, sizeof(FIXED_INFO)); + BufLen = sizeof(FIXED_INFO); + + if (ERROR_BUFFER_OVERFLOW == GetNetworkParams(FixedInfo, &BufLen)) { + if (FixedInfo) { + GlobalFree(FixedInfo); + FixedInfo = NULL; + } + FixedInfo = GlobalAlloc(GPTR, BufLen); + } + + if ((ret = GetNetworkParams(FixedInfo, &BufLen)) != ERROR_SUCCESS) { + printf("GetNetworkParams failed. ret = %08x\n", (u_int)ret ); + if (FixedInfo) { + GlobalFree(FixedInfo); + FixedInfo = NULL; + } + return -1; + } + + pIPAddr = &(FixedInfo->DnsServerList); + inet_aton(pIPAddr->IpAddress.String, &tmp_addr); + *pdns_addr = tmp_addr; + dns_addr = tmp_addr; + dns_addr_time = curtime; + if (FixedInfo) { + GlobalFree(FixedInfo); + FixedInfo = NULL; + } + return 0; +} + +static void winsock_cleanup(void) +{ + WSACleanup(); +} + +#else + +static struct stat dns_addr_stat; + +int get_dns_addr(struct in_addr *pdns_addr) +{ + char buff[512]; + char buff2[257]; + FILE *f; + int found = 0; + struct in_addr tmp_addr; + + if (dns_addr.s_addr != 0) { + struct stat old_stat; + if ((curtime - dns_addr_time) < 1000) { + *pdns_addr = dns_addr; + return 0; + } + old_stat = dns_addr_stat; + if (stat("/etc/resolv.conf", &dns_addr_stat) != 0) + return -1; + if ((dns_addr_stat.st_dev == old_stat.st_dev) + && (dns_addr_stat.st_ino == old_stat.st_ino) + && (dns_addr_stat.st_size == old_stat.st_size) + && (dns_addr_stat.st_mtime == old_stat.st_mtime)) { + *pdns_addr = dns_addr; + return 0; + } + } + + f = fopen("/etc/resolv.conf", "r"); + if (!f) + return -1; + +#ifdef DEBUG + lprint("IP address of your DNS(s): "); +#endif + while (fgets(buff, 512, f) != NULL) { + if (sscanf(buff, "nameserver%*[ \t]%256s", buff2) == 1) { + if (!inet_aton(buff2, &tmp_addr)) + continue; + /* If it's the first one, set it to dns_addr */ + if (!found) { + *pdns_addr = tmp_addr; + dns_addr = tmp_addr; + dns_addr_time = curtime; + } +#ifdef DEBUG + else + lprint(", "); +#endif + if (++found > 3) { +#ifdef DEBUG + lprint("(more)"); +#endif + break; + } +#ifdef DEBUG + else + lprint("%s", inet_ntoa(tmp_addr)); +#endif + } + } + fclose(f); + if (!found) + return -1; + return 0; +} + +#endif + +static void slirp_init_once(void) +{ + static int initialized; +#ifdef _WIN32 + WSADATA Data; +#endif + + if (initialized) { + return; + } + initialized = 1; + +#ifdef _WIN32 + WSAStartup(MAKEWORD(2,0), &Data); + atexit(winsock_cleanup); +#endif + + loopback_addr.s_addr = htonl(INADDR_LOOPBACK); +} + +Slirp *slirp_init(int restricted, struct in_addr vnetwork, + struct in_addr vnetmask, struct in_addr vhost, + const char *vhostname, const char *tftp_path, + const char *bootfile, struct in_addr vdhcp_start, + struct in_addr vnameserver, void *opaque) +{ + Slirp *slirp = mallocz(sizeof(Slirp)); + + slirp_init_once(); + + slirp->restricted = restricted; + + if_init(slirp); + ip_init(slirp); + + /* Initialise mbufs *after* setting the MTU */ + m_init(slirp); + + slirp->vnetwork_addr = vnetwork; + slirp->vnetwork_mask = vnetmask; + slirp->vhost_addr = vhost; + if (vhostname) { + pstrcpy(slirp->client_hostname, sizeof(slirp->client_hostname), + vhostname); + } + if (tftp_path) { + slirp->tftp_prefix = strdup(tftp_path); + } + if (bootfile) { + slirp->bootp_filename = strdup(bootfile); + } + slirp->vdhcp_startaddr = vdhcp_start; + slirp->vnameserver_addr = vnameserver; + + slirp->opaque = opaque; + + return slirp; +} + +void slirp_cleanup(Slirp *slirp) +{ + free(slirp->tftp_prefix); + free(slirp->bootp_filename); + free(slirp); +} + +#define CONN_CANFSEND(so) (((so)->so_state & (SS_FCANTSENDMORE|SS_ISFCONNECTED)) == SS_ISFCONNECTED) +#define CONN_CANFRCV(so) (((so)->so_state & (SS_FCANTRCVMORE|SS_ISFCONNECTED)) == SS_ISFCONNECTED) +#define UPD_NFDS(x) if (nfds < (x)) nfds = (x) + +void slirp_select_fill(Slirp *slirp, int *pnfds, + fd_set *readfds, fd_set *writefds, fd_set *xfds) +{ + struct socket *so, *so_next; + int nfds; + + /* fail safe */ + global_readfds = NULL; + global_writefds = NULL; + global_xfds = NULL; + + nfds = *pnfds; + /* + * First, TCP sockets + */ + do_slowtimo = 0; + + { + /* + * *_slowtimo needs calling if there are IP fragments + * in the fragment queue, or there are TCP connections active + */ + do_slowtimo |= ((slirp->tcb.so_next != &slirp->tcb) || + (&slirp->ipq.ip_link != slirp->ipq.ip_link.next)); + + for (so = slirp->tcb.so_next; so != &slirp->tcb; + so = so_next) { + so_next = so->so_next; + + /* + * See if we need a tcp_fasttimo + */ + if (time_fasttimo == 0 && so->so_tcpcb->t_flags & TF_DELACK) + time_fasttimo = curtime; /* Flag when we want a fasttimo */ + + /* + * NOFDREF can include still connecting to local-host, + * newly socreated() sockets etc. Don't want to select these. + */ + if (so->so_state & SS_NOFDREF || so->s == -1) + continue; + + /* + * Set for reading sockets which are accepting + */ + if (so->so_state & SS_FACCEPTCONN) { + FD_SET(so->s, readfds); + UPD_NFDS(so->s); + continue; + } + + /* + * Set for writing sockets which are connecting + */ + if (so->so_state & SS_ISFCONNECTING) { + FD_SET(so->s, writefds); + UPD_NFDS(so->s); + continue; + } + + /* + * Set for writing if we are connected, can send more, and + * we have something to send + */ + if (CONN_CANFSEND(so) && so->so_rcv.sb_cc) { + FD_SET(so->s, writefds); + UPD_NFDS(so->s); + } + + /* + * Set for reading (and urgent data) if we are connected, can + * receive more, and we have room for it XXX /2 ? + */ + if (CONN_CANFRCV(so) && (so->so_snd.sb_cc < (so->so_snd.sb_datalen/2))) { + FD_SET(so->s, readfds); + FD_SET(so->s, xfds); + UPD_NFDS(so->s); + } + } + + /* + * UDP sockets + */ + for (so = slirp->udb.so_next; so != &slirp->udb; + so = so_next) { + so_next = so->so_next; + + /* + * See if it's timed out + */ + if (so->so_expire) { + if (so->so_expire <= curtime) { + udp_detach(so); + continue; + } else + do_slowtimo = 1; /* Let socket expire */ + } + + /* + * When UDP packets are received from over the + * link, they're sendto()'d straight away, so + * no need for setting for writing + * Limit the number of packets queued by this session + * to 4. Note that even though we try and limit this + * to 4 packets, the session could have more queued + * if the packets needed to be fragmented + * (XXX <= 4 ?) + */ + if ((so->so_state & SS_ISFCONNECTED) && so->so_queued <= 4) { + FD_SET(so->s, readfds); + UPD_NFDS(so->s); + } + } + } + + *pnfds = nfds; +} + +void slirp_select_poll(Slirp *slirp, + fd_set *readfds, fd_set *writefds, fd_set *xfds, + int select_error) +{ + struct socket *so, *so_next; + int ret; + + global_readfds = readfds; + global_writefds = writefds; + global_xfds = xfds; + + curtime = os_get_time_ms(); + + { + /* + * See if anything has timed out + */ + if (time_fasttimo && ((curtime - time_fasttimo) >= 2)) { + tcp_fasttimo(slirp); + time_fasttimo = 0; + } + if (do_slowtimo && ((curtime - last_slowtimo) >= 499)) { + ip_slowtimo(slirp); + tcp_slowtimo(slirp); + last_slowtimo = curtime; + } + + /* + * Check sockets + */ + if (!select_error) { + /* + * Check TCP sockets + */ + for (so = slirp->tcb.so_next; so != &slirp->tcb; + so = so_next) { + so_next = so->so_next; + + /* + * FD_ISSET is meaningless on these sockets + * (and they can crash the program) + */ + if (so->so_state & SS_NOFDREF || so->s == -1) + continue; + + /* + * Check for URG data + * This will soread as well, so no need to + * test for readfds below if this succeeds + */ + if (FD_ISSET(so->s, xfds)) + sorecvoob(so); + /* + * Check sockets for reading + */ + else if (FD_ISSET(so->s, readfds)) { + /* + * Check for incoming connections + */ + if (so->so_state & SS_FACCEPTCONN) { + tcp_connect(so); + continue; + } /* else */ + ret = soread(so); + + /* Output it if we read something */ + if (ret > 0) + tcp_output(sototcpcb(so)); + } + + /* + * Check sockets for writing + */ + if (FD_ISSET(so->s, writefds)) { + /* + * Check for non-blocking, still-connecting sockets + */ + if (so->so_state & SS_ISFCONNECTING) { + /* Connected */ + so->so_state &= ~SS_ISFCONNECTING; + + ret = send(so->s, (const void *) &ret, 0, 0); + if (ret < 0) { + /* XXXXX Must fix, zero bytes is a NOP */ + if (errno == EAGAIN || errno == EWOULDBLOCK || + errno == EINPROGRESS || errno == ENOTCONN) + continue; + + /* else failed */ + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; + } + /* else so->so_state &= ~SS_ISFCONNECTING; */ + + /* + * Continue tcp_input + */ + tcp_input((struct mbuf *)NULL, sizeof(struct ip), so); + /* continue; */ + } else + ret = sowrite(so); + /* + * XXXXX If we wrote something (a lot), there + * could be a need for a window update. + * In the worst case, the remote will send + * a window probe to get things going again + */ + } + + /* + * Probe a still-connecting, non-blocking socket + * to check if it's still alive + */ +#ifdef PROBE_CONN + if (so->so_state & SS_ISFCONNECTING) { + ret = recv(so->s, (char *)&ret, 0,0); + + if (ret < 0) { + /* XXX */ + if (errno == EAGAIN || errno == EWOULDBLOCK || + errno == EINPROGRESS || errno == ENOTCONN) + continue; /* Still connecting, continue */ + + /* else failed */ + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; + + /* tcp_input will take care of it */ + } else { + ret = send(so->s, &ret, 0,0); + if (ret < 0) { + /* XXX */ + if (errno == EAGAIN || errno == EWOULDBLOCK || + errno == EINPROGRESS || errno == ENOTCONN) + continue; + /* else failed */ + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; + } else + so->so_state &= ~SS_ISFCONNECTING; + + } + tcp_input((struct mbuf *)NULL, sizeof(struct ip),so); + } /* SS_ISFCONNECTING */ +#endif + } + + /* + * Now UDP sockets. + * Incoming packets are sent straight away, they're not buffered. + * Incoming UDP data isn't buffered either. + */ + for (so = slirp->udb.so_next; so != &slirp->udb; + so = so_next) { + so_next = so->so_next; + + if (so->s != -1 && FD_ISSET(so->s, readfds)) { + sorecvfrom(so); + } + } + } + + /* + * See if we can start outputting + */ + if (slirp->if_queued) { + if_start(slirp); + } + } + + /* clear global file descriptor sets. + * these reside on the stack in vl.c + * so they're unusable if we're not in + * slirp_select_fill or slirp_select_poll. + */ + global_readfds = NULL; + global_writefds = NULL; + global_xfds = NULL; +} + +#define ETH_ALEN 6 +#define ETH_HLEN 14 + +#define ETH_P_IP 0x0800 /* Internet Protocol packet */ +#define ETH_P_ARP 0x0806 /* Address Resolution packet */ + +#define ARPOP_REQUEST 1 /* ARP request */ +#define ARPOP_REPLY 2 /* ARP reply */ + +struct ethhdr +{ + unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ + unsigned char h_source[ETH_ALEN]; /* source ether addr */ + unsigned short h_proto; /* packet type ID field */ +}; + +struct arphdr +{ + unsigned short ar_hrd; /* format of hardware address */ + unsigned short ar_pro; /* format of protocol address */ + unsigned char ar_hln; /* length of hardware address */ + unsigned char ar_pln; /* length of protocol address */ + unsigned short ar_op; /* ARP opcode (command) */ + + /* + * Ethernet looks like this : This bit is variable sized however... + */ + unsigned char ar_sha[ETH_ALEN]; /* sender hardware address */ + uint32_t ar_sip; /* sender IP address */ + unsigned char ar_tha[ETH_ALEN]; /* target hardware address */ + uint32_t ar_tip ; /* target IP address */ +} __attribute__((packed)); + +static void arp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len) +{ + struct ethhdr *eh = (struct ethhdr *)pkt; + struct arphdr *ah = (struct arphdr *)(pkt + ETH_HLEN); + uint8_t arp_reply[max(ETH_HLEN + sizeof(struct arphdr), 64)]; + struct ethhdr *reh = (struct ethhdr *)arp_reply; + struct arphdr *rah = (struct arphdr *)(arp_reply + ETH_HLEN); + int ar_op; + struct ex_list *ex_ptr; + + ar_op = ntohs(ah->ar_op); + switch(ar_op) { + case ARPOP_REQUEST: + if ((ah->ar_tip & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + if (ah->ar_tip == slirp->vnameserver_addr.s_addr || + ah->ar_tip == slirp->vhost_addr.s_addr) + goto arp_ok; + for (ex_ptr = slirp->exec_list; ex_ptr; ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->ex_addr.s_addr == ah->ar_tip) + goto arp_ok; + } + return; + arp_ok: + memset(arp_reply, 0, sizeof(arp_reply)); + /* XXX: make an ARP request to have the client address */ + memcpy(slirp->client_ethaddr, eh->h_source, ETH_ALEN); + + /* ARP request for alias/dns mac address */ + memcpy(reh->h_dest, pkt + ETH_ALEN, ETH_ALEN); + memcpy(reh->h_source, special_ethaddr, ETH_ALEN - 4); + memcpy(&reh->h_source[2], &ah->ar_tip, 4); + reh->h_proto = htons(ETH_P_ARP); + + rah->ar_hrd = htons(1); + rah->ar_pro = htons(ETH_P_IP); + rah->ar_hln = ETH_ALEN; + rah->ar_pln = 4; + rah->ar_op = htons(ARPOP_REPLY); + memcpy(rah->ar_sha, reh->h_source, ETH_ALEN); + rah->ar_sip = ah->ar_tip; + memcpy(rah->ar_tha, ah->ar_sha, ETH_ALEN); + rah->ar_tip = ah->ar_sip; + slirp_output(slirp->opaque, arp_reply, sizeof(arp_reply)); + } + break; + case ARPOP_REPLY: + /* reply to request of client mac address ? */ + if (!memcmp(slirp->client_ethaddr, zero_ethaddr, ETH_ALEN) && + ah->ar_sip == slirp->client_ipaddr.s_addr) { + memcpy(slirp->client_ethaddr, ah->ar_sha, ETH_ALEN); + } + break; + default: + break; + } +} + +void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len) +{ + struct mbuf *m; + int proto; + + if (pkt_len < ETH_HLEN) + return; + + proto = ntohs(*(uint16_t *)(pkt + 12)); + switch(proto) { + case ETH_P_ARP: + arp_input(slirp, pkt, pkt_len); + break; + case ETH_P_IP: + m = m_get(slirp); + if (!m) + return; + /* Note: we add to align the IP header */ + if (M_FREEROOM(m) < pkt_len + 2) { + m_inc(m, pkt_len + 2); + } + m->m_len = pkt_len + 2; + memcpy(m->m_data + 2, pkt, pkt_len); + + m->m_data += 2 + ETH_HLEN; + m->m_len -= 2 + ETH_HLEN; + + ip_input(m); + break; + default: + break; + } +} + +/* output the IP packet to the ethernet device */ +void if_encap(Slirp *slirp, const uint8_t *ip_data, int ip_data_len) +{ + uint8_t buf[1600]; + struct ethhdr *eh = (struct ethhdr *)buf; + + if (ip_data_len + ETH_HLEN > sizeof(buf)) + return; + + if (!memcmp(slirp->client_ethaddr, zero_ethaddr, ETH_ALEN)) { + uint8_t arp_req[ETH_HLEN + sizeof(struct arphdr)]; + struct ethhdr *reh = (struct ethhdr *)arp_req; + struct arphdr *rah = (struct arphdr *)(arp_req + ETH_HLEN); + const struct ip *iph = (const struct ip *)ip_data; + + /* If the client addr is not known, there is no point in + sending the packet to it. Normally the sender should have + done an ARP request to get its MAC address. Here we do it + in place of sending the packet and we hope that the sender + will retry sending its packet. */ + memset(reh->h_dest, 0xff, ETH_ALEN); + memcpy(reh->h_source, special_ethaddr, ETH_ALEN - 4); + memcpy(&reh->h_source[2], &slirp->vhost_addr, 4); + reh->h_proto = htons(ETH_P_ARP); + rah->ar_hrd = htons(1); + rah->ar_pro = htons(ETH_P_IP); + rah->ar_hln = ETH_ALEN; + rah->ar_pln = 4; + rah->ar_op = htons(ARPOP_REQUEST); + /* source hw addr */ + memcpy(rah->ar_sha, special_ethaddr, ETH_ALEN - 4); + memcpy(&rah->ar_sha[2], &slirp->vhost_addr, 4); + /* source IP */ + rah->ar_sip = slirp->vhost_addr.s_addr; + /* target hw addr (none) */ + memset(rah->ar_tha, 0, ETH_ALEN); + /* target IP */ + rah->ar_tip = iph->ip_dst.s_addr; + slirp->client_ipaddr = iph->ip_dst; + slirp_output(slirp->opaque, arp_req, sizeof(arp_req)); + } else { + memcpy(eh->h_dest, slirp->client_ethaddr, ETH_ALEN); + memcpy(eh->h_source, special_ethaddr, ETH_ALEN - 4); + /* XXX: not correct */ + memcpy(&eh->h_source[2], &slirp->vhost_addr, 4); + eh->h_proto = htons(ETH_P_IP); + memcpy(buf + sizeof(struct ethhdr), ip_data, ip_data_len); + slirp_output(slirp->opaque, buf, ip_data_len + ETH_HLEN); + } +} + +/* Drop host forwarding rule, return 0 if found. */ +int slirp_remove_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port) +{ + struct socket *so; + struct socket *head = (is_udp ? &slirp->udb : &slirp->tcb); + struct sockaddr_in addr; + int port = htons(host_port); + socklen_t addr_len; + + for (so = head->so_next; so != head; so = so->so_next) { + addr_len = sizeof(addr); + if ((so->so_state & SS_HOSTFWD) && + getsockname(so->s, (struct sockaddr *)&addr, &addr_len) == 0 && + addr.sin_addr.s_addr == host_addr.s_addr && + addr.sin_port == port) { + close(so->s); + sofree(so); + return 0; + } + } + + return -1; +} + +int slirp_add_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port, struct in_addr guest_addr, int guest_port) +{ + if (!guest_addr.s_addr) { + guest_addr = slirp->vdhcp_startaddr; + } + if (is_udp) { + if (!udp_listen(slirp, host_addr.s_addr, htons(host_port), + guest_addr.s_addr, htons(guest_port), SS_HOSTFWD)) + return -1; + } else { + if (!tcp_listen(slirp, host_addr.s_addr, htons(host_port), + guest_addr.s_addr, htons(guest_port), SS_HOSTFWD)) + return -1; + } + return 0; +} + +int slirp_add_exec(Slirp *slirp, int do_pty, const void *args, + struct in_addr *guest_addr, int guest_port) +{ + if (!guest_addr->s_addr) { + guest_addr->s_addr = slirp->vnetwork_addr.s_addr | + (htonl(0x0204) & ~slirp->vnetwork_mask.s_addr); + } + if ((guest_addr->s_addr & slirp->vnetwork_mask.s_addr) != + slirp->vnetwork_addr.s_addr || + guest_addr->s_addr == slirp->vhost_addr.s_addr || + guest_addr->s_addr == slirp->vnameserver_addr.s_addr) { + return -1; + } + return add_exec(&slirp->exec_list, do_pty, (char *)args, *guest_addr, + htons(guest_port)); +} + +ssize_t slirp_send(struct socket *so, const void *buf, size_t len, int flags) +{ +#if 0 + if (so->s == -1 && so->extra) { + qemu_chr_write(so->extra, buf, len); + return len; + } +#endif + return send(so->s, buf, len, flags); +} + +static struct socket * +slirp_find_ctl_socket(Slirp *slirp, struct in_addr guest_addr, int guest_port) +{ + struct socket *so; + + for (so = slirp->tcb.so_next; so != &slirp->tcb; so = so->so_next) { + if (so->so_faddr.s_addr == guest_addr.s_addr && + htons(so->so_fport) == guest_port) { + return so; + } + } + return NULL; +} + +size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr guest_addr, + int guest_port) +{ + struct iovec iov[2]; + struct socket *so; + + so = slirp_find_ctl_socket(slirp, guest_addr, guest_port); + + if (!so || so->so_state & SS_NOFDREF) + return 0; + + if (!CONN_CANFRCV(so) || so->so_snd.sb_cc >= (so->so_snd.sb_datalen/2)) + return 0; + + return sopreprbuf(so, iov, NULL); +} + +void slirp_socket_recv(Slirp *slirp, struct in_addr guest_addr, int guest_port, + const uint8_t *buf, int size) +{ + int ret; + struct socket *so = slirp_find_ctl_socket(slirp, guest_addr, guest_port); + + if (!so) + return; + + ret = soreadbuf(so, (const char *)buf, size); + + if (ret > 0) + tcp_output(sototcpcb(so)); +} diff --git a/slirp/slirp.h b/slirp/slirp.h new file mode 100644 index 0000000..e2fc655 --- /dev/null +++ b/slirp/slirp.h @@ -0,0 +1,313 @@ +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#include +#include "../cutils.h" +#include "slirp_config.h" + +#ifdef _WIN32 +# include + +typedef char *caddr_t; + +# include +# include +# include +# include +# include + +# define EWOULDBLOCK WSAEWOULDBLOCK +# define EINPROGRESS WSAEINPROGRESS +# define ENOTCONN WSAENOTCONN +# define EHOSTUNREACH WSAEHOSTUNREACH +# define ENETUNREACH WSAENETUNREACH +# define ECONNREFUSED WSAECONNREFUSED +#else +# define ioctlsocket ioctl +# define closesocket(s) close(s) +# if !defined(__HAIKU__) +# define O_BINARY 0 +# endif +#endif + +#include +#ifdef HAVE_SYS_BITYPES_H +# include +#endif + +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifdef HAVE_STDLIB_H +# include +#endif + +#include +#include + +#ifndef HAVE_MEMMOVE +#define memmove(x, y, z) bcopy(y, x, z) +#endif + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#ifdef HAVE_STRING_H +# include +#else +# include +#endif + +#ifndef _WIN32 +#include +#endif + +#ifndef _WIN32 +#include +#include +#endif + +/* Systems lacking strdup() definition in . */ +#if defined(ultrix) +char *strdup(const char *); +#endif + +/* Systems lacking malloc() definition in . */ +#if defined(ultrix) || defined(hcx) +void *malloc(size_t arg); +void free(void *ptr); +#endif + +#ifndef HAVE_INET_ATON +int inet_aton(const char *cp, struct in_addr *ia); +#endif + +#include +#ifndef NO_UNIX_SOCKETS +#include +#endif +#include +#ifdef HAVE_SYS_SIGNAL_H +# include +#endif +#ifndef _WIN32 +#include +#endif + +#if defined(HAVE_SYS_IOCTL_H) +# include +#endif + +#ifdef HAVE_SYS_SELECT_H +# include +#endif + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +#ifdef HAVE_SYS_FILIO_H +# include +#endif + +#ifdef USE_PPP +#include +#endif + +#ifdef __STDC__ +#include +#else +#include +#endif + +#include + +/* Avoid conflicting with the libc insque() and remque(), which + have different prototypes. */ +#define insque slirp_insque +#define remque slirp_remque + +#ifdef HAVE_SYS_STROPTS_H +#include +#endif + +#include "debug.h" + +#include "libslirp.h" +#include "ip.h" +#include "tcp.h" +#include "tcp_timer.h" +#include "tcp_var.h" +#include "tcpip.h" +#include "udp.h" +#include "mbuf.h" +#include "sbuf.h" +#include "socket.h" +#include "if.h" +#include "main.h" +#include "misc.h" +#ifdef USE_PPP +#include "ppp/pppd.h" +#include "ppp/ppp.h" +#endif + +#include "bootp.h" +#include "tftp.h" + +struct Slirp { + /* virtual network configuration */ + struct in_addr vnetwork_addr; + struct in_addr vnetwork_mask; + struct in_addr vhost_addr; + struct in_addr vdhcp_startaddr; + struct in_addr vnameserver_addr; + + /* ARP cache for the guest IP addresses (XXX: allow many entries) */ + uint8_t client_ethaddr[6]; + + struct in_addr client_ipaddr; + char client_hostname[33]; + + int restricted; + struct timeval tt; + struct ex_list *exec_list; + + /* mbuf states */ + struct mbuf m_freelist, m_usedlist; + int mbuf_alloced; + + /* if states */ + int if_queued; /* number of packets queued so far */ + struct mbuf if_fastq; /* fast queue (for interactive data) */ + struct mbuf if_batchq; /* queue for non-interactive data */ + struct mbuf *next_m; /* pointer to next mbuf to output */ + + /* ip states */ + struct ipq ipq; /* ip reass. queue */ + uint16_t ip_id; /* ip packet ctr, for ids */ + + /* bootp/dhcp states */ + BOOTPClient bootp_clients[NB_BOOTP_CLIENTS]; + char *bootp_filename; + + /* tcp states */ + struct socket tcb; + struct socket *tcp_last_so; + tcp_seq tcp_iss; /* tcp initial send seq # */ + uint32_t tcp_now; /* for RFC 1323 timestamps */ + + /* udp states */ + struct socket udb; + struct socket *udp_last_so; + + /* tftp states */ + char *tftp_prefix; + struct tftp_session tftp_sessions[TFTP_SESSIONS_MAX]; + + void *opaque; +}; + +extern Slirp *slirp_instance; + +#ifndef NULL +#define NULL (void *)0 +#endif + +#ifndef FULL_BOLT +void if_start(Slirp *); +#else +void if_start(struct ttys *); +#endif + +#ifndef HAVE_STRERROR + char *strerror(int error); +#endif + +#ifndef HAVE_INDEX + char *index(const char *, int); +#endif + +#ifndef HAVE_GETHOSTID + long gethostid(void); +#endif + +void lprint(const char *, ...) __attribute__((format(printf, 1, 2))); + +#ifndef _WIN32 +#include +#endif + +#define DEFAULT_BAUD 115200 + +#define SO_OPTIONS DO_KEEPALIVE +#define TCP_MAXIDLE (TCPTV_KEEPCNT * TCPTV_KEEPINTVL) + +/* cksum.c */ +int cksum(struct mbuf *m, int len); + +/* if.c */ +void if_init(Slirp *); +void if_output(struct socket *, struct mbuf *); + +/* ip_input.c */ +void ip_init(Slirp *); +void ip_input(struct mbuf *); +void ip_slowtimo(Slirp *); +void ip_stripoptions(register struct mbuf *, struct mbuf *); + +/* ip_output.c */ +int ip_output(struct socket *, struct mbuf *); + +/* tcp_input.c */ +void tcp_input(register struct mbuf *, int, struct socket *); +int tcp_mss(register struct tcpcb *, u_int); + +/* tcp_output.c */ +int tcp_output(register struct tcpcb *); +void tcp_setpersist(register struct tcpcb *); + +/* tcp_subr.c */ +void tcp_init(Slirp *); +void tcp_template(struct tcpcb *); +void tcp_respond(struct tcpcb *, register struct tcpiphdr *, register struct mbuf *, tcp_seq, tcp_seq, int); +struct tcpcb * tcp_newtcpcb(struct socket *); +struct tcpcb * tcp_close(register struct tcpcb *); +void tcp_sockclosed(struct tcpcb *); +int tcp_fconnect(struct socket *); +void tcp_connect(struct socket *); +int tcp_attach(struct socket *); +uint8_t tcp_tos(struct socket *); +int tcp_emu(struct socket *, struct mbuf *); +int tcp_ctl(struct socket *); +struct tcpcb *tcp_drop(struct tcpcb *tp, int err); + +#ifdef USE_PPP +#define MIN_MRU MINMRU +#define MAX_MRU MAXMRU +#else +#define MIN_MRU 128 +#define MAX_MRU 16384 +#endif + +#ifndef _WIN32 +#define min(x,y) ((x) < (y) ? (x) : (y)) +#define max(x,y) ((x) > (y) ? (x) : (y)) +#endif + +#ifdef _WIN32 +#undef errno +#define errno (WSAGetLastError()) +#endif + +#endif diff --git a/slirp/slirp_config.h b/slirp/slirp_config.h new file mode 100644 index 0000000..18db45c --- /dev/null +++ b/slirp/slirp_config.h @@ -0,0 +1,188 @@ +/* + * User definable configuration options + */ + +/* Define if you want the connection to be probed */ +/* XXX Not working yet, so ignore this for now */ +#undef PROBE_CONN + +/* Define to 1 if you want KEEPALIVE timers */ +#define DO_KEEPALIVE 0 + +/* Define to MAX interfaces you expect to use at once */ +/* MAX_INTERFACES determines the max. TOTAL number of interfaces (SLIP and PPP) */ +/* MAX_PPP_INTERFACES determines max. number of PPP interfaces */ +#define MAX_INTERFACES 1 +#define MAX_PPP_INTERFACES 1 + +/* Define if you want slirp's socket in /tmp */ +/* XXXXXX Do this in ./configure */ +#undef USE_TMPSOCKET + +/* Define if you want slirp to use cfsetXspeed() on the terminal */ +#undef DO_CFSETSPEED + +/* Define this if you want slirp to write to the tty as fast as it can */ +/* This should only be set if you are using load-balancing, slirp does a */ +/* pretty good job on single modems already, and seting this will make */ +/* interactive sessions less responsive */ +/* XXXXX Talk about having fast modem as unit 0 */ +#undef FULL_BOLT + +/* + * Define if you want slirp to use less CPU + * You will notice a small lag in interactive sessions, but it's not that bad + * Things like Netscape/ftp/etc. are completely unaffected + * This is mainly for sysadmins who have many slirp users + */ +#undef USE_LOWCPU + +/* Define this if your compiler doesn't like prototypes */ +#ifndef __STDC__ +#define NO_PROTOTYPES +#endif + +/*********************************************************/ +/* + * Autoconf defined configuration options + * You shouldn't need to touch any of these + */ + +/* Ignore this */ +#undef DUMMY_PPP + +/* Define if you have unistd.h */ +#define HAVE_UNISTD_H + +/* Define if you have stdlib.h */ +#define HAVE_STDLIB_H + +/* Define if you have sys/ioctl.h */ +#undef HAVE_SYS_IOCTL_H +#ifndef _WIN32 +#define HAVE_SYS_IOCTL_H +#endif + +/* Define if you have sys/filio.h */ +#undef HAVE_SYS_FILIO_H +#ifdef __APPLE__ +#define HAVE_SYS_FILIO_H +#endif + +/* Define if you have strerror */ +#define HAVE_STRERROR + +/* Define if you have strdup() */ +#define HAVE_STRDUP + +/* Define according to how time.h should be included */ +#define TIME_WITH_SYS_TIME 0 +#undef HAVE_SYS_TIME_H + +/* Define if you have sys/bitypes.h */ +#undef HAVE_SYS_BITYPES_H + +/* Define if the machine is big endian */ +//#undef HOST_WORDS_BIGENDIAN + +/* Define if you have readv */ +#undef HAVE_READV + +/* Define if iovec needs to be declared */ +#undef DECLARE_IOVEC +#ifdef _WIN32 +#define DECLARE_IOVEC +#endif + +/* Define if you have a POSIX.1 sys/wait.h */ +#undef HAVE_SYS_WAIT_H + +/* Define if you have sys/select.h */ +#undef HAVE_SYS_SELECT_H +#ifndef _WIN32 +#define HAVE_SYS_SELECT_H +#endif + +/* Define if you have strings.h */ +#define HAVE_STRING_H + +/* Define if you have arpa/inet.h */ +#undef HAVE_ARPA_INET_H +#ifndef _WIN32 +#define HAVE_ARPA_INET_H +#endif + +/* Define if you have sys/signal.h */ +#undef HAVE_SYS_SIGNAL_H + +/* Define if you have sys/stropts.h */ +#undef HAVE_SYS_STROPTS_H + +/* Define to whatever your compiler thinks inline should be */ +//#define inline inline + +/* Define to whatever your compiler thinks const should be */ +//#define const const + +/* Define if your compiler doesn't like prototypes */ +#undef NO_PROTOTYPES + +/* Define to sizeof(char) */ +#define SIZEOF_CHAR 1 + +/* Define to sizeof(short) */ +#define SIZEOF_SHORT 2 + +/* Define to sizeof(int) */ +#define SIZEOF_INT 4 + +/* Define to sizeof(char *) */ +#define SIZEOF_CHAR_P (HOST_LONG_BITS / 8) + +/* Define if you have random() */ +#undef HAVE_RANDOM + +/* Define if you have srandom() */ +#undef HAVE_SRANDOM + +/* Define if you have inet_aton */ +#undef HAVE_INET_ATON +#ifndef _WIN32 +#define HAVE_INET_ATON +#endif + +/* Define if you have setenv */ +#undef HAVE_SETENV + +/* Define if you have index() */ +#define HAVE_INDEX + +/* Define if you have bcmp() */ +#undef HAVE_BCMP + +/* Define if you have drand48 */ +#undef HAVE_DRAND48 + +/* Define if you have memmove */ +#define HAVE_MEMMOVE + +/* Define if you have gethostid */ +#define HAVE_GETHOSTID + +/* Define if you DON'T have unix-domain sockets */ +#undef NO_UNIX_SOCKETS +#ifdef _WIN32 +#define NO_UNIX_SOCKETS +#endif + +/* Define if you have revoke() */ +#undef HAVE_REVOKE + +/* Define if you have the sysv method of opening pty's (/dev/ptmx, etc.) */ +#undef HAVE_GRANTPT + +/* Define if you have fchmod */ +#undef HAVE_FCHMOD + +/* Define if you have */ +#undef HAVE_SYS_TYPES32_H diff --git a/slirp/socket.c b/slirp/socket.c new file mode 100644 index 0000000..1ba8ae1 --- /dev/null +++ b/slirp/socket.c @@ -0,0 +1,722 @@ +/* + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" +#include "ip_icmp.h" + +static void sofcantrcvmore(struct socket *so); +static void sofcantsendmore(struct socket *so); + +struct socket * +solookup(struct socket *head, struct in_addr laddr, u_int lport, + struct in_addr faddr, u_int fport) +{ + struct socket *so; + + for (so = head->so_next; so != head; so = so->so_next) { + if (so->so_lport == lport && + so->so_laddr.s_addr == laddr.s_addr && + so->so_faddr.s_addr == faddr.s_addr && + so->so_fport == fport) + break; + } + + if (so == head) + return (struct socket *)NULL; + return so; + +} + +/* + * Create a new socket, initialise the fields + * It is the responsibility of the caller to + * insque() it into the correct linked-list + */ +struct socket * +socreate(Slirp *slirp) +{ + struct socket *so; + + so = (struct socket *)malloc(sizeof(struct socket)); + if(so) { + memset(so, 0, sizeof(struct socket)); + so->so_state = SS_NOFDREF; + so->s = -1; + so->slirp = slirp; + } + return(so); +} + +/* + * remque and free a socket, clobber cache + */ +void +sofree(struct socket *so) +{ + Slirp *slirp = so->slirp; + + if (so->so_emu==EMU_RSH && so->extra) { + sofree(so->extra); + so->extra=NULL; + } + if (so == slirp->tcp_last_so) { + slirp->tcp_last_so = &slirp->tcb; + } else if (so == slirp->udp_last_so) { + slirp->udp_last_so = &slirp->udb; + } + m_free(so->so_m); + + if(so->so_next && so->so_prev) + remque(so); /* crashes if so is not in a queue */ + + free(so); +} + +size_t sopreprbuf(struct socket *so, struct iovec *iov, int *np) +{ + int n, lss, total; + struct sbuf *sb = &so->so_snd; + int len = sb->sb_datalen - sb->sb_cc; + int mss = so->so_tcpcb->t_maxseg; + + DEBUG_CALL("sopreprbuf"); + DEBUG_ARG("so = %lx", (long )so); + + if (len <= 0) + return 0; + + iov[0].iov_base = sb->sb_wptr; + iov[1].iov_base = NULL; + iov[1].iov_len = 0; + if (sb->sb_wptr < sb->sb_rptr) { + iov[0].iov_len = sb->sb_rptr - sb->sb_wptr; + /* Should never succeed, but... */ + if (iov[0].iov_len > len) + iov[0].iov_len = len; + if (iov[0].iov_len > mss) + iov[0].iov_len -= iov[0].iov_len%mss; + n = 1; + } else { + iov[0].iov_len = (sb->sb_data + sb->sb_datalen) - sb->sb_wptr; + /* Should never succeed, but... */ + if (iov[0].iov_len > len) iov[0].iov_len = len; + len -= iov[0].iov_len; + if (len) { + iov[1].iov_base = sb->sb_data; + iov[1].iov_len = sb->sb_rptr - sb->sb_data; + if(iov[1].iov_len > len) + iov[1].iov_len = len; + total = iov[0].iov_len + iov[1].iov_len; + if (total > mss) { + lss = total%mss; + if (iov[1].iov_len > lss) { + iov[1].iov_len -= lss; + n = 2; + } else { + lss -= iov[1].iov_len; + iov[0].iov_len -= lss; + n = 1; + } + } else + n = 2; + } else { + if (iov[0].iov_len > mss) + iov[0].iov_len -= iov[0].iov_len%mss; + n = 1; + } + } + if (np) + *np = n; + + return iov[0].iov_len + (n - 1) * iov[1].iov_len; +} + +/* + * Read from so's socket into sb_snd, updating all relevant sbuf fields + * NOTE: This will only be called if it is select()ed for reading, so + * a read() of 0 (or less) means it's disconnected + */ +int +soread(struct socket *so) +{ + int n, nn; + struct sbuf *sb = &so->so_snd; + struct iovec iov[2]; + + DEBUG_CALL("soread"); + DEBUG_ARG("so = %lx", (long )so); + + /* + * No need to check if there's enough room to read. + * soread wouldn't have been called if there weren't + */ + sopreprbuf(so, iov, &n); + +#ifdef HAVE_READV + nn = readv(so->s, (struct iovec *)iov, n); + DEBUG_MISC((dfd, " ... read nn = %d bytes\n", nn)); +#else + nn = recv(so->s, iov[0].iov_base, iov[0].iov_len,0); +#endif + if (nn <= 0) { + if (nn < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + else { + DEBUG_MISC((dfd, " --- soread() disconnected, nn = %d, errno = %d-%s\n", nn, errno,strerror(errno))); + sofcantrcvmore(so); + tcp_sockclosed(sototcpcb(so)); + return -1; + } + } + +#ifndef HAVE_READV + /* + * If there was no error, try and read the second time round + * We read again if n = 2 (ie, there's another part of the buffer) + * and we read as much as we could in the first read + * We don't test for <= 0 this time, because there legitimately + * might not be any more data (since the socket is non-blocking), + * a close will be detected on next iteration. + * A return of -1 wont (shouldn't) happen, since it didn't happen above + */ + if (n == 2 && nn == iov[0].iov_len) { + int ret; + ret = recv(so->s, iov[1].iov_base, iov[1].iov_len,0); + if (ret > 0) + nn += ret; + } + + DEBUG_MISC((dfd, " ... read nn = %d bytes\n", nn)); +#endif + + /* Update fields */ + sb->sb_cc += nn; + sb->sb_wptr += nn; + if (sb->sb_wptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_wptr -= sb->sb_datalen; + return nn; +} + +int soreadbuf(struct socket *so, const char *buf, int size) +{ + int n, nn, copy = size; + struct sbuf *sb = &so->so_snd; + struct iovec iov[2]; + + DEBUG_CALL("soreadbuf"); + DEBUG_ARG("so = %lx", (long )so); + + /* + * No need to check if there's enough room to read. + * soread wouldn't have been called if there weren't + */ + if (sopreprbuf(so, iov, &n) < size) + goto err; + + nn = min(iov[0].iov_len, copy); + memcpy(iov[0].iov_base, buf, nn); + + copy -= nn; + buf += nn; + + if (copy == 0) + goto done; + + memcpy(iov[1].iov_base, buf, copy); + +done: + /* Update fields */ + sb->sb_cc += size; + sb->sb_wptr += size; + if (sb->sb_wptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_wptr -= sb->sb_datalen; + return size; +err: + + sofcantrcvmore(so); + tcp_sockclosed(sototcpcb(so)); + fprintf(stderr, "soreadbuf buffer to small"); + return -1; +} + +/* + * Get urgent data + * + * When the socket is created, we set it SO_OOBINLINE, + * so when OOB data arrives, we soread() it and everything + * in the send buffer is sent as urgent data + */ +void +sorecvoob(struct socket *so) +{ + struct tcpcb *tp = sototcpcb(so); + + DEBUG_CALL("sorecvoob"); + DEBUG_ARG("so = %lx", (long)so); + + /* + * We take a guess at how much urgent data has arrived. + * In most situations, when urgent data arrives, the next + * read() should get all the urgent data. This guess will + * be wrong however if more data arrives just after the + * urgent data, or the read() doesn't return all the + * urgent data. + */ + soread(so); + tp->snd_up = tp->snd_una + so->so_snd.sb_cc; + tp->t_force = 1; + tcp_output(tp); + tp->t_force = 0; +} + +/* + * Send urgent data + * There's a lot duplicated code here, but... + */ +int +sosendoob(struct socket *so) +{ + struct sbuf *sb = &so->so_rcv; + char buff[2048]; /* XXX Shouldn't be sending more oob data than this */ + + int n, len; + + DEBUG_CALL("sosendoob"); + DEBUG_ARG("so = %lx", (long)so); + DEBUG_ARG("sb->sb_cc = %d", sb->sb_cc); + + if (so->so_urgc > 2048) + so->so_urgc = 2048; /* XXXX */ + + if (sb->sb_rptr < sb->sb_wptr) { + /* We can send it directly */ + n = slirp_send(so, sb->sb_rptr, so->so_urgc, (MSG_OOB)); /* |MSG_DONTWAIT)); */ + so->so_urgc -= n; + + DEBUG_MISC((dfd, " --- sent %d bytes urgent data, %d urgent bytes left\n", n, so->so_urgc)); + } else { + /* + * Since there's no sendv or sendtov like writev, + * we must copy all data to a linear buffer then + * send it all + */ + len = (sb->sb_data + sb->sb_datalen) - sb->sb_rptr; + if (len > so->so_urgc) len = so->so_urgc; + memcpy(buff, sb->sb_rptr, len); + so->so_urgc -= len; + if (so->so_urgc) { + n = sb->sb_wptr - sb->sb_data; + if (n > so->so_urgc) n = so->so_urgc; + memcpy((buff + len), sb->sb_data, n); + so->so_urgc -= n; + len += n; + } + n = slirp_send(so, buff, len, (MSG_OOB)); /* |MSG_DONTWAIT)); */ +#ifdef DEBUG + if (n != len) + DEBUG_ERROR((dfd, "Didn't send all data urgently XXXXX\n")); +#endif + DEBUG_MISC((dfd, " ---2 sent %d bytes urgent data, %d urgent bytes left\n", n, so->so_urgc)); + } + + sb->sb_cc -= n; + sb->sb_rptr += n; + if (sb->sb_rptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_rptr -= sb->sb_datalen; + + return n; +} + +/* + * Write data from so_rcv to so's socket, + * updating all sbuf field as necessary + */ +int +sowrite(struct socket *so) +{ + int n,nn; + struct sbuf *sb = &so->so_rcv; + int len = sb->sb_cc; + struct iovec iov[2]; + + DEBUG_CALL("sowrite"); + DEBUG_ARG("so = %lx", (long)so); + + if (so->so_urgc) { + sosendoob(so); + if (sb->sb_cc == 0) + return 0; + } + + /* + * No need to check if there's something to write, + * sowrite wouldn't have been called otherwise + */ + + iov[0].iov_base = sb->sb_rptr; + iov[1].iov_base = NULL; + iov[1].iov_len = 0; + if (sb->sb_rptr < sb->sb_wptr) { + iov[0].iov_len = sb->sb_wptr - sb->sb_rptr; + /* Should never succeed, but... */ + if (iov[0].iov_len > len) iov[0].iov_len = len; + n = 1; + } else { + iov[0].iov_len = (sb->sb_data + sb->sb_datalen) - sb->sb_rptr; + if (iov[0].iov_len > len) iov[0].iov_len = len; + len -= iov[0].iov_len; + if (len) { + iov[1].iov_base = sb->sb_data; + iov[1].iov_len = sb->sb_wptr - sb->sb_data; + if (iov[1].iov_len > len) iov[1].iov_len = len; + n = 2; + } else + n = 1; + } + /* Check if there's urgent data to send, and if so, send it */ + +#ifdef HAVE_READV + nn = writev(so->s, (const struct iovec *)iov, n); + + DEBUG_MISC((dfd, " ... wrote nn = %d bytes\n", nn)); +#else + nn = slirp_send(so, iov[0].iov_base, iov[0].iov_len,0); +#endif + /* This should never happen, but people tell me it does *shrug* */ + if (nn < 0 && (errno == EAGAIN || errno == EINTR)) + return 0; + + if (nn <= 0) { + DEBUG_MISC((dfd, " --- sowrite disconnected, so->so_state = %x, errno = %d\n", + so->so_state, errno)); + sofcantsendmore(so); + tcp_sockclosed(sototcpcb(so)); + return -1; + } + +#ifndef HAVE_READV + if (n == 2 && nn == iov[0].iov_len) { + int ret; + ret = slirp_send(so, iov[1].iov_base, iov[1].iov_len,0); + if (ret > 0) + nn += ret; + } + DEBUG_MISC((dfd, " ... wrote nn = %d bytes\n", nn)); +#endif + + /* Update sbuf */ + sb->sb_cc -= nn; + sb->sb_rptr += nn; + if (sb->sb_rptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_rptr -= sb->sb_datalen; + + /* + * If in DRAIN mode, and there's no more data, set + * it CANTSENDMORE + */ + if ((so->so_state & SS_FWDRAIN) && sb->sb_cc == 0) + sofcantsendmore(so); + + return nn; +} + +/* + * recvfrom() a UDP socket + */ +void +sorecvfrom(struct socket *so) +{ + struct sockaddr_in addr; + socklen_t addrlen = sizeof(struct sockaddr_in); + + DEBUG_CALL("sorecvfrom"); + DEBUG_ARG("so = %lx", (long)so); + + if (so->so_type == IPPROTO_ICMP) { /* This is a "ping" reply */ + char buff[256]; + int len; + + len = recvfrom(so->s, buff, 256, 0, + (struct sockaddr *)&addr, &addrlen); + /* XXX Check if reply is "correct"? */ + + if(len == -1 || len == 0) { + u_char code=ICMP_UNREACH_PORT; + + if(errno == EHOSTUNREACH) code=ICMP_UNREACH_HOST; + else if(errno == ENETUNREACH) code=ICMP_UNREACH_NET; + + DEBUG_MISC((dfd," udp icmp rx errno = %d-%s\n", + errno,strerror(errno))); + icmp_error(so->so_m, ICMP_UNREACH,code, 0,strerror(errno)); + } else { + icmp_reflect(so->so_m); + so->so_m = NULL; /* Don't m_free() it again! */ + } + /* No need for this socket anymore, udp_detach it */ + udp_detach(so); + } else { /* A "normal" UDP packet */ + struct mbuf *m; + int len; +#ifdef _WIN32 + unsigned long n; +#else + int n; +#endif + + m = m_get(so->slirp); + if (!m) { + return; + } + m->m_data += IF_MAXLINKHDR; + + /* + * XXX Shouldn't FIONREAD packets destined for port 53, + * but I don't know the max packet size for DNS lookups + */ + len = M_FREEROOM(m); + /* if (so->so_fport != htons(53)) { */ + ioctlsocket(so->s, FIONREAD, &n); + + if (n > len) { + n = (m->m_data - m->m_dat) + m->m_len + n + 1; + m_inc(m, n); + len = M_FREEROOM(m); + } + /* } */ + + m->m_len = recvfrom(so->s, m->m_data, len, 0, + (struct sockaddr *)&addr, &addrlen); + DEBUG_MISC((dfd, " did recvfrom %d, errno = %d-%s\n", + m->m_len, errno,strerror(errno))); + if(m->m_len<0) { + u_char code=ICMP_UNREACH_PORT; + + if(errno == EHOSTUNREACH) code=ICMP_UNREACH_HOST; + else if(errno == ENETUNREACH) code=ICMP_UNREACH_NET; + + DEBUG_MISC((dfd," rx error, tx icmp ICMP_UNREACH:%i\n", code)); + icmp_error(so->so_m, ICMP_UNREACH,code, 0,strerror(errno)); + m_free(m); + } else { + /* + * Hack: domain name lookup will be used the most for UDP, + * and since they'll only be used once there's no need + * for the 4 minute (or whatever) timeout... So we time them + * out much quicker (10 seconds for now...) + */ + if (so->so_expire) { + if (so->so_fport == htons(53)) + so->so_expire = curtime + SO_EXPIREFAST; + else + so->so_expire = curtime + SO_EXPIRE; + } + + /* + * If this packet was destined for CTL_ADDR, + * make it look like that's where it came from, done by udp_output + */ + udp_output(so, m, &addr); + } /* rx error */ + } /* if ping packet */ +} + +/* + * sendto() a socket + */ +int +sosendto(struct socket *so, struct mbuf *m) +{ + Slirp *slirp = so->slirp; + int ret; + struct sockaddr_in addr; + + DEBUG_CALL("sosendto"); + DEBUG_ARG("so = %lx", (long)so); + DEBUG_ARG("m = %lx", (long)m); + + addr.sin_family = AF_INET; + if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + /* It's an alias */ + if (so->so_faddr.s_addr == slirp->vnameserver_addr.s_addr) { + if (get_dns_addr(&addr.sin_addr) < 0) + addr.sin_addr = loopback_addr; + } else { + addr.sin_addr = loopback_addr; + } + } else + addr.sin_addr = so->so_faddr; + addr.sin_port = so->so_fport; + + DEBUG_MISC((dfd, " sendto()ing, addr.sin_port=%d, addr.sin_addr.s_addr=%.16s\n", ntohs(addr.sin_port), inet_ntoa(addr.sin_addr))); + + /* Don't care what port we get */ + ret = sendto(so->s, m->m_data, m->m_len, 0, + (struct sockaddr *)&addr, sizeof (struct sockaddr)); + if (ret < 0) + return -1; + + /* + * Kill the socket if there's no reply in 4 minutes, + * but only if it's an expirable socket + */ + if (so->so_expire) + so->so_expire = curtime + SO_EXPIRE; + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_ISFCONNECTED; /* So that it gets select()ed */ + return 0; +} + +/* + * Listen for incoming TCP connections + */ +struct socket * +tcp_listen(Slirp *slirp, uint32_t haddr, u_int hport, uint32_t laddr, + u_int lport, int flags) +{ + struct sockaddr_in addr; + struct socket *so; + int s, opt = 1; + socklen_t addrlen = sizeof(addr); + memset(&addr, 0, addrlen); + + DEBUG_CALL("tcp_listen"); + DEBUG_ARG("haddr = %x", haddr); + DEBUG_ARG("hport = %d", hport); + DEBUG_ARG("laddr = %x", laddr); + DEBUG_ARG("lport = %d", lport); + DEBUG_ARG("flags = %x", flags); + + so = socreate(slirp); + if (!so) { + return NULL; + } + + /* Don't tcp_attach... we don't need so_snd nor so_rcv */ + if ((so->so_tcpcb = tcp_newtcpcb(so)) == NULL) { + free(so); + return NULL; + } + insque(so, &slirp->tcb); + + /* + * SS_FACCEPTONCE sockets must time out. + */ + if (flags & SS_FACCEPTONCE) + so->so_tcpcb->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT*2; + + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= (SS_FACCEPTCONN | flags); + so->so_lport = lport; /* Kept in network format */ + so->so_laddr.s_addr = laddr; /* Ditto */ + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = haddr; + addr.sin_port = hport; + + if (((s = os_socket(AF_INET,SOCK_STREAM,0)) < 0) || + (setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char *)&opt,sizeof(int)) < 0) || + (bind(s,(struct sockaddr *)&addr, sizeof(addr)) < 0) || + (listen(s,1) < 0)) { + int tmperrno = errno; /* Don't clobber the real reason we failed */ + + close(s); + sofree(so); + /* Restore the real errno */ +#ifdef _WIN32 + WSASetLastError(tmperrno); +#else + errno = tmperrno; +#endif + return NULL; + } + setsockopt(s,SOL_SOCKET,SO_OOBINLINE,(char *)&opt,sizeof(int)); + + getsockname(s,(struct sockaddr *)&addr,&addrlen); + so->so_fport = addr.sin_port; + if (addr.sin_addr.s_addr == 0 || addr.sin_addr.s_addr == loopback_addr.s_addr) + so->so_faddr = slirp->vhost_addr; + else + so->so_faddr = addr.sin_addr; + + so->s = s; + return so; +} + +/* + * Various session state calls + * XXX Should be #define's + * The socket state stuff needs work, these often get call 2 or 3 + * times each when only 1 was needed + */ +void +soisfconnecting(struct socket *so) +{ + so->so_state &= ~(SS_NOFDREF|SS_ISFCONNECTED|SS_FCANTRCVMORE| + SS_FCANTSENDMORE|SS_FWDRAIN); + so->so_state |= SS_ISFCONNECTING; /* Clobber other states */ +} + +void +soisfconnected(struct socket *so) +{ + so->so_state &= ~(SS_ISFCONNECTING|SS_FWDRAIN|SS_NOFDREF); + so->so_state |= SS_ISFCONNECTED; /* Clobber other states */ +} + +static void +sofcantrcvmore(struct socket *so) +{ + if ((so->so_state & SS_NOFDREF) == 0) { + shutdown(so->s,0); + if(global_writefds) { + FD_CLR(so->s,global_writefds); + } + } + so->so_state &= ~(SS_ISFCONNECTING); + if (so->so_state & SS_FCANTSENDMORE) { + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; /* Don't select it */ + } else { + so->so_state |= SS_FCANTRCVMORE; + } +} + +static void +sofcantsendmore(struct socket *so) +{ + if ((so->so_state & SS_NOFDREF) == 0) { + shutdown(so->s,1); /* send FIN to fhost */ + if (global_readfds) { + FD_CLR(so->s,global_readfds); + } + if (global_xfds) { + FD_CLR(so->s,global_xfds); + } + } + so->so_state &= ~(SS_ISFCONNECTING); + if (so->so_state & SS_FCANTRCVMORE) { + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; /* as above */ + } else { + so->so_state |= SS_FCANTSENDMORE; + } +} + +/* + * Set write drain mode + * Set CANTSENDMORE once all data has been write()n + */ +void +sofwdrain(struct socket *so) +{ + if (so->so_rcv.sb_cc) + so->so_state |= SS_FWDRAIN; + else + sofcantsendmore(so); +} diff --git a/slirp/socket.h b/slirp/socket.h new file mode 100644 index 0000000..857b0da --- /dev/null +++ b/slirp/socket.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#ifndef _SLIRP_SOCKET_H_ +#define _SLIRP_SOCKET_H_ + +#define SO_EXPIRE 240000 +#define SO_EXPIREFAST 10000 + +/* + * Our socket structure + */ + +struct socket { + struct socket *so_next,*so_prev; /* For a linked list of sockets */ + + int s; /* The actual socket */ + + Slirp *slirp; /* managing slirp instance */ + + /* XXX union these with not-yet-used sbuf params */ + struct mbuf *so_m; /* Pointer to the original SYN packet, + * for non-blocking connect()'s, and + * PING reply's */ + struct tcpiphdr *so_ti; /* Pointer to the original ti within + * so_mconn, for non-blocking connections */ + int so_urgc; + struct in_addr so_faddr; /* foreign host table entry */ + struct in_addr so_laddr; /* local host table entry */ + uint16_t so_fport; /* foreign port */ + uint16_t so_lport; /* local port */ + + uint8_t so_iptos; /* Type of service */ + uint8_t so_emu; /* Is the socket emulated? */ + + u_char so_type; /* Type of socket, UDP or TCP */ + int so_state; /* internal state flags SS_*, below */ + + struct tcpcb *so_tcpcb; /* pointer to TCP protocol control block */ + u_int so_expire; /* When the socket will expire */ + + int so_queued; /* Number of packets queued from this socket */ + int so_nqueued; /* Number of packets queued in a row + * Used to determine when to "downgrade" a session + * from fastq to batchq */ + + struct sbuf so_rcv; /* Receive buffer */ + struct sbuf so_snd; /* Send buffer */ + void * extra; /* Extra pointer */ +}; + + +/* + * Socket state bits. (peer means the host on the Internet, + * local host means the host on the other end of the modem) + */ +#define SS_NOFDREF 0x001 /* No fd reference */ + +#define SS_ISFCONNECTING 0x002 /* Socket is connecting to peer (non-blocking connect()'s) */ +#define SS_ISFCONNECTED 0x004 /* Socket is connected to peer */ +#define SS_FCANTRCVMORE 0x008 /* Socket can't receive more from peer (for half-closes) */ +#define SS_FCANTSENDMORE 0x010 /* Socket can't send more to peer (for half-closes) */ +#define SS_FWDRAIN 0x040 /* We received a FIN, drain data and set SS_FCANTSENDMORE */ + +#define SS_CTL 0x080 +#define SS_FACCEPTCONN 0x100 /* Socket is accepting connections from a host on the internet */ +#define SS_FACCEPTONCE 0x200 /* If set, the SS_FACCEPTCONN socket will die after one accept */ + +#define SS_PERSISTENT_MASK 0xf000 /* Unremovable state bits */ +#define SS_HOSTFWD 0x1000 /* Socket describes host->guest forwarding */ +#define SS_INCOMING 0x2000 /* Connection was initiated by a host on the internet */ + +struct socket * solookup(struct socket *, struct in_addr, u_int, struct in_addr, u_int); +struct socket * socreate(Slirp *); +void sofree(struct socket *); +int soread(struct socket *); +void sorecvoob(struct socket *); +int sosendoob(struct socket *); +int sowrite(struct socket *); +void sorecvfrom(struct socket *); +int sosendto(struct socket *, struct mbuf *); +struct socket * tcp_listen(Slirp *, uint32_t, u_int, uint32_t, u_int, + int); +void soisfconnecting(register struct socket *); +void soisfconnected(register struct socket *); +void sofwdrain(struct socket *); +struct iovec; /* For win32 */ +size_t sopreprbuf(struct socket *so, struct iovec *iov, int *np); +int soreadbuf(struct socket *so, const char *buf, int size); + +#endif /* _SOCKET_H_ */ diff --git a/slirp/tcp.h b/slirp/tcp.h new file mode 100644 index 0000000..9d06836 --- /dev/null +++ b/slirp/tcp.h @@ -0,0 +1,164 @@ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp.h 8.1 (Berkeley) 6/10/93 + * tcp.h,v 1.3 1994/08/21 05:27:34 paul Exp + */ + +#ifndef _TCP_H_ +#define _TCP_H_ + +typedef uint32_t tcp_seq; + +#define PR_SLOWHZ 2 /* 2 slow timeouts per second (approx) */ +#define PR_FASTHZ 5 /* 5 fast timeouts per second (not important) */ + +#define TCP_SNDSPACE 8192 +#define TCP_RCVSPACE 8192 + +/* + * TCP header. + * Per RFC 793, September, 1981. + */ +struct tcphdr { + uint16_t th_sport; /* source port */ + uint16_t th_dport; /* destination port */ + tcp_seq th_seq; /* sequence number */ + tcp_seq th_ack; /* acknowledgement number */ +#ifdef HOST_WORDS_BIGENDIAN + u_int th_off:4, /* data offset */ + th_x2:4; /* (unused) */ +#else + u_int th_x2:4, /* (unused) */ + th_off:4; /* data offset */ +#endif + uint8_t th_flags; +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 + uint16_t th_win; /* window */ + uint16_t th_sum; /* checksum */ + uint16_t th_urp; /* urgent pointer */ +}; + +#include "tcp_var.h" + +#define TCPOPT_EOL 0 +#define TCPOPT_NOP 1 +#define TCPOPT_MAXSEG 2 +#define TCPOLEN_MAXSEG 4 +#define TCPOPT_WINDOW 3 +#define TCPOLEN_WINDOW 3 +#define TCPOPT_SACK_PERMITTED 4 /* Experimental */ +#define TCPOLEN_SACK_PERMITTED 2 +#define TCPOPT_SACK 5 /* Experimental */ +#define TCPOPT_TIMESTAMP 8 +#define TCPOLEN_TIMESTAMP 10 +#define TCPOLEN_TSTAMP_APPA (TCPOLEN_TIMESTAMP+2) /* appendix A */ + +#define TCPOPT_TSTAMP_HDR \ + (TCPOPT_NOP<<24|TCPOPT_NOP<<16|TCPOPT_TIMESTAMP<<8|TCPOLEN_TIMESTAMP) + +/* + * Default maximum segment size for TCP. + * With an IP MSS of 576, this is 536, + * but 512 is probably more convenient. + * This should be defined as MIN(512, IP_MSS - sizeof (struct tcpiphdr)). + * + * We make this 1460 because we only care about Ethernet in the qemu context. + */ +#define TCP_MSS 1460 + +#define TCP_MAXWIN 65535 /* largest value for (unscaled) window */ + +#define TCP_MAX_WINSHIFT 14 /* maximum window shift */ + +/* + * User-settable options (used with setsockopt). + * + * We don't use the system headers on unix because we have conflicting + * local structures. We can't avoid the system definitions on Windows, + * so we undefine them. + */ +#undef TCP_NODELAY +#define TCP_NODELAY 0x01 /* don't delay send to coalesce packets */ +#undef TCP_MAXSEG + +/* + * TCP FSM state definitions. + * Per RFC793, September, 1981. + */ + +#define TCP_NSTATES 11 + +#define TCPS_CLOSED 0 /* closed */ +#define TCPS_LISTEN 1 /* listening for connection */ +#define TCPS_SYN_SENT 2 /* active, have sent syn */ +#define TCPS_SYN_RECEIVED 3 /* have send and received syn */ +/* states < TCPS_ESTABLISHED are those where connections not established */ +#define TCPS_ESTABLISHED 4 /* established */ +#define TCPS_CLOSE_WAIT 5 /* rcvd fin, waiting for close */ +/* states > TCPS_CLOSE_WAIT are those where user has closed */ +#define TCPS_FIN_WAIT_1 6 /* have closed, sent fin */ +#define TCPS_CLOSING 7 /* closed xchd FIN; await FIN ACK */ +#define TCPS_LAST_ACK 8 /* had fin and close; await FIN ACK */ +/* states > TCPS_CLOSE_WAIT && < TCPS_FIN_WAIT_2 await ACK of FIN */ +#define TCPS_FIN_WAIT_2 9 /* have closed, fin is acked */ +#define TCPS_TIME_WAIT 10 /* in 2*msl quiet wait after close */ + +#define TCPS_HAVERCVDSYN(s) ((s) >= TCPS_SYN_RECEIVED) +#define TCPS_HAVEESTABLISHED(s) ((s) >= TCPS_ESTABLISHED) +#define TCPS_HAVERCVDFIN(s) ((s) >= TCPS_TIME_WAIT) + +/* + * TCP sequence numbers are 32 bit integers operated + * on with modular arithmetic. These macros can be + * used to compare such integers. + */ +#define SEQ_LT(a,b) ((int)((a)-(b)) < 0) +#define SEQ_LEQ(a,b) ((int)((a)-(b)) <= 0) +#define SEQ_GT(a,b) ((int)((a)-(b)) > 0) +#define SEQ_GEQ(a,b) ((int)((a)-(b)) >= 0) + +/* + * Macros to initialize tcp sequence numbers for + * send and receive from initial send and receive + * sequence numbers. + */ +#define tcp_rcvseqinit(tp) \ + (tp)->rcv_adv = (tp)->rcv_nxt = (tp)->irs + 1 + +#define tcp_sendseqinit(tp) \ + (tp)->snd_una = (tp)->snd_nxt = (tp)->snd_max = (tp)->snd_up = (tp)->iss + +#define TCP_ISSINCR (125*1024) /* increment for tcp_iss each second */ + +#endif diff --git a/slirp/tcp_input.c b/slirp/tcp_input.c new file mode 100644 index 0000000..53dbc87 --- /dev/null +++ b/slirp/tcp_input.c @@ -0,0 +1,1487 @@ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_input.c 8.5 (Berkeley) 4/10/94 + * tcp_input.c,v 1.10 1994/10/13 18:36:32 wollman Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" +#include "ip_icmp.h" + +#define TCPREXMTTHRESH 3 + +#define TCP_PAWS_IDLE (24 * 24 * 60 * 60 * PR_SLOWHZ) + +/* for modulo comparisons of timestamps */ +#define TSTMP_LT(a,b) ((int)((a)-(b)) < 0) +#define TSTMP_GEQ(a,b) ((int)((a)-(b)) >= 0) + +/* + * Insert segment ti into reassembly queue of tcp with + * control block tp. Return TH_FIN if reassembly now includes + * a segment with FIN. The macro form does the common case inline + * (segment is the next to be received on an established connection, + * and the queue is empty), avoiding linkage into and removal + * from the queue and repetition of various conversions. + * Set DELACK for segments received in order, but ack immediately + * when segments are out of order (so fast retransmit can work). + */ +#ifdef TCP_ACK_HACK +#define TCP_REASS(tp, ti, m, so, flags) {\ + if ((ti)->ti_seq == (tp)->rcv_nxt && \ + tcpfrag_list_empty(tp) && \ + (tp)->t_state == TCPS_ESTABLISHED) {\ + if (ti->ti_flags & TH_PUSH) \ + tp->t_flags |= TF_ACKNOW; \ + else \ + tp->t_flags |= TF_DELACK; \ + (tp)->rcv_nxt += (ti)->ti_len; \ + flags = (ti)->ti_flags & TH_FIN; \ + if (so->so_emu) { \ + if (tcp_emu((so),(m))) sbappend((so), (m)); \ + } else \ + sbappend((so), (m)); \ + } else {\ + (flags) = tcp_reass((tp), (ti), (m)); \ + tp->t_flags |= TF_ACKNOW; \ + } \ +} +#else +#define TCP_REASS(tp, ti, m, so, flags) { \ + if ((ti)->ti_seq == (tp)->rcv_nxt && \ + tcpfrag_list_empty(tp) && \ + (tp)->t_state == TCPS_ESTABLISHED) { \ + tp->t_flags |= TF_DELACK; \ + (tp)->rcv_nxt += (ti)->ti_len; \ + flags = (ti)->ti_flags & TH_FIN; \ + if (so->so_emu) { \ + if (tcp_emu((so),(m))) sbappend(so, (m)); \ + } else \ + sbappend((so), (m)); \ + } else { \ + (flags) = tcp_reass((tp), (ti), (m)); \ + tp->t_flags |= TF_ACKNOW; \ + } \ +} +#endif +static void tcp_dooptions(struct tcpcb *tp, u_char *cp, int cnt, + struct tcpiphdr *ti); +static void tcp_xmit_timer(register struct tcpcb *tp, int rtt); + +static int +tcp_reass(register struct tcpcb *tp, register struct tcpiphdr *ti, + struct mbuf *m) +{ + register struct tcpiphdr *q; + struct socket *so = tp->t_socket; + int flags; + + /* + * Call with ti==NULL after become established to + * force pre-ESTABLISHED data up to user socket. + */ + if (ti == NULL) + goto present; + + /* + * Find a segment which begins after this one does. + */ + for (q = tcpfrag_list_first(tp); !tcpfrag_list_end(q, tp); + q = tcpiphdr_next(q)) + if (SEQ_GT(q->ti_seq, ti->ti_seq)) + break; + + /* + * If there is a preceding segment, it may provide some of + * our data already. If so, drop the data from the incoming + * segment. If it provides all of our data, drop us. + */ + if (!tcpfrag_list_end(tcpiphdr_prev(q), tp)) { + register int i; + q = tcpiphdr_prev(q); + /* conversion to int (in i) handles seq wraparound */ + i = q->ti_seq + q->ti_len - ti->ti_seq; + if (i > 0) { + if (i >= ti->ti_len) { + m_freem(m); + /* + * Try to present any queued data + * at the left window edge to the user. + * This is needed after the 3-WHS + * completes. + */ + goto present; /* ??? */ + } + m_adj(m, i); + ti->ti_len -= i; + ti->ti_seq += i; + } + q = tcpiphdr_next(q); + } + ti->ti_mbuf = m; + + /* + * While we overlap succeeding segments trim them or, + * if they are completely covered, dequeue them. + */ + while (!tcpfrag_list_end(q, tp)) { + register int i = (ti->ti_seq + ti->ti_len) - q->ti_seq; + if (i <= 0) + break; + if (i < q->ti_len) { + q->ti_seq += i; + q->ti_len -= i; + m_adj(q->ti_mbuf, i); + break; + } + q = tcpiphdr_next(q); + m = tcpiphdr_prev(q)->ti_mbuf; + remque(tcpiphdr2qlink(tcpiphdr_prev(q))); + m_freem(m); + } + + /* + * Stick new segment in its place. + */ + insque(tcpiphdr2qlink(ti), tcpiphdr2qlink(tcpiphdr_prev(q))); + +present: + /* + * Present data to user, advancing rcv_nxt through + * completed sequence space. + */ + if (!TCPS_HAVEESTABLISHED(tp->t_state)) + return (0); + ti = tcpfrag_list_first(tp); + if (tcpfrag_list_end(ti, tp) || ti->ti_seq != tp->rcv_nxt) + return (0); + if (tp->t_state == TCPS_SYN_RECEIVED && ti->ti_len) + return (0); + do { + tp->rcv_nxt += ti->ti_len; + flags = ti->ti_flags & TH_FIN; + remque(tcpiphdr2qlink(ti)); + m = ti->ti_mbuf; + ti = tcpiphdr_next(ti); + if (so->so_state & SS_FCANTSENDMORE) + m_freem(m); + else { + if (so->so_emu) { + if (tcp_emu(so,m)) sbappend(so, m); + } else + sbappend(so, m); + } + } while (ti != (struct tcpiphdr *)tp && ti->ti_seq == tp->rcv_nxt); + return (flags); +} + +/* + * TCP input routine, follows pages 65-76 of the + * protocol specification dated September, 1981 very closely. + */ +void +tcp_input(struct mbuf *m, int iphlen, struct socket *inso) +{ + struct ip save_ip, *ip; + register struct tcpiphdr *ti; + caddr_t optp = NULL; + int optlen = 0; + int len, tlen, off; + register struct tcpcb *tp = NULL; + register int tiflags; + struct socket *so = NULL; + int todrop, acked, ourfinisacked, needoutput = 0; + int iss = 0; + u_long tiwin; + int ret; + struct ex_list *ex_ptr; + Slirp *slirp; + + DEBUG_CALL("tcp_input"); + DEBUG_ARGS((dfd," m = %8lx iphlen = %2d inso = %lx\n", + (long )m, iphlen, (long )inso )); + + /* + * If called with m == 0, then we're continuing the connect + */ + if (m == NULL) { + so = inso; + slirp = so->slirp; + + /* Re-set a few variables */ + tp = sototcpcb(so); + m = so->so_m; + so->so_m = NULL; + ti = so->so_ti; + tiwin = ti->ti_win; + tiflags = ti->ti_flags; + + goto cont_conn; + } + slirp = m->slirp; + + /* + * Get IP and TCP header together in first mbuf. + * Note: IP leaves IP header in first mbuf. + */ + ti = mtod(m, struct tcpiphdr *); + if (iphlen > sizeof(struct ip )) { + ip_stripoptions(m, (struct mbuf *)0); + iphlen=sizeof(struct ip ); + } + /* XXX Check if too short */ + + + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + ip=mtod(m, struct ip *); + save_ip = *ip; + save_ip.ip_len+= iphlen; + + /* + * Checksum extended TCP header and data. + */ + tlen = ((struct ip *)ti)->ip_len; + tcpiphdr2qlink(ti)->next = tcpiphdr2qlink(ti)->prev = NULL; + memset(&ti->ti_i.ih_mbuf, 0 , sizeof(struct mbuf_ptr)); + ti->ti_x1 = 0; + ti->ti_len = htons((uint16_t)tlen); + len = sizeof(struct ip ) + tlen; + if(cksum(m, len)) { + goto drop; + } + + /* + * Check that TCP offset makes sense, + * pull out TCP options and adjust length. XXX + */ + off = ti->ti_off << 2; + if (off < sizeof (struct tcphdr) || off > tlen) { + goto drop; + } + tlen -= off; + ti->ti_len = tlen; + if (off > sizeof (struct tcphdr)) { + optlen = off - sizeof (struct tcphdr); + optp = mtod(m, caddr_t) + sizeof (struct tcpiphdr); + } + tiflags = ti->ti_flags; + + /* + * Convert TCP protocol specific fields to host format. + */ + NTOHL(ti->ti_seq); + NTOHL(ti->ti_ack); + NTOHS(ti->ti_win); + NTOHS(ti->ti_urp); + + /* + * Drop TCP, IP headers and TCP options. + */ + m->m_data += sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr); + m->m_len -= sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr); + + if (slirp->restricted) { + for (ex_ptr = slirp->exec_list; ex_ptr; ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->ex_fport == ti->ti_dport && + ti->ti_dst.s_addr == ex_ptr->ex_addr.s_addr) { + break; + } + } + if (!ex_ptr) + goto drop; + } + /* + * Locate pcb for segment. + */ +findso: + so = slirp->tcp_last_so; + if (so->so_fport != ti->ti_dport || + so->so_lport != ti->ti_sport || + so->so_laddr.s_addr != ti->ti_src.s_addr || + so->so_faddr.s_addr != ti->ti_dst.s_addr) { + so = solookup(&slirp->tcb, ti->ti_src, ti->ti_sport, + ti->ti_dst, ti->ti_dport); + if (so) + slirp->tcp_last_so = so; + } + + /* + * If the state is CLOSED (i.e., TCB does not exist) then + * all data in the incoming segment is discarded. + * If the TCB exists but is in CLOSED state, it is embryonic, + * but should either do a listen or a connect soon. + * + * state == CLOSED means we've done socreate() but haven't + * attached it to a protocol yet... + * + * XXX If a TCB does not exist, and the TH_SYN flag is + * the only flag set, then create a session, mark it + * as if it was LISTENING, and continue... + */ + if (so == NULL) { + if ((tiflags & (TH_SYN|TH_FIN|TH_RST|TH_URG|TH_ACK)) != TH_SYN) + goto dropwithreset; + + if ((so = socreate(slirp)) == NULL) + goto dropwithreset; + if (tcp_attach(so) < 0) { + free(so); /* Not sofree (if it failed, it's not insqued) */ + goto dropwithreset; + } + + sbreserve(&so->so_snd, TCP_SNDSPACE); + sbreserve(&so->so_rcv, TCP_RCVSPACE); + + so->so_laddr = ti->ti_src; + so->so_lport = ti->ti_sport; + so->so_faddr = ti->ti_dst; + so->so_fport = ti->ti_dport; + + if ((so->so_iptos = tcp_tos(so)) == 0) + so->so_iptos = ((struct ip *)ti)->ip_tos; + + tp = sototcpcb(so); + tp->t_state = TCPS_LISTEN; + } + + /* + * If this is a still-connecting socket, this probably + * a retransmit of the SYN. Whether it's a retransmit SYN + * or something else, we nuke it. + */ + if (so->so_state & SS_ISFCONNECTING) + goto drop; + + tp = sototcpcb(so); + + /* XXX Should never fail */ + if (tp == NULL) + goto dropwithreset; + if (tp->t_state == TCPS_CLOSED) + goto drop; + + tiwin = ti->ti_win; + + /* + * Segment received on connection. + * Reset idle time and keep-alive timer. + */ + tp->t_idle = 0; + if (SO_OPTIONS) + tp->t_timer[TCPT_KEEP] = TCPTV_KEEPINTVL; + else + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_IDLE; + + /* + * Process options if not in LISTEN state, + * else do it below (after getting remote address). + */ + if (optp && tp->t_state != TCPS_LISTEN) + tcp_dooptions(tp, (u_char *)optp, optlen, ti); + + /* + * Header prediction: check for the two common cases + * of a uni-directional data xfer. If the packet has + * no control flags, is in-sequence, the window didn't + * change and we're not retransmitting, it's a + * candidate. If the length is zero and the ack moved + * forward, we're the sender side of the xfer. Just + * free the data acked & wake any higher level process + * that was blocked waiting for space. If the length + * is non-zero and the ack didn't move, we're the + * receiver side. If we're getting packets in-order + * (the reassembly queue is empty), add the data to + * the socket buffer and note that we need a delayed ack. + * + * XXX Some of these tests are not needed + * eg: the tiwin == tp->snd_wnd prevents many more + * predictions.. with no *real* advantage.. + */ + if (tp->t_state == TCPS_ESTABLISHED && + (tiflags & (TH_SYN|TH_FIN|TH_RST|TH_URG|TH_ACK)) == TH_ACK && + ti->ti_seq == tp->rcv_nxt && + tiwin && tiwin == tp->snd_wnd && + tp->snd_nxt == tp->snd_max) { + if (ti->ti_len == 0) { + if (SEQ_GT(ti->ti_ack, tp->snd_una) && + SEQ_LEQ(ti->ti_ack, tp->snd_max) && + tp->snd_cwnd >= tp->snd_wnd) { + /* + * this is a pure ack for outstanding data. + */ + if (tp->t_rtt && + SEQ_GT(ti->ti_ack, tp->t_rtseq)) + tcp_xmit_timer(tp, tp->t_rtt); + acked = ti->ti_ack - tp->snd_una; + sbdrop(&so->so_snd, acked); + tp->snd_una = ti->ti_ack; + m_freem(m); + + /* + * If all outstanding data are acked, stop + * retransmit timer, otherwise restart timer + * using current (possibly backed-off) value. + * If process is waiting for space, + * wakeup/selwakeup/signal. If data + * are ready to send, let tcp_output + * decide between more output or persist. + */ + if (tp->snd_una == tp->snd_max) + tp->t_timer[TCPT_REXMT] = 0; + else if (tp->t_timer[TCPT_PERSIST] == 0) + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + + /* + * This is called because sowwakeup might have + * put data into so_snd. Since we don't so sowwakeup, + * we don't need this.. XXX??? + */ + if (so->so_snd.sb_cc) + (void) tcp_output(tp); + + return; + } + } else if (ti->ti_ack == tp->snd_una && + tcpfrag_list_empty(tp) && + ti->ti_len <= sbspace(&so->so_rcv)) { + /* + * this is a pure, in-sequence data packet + * with nothing on the reassembly queue and + * we have enough buffer space to take it. + */ + tp->rcv_nxt += ti->ti_len; + /* + * Add data to socket buffer. + */ + if (so->so_emu) { + if (tcp_emu(so,m)) sbappend(so, m); + } else + sbappend(so, m); + + /* + * If this is a short packet, then ACK now - with Nagel + * congestion avoidance sender won't send more until + * he gets an ACK. + * + * It is better to not delay acks at all to maximize + * TCP throughput. See RFC 2581. + */ + tp->t_flags |= TF_ACKNOW; + tcp_output(tp); + return; + } + } /* header prediction */ + /* + * Calculate amount of space in receive window, + * and then do TCP input processing. + * Receive window is amount of space in rcv queue, + * but not less than advertised window. + */ + { int win; + win = sbspace(&so->so_rcv); + if (win < 0) + win = 0; + tp->rcv_wnd = max(win, (int)(tp->rcv_adv - tp->rcv_nxt)); + } + + switch (tp->t_state) { + + /* + * If the state is LISTEN then ignore segment if it contains an RST. + * If the segment contains an ACK then it is bad and send a RST. + * If it does not contain a SYN then it is not interesting; drop it. + * Don't bother responding if the destination was a broadcast. + * Otherwise initialize tp->rcv_nxt, and tp->irs, select an initial + * tp->iss, and send a segment: + * + * Also initialize tp->snd_nxt to tp->iss+1 and tp->snd_una to tp->iss. + * Fill in remote peer address fields if not previously specified. + * Enter SYN_RECEIVED state, and process any other fields of this + * segment in this state. + */ + case TCPS_LISTEN: { + + if (tiflags & TH_RST) + goto drop; + if (tiflags & TH_ACK) + goto dropwithreset; + if ((tiflags & TH_SYN) == 0) + goto drop; + + /* + * This has way too many gotos... + * But a bit of spaghetti code never hurt anybody :) + */ + + /* + * If this is destined for the control address, then flag to + * tcp_ctl once connected, otherwise connect + */ + if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + if (so->so_faddr.s_addr != slirp->vhost_addr.s_addr && + so->so_faddr.s_addr != slirp->vnameserver_addr.s_addr) { + /* May be an add exec */ + for (ex_ptr = slirp->exec_list; ex_ptr; + ex_ptr = ex_ptr->ex_next) { + if(ex_ptr->ex_fport == so->so_fport && + so->so_faddr.s_addr == ex_ptr->ex_addr.s_addr) { + so->so_state |= SS_CTL; + break; + } + } + if (so->so_state & SS_CTL) { + goto cont_input; + } + } + /* CTL_ALIAS: Do nothing, tcp_fconnect will be called on it */ + } + + if (so->so_emu & EMU_NOCONNECT) { + so->so_emu &= ~EMU_NOCONNECT; + goto cont_input; + } + + if((tcp_fconnect(so) == -1) && (errno != EINPROGRESS) && (errno != EWOULDBLOCK)) { + u_char code=ICMP_UNREACH_NET; + DEBUG_MISC((dfd," tcp fconnect errno = %d-%s\n", + errno,strerror(errno))); + if(errno == ECONNREFUSED) { + /* ACK the SYN, send RST to refuse the connection */ + tcp_respond(tp, ti, m, ti->ti_seq+1, (tcp_seq)0, + TH_RST|TH_ACK); + } else { + if(errno == EHOSTUNREACH) code=ICMP_UNREACH_HOST; + HTONL(ti->ti_seq); /* restore tcp header */ + HTONL(ti->ti_ack); + HTONS(ti->ti_win); + HTONS(ti->ti_urp); + m->m_data -= sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr); + m->m_len += sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr); + *ip=save_ip; + icmp_error(m, ICMP_UNREACH,code, 0,strerror(errno)); + } + tcp_close(tp); + m_free(m); + } else { + /* + * Haven't connected yet, save the current mbuf + * and ti, and return + * XXX Some OS's don't tell us whether the connect() + * succeeded or not. So we must time it out. + */ + so->so_m = m; + so->so_ti = ti; + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; + tp->t_state = TCPS_SYN_RECEIVED; + } + return; + + cont_conn: + /* m==NULL + * Check if the connect succeeded + */ + if (so->so_state & SS_NOFDREF) { + tp = tcp_close(tp); + goto dropwithreset; + } + cont_input: + tcp_template(tp); + + if (optp) + tcp_dooptions(tp, (u_char *)optp, optlen, ti); + + if (iss) + tp->iss = iss; + else + tp->iss = slirp->tcp_iss; + slirp->tcp_iss += TCP_ISSINCR/2; + tp->irs = ti->ti_seq; + tcp_sendseqinit(tp); + tcp_rcvseqinit(tp); + tp->t_flags |= TF_ACKNOW; + tp->t_state = TCPS_SYN_RECEIVED; + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; + goto trimthenstep6; + } /* case TCPS_LISTEN */ + + /* + * If the state is SYN_SENT: + * if seg contains an ACK, but not for our SYN, drop the input. + * if seg contains a RST, then drop the connection. + * if seg does not contain SYN, then drop it. + * Otherwise this is an acceptable SYN segment + * initialize tp->rcv_nxt and tp->irs + * if seg contains ack then advance tp->snd_una + * if SYN has been acked change to ESTABLISHED else SYN_RCVD state + * arrange for segment to be acked (eventually) + * continue processing rest of data/controls, beginning with URG + */ + case TCPS_SYN_SENT: + if ((tiflags & TH_ACK) && + (SEQ_LEQ(ti->ti_ack, tp->iss) || + SEQ_GT(ti->ti_ack, tp->snd_max))) + goto dropwithreset; + + if (tiflags & TH_RST) { + if (tiflags & TH_ACK) { + tcp_drop(tp, 0); /* XXX Check t_softerror! */ + } + goto drop; + } + + if ((tiflags & TH_SYN) == 0) + goto drop; + if (tiflags & TH_ACK) { + tp->snd_una = ti->ti_ack; + if (SEQ_LT(tp->snd_nxt, tp->snd_una)) + tp->snd_nxt = tp->snd_una; + } + + tp->t_timer[TCPT_REXMT] = 0; + tp->irs = ti->ti_seq; + tcp_rcvseqinit(tp); + tp->t_flags |= TF_ACKNOW; + if (tiflags & TH_ACK && SEQ_GT(tp->snd_una, tp->iss)) { + soisfconnected(so); + tp->t_state = TCPS_ESTABLISHED; + + (void) tcp_reass(tp, (struct tcpiphdr *)0, + (struct mbuf *)0); + /* + * if we didn't have to retransmit the SYN, + * use its rtt as our initial srtt & rtt var. + */ + if (tp->t_rtt) + tcp_xmit_timer(tp, tp->t_rtt); + } else + tp->t_state = TCPS_SYN_RECEIVED; + +trimthenstep6: + /* + * Advance ti->ti_seq to correspond to first data byte. + * If data, trim to stay within window, + * dropping FIN if necessary. + */ + ti->ti_seq++; + if (ti->ti_len > tp->rcv_wnd) { + todrop = ti->ti_len - tp->rcv_wnd; + m_adj(m, -todrop); + ti->ti_len = tp->rcv_wnd; + tiflags &= ~TH_FIN; + } + tp->snd_wl1 = ti->ti_seq - 1; + tp->rcv_up = ti->ti_seq; + goto step6; + } /* switch tp->t_state */ + /* + * States other than LISTEN or SYN_SENT. + * Check that at least some bytes of segment are within + * receive window. If segment begins before rcv_nxt, + * drop leading data (and SYN); if nothing left, just ack. + */ + todrop = tp->rcv_nxt - ti->ti_seq; + if (todrop > 0) { + if (tiflags & TH_SYN) { + tiflags &= ~TH_SYN; + ti->ti_seq++; + if (ti->ti_urp > 1) + ti->ti_urp--; + else + tiflags &= ~TH_URG; + todrop--; + } + /* + * Following if statement from Stevens, vol. 2, p. 960. + */ + if (todrop > ti->ti_len + || (todrop == ti->ti_len && (tiflags & TH_FIN) == 0)) { + /* + * Any valid FIN must be to the left of the window. + * At this point the FIN must be a duplicate or out + * of sequence; drop it. + */ + tiflags &= ~TH_FIN; + + /* + * Send an ACK to resynchronize and drop any data. + * But keep on processing for RST or ACK. + */ + tp->t_flags |= TF_ACKNOW; + todrop = ti->ti_len; + } + m_adj(m, todrop); + ti->ti_seq += todrop; + ti->ti_len -= todrop; + if (ti->ti_urp > todrop) + ti->ti_urp -= todrop; + else { + tiflags &= ~TH_URG; + ti->ti_urp = 0; + } + } + /* + * If new data are received on a connection after the + * user processes are gone, then RST the other end. + */ + if ((so->so_state & SS_NOFDREF) && + tp->t_state > TCPS_CLOSE_WAIT && ti->ti_len) { + tp = tcp_close(tp); + goto dropwithreset; + } + + /* + * If segment ends after window, drop trailing data + * (and PUSH and FIN); if nothing left, just ACK. + */ + todrop = (ti->ti_seq+ti->ti_len) - (tp->rcv_nxt+tp->rcv_wnd); + if (todrop > 0) { + if (todrop >= ti->ti_len) { + /* + * If a new connection request is received + * while in TIME_WAIT, drop the old connection + * and start over if the sequence numbers + * are above the previous ones. + */ + if (tiflags & TH_SYN && + tp->t_state == TCPS_TIME_WAIT && + SEQ_GT(ti->ti_seq, tp->rcv_nxt)) { + iss = tp->rcv_nxt + TCP_ISSINCR; + tp = tcp_close(tp); + goto findso; + } + /* + * If window is closed can only take segments at + * window edge, and have to drop data and PUSH from + * incoming segments. Continue processing, but + * remember to ack. Otherwise, drop segment + * and ack. + */ + if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) { + tp->t_flags |= TF_ACKNOW; + } else { + goto dropafterack; + } + } + m_adj(m, -todrop); + ti->ti_len -= todrop; + tiflags &= ~(TH_PUSH|TH_FIN); + } + + /* + * If the RST bit is set examine the state: + * SYN_RECEIVED STATE: + * If passive open, return to LISTEN state. + * If active open, inform user that connection was refused. + * ESTABLISHED, FIN_WAIT_1, FIN_WAIT2, CLOSE_WAIT STATES: + * Inform user that connection was reset, and close tcb. + * CLOSING, LAST_ACK, TIME_WAIT STATES + * Close the tcb. + */ + if (tiflags&TH_RST) switch (tp->t_state) { + + case TCPS_SYN_RECEIVED: + case TCPS_ESTABLISHED: + case TCPS_FIN_WAIT_1: + case TCPS_FIN_WAIT_2: + case TCPS_CLOSE_WAIT: + tp->t_state = TCPS_CLOSED; + tcp_close(tp); + goto drop; + + case TCPS_CLOSING: + case TCPS_LAST_ACK: + case TCPS_TIME_WAIT: + tcp_close(tp); + goto drop; + } + + /* + * If a SYN is in the window, then this is an + * error and we send an RST and drop the connection. + */ + if (tiflags & TH_SYN) { + tp = tcp_drop(tp,0); + goto dropwithreset; + } + + /* + * If the ACK bit is off we drop the segment and return. + */ + if ((tiflags & TH_ACK) == 0) goto drop; + + /* + * Ack processing. + */ + switch (tp->t_state) { + /* + * In SYN_RECEIVED state if the ack ACKs our SYN then enter + * ESTABLISHED state and continue processing, otherwise + * send an RST. una<=ack<=max + */ + case TCPS_SYN_RECEIVED: + + if (SEQ_GT(tp->snd_una, ti->ti_ack) || + SEQ_GT(ti->ti_ack, tp->snd_max)) + goto dropwithreset; + tp->t_state = TCPS_ESTABLISHED; + /* + * The sent SYN is ack'ed with our sequence number +1 + * The first data byte already in the buffer will get + * lost if no correction is made. This is only needed for + * SS_CTL since the buffer is empty otherwise. + * tp->snd_una++; or: + */ + tp->snd_una=ti->ti_ack; + if (so->so_state & SS_CTL) { + /* So tcp_ctl reports the right state */ + ret = tcp_ctl(so); + if (ret == 1) { + soisfconnected(so); + so->so_state &= ~SS_CTL; /* success XXX */ + } else if (ret == 2) { + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; /* CTL_CMD */ + } else { + needoutput = 1; + tp->t_state = TCPS_FIN_WAIT_1; + } + } else { + soisfconnected(so); + } + + (void) tcp_reass(tp, (struct tcpiphdr *)0, (struct mbuf *)0); + tp->snd_wl1 = ti->ti_seq - 1; + /* Avoid ack processing; snd_una==ti_ack => dup ack */ + goto synrx_to_est; + /* fall into ... */ + + /* + * In ESTABLISHED state: drop duplicate ACKs; ACK out of range + * ACKs. If the ack is in the range + * tp->snd_una < ti->ti_ack <= tp->snd_max + * then advance tp->snd_una to ti->ti_ack and drop + * data from the retransmission queue. If this ACK reflects + * more up to date window information we update our window information. + */ + case TCPS_ESTABLISHED: + case TCPS_FIN_WAIT_1: + case TCPS_FIN_WAIT_2: + case TCPS_CLOSE_WAIT: + case TCPS_CLOSING: + case TCPS_LAST_ACK: + case TCPS_TIME_WAIT: + + if (SEQ_LEQ(ti->ti_ack, tp->snd_una)) { + if (ti->ti_len == 0 && tiwin == tp->snd_wnd) { + DEBUG_MISC((dfd," dup ack m = %lx so = %lx \n", + (long )m, (long )so)); + /* + * If we have outstanding data (other than + * a window probe), this is a completely + * duplicate ack (ie, window info didn't + * change), the ack is the biggest we've + * seen and we've seen exactly our rexmt + * threshold of them, assume a packet + * has been dropped and retransmit it. + * Kludge snd_nxt & the congestion + * window so we send only this one + * packet. + * + * We know we're losing at the current + * window size so do congestion avoidance + * (set ssthresh to half the current window + * and pull our congestion window back to + * the new ssthresh). + * + * Dup acks mean that packets have left the + * network (they're now cached at the receiver) + * so bump cwnd by the amount in the receiver + * to keep a constant cwnd packets in the + * network. + */ + if (tp->t_timer[TCPT_REXMT] == 0 || + ti->ti_ack != tp->snd_una) + tp->t_dupacks = 0; + else if (++tp->t_dupacks == TCPREXMTTHRESH) { + tcp_seq onxt = tp->snd_nxt; + u_int win = + min(tp->snd_wnd, tp->snd_cwnd) / 2 / + tp->t_maxseg; + + if (win < 2) + win = 2; + tp->snd_ssthresh = win * tp->t_maxseg; + tp->t_timer[TCPT_REXMT] = 0; + tp->t_rtt = 0; + tp->snd_nxt = ti->ti_ack; + tp->snd_cwnd = tp->t_maxseg; + (void) tcp_output(tp); + tp->snd_cwnd = tp->snd_ssthresh + + tp->t_maxseg * tp->t_dupacks; + if (SEQ_GT(onxt, tp->snd_nxt)) + tp->snd_nxt = onxt; + goto drop; + } else if (tp->t_dupacks > TCPREXMTTHRESH) { + tp->snd_cwnd += tp->t_maxseg; + (void) tcp_output(tp); + goto drop; + } + } else + tp->t_dupacks = 0; + break; + } + synrx_to_est: + /* + * If the congestion window was inflated to account + * for the other side's cached packets, retract it. + */ + if (tp->t_dupacks > TCPREXMTTHRESH && + tp->snd_cwnd > tp->snd_ssthresh) + tp->snd_cwnd = tp->snd_ssthresh; + tp->t_dupacks = 0; + if (SEQ_GT(ti->ti_ack, tp->snd_max)) { + goto dropafterack; + } + acked = ti->ti_ack - tp->snd_una; + + /* + * If transmit timer is running and timed sequence + * number was acked, update smoothed round trip time. + * Since we now have an rtt measurement, cancel the + * timer backoff (cf., Phil Karn's retransmit alg.). + * Recompute the initial retransmit timer. + */ + if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) + tcp_xmit_timer(tp,tp->t_rtt); + + /* + * If all outstanding data is acked, stop retransmit + * timer and remember to restart (more output or persist). + * If there is more data to be acked, restart retransmit + * timer, using current (possibly backed-off) value. + */ + if (ti->ti_ack == tp->snd_max) { + tp->t_timer[TCPT_REXMT] = 0; + needoutput = 1; + } else if (tp->t_timer[TCPT_PERSIST] == 0) + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + /* + * When new data is acked, open the congestion window. + * If the window gives us less than ssthresh packets + * in flight, open exponentially (maxseg per packet). + * Otherwise open linearly: maxseg per window + * (maxseg^2 / cwnd per packet). + */ + { + register u_int cw = tp->snd_cwnd; + register u_int incr = tp->t_maxseg; + + if (cw > tp->snd_ssthresh) + incr = incr * incr / cw; + tp->snd_cwnd = min(cw + incr, TCP_MAXWIN<snd_scale); + } + if (acked > so->so_snd.sb_cc) { + tp->snd_wnd -= so->so_snd.sb_cc; + sbdrop(&so->so_snd, (int )so->so_snd.sb_cc); + ourfinisacked = 1; + } else { + sbdrop(&so->so_snd, acked); + tp->snd_wnd -= acked; + ourfinisacked = 0; + } + tp->snd_una = ti->ti_ack; + if (SEQ_LT(tp->snd_nxt, tp->snd_una)) + tp->snd_nxt = tp->snd_una; + + switch (tp->t_state) { + + /* + * In FIN_WAIT_1 STATE in addition to the processing + * for the ESTABLISHED state if our FIN is now acknowledged + * then enter FIN_WAIT_2. + */ + case TCPS_FIN_WAIT_1: + if (ourfinisacked) { + /* + * If we can't receive any more + * data, then closing user can proceed. + * Starting the timer is contrary to the + * specification, but if we don't get a FIN + * we'll hang forever. + */ + if (so->so_state & SS_FCANTRCVMORE) { + tp->t_timer[TCPT_2MSL] = TCP_MAXIDLE; + } + tp->t_state = TCPS_FIN_WAIT_2; + } + break; + + /* + * In CLOSING STATE in addition to the processing for + * the ESTABLISHED state if the ACK acknowledges our FIN + * then enter the TIME-WAIT state, otherwise ignore + * the segment. + */ + case TCPS_CLOSING: + if (ourfinisacked) { + tp->t_state = TCPS_TIME_WAIT; + tcp_canceltimers(tp); + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + } + break; + + /* + * In LAST_ACK, we may still be waiting for data to drain + * and/or to be acked, as well as for the ack of our FIN. + * If our FIN is now acknowledged, delete the TCB, + * enter the closed state and return. + */ + case TCPS_LAST_ACK: + if (ourfinisacked) { + tcp_close(tp); + goto drop; + } + break; + + /* + * In TIME_WAIT state the only thing that should arrive + * is a retransmission of the remote FIN. Acknowledge + * it and restart the finack timer. + */ + case TCPS_TIME_WAIT: + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + goto dropafterack; + } + } /* switch(tp->t_state) */ + +step6: + /* + * Update window information. + * Don't look at window if no ACK: TAC's send garbage on first SYN. + */ + if ((tiflags & TH_ACK) && + (SEQ_LT(tp->snd_wl1, ti->ti_seq) || + (tp->snd_wl1 == ti->ti_seq && (SEQ_LT(tp->snd_wl2, ti->ti_ack) || + (tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd))))) { + tp->snd_wnd = tiwin; + tp->snd_wl1 = ti->ti_seq; + tp->snd_wl2 = ti->ti_ack; + if (tp->snd_wnd > tp->max_sndwnd) + tp->max_sndwnd = tp->snd_wnd; + needoutput = 1; + } + + /* + * Process segments with URG. + */ + if ((tiflags & TH_URG) && ti->ti_urp && + TCPS_HAVERCVDFIN(tp->t_state) == 0) { + /* + * This is a kludge, but if we receive and accept + * random urgent pointers, we'll crash in + * soreceive. It's hard to imagine someone + * actually wanting to send this much urgent data. + */ + if (ti->ti_urp + so->so_rcv.sb_cc > so->so_rcv.sb_datalen) { + ti->ti_urp = 0; + tiflags &= ~TH_URG; + goto dodata; + } + /* + * If this segment advances the known urgent pointer, + * then mark the data stream. This should not happen + * in CLOSE_WAIT, CLOSING, LAST_ACK or TIME_WAIT STATES since + * a FIN has been received from the remote side. + * In these states we ignore the URG. + * + * According to RFC961 (Assigned Protocols), + * the urgent pointer points to the last octet + * of urgent data. We continue, however, + * to consider it to indicate the first octet + * of data past the urgent section as the original + * spec states (in one of two places). + */ + if (SEQ_GT(ti->ti_seq+ti->ti_urp, tp->rcv_up)) { + tp->rcv_up = ti->ti_seq + ti->ti_urp; + so->so_urgc = so->so_rcv.sb_cc + + (tp->rcv_up - tp->rcv_nxt); /* -1; */ + tp->rcv_up = ti->ti_seq + ti->ti_urp; + + } + } else + /* + * If no out of band data is expected, + * pull receive urgent pointer along + * with the receive window. + */ + if (SEQ_GT(tp->rcv_nxt, tp->rcv_up)) + tp->rcv_up = tp->rcv_nxt; +dodata: + + /* + * Process the segment text, merging it into the TCP sequencing queue, + * and arranging for acknowledgment of receipt if necessary. + * This process logically involves adjusting tp->rcv_wnd as data + * is presented to the user (this happens in tcp_usrreq.c, + * case PRU_RCVD). If a FIN has already been received on this + * connection then we just ignore the text. + */ + if ((ti->ti_len || (tiflags&TH_FIN)) && + TCPS_HAVERCVDFIN(tp->t_state) == 0) { + TCP_REASS(tp, ti, m, so, tiflags); + } else { + m_free(m); + tiflags &= ~TH_FIN; + } + + /* + * If FIN is received ACK the FIN and let the user know + * that the connection is closing. + */ + if (tiflags & TH_FIN) { + if (TCPS_HAVERCVDFIN(tp->t_state) == 0) { + /* + * If we receive a FIN we can't send more data, + * set it SS_FDRAIN + * Shutdown the socket if there is no rx data in the + * buffer. + * soread() is called on completion of shutdown() and + * will got to TCPS_LAST_ACK, and use tcp_output() + * to send the FIN. + */ + sofwdrain(so); + + tp->t_flags |= TF_ACKNOW; + tp->rcv_nxt++; + } + switch (tp->t_state) { + + /* + * In SYN_RECEIVED and ESTABLISHED STATES + * enter the CLOSE_WAIT state. + */ + case TCPS_SYN_RECEIVED: + case TCPS_ESTABLISHED: + if(so->so_emu == EMU_CTL) /* no shutdown on socket */ + tp->t_state = TCPS_LAST_ACK; + else + tp->t_state = TCPS_CLOSE_WAIT; + break; + + /* + * If still in FIN_WAIT_1 STATE FIN has not been acked so + * enter the CLOSING state. + */ + case TCPS_FIN_WAIT_1: + tp->t_state = TCPS_CLOSING; + break; + + /* + * In FIN_WAIT_2 state enter the TIME_WAIT state, + * starting the time-wait timer, turning off the other + * standard timers. + */ + case TCPS_FIN_WAIT_2: + tp->t_state = TCPS_TIME_WAIT; + tcp_canceltimers(tp); + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + break; + + /* + * In TIME_WAIT state restart the 2 MSL time_wait timer. + */ + case TCPS_TIME_WAIT: + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + break; + } + } + + /* + * If this is a small packet, then ACK now - with Nagel + * congestion avoidance sender won't send more until + * he gets an ACK. + * + * See above. + */ + if (ti->ti_len && (unsigned)ti->ti_len <= 5 && + ((struct tcpiphdr_2 *)ti)->first_char == (char)27) { + tp->t_flags |= TF_ACKNOW; + } + + /* + * Return any desired output. + */ + if (needoutput || (tp->t_flags & TF_ACKNOW)) { + (void) tcp_output(tp); + } + return; + +dropafterack: + /* + * Generate an ACK dropping incoming segment if it occupies + * sequence space, where the ACK reflects our state. + */ + if (tiflags & TH_RST) + goto drop; + m_freem(m); + tp->t_flags |= TF_ACKNOW; + (void) tcp_output(tp); + return; + +dropwithreset: + /* reuses m if m!=NULL, m_free() unnecessary */ + if (tiflags & TH_ACK) + tcp_respond(tp, ti, m, (tcp_seq)0, ti->ti_ack, TH_RST); + else { + if (tiflags & TH_SYN) ti->ti_len++; + tcp_respond(tp, ti, m, ti->ti_seq+ti->ti_len, (tcp_seq)0, + TH_RST|TH_ACK); + } + + return; + +drop: + /* + * Drop space held by incoming segment and return. + */ + m_free(m); + + return; +} + +static void +tcp_dooptions(struct tcpcb *tp, u_char *cp, int cnt, struct tcpiphdr *ti) +{ + uint16_t mss; + int opt, optlen; + + DEBUG_CALL("tcp_dooptions"); + DEBUG_ARGS((dfd," tp = %lx cnt=%i \n", (long )tp, cnt)); + + for (; cnt > 0; cnt -= optlen, cp += optlen) { + opt = cp[0]; + if (opt == TCPOPT_EOL) + break; + if (opt == TCPOPT_NOP) + optlen = 1; + else { + optlen = cp[1]; + if (optlen <= 0) + break; + } + switch (opt) { + + default: + continue; + + case TCPOPT_MAXSEG: + if (optlen != TCPOLEN_MAXSEG) + continue; + if (!(ti->ti_flags & TH_SYN)) + continue; + memcpy((char *) &mss, (char *) cp + 2, sizeof(mss)); + NTOHS(mss); + (void) tcp_mss(tp, mss); /* sets t_maxseg */ + break; + } + } +} + + +/* + * Pull out of band byte out of a segment so + * it doesn't appear in the user's data queue. + * It is still reflected in the segment length for + * sequencing purposes. + */ + +#ifdef notdef + +void +tcp_pulloutofband(so, ti, m) + struct socket *so; + struct tcpiphdr *ti; + register struct mbuf *m; +{ + int cnt = ti->ti_urp - 1; + + while (cnt >= 0) { + if (m->m_len > cnt) { + char *cp = mtod(m, caddr_t) + cnt; + struct tcpcb *tp = sototcpcb(so); + + tp->t_iobc = *cp; + tp->t_oobflags |= TCPOOB_HAVEDATA; + memcpy(sp, cp+1, (unsigned)(m->m_len - cnt - 1)); + m->m_len--; + return; + } + cnt -= m->m_len; + m = m->m_next; /* XXX WRONG! Fix it! */ + if (m == 0) + break; + } + panic("tcp_pulloutofband"); +} + +#endif /* notdef */ + +/* + * Collect new round-trip time estimate + * and update averages and current timeout. + */ + +static void +tcp_xmit_timer(register struct tcpcb *tp, int rtt) +{ + register short delta; + + DEBUG_CALL("tcp_xmit_timer"); + DEBUG_ARG("tp = %lx", (long)tp); + DEBUG_ARG("rtt = %d", rtt); + + if (tp->t_srtt != 0) { + /* + * srtt is stored as fixed point with 3 bits after the + * binary point (i.e., scaled by 8). The following magic + * is equivalent to the smoothing algorithm in rfc793 with + * an alpha of .875 (srtt = rtt/8 + srtt*7/8 in fixed + * point). Adjust rtt to origin 0. + */ + delta = rtt - 1 - (tp->t_srtt >> TCP_RTT_SHIFT); + if ((tp->t_srtt += delta) <= 0) + tp->t_srtt = 1; + /* + * We accumulate a smoothed rtt variance (actually, a + * smoothed mean difference), then set the retransmit + * timer to smoothed rtt + 4 times the smoothed variance. + * rttvar is stored as fixed point with 2 bits after the + * binary point (scaled by 4). The following is + * equivalent to rfc793 smoothing with an alpha of .75 + * (rttvar = rttvar*3/4 + |delta| / 4). This replaces + * rfc793's wired-in beta. + */ + if (delta < 0) + delta = -delta; + delta -= (tp->t_rttvar >> TCP_RTTVAR_SHIFT); + if ((tp->t_rttvar += delta) <= 0) + tp->t_rttvar = 1; + } else { + /* + * No rtt measurement yet - use the unsmoothed rtt. + * Set the variance to half the rtt (so our first + * retransmit happens at 3*rtt). + */ + tp->t_srtt = rtt << TCP_RTT_SHIFT; + tp->t_rttvar = rtt << (TCP_RTTVAR_SHIFT - 1); + } + tp->t_rtt = 0; + tp->t_rxtshift = 0; + + /* + * the retransmit should happen at rtt + 4 * rttvar. + * Because of the way we do the smoothing, srtt and rttvar + * will each average +1/2 tick of bias. When we compute + * the retransmit timer, we want 1/2 tick of rounding and + * 1 extra tick because of +-1/2 tick uncertainty in the + * firing of the timer. The bias will give us exactly the + * 1.5 tick we need. But, because the bias is + * statistical, we have to test that we don't drop below + * the minimum feasible timer (which is 2 ticks). + */ + TCPT_RANGESET(tp->t_rxtcur, TCP_REXMTVAL(tp), + (short)tp->t_rttmin, TCPTV_REXMTMAX); /* XXX */ + + /* + * We received an ack for a packet that wasn't retransmitted; + * it is probably safe to discard any error indications we've + * received recently. This isn't quite right, but close enough + * for now (a route might have failed after we sent a segment, + * and the return path might not be symmetrical). + */ + tp->t_softerror = 0; +} + +/* + * Determine a reasonable value for maxseg size. + * If the route is known, check route for mtu. + * If none, use an mss that can be handled on the outgoing + * interface without forcing IP to fragment; if bigger than + * an mbuf cluster (MCLBYTES), round down to nearest multiple of MCLBYTES + * to utilize large mbufs. If no route is found, route has no mtu, + * or the destination isn't local, use a default, hopefully conservative + * size (usually 512 or the default IP max size, but no more than the mtu + * of the interface), as we can't discover anything about intervening + * gateways or networks. We also initialize the congestion/slow start + * window to be a single segment if the destination isn't local. + * While looking at the routing entry, we also initialize other path-dependent + * parameters from pre-set or cached values in the routing entry. + */ + +int +tcp_mss(struct tcpcb *tp, u_int offer) +{ + struct socket *so = tp->t_socket; + int mss; + + DEBUG_CALL("tcp_mss"); + DEBUG_ARG("tp = %lx", (long)tp); + DEBUG_ARG("offer = %d", offer); + + mss = min(IF_MTU, IF_MRU) - sizeof(struct tcpiphdr); + if (offer) + mss = min(mss, offer); + mss = max(mss, 32); + if (mss < tp->t_maxseg || offer != 0) + tp->t_maxseg = mss; + + tp->snd_cwnd = mss; + + sbreserve(&so->so_snd, TCP_SNDSPACE + ((TCP_SNDSPACE % mss) ? + (mss - (TCP_SNDSPACE % mss)) : + 0)); + sbreserve(&so->so_rcv, TCP_RCVSPACE + ((TCP_RCVSPACE % mss) ? + (mss - (TCP_RCVSPACE % mss)) : + 0)); + + DEBUG_MISC((dfd, " returning mss = %d\n", mss)); + + return mss; +} diff --git a/slirp/tcp_output.c b/slirp/tcp_output.c new file mode 100644 index 0000000..01725db --- /dev/null +++ b/slirp/tcp_output.c @@ -0,0 +1,492 @@ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_output.c 8.3 (Berkeley) 12/30/93 + * tcp_output.c,v 1.3 1994/09/15 10:36:55 davidg Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" + +static const u_char tcp_outflags[TCP_NSTATES] = { + TH_RST|TH_ACK, 0, TH_SYN, TH_SYN|TH_ACK, + TH_ACK, TH_ACK, TH_FIN|TH_ACK, TH_FIN|TH_ACK, + TH_FIN|TH_ACK, TH_ACK, TH_ACK, +}; + + +#define MAX_TCPOPTLEN 32 /* max # bytes that go in options */ + +/* + * Tcp output routine: figure out what should be sent and send it. + */ +int +tcp_output(struct tcpcb *tp) +{ + register struct socket *so = tp->t_socket; + register long len, win; + int off, flags, error; + register struct mbuf *m; + register struct tcpiphdr *ti; + u_char opt[MAX_TCPOPTLEN]; + unsigned optlen, hdrlen; + int idle, sendalot; + + DEBUG_CALL("tcp_output"); + DEBUG_ARG("tp = %lx", (long )tp); + + /* + * Determine length of data that should be transmitted, + * and flags that will be used. + * If there is some data or critical controls (SYN, RST) + * to send, then transmit; otherwise, investigate further. + */ + idle = (tp->snd_max == tp->snd_una); + if (idle && tp->t_idle >= tp->t_rxtcur) + /* + * We have been idle for "a while" and no acks are + * expected to clock out any data we send -- + * slow start to get ack "clock" running again. + */ + tp->snd_cwnd = tp->t_maxseg; +again: + sendalot = 0; + off = tp->snd_nxt - tp->snd_una; + win = min(tp->snd_wnd, tp->snd_cwnd); + + flags = tcp_outflags[tp->t_state]; + + DEBUG_MISC((dfd, " --- tcp_output flags = 0x%x\n",flags)); + + /* + * If in persist timeout with window of 0, send 1 byte. + * Otherwise, if window is small but nonzero + * and timer expired, we will send what we can + * and go to transmit state. + */ + if (tp->t_force) { + if (win == 0) { + /* + * If we still have some data to send, then + * clear the FIN bit. Usually this would + * happen below when it realizes that we + * aren't sending all the data. However, + * if we have exactly 1 byte of unset data, + * then it won't clear the FIN bit below, + * and if we are in persist state, we wind + * up sending the packet without recording + * that we sent the FIN bit. + * + * We can't just blindly clear the FIN bit, + * because if we don't have any more data + * to send then the probe will be the FIN + * itself. + */ + if (off < so->so_snd.sb_cc) + flags &= ~TH_FIN; + win = 1; + } else { + tp->t_timer[TCPT_PERSIST] = 0; + tp->t_rxtshift = 0; + } + } + + len = min(so->so_snd.sb_cc, win) - off; + + if (len < 0) { + /* + * If FIN has been sent but not acked, + * but we haven't been called to retransmit, + * len will be -1. Otherwise, window shrank + * after we sent into it. If window shrank to 0, + * cancel pending retransmit and pull snd_nxt + * back to (closed) window. We will enter persist + * state below. If the window didn't close completely, + * just wait for an ACK. + */ + len = 0; + if (win == 0) { + tp->t_timer[TCPT_REXMT] = 0; + tp->snd_nxt = tp->snd_una; + } + } + + if (len > tp->t_maxseg) { + len = tp->t_maxseg; + sendalot = 1; + } + if (SEQ_LT(tp->snd_nxt + len, tp->snd_una + so->so_snd.sb_cc)) + flags &= ~TH_FIN; + + win = sbspace(&so->so_rcv); + + /* + * Sender silly window avoidance. If connection is idle + * and can send all data, a maximum segment, + * at least a maximum default-size segment do it, + * or are forced, do it; otherwise don't bother. + * If peer's buffer is tiny, then send + * when window is at least half open. + * If retransmitting (possibly after persist timer forced us + * to send into a small window), then must resend. + */ + if (len) { + if (len == tp->t_maxseg) + goto send; + if ((1 || idle || tp->t_flags & TF_NODELAY) && + len + off >= so->so_snd.sb_cc) + goto send; + if (tp->t_force) + goto send; + if (len >= tp->max_sndwnd / 2 && tp->max_sndwnd > 0) + goto send; + if (SEQ_LT(tp->snd_nxt, tp->snd_max)) + goto send; + } + + /* + * Compare available window to amount of window + * known to peer (as advertised window less + * next expected input). If the difference is at least two + * max size segments, or at least 50% of the maximum possible + * window, then want to send a window update to peer. + */ + if (win > 0) { + /* + * "adv" is the amount we can increase the window, + * taking into account that we are limited by + * TCP_MAXWIN << tp->rcv_scale. + */ + long adv = min(win, (long)TCP_MAXWIN << tp->rcv_scale) - + (tp->rcv_adv - tp->rcv_nxt); + + if (adv >= (long) (2 * tp->t_maxseg)) + goto send; + if (2 * adv >= (long) so->so_rcv.sb_datalen) + goto send; + } + + /* + * Send if we owe peer an ACK. + */ + if (tp->t_flags & TF_ACKNOW) + goto send; + if (flags & (TH_SYN|TH_RST)) + goto send; + if (SEQ_GT(tp->snd_up, tp->snd_una)) + goto send; + /* + * If our state indicates that FIN should be sent + * and we have not yet done so, or we're retransmitting the FIN, + * then we need to send. + */ + if (flags & TH_FIN && + ((tp->t_flags & TF_SENTFIN) == 0 || tp->snd_nxt == tp->snd_una)) + goto send; + + /* + * TCP window updates are not reliable, rather a polling protocol + * using ``persist'' packets is used to insure receipt of window + * updates. The three ``states'' for the output side are: + * idle not doing retransmits or persists + * persisting to move a small or zero window + * (re)transmitting and thereby not persisting + * + * tp->t_timer[TCPT_PERSIST] + * is set when we are in persist state. + * tp->t_force + * is set when we are called to send a persist packet. + * tp->t_timer[TCPT_REXMT] + * is set when we are retransmitting + * The output side is idle when both timers are zero. + * + * If send window is too small, there is data to transmit, and no + * retransmit or persist is pending, then go to persist state. + * If nothing happens soon, send when timer expires: + * if window is nonzero, transmit what we can, + * otherwise force out a byte. + */ + if (so->so_snd.sb_cc && tp->t_timer[TCPT_REXMT] == 0 && + tp->t_timer[TCPT_PERSIST] == 0) { + tp->t_rxtshift = 0; + tcp_setpersist(tp); + } + + /* + * No reason to send a segment, just return. + */ + return (0); + +send: + /* + * Before ESTABLISHED, force sending of initial options + * unless TCP set not to do any options. + * NOTE: we assume that the IP/TCP header plus TCP options + * always fit in a single mbuf, leaving room for a maximum + * link header, i.e. + * max_linkhdr + sizeof (struct tcpiphdr) + optlen <= MHLEN + */ + optlen = 0; + hdrlen = sizeof (struct tcpiphdr); + if (flags & TH_SYN) { + tp->snd_nxt = tp->iss; + if ((tp->t_flags & TF_NOOPT) == 0) { + uint16_t mss; + + opt[0] = TCPOPT_MAXSEG; + opt[1] = 4; + mss = htons((uint16_t) tcp_mss(tp, 0)); + memcpy((caddr_t)(opt + 2), (caddr_t)&mss, sizeof(mss)); + optlen = 4; + } + } + + hdrlen += optlen; + + /* + * Adjust data length if insertion of options will + * bump the packet length beyond the t_maxseg length. + */ + if (len > tp->t_maxseg - optlen) { + len = tp->t_maxseg - optlen; + sendalot = 1; + } + + /* + * Grab a header mbuf, attaching a copy of data to + * be transmitted, and initialize the header from + * the template for sends on this connection. + */ + if (len) { + m = m_get(so->slirp); + if (m == NULL) { + error = 1; + goto out; + } + m->m_data += IF_MAXLINKHDR; + m->m_len = hdrlen; + + sbcopy(&so->so_snd, off, (int) len, mtod(m, caddr_t) + hdrlen); + m->m_len += len; + + /* + * If we're sending everything we've got, set PUSH. + * (This will keep happy those implementations which only + * give data to the user when a buffer fills or + * a PUSH comes in.) + */ + if (off + len == so->so_snd.sb_cc) + flags |= TH_PUSH; + } else { + m = m_get(so->slirp); + if (m == NULL) { + error = 1; + goto out; + } + m->m_data += IF_MAXLINKHDR; + m->m_len = hdrlen; + } + + ti = mtod(m, struct tcpiphdr *); + + memcpy((caddr_t)ti, &tp->t_template, sizeof (struct tcpiphdr)); + + /* + * Fill in fields, remembering maximum advertised + * window for use in delaying messages about window sizes. + * If resending a FIN, be sure not to use a new sequence number. + */ + if (flags & TH_FIN && tp->t_flags & TF_SENTFIN && + tp->snd_nxt == tp->snd_max) + tp->snd_nxt--; + /* + * If we are doing retransmissions, then snd_nxt will + * not reflect the first unsent octet. For ACK only + * packets, we do not want the sequence number of the + * retransmitted packet, we want the sequence number + * of the next unsent octet. So, if there is no data + * (and no SYN or FIN), use snd_max instead of snd_nxt + * when filling in ti_seq. But if we are in persist + * state, snd_max might reflect one byte beyond the + * right edge of the window, so use snd_nxt in that + * case, since we know we aren't doing a retransmission. + * (retransmit and persist are mutually exclusive...) + */ + if (len || (flags & (TH_SYN|TH_FIN)) || tp->t_timer[TCPT_PERSIST]) + ti->ti_seq = htonl(tp->snd_nxt); + else + ti->ti_seq = htonl(tp->snd_max); + ti->ti_ack = htonl(tp->rcv_nxt); + if (optlen) { + memcpy((caddr_t)(ti + 1), (caddr_t)opt, optlen); + ti->ti_off = (sizeof (struct tcphdr) + optlen) >> 2; + } + ti->ti_flags = flags; + /* + * Calculate receive window. Don't shrink window, + * but avoid silly window syndrome. + */ + if (win < (long)(so->so_rcv.sb_datalen / 4) && win < (long)tp->t_maxseg) + win = 0; + if (win > (long)TCP_MAXWIN << tp->rcv_scale) + win = (long)TCP_MAXWIN << tp->rcv_scale; + if (win < (long)(tp->rcv_adv - tp->rcv_nxt)) + win = (long)(tp->rcv_adv - tp->rcv_nxt); + ti->ti_win = htons((uint16_t) (win>>tp->rcv_scale)); + + if (SEQ_GT(tp->snd_up, tp->snd_una)) { + ti->ti_urp = htons((uint16_t)(tp->snd_up - ntohl(ti->ti_seq))); + ti->ti_flags |= TH_URG; + } else + /* + * If no urgent pointer to send, then we pull + * the urgent pointer to the left edge of the send window + * so that it doesn't drift into the send window on sequence + * number wraparound. + */ + tp->snd_up = tp->snd_una; /* drag it along */ + + /* + * Put TCP length in extended header, and then + * checksum extended header and data. + */ + if (len + optlen) + ti->ti_len = htons((uint16_t)(sizeof (struct tcphdr) + + optlen + len)); + ti->ti_sum = cksum(m, (int)(hdrlen + len)); + + /* + * In transmit state, time the transmission and arrange for + * the retransmit. In persist state, just set snd_max. + */ + if (tp->t_force == 0 || tp->t_timer[TCPT_PERSIST] == 0) { + tcp_seq startseq = tp->snd_nxt; + + /* + * Advance snd_nxt over sequence space of this segment. + */ + if (flags & (TH_SYN|TH_FIN)) { + if (flags & TH_SYN) + tp->snd_nxt++; + if (flags & TH_FIN) { + tp->snd_nxt++; + tp->t_flags |= TF_SENTFIN; + } + } + tp->snd_nxt += len; + if (SEQ_GT(tp->snd_nxt, tp->snd_max)) { + tp->snd_max = tp->snd_nxt; + /* + * Time this transmission if not a retransmission and + * not currently timing anything. + */ + if (tp->t_rtt == 0) { + tp->t_rtt = 1; + tp->t_rtseq = startseq; + } + } + + /* + * Set retransmit timer if not currently set, + * and not doing an ack or a keep-alive probe. + * Initial value for retransmit timer is smoothed + * round-trip time + 2 * round-trip time variance. + * Initialize shift counter which is used for backoff + * of retransmit time. + */ + if (tp->t_timer[TCPT_REXMT] == 0 && + tp->snd_nxt != tp->snd_una) { + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + if (tp->t_timer[TCPT_PERSIST]) { + tp->t_timer[TCPT_PERSIST] = 0; + tp->t_rxtshift = 0; + } + } + } else + if (SEQ_GT(tp->snd_nxt + len, tp->snd_max)) + tp->snd_max = tp->snd_nxt + len; + + /* + * Fill in IP length and desired time to live and + * send to IP level. There should be a better way + * to handle ttl and tos; we could keep them in + * the template, but need a way to checksum without them. + */ + m->m_len = hdrlen + len; /* XXX Needed? m_len should be correct */ + + { + + ((struct ip *)ti)->ip_len = m->m_len; + + ((struct ip *)ti)->ip_ttl = IPDEFTTL; + ((struct ip *)ti)->ip_tos = so->so_iptos; + + error = ip_output(so, m); + } + if (error) { +out: + return (error); + } + + /* + * Data sent (as far as we can tell). + * If this advertises a larger window than any other segment, + * then remember the size of the advertised window. + * Any pending ACK has now been sent. + */ + if (win > 0 && SEQ_GT(tp->rcv_nxt+win, tp->rcv_adv)) + tp->rcv_adv = tp->rcv_nxt + win; + tp->last_ack_sent = tp->rcv_nxt; + tp->t_flags &= ~(TF_ACKNOW|TF_DELACK); + if (sendalot) + goto again; + + return (0); +} + +void +tcp_setpersist(struct tcpcb *tp) +{ + int t = ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1; + + /* + * Start/restart persistence timer. + */ + TCPT_RANGESET(tp->t_timer[TCPT_PERSIST], + t * tcp_backoff[tp->t_rxtshift], + TCPTV_PERSMIN, TCPTV_PERSMAX); + if (tp->t_rxtshift < TCP_MAXRXTSHIFT) + tp->t_rxtshift++; +} diff --git a/slirp/tcp_subr.c b/slirp/tcp_subr.c new file mode 100644 index 0000000..1557e89 --- /dev/null +++ b/slirp/tcp_subr.c @@ -0,0 +1,915 @@ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_subr.c 8.1 (Berkeley) 6/10/93 + * tcp_subr.c,v 1.5 1994/10/08 22:39:58 phk Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" + +/* patchable/settable parameters for tcp */ +/* Don't do rfc1323 performance enhancements */ +#define TCP_DO_RFC1323 0 + +/* + * Tcp initialization + */ +void +tcp_init(Slirp *slirp) +{ + slirp->tcp_iss = 1; /* wrong */ + slirp->tcb.so_next = slirp->tcb.so_prev = &slirp->tcb; + slirp->tcp_last_so = &slirp->tcb; +} + +/* + * Create template to be used to send tcp packets on a connection. + * Call after host entry created, fills + * in a skeletal tcp/ip header, minimizing the amount of work + * necessary when the connection is used. + */ +void +tcp_template(struct tcpcb *tp) +{ + struct socket *so = tp->t_socket; + register struct tcpiphdr *n = &tp->t_template; + + n->ti_mbuf = NULL; + n->ti_x1 = 0; + n->ti_pr = IPPROTO_TCP; + n->ti_len = htons(sizeof (struct tcpiphdr) - sizeof (struct ip)); + n->ti_src = so->so_faddr; + n->ti_dst = so->so_laddr; + n->ti_sport = so->so_fport; + n->ti_dport = so->so_lport; + + n->ti_seq = 0; + n->ti_ack = 0; + n->ti_x2 = 0; + n->ti_off = 5; + n->ti_flags = 0; + n->ti_win = 0; + n->ti_sum = 0; + n->ti_urp = 0; +} + +/* + * Send a single message to the TCP at address specified by + * the given TCP/IP header. If m == 0, then we make a copy + * of the tcpiphdr at ti and send directly to the addressed host. + * This is used to force keep alive messages out using the TCP + * template for a connection tp->t_template. If flags are given + * then we send a message back to the TCP which originated the + * segment ti, and discard the mbuf containing it and any other + * attached mbufs. + * + * In any case the ack and sequence number of the transmitted + * segment are as specified by the parameters. + */ +void +tcp_respond(struct tcpcb *tp, struct tcpiphdr *ti, struct mbuf *m, + tcp_seq ack, tcp_seq seq, int flags) +{ + register int tlen; + int win = 0; + + DEBUG_CALL("tcp_respond"); + DEBUG_ARG("tp = %lx", (long)tp); + DEBUG_ARG("ti = %lx", (long)ti); + DEBUG_ARG("m = %lx", (long)m); + DEBUG_ARG("ack = %u", ack); + DEBUG_ARG("seq = %u", seq); + DEBUG_ARG("flags = %x", flags); + + if (tp) + win = sbspace(&tp->t_socket->so_rcv); + if (m == NULL) { + if ((m = m_get(tp->t_socket->slirp)) == NULL) + return; + tlen = 0; + m->m_data += IF_MAXLINKHDR; + *mtod(m, struct tcpiphdr *) = *ti; + ti = mtod(m, struct tcpiphdr *); + flags = TH_ACK; + } else { + /* + * ti points into m so the next line is just making + * the mbuf point to ti + */ + m->m_data = (caddr_t)ti; + + m->m_len = sizeof (struct tcpiphdr); + tlen = 0; +#define xchg(a,b,type) { type t; t=a; a=b; b=t; } + xchg(ti->ti_dst.s_addr, ti->ti_src.s_addr, uint32_t); + xchg(ti->ti_dport, ti->ti_sport, uint16_t); +#undef xchg + } + ti->ti_len = htons((u_short)(sizeof (struct tcphdr) + tlen)); + tlen += sizeof (struct tcpiphdr); + m->m_len = tlen; + + ti->ti_mbuf = NULL; + ti->ti_x1 = 0; + ti->ti_seq = htonl(seq); + ti->ti_ack = htonl(ack); + ti->ti_x2 = 0; + ti->ti_off = sizeof (struct tcphdr) >> 2; + ti->ti_flags = flags; + if (tp) + ti->ti_win = htons((uint16_t) (win >> tp->rcv_scale)); + else + ti->ti_win = htons((uint16_t)win); + ti->ti_urp = 0; + ti->ti_sum = 0; + ti->ti_sum = cksum(m, tlen); + ((struct ip *)ti)->ip_len = tlen; + + if(flags & TH_RST) + ((struct ip *)ti)->ip_ttl = MAXTTL; + else + ((struct ip *)ti)->ip_ttl = IPDEFTTL; + + (void) ip_output((struct socket *)0, m); +} + +/* + * Create a new TCP control block, making an + * empty reassembly queue and hooking it to the argument + * protocol control block. + */ +struct tcpcb * +tcp_newtcpcb(struct socket *so) +{ + register struct tcpcb *tp; + + tp = (struct tcpcb *)malloc(sizeof(*tp)); + if (tp == NULL) + return ((struct tcpcb *)0); + + memset((char *) tp, 0, sizeof(struct tcpcb)); + tp->seg_next = tp->seg_prev = (struct tcpiphdr*)tp; + tp->t_maxseg = TCP_MSS; + + tp->t_flags = TCP_DO_RFC1323 ? (TF_REQ_SCALE|TF_REQ_TSTMP) : 0; + tp->t_socket = so; + + /* + * Init srtt to TCPTV_SRTTBASE (0), so we can tell that we have no + * rtt estimate. Set rttvar so that srtt + 2 * rttvar gives + * reasonable initial retransmit time. + */ + tp->t_srtt = TCPTV_SRTTBASE; + tp->t_rttvar = TCPTV_SRTTDFLT << 2; + tp->t_rttmin = TCPTV_MIN; + + TCPT_RANGESET(tp->t_rxtcur, + ((TCPTV_SRTTBASE >> 2) + (TCPTV_SRTTDFLT << 2)) >> 1, + TCPTV_MIN, TCPTV_REXMTMAX); + + tp->snd_cwnd = TCP_MAXWIN << TCP_MAX_WINSHIFT; + tp->snd_ssthresh = TCP_MAXWIN << TCP_MAX_WINSHIFT; + tp->t_state = TCPS_CLOSED; + + so->so_tcpcb = tp; + + return (tp); +} + +/* + * Drop a TCP connection, reporting + * the specified error. If connection is synchronized, + * then send a RST to peer. + */ +struct tcpcb *tcp_drop(struct tcpcb *tp, int err) +{ + DEBUG_CALL("tcp_drop"); + DEBUG_ARG("tp = %lx", (long)tp); + DEBUG_ARG("errno = %d", errno); + + if (TCPS_HAVERCVDSYN(tp->t_state)) { + tp->t_state = TCPS_CLOSED; + (void) tcp_output(tp); + } + return (tcp_close(tp)); +} + +/* + * Close a TCP control block: + * discard all space held by the tcp + * discard internet protocol block + * wake up any sleepers + */ +struct tcpcb * +tcp_close(struct tcpcb *tp) +{ + register struct tcpiphdr *t; + struct socket *so = tp->t_socket; + Slirp *slirp = so->slirp; + register struct mbuf *m; + + DEBUG_CALL("tcp_close"); + DEBUG_ARG("tp = %lx", (long )tp); + + /* free the reassembly queue, if any */ + t = tcpfrag_list_first(tp); + while (!tcpfrag_list_end(t, tp)) { + t = tcpiphdr_next(t); + m = tcpiphdr_prev(t)->ti_mbuf; + remque(tcpiphdr2qlink(tcpiphdr_prev(t))); + m_freem(m); + } + free(tp); + so->so_tcpcb = NULL; + /* clobber input socket cache if we're closing the cached connection */ + if (so == slirp->tcp_last_so) + slirp->tcp_last_so = &slirp->tcb; + closesocket(so->s); + sbfree(&so->so_rcv); + sbfree(&so->so_snd); + sofree(so); + return ((struct tcpcb *)0); +} + +/* + * TCP protocol interface to socket abstraction. + */ + +/* + * User issued close, and wish to trail through shutdown states: + * if never received SYN, just forget it. If got a SYN from peer, + * but haven't sent FIN, then go to FIN_WAIT_1 state to send peer a FIN. + * If already got a FIN from peer, then almost done; go to LAST_ACK + * state. In all other cases, have already sent FIN to peer (e.g. + * after PRU_SHUTDOWN), and just have to play tedious game waiting + * for peer to send FIN or not respond to keep-alives, etc. + * We can let the user exit from the close as soon as the FIN is acked. + */ +void +tcp_sockclosed(struct tcpcb *tp) +{ + + DEBUG_CALL("tcp_sockclosed"); + DEBUG_ARG("tp = %lx", (long)tp); + + switch (tp->t_state) { + + case TCPS_CLOSED: + case TCPS_LISTEN: + case TCPS_SYN_SENT: + tp->t_state = TCPS_CLOSED; + tp = tcp_close(tp); + break; + + case TCPS_SYN_RECEIVED: + case TCPS_ESTABLISHED: + tp->t_state = TCPS_FIN_WAIT_1; + break; + + case TCPS_CLOSE_WAIT: + tp->t_state = TCPS_LAST_ACK; + break; + } + if (tp) + tcp_output(tp); +} + +/* + * Connect to a host on the Internet + * Called by tcp_input + * Only do a connect, the tcp fields will be set in tcp_input + * return 0 if there's a result of the connect, + * else return -1 means we're still connecting + * The return value is almost always -1 since the socket is + * nonblocking. Connect returns after the SYN is sent, and does + * not wait for ACK+SYN. + */ +int tcp_fconnect(struct socket *so) +{ + Slirp *slirp = so->slirp; + int ret=0; + + DEBUG_CALL("tcp_fconnect"); + DEBUG_ARG("so = %lx", (long )so); + + if( (ret = so->s = os_socket(AF_INET,SOCK_STREAM,0)) >= 0) { + int opt, s=so->s; + struct sockaddr_in addr; + + fd_nonblock(s); + opt = 1; + setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char *)&opt,sizeof(opt )); + opt = 1; + setsockopt(s,SOL_SOCKET,SO_OOBINLINE,(char *)&opt,sizeof(opt )); + + addr.sin_family = AF_INET; + if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + /* It's an alias */ + if (so->so_faddr.s_addr == slirp->vnameserver_addr.s_addr) { + if (get_dns_addr(&addr.sin_addr) < 0) + addr.sin_addr = loopback_addr; + } else { + addr.sin_addr = loopback_addr; + } + } else + addr.sin_addr = so->so_faddr; + addr.sin_port = so->so_fport; + + DEBUG_MISC((dfd, " connect()ing, addr.sin_port=%d, " + "addr.sin_addr.s_addr=%.16s\n", + ntohs(addr.sin_port), inet_ntoa(addr.sin_addr))); + /* We don't care what port we get */ + ret = connect(s,(struct sockaddr *)&addr,sizeof (addr)); + + /* + * If it's not in progress, it failed, so we just return 0, + * without clearing SS_NOFDREF + */ + soisfconnecting(so); + } + + return(ret); +} + +/* + * Accept the socket and connect to the local-host + * + * We have a problem. The correct thing to do would be + * to first connect to the local-host, and only if the + * connection is accepted, then do an accept() here. + * But, a) we need to know who's trying to connect + * to the socket to be able to SYN the local-host, and + * b) we are already connected to the foreign host by + * the time it gets to accept(), so... We simply accept + * here and SYN the local-host. + */ +void +tcp_connect(struct socket *inso) +{ + Slirp *slirp = inso->slirp; + struct socket *so; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(struct sockaddr_in); + struct tcpcb *tp; + int s, opt; + + DEBUG_CALL("tcp_connect"); + DEBUG_ARG("inso = %lx", (long)inso); + + /* + * If it's an SS_ACCEPTONCE socket, no need to socreate() + * another socket, just use the accept() socket. + */ + if (inso->so_state & SS_FACCEPTONCE) { + /* FACCEPTONCE already have a tcpcb */ + so = inso; + } else { + if ((so = socreate(slirp)) == NULL) { + /* If it failed, get rid of the pending connection */ + closesocket(accept(inso->s,(struct sockaddr *)&addr,&addrlen)); + return; + } + if (tcp_attach(so) < 0) { + free(so); /* NOT sofree */ + return; + } + so->so_laddr = inso->so_laddr; + so->so_lport = inso->so_lport; + } + + (void) tcp_mss(sototcpcb(so), 0); + + if ((s = accept(inso->s,(struct sockaddr *)&addr,&addrlen)) < 0) { + tcp_close(sototcpcb(so)); /* This will sofree() as well */ + return; + } + fd_nonblock(s); + opt = 1; + setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char *)&opt,sizeof(int)); + opt = 1; + setsockopt(s,SOL_SOCKET,SO_OOBINLINE,(char *)&opt,sizeof(int)); + opt = 1; + setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char *)&opt,sizeof(int)); + + so->so_fport = addr.sin_port; + so->so_faddr = addr.sin_addr; + /* Translate connections from localhost to the real hostname */ + if (so->so_faddr.s_addr == 0 || so->so_faddr.s_addr == loopback_addr.s_addr) + so->so_faddr = slirp->vhost_addr; + + /* Close the accept() socket, set right state */ + if (inso->so_state & SS_FACCEPTONCE) { + closesocket(so->s); /* If we only accept once, close the accept() socket */ + so->so_state = SS_NOFDREF; /* Don't select it yet, even though we have an FD */ + /* if it's not FACCEPTONCE, it's already NOFDREF */ + } + so->s = s; + so->so_state |= SS_INCOMING; + + so->so_iptos = tcp_tos(so); + tp = sototcpcb(so); + + tcp_template(tp); + + tp->t_state = TCPS_SYN_SENT; + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; + tp->iss = slirp->tcp_iss; + slirp->tcp_iss += TCP_ISSINCR/2; + tcp_sendseqinit(tp); + tcp_output(tp); +} + +/* + * Attach a TCPCB to a socket. + */ +int +tcp_attach(struct socket *so) +{ + if ((so->so_tcpcb = tcp_newtcpcb(so)) == NULL) + return -1; + + insque(so, &so->slirp->tcb); + + return 0; +} + +/* + * Set the socket's type of service field + */ +static const struct tos_t tcptos[] = { + {0, 20, IPTOS_THROUGHPUT, 0}, /* ftp data */ + {21, 21, IPTOS_LOWDELAY, EMU_FTP}, /* ftp control */ + {0, 23, IPTOS_LOWDELAY, 0}, /* telnet */ + {0, 80, IPTOS_THROUGHPUT, 0}, /* WWW */ + {0, 513, IPTOS_LOWDELAY, EMU_RLOGIN|EMU_NOCONNECT}, /* rlogin */ + {0, 514, IPTOS_LOWDELAY, EMU_RSH|EMU_NOCONNECT}, /* shell */ + {0, 544, IPTOS_LOWDELAY, EMU_KSH}, /* kshell */ + {0, 543, IPTOS_LOWDELAY, 0}, /* klogin */ + {0, 6667, IPTOS_THROUGHPUT, EMU_IRC}, /* IRC */ + {0, 6668, IPTOS_THROUGHPUT, EMU_IRC}, /* IRC undernet */ + {0, 7070, IPTOS_LOWDELAY, EMU_REALAUDIO }, /* RealAudio control */ + {0, 113, IPTOS_LOWDELAY, EMU_IDENT }, /* identd protocol */ + {0, 0, 0, 0} +}; + +static struct emu_t *tcpemu = NULL; + +/* + * Return TOS according to the above table + */ +uint8_t +tcp_tos(struct socket *so) +{ + int i = 0; + struct emu_t *emup; + + while(tcptos[i].tos) { + if ((tcptos[i].fport && (ntohs(so->so_fport) == tcptos[i].fport)) || + (tcptos[i].lport && (ntohs(so->so_lport) == tcptos[i].lport))) { + so->so_emu = tcptos[i].emu; + return tcptos[i].tos; + } + i++; + } + + /* Nope, lets see if there's a user-added one */ + for (emup = tcpemu; emup; emup = emup->next) { + if ((emup->fport && (ntohs(so->so_fport) == emup->fport)) || + (emup->lport && (ntohs(so->so_lport) == emup->lport))) { + so->so_emu = emup->emu; + return emup->tos; + } + } + + return 0; +} + +/* + * Emulate programs that try and connect to us + * This includes ftp (the data connection is + * initiated by the server) and IRC (DCC CHAT and + * DCC SEND) for now + * + * NOTE: It's possible to crash SLiRP by sending it + * unstandard strings to emulate... if this is a problem, + * more checks are needed here + * + * XXX Assumes the whole command came in one packet + * + * XXX Some ftp clients will have their TOS set to + * LOWDELAY and so Nagel will kick in. Because of this, + * we'll get the first letter, followed by the rest, so + * we simply scan for ORT instead of PORT... + * DCC doesn't have this problem because there's other stuff + * in the packet before the DCC command. + * + * Return 1 if the mbuf m is still valid and should be + * sbappend()ed + * + * NOTE: if you return 0 you MUST m_free() the mbuf! + */ +int +tcp_emu(struct socket *so, struct mbuf *m) +{ + Slirp *slirp = so->slirp; + u_int n1, n2, n3, n4, n5, n6; + char buff[257]; + uint32_t laddr; + u_int lport; + char *bptr; + + DEBUG_CALL("tcp_emu"); + DEBUG_ARG("so = %lx", (long)so); + DEBUG_ARG("m = %lx", (long)m); + + switch(so->so_emu) { + int x, i; + + case EMU_IDENT: + /* + * Identification protocol as per rfc-1413 + */ + + { + struct socket *tmpso; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(struct sockaddr_in); + struct sbuf *so_rcv = &so->so_rcv; + + memcpy(so_rcv->sb_wptr, m->m_data, m->m_len); + so_rcv->sb_wptr += m->m_len; + so_rcv->sb_rptr += m->m_len; + m->m_data[m->m_len] = 0; /* NULL terminate */ + if (strchr(m->m_data, '\r') || strchr(m->m_data, '\n')) { + if (sscanf(so_rcv->sb_data, "%u%*[ ,]%u", &n1, &n2) == 2) { + HTONS(n1); + HTONS(n2); + /* n2 is the one on our host */ + for (tmpso = slirp->tcb.so_next; + tmpso != &slirp->tcb; + tmpso = tmpso->so_next) { + if (tmpso->so_laddr.s_addr == so->so_laddr.s_addr && + tmpso->so_lport == n2 && + tmpso->so_faddr.s_addr == so->so_faddr.s_addr && + tmpso->so_fport == n1) { + if (getsockname(tmpso->s, + (struct sockaddr *)&addr, &addrlen) == 0) + n2 = ntohs(addr.sin_port); + break; + } + } + } + so_rcv->sb_cc = snprintf(so_rcv->sb_data, + so_rcv->sb_datalen, + "%d,%d\r\n", n1, n2); + so_rcv->sb_rptr = so_rcv->sb_data; + so_rcv->sb_wptr = so_rcv->sb_data + so_rcv->sb_cc; + } + m_free(m); + return 0; + } + + case EMU_FTP: /* ftp */ + *(m->m_data+m->m_len) = 0; /* NUL terminate for strstr */ + if ((bptr = (char *)strstr(m->m_data, "ORT")) != NULL) { + /* + * Need to emulate the PORT command + */ + x = sscanf(bptr, "ORT %u,%u,%u,%u,%u,%u\r\n%256[^\177]", + &n1, &n2, &n3, &n4, &n5, &n6, buff); + if (x < 6) + return 1; + + laddr = htonl((n1 << 24) | (n2 << 16) | (n3 << 8) | (n4)); + lport = htons((n5 << 8) | (n6)); + + if ((so = tcp_listen(slirp, INADDR_ANY, 0, laddr, + lport, SS_FACCEPTONCE)) == NULL) { + return 1; + } + n6 = ntohs(so->so_fport); + + n5 = (n6 >> 8) & 0xff; + n6 &= 0xff; + + laddr = ntohl(so->so_faddr.s_addr); + + n1 = ((laddr >> 24) & 0xff); + n2 = ((laddr >> 16) & 0xff); + n3 = ((laddr >> 8) & 0xff); + n4 = (laddr & 0xff); + + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += snprintf(bptr, m->m_hdr.mh_size - m->m_len, + "ORT %d,%d,%d,%d,%d,%d\r\n%s", + n1, n2, n3, n4, n5, n6, x==7?buff:""); + return 1; + } else if ((bptr = (char *)strstr(m->m_data, "27 Entering")) != NULL) { + /* + * Need to emulate the PASV response + */ + x = sscanf(bptr, "27 Entering Passive Mode (%u,%u,%u,%u,%u,%u)\r\n%256[^\177]", + &n1, &n2, &n3, &n4, &n5, &n6, buff); + if (x < 6) + return 1; + + laddr = htonl((n1 << 24) | (n2 << 16) | (n3 << 8) | (n4)); + lport = htons((n5 << 8) | (n6)); + + if ((so = tcp_listen(slirp, INADDR_ANY, 0, laddr, + lport, SS_FACCEPTONCE)) == NULL) { + return 1; + } + n6 = ntohs(so->so_fport); + + n5 = (n6 >> 8) & 0xff; + n6 &= 0xff; + + laddr = ntohl(so->so_faddr.s_addr); + + n1 = ((laddr >> 24) & 0xff); + n2 = ((laddr >> 16) & 0xff); + n3 = ((laddr >> 8) & 0xff); + n4 = (laddr & 0xff); + + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += snprintf(bptr, m->m_hdr.mh_size - m->m_len, + "27 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n%s", + n1, n2, n3, n4, n5, n6, x==7?buff:""); + + return 1; + } + + return 1; + + case EMU_KSH: + /* + * The kshell (Kerberos rsh) and shell services both pass + * a local port port number to carry signals to the server + * and stderr to the client. It is passed at the beginning + * of the connection as a NUL-terminated decimal ASCII string. + */ + so->so_emu = 0; + for (lport = 0, i = 0; i < m->m_len-1; ++i) { + if (m->m_data[i] < '0' || m->m_data[i] > '9') + return 1; /* invalid number */ + lport *= 10; + lport += m->m_data[i] - '0'; + } + if (m->m_data[m->m_len-1] == '\0' && lport != 0 && + (so = tcp_listen(slirp, INADDR_ANY, 0, so->so_laddr.s_addr, + htons(lport), SS_FACCEPTONCE)) != NULL) + m->m_len = snprintf(m->m_data, m->m_hdr.mh_size, "%d", + ntohs(so->so_fport)) + 1; + return 1; + + case EMU_IRC: + /* + * Need to emulate DCC CHAT, DCC SEND and DCC MOVE + */ + *(m->m_data+m->m_len) = 0; /* NULL terminate the string for strstr */ + if ((bptr = (char *)strstr(m->m_data, "DCC")) == NULL) + return 1; + + /* The %256s is for the broken mIRC */ + if (sscanf(bptr, "DCC CHAT %256s %u %u", buff, &laddr, &lport) == 3) { + if ((so = tcp_listen(slirp, INADDR_ANY, 0, + htonl(laddr), htons(lport), + SS_FACCEPTONCE)) == NULL) { + return 1; + } + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += snprintf(bptr, m->m_hdr.mh_size, + "DCC CHAT chat %lu %u%c\n", + (unsigned long)ntohl(so->so_faddr.s_addr), + ntohs(so->so_fport), 1); + } else if (sscanf(bptr, "DCC SEND %256s %u %u %u", buff, &laddr, &lport, &n1) == 4) { + if ((so = tcp_listen(slirp, INADDR_ANY, 0, + htonl(laddr), htons(lport), + SS_FACCEPTONCE)) == NULL) { + return 1; + } + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += snprintf(bptr, m->m_hdr.mh_size, + "DCC SEND %s %lu %u %u%c\n", buff, + (unsigned long)ntohl(so->so_faddr.s_addr), + ntohs(so->so_fport), n1, 1); + } else if (sscanf(bptr, "DCC MOVE %256s %u %u %u", buff, &laddr, &lport, &n1) == 4) { + if ((so = tcp_listen(slirp, INADDR_ANY, 0, + htonl(laddr), htons(lport), + SS_FACCEPTONCE)) == NULL) { + return 1; + } + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += snprintf(bptr, m->m_hdr.mh_size, + "DCC MOVE %s %lu %u %u%c\n", buff, + (unsigned long)ntohl(so->so_faddr.s_addr), + ntohs(so->so_fport), n1, 1); + } + return 1; + + case EMU_REALAUDIO: + /* + * RealAudio emulation - JP. We must try to parse the incoming + * data and try to find the two characters that contain the + * port number. Then we redirect an udp port and replace the + * number with the real port we got. + * + * The 1.0 beta versions of the player are not supported + * any more. + * + * A typical packet for player version 1.0 (release version): + * + * 0000:50 4E 41 00 05 + * 0000:00 01 00 02 1B D7 00 00 67 E6 6C DC 63 00 12 50 ........g.l.c..P + * 0010:4E 43 4C 49 45 4E 54 20 31 30 31 20 41 4C 50 48 NCLIENT 101 ALPH + * 0020:41 6C 00 00 52 00 17 72 61 66 69 6C 65 73 2F 76 Al..R..rafiles/v + * 0030:6F 61 2F 65 6E 67 6C 69 73 68 5F 2E 72 61 79 42 oa/english_.rayB + * + * Now the port number 0x1BD7 is found at offset 0x04 of the + * Now the port number 0x1BD7 is found at offset 0x04 of the + * second packet. This time we received five bytes first and + * then the rest. You never know how many bytes you get. + * + * A typical packet for player version 2.0 (beta): + * + * 0000:50 4E 41 00 06 00 02 00 00 00 01 00 02 1B C1 00 PNA............. + * 0010:00 67 75 78 F5 63 00 0A 57 69 6E 32 2E 30 2E 30 .gux.c..Win2.0.0 + * 0020:2E 35 6C 00 00 52 00 1C 72 61 66 69 6C 65 73 2F .5l..R..rafiles/ + * 0030:77 65 62 73 69 74 65 2F 32 30 72 65 6C 65 61 73 website/20releas + * 0040:65 2E 72 61 79 53 00 00 06 36 42 e.rayS...6B + * + * Port number 0x1BC1 is found at offset 0x0d. + * + * This is just a horrible switch statement. Variable ra tells + * us where we're going. + */ + + bptr = m->m_data; + while (bptr < m->m_data + m->m_len) { + u_short p; + static int ra = 0; + char ra_tbl[4]; + + ra_tbl[0] = 0x50; + ra_tbl[1] = 0x4e; + ra_tbl[2] = 0x41; + ra_tbl[3] = 0; + + switch (ra) { + case 0: + case 2: + case 3: + if (*bptr++ != ra_tbl[ra]) { + ra = 0; + continue; + } + break; + + case 1: + /* + * We may get 0x50 several times, ignore them + */ + if (*bptr == 0x50) { + ra = 1; + bptr++; + continue; + } else if (*bptr++ != ra_tbl[ra]) { + ra = 0; + continue; + } + break; + + case 4: + /* + * skip version number + */ + bptr++; + break; + + case 5: + /* + * The difference between versions 1.0 and + * 2.0 is here. For future versions of + * the player this may need to be modified. + */ + if (*(bptr + 1) == 0x02) + bptr += 8; + else + bptr += 4; + break; + + case 6: + /* This is the field containing the port + * number that RA-player is listening to. + */ + lport = (((u_char*)bptr)[0] << 8) + + ((u_char *)bptr)[1]; + if (lport < 6970) + lport += 256; /* don't know why */ + if (lport < 6970 || lport > 7170) + return 1; /* failed */ + + /* try to get udp port between 6970 - 7170 */ + for (p = 6970; p < 7071; p++) { + if (udp_listen(slirp, INADDR_ANY, + htons(p), + so->so_laddr.s_addr, + htons(lport), + SS_FACCEPTONCE)) { + break; + } + } + if (p == 7071) + p = 0; + *(u_char *)bptr++ = (p >> 8) & 0xff; + *(u_char *)bptr = p & 0xff; + ra = 0; + return 1; /* port redirected, we're done */ + break; + + default: + ra = 0; + } + ra++; + } + return 1; + + default: + /* Ooops, not emulated, won't call tcp_emu again */ + so->so_emu = 0; + return 1; + } +} + +/* + * Do misc. config of SLiRP while its running. + * Return 0 if this connections is to be closed, 1 otherwise, + * return 2 if this is a command-line connection + */ +int tcp_ctl(struct socket *so) +{ + Slirp *slirp = so->slirp; + struct sbuf *sb = &so->so_snd; + struct ex_list *ex_ptr; + int do_pty; + + DEBUG_CALL("tcp_ctl"); + DEBUG_ARG("so = %lx", (long )so); + + if (so->so_faddr.s_addr != slirp->vhost_addr.s_addr) { + /* Check if it's pty_exec */ + for (ex_ptr = slirp->exec_list; ex_ptr; ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->ex_fport == so->so_fport && + so->so_faddr.s_addr == ex_ptr->ex_addr.s_addr) { + if (ex_ptr->ex_pty == 3) { + so->s = -1; + so->extra = (void *)ex_ptr->ex_exec; + return 1; + } + do_pty = ex_ptr->ex_pty; + DEBUG_MISC((dfd, " executing %s \n",ex_ptr->ex_exec)); + return fork_exec(so, ex_ptr->ex_exec, do_pty); + } + } + } + sb->sb_cc = + snprintf(sb->sb_wptr, sb->sb_datalen - (sb->sb_wptr - sb->sb_data), + "Error: No application configured.\r\n"); + sb->sb_wptr += sb->sb_cc; + return 0; +} diff --git a/slirp/tcp_timer.c b/slirp/tcp_timer.c new file mode 100644 index 0000000..1e6e051 --- /dev/null +++ b/slirp/tcp_timer.c @@ -0,0 +1,292 @@ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_timer.c 8.1 (Berkeley) 6/10/93 + * tcp_timer.c,v 1.2 1994/08/02 07:49:10 davidg Exp + */ + +#include "slirp.h" + +static struct tcpcb *tcp_timers(register struct tcpcb *tp, int timer); + +/* + * Fast timeout routine for processing delayed acks + */ +void +tcp_fasttimo(Slirp *slirp) +{ + register struct socket *so; + register struct tcpcb *tp; + + DEBUG_CALL("tcp_fasttimo"); + + so = slirp->tcb.so_next; + if (so) + for (; so != &slirp->tcb; so = so->so_next) + if ((tp = (struct tcpcb *)so->so_tcpcb) && + (tp->t_flags & TF_DELACK)) { + tp->t_flags &= ~TF_DELACK; + tp->t_flags |= TF_ACKNOW; + (void) tcp_output(tp); + } +} + +/* + * Tcp protocol timeout routine called every 500 ms. + * Updates the timers in all active tcb's and + * causes finite state machine actions if timers expire. + */ +void +tcp_slowtimo(Slirp *slirp) +{ + register struct socket *ip, *ipnxt; + register struct tcpcb *tp; + register int i; + + DEBUG_CALL("tcp_slowtimo"); + + /* + * Search through tcb's and update active timers. + */ + ip = slirp->tcb.so_next; + if (ip == NULL) { + return; + } + for (; ip != &slirp->tcb; ip = ipnxt) { + ipnxt = ip->so_next; + tp = sototcpcb(ip); + if (tp == NULL) { + continue; + } + for (i = 0; i < TCPT_NTIMERS; i++) { + if (tp->t_timer[i] && --tp->t_timer[i] == 0) { + tcp_timers(tp,i); + if (ipnxt->so_prev != ip) + goto tpgone; + } + } + tp->t_idle++; + if (tp->t_rtt) + tp->t_rtt++; +tpgone: + ; + } + slirp->tcp_iss += TCP_ISSINCR/PR_SLOWHZ; /* increment iss */ + slirp->tcp_now++; /* for timestamps */ +} + +/* + * Cancel all timers for TCP tp. + */ +void +tcp_canceltimers(struct tcpcb *tp) +{ + register int i; + + for (i = 0; i < TCPT_NTIMERS; i++) + tp->t_timer[i] = 0; +} + +const int tcp_backoff[TCP_MAXRXTSHIFT + 1] = + { 1, 2, 4, 8, 16, 32, 64, 64, 64, 64, 64, 64, 64 }; + +/* + * TCP timer processing. + */ +static struct tcpcb * +tcp_timers(register struct tcpcb *tp, int timer) +{ + register int rexmt; + + DEBUG_CALL("tcp_timers"); + + switch (timer) { + + /* + * 2 MSL timeout in shutdown went off. If we're closed but + * still waiting for peer to close and connection has been idle + * too long, or if 2MSL time is up from TIME_WAIT, delete connection + * control block. Otherwise, check again in a bit. + */ + case TCPT_2MSL: + if (tp->t_state != TCPS_TIME_WAIT && + tp->t_idle <= TCP_MAXIDLE) + tp->t_timer[TCPT_2MSL] = TCPTV_KEEPINTVL; + else + tp = tcp_close(tp); + break; + + /* + * Retransmission timer went off. Message has not + * been acked within retransmit interval. Back off + * to a longer retransmit interval and retransmit one segment. + */ + case TCPT_REXMT: + + /* + * XXXXX If a packet has timed out, then remove all the queued + * packets for that session. + */ + + if (++tp->t_rxtshift > TCP_MAXRXTSHIFT) { + /* + * This is a hack to suit our terminal server here at the uni of canberra + * since they have trouble with zeroes... It usually lets them through + * unharmed, but under some conditions, it'll eat the zeros. If we + * keep retransmitting it, it'll keep eating the zeroes, so we keep + * retransmitting, and eventually the connection dies... + * (this only happens on incoming data) + * + * So, if we were gonna drop the connection from too many retransmits, + * don't... instead halve the t_maxseg, which might break up the NULLs and + * let them through + * + * *sigh* + */ + + tp->t_maxseg >>= 1; + if (tp->t_maxseg < 32) { + /* + * We tried our best, now the connection must die! + */ + tp->t_rxtshift = TCP_MAXRXTSHIFT; + tp = tcp_drop(tp, tp->t_softerror); + /* tp->t_softerror : ETIMEDOUT); */ /* XXX */ + return (tp); /* XXX */ + } + + /* + * Set rxtshift to 6, which is still at the maximum + * backoff time + */ + tp->t_rxtshift = 6; + } + rexmt = TCP_REXMTVAL(tp) * tcp_backoff[tp->t_rxtshift]; + TCPT_RANGESET(tp->t_rxtcur, rexmt, + (short)tp->t_rttmin, TCPTV_REXMTMAX); /* XXX */ + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + /* + * If losing, let the lower level know and try for + * a better route. Also, if we backed off this far, + * our srtt estimate is probably bogus. Clobber it + * so we'll take the next rtt measurement as our srtt; + * move the current srtt into rttvar to keep the current + * retransmit times until then. + */ + if (tp->t_rxtshift > TCP_MAXRXTSHIFT / 4) { + tp->t_rttvar += (tp->t_srtt >> TCP_RTT_SHIFT); + tp->t_srtt = 0; + } + tp->snd_nxt = tp->snd_una; + /* + * If timing a segment in this window, stop the timer. + */ + tp->t_rtt = 0; + /* + * Close the congestion window down to one segment + * (we'll open it by one segment for each ack we get). + * Since we probably have a window's worth of unacked + * data accumulated, this "slow start" keeps us from + * dumping all that data as back-to-back packets (which + * might overwhelm an intermediate gateway). + * + * There are two phases to the opening: Initially we + * open by one mss on each ack. This makes the window + * size increase exponentially with time. If the + * window is larger than the path can handle, this + * exponential growth results in dropped packet(s) + * almost immediately. To get more time between + * drops but still "push" the network to take advantage + * of improving conditions, we switch from exponential + * to linear window opening at some threshold size. + * For a threshold, we use half the current window + * size, truncated to a multiple of the mss. + * + * (the minimum cwnd that will give us exponential + * growth is 2 mss. We don't allow the threshold + * to go below this.) + */ + { + u_int win = min(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg; + if (win < 2) + win = 2; + tp->snd_cwnd = tp->t_maxseg; + tp->snd_ssthresh = win * tp->t_maxseg; + tp->t_dupacks = 0; + } + (void) tcp_output(tp); + break; + + /* + * Persistence timer into zero window. + * Force a byte to be output, if possible. + */ + case TCPT_PERSIST: + tcp_setpersist(tp); + tp->t_force = 1; + (void) tcp_output(tp); + tp->t_force = 0; + break; + + /* + * Keep-alive timer went off; send something + * or drop connection if idle for too long. + */ + case TCPT_KEEP: + if (tp->t_state < TCPS_ESTABLISHED) + goto dropit; + + if ((SO_OPTIONS) && tp->t_state <= TCPS_CLOSE_WAIT) { + if (tp->t_idle >= TCPTV_KEEP_IDLE + TCP_MAXIDLE) + goto dropit; + /* + * Send a packet designed to force a response + * if the peer is up and reachable: + * either an ACK if the connection is still alive, + * or an RST if the peer has closed the connection + * due to timeout or reboot. + * Using sequence number tp->snd_una-1 + * causes the transmitted zero-length segment + * to lie outside the receive window; + * by the protocol spec, this requires the + * correspondent TCP to respond. + */ + tcp_respond(tp, &tp->t_template, (struct mbuf *)NULL, + tp->rcv_nxt, tp->snd_una - 1, 0); + tp->t_timer[TCPT_KEEP] = TCPTV_KEEPINTVL; + } else + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_IDLE; + break; + + dropit: + tp = tcp_drop(tp, 0); + break; + } + + return (tp); +} diff --git a/slirp/tcp_timer.h b/slirp/tcp_timer.h new file mode 100644 index 0000000..ff17914 --- /dev/null +++ b/slirp/tcp_timer.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_timer.h 8.1 (Berkeley) 6/10/93 + * tcp_timer.h,v 1.4 1994/08/21 05:27:38 paul Exp + */ + +#ifndef _TCP_TIMER_H_ +#define _TCP_TIMER_H_ + +/* + * Definitions of the TCP timers. These timers are counted + * down PR_SLOWHZ times a second. + */ +#define TCPT_NTIMERS 4 + +#define TCPT_REXMT 0 /* retransmit */ +#define TCPT_PERSIST 1 /* retransmit persistence */ +#define TCPT_KEEP 2 /* keep alive */ +#define TCPT_2MSL 3 /* 2*msl quiet time timer */ + +/* + * The TCPT_REXMT timer is used to force retransmissions. + * The TCP has the TCPT_REXMT timer set whenever segments + * have been sent for which ACKs are expected but not yet + * received. If an ACK is received which advances tp->snd_una, + * then the retransmit timer is cleared (if there are no more + * outstanding segments) or reset to the base value (if there + * are more ACKs expected). Whenever the retransmit timer goes off, + * we retransmit one unacknowledged segment, and do a backoff + * on the retransmit timer. + * + * The TCPT_PERSIST timer is used to keep window size information + * flowing even if the window goes shut. If all previous transmissions + * have been acknowledged (so that there are no retransmissions in progress), + * and the window is too small to bother sending anything, then we start + * the TCPT_PERSIST timer. When it expires, if the window is nonzero, + * we go to transmit state. Otherwise, at intervals send a single byte + * into the peer's window to force him to update our window information. + * We do this at most as often as TCPT_PERSMIN time intervals, + * but no more frequently than the current estimate of round-trip + * packet time. The TCPT_PERSIST timer is cleared whenever we receive + * a window update from the peer. + * + * The TCPT_KEEP timer is used to keep connections alive. If an + * connection is idle (no segments received) for TCPTV_KEEP_INIT amount of time, + * but not yet established, then we drop the connection. Once the connection + * is established, if the connection is idle for TCPTV_KEEP_IDLE time + * (and keepalives have been enabled on the socket), we begin to probe + * the connection. We force the peer to send us a segment by sending: + * + * This segment is (deliberately) outside the window, and should elicit + * an ack segment in response from the peer. If, despite the TCPT_KEEP + * initiated segments we cannot elicit a response from a peer in TCPT_MAXIDLE + * amount of time probing, then we drop the connection. + */ + +/* + * Time constants. + */ +#define TCPTV_MSL ( 5*PR_SLOWHZ) /* max seg lifetime (hah!) */ + +#define TCPTV_SRTTBASE 0 /* base roundtrip time; + if 0, no idea yet */ +#define TCPTV_SRTTDFLT ( 3*PR_SLOWHZ) /* assumed RTT if no info */ + +#define TCPTV_PERSMIN ( 5*PR_SLOWHZ) /* retransmit persistence */ +#define TCPTV_PERSMAX ( 60*PR_SLOWHZ) /* maximum persist interval */ + +#define TCPTV_KEEP_INIT ( 75*PR_SLOWHZ) /* initial connect keep alive */ +#define TCPTV_KEEP_IDLE (120*60*PR_SLOWHZ) /* dflt time before probing */ +#define TCPTV_KEEPINTVL ( 75*PR_SLOWHZ) /* default probe interval */ +#define TCPTV_KEEPCNT 8 /* max probes before drop */ + +#define TCPTV_MIN ( 1*PR_SLOWHZ) /* minimum allowable value */ +#define TCPTV_REXMTMAX ( 12*PR_SLOWHZ) /* max allowable REXMT value */ + +#define TCP_LINGERTIME 120 /* linger at most 2 minutes */ + +#define TCP_MAXRXTSHIFT 12 /* maximum retransmits */ + + +/* + * Force a time value to be in a certain range. + */ +#define TCPT_RANGESET(tv, value, tvmin, tvmax) { \ + (tv) = (value); \ + if ((tv) < (tvmin)) \ + (tv) = (tvmin); \ + else if ((tv) > (tvmax)) \ + (tv) = (tvmax); \ +} + +extern const int tcp_backoff[]; + +struct tcpcb; + +void tcp_fasttimo(Slirp *); +void tcp_slowtimo(Slirp *); +void tcp_canceltimers(struct tcpcb *); + +#endif diff --git a/slirp/tcp_var.h b/slirp/tcp_var.h new file mode 100644 index 0000000..004193f --- /dev/null +++ b/slirp/tcp_var.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 1982, 1986, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_var.h 8.3 (Berkeley) 4/10/94 + * tcp_var.h,v 1.3 1994/08/21 05:27:39 paul Exp + */ + +#ifndef _TCP_VAR_H_ +#define _TCP_VAR_H_ + +#include "tcpip.h" +#include "tcp_timer.h" + +/* + * Tcp control block, one per tcp; fields: + */ +struct tcpcb { + struct tcpiphdr *seg_next; /* sequencing queue */ + struct tcpiphdr *seg_prev; + short t_state; /* state of this connection */ + short t_timer[TCPT_NTIMERS]; /* tcp timers */ + short t_rxtshift; /* log(2) of rexmt exp. backoff */ + short t_rxtcur; /* current retransmit value */ + short t_dupacks; /* consecutive dup acks recd */ + u_short t_maxseg; /* maximum segment size */ + char t_force; /* 1 if forcing out a byte */ + u_short t_flags; +#define TF_ACKNOW 0x0001 /* ack peer immediately */ +#define TF_DELACK 0x0002 /* ack, but try to delay it */ +#define TF_NODELAY 0x0004 /* don't delay packets to coalesce */ +#define TF_NOOPT 0x0008 /* don't use tcp options */ +#define TF_SENTFIN 0x0010 /* have sent FIN */ +#define TF_REQ_SCALE 0x0020 /* have/will request window scaling */ +#define TF_RCVD_SCALE 0x0040 /* other side has requested scaling */ +#define TF_REQ_TSTMP 0x0080 /* have/will request timestamps */ +#define TF_RCVD_TSTMP 0x0100 /* a timestamp was received in SYN */ +#define TF_SACK_PERMIT 0x0200 /* other side said I could SACK */ + + struct tcpiphdr t_template; /* static skeletal packet for xmit */ + + struct socket *t_socket; /* back pointer to socket */ +/* + * The following fields are used as in the protocol specification. + * See RFC783, Dec. 1981, page 21. + */ +/* send sequence variables */ + tcp_seq snd_una; /* send unacknowledged */ + tcp_seq snd_nxt; /* send next */ + tcp_seq snd_up; /* send urgent pointer */ + tcp_seq snd_wl1; /* window update seg seq number */ + tcp_seq snd_wl2; /* window update seg ack number */ + tcp_seq iss; /* initial send sequence number */ + uint32_t snd_wnd; /* send window */ +/* receive sequence variables */ + uint32_t rcv_wnd; /* receive window */ + tcp_seq rcv_nxt; /* receive next */ + tcp_seq rcv_up; /* receive urgent pointer */ + tcp_seq irs; /* initial receive sequence number */ +/* + * Additional variables for this implementation. + */ +/* receive variables */ + tcp_seq rcv_adv; /* advertised window */ +/* retransmit variables */ + tcp_seq snd_max; /* highest sequence number sent; + * used to recognize retransmits + */ +/* congestion control (for slow start, source quench, retransmit after loss) */ + uint32_t snd_cwnd; /* congestion-controlled window */ + uint32_t snd_ssthresh; /* snd_cwnd size threshold for + * for slow start exponential to + * linear switch + */ +/* + * transmit timing stuff. See below for scale of srtt and rttvar. + * "Variance" is actually smoothed difference. + */ + short t_idle; /* inactivity time */ + short t_rtt; /* round trip time */ + tcp_seq t_rtseq; /* sequence number being timed */ + short t_srtt; /* smoothed round-trip time */ + short t_rttvar; /* variance in round-trip time */ + u_short t_rttmin; /* minimum rtt allowed */ + uint32_t max_sndwnd; /* largest window peer has offered */ + +/* out-of-band data */ + char t_oobflags; /* have some */ + char t_iobc; /* input character */ +#define TCPOOB_HAVEDATA 0x01 +#define TCPOOB_HADDATA 0x02 + short t_softerror; /* possible error not yet reported */ + +/* RFC 1323 variables */ + u_char snd_scale; /* window scaling for send window */ + u_char rcv_scale; /* window scaling for recv window */ + u_char request_r_scale; /* pending window scaling */ + u_char requested_s_scale; + uint32_t ts_recent; /* timestamp echo data */ + uint32_t ts_recent_age; /* when last updated */ + tcp_seq last_ack_sent; + +}; + +#define sototcpcb(so) ((so)->so_tcpcb) + +/* + * The smoothed round-trip time and estimated variance + * are stored as fixed point numbers scaled by the values below. + * For convenience, these scales are also used in smoothing the average + * (smoothed = (1/scale)sample + ((scale-1)/scale)smoothed). + * With these scales, srtt has 3 bits to the right of the binary point, + * and thus an "ALPHA" of 0.875. rttvar has 2 bits to the right of the + * binary point, and is smoothed with an ALPHA of 0.75. + */ +#define TCP_RTT_SCALE 8 /* multiplier for srtt; 3 bits frac. */ +#define TCP_RTT_SHIFT 3 /* shift for srtt; 3 bits frac. */ +#define TCP_RTTVAR_SCALE 4 /* multiplier for rttvar; 2 bits */ +#define TCP_RTTVAR_SHIFT 2 /* multiplier for rttvar; 2 bits */ + +/* + * The initial retransmission should happen at rtt + 4 * rttvar. + * Because of the way we do the smoothing, srtt and rttvar + * will each average +1/2 tick of bias. When we compute + * the retransmit timer, we want 1/2 tick of rounding and + * 1 extra tick because of +-1/2 tick uncertainty in the + * firing of the timer. The bias will give us exactly the + * 1.5 tick we need. But, because the bias is + * statistical, we have to test that we don't drop below + * the minimum feasible timer (which is 2 ticks). + * This macro assumes that the value of TCP_RTTVAR_SCALE + * is the same as the multiplier for rttvar. + */ +#define TCP_REXMTVAL(tp) \ + (((tp)->t_srtt >> TCP_RTT_SHIFT) + (tp)->t_rttvar) + +#endif diff --git a/slirp/tcpip.h b/slirp/tcpip.h new file mode 100644 index 0000000..7974ce3 --- /dev/null +++ b/slirp/tcpip.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcpip.h 8.1 (Berkeley) 6/10/93 + * tcpip.h,v 1.3 1994/08/21 05:27:40 paul Exp + */ + +#ifndef _TCPIP_H_ +#define _TCPIP_H_ + +/* + * Tcp+ip header, after ip options removed. + */ +struct tcpiphdr { + struct ipovly ti_i; /* overlaid ip structure */ + struct tcphdr ti_t; /* tcp header */ +}; +#define ti_mbuf ti_i.ih_mbuf.mptr +#define ti_x1 ti_i.ih_x1 +#define ti_pr ti_i.ih_pr +#define ti_len ti_i.ih_len +#define ti_src ti_i.ih_src +#define ti_dst ti_i.ih_dst +#define ti_sport ti_t.th_sport +#define ti_dport ti_t.th_dport +#define ti_seq ti_t.th_seq +#define ti_ack ti_t.th_ack +#define ti_x2 ti_t.th_x2 +#define ti_off ti_t.th_off +#define ti_flags ti_t.th_flags +#define ti_win ti_t.th_win +#define ti_sum ti_t.th_sum +#define ti_urp ti_t.th_urp + +#define tcpiphdr2qlink(T) ((struct qlink*)(((char*)(T)) - sizeof(struct qlink))) +#define qlink2tcpiphdr(Q) ((struct tcpiphdr*)(((char*)(Q)) + sizeof(struct qlink))) +#define tcpiphdr_next(T) qlink2tcpiphdr(tcpiphdr2qlink(T)->next) +#define tcpiphdr_prev(T) qlink2tcpiphdr(tcpiphdr2qlink(T)->prev) +#define tcpfrag_list_first(T) qlink2tcpiphdr((T)->seg_next) +#define tcpfrag_list_end(F, T) (tcpiphdr2qlink(F) == (struct qlink*)(T)) +#define tcpfrag_list_empty(T) ((T)->seg_next == (struct tcpiphdr*)(T)) + +/* + * Just a clean way to get to the first byte + * of the packet + */ +struct tcpiphdr_2 { + struct tcpiphdr dummy; + char first_char; +}; + +#endif diff --git a/slirp/tftp.h b/slirp/tftp.h new file mode 100644 index 0000000..b9f0847 --- /dev/null +++ b/slirp/tftp.h @@ -0,0 +1,43 @@ +/* tftp defines */ + +#define TFTP_SESSIONS_MAX 3 + +#define TFTP_SERVER 69 + +#define TFTP_RRQ 1 +#define TFTP_WRQ 2 +#define TFTP_DATA 3 +#define TFTP_ACK 4 +#define TFTP_ERROR 5 +#define TFTP_OACK 6 + +#define TFTP_FILENAME_MAX 512 + +struct tftp_t { + struct ip ip; + struct udphdr udp; + uint16_t tp_op; + union { + struct { + uint16_t tp_block_nr; + uint8_t tp_buf[512]; + } tp_data; + struct { + uint16_t tp_error_code; + uint8_t tp_msg[512]; + } tp_error; + uint8_t tp_buf[512 + 2]; + } x; +}; + +struct tftp_session { + Slirp *slirp; + char *filename; + + struct in_addr client_ip; + uint16_t client_port; + + int timestamp; +}; + +void tftp_input(struct mbuf *m); diff --git a/slirp/udp.c b/slirp/udp.c new file mode 100644 index 0000000..2d04f12 --- /dev/null +++ b/slirp/udp.c @@ -0,0 +1,386 @@ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)udp_usrreq.c 8.4 (Berkeley) 1/21/94 + * udp_usrreq.c,v 1.4 1994/10/02 17:48:45 phk Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" +#include "ip_icmp.h" + +static uint8_t udp_tos(struct socket *so); + +void +udp_init(Slirp *slirp) +{ + slirp->udb.so_next = slirp->udb.so_prev = &slirp->udb; + slirp->udp_last_so = &slirp->udb; +} +/* m->m_data points at ip packet header + * m->m_len length ip packet + * ip->ip_len length data (IPDU) + */ +void +udp_input(register struct mbuf *m, int iphlen) +{ + Slirp *slirp = m->slirp; + register struct ip *ip; + register struct udphdr *uh; + int len; + struct ip save_ip; + struct socket *so; + + DEBUG_CALL("udp_input"); + DEBUG_ARG("m = %lx", (long)m); + DEBUG_ARG("iphlen = %d", iphlen); + + /* + * Strip IP options, if any; should skip this, + * make available to user, and use on returned packets, + * but we don't yet have a way to check the checksum + * with options still present. + */ + if(iphlen > sizeof(struct ip)) { + ip_stripoptions(m, (struct mbuf *)0); + iphlen = sizeof(struct ip); + } + + /* + * Get IP and UDP header together in first mbuf. + */ + ip = mtod(m, struct ip *); + uh = (struct udphdr *)((caddr_t)ip + iphlen); + + /* + * Make mbuf data length reflect UDP length. + * If not enough data to reflect UDP length, drop. + */ + len = ntohs((uint16_t)uh->uh_ulen); + + if (ip->ip_len != len) { + if (len > ip->ip_len) { + goto bad; + } + m_adj(m, len - ip->ip_len); + ip->ip_len = len; + } + + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip = *ip; + save_ip.ip_len+= iphlen; /* tcp_input subtracts this */ + + /* + * Checksum extended UDP header and data. + */ + if (uh->uh_sum) { + memset(&((struct ipovly *)ip)->ih_mbuf, 0, sizeof(struct mbuf_ptr)); + ((struct ipovly *)ip)->ih_x1 = 0; + ((struct ipovly *)ip)->ih_len = uh->uh_ulen; + if(cksum(m, len + sizeof(struct ip))) { + goto bad; + } + } + + /* + * handle DHCP/BOOTP + */ + if (ntohs(uh->uh_dport) == BOOTP_SERVER) { + bootp_input(m); + goto bad; + } + + if (slirp->restricted) { + goto bad; + } + +#if 0 + /* + * handle TFTP + */ + if (ntohs(uh->uh_dport) == TFTP_SERVER) { + tftp_input(m); + goto bad; + } +#endif + + /* + * Locate pcb for datagram. + */ + so = slirp->udp_last_so; + if (so->so_lport != uh->uh_sport || + so->so_laddr.s_addr != ip->ip_src.s_addr) { + struct socket *tmp; + + for (tmp = slirp->udb.so_next; tmp != &slirp->udb; + tmp = tmp->so_next) { + if (tmp->so_lport == uh->uh_sport && + tmp->so_laddr.s_addr == ip->ip_src.s_addr) { + so = tmp; + break; + } + } + if (tmp == &slirp->udb) { + so = NULL; + } else { + slirp->udp_last_so = so; + } + } + + if (so == NULL) { + /* + * If there's no socket for this packet, + * create one + */ + so = socreate(slirp); + if (!so) { + goto bad; + } + if(udp_attach(so) == -1) { + DEBUG_MISC((dfd," udp_attach errno = %d-%s\n", + errno,strerror(errno))); + sofree(so); + goto bad; + } + + /* + * Setup fields + */ + so->so_laddr = ip->ip_src; + so->so_lport = uh->uh_sport; + + if ((so->so_iptos = udp_tos(so)) == 0) + so->so_iptos = ip->ip_tos; + + /* + * XXXXX Here, check if it's in udpexec_list, + * and if it is, do the fork_exec() etc. + */ + } + + so->so_faddr = ip->ip_dst; /* XXX */ + so->so_fport = uh->uh_dport; /* XXX */ + + iphlen += sizeof(struct udphdr); + m->m_len -= iphlen; + m->m_data += iphlen; + + /* + * Now we sendto() the packet. + */ + if(sosendto(so,m) == -1) { + m->m_len += iphlen; + m->m_data -= iphlen; + *ip=save_ip; + DEBUG_MISC((dfd,"udp tx errno = %d-%s\n",errno,strerror(errno))); + icmp_error(m, ICMP_UNREACH,ICMP_UNREACH_NET, 0,strerror(errno)); + } + + m_free(so->so_m); /* used for ICMP if error on sorecvfrom */ + + /* restore the orig mbuf packet */ + m->m_len += iphlen; + m->m_data -= iphlen; + *ip=save_ip; + so->so_m=m; /* ICMP backup */ + + return; +bad: + m_freem(m); + return; +} + +int udp_output2(struct socket *so, struct mbuf *m, + struct sockaddr_in *saddr, struct sockaddr_in *daddr, + int iptos) +{ + register struct udpiphdr *ui; + int error = 0; + + DEBUG_CALL("udp_output"); + DEBUG_ARG("so = %lx", (long)so); + DEBUG_ARG("m = %lx", (long)m); + DEBUG_ARG("saddr = %lx", (long)saddr->sin_addr.s_addr); + DEBUG_ARG("daddr = %lx", (long)daddr->sin_addr.s_addr); + + /* + * Adjust for header + */ + m->m_data -= sizeof(struct udpiphdr); + m->m_len += sizeof(struct udpiphdr); + + /* + * Fill in mbuf with extended UDP header + * and addresses and length put into network format. + */ + ui = mtod(m, struct udpiphdr *); + memset(&ui->ui_i.ih_mbuf, 0 , sizeof(struct mbuf_ptr)); + ui->ui_x1 = 0; + ui->ui_pr = IPPROTO_UDP; + ui->ui_len = htons(m->m_len - sizeof(struct ip)); + /* XXXXX Check for from-one-location sockets, or from-any-location sockets */ + ui->ui_src = saddr->sin_addr; + ui->ui_dst = daddr->sin_addr; + ui->ui_sport = saddr->sin_port; + ui->ui_dport = daddr->sin_port; + ui->ui_ulen = ui->ui_len; + + /* + * Stuff checksum and output datagram. + */ + ui->ui_sum = 0; + if ((ui->ui_sum = cksum(m, m->m_len)) == 0) + ui->ui_sum = 0xffff; + ((struct ip *)ui)->ip_len = m->m_len; + + ((struct ip *)ui)->ip_ttl = IPDEFTTL; + ((struct ip *)ui)->ip_tos = iptos; + + error = ip_output(so, m); + + return (error); +} + +int udp_output(struct socket *so, struct mbuf *m, + struct sockaddr_in *addr) + +{ + Slirp *slirp = so->slirp; + struct sockaddr_in saddr, daddr; + + saddr = *addr; + if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + uint32_t inv_mask = ~slirp->vnetwork_mask.s_addr; + + if ((so->so_faddr.s_addr & inv_mask) == inv_mask) { + saddr.sin_addr = slirp->vhost_addr; + } else if (addr->sin_addr.s_addr == loopback_addr.s_addr || + so->so_faddr.s_addr != slirp->vhost_addr.s_addr) { + saddr.sin_addr = so->so_faddr; + } + } + daddr.sin_addr = so->so_laddr; + daddr.sin_port = so->so_lport; + + return udp_output2(so, m, &saddr, &daddr, so->so_iptos); +} + +int +udp_attach(struct socket *so) +{ + if((so->s = os_socket(AF_INET,SOCK_DGRAM,0)) != -1) { + so->so_expire = curtime + SO_EXPIRE; + insque(so, &so->slirp->udb); + } + return(so->s); +} + +void +udp_detach(struct socket *so) +{ + closesocket(so->s); + sofree(so); +} + +static const struct tos_t udptos[] = { + {0, 53, IPTOS_LOWDELAY, 0}, /* DNS */ + {0, 0, 0, 0} +}; + +static uint8_t +udp_tos(struct socket *so) +{ + int i = 0; + + while(udptos[i].tos) { + if ((udptos[i].fport && ntohs(so->so_fport) == udptos[i].fport) || + (udptos[i].lport && ntohs(so->so_lport) == udptos[i].lport)) { + so->so_emu = udptos[i].emu; + return udptos[i].tos; + } + i++; + } + + return 0; +} + +struct socket * +udp_listen(Slirp *slirp, uint32_t haddr, u_int hport, uint32_t laddr, + u_int lport, int flags) +{ + struct sockaddr_in addr; + struct socket *so; + socklen_t addrlen = sizeof(struct sockaddr_in), opt = 1; + + so = socreate(slirp); + if (!so) { + return NULL; + } + so->s = os_socket(AF_INET,SOCK_DGRAM,0); + so->so_expire = curtime + SO_EXPIRE; + insque(so, &slirp->udb); + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = haddr; + addr.sin_port = hport; + + if (bind(so->s,(struct sockaddr *)&addr, addrlen) < 0) { + udp_detach(so); + return NULL; + } + setsockopt(so->s,SOL_SOCKET,SO_REUSEADDR,(char *)&opt,sizeof(int)); + + getsockname(so->s,(struct sockaddr *)&addr,&addrlen); + so->so_fport = addr.sin_port; + if (addr.sin_addr.s_addr == 0 || + addr.sin_addr.s_addr == loopback_addr.s_addr) { + so->so_faddr = slirp->vhost_addr; + } else { + so->so_faddr = addr.sin_addr; + } + so->so_lport = lport; + so->so_laddr.s_addr = laddr; + if (flags != SS_FACCEPTONCE) + so->so_expire = 0; + + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_ISFCONNECTED | flags; + + return so; +} diff --git a/slirp/udp.h b/slirp/udp.h new file mode 100644 index 0000000..9b5c3cf --- /dev/null +++ b/slirp/udp.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)udp.h 8.1 (Berkeley) 6/10/93 + * udp.h,v 1.3 1994/08/21 05:27:41 paul Exp + */ + +#ifndef _UDP_H_ +#define _UDP_H_ + +#define UDP_TTL 0x60 +#define UDP_UDPDATALEN 16192 + +/* + * Udp protocol header. + * Per RFC 768, September, 1981. + */ +struct udphdr { + uint16_t uh_sport; /* source port */ + uint16_t uh_dport; /* destination port */ + int16_t uh_ulen; /* udp length */ + uint16_t uh_sum; /* udp checksum */ +}; + +/* + * UDP kernel structures and variables. + */ +struct udpiphdr { + struct ipovly ui_i; /* overlaid ip structure */ + struct udphdr ui_u; /* udp header */ +}; +#define ui_mbuf ui_i.ih_mbuf.mptr +#define ui_x1 ui_i.ih_x1 +#define ui_pr ui_i.ih_pr +#define ui_len ui_i.ih_len +#define ui_src ui_i.ih_src +#define ui_dst ui_i.ih_dst +#define ui_sport ui_u.uh_sport +#define ui_dport ui_u.uh_dport +#define ui_ulen ui_u.uh_ulen +#define ui_sum ui_u.uh_sum + +/* + * Names for UDP sysctl objects + */ +#define UDPCTL_CHECKSUM 1 /* checksum UDP packets */ +#define UDPCTL_MAXID 2 + +struct mbuf; + +void udp_init(Slirp *); +void udp_input(register struct mbuf *, int); +int udp_output(struct socket *, struct mbuf *, struct sockaddr_in *); +int udp_attach(struct socket *); +void udp_detach(struct socket *); +struct socket * udp_listen(Slirp *, uint32_t, u_int, uint32_t, u_int, + int); +int udp_output2(struct socket *so, struct mbuf *m, + struct sockaddr_in *saddr, struct sockaddr_in *daddr, + int iptos); +#endif diff --git a/softfp.c b/softfp.c new file mode 100644 index 0000000..2a1aad0 --- /dev/null +++ b/softfp.c @@ -0,0 +1,87 @@ +/* + * SoftFP Library + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include + +#include "cutils.h" +#include "softfp.h" + +static inline int clz32(uint32_t a) +{ + int r; + if (a == 0) { + r = 32; + } else { + r = __builtin_clz(a); + } + return r; +} + +static inline int clz64(uint64_t a) +{ + int r; + if (a == 0) { + r = 64; + } else + { + r = __builtin_clzll(a); + } + return r; +} + +#ifdef HAVE_INT128 +static inline int clz128(uint128_t a) +{ + int r; + if (a == 0) { + r = 128; + } else + { + uint64_t ah, al; + ah = a >> 64; + al = a; + if (ah != 0) + r = __builtin_clzll(ah); + else + r = __builtin_clzll(al) + 64; + } + return r; +} +#endif + +#define F_SIZE 32 +#include "softfp_template.h" + +#define F_SIZE 64 +#include "softfp_template.h" + +#ifdef HAVE_INT128 + +#define F_SIZE 128 +#include "softfp_template.h" + +#endif + diff --git a/softfp.h b/softfp.h new file mode 100644 index 0000000..974c7ec --- /dev/null +++ b/softfp.h @@ -0,0 +1,181 @@ +/* + * SoftFP Library + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef SOFTFP_H +#define SOFTFP_H + +#include +#include "cutils.h" + +typedef enum { + RM_RNE, /* Round to Nearest, ties to Even */ + RM_RTZ, /* Round towards Zero */ + RM_RDN, /* Round Down */ + RM_RUP, /* Round Up */ + RM_RMM, /* Round to Nearest, ties to Max Magnitude */ +} RoundingModeEnum; + +#define FFLAG_INVALID_OP (1 << 4) +#define FFLAG_DIVIDE_ZERO (1 << 3) +#define FFLAG_OVERFLOW (1 << 2) +#define FFLAG_UNDERFLOW (1 << 1) +#define FFLAG_INEXACT (1 << 0) + +#define FCLASS_NINF (1 << 0) +#define FCLASS_NNORMAL (1 << 1) +#define FCLASS_NSUBNORMAL (1 << 2) +#define FCLASS_NZERO (1 << 3) +#define FCLASS_PZERO (1 << 4) +#define FCLASS_PSUBNORMAL (1 << 5) +#define FCLASS_PNORMAL (1 << 6) +#define FCLASS_PINF (1 << 7) +#define FCLASS_SNAN (1 << 8) +#define FCLASS_QNAN (1 << 9) + +typedef enum { + FMINMAX_PROP, /* min(1, qNaN/sNaN) -> qNaN */ + FMINMAX_IEEE754_2008, /* min(1, qNaN) -> 1, min(1, sNaN) -> qNaN */ + FMINMAX_IEEE754_201X, /* min(1, qNaN/sNaN) -> 1 */ +} SoftFPMinMaxTypeEnum; + +typedef uint32_t sfloat32; +typedef uint64_t sfloat64; +#ifdef HAVE_INT128 +typedef uint128_t sfloat128; +#endif + +/* 32 bit floats */ + +#define FSIGN_MASK32 (1 << 31) + +sfloat32 add_sf32(sfloat32 a, sfloat32 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat32 sub_sf32(sfloat32 a, sfloat32 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat32 mul_sf32(sfloat32 a, sfloat32 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat32 div_sf32(sfloat32 a, sfloat32 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat32 sqrt_sf32(sfloat32 a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat32 fma_sf32(sfloat32 a, sfloat32 b, sfloat32 c, RoundingModeEnum rm, uint32_t *pfflags); + +sfloat32 min_sf32(sfloat32 a, sfloat32 b, uint32_t *pfflags, SoftFPMinMaxTypeEnum minmax_type); +sfloat32 max_sf32(sfloat32 a, sfloat32 b, uint32_t *pfflags, SoftFPMinMaxTypeEnum minmax_type); +int eq_quiet_sf32(sfloat32 a, sfloat32 b, uint32_t *pfflags); +int le_sf32(sfloat32 a, sfloat32 b, uint32_t *pfflags); +int lt_sf32(sfloat32 a, sfloat32 b, uint32_t *pfflags); +uint32_t fclass_sf32(sfloat32 a); + +sfloat64 cvt_sf32_sf64(sfloat32 a, uint32_t *pfflags); +sfloat32 cvt_sf64_sf32(sfloat64 a, RoundingModeEnum rm, uint32_t *pfflags); +int32_t cvt_sf32_i32(sfloat32 a, RoundingModeEnum rm, uint32_t *pfflags); +uint32_t cvt_sf32_u32(sfloat32 a, RoundingModeEnum rm, uint32_t *pfflags); +int64_t cvt_sf32_i64(sfloat32 a, RoundingModeEnum rm, uint32_t *pfflags); +uint64_t cvt_sf32_u64(sfloat32 a, RoundingModeEnum rm, uint32_t *pfflags); +#ifdef HAVE_INT128 +int128_t cvt_sf32_i128(sfloat32 a, RoundingModeEnum rm, uint32_t *pfflags); +uint128_t cvt_sf32_u128(sfloat32 a, RoundingModeEnum rm, uint32_t *pfflags); +#endif +sfloat32 cvt_i32_sf32(int32_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat32 cvt_u32_sf32(uint32_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat32 cvt_i64_sf32(int64_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat32 cvt_u64_sf32(uint64_t a, RoundingModeEnum rm, uint32_t *pfflags); +#ifdef HAVE_INT128 +sfloat32 cvt_i128_sf32(int128_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat32 cvt_u128_sf32(uint128_t a, RoundingModeEnum rm, uint32_t *pfflags); +#endif + +/* 64 bit floats */ + +#define FSIGN_MASK64 ((uint64_t)1 << 63) + +sfloat64 add_sf64(sfloat64 a, sfloat64 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat64 sub_sf64(sfloat64 a, sfloat64 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat64 mul_sf64(sfloat64 a, sfloat64 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat64 div_sf64(sfloat64 a, sfloat64 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat64 sqrt_sf64(sfloat64 a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat64 fma_sf64(sfloat64 a, sfloat64 b, sfloat64 c, RoundingModeEnum rm, uint32_t *pfflags); + +sfloat64 min_sf64(sfloat64 a, sfloat64 b, uint32_t *pfflags, SoftFPMinMaxTypeEnum minmax_type); +sfloat64 max_sf64(sfloat64 a, sfloat64 b, uint32_t *pfflags, SoftFPMinMaxTypeEnum minmax_type); +int eq_quiet_sf64(sfloat64 a, sfloat64 b, uint32_t *pfflags); +int le_sf64(sfloat64 a, sfloat64 b, uint32_t *pfflags); +int lt_sf64(sfloat64 a, sfloat64 b, uint32_t *pfflags); +uint32_t fclass_sf64(sfloat64 a); + +sfloat64 cvt_sf32_sf64(sfloat32 a, uint32_t *pfflags); +sfloat32 cvt_sf64_sf32(sfloat64 a, RoundingModeEnum rm, uint32_t *pfflags); +int32_t cvt_sf64_i32(sfloat64 a, RoundingModeEnum rm, uint32_t *pfflags); +uint32_t cvt_sf64_u32(sfloat64 a, RoundingModeEnum rm, uint32_t *pfflags); +int64_t cvt_sf64_i64(sfloat64 a, RoundingModeEnum rm, uint32_t *pfflags); +uint64_t cvt_sf64_u64(sfloat64 a, RoundingModeEnum rm, uint32_t *pfflags); +#ifdef HAVE_INT128 +int128_t cvt_sf64_i128(sfloat64 a, RoundingModeEnum rm, uint32_t *pfflags); +uint128_t cvt_sf64_u128(sfloat64 a, RoundingModeEnum rm, uint32_t *pfflags); +#endif +sfloat64 cvt_i32_sf64(int32_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat64 cvt_u32_sf64(uint32_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat64 cvt_i64_sf64(int64_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat64 cvt_u64_sf64(uint64_t a, RoundingModeEnum rm, uint32_t *pfflags); +#ifdef HAVE_INT128 +sfloat64 cvt_i128_sf64(int128_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat64 cvt_u128_sf64(uint128_t a, RoundingModeEnum rm, uint32_t *pfflags); +#endif + +/* 128 bit floats */ + +#ifdef HAVE_INT128 + +#define FSIGN_MASK128 ((uint128_t)1 << 127) + +sfloat128 add_sf128(sfloat128 a, sfloat128 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 sub_sf128(sfloat128 a, sfloat128 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 mul_sf128(sfloat128 a, sfloat128 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 div_sf128(sfloat128 a, sfloat128 b, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 sqrt_sf128(sfloat128 a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 fma_sf128(sfloat128 a, sfloat128 b, sfloat128 c, RoundingModeEnum rm, uint32_t *pfflags); + +sfloat128 min_sf128(sfloat128 a, sfloat128 b, uint32_t *pfflags, SoftFPMinMaxTypeEnum minmax_type); +sfloat128 max_sf128(sfloat128 a, sfloat128 b, uint32_t *pfflags, SoftFPMinMaxTypeEnum minmax_type); +int eq_quiet_sf128(sfloat128 a, sfloat128 b, uint32_t *pfflags); +int le_sf128(sfloat128 a, sfloat128 b, uint32_t *pfflags); +int lt_sf128(sfloat128 a, sfloat128 b, uint32_t *pfflags); +uint32_t fclass_sf128(sfloat128 a); + +sfloat128 cvt_sf32_sf128(sfloat32 a, uint32_t *pfflags); +sfloat32 cvt_sf128_sf32(sfloat128 a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 cvt_sf64_sf128(sfloat64 a, uint32_t *pfflags); +sfloat64 cvt_sf128_sf64(sfloat128 a, RoundingModeEnum rm, uint32_t *pfflags); + +int32_t cvt_sf128_i32(sfloat128 a, RoundingModeEnum rm, uint32_t *pfflags); +uint32_t cvt_sf128_u32(sfloat128 a, RoundingModeEnum rm, uint32_t *pfflags); +int64_t cvt_sf128_i64(sfloat128 a, RoundingModeEnum rm, uint32_t *pfflags); +uint64_t cvt_sf128_u64(sfloat128 a, RoundingModeEnum rm, uint32_t *pfflags); +int128_t cvt_sf128_i128(sfloat128 a, RoundingModeEnum rm, uint32_t *pfflags); +uint128_t cvt_sf128_u128(sfloat128 a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 cvt_i32_sf128(int32_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 cvt_u32_sf128(uint32_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 cvt_i64_sf128(int64_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 cvt_u64_sf128(uint64_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 cvt_i128_sf128(int128_t a, RoundingModeEnum rm, uint32_t *pfflags); +sfloat128 cvt_u128_sf128(uint128_t a, RoundingModeEnum rm, uint32_t *pfflags); + +#endif + +#endif /* SOFTFP_H */ diff --git a/softfp_template.h b/softfp_template.h new file mode 100644 index 0000000..88dbf66 --- /dev/null +++ b/softfp_template.h @@ -0,0 +1,1129 @@ +/* + * SoftFP Library + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#if F_SIZE == 32 +#define F_UINT uint32_t +#define F_ULONG uint64_t +#define MANT_SIZE 23 +#define EXP_SIZE 8 +#elif F_SIZE == 64 +#define F_UHALF uint32_t +#define F_UINT uint64_t +#ifdef HAVE_INT128 +#define F_ULONG uint128_t +#endif +#define MANT_SIZE 52 +#define EXP_SIZE 11 +#elif F_SIZE == 128 +#define F_UHALF uint64_t +#define F_UINT uint128_t +#define MANT_SIZE 112 +#define EXP_SIZE 15 +#else +#error unsupported F_SIZE +#endif + +#define EXP_MASK ((1 << EXP_SIZE) - 1) +#define MANT_MASK (((F_UINT)1 << MANT_SIZE) - 1) +#define SIGN_MASK ((F_UINT)1 << (F_SIZE - 1)) +#define IMANT_SIZE (F_SIZE - 2) /* internal mantissa size */ +#define RND_SIZE (IMANT_SIZE - MANT_SIZE) +#define QNAN_MASK ((F_UINT)1 << (MANT_SIZE - 1)) + +/* quiet NaN */ +#define F_QNAN glue(F_QNAN, F_SIZE) +#define clz glue(clz, F_SIZE) +#define pack_sf glue(pack_sf, F_SIZE) +#define unpack_sf glue(unpack_sf, F_SIZE) +#define rshift_rnd glue(rshift_rnd, F_SIZE) +#define round_pack_sf glue(roundpack_sf, F_SIZE) +#define normalize_sf glue(normalize_sf, F_SIZE) +#define normalize2_sf glue(normalize2_sf, F_SIZE) +#define issignan_sf glue(issignan_sf, F_SIZE) +#define isnan_sf glue(isnan_sf, F_SIZE) +#define add_sf glue(add_sf, F_SIZE) +#define mul_sf glue(mul_sf, F_SIZE) +#define fma_sf glue(fma_sf, F_SIZE) +#define div_sf glue(div_sf, F_SIZE) +#define sqrt_sf glue(sqrt_sf, F_SIZE) +#define normalize_subnormal_sf glue(normalize_subnormal_sf, F_SIZE) +#define divrem_u glue(divrem_u, F_SIZE) +#define sqrtrem_u glue(sqrtrem_u, F_SIZE) +#define mul_u glue(mul_u, F_SIZE) +#define cvt_sf32_sf glue(cvt_sf32_sf, F_SIZE) +#define cvt_sf64_sf glue(cvt_sf64_sf, F_SIZE) + +static const F_UINT F_QNAN = (((F_UINT)EXP_MASK << MANT_SIZE) | ((F_UINT)1 << (MANT_SIZE - 1))); + +static inline F_UINT pack_sf(uint32_t a_sign, uint32_t a_exp, F_UINT a_mant) +{ + return ((F_UINT)a_sign << (F_SIZE - 1)) | + ((F_UINT)a_exp << MANT_SIZE) | + (a_mant & MANT_MASK); +} + +static inline F_UINT unpack_sf(uint32_t *pa_sign, int32_t *pa_exp, + F_UINT a) +{ + *pa_sign = a >> (F_SIZE - 1); + *pa_exp = (a >> MANT_SIZE) & EXP_MASK; + return a & MANT_MASK; +} + +static F_UINT rshift_rnd(F_UINT a, int d) +{ + F_UINT mask; + if (d != 0) { + if (d >= F_SIZE) { + a = (a != 0); + } else { + mask = ((F_UINT)1 << d) - 1; + a = (a >> d) | ((a & mask) != 0); + } + } + return a; +} + +/* a_mant is considered to have its MSB at F_SIZE - 2 bits */ +static F_UINT round_pack_sf(uint32_t a_sign, int a_exp, F_UINT a_mant, + RoundingModeEnum rm, uint32_t *pfflags) +{ + int diff; + uint32_t addend, rnd_bits; + + switch(rm) { + case RM_RNE: + case RM_RMM: + addend = (1 << (RND_SIZE - 1)); + break; + case RM_RTZ: + addend = 0; + break; + default: + case RM_RDN: + case RM_RUP: + // printf("s=%d rm=%d m=%x\n", a_sign, rm, a_mant); + if (a_sign ^ (rm & 1)) + addend = (1 << RND_SIZE) - 1; + else + addend = 0; + break; + } + + /* potentially subnormal */ + if (a_exp <= 0) { + BOOL is_subnormal; + /* Note: we set the underflow flag if the rounded result + is subnormal and inexact */ + is_subnormal = (a_exp < 0 || + (a_mant + addend) < ((F_UINT)1 << (F_SIZE - 1))); + diff = 1 - a_exp; + a_mant = rshift_rnd(a_mant, diff); + rnd_bits = a_mant & ((1 << RND_SIZE ) - 1); + if (is_subnormal && rnd_bits != 0) { + *pfflags |= FFLAG_UNDERFLOW; + } + a_exp = 1; + } else { + rnd_bits = a_mant & ((1 << RND_SIZE ) - 1); + } + if (rnd_bits != 0) + *pfflags |= FFLAG_INEXACT; + a_mant = (a_mant + addend) >> RND_SIZE; + /* half way: select even result */ + if (rm == RM_RNE && rnd_bits == (1 << (RND_SIZE - 1))) + a_mant &= ~1; + /* Note the rounding adds at least 1, so this is the maximum + value */ + a_exp += a_mant >> (MANT_SIZE + 1); + if (a_mant <= MANT_MASK) { + /* denormalized or zero */ + a_exp = 0; + } else if (a_exp >= EXP_MASK) { + /* overflow */ + if (addend == 0) { + a_exp = EXP_MASK - 1; + a_mant = MANT_MASK; + } else { + /* infinity */ + a_exp = EXP_MASK; + a_mant = 0; + } + *pfflags |= FFLAG_OVERFLOW | FFLAG_INEXACT; + } + return pack_sf(a_sign, a_exp, a_mant); +} + +/* a_mant is considered to have at most F_SIZE - 1 bits */ +static F_UINT normalize_sf(uint32_t a_sign, int a_exp, F_UINT a_mant, + RoundingModeEnum rm, uint32_t *pfflags) +{ + int shift; + shift = clz(a_mant) - (F_SIZE - 1 - IMANT_SIZE); + assert(shift >= 0); + a_exp -= shift; + a_mant <<= shift; + return round_pack_sf(a_sign, a_exp, a_mant, rm, pfflags); +} + +/* same as normalize_sf() but with a double word mantissa. a_mant1 is + considered to have at most F_SIZE - 1 bits */ +static F_UINT normalize2_sf(uint32_t a_sign, int a_exp, F_UINT a_mant1, F_UINT a_mant0, + RoundingModeEnum rm, uint32_t *pfflags) +{ + int l, shift; + if (a_mant1 == 0) { + l = F_SIZE + clz(a_mant0); + } else { + l = clz(a_mant1); + } + shift = l - (F_SIZE - 1 - IMANT_SIZE); + assert(shift >= 0); + a_exp -= shift; + if (shift == 0) { + a_mant1 |= (a_mant0 != 0); + } else if (shift < F_SIZE) { + a_mant1 = (a_mant1 << shift) | (a_mant0 >> (F_SIZE - shift)); + a_mant0 <<= shift; + a_mant1 |= (a_mant0 != 0); + } else { + a_mant1 = a_mant0 << (shift - F_SIZE); + } + return round_pack_sf(a_sign, a_exp, a_mant1, rm, pfflags); +} + +BOOL issignan_sf(F_UINT a) +{ + uint32_t a_exp1; + F_UINT a_mant; + a_exp1 = (a >> (MANT_SIZE - 1)) & ((1 << (EXP_SIZE + 1)) - 1); + a_mant = a & MANT_MASK; + return (a_exp1 == (2 * EXP_MASK) && a_mant != 0); +} + +BOOL isnan_sf(F_UINT a) +{ + uint32_t a_exp; + F_UINT a_mant; + a_exp = (a >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + return (a_exp == EXP_MASK && a_mant != 0); +} + + +F_UINT add_sf(F_UINT a, F_UINT b, RoundingModeEnum rm, + uint32_t *pfflags) +{ + uint32_t a_sign, b_sign, a_exp, b_exp; + F_UINT tmp, a_mant, b_mant; + + /* swap so that abs(a) >= abs(b) */ + if ((a & ~SIGN_MASK) < (b & ~SIGN_MASK)) { + tmp = a; + a = b; + b = tmp; + } + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + a_mant = (a & MANT_MASK) << 3; + b_mant = (b & MANT_MASK) << 3; + if (unlikely(a_exp == EXP_MASK)) { + if (a_mant != 0) { + /* NaN result */ + if (!(a_mant & (QNAN_MASK << 3)) || issignan_sf(b)) + *pfflags |= FFLAG_INVALID_OP; + return F_QNAN; + } else if (b_exp == EXP_MASK && a_sign != b_sign) { + *pfflags |= FFLAG_INVALID_OP; + return F_QNAN; + } else { + /* infinity */ + return a; + } + } + if (a_exp == 0) { + a_exp = 1; + } else { + a_mant |= (F_UINT)1 << (MANT_SIZE + 3); + } + if (b_exp == 0) { + b_exp = 1; + } else { + b_mant |= (F_UINT)1 << (MANT_SIZE + 3); + } + b_mant = rshift_rnd(b_mant, a_exp - b_exp); + if (a_sign == b_sign) { + /* same signs : add the absolute values */ + a_mant += b_mant; + } else { + /* different signs : subtract the absolute values */ + a_mant -= b_mant; + if (a_mant == 0) { + /* zero result : the sign needs a specific handling */ + a_sign = (rm == RM_RDN); + } + } + a_exp += (RND_SIZE - 3); + return normalize_sf(a_sign, a_exp, a_mant, rm, pfflags); +} + +F_UINT glue(sub_sf, F_SIZE)(F_UINT a, F_UINT b, RoundingModeEnum rm, + uint32_t *pfflags) +{ + return add_sf(a, b ^ SIGN_MASK, rm, pfflags); +} + +static inline F_UINT normalize_subnormal_sf(int32_t *pa_exp, F_UINT a_mant) +{ + int shift; + shift = MANT_SIZE - ((F_SIZE - 1 - clz(a_mant))); + *pa_exp = 1 - shift; + return a_mant << shift; +} + +#ifdef F_ULONG + +static F_UINT mul_u(F_UINT *plow, F_UINT a, F_UINT b) +{ + F_ULONG r; + r = (F_ULONG)a * (F_ULONG)b; + *plow = r; + return r >> F_SIZE; +} + +#else + +#define FH_SIZE (F_SIZE / 2) + +static F_UINT mul_u(F_UINT *plow, F_UINT a, F_UINT b) +{ + F_UHALF a0, a1, b0, b1, r0, r1, r2, r3; + F_UINT r00, r01, r10, r11, c; + a0 = a; + a1 = a >> FH_SIZE; + b0 = b; + b1 = b >> FH_SIZE; + + r00 = (F_UINT)a0 * (F_UINT)b0; + r01 = (F_UINT)a0 * (F_UINT)b1; + r10 = (F_UINT)a1 * (F_UINT)b0; + r11 = (F_UINT)a1 * (F_UINT)b1; + + r0 = r00; + c = (r00 >> FH_SIZE) + (F_UHALF)r01 + (F_UHALF)r10; + r1 = c; + c = (c >> FH_SIZE) + (r01 >> FH_SIZE) + (r10 >> FH_SIZE) + (F_UHALF)r11; + r2 = c; + r3 = (c >> FH_SIZE) + (r11 >> FH_SIZE); + + *plow = ((F_UINT)r1 << FH_SIZE) | r0; + return ((F_UINT)r3 << FH_SIZE) | r2; +} + +#undef FH_SIZE + +#endif + +F_UINT mul_sf(F_UINT a, F_UINT b, RoundingModeEnum rm, + uint32_t *pfflags) +{ + uint32_t a_sign, b_sign, r_sign; + int32_t a_exp, b_exp, r_exp; + F_UINT a_mant, b_mant, r_mant, r_mant_low; + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + r_sign = a_sign ^ b_sign; + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + b_mant = b & MANT_MASK; + if (a_exp == EXP_MASK || b_exp == EXP_MASK) { + if (isnan_sf(a) || isnan_sf(b)) { + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } + return F_QNAN; + } else { + /* infinity */ + if ((a_exp == EXP_MASK && (b_exp == 0 && b_mant == 0)) || + (b_exp == EXP_MASK && (a_exp == 0 && a_mant == 0))) { + *pfflags |= FFLAG_INVALID_OP; + return F_QNAN; + } else { + return pack_sf(r_sign, EXP_MASK, 0); + } + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(r_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + if (b_exp == 0) { + if (b_mant == 0) + return pack_sf(r_sign, 0, 0); /* zero */ + b_mant = normalize_subnormal_sf(&b_exp, b_mant); + } else { + b_mant |= (F_UINT)1 << MANT_SIZE; + } + r_exp = a_exp + b_exp - (1 << (EXP_SIZE - 1)) + 2; + + r_mant = mul_u(&r_mant_low,a_mant << RND_SIZE, b_mant << (RND_SIZE + 1)); + r_mant |= (r_mant_low != 0); + return normalize_sf(r_sign, r_exp, r_mant, rm, pfflags); +} + +/* fused multiply and add */ +F_UINT fma_sf(F_UINT a, F_UINT b, F_UINT c, RoundingModeEnum rm, + uint32_t *pfflags) +{ + uint32_t a_sign, b_sign, c_sign, r_sign; + int32_t a_exp, b_exp, c_exp, r_exp, shift; + F_UINT a_mant, b_mant, c_mant, r_mant1, r_mant0, c_mant1, c_mant0, mask; + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + c_sign = c >> (F_SIZE - 1); + r_sign = a_sign ^ b_sign; + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + c_exp = (c >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + b_mant = b & MANT_MASK; + c_mant = c & MANT_MASK; + if (a_exp == EXP_MASK || b_exp == EXP_MASK || c_exp == EXP_MASK) { + if (isnan_sf(a) || isnan_sf(b) || isnan_sf(c)) { + if (issignan_sf(a) || issignan_sf(b) || issignan_sf(c)) { + *pfflags |= FFLAG_INVALID_OP; + } + return F_QNAN; + } else { + /* infinities */ + if ((a_exp == EXP_MASK && (b_exp == 0 && b_mant == 0)) || + (b_exp == EXP_MASK && (a_exp == 0 && a_mant == 0)) || + ((a_exp == EXP_MASK || b_exp == EXP_MASK) && + (c_exp == EXP_MASK && r_sign != c_sign))) { + *pfflags |= FFLAG_INVALID_OP; + return F_QNAN; + } else if (c_exp == EXP_MASK) { + return pack_sf(c_sign, EXP_MASK, 0); + } else { + return pack_sf(r_sign, EXP_MASK, 0); + } + } + } + if (a_exp == 0) { + if (a_mant == 0) + goto mul_zero; + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + if (b_exp == 0) { + if (b_mant == 0) { + mul_zero: + if (c_exp == 0 && c_mant == 0) { + if (c_sign != r_sign) + r_sign = (rm == RM_RDN); + return pack_sf(r_sign, 0, 0); + } else { + return c; + } + } + b_mant = normalize_subnormal_sf(&b_exp, b_mant); + } else { + b_mant |= (F_UINT)1 << MANT_SIZE; + } + /* multiply */ + r_exp = a_exp + b_exp - (1 << (EXP_SIZE - 1)) + 3; + + r_mant1 = mul_u(&r_mant0, a_mant << RND_SIZE, b_mant << RND_SIZE); + /* normalize to F_SIZE - 3 */ + if (r_mant1 < ((F_UINT)1 << (F_SIZE - 3))) { + r_mant1 = (r_mant1 << 1) | (r_mant0 >> (F_SIZE - 1)); + r_mant0 <<= 1; + r_exp--; + } + + /* add */ + if (c_exp == 0) { + if (c_mant == 0) { + /* add zero */ + r_mant1 |= (r_mant0 != 0); + return normalize_sf(r_sign, r_exp, r_mant1, rm, pfflags); + } + c_mant = normalize_subnormal_sf(&c_exp, c_mant); + } else { + c_mant |= (F_UINT)1 << MANT_SIZE; + } + c_exp++; + c_mant1 = c_mant << (RND_SIZE - 1); + c_mant0 = 0; + + // printf("r_s=%d r_exp=%d r_mant=%08x %08x\n", r_sign, r_exp, (uint32_t)r_mant1, (uint32_t)r_mant0); + // printf("c_s=%d c_exp=%d c_mant=%08x %08x\n", c_sign, c_exp, (uint32_t)c_mant1, (uint32_t)c_mant0); + + /* ensure that abs(r) >= abs(c) */ + if (!(r_exp > c_exp || (r_exp == c_exp && r_mant1 >= c_mant1))) { + F_UINT tmp; + int32_t c_tmp; + /* swap */ + tmp = r_mant1; r_mant1 = c_mant1; c_mant1 = tmp; + tmp = r_mant0; r_mant0 = c_mant0; c_mant0 = tmp; + c_tmp = r_exp; r_exp = c_exp; c_exp = c_tmp; + c_tmp = r_sign; r_sign = c_sign; c_sign = c_tmp; + } + /* right shift c_mant */ + shift = r_exp - c_exp; + if (shift >= 2 * F_SIZE) { + c_mant0 = (c_mant0 | c_mant1) != 0; + c_mant1 = 0; + } else if (shift >= F_SIZE + 1) { + c_mant0 = rshift_rnd(c_mant1, shift - F_SIZE); + c_mant1 = 0; + } else if (shift == F_SIZE) { + c_mant0 = c_mant1 | (c_mant0 != 0); + c_mant1 = 0; + } else if (shift != 0) { + mask = ((F_UINT)1 << shift) - 1; + c_mant0 = (c_mant1 << (F_SIZE - shift)) | (c_mant0 >> shift) | ((c_mant0 & mask) != 0); + c_mant1 = c_mant1 >> shift; + } + // printf(" r_mant=%08x %08x\n", (uint32_t)r_mant1, (uint32_t)r_mant0); + // printf(" c_mant=%08x %08x\n", (uint32_t)c_mant1, (uint32_t)c_mant0); + /* add or subtract */ + if (r_sign == c_sign) { + r_mant0 += c_mant0; + r_mant1 += c_mant1 + (r_mant0 < c_mant0); + } else { + F_UINT tmp; + tmp = r_mant0; + r_mant0 -= c_mant0; + r_mant1 = r_mant1 - c_mant1 - (r_mant0 > tmp); + if ((r_mant0 | r_mant1) == 0) { + /* zero result : the sign needs a specific handling */ + r_sign = (rm == RM_RDN); + } + } +#if 0 + // printf(" r1_mant=%08x %08x\n", (uint32_t)r_mant1, (uint32_t)r_mant0); + /* normalize */ + if (r_mant1 == 0) { + r_mant1 = r_mant0; + r_exp -= F_SIZE; + } else { + shift = clz(r_mant1) - (F_SIZE - 1 - IMANT_SIZE); + if (shift != 0) { + r_mant1 = (r_mant1 << shift) | (r_mant0 >> (F_SIZE - shift)); + r_mant0 <<= shift; + r_exp -= shift; + } + r_mant1 |= (r_mant0 != 0); + } + return normalize_sf(r_sign, r_exp, r_mant1, rm, pfflags); +#else + return normalize2_sf(r_sign, r_exp, r_mant1, r_mant0, rm, pfflags); +#endif +} + +#ifdef F_ULONG + +static F_UINT divrem_u(F_UINT *pr, F_UINT ah, F_UINT al, F_UINT b) +{ + F_ULONG a; + a = ((F_ULONG)ah << F_SIZE) | al; + *pr = a % b; + return a / b; +} + +#else + +/* XXX: optimize */ +static F_UINT divrem_u(F_UINT *pr, F_UINT a1, F_UINT a0, F_UINT b) +{ + int i, qb, ab; + + assert(a1 < b); + for(i = 0; i < F_SIZE; i++) { + ab = a1 >> (F_SIZE - 1); + a1 = (a1 << 1) | (a0 >> (F_SIZE - 1)); + if (ab || a1 >= b) { + a1 -= b; + qb = 1; + } else { + qb = 0; + } + a0 = (a0 << 1) | qb; + } + *pr = a1; + return a0; +} + +#endif + +F_UINT div_sf(F_UINT a, F_UINT b, RoundingModeEnum rm, + uint32_t *pfflags) +{ + uint32_t a_sign, b_sign, r_sign; + int32_t a_exp, b_exp, r_exp; + F_UINT a_mant, b_mant, r_mant, r; + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + r_sign = a_sign ^ b_sign; + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + b_mant = b & MANT_MASK; + if (a_exp == EXP_MASK) { + if (a_mant != 0 || isnan_sf(b)) { + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } + return F_QNAN; + } else if (b_exp == EXP_MASK) { + *pfflags |= FFLAG_INVALID_OP; + return F_QNAN; + } else { + return pack_sf(r_sign, EXP_MASK, 0); + } + } else if (b_exp == EXP_MASK) { + if (b_mant != 0) { + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } + return F_QNAN; + } else { + return pack_sf(r_sign, 0, 0); + } + } + + if (b_exp == 0) { + if (b_mant == 0) { + /* zero */ + if (a_exp == 0 && a_mant == 0) { + *pfflags |= FFLAG_INVALID_OP; + return F_QNAN; + } else { + *pfflags |= FFLAG_DIVIDE_ZERO; + return pack_sf(r_sign, EXP_MASK, 0); + } + } + b_mant = normalize_subnormal_sf(&b_exp, b_mant); + } else { + b_mant |= (F_UINT)1 << MANT_SIZE; + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(r_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + r_exp = a_exp - b_exp + (1 << (EXP_SIZE - 1)) - 1; + r_mant = divrem_u(&r, a_mant, 0, b_mant << 2); + if (r != 0) + r_mant |= 1; + return normalize_sf(r_sign, r_exp, r_mant, rm, pfflags); +} + +#ifdef F_ULONG + +/* compute sqrt(a) with a = ah*2^F_SIZE+al and a < 2^(F_SIZE - 2) + return true if not exact square. */ +static int sqrtrem_u(F_UINT *pr, F_UINT ah, F_UINT al) +{ + F_ULONG a, u, s; + int l, inexact; + + /* 2^l >= a */ + if (ah != 0) { + l = 2 * F_SIZE - clz(ah - 1); + } else { + if (al == 0) { + *pr = 0; + return 0; + } + l = F_SIZE - clz(al - 1); + } + a = ((F_ULONG)ah << F_SIZE) | al; + u = (F_ULONG)1 << ((l + 1) / 2); + for(;;) { + s = u; + u = ((a / s) + s) / 2; + if (u >= s) + break; + } + inexact = (a - s * s) != 0; + *pr = s; + return inexact; +} + +#else + +static int sqrtrem_u(F_UINT *pr, F_UINT a1, F_UINT a0) +{ + int l, inexact; + F_UINT u, s, r, q, sq0, sq1; + + /* 2^l >= a */ + if (a1 != 0) { + l = 2 * F_SIZE - clz(a1 - 1); + } else { + if (a0 == 0) { + *pr = 0; + return 0; + } + l = F_SIZE - clz(a0 - 1); + } + u = (F_UINT)1 << ((l + 1) / 2); + for(;;) { + s = u; + q = divrem_u(&r, a1, a0, s); + u = (q + s) / 2; + if (u >= s) + break; + } + sq1 = mul_u(&sq0, s, s); + inexact = (sq0 != a0 || sq1 != a1); + *pr = s; + return inexact; +} + +#endif + +F_UINT sqrt_sf(F_UINT a, RoundingModeEnum rm, + uint32_t *pfflags) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_sign = a >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + if (a_exp == EXP_MASK) { + if (a_mant != 0) { + if (issignan_sf(a)) { + *pfflags |= FFLAG_INVALID_OP; + } + return F_QNAN; + } else if (a_sign) { + goto neg_error; + } else { + return a; /* +infinity */ + } + } + if (a_sign) { + if (a_exp == 0 && a_mant == 0) + return a; /* -zero */ + neg_error: + *pfflags |= FFLAG_INVALID_OP; + return F_QNAN; + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(0, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + a_exp -= EXP_MASK / 2; + /* simpler to handle an even exponent */ + if (a_exp & 1) { + a_exp--; + a_mant <<= 1; + } + a_exp = (a_exp >> 1) + EXP_MASK / 2; + a_mant <<= (F_SIZE - 4 - MANT_SIZE); + if (sqrtrem_u(&a_mant, a_mant, 0)) + a_mant |= 1; + return normalize_sf(a_sign, a_exp, a_mant, rm, pfflags); +} + +/* comparisons */ + +static F_UINT glue(min_max_nan_sf, F_SIZE)(F_UINT a, F_UINT b, uint32_t *pfflags, SoftFPMinMaxTypeEnum minmax_type) +{ + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + if (minmax_type == FMINMAX_IEEE754_2008) + return F_QNAN; + } + if (minmax_type == FMINMAX_PROP) { + return F_QNAN; + } else { + if (isnan_sf(a)) { + if (isnan_sf(b)) + return F_QNAN; + else + return b; + } else { + return a; + } + } +} + +F_UINT glue(min_sf, F_SIZE)(F_UINT a, F_UINT b, uint32_t *pfflags, + SoftFPMinMaxTypeEnum minmax_type) +{ + uint32_t a_sign, b_sign; + + if (isnan_sf(a) || isnan_sf(b)) { + return glue(min_max_nan_sf, F_SIZE)(a, b, pfflags, minmax_type); + } + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + + if (a_sign != b_sign) { + if (a_sign) + return a; + else + return b; + } else { + if ((a < b) ^ a_sign) + return a; + else + return b; + } +} + +F_UINT glue(max_sf, F_SIZE)(F_UINT a, F_UINT b, uint32_t *pfflags, + SoftFPMinMaxTypeEnum minmax_type) +{ + uint32_t a_sign, b_sign; + + if (isnan_sf(a) || isnan_sf(b)) { + return glue(min_max_nan_sf, F_SIZE)(a, b, pfflags, minmax_type); + } + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + + if (a_sign != b_sign) { + if (a_sign) + return b; + else + return a; + } else { + if ((a < b) ^ a_sign) + return b; + else + return a; + } +} + +int glue(eq_quiet_sf, F_SIZE)(F_UINT a, F_UINT b, uint32_t *pfflags) +{ + if (isnan_sf(a) || isnan_sf(b)) { + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } + return 0; + } + + if ((F_UINT)((a | b) << 1) == 0) + return 1; /* zero case */ + return (a == b); +} + +int glue(le_sf, F_SIZE)(F_UINT a, F_UINT b, uint32_t *pfflags) +{ + uint32_t a_sign, b_sign; + + if (isnan_sf(a) || isnan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + return 0; + } + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + if (a_sign != b_sign) { + return (a_sign || ((F_UINT)((a | b) << 1) == 0)); + } else { + if (a_sign) { + return (a >= b); + } else { + return (a <= b); + } + } +} + +int glue(lt_sf, F_SIZE)(F_UINT a, F_UINT b, uint32_t *pfflags) +{ + uint32_t a_sign, b_sign; + + if (isnan_sf(a) || isnan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + return 0; + } + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + if (a_sign != b_sign) { + return (a_sign && ((F_UINT)((a | b) << 1) != 0)); + } else { + if (a_sign) { + return (a > b); + } else { + return (a < b); + } + } +} + +uint32_t glue(fclass_sf, F_SIZE)(F_UINT a) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + uint32_t ret; + + a_sign = a >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + if (a_exp == EXP_MASK) { + if (a_mant != 0) { + if (a_mant & QNAN_MASK) + ret = FCLASS_QNAN; + else + ret = FCLASS_SNAN; + } else { + if (a_sign) + ret = FCLASS_NINF; + else + ret = FCLASS_PINF; + } + } else if (a_exp == 0) { + if (a_mant == 0) { + if (a_sign) + ret = FCLASS_NZERO; + else + ret = FCLASS_PZERO; + } else { + if (a_sign) + ret = FCLASS_NSUBNORMAL; + else + ret = FCLASS_PSUBNORMAL; + } + } else { + if (a_sign) + ret = FCLASS_NNORMAL; + else + ret = FCLASS_PNORMAL; + } + return ret; +} + +/* conversions between floats */ + +#if F_SIZE >= 64 + +F_UINT cvt_sf32_sf(uint32_t a, uint32_t *pfflags) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf32(&a_sign, &a_exp, a); + if (a_exp == 0xff) { + if (a_mant != 0) { + /* NaN */ + if (issignan_sf32(a)) { + *pfflags |= FFLAG_INVALID_OP; + } + return F_QNAN; + } else { + /* infinity */ + return pack_sf(a_sign, EXP_MASK, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(a_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf32(&a_exp, a_mant); + } + /* convert the exponent value */ + a_exp = a_exp - 0x7f + (EXP_MASK / 2); + /* shift the mantissa */ + a_mant <<= (MANT_SIZE - 23); + /* We assume the target float is large enough to that no + normalization is necessary */ + return pack_sf(a_sign, a_exp, a_mant); +} + +uint32_t glue(glue(cvt_sf, F_SIZE), _sf32)(F_UINT a, RoundingModeEnum rm, + uint32_t *pfflags) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf(&a_sign, &a_exp, a); + if (a_exp == EXP_MASK) { + if (a_mant != 0) { + /* NaN */ + if (issignan_sf(a)) { + *pfflags |= FFLAG_INVALID_OP; + } + return F_QNAN32; + } else { + /* infinity */ + return pack_sf32(a_sign, 0xff, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf32(a_sign, 0, 0); /* zero */ + normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + /* convert the exponent value */ + a_exp = a_exp - (EXP_MASK / 2) + 0x7f; + /* shift the mantissa */ + a_mant = rshift_rnd(a_mant, MANT_SIZE - (32 - 2)); + return normalize_sf32(a_sign, a_exp, a_mant, rm, pfflags); +} + +#endif + +#if F_SIZE >= 128 + +F_UINT cvt_sf64_sf(uint64_t a, uint32_t *pfflags) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf64(&a_sign, &a_exp, a); + + if (a_exp == 0x7ff) { + if (a_mant != 0) { + /* NaN */ + if (issignan_sf64(a)) { + *pfflags |= FFLAG_INVALID_OP; + } + return F_QNAN; + } else { + /* infinity */ + return pack_sf(a_sign, EXP_MASK, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(a_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf64(&a_exp, a_mant); + } + /* convert the exponent value */ + a_exp = a_exp - 0x3ff + (EXP_MASK / 2); + /* shift the mantissa */ + a_mant <<= (MANT_SIZE - 52); + return pack_sf(a_sign, a_exp, a_mant); +} + +uint64_t glue(glue(cvt_sf, F_SIZE), _sf64)(F_UINT a, RoundingModeEnum rm, + uint32_t *pfflags) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf(&a_sign, &a_exp, a); + if (a_exp == EXP_MASK) { + if (a_mant != 0) { + /* NaN */ + if (issignan_sf(a)) { + *pfflags |= FFLAG_INVALID_OP; + } + return F_QNAN64; + } else { + /* infinity */ + return pack_sf64(a_sign, 0x7ff, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf64(a_sign, 0, 0); /* zero */ + normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + /* convert the exponent value */ + a_exp = a_exp - (EXP_MASK / 2) + 0x3ff; + /* shift the mantissa */ + a_mant = rshift_rnd(a_mant, MANT_SIZE - (64 - 2)); + return normalize_sf64(a_sign, a_exp, a_mant, rm, pfflags); +} + +#endif + +#undef clz + +#define ICVT_SIZE 32 +#include "softfp_template_icvt.h" + +#define ICVT_SIZE 64 +#include "softfp_template_icvt.h" + +#ifdef HAVE_INT128 +#define ICVT_SIZE 128 +#include "softfp_template_icvt.h" +#endif + +#undef F_SIZE +#undef F_UINT +#undef F_ULONG +#undef F_UHALF +#undef MANT_SIZE +#undef EXP_SIZE +#undef EXP_MASK +#undef MANT_MASK +#undef SIGN_MASK +#undef IMANT_SIZE +#undef RND_SIZE +#undef QNAN_MASK +#undef F_QNAN + +#undef pack_sf +#undef unpack_sf +#undef rshift_rnd +#undef round_pack_sf +#undef normalize_sf +#undef normalize2_sf +#undef issignan_sf +#undef isnan_sf +#undef add_sf +#undef mul_sf +#undef fma_sf +#undef div_sf +#undef sqrt_sf +#undef normalize_subnormal_sf +#undef divrem_u +#undef sqrtrem_u +#undef mul_u +#undef cvt_sf32_sf +#undef cvt_sf64_sf diff --git a/softfp_template_icvt.h b/softfp_template_icvt.h new file mode 100644 index 0000000..9360ac1 --- /dev/null +++ b/softfp_template_icvt.h @@ -0,0 +1,171 @@ +/* + * SoftFP Library + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#if ICVT_SIZE == 32 +#define ICVT_UINT uint32_t +#define ICVT_INT int32_t +#elif ICVT_SIZE == 64 +#define ICVT_UINT uint64_t +#define ICVT_INT int64_t +#elif ICVT_SIZE == 128 +#define ICVT_UINT uint128_t +#define ICVT_INT int128_t +#else +#error unsupported icvt +#endif + +/* conversions between float and integers */ +static ICVT_INT glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm, + uint32_t *pfflags, BOOL is_unsigned) +{ + uint32_t a_sign, addend, rnd_bits; + int32_t a_exp; + F_UINT a_mant; + ICVT_UINT r, r_max; + + a_sign = a >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + if (a_exp == EXP_MASK && a_mant != 0) + a_sign = 0; /* NaN is like +infinity */ + if (a_exp == 0) { + a_exp = 1; + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + a_mant <<= RND_SIZE; + a_exp = a_exp - (EXP_MASK / 2) - MANT_SIZE; + + if (is_unsigned) + r_max = (ICVT_UINT)a_sign - 1; + else + r_max = ((ICVT_UINT)1 << (ICVT_SIZE - 1)) - (ICVT_UINT)(a_sign ^ 1); + if (a_exp >= 0) { + if (a_exp <= (ICVT_SIZE - 1 - MANT_SIZE)) { + r = (ICVT_UINT)(a_mant >> RND_SIZE) << a_exp; + if (r > r_max) + goto overflow; + } else { + overflow: + *pfflags |= FFLAG_INVALID_OP; + return r_max; + } + } else { + a_mant = rshift_rnd(a_mant, -a_exp); + + switch(rm) { + case RM_RNE: + case RM_RMM: + addend = (1 << (RND_SIZE - 1)); + break; + case RM_RTZ: + addend = 0; + break; + default: + case RM_RDN: + case RM_RUP: + if (a_sign ^ (rm & 1)) + addend = (1 << RND_SIZE) - 1; + else + addend = 0; + break; + } + + rnd_bits = a_mant & ((1 << RND_SIZE ) - 1); + a_mant = (a_mant + addend) >> RND_SIZE; + /* half way: select even result */ + if (rm == RM_RNE && rnd_bits == (1 << (RND_SIZE - 1))) + a_mant &= ~1; + if (a_mant > r_max) + goto overflow; + r = a_mant; + if (rnd_bits != 0) + *pfflags |= FFLAG_INEXACT; + } + if (a_sign) + r = -r; + return r; +} + +ICVT_INT glue(glue(glue(cvt_sf, F_SIZE), _i), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm, + uint32_t *pfflags) +{ + return glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE)(a, rm, + pfflags, FALSE); +} + +ICVT_UINT glue(glue(glue(cvt_sf, F_SIZE), _u), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm, + uint32_t *pfflags) +{ + return glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE) (a, rm, + pfflags, TRUE); +} + +/* conversions between float and integers */ +static F_UINT glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(ICVT_INT a, + RoundingModeEnum rm, + uint32_t *pfflags, + BOOL is_unsigned) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + ICVT_UINT r, mask; + int l; + + if (!is_unsigned && a < 0) { + a_sign = 1; + r = -(ICVT_UINT)a; + } else { + a_sign = 0; + r = a; + } + a_exp = (EXP_MASK / 2) + F_SIZE - 2; + /* need to reduce range before generic float normalization */ + l = ICVT_SIZE - glue(clz, ICVT_SIZE)(r) - (F_SIZE - 1); + if (l > 0) { + mask = r & (((ICVT_UINT)1 << l) - 1); + r = (r >> l) | ((r & mask) != 0); + a_exp += l; + } + a_mant = r; + return normalize_sf(a_sign, a_exp, a_mant, rm, pfflags); +} + +F_UINT glue(glue(glue(cvt_i, ICVT_SIZE), _sf), F_SIZE)(ICVT_INT a, + RoundingModeEnum rm, + uint32_t *pfflags) +{ + return glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(a, rm, pfflags, FALSE); +} + +F_UINT glue(glue(glue(cvt_u, ICVT_SIZE), _sf), F_SIZE)(ICVT_UINT a, + RoundingModeEnum rm, + uint32_t *pfflags) +{ + return glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(a, rm, pfflags, TRUE); +} + +#undef ICVT_SIZE +#undef ICVT_INT +#undef ICVT_UINT diff --git a/splitimg.c b/splitimg.c new file mode 100644 index 0000000..3271290 --- /dev/null +++ b/splitimg.c @@ -0,0 +1,102 @@ +/* + * Disk image splitter + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + int blocksize, ret, i; + const char *infilename, *outpath; + FILE *f, *fo; + char buf1[1024]; + uint8_t *buf; + + if ((optind + 1) >= argc) { + printf("splitimg version " CONFIG_VERSION ", Copyright (c) 2011-2016 Fabrice Bellard\n" + "usage: splitimg infile outpath [blocksize]\n" + "Create a multi-file disk image for the RISCVEMU HTTP block device\n" + "\n" + "outpath must be a directory\n" + "blocksize is the block size in KB\n"); + exit(1); + } + + infilename = argv[optind++]; + outpath = argv[optind++]; + blocksize = 256; + if (optind < argc) + blocksize = strtol(argv[optind++], NULL, 0); + + blocksize *= 1024; + + buf = malloc(blocksize); + + f = fopen(infilename, "rb"); + if (!f) { + perror(infilename); + exit(1); + } + i = 0; + for(;;) { + ret = fread(buf, 1, blocksize, f); + if (ret < 0) { + perror("fread"); + exit(1); + } + if (ret == 0) + break; + if (ret < blocksize) { + printf("warning: last block is not full\n"); + memset(buf + ret, 0, blocksize - ret); + } + snprintf(buf1, sizeof(buf1), "%s/blk%09u.bin", outpath, i); + fo = fopen(buf1, "wb"); + if (!fo) { + perror(buf1); + exit(1); + } + fwrite(buf, 1, blocksize, fo); + fclose(fo); + i++; + } + fclose(f); + printf("%d blocks\n", i); + + snprintf(buf1, sizeof(buf1), "%s/blk.txt", outpath); + fo = fopen(buf1, "wb"); + if (!fo) { + perror(buf1); + exit(1); + } + fprintf(fo, "{\n"); + fprintf(fo, " block_size: %d,\n", blocksize / 1024); + fprintf(fo, " n_block: %d,\n", i); + fprintf(fo, "}\n"); + fclose(fo); + return 0; +} diff --git a/temu.c b/temu.c new file mode 100644 index 0000000..7c07f3b --- /dev/null +++ b/temu.c @@ -0,0 +1,835 @@ +/* + * TinyEMU + * + * Copyright (c) 2016-2018 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#include +#endif +#include +#include + +#include "cutils.h" +#include "iomem.h" +#include "virtio.h" +#include "machine.h" +#ifdef CONFIG_FS_NET +#include "fs_utils.h" +#include "fs_wget.h" +#endif +#ifdef CONFIG_SLIRP +#include "slirp/libslirp.h" +#endif + +#ifndef _WIN32 + +typedef struct { + int stdin_fd; + int console_esc_state; + BOOL resize_pending; +} STDIODevice; + +static struct termios oldtty; +static int old_fd0_flags; +static STDIODevice *global_stdio_device; + +static void term_exit(void) +{ + tcsetattr (0, TCSANOW, &oldtty); + fcntl(0, F_SETFL, old_fd0_flags); +} + +static void term_init(BOOL allow_ctrlc) +{ + struct termios tty; + + memset(&tty, 0, sizeof(tty)); + tcgetattr (0, &tty); + oldtty = tty; + old_fd0_flags = fcntl(0, F_GETFL); + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP + |INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + if (!allow_ctrlc) + tty.c_lflag &= ~ISIG; + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; + + tcsetattr (0, TCSANOW, &tty); + + atexit(term_exit); +} + +static void console_write(void *opaque, const uint8_t *buf, int len) +{ + fwrite(buf, 1, len, stdout); + fflush(stdout); +} + +static int console_read(void *opaque, uint8_t *buf, int len) +{ + STDIODevice *s = opaque; + int ret, i, j; + uint8_t ch; + + if (len <= 0) + return 0; + + ret = read(s->stdin_fd, buf, len); + if (ret < 0) + return 0; + if (ret == 0) { + /* EOF */ + exit(1); + } + + j = 0; + for(i = 0; i < ret; i++) { + ch = buf[i]; + if (s->console_esc_state) { + s->console_esc_state = 0; + switch(ch) { + case 'x': + printf("Terminated\n"); + exit(0); + case 'h': + printf("\n" + "C-a h print this help\n" + "C-a x exit emulator\n" + "C-a C-a send C-a\n" + ); + break; + case 1: + goto output_char; + default: + break; + } + } else { + if (ch == 1) { + s->console_esc_state = 1; + } else { + output_char: + buf[j++] = ch; + } + } + } + return j; +} + +static void term_resize_handler(int sig) +{ + if (global_stdio_device) + global_stdio_device->resize_pending = TRUE; +} + +static void console_get_size(STDIODevice *s, int *pw, int *ph) +{ + struct winsize ws; + int width, height; + /* default values */ + width = 80; + height = 25; + if (ioctl(s->stdin_fd, TIOCGWINSZ, &ws) == 0 && + ws.ws_col >= 4 && ws.ws_row >= 4) { + width = ws.ws_col; + height = ws.ws_row; + } + *pw = width; + *ph = height; +} + +CharacterDevice *console_init(BOOL allow_ctrlc) +{ + CharacterDevice *dev; + STDIODevice *s; + struct sigaction sig; + + term_init(allow_ctrlc); + + dev = mallocz(sizeof(*dev)); + s = mallocz(sizeof(*s)); + s->stdin_fd = 0; + /* Note: the glibc does not properly tests the return value of + write() in printf, so some messages on stdout may be lost */ + fcntl(s->stdin_fd, F_SETFL, O_NONBLOCK); + + s->resize_pending = TRUE; + global_stdio_device = s; + + /* use a signal to get the host terminal resize events */ + sig.sa_handler = term_resize_handler; + sigemptyset(&sig.sa_mask); + sig.sa_flags = 0; + sigaction(SIGWINCH, &sig, NULL); + + dev->opaque = s; + dev->write_data = console_write; + dev->read_data = console_read; + return dev; +} + +#endif /* !_WIN32 */ + +typedef enum { + BF_MODE_RO, + BF_MODE_RW, + BF_MODE_SNAPSHOT, +} BlockDeviceModeEnum; + +#define SECTOR_SIZE 512 + +typedef struct BlockDeviceFile { + FILE *f; + int64_t nb_sectors; + BlockDeviceModeEnum mode; + uint8_t **sector_table; +} BlockDeviceFile; + +static int64_t bf_get_sector_count(BlockDevice *bs) +{ + BlockDeviceFile *bf = bs->opaque; + return bf->nb_sectors; +} + +//#define DUMP_BLOCK_READ + +static int bf_read_async(BlockDevice *bs, + uint64_t sector_num, uint8_t *buf, int n, + BlockDeviceCompletionFunc *cb, void *opaque) +{ + BlockDeviceFile *bf = bs->opaque; + // printf("bf_read_async: sector_num=%" PRId64 " n=%d\n", sector_num, n); +#ifdef DUMP_BLOCK_READ + { + static FILE *f; + if (!f) + f = fopen("/tmp/read_sect.txt", "wb"); + fprintf(f, "%" PRId64 " %d\n", sector_num, n); + } +#endif + if (!bf->f) + return -1; + if (bf->mode == BF_MODE_SNAPSHOT) { + int i; + for(i = 0; i < n; i++) { + if (!bf->sector_table[sector_num]) { + fseek(bf->f, sector_num * SECTOR_SIZE, SEEK_SET); + fread(buf, 1, SECTOR_SIZE, bf->f); + } else { + memcpy(buf, bf->sector_table[sector_num], SECTOR_SIZE); + } + sector_num++; + buf += SECTOR_SIZE; + } + } else { + fseek(bf->f, sector_num * SECTOR_SIZE, SEEK_SET); + fread(buf, 1, n * SECTOR_SIZE, bf->f); + } + /* synchronous read */ + return 0; +} + +static int bf_write_async(BlockDevice *bs, + uint64_t sector_num, const uint8_t *buf, int n, + BlockDeviceCompletionFunc *cb, void *opaque) +{ + BlockDeviceFile *bf = bs->opaque; + int ret; + + switch(bf->mode) { + case BF_MODE_RO: + ret = -1; /* error */ + break; + case BF_MODE_RW: + fseek(bf->f, sector_num * SECTOR_SIZE, SEEK_SET); + fwrite(buf, 1, n * SECTOR_SIZE, bf->f); + ret = 0; + break; + case BF_MODE_SNAPSHOT: + { + int i; + if ((sector_num + n) > bf->nb_sectors) + return -1; + for(i = 0; i < n; i++) { + if (!bf->sector_table[sector_num]) { + bf->sector_table[sector_num] = malloc(SECTOR_SIZE); + } + memcpy(bf->sector_table[sector_num], buf, SECTOR_SIZE); + sector_num++; + buf += SECTOR_SIZE; + } + ret = 0; + } + break; + default: + abort(); + } + + return ret; +} + +static BlockDevice *block_device_init(const char *filename, + BlockDeviceModeEnum mode) +{ + BlockDevice *bs; + BlockDeviceFile *bf; + int64_t file_size; + FILE *f; + const char *mode_str; + + if (mode == BF_MODE_RW) { + mode_str = "r+b"; + } else { + mode_str = "rb"; + } + + f = fopen(filename, mode_str); + if (!f) { + perror(filename); + exit(1); + } + fseek(f, 0, SEEK_END); + file_size = ftello(f); + + bs = mallocz(sizeof(*bs)); + bf = mallocz(sizeof(*bf)); + + bf->mode = mode; + bf->nb_sectors = file_size / 512; + bf->f = f; + + if (mode == BF_MODE_SNAPSHOT) { + bf->sector_table = mallocz(sizeof(bf->sector_table[0]) * + bf->nb_sectors); + } + + bs->opaque = bf; + bs->get_sector_count = bf_get_sector_count; + bs->read_async = bf_read_async; + bs->write_async = bf_write_async; + return bs; +} + +#ifndef _WIN32 + +typedef struct { + int fd; + BOOL select_filled; +} TunState; + +static void tun_write_packet(EthernetDevice *net, + const uint8_t *buf, int len) +{ + TunState *s = net->opaque; + write(s->fd, buf, len); +} + +static void tun_select_fill(EthernetDevice *net, int *pfd_max, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int *pdelay) +{ + TunState *s = net->opaque; + int net_fd = s->fd; + + s->select_filled = net->device_can_write_packet(net); + if (s->select_filled) { + FD_SET(net_fd, rfds); + *pfd_max = max_int(*pfd_max, net_fd); + } +} + +static void tun_select_poll(EthernetDevice *net, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int select_ret) +{ + TunState *s = net->opaque; + int net_fd = s->fd; + uint8_t buf[2048]; + int ret; + + if (select_ret <= 0) + return; + if (s->select_filled && FD_ISSET(net_fd, rfds)) { + ret = read(net_fd, buf, sizeof(buf)); + if (ret > 0) + net->device_write_packet(net, buf, ret); + } + +} + +/* configure with: +# bridge configuration (connect tap0 to bridge interface br0) + ip link add br0 type bridge + ip tuntap add dev tap0 mode tap [user x] [group x] + ip link set tap0 master br0 + ip link set dev br0 up + ip link set dev tap0 up + +# NAT configuration (eth1 is the interface connected to internet) + ifconfig br0 192.168.3.1 + echo 1 > /proc/sys/net/ipv4/ip_forward + iptables -D FORWARD 1 + iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE + + In the VM: + ifconfig eth0 192.168.3.2 + route add -net 0.0.0.0 netmask 0.0.0.0 gw 192.168.3.1 +*/ +static EthernetDevice *tun_open(const char *ifname) +{ + struct ifreq ifr; + int fd, ret; + EthernetDevice *net; + TunState *s; + + fd = open("/dev/net/tun", O_RDWR); + if (fd < 0) { + fprintf(stderr, "Error: could not open /dev/net/tun\n"); + return NULL; + } + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + pstrcpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname); + ret = ioctl(fd, TUNSETIFF, (void *) &ifr); + if (ret != 0) { + fprintf(stderr, "Error: could not configure /dev/net/tun\n"); + close(fd); + return NULL; + } + fcntl(fd, F_SETFL, O_NONBLOCK); + + net = mallocz(sizeof(*net)); + net->mac_addr[0] = 0x02; + net->mac_addr[1] = 0x00; + net->mac_addr[2] = 0x00; + net->mac_addr[3] = 0x00; + net->mac_addr[4] = 0x00; + net->mac_addr[5] = 0x01; + s = mallocz(sizeof(*s)); + s->fd = fd; + net->opaque = s; + net->write_packet = tun_write_packet; + net->select_fill = tun_select_fill; + net->select_poll = tun_select_poll; + return net; +} + +#endif /* !_WIN32 */ + +#ifdef CONFIG_SLIRP + +/*******************************************************/ +/* slirp */ + +static Slirp *slirp_state; + +static void slirp_write_packet(EthernetDevice *net, + const uint8_t *buf, int len) +{ + Slirp *slirp_state = net->opaque; + slirp_input(slirp_state, buf, len); +} + +int slirp_can_output(void *opaque) +{ + EthernetDevice *net = opaque; + return net->device_can_write_packet(net); +} + +void slirp_output(void *opaque, const uint8_t *pkt, int pkt_len) +{ + EthernetDevice *net = opaque; + return net->device_write_packet(net, pkt, pkt_len); +} + +static void slirp_select_fill1(EthernetDevice *net, int *pfd_max, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int *pdelay) +{ + Slirp *slirp_state = net->opaque; + slirp_select_fill(slirp_state, pfd_max, rfds, wfds, efds); +} + +static void slirp_select_poll1(EthernetDevice *net, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int select_ret) +{ + Slirp *slirp_state = net->opaque; + slirp_select_poll(slirp_state, rfds, wfds, efds, (select_ret <= 0)); +} + +static EthernetDevice *slirp_open(void) +{ + EthernetDevice *net; + struct in_addr net_addr = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */ + struct in_addr mask = { .s_addr = htonl(0xffffff00) }; /* 255.255.255.0 */ + struct in_addr host = { .s_addr = htonl(0x0a000202) }; /* 10.0.2.2 */ + struct in_addr dhcp = { .s_addr = htonl(0x0a00020f) }; /* 10.0.2.15 */ + struct in_addr dns = { .s_addr = htonl(0x0a000203) }; /* 10.0.2.3 */ + const char *bootfile = NULL; + const char *vhostname = NULL; + int restricted = 0; + + if (slirp_state) { + fprintf(stderr, "Only a single slirp instance is allowed\n"); + return NULL; + } + net = mallocz(sizeof(*net)); + + slirp_state = slirp_init(restricted, net_addr, mask, host, vhostname, + "", bootfile, dhcp, dns, net); + + net->mac_addr[0] = 0x02; + net->mac_addr[1] = 0x00; + net->mac_addr[2] = 0x00; + net->mac_addr[3] = 0x00; + net->mac_addr[4] = 0x00; + net->mac_addr[5] = 0x01; + net->opaque = slirp_state; + net->write_packet = slirp_write_packet; + net->select_fill = slirp_select_fill1; + net->select_poll = slirp_select_poll1; + + return net; +} + +#endif /* CONFIG_SLIRP */ + +#define MAX_EXEC_CYCLE 500000 +#define MAX_SLEEP_TIME 10 /* in ms */ + +void virt_machine_run(VirtMachine *m) +{ + fd_set rfds, wfds, efds; + int fd_max, ret, delay; + struct timeval tv; +#ifndef _WIN32 + int stdin_fd; +#endif + + delay = virt_machine_get_sleep_duration(m, MAX_SLEEP_TIME); + + /* wait for an event */ + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + fd_max = -1; +#ifndef _WIN32 + if (m->console_dev && virtio_console_can_write_data(m->console_dev)) { + STDIODevice *s = m->console->opaque; + stdin_fd = s->stdin_fd; + FD_SET(stdin_fd, &rfds); + fd_max = stdin_fd; + + if (s->resize_pending) { + int width, height; + console_get_size(s, &width, &height); + virtio_console_resize_event(m->console_dev, width, height); + s->resize_pending = FALSE; + } + } +#endif + if (m->net) { + m->net->select_fill(m->net, &fd_max, &rfds, &wfds, &efds, &delay); + } +#ifdef CONFIG_FS_NET + fs_net_set_fdset(&fd_max, &rfds, &wfds, &efds, &delay); +#endif + tv.tv_sec = delay / 1000; + tv.tv_usec = (delay % 1000) * 1000; + ret = select(fd_max + 1, &rfds, &wfds, &efds, &tv); + if (m->net) { + m->net->select_poll(m->net, &rfds, &wfds, &efds, ret); + } + if (ret > 0) { +#ifndef _WIN32 + if (m->console_dev && FD_ISSET(stdin_fd, &rfds)) { + uint8_t buf[128]; + int ret, len; + len = virtio_console_get_write_len(m->console_dev); + len = min_int(len, sizeof(buf)); + ret = m->console->read_data(m->console->opaque, buf, len); + if (ret > 0) { + virtio_console_write_data(m->console_dev, buf, ret); + } + } +#endif + } + +#ifdef CONFIG_SDL + sdl_refresh(m); +#endif + + virt_machine_interp(m, MAX_EXEC_CYCLE); +} + +/*******************************************************/ + +static struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "ctrlc", no_argument }, + { "rw", no_argument }, + { "ro", no_argument }, + { "append", required_argument }, + { "no-accel", no_argument }, + { "build-preload", required_argument }, + { NULL }, +}; + +void help(void) +{ + printf("temu version " CONFIG_VERSION ", Copyright (c) 2016-2018 Fabrice Bellard\n" + "usage: riscvemu [options] config_file\n" + "options are:\n" + "-m ram_size set the RAM size in MB\n" + "-rw allow write access to the disk image (default=snapshot)\n" + "-ctrlc the C-c key stops the emulator instead of being sent to the\n" + " emulated software\n" + "-append cmdline append cmdline to the kernel command line\n" + "-no-accel disable VM acceleration (KVM, x86 machine only)\n" + "\n" + "Console keys:\n" + "Press C-a x to exit the emulator, C-a h to get some help.\n"); + exit(1); +} + +#ifdef CONFIG_FS_NET +static BOOL net_completed; + +static void net_start_cb(void *arg) +{ + net_completed = TRUE; +} + +static BOOL net_poll_cb(void *arg) +{ + return net_completed; +} + +#endif + +int main(int argc, char **argv) +{ + VirtMachine *s; + const char *path, *cmdline, *build_preload_file; + int c, option_index, i, ram_size, accel_enable; + BOOL allow_ctrlc; + BlockDeviceModeEnum drive_mode; + VirtMachineParams p_s, *p = &p_s; + + ram_size = -1; + allow_ctrlc = FALSE; + (void)allow_ctrlc; + drive_mode = BF_MODE_SNAPSHOT; + accel_enable = -1; + cmdline = NULL; + build_preload_file = NULL; + for(;;) { + c = getopt_long_only(argc, argv, "hm:", options, &option_index); + if (c == -1) + break; + switch(c) { + case 0: + switch(option_index) { + case 1: /* ctrlc */ + allow_ctrlc = TRUE; + break; + case 2: /* rw */ + drive_mode = BF_MODE_RW; + break; + case 3: /* ro */ + drive_mode = BF_MODE_RO; + break; + case 4: /* append */ + cmdline = optarg; + break; + case 5: /* no-accel */ + accel_enable = FALSE; + break; + case 6: /* build-preload */ + build_preload_file = optarg; + break; + default: + fprintf(stderr, "unknown option index: %d\n", option_index); + exit(1); + } + break; + case 'h': + help(); + break; + case 'm': + ram_size = strtoul(optarg, NULL, 0); + break; + default: + exit(1); + } + } + + if (optind >= argc) { + help(); + } + + path = argv[optind++]; + + virt_machine_set_defaults(p); +#ifdef CONFIG_FS_NET + fs_wget_init(); +#endif + virt_machine_load_config_file(p, path, NULL, NULL); +#ifdef CONFIG_FS_NET + fs_net_event_loop(NULL, NULL); +#endif + + /* override some config parameters */ + + if (ram_size > 0) { + p->ram_size = (uint64_t)ram_size << 20; + } + if (accel_enable != -1) + p->accel_enable = accel_enable; + if (cmdline) { + vm_add_cmdline(p, cmdline); + } + + /* open the files & devices */ + for(i = 0; i < p->drive_count; i++) { + BlockDevice *drive; + char *fname; + fname = get_file_path(p->cfg_filename, p->tab_drive[i].filename); +#ifdef CONFIG_FS_NET + if (is_url(fname)) { + net_completed = FALSE; + drive = block_device_init_http(fname, 128 * 1024, + net_start_cb, NULL); + /* wait until the drive is initialized */ + fs_net_event_loop(net_poll_cb, NULL); + } else +#endif + { + drive = block_device_init(fname, drive_mode); + } + free(fname); + p->tab_drive[i].block_dev = drive; + } + + for(i = 0; i < p->fs_count; i++) { + FSDevice *fs; + const char *path; + path = p->tab_fs[i].filename; +#ifdef CONFIG_FS_NET + if (is_url(path)) { + fs = fs_net_init(path, NULL, NULL); + if (!fs) + exit(1); + if (build_preload_file) + fs_dump_cache_load(fs, build_preload_file); + fs_net_event_loop(NULL, NULL); + } else +#endif + { +#ifdef _WIN32 + fprintf(stderr, "Filesystem access not supported yet\n"); + exit(1); +#else + char *fname; + fname = get_file_path(p->cfg_filename, path); + fs = fs_disk_init(fname); + if (!fs) { + fprintf(stderr, "%s: must be a directory\n", fname); + exit(1); + } + free(fname); +#endif + } + p->tab_fs[i].fs_dev = fs; + } + + for(i = 0; i < p->eth_count; i++) { +#ifdef CONFIG_SLIRP + if (!strcmp(p->tab_eth[i].driver, "user")) { + p->tab_eth[i].net = slirp_open(); + if (!p->tab_eth[i].net) + exit(1); + } else +#endif +#ifndef _WIN32 + if (!strcmp(p->tab_eth[i].driver, "tap")) { + p->tab_eth[i].net = tun_open(p->tab_eth[i].ifname); + if (!p->tab_eth[i].net) + exit(1); + } else +#endif + { + fprintf(stderr, "Unsupported network driver '%s'\n", + p->tab_eth[i].driver); + exit(1); + } + } + +#ifdef CONFIG_SDL + if (p->display_device) { + sdl_init(p->width, p->height); + } else +#endif + { +#ifdef _WIN32 + fprintf(stderr, "Console not supported yet\n"); + exit(1); +#else + p->console = console_init(allow_ctrlc); +#endif + } + p->rtc_real_time = TRUE; + + s = virt_machine_init(p); + if (!s) + exit(1); + + virt_machine_free_config(p); + + if (s->net) { + s->net->device_set_carrier(s->net, TRUE); + } + + for(;;) { + virt_machine_run(s); + } + virt_machine_end(s); + return 0; +} diff --git a/vga.c b/vga.c new file mode 100644 index 0000000..908a320 --- /dev/null +++ b/vga.c @@ -0,0 +1,804 @@ +/* + * Dummy VGA device + * + * Copyright (c) 2003-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "iomem.h" +#include "virtio.h" +#include "machine.h" + +//#define DEBUG_VBE + +#define MSR_COLOR_EMULATION 0x01 +#define MSR_PAGE_SELECT 0x20 + +#define ST01_V_RETRACE 0x08 +#define ST01_DISP_ENABLE 0x01 + +#define VBE_DISPI_INDEX_ID 0x0 +#define VBE_DISPI_INDEX_XRES 0x1 +#define VBE_DISPI_INDEX_YRES 0x2 +#define VBE_DISPI_INDEX_BPP 0x3 +#define VBE_DISPI_INDEX_ENABLE 0x4 +#define VBE_DISPI_INDEX_BANK 0x5 +#define VBE_DISPI_INDEX_VIRT_WIDTH 0x6 +#define VBE_DISPI_INDEX_VIRT_HEIGHT 0x7 +#define VBE_DISPI_INDEX_X_OFFSET 0x8 +#define VBE_DISPI_INDEX_Y_OFFSET 0x9 +#define VBE_DISPI_INDEX_VIDEO_MEMORY_64K 0xa +#define VBE_DISPI_INDEX_NB 0xb + +#define VBE_DISPI_ID0 0xB0C0 +#define VBE_DISPI_ID1 0xB0C1 +#define VBE_DISPI_ID2 0xB0C2 +#define VBE_DISPI_ID3 0xB0C3 +#define VBE_DISPI_ID4 0xB0C4 +#define VBE_DISPI_ID5 0xB0C5 + +#define VBE_DISPI_DISABLED 0x00 +#define VBE_DISPI_ENABLED 0x01 +#define VBE_DISPI_GETCAPS 0x02 +#define VBE_DISPI_8BIT_DAC 0x20 +#define VBE_DISPI_LFB_ENABLED 0x40 +#define VBE_DISPI_NOCLEARMEM 0x80 + +#define FB_ALLOC_ALIGN (1 << 20) + +#define MAX_TEXT_WIDTH 132 +#define MAX_TEXT_HEIGHT 60 + +struct VGAState { + FBDevice *fb_dev; + int fb_page_count; + PhysMemoryRange *mem_range; + PhysMemoryRange *mem_range2; + PCIDevice *pci_dev; + PhysMemoryRange *rom_range; + + uint8_t *vga_ram; /* 128K at 0xa0000 */ + + uint8_t sr_index; + uint8_t sr[8]; + uint8_t gr_index; + uint8_t gr[16]; + uint8_t ar_index; + uint8_t ar[21]; + int ar_flip_flop; + uint8_t cr_index; + uint8_t cr[256]; /* CRT registers */ + uint8_t msr; /* Misc Output Register */ + uint8_t fcr; /* Feature Control Register */ + uint8_t st00; /* status 0 */ + uint8_t st01; /* status 1 */ + uint8_t dac_state; + uint8_t dac_sub_index; + uint8_t dac_read_index; + uint8_t dac_write_index; + uint8_t dac_cache[3]; /* used when writing */ + uint8_t palette[768]; + + /* text mode state */ + uint32_t last_palette[16]; + uint16_t last_ch_attr[MAX_TEXT_WIDTH * MAX_TEXT_HEIGHT]; + uint32_t last_width; + uint32_t last_height; + uint16_t last_line_offset; + uint16_t last_start_addr; + uint16_t last_cursor_offset; + uint8_t last_cursor_start; + uint8_t last_cursor_end; + + /* VBE extension */ + uint16_t vbe_index; + uint16_t vbe_regs[VBE_DISPI_INDEX_NB]; +}; + +static void vga_draw_glyph8(uint8_t *d, int linesize, + const uint8_t *font_ptr, int h, + uint32_t fgcol, uint32_t bgcol) +{ + uint32_t font_data, xorcol; + + xorcol = bgcol ^ fgcol; + do { + font_data = font_ptr[0]; + ((uint32_t *)d)[0] = (-((font_data >> 7)) & xorcol) ^ bgcol; + ((uint32_t *)d)[1] = (-((font_data >> 6) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[2] = (-((font_data >> 5) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[3] = (-((font_data >> 4) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[4] = (-((font_data >> 3) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[5] = (-((font_data >> 2) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[6] = (-((font_data >> 1) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[7] = (-((font_data >> 0) & 1) & xorcol) ^ bgcol; + font_ptr++; + d += linesize; + } while (--h); +} + +static void vga_draw_glyph9(uint8_t *d, int linesize, + const uint8_t *font_ptr, int h, + uint32_t fgcol, uint32_t bgcol, + int dup9) +{ + uint32_t font_data, xorcol, v; + + xorcol = bgcol ^ fgcol; + do { + font_data = font_ptr[0]; + ((uint32_t *)d)[0] = (-((font_data >> 7)) & xorcol) ^ bgcol; + ((uint32_t *)d)[1] = (-((font_data >> 6) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[2] = (-((font_data >> 5) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[3] = (-((font_data >> 4) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[4] = (-((font_data >> 3) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[5] = (-((font_data >> 2) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[6] = (-((font_data >> 1) & 1) & xorcol) ^ bgcol; + v = (-((font_data >> 0) & 1) & xorcol) ^ bgcol; + ((uint32_t *)d)[7] = v; + if (dup9) + ((uint32_t *)d)[8] = v; + else + ((uint32_t *)d)[8] = bgcol; + font_ptr++; + d += linesize; + } while (--h); +} + +static const uint8_t cursor_glyph[32] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static inline int c6_to_8(int v) +{ + int b; + v &= 0x3f; + b = v & 1; + return (v << 2) | (b << 1) | b; +} + +static int update_palette16(VGAState *s, uint32_t *palette) +{ + int full_update, i; + uint32_t v, col; + + full_update = 0; + for(i = 0; i < 16; i++) { + v = s->ar[i]; + if (s->ar[0x10] & 0x80) + v = ((s->ar[0x14] & 0xf) << 4) | (v & 0xf); + else + v = ((s->ar[0x14] & 0xc) << 4) | (v & 0x3f); + v = v * 3; + col = (c6_to_8(s->palette[v]) << 16) | + (c6_to_8(s->palette[v + 1]) << 8) | + c6_to_8(s->palette[v + 2]); + if (col != palette[i]) { + full_update = 1; + palette[i] = col; + } + } + return full_update; +} + +/* the text refresh is just for debugging and initial boot message, so + it is very incomplete */ +static void vga_text_refresh(VGAState *s, + SimpleFBDrawFunc *redraw_func, void *opaque) +{ + FBDevice *fb_dev = s->fb_dev; + int width, height, cwidth, cheight, cy, cx, x1, y1, width1, height1; + int cx_min, cx_max, dup9; + uint32_t ch_attr, line_offset, start_addr, ch_addr, ch_addr1, ch, cattr; + uint8_t *vga_ram, *font_ptr, *dst; + uint32_t fgcol, bgcol, cursor_offset, cursor_start, cursor_end; + BOOL full_update; + + full_update = update_palette16(s, s->last_palette); + + vga_ram = s->vga_ram; + + line_offset = s->cr[0x13]; + line_offset <<= 3; + line_offset >>= 1; + + start_addr = s->cr[0x0d] | (s->cr[0x0c] << 8); + + cheight = (s->cr[9] & 0x1f) + 1; + cwidth = 8; + if (!(s->sr[1] & 0x01)) + cwidth++; + + width = (s->cr[0x01] + 1); + height = s->cr[0x12] | + ((s->cr[0x07] & 0x02) << 7) | + ((s->cr[0x07] & 0x40) << 3); + height = (height + 1) / cheight; + + width1 = width * cwidth; + height1 = height * cheight; + if (fb_dev->width < width1 || fb_dev->height < height1 || + width > MAX_TEXT_WIDTH || height > MAX_TEXT_HEIGHT) + return; /* not enough space */ + if (s->last_line_offset != line_offset || + s->last_start_addr != start_addr || + s->last_width != width || + s->last_height != height) { + s->last_line_offset = line_offset; + s->last_start_addr = start_addr; + s->last_width = width; + s->last_height = height; + full_update = TRUE; + } + + /* update cursor position */ + cursor_offset = ((s->cr[0x0e] << 8) | s->cr[0x0f]) - start_addr; + cursor_start = s->cr[0xa]; + cursor_end = s->cr[0xb]; + if (cursor_offset != s->last_cursor_offset || + cursor_start != s->last_cursor_start || + cursor_end != s->last_cursor_end) { + /* force refresh of characters with the cursor */ + if (s->last_cursor_offset < MAX_TEXT_WIDTH * MAX_TEXT_HEIGHT) + s->last_ch_attr[s->last_cursor_offset] = -1; + if (cursor_offset < MAX_TEXT_WIDTH * MAX_TEXT_HEIGHT) + s->last_ch_attr[cursor_offset] = -1; + s->last_cursor_offset = cursor_offset; + s->last_cursor_start = cursor_start; + s->last_cursor_end = cursor_end; + } + + ch_addr1 = 0x18000 + (start_addr * 2); + cursor_offset = 0x18000 + (start_addr + cursor_offset) * 2; + + x1 = (fb_dev->width - width1) / 2; + y1 = (fb_dev->height - height1) / 2; +#if 0 + printf("text refresh %dx%d font=%dx%d start_addr=0x%x line_offset=0x%x\n", + width, height, cwidth, cheight, start_addr, line_offset); +#endif + for(cy = 0; cy < height; cy++) { + ch_addr = ch_addr1; + dst = fb_dev->fb_data + (y1 + cy * cheight) * fb_dev->stride + x1 * 4; + cx_min = width; + cx_max = -1; + for(cx = 0; cx < width; cx++) { + ch_attr = *(uint16_t *)(vga_ram + (ch_addr & 0x1fffe)); + if (full_update || ch_attr != s->last_ch_attr[cy * width + cx]) { + s->last_ch_attr[cy * width + cx] = ch_attr; + cx_min = min_int(cx_min, cx); + cx_max = max_int(cx_max, cx); + ch = ch_attr & 0xff; + cattr = ch_attr >> 8; + font_ptr = vga_ram + 32 * ch; + bgcol = s->last_palette[cattr >> 4]; + fgcol = s->last_palette[cattr & 0x0f]; + if (cwidth == 8) { + vga_draw_glyph8(dst, fb_dev->stride, font_ptr, cheight, + fgcol, bgcol); + } else { + dup9 = 0; + if (ch >= 0xb0 && ch <= 0xdf && (s->ar[0x10] & 0x04)) + dup9 = 1; + vga_draw_glyph9(dst, fb_dev->stride, font_ptr, cheight, + fgcol, bgcol, dup9); + } + /* cursor display */ + if (cursor_offset == ch_addr && !(cursor_start & 0x20)) { + int line_start, line_last, h; + uint8_t *dst1; + line_start = cursor_start & 0x1f; + line_last = min_int(cursor_end & 0x1f, cheight - 1); + + if (line_last >= line_start && line_start < cheight) { + h = line_last - line_start + 1; + dst1 = dst + fb_dev->stride * line_start; + if (cwidth == 8) { + vga_draw_glyph8(dst1, fb_dev->stride, + cursor_glyph, + h, fgcol, bgcol); + } else { + vga_draw_glyph9(dst1, fb_dev->stride, + cursor_glyph, + h, fgcol, bgcol, 1); + } + } + } + } + ch_addr += 2; + dst += 4 * cwidth; + } + if (cx_max >= cx_min) { + // printf("redraw %d %d %d\n", cy, cx_min, cx_max); + redraw_func(fb_dev, opaque, + x1 + cx_min * cwidth, y1 + cy * cheight, + (cx_max - cx_min + 1) * cwidth, cheight); + } + ch_addr1 += line_offset; + } +} + +static void vga_refresh(FBDevice *fb_dev, + SimpleFBDrawFunc *redraw_func, void *opaque) +{ + VGAState *s = fb_dev->device_opaque; + + if (!(s->ar_index & 0x20)) { + /* blank */ + } else if (s->gr[0x06] & 1) { + /* graphic mode (VBE) */ + simplefb_refresh(fb_dev, redraw_func, opaque, s->mem_range, s->fb_page_count); + } else { + /* text mode */ + vga_text_refresh(s, redraw_func, opaque); + } +} + +/* force some bits to zero */ +static const uint8_t sr_mask[8] = { + (uint8_t)~0xfc, + (uint8_t)~0xc2, + (uint8_t)~0xf0, + (uint8_t)~0xc0, + (uint8_t)~0xf1, + (uint8_t)~0xff, + (uint8_t)~0xff, + (uint8_t)~0x00, +}; + +static const uint8_t gr_mask[16] = { + (uint8_t)~0xf0, /* 0x00 */ + (uint8_t)~0xf0, /* 0x01 */ + (uint8_t)~0xf0, /* 0x02 */ + (uint8_t)~0xe0, /* 0x03 */ + (uint8_t)~0xfc, /* 0x04 */ + (uint8_t)~0x84, /* 0x05 */ + (uint8_t)~0xf0, /* 0x06 */ + (uint8_t)~0xf0, /* 0x07 */ + (uint8_t)~0x00, /* 0x08 */ + (uint8_t)~0xff, /* 0x09 */ + (uint8_t)~0xff, /* 0x0a */ + (uint8_t)~0xff, /* 0x0b */ + (uint8_t)~0xff, /* 0x0c */ + (uint8_t)~0xff, /* 0x0d */ + (uint8_t)~0xff, /* 0x0e */ + (uint8_t)~0xff, /* 0x0f */ +}; + +static uint32_t vga_ioport_read(VGAState *s, uint32_t addr) +{ + int val, index; + + /* check port range access depending on color/monochrome mode */ + if ((addr >= 0x3b0 && addr <= 0x3bf && (s->msr & MSR_COLOR_EMULATION)) || + (addr >= 0x3d0 && addr <= 0x3df && !(s->msr & MSR_COLOR_EMULATION))) { + val = 0xff; + } else { + switch(addr) { + case 0x3c0: + if (s->ar_flip_flop == 0) { + val = s->ar_index; + } else { + val = 0; + } + break; + case 0x3c1: + index = s->ar_index & 0x1f; + if (index < 21) + val = s->ar[index]; + else + val = 0; + break; + case 0x3c2: + val = s->st00; + break; + case 0x3c4: + val = s->sr_index; + break; + case 0x3c5: + val = s->sr[s->sr_index]; +#ifdef DEBUG_VGA_REG + printf("vga: read SR%x = 0x%02x\n", s->sr_index, val); +#endif + break; + case 0x3c7: + val = s->dac_state; + break; + case 0x3c8: + val = s->dac_write_index; + break; + case 0x3c9: + val = s->palette[s->dac_read_index * 3 + s->dac_sub_index]; + if (++s->dac_sub_index == 3) { + s->dac_sub_index = 0; + s->dac_read_index++; + } + break; + case 0x3ca: + val = s->fcr; + break; + case 0x3cc: + val = s->msr; + break; + case 0x3ce: + val = s->gr_index; + break; + case 0x3cf: + val = s->gr[s->gr_index]; +#ifdef DEBUG_VGA_REG + printf("vga: read GR%x = 0x%02x\n", s->gr_index, val); +#endif + break; + case 0x3b4: + case 0x3d4: + val = s->cr_index; + break; + case 0x3b5: + case 0x3d5: + val = s->cr[s->cr_index]; +#ifdef DEBUG_VGA_REG + printf("vga: read CR%x = 0x%02x\n", s->cr_index, val); +#endif + break; + case 0x3ba: + case 0x3da: + /* just toggle to fool polling */ + s->st01 ^= ST01_V_RETRACE | ST01_DISP_ENABLE; + val = s->st01; + s->ar_flip_flop = 0; + break; + default: + val = 0x00; + break; + } + } +#if defined(DEBUG_VGA) + printf("VGA: read addr=0x%04x data=0x%02x\n", addr, val); +#endif + return val; +} + +static void vga_ioport_write(VGAState *s, uint32_t addr, uint32_t val) +{ + int index; + + /* check port range access depending on color/monochrome mode */ + if ((addr >= 0x3b0 && addr <= 0x3bf && (s->msr & MSR_COLOR_EMULATION)) || + (addr >= 0x3d0 && addr <= 0x3df && !(s->msr & MSR_COLOR_EMULATION))) + return; + +#ifdef DEBUG_VGA + printf("VGA: write addr=0x%04x data=0x%02x\n", addr, val); +#endif + + switch(addr) { + case 0x3c0: + if (s->ar_flip_flop == 0) { + val &= 0x3f; + s->ar_index = val; + } else { + index = s->ar_index & 0x1f; + switch(index) { + case 0x00 ... 0x0f: + s->ar[index] = val & 0x3f; + break; + case 0x10: + s->ar[index] = val & ~0x10; + break; + case 0x11: + s->ar[index] = val; + break; + case 0x12: + s->ar[index] = val & ~0xc0; + break; + case 0x13: + s->ar[index] = val & ~0xf0; + break; + case 0x14: + s->ar[index] = val & ~0xf0; + break; + default: + break; + } + } + s->ar_flip_flop ^= 1; + break; + case 0x3c2: + s->msr = val & ~0x10; + break; + case 0x3c4: + s->sr_index = val & 7; + break; + case 0x3c5: +#ifdef DEBUG_VGA_REG + printf("vga: write SR%x = 0x%02x\n", s->sr_index, val); +#endif + s->sr[s->sr_index] = val & sr_mask[s->sr_index]; + break; + case 0x3c7: + s->dac_read_index = val; + s->dac_sub_index = 0; + s->dac_state = 3; + break; + case 0x3c8: + s->dac_write_index = val; + s->dac_sub_index = 0; + s->dac_state = 0; + break; + case 0x3c9: + s->dac_cache[s->dac_sub_index] = val; + if (++s->dac_sub_index == 3) { + memcpy(&s->palette[s->dac_write_index * 3], s->dac_cache, 3); + s->dac_sub_index = 0; + s->dac_write_index++; + } + break; + case 0x3ce: + s->gr_index = val & 0x0f; + break; + case 0x3cf: +#ifdef DEBUG_VGA_REG + printf("vga: write GR%x = 0x%02x\n", s->gr_index, val); +#endif + s->gr[s->gr_index] = val & gr_mask[s->gr_index]; + break; + case 0x3b4: + case 0x3d4: + s->cr_index = val; + break; + case 0x3b5: + case 0x3d5: +#ifdef DEBUG_VGA_REG + printf("vga: write CR%x = 0x%02x\n", s->cr_index, val); +#endif + /* handle CR0-7 protection */ + if ((s->cr[0x11] & 0x80) && s->cr_index <= 7) { + /* can always write bit 4 of CR7 */ + if (s->cr_index == 7) + s->cr[7] = (s->cr[7] & ~0x10) | (val & 0x10); + return; + } + switch(s->cr_index) { + case 0x01: /* horizontal display end */ + case 0x07: + case 0x09: + case 0x0c: + case 0x0d: + case 0x12: /* vertical display end */ + s->cr[s->cr_index] = val; + break; + default: + s->cr[s->cr_index] = val; + break; + } + break; + case 0x3ba: + case 0x3da: + s->fcr = val & 0x10; + break; + } +} + +#define VGA_IO(base) \ +static uint32_t vga_read_ ## base(void *opaque, uint32_t addr, int size_log2)\ +{\ + return vga_ioport_read(opaque, base + addr);\ +}\ +static void vga_write_ ## base(void *opaque, uint32_t addr, uint32_t val, int size_log2)\ +{\ + return vga_ioport_write(opaque, base + addr, val);\ +} + +VGA_IO(0x3c0) +VGA_IO(0x3b4) +VGA_IO(0x3d4) +VGA_IO(0x3ba) +VGA_IO(0x3da) + +static void vbe_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ + VGAState *s = opaque; + FBDevice *fb_dev = s->fb_dev; + + if (offset == 0) { + s->vbe_index = val; + } else { +#ifdef DEBUG_VBE + printf("VBE write: index=0x%04x val=0x%04x\n", s->vbe_index, val); +#endif + switch(s->vbe_index) { + case VBE_DISPI_INDEX_ID: + if (val >= VBE_DISPI_ID0 && val <= VBE_DISPI_ID5) + s->vbe_regs[s->vbe_index] = val; + break; + case VBE_DISPI_INDEX_ENABLE: + if ((val & VBE_DISPI_ENABLED) && + !(s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED)) { + /* set graphic mode */ + /* XXX: resolution change not really supported */ + if (s->vbe_regs[VBE_DISPI_INDEX_XRES] <= 4096 && + s->vbe_regs[VBE_DISPI_INDEX_YRES] <= 4096 && + (s->vbe_regs[VBE_DISPI_INDEX_XRES] * 4 * + s->vbe_regs[VBE_DISPI_INDEX_YRES]) <= fb_dev->fb_size) { + fb_dev->width = s->vbe_regs[VBE_DISPI_INDEX_XRES]; + fb_dev->height = s->vbe_regs[VBE_DISPI_INDEX_YRES]; + fb_dev->stride = fb_dev->width * 4; + } + s->vbe_regs[VBE_DISPI_INDEX_VIRT_WIDTH] = + s->vbe_regs[VBE_DISPI_INDEX_XRES]; + s->vbe_regs[VBE_DISPI_INDEX_VIRT_HEIGHT] = + s->vbe_regs[VBE_DISPI_INDEX_YRES]; + s->vbe_regs[VBE_DISPI_INDEX_X_OFFSET] = 0; + s->vbe_regs[VBE_DISPI_INDEX_Y_OFFSET] = 0; + } + s->vbe_regs[s->vbe_index] = val; + break; + case VBE_DISPI_INDEX_XRES: + case VBE_DISPI_INDEX_YRES: + case VBE_DISPI_INDEX_BPP: + case VBE_DISPI_INDEX_BANK: + case VBE_DISPI_INDEX_VIRT_WIDTH: + case VBE_DISPI_INDEX_VIRT_HEIGHT: + case VBE_DISPI_INDEX_X_OFFSET: + case VBE_DISPI_INDEX_Y_OFFSET: + s->vbe_regs[s->vbe_index] = val; + break; + } + } +} + +static uint32_t vbe_read(void *opaque, uint32_t offset, int size_log2) +{ + VGAState *s = opaque; + uint32_t val; + + if (offset == 0) { + val = s->vbe_index; + } else { + if (s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_GETCAPS) { + switch(s->vbe_index) { + case VBE_DISPI_INDEX_XRES: + val = s->fb_dev->width; + break; + case VBE_DISPI_INDEX_YRES: + val = s->fb_dev->height; + break; + case VBE_DISPI_INDEX_BPP: + val = 32; + break; + default: + goto read_reg; + } + } else { + read_reg: + if (s->vbe_index < VBE_DISPI_INDEX_NB) + val = s->vbe_regs[s->vbe_index]; + else + val = 0; + } +#ifdef DEBUG_VBE + printf("VBE read: index=0x%04x val=0x%04x\n", s->vbe_index, val); +#endif + } + return val; +} + + +static void simplefb_bar_set(void *opaque, int bar_num, + uint32_t addr, BOOL enabled) +{ + VGAState *s = opaque; + if (bar_num == 0) + phys_mem_set_addr(s->mem_range, addr, enabled); + else + phys_mem_set_addr(s->rom_range, addr, enabled); +} + +VGAState *pci_vga_init(PCIBus *bus, FBDevice *fb_dev, + int width, int height, + const uint8_t *vga_rom_buf, int vga_rom_size) +{ + VGAState *s; + PCIDevice *d; + uint32_t bar_size; + PhysMemoryMap *mem_map, *port_map; + + d = pci_register_device(bus, "VGA", -1, 0x1234, 0x1111, 0x00, 0x0300); + + mem_map = pci_device_get_mem_map(d); + port_map = pci_device_get_port_map(d); + + s = mallocz(sizeof(*s)); + s->fb_dev = fb_dev; + + fb_dev->width = width; + fb_dev->height = height; + fb_dev->stride = width * 4; + + fb_dev->fb_size = (height * fb_dev->stride + FB_ALLOC_ALIGN - 1) & ~(FB_ALLOC_ALIGN - 1); + s->fb_page_count = fb_dev->fb_size >> DEVRAM_PAGE_SIZE_LOG2; + + s->mem_range = + cpu_register_ram(mem_map, 0, fb_dev->fb_size, + DEVRAM_FLAG_DIRTY_BITS | DEVRAM_FLAG_DISABLED); + + fb_dev->fb_data = s->mem_range->phys_mem; + + s->pci_dev = d; + bar_size = 1; + while (bar_size < fb_dev->fb_size) + bar_size <<= 1; + pci_register_bar(d, 0, bar_size, PCI_ADDRESS_SPACE_MEM, s, + simplefb_bar_set); + + if (vga_rom_size > 0) { + int rom_size; + /* align to page size */ + rom_size = (vga_rom_size + DEVRAM_PAGE_SIZE - 1) & ~(DEVRAM_PAGE_SIZE - 1); + s->rom_range = cpu_register_ram(mem_map, 0, rom_size, + DEVRAM_FLAG_ROM | DEVRAM_FLAG_DISABLED); + memcpy(s->rom_range->phys_mem, vga_rom_buf, vga_rom_size); + + bar_size = 1; + while (bar_size < rom_size) + bar_size <<= 1; + pci_register_bar(d, PCI_ROM_SLOT, bar_size, PCI_ADDRESS_SPACE_MEM, s, + simplefb_bar_set); + } + + /* VGA memory (for simple text mode no need for callbacks) */ + s->mem_range2 = cpu_register_ram(mem_map, 0xa0000, 0x20000, 0); + s->vga_ram = s->mem_range2->phys_mem; + + /* standard VGA ports */ + + cpu_register_device(port_map, 0x3c0, 16, s, vga_read_0x3c0, vga_write_0x3c0, + DEVIO_SIZE8); + cpu_register_device(port_map, 0x3b4, 2, s, vga_read_0x3b4, vga_write_0x3b4, + DEVIO_SIZE8); + cpu_register_device(port_map, 0x3d4, 2, s, vga_read_0x3d4, vga_write_0x3d4, + DEVIO_SIZE8); + cpu_register_device(port_map, 0x3ba, 1, s, vga_read_0x3ba, vga_write_0x3ba, + DEVIO_SIZE8); + cpu_register_device(port_map, 0x3da, 1, s, vga_read_0x3da, vga_write_0x3da, + DEVIO_SIZE8); + + /* VBE extension */ + cpu_register_device(port_map, 0x1ce, 2, s, vbe_read, vbe_write, + DEVIO_SIZE16); + + s->vbe_regs[VBE_DISPI_INDEX_ID] = VBE_DISPI_ID5; + s->vbe_regs[VBE_DISPI_INDEX_VIDEO_MEMORY_64K] = fb_dev->fb_size >> 16; + + fb_dev->device_opaque = s; + fb_dev->refresh = vga_refresh; + return s; +} diff --git a/virtio.c b/virtio.c new file mode 100644 index 0000000..8d07f44 --- /dev/null +++ b/virtio.c @@ -0,0 +1,2650 @@ +/* + * VIRTIO driver + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "list.h" +#include "virtio.h" + +//#define DEBUG_VIRTIO + +/* MMIO addresses - from the Linux kernel */ +#define VIRTIO_MMIO_MAGIC_VALUE 0x000 +#define VIRTIO_MMIO_VERSION 0x004 +#define VIRTIO_MMIO_DEVICE_ID 0x008 +#define VIRTIO_MMIO_VENDOR_ID 0x00c +#define VIRTIO_MMIO_DEVICE_FEATURES 0x010 +#define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014 +#define VIRTIO_MMIO_DRIVER_FEATURES 0x020 +#define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024 +#define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028 /* version 1 only */ +#define VIRTIO_MMIO_QUEUE_SEL 0x030 +#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 +#define VIRTIO_MMIO_QUEUE_NUM 0x038 +#define VIRTIO_MMIO_QUEUE_ALIGN 0x03c /* version 1 only */ +#define VIRTIO_MMIO_QUEUE_PFN 0x040 /* version 1 only */ +#define VIRTIO_MMIO_QUEUE_READY 0x044 +#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 +#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 +#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 +#define VIRTIO_MMIO_STATUS 0x070 +#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 +#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 +#define VIRTIO_MMIO_QUEUE_AVAIL_LOW 0x090 +#define VIRTIO_MMIO_QUEUE_AVAIL_HIGH 0x094 +#define VIRTIO_MMIO_QUEUE_USED_LOW 0x0a0 +#define VIRTIO_MMIO_QUEUE_USED_HIGH 0x0a4 +#define VIRTIO_MMIO_CONFIG_GENERATION 0x0fc +#define VIRTIO_MMIO_CONFIG 0x100 + +/* PCI registers */ +#define VIRTIO_PCI_DEVICE_FEATURE_SEL 0x000 +#define VIRTIO_PCI_DEVICE_FEATURE 0x004 +#define VIRTIO_PCI_GUEST_FEATURE_SEL 0x008 +#define VIRTIO_PCI_GUEST_FEATURE 0x00c +#define VIRTIO_PCI_MSIX_CONFIG 0x010 +#define VIRTIO_PCI_NUM_QUEUES 0x012 +#define VIRTIO_PCI_DEVICE_STATUS 0x014 +#define VIRTIO_PCI_CONFIG_GENERATION 0x015 +#define VIRTIO_PCI_QUEUE_SEL 0x016 +#define VIRTIO_PCI_QUEUE_SIZE 0x018 +#define VIRTIO_PCI_QUEUE_MSIX_VECTOR 0x01a +#define VIRTIO_PCI_QUEUE_ENABLE 0x01c +#define VIRTIO_PCI_QUEUE_NOTIFY_OFF 0x01e +#define VIRTIO_PCI_QUEUE_DESC_LOW 0x020 +#define VIRTIO_PCI_QUEUE_DESC_HIGH 0x024 +#define VIRTIO_PCI_QUEUE_AVAIL_LOW 0x028 +#define VIRTIO_PCI_QUEUE_AVAIL_HIGH 0x02c +#define VIRTIO_PCI_QUEUE_USED_LOW 0x030 +#define VIRTIO_PCI_QUEUE_USED_HIGH 0x034 + +#define VIRTIO_PCI_CFG_OFFSET 0x0000 +#define VIRTIO_PCI_ISR_OFFSET 0x1000 +#define VIRTIO_PCI_CONFIG_OFFSET 0x2000 +#define VIRTIO_PCI_NOTIFY_OFFSET 0x3000 + +#define VIRTIO_PCI_CAP_LEN 16 + +#define MAX_QUEUE 8 +#define MAX_CONFIG_SPACE_SIZE 256 +#define MAX_QUEUE_NUM 16 + +typedef struct { + uint32_t ready; /* 0 or 1 */ + uint32_t num; + uint16_t last_avail_idx; + virtio_phys_addr_t desc_addr; + virtio_phys_addr_t avail_addr; + virtio_phys_addr_t used_addr; + BOOL manual_recv; /* if TRUE, the device_recv() callback is not called */ +} QueueState; + +#define VRING_DESC_F_NEXT 1 +#define VRING_DESC_F_WRITE 2 +#define VRING_DESC_F_INDIRECT 4 + +typedef struct { + uint64_t addr; + uint32_t len; + uint16_t flags; /* VRING_DESC_F_x */ + uint16_t next; +} VIRTIODesc; + +/* return < 0 to stop the notification (it must be manually restarted + later), 0 if OK */ +typedef int VIRTIODeviceRecvFunc(VIRTIODevice *s1, int queue_idx, + int desc_idx, int read_size, + int write_size); + +/* return NULL if no RAM at this address. The mapping is valid for one page */ +typedef uint8_t *VIRTIOGetRAMPtrFunc(VIRTIODevice *s, virtio_phys_addr_t paddr, BOOL is_rw); + +struct VIRTIODevice { + PhysMemoryMap *mem_map; + PhysMemoryRange *mem_range; + /* PCI only */ + PCIDevice *pci_dev; + /* MMIO only */ + IRQSignal *irq; + VIRTIOGetRAMPtrFunc *get_ram_ptr; + int debug; + + uint32_t int_status; + uint32_t status; + uint32_t device_features_sel; + uint32_t queue_sel; /* currently selected queue */ + QueueState queue[MAX_QUEUE]; + + /* device specific */ + uint32_t device_id; + uint32_t vendor_id; + uint32_t device_features; + VIRTIODeviceRecvFunc *device_recv; + void (*config_write)(VIRTIODevice *s); /* called after the config + is written */ + uint32_t config_space_size; /* in bytes, must be multiple of 4 */ + uint8_t config_space[MAX_CONFIG_SPACE_SIZE]; +}; + +static uint32_t virtio_mmio_read(void *opaque, uint32_t offset1, int size_log2); +static void virtio_mmio_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2); +static uint32_t virtio_pci_read(void *opaque, uint32_t offset, int size_log2); +static void virtio_pci_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2); + +static void virtio_reset(VIRTIODevice *s) +{ + int i; + + s->status = 0; + s->queue_sel = 0; + s->device_features_sel = 0; + s->int_status = 0; + for(i = 0; i < MAX_QUEUE; i++) { + QueueState *qs = &s->queue[i]; + qs->ready = 0; + qs->num = MAX_QUEUE_NUM; + qs->desc_addr = 0; + qs->avail_addr = 0; + qs->used_addr = 0; + qs->last_avail_idx = 0; + } +} + +static uint8_t *virtio_pci_get_ram_ptr(VIRTIODevice *s, virtio_phys_addr_t paddr, BOOL is_rw) +{ + return pci_device_get_dma_ptr(s->pci_dev, paddr, is_rw); +} + +static uint8_t *virtio_mmio_get_ram_ptr(VIRTIODevice *s, virtio_phys_addr_t paddr, BOOL is_rw) +{ + return phys_mem_get_ram_ptr(s->mem_map, paddr, is_rw); +} + +static void virtio_add_pci_capability(VIRTIODevice *s, int cfg_type, + int bar, uint32_t offset, uint32_t len, + uint32_t mult) +{ + uint8_t cap[20]; + int cap_len; + if (cfg_type == 2) + cap_len = 20; + else + cap_len = 16; + memset(cap, 0, cap_len); + cap[0] = 0x09; /* vendor specific */ + cap[2] = cap_len; /* set by pci_add_capability() */ + cap[3] = cfg_type; + cap[4] = bar; + put_le32(cap + 8, offset); + put_le32(cap + 12, len); + if (cfg_type == 2) + put_le32(cap + 16, mult); + pci_add_capability(s->pci_dev, cap, cap_len); +} + +static void virtio_pci_bar_set(void *opaque, int bar_num, + uint32_t addr, BOOL enabled) +{ + VIRTIODevice *s = opaque; + phys_mem_set_addr(s->mem_range, addr, enabled); +} + +static void virtio_init(VIRTIODevice *s, VIRTIOBusDef *bus, + uint32_t device_id, int config_space_size, + VIRTIODeviceRecvFunc *device_recv) +{ + memset(s, 0, sizeof(*s)); + + if (bus->pci_bus) { + uint16_t pci_device_id, class_id; + char name[32]; + int bar_num; + + switch(device_id) { + case 1: + pci_device_id = 0x1000; /* net */ + class_id = 0x0200; + break; + case 2: + pci_device_id = 0x1001; /* block */ + class_id = 0x0100; /* XXX: check it */ + break; + case 3: + pci_device_id = 0x1003; /* console */ + class_id = 0x0780; + break; + case 9: + pci_device_id = 0x1040 + device_id; /* use new device ID */ + class_id = 0x2; + break; + case 18: + pci_device_id = 0x1040 + device_id; /* use new device ID */ + class_id = 0x0980; + break; + default: + abort(); + } + snprintf(name, sizeof(name), "virtio_%04x", pci_device_id); + s->pci_dev = pci_register_device(bus->pci_bus, name, -1, + 0x1af4, pci_device_id, 0x00, + class_id); + pci_device_set_config16(s->pci_dev, 0x2c, 0x1af4); + pci_device_set_config16(s->pci_dev, 0x2e, device_id); + pci_device_set_config8(s->pci_dev, PCI_INTERRUPT_PIN, 1); + + bar_num = 4; + virtio_add_pci_capability(s, 1, bar_num, + VIRTIO_PCI_CFG_OFFSET, 0x1000, 0); /* common */ + virtio_add_pci_capability(s, 3, bar_num, + VIRTIO_PCI_ISR_OFFSET, 0x1000, 0); /* isr */ + virtio_add_pci_capability(s, 4, bar_num, + VIRTIO_PCI_CONFIG_OFFSET, 0x1000, 0); /* config */ + virtio_add_pci_capability(s, 2, bar_num, + VIRTIO_PCI_NOTIFY_OFFSET, 0x1000, 0); /* notify */ + + s->get_ram_ptr = virtio_pci_get_ram_ptr; + s->irq = pci_device_get_irq(s->pci_dev, 0); + s->mem_map = pci_device_get_mem_map(s->pci_dev); + s->mem_range = cpu_register_device(s->mem_map, 0, 0x4000, s, + virtio_pci_read, virtio_pci_write, + DEVIO_SIZE8 | DEVIO_SIZE16 | DEVIO_SIZE32 | DEVIO_DISABLED); + pci_register_bar(s->pci_dev, bar_num, 0x4000, PCI_ADDRESS_SPACE_MEM, + s, virtio_pci_bar_set); + } else { + /* MMIO case */ + s->mem_map = bus->mem_map; + s->irq = bus->irq; + s->mem_range = cpu_register_device(s->mem_map, bus->addr, VIRTIO_PAGE_SIZE, + s, virtio_mmio_read, virtio_mmio_write, + DEVIO_SIZE8 | DEVIO_SIZE16 | DEVIO_SIZE32); + s->get_ram_ptr = virtio_mmio_get_ram_ptr; + } + + s->device_id = device_id; + s->vendor_id = 0xffff; + s->config_space_size = config_space_size; + s->device_recv = device_recv; + virtio_reset(s); +} + +static uint16_t virtio_read16(VIRTIODevice *s, virtio_phys_addr_t addr) +{ + uint8_t *ptr; + if (addr & 1) + return 0; /* unaligned access are not supported */ + ptr = s->get_ram_ptr(s, addr, FALSE); + if (!ptr) + return 0; + return *(uint16_t *)ptr; +} + +static void virtio_write16(VIRTIODevice *s, virtio_phys_addr_t addr, + uint16_t val) +{ + uint8_t *ptr; + if (addr & 1) + return; /* unaligned access are not supported */ + ptr = s->get_ram_ptr(s, addr, TRUE); + if (!ptr) + return; + *(uint16_t *)ptr = val; +} + +static void virtio_write32(VIRTIODevice *s, virtio_phys_addr_t addr, + uint32_t val) +{ + uint8_t *ptr; + if (addr & 3) + return; /* unaligned access are not supported */ + ptr = s->get_ram_ptr(s, addr, TRUE); + if (!ptr) + return; + *(uint32_t *)ptr = val; +} + +static int virtio_memcpy_from_ram(VIRTIODevice *s, uint8_t *buf, + virtio_phys_addr_t addr, int count) +{ + uint8_t *ptr; + int l; + + while (count > 0) { + l = min_int(count, VIRTIO_PAGE_SIZE - (addr & (VIRTIO_PAGE_SIZE - 1))); + ptr = s->get_ram_ptr(s, addr, FALSE); + if (!ptr) + return -1; + memcpy(buf, ptr, l); + addr += l; + buf += l; + count -= l; + } + return 0; +} + +static int virtio_memcpy_to_ram(VIRTIODevice *s, virtio_phys_addr_t addr, + const uint8_t *buf, int count) +{ + uint8_t *ptr; + int l; + + while (count > 0) { + l = min_int(count, VIRTIO_PAGE_SIZE - (addr & (VIRTIO_PAGE_SIZE - 1))); + ptr = s->get_ram_ptr(s, addr, TRUE); + if (!ptr) + return -1; + memcpy(ptr, buf, l); + addr += l; + buf += l; + count -= l; + } + return 0; +} + +static int get_desc(VIRTIODevice *s, VIRTIODesc *desc, + int queue_idx, int desc_idx) +{ + QueueState *qs = &s->queue[queue_idx]; + return virtio_memcpy_from_ram(s, (void *)desc, qs->desc_addr + + desc_idx * sizeof(VIRTIODesc), + sizeof(VIRTIODesc)); +} + +static int memcpy_to_from_queue(VIRTIODevice *s, uint8_t *buf, + int queue_idx, int desc_idx, + int offset, int count, BOOL to_queue) +{ + VIRTIODesc desc; + int l, f_write_flag; + + if (count == 0) + return 0; + + get_desc(s, &desc, queue_idx, desc_idx); + + if (to_queue) { + f_write_flag = VRING_DESC_F_WRITE; + /* find the first write descriptor */ + for(;;) { + if ((desc.flags & VRING_DESC_F_WRITE) == f_write_flag) + break; + if (!(desc.flags & VRING_DESC_F_NEXT)) + return -1; + desc_idx = desc.next; + get_desc(s, &desc, queue_idx, desc_idx); + } + } else { + f_write_flag = 0; + } + + /* find the descriptor at offset */ + for(;;) { + if ((desc.flags & VRING_DESC_F_WRITE) != f_write_flag) + return -1; + if (offset < desc.len) + break; + if (!(desc.flags & VRING_DESC_F_NEXT)) + return -1; + desc_idx = desc.next; + offset -= desc.len; + get_desc(s, &desc, queue_idx, desc_idx); + } + + for(;;) { + l = min_int(count, desc.len - offset); + if (to_queue) + virtio_memcpy_to_ram(s, desc.addr + offset, buf, l); + else + virtio_memcpy_from_ram(s, buf, desc.addr + offset, l); + count -= l; + if (count == 0) + break; + offset += l; + buf += l; + if (offset == desc.len) { + if (!(desc.flags & VRING_DESC_F_NEXT)) + return -1; + desc_idx = desc.next; + get_desc(s, &desc, queue_idx, desc_idx); + if ((desc.flags & VRING_DESC_F_WRITE) != f_write_flag) + return -1; + offset = 0; + } + } + return 0; +} + +static int memcpy_from_queue(VIRTIODevice *s, void *buf, + int queue_idx, int desc_idx, + int offset, int count) +{ + return memcpy_to_from_queue(s, buf, queue_idx, desc_idx, offset, count, + FALSE); +} + +static int memcpy_to_queue(VIRTIODevice *s, + int queue_idx, int desc_idx, + int offset, const void *buf, int count) +{ + return memcpy_to_from_queue(s, (void *)buf, queue_idx, desc_idx, offset, + count, TRUE); +} + +/* signal that the descriptor has been consumed */ +static void virtio_consume_desc(VIRTIODevice *s, + int queue_idx, int desc_idx, int desc_len) +{ + QueueState *qs = &s->queue[queue_idx]; + virtio_phys_addr_t addr; + uint32_t index; + + addr = qs->used_addr + 2; + index = virtio_read16(s, addr); + virtio_write16(s, addr, index + 1); + + addr = qs->used_addr + 4 + (index & (qs->num - 1)) * 8; + virtio_write32(s, addr, desc_idx); + virtio_write32(s, addr + 4, desc_len); + + s->int_status |= 1; + set_irq(s->irq, 1); +} + +static int get_desc_rw_size(VIRTIODevice *s, + int *pread_size, int *pwrite_size, + int queue_idx, int desc_idx) +{ + VIRTIODesc desc; + int read_size, write_size; + + read_size = 0; + write_size = 0; + get_desc(s, &desc, queue_idx, desc_idx); + + for(;;) { + if (desc.flags & VRING_DESC_F_WRITE) + break; + read_size += desc.len; + if (!(desc.flags & VRING_DESC_F_NEXT)) + goto done; + desc_idx = desc.next; + get_desc(s, &desc, queue_idx, desc_idx); + } + + for(;;) { + if (!(desc.flags & VRING_DESC_F_WRITE)) + return -1; + write_size += desc.len; + if (!(desc.flags & VRING_DESC_F_NEXT)) + break; + desc_idx = desc.next; + get_desc(s, &desc, queue_idx, desc_idx); + } + + done: + *pread_size = read_size; + *pwrite_size = write_size; + return 0; +} + +/* XXX: test if the queue is ready ? */ +static void queue_notify(VIRTIODevice *s, int queue_idx) +{ + QueueState *qs = &s->queue[queue_idx]; + uint16_t avail_idx; + int desc_idx, read_size, write_size; + + if (qs->manual_recv) + return; + + avail_idx = virtio_read16(s, qs->avail_addr + 2); + while (qs->last_avail_idx != avail_idx) { + desc_idx = virtio_read16(s, qs->avail_addr + 4 + + (qs->last_avail_idx & (qs->num - 1)) * 2); + if (!get_desc_rw_size(s, &read_size, &write_size, queue_idx, desc_idx)) { +#ifdef DEBUG_VIRTIO + if (s->debug & VIRTIO_DEBUG_IO) { + printf("queue_notify: idx=%d read_size=%d write_size=%d\n", + queue_idx, read_size, write_size); + } +#endif + if (s->device_recv(s, queue_idx, desc_idx, + read_size, write_size) < 0) + break; + } + qs->last_avail_idx++; + } +} + +static uint32_t virtio_config_read(VIRTIODevice *s, uint32_t offset, + int size_log2) +{ + uint32_t val; + switch(size_log2) { + case 0: + if (offset < s->config_space_size) { + val = s->config_space[offset]; + } else { + val = 0; + } + break; + case 1: + if (offset < (s->config_space_size - 1)) { + val = get_le16(&s->config_space[offset]); + } else { + val = 0; + } + break; + case 2: + if (offset < (s->config_space_size - 3)) { + val = get_le32(s->config_space + offset); + } else { + val = 0; + } + break; + default: + abort(); + } + return val; +} + +static void virtio_config_write(VIRTIODevice *s, uint32_t offset, + uint32_t val, int size_log2) +{ + switch(size_log2) { + case 0: + if (offset < s->config_space_size) { + s->config_space[offset] = val; + if (s->config_write) + s->config_write(s); + } + break; + case 1: + if (offset < s->config_space_size - 1) { + put_le16(s->config_space + offset, val); + if (s->config_write) + s->config_write(s); + } + break; + case 2: + if (offset < s->config_space_size - 3) { + put_le32(s->config_space + offset, val); + if (s->config_write) + s->config_write(s); + } + break; + } +} + +static uint32_t virtio_mmio_read(void *opaque, uint32_t offset, int size_log2) +{ + VIRTIODevice *s = opaque; + uint32_t val; + + if (offset >= VIRTIO_MMIO_CONFIG) { + return virtio_config_read(s, offset - VIRTIO_MMIO_CONFIG, size_log2); + } + + if (size_log2 == 2) { + switch(offset) { + case VIRTIO_MMIO_MAGIC_VALUE: + val = 0x74726976; + break; + case VIRTIO_MMIO_VERSION: + val = 2; + break; + case VIRTIO_MMIO_DEVICE_ID: + val = s->device_id; + break; + case VIRTIO_MMIO_VENDOR_ID: + val = s->vendor_id; + break; + case VIRTIO_MMIO_DEVICE_FEATURES: + switch(s->device_features_sel) { + case 0: + val = s->device_features; + break; + case 1: + val = 1; /* version 1 */ + break; + default: + val = 0; + break; + } + break; + case VIRTIO_MMIO_DEVICE_FEATURES_SEL: + val = s->device_features_sel; + break; + case VIRTIO_MMIO_QUEUE_SEL: + val = s->queue_sel; + break; + case VIRTIO_MMIO_QUEUE_NUM_MAX: + val = MAX_QUEUE_NUM; + break; + case VIRTIO_MMIO_QUEUE_NUM: + val = s->queue[s->queue_sel].num; + break; + case VIRTIO_MMIO_QUEUE_DESC_LOW: + val = s->queue[s->queue_sel].desc_addr; + break; + case VIRTIO_MMIO_QUEUE_AVAIL_LOW: + val = s->queue[s->queue_sel].avail_addr; + break; + case VIRTIO_MMIO_QUEUE_USED_LOW: + val = s->queue[s->queue_sel].used_addr; + break; +#if VIRTIO_ADDR_BITS == 64 + case VIRTIO_MMIO_QUEUE_DESC_HIGH: + val = s->queue[s->queue_sel].desc_addr >> 32; + break; + case VIRTIO_MMIO_QUEUE_AVAIL_HIGH: + val = s->queue[s->queue_sel].avail_addr >> 32; + break; + case VIRTIO_MMIO_QUEUE_USED_HIGH: + val = s->queue[s->queue_sel].used_addr >> 32; + break; +#endif + case VIRTIO_MMIO_QUEUE_READY: + val = s->queue[s->queue_sel].ready; + break; + case VIRTIO_MMIO_INTERRUPT_STATUS: + val = s->int_status; + break; + case VIRTIO_MMIO_STATUS: + val = s->status; + break; + case VIRTIO_MMIO_CONFIG_GENERATION: + val = 0; + break; + default: + val = 0; + break; + } + } else { + val = 0; + } +#ifdef DEBUG_VIRTIO + if (s->debug & VIRTIO_DEBUG_IO) { + printf("virto_mmio_read: offset=0x%x val=0x%x size=%d\n", + offset, val, 1 << size_log2); + } +#endif + return val; +} + +#if VIRTIO_ADDR_BITS == 64 +static void set_low32(virtio_phys_addr_t *paddr, uint32_t val) +{ + *paddr = (*paddr & ~(virtio_phys_addr_t)0xffffffff) | val; +} + +static void set_high32(virtio_phys_addr_t *paddr, uint32_t val) +{ + *paddr = (*paddr & 0xffffffff) | ((virtio_phys_addr_t)val << 32); +} +#else +static void set_low32(virtio_phys_addr_t *paddr, uint32_t val) +{ + *paddr = val; +} +#endif + +static void virtio_mmio_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ + VIRTIODevice *s = opaque; + +#ifdef DEBUG_VIRTIO + if (s->debug & VIRTIO_DEBUG_IO) { + printf("virto_mmio_write: offset=0x%x val=0x%x size=%d\n", + offset, val, 1 << size_log2); + } +#endif + + if (offset >= VIRTIO_MMIO_CONFIG) { + virtio_config_write(s, offset - VIRTIO_MMIO_CONFIG, val, size_log2); + return; + } + + if (size_log2 == 2) { + switch(offset) { + case VIRTIO_MMIO_DEVICE_FEATURES_SEL: + s->device_features_sel = val; + break; + case VIRTIO_MMIO_QUEUE_SEL: + if (val < MAX_QUEUE) + s->queue_sel = val; + break; + case VIRTIO_MMIO_QUEUE_NUM: + if ((val & (val - 1)) == 0 && val > 0) { + s->queue[s->queue_sel].num = val; + } + break; + case VIRTIO_MMIO_QUEUE_DESC_LOW: + set_low32(&s->queue[s->queue_sel].desc_addr, val); + break; + case VIRTIO_MMIO_QUEUE_AVAIL_LOW: + set_low32(&s->queue[s->queue_sel].avail_addr, val); + break; + case VIRTIO_MMIO_QUEUE_USED_LOW: + set_low32(&s->queue[s->queue_sel].used_addr, val); + break; +#if VIRTIO_ADDR_BITS == 64 + case VIRTIO_MMIO_QUEUE_DESC_HIGH: + set_high32(&s->queue[s->queue_sel].desc_addr, val); + break; + case VIRTIO_MMIO_QUEUE_AVAIL_HIGH: + set_high32(&s->queue[s->queue_sel].avail_addr, val); + break; + case VIRTIO_MMIO_QUEUE_USED_HIGH: + set_high32(&s->queue[s->queue_sel].used_addr, val); + break; +#endif + case VIRTIO_MMIO_STATUS: + s->status = val; + if (val == 0) { + /* reset */ + set_irq(s->irq, 0); + virtio_reset(s); + } + break; + case VIRTIO_MMIO_QUEUE_READY: + s->queue[s->queue_sel].ready = val & 1; + break; + case VIRTIO_MMIO_QUEUE_NOTIFY: + if (val < MAX_QUEUE) + queue_notify(s, val); + break; + case VIRTIO_MMIO_INTERRUPT_ACK: + s->int_status &= ~val; + if (s->int_status == 0) { + set_irq(s->irq, 0); + } + break; + } + } +} + +static uint32_t virtio_pci_read(void *opaque, uint32_t offset1, int size_log2) +{ + VIRTIODevice *s = opaque; + uint32_t offset; + uint32_t val = 0; + + offset = offset1 & 0xfff; + switch(offset1 >> 12) { + case VIRTIO_PCI_CFG_OFFSET >> 12: + if (size_log2 == 2) { + switch(offset) { + case VIRTIO_PCI_DEVICE_FEATURE: + switch(s->device_features_sel) { + case 0: + val = s->device_features; + break; + case 1: + val = 1; /* version 1 */ + break; + default: + val = 0; + break; + } + break; + case VIRTIO_PCI_DEVICE_FEATURE_SEL: + val = s->device_features_sel; + break; + case VIRTIO_PCI_QUEUE_DESC_LOW: + val = s->queue[s->queue_sel].desc_addr; + break; + case VIRTIO_PCI_QUEUE_AVAIL_LOW: + val = s->queue[s->queue_sel].avail_addr; + break; + case VIRTIO_PCI_QUEUE_USED_LOW: + val = s->queue[s->queue_sel].used_addr; + break; +#if VIRTIO_ADDR_BITS == 64 + case VIRTIO_PCI_QUEUE_DESC_HIGH: + val = s->queue[s->queue_sel].desc_addr >> 32; + break; + case VIRTIO_PCI_QUEUE_AVAIL_HIGH: + val = s->queue[s->queue_sel].avail_addr >> 32; + break; + case VIRTIO_PCI_QUEUE_USED_HIGH: + val = s->queue[s->queue_sel].used_addr >> 32; + break; +#endif + } + } else if (size_log2 == 1) { + switch(offset) { + case VIRTIO_PCI_NUM_QUEUES: + val = MAX_QUEUE_NUM; + break; + case VIRTIO_PCI_QUEUE_SEL: + val = s->queue_sel; + break; + case VIRTIO_PCI_QUEUE_SIZE: + val = s->queue[s->queue_sel].num; + break; + case VIRTIO_PCI_QUEUE_ENABLE: + val = s->queue[s->queue_sel].ready; + break; + case VIRTIO_PCI_QUEUE_NOTIFY_OFF: + val = 0; + break; + } + } else if (size_log2 == 0) { + switch(offset) { + case VIRTIO_PCI_DEVICE_STATUS: + val = s->status; + break; + } + } + break; + case VIRTIO_PCI_ISR_OFFSET >> 12: + if (offset == 0 && size_log2 == 0) { + val = s->int_status; + s->int_status = 0; + set_irq(s->irq, 0); + } + break; + case VIRTIO_PCI_CONFIG_OFFSET >> 12: + val = virtio_config_read(s, offset, size_log2); + break; + } +#ifdef DEBUG_VIRTIO + if (s->debug & VIRTIO_DEBUG_IO) { + printf("virto_pci_read: offset=0x%x val=0x%x size=%d\n", + offset1, val, 1 << size_log2); + } +#endif + return val; +} + +static void virtio_pci_write(void *opaque, uint32_t offset1, + uint32_t val, int size_log2) +{ + VIRTIODevice *s = opaque; + uint32_t offset; + +#ifdef DEBUG_VIRTIO + if (s->debug & VIRTIO_DEBUG_IO) { + printf("virto_pci_write: offset=0x%x val=0x%x size=%d\n", + offset1, val, 1 << size_log2); + } +#endif + offset = offset1 & 0xfff; + switch(offset1 >> 12) { + case VIRTIO_PCI_CFG_OFFSET >> 12: + if (size_log2 == 2) { + switch(offset) { + case VIRTIO_PCI_DEVICE_FEATURE_SEL: + s->device_features_sel = val; + break; + case VIRTIO_PCI_QUEUE_DESC_LOW: + set_low32(&s->queue[s->queue_sel].desc_addr, val); + break; + case VIRTIO_PCI_QUEUE_AVAIL_LOW: + set_low32(&s->queue[s->queue_sel].avail_addr, val); + break; + case VIRTIO_PCI_QUEUE_USED_LOW: + set_low32(&s->queue[s->queue_sel].used_addr, val); + break; +#if VIRTIO_ADDR_BITS == 64 + case VIRTIO_PCI_QUEUE_DESC_HIGH: + set_high32(&s->queue[s->queue_sel].desc_addr, val); + break; + case VIRTIO_PCI_QUEUE_AVAIL_HIGH: + set_high32(&s->queue[s->queue_sel].avail_addr, val); + break; + case VIRTIO_PCI_QUEUE_USED_HIGH: + set_high32(&s->queue[s->queue_sel].used_addr, val); + break; +#endif + } + } else if (size_log2 == 1) { + switch(offset) { + case VIRTIO_PCI_QUEUE_SEL: + if (val < MAX_QUEUE) + s->queue_sel = val; + break; + case VIRTIO_PCI_QUEUE_SIZE: + if ((val & (val - 1)) == 0 && val > 0) { + s->queue[s->queue_sel].num = val; + } + break; + case VIRTIO_PCI_QUEUE_ENABLE: + s->queue[s->queue_sel].ready = val & 1; + break; + } + } else if (size_log2 == 0) { + switch(offset) { + case VIRTIO_PCI_DEVICE_STATUS: + s->status = val; + if (val == 0) { + /* reset */ + set_irq(s->irq, 0); + virtio_reset(s); + } + break; + } + } + break; + case VIRTIO_PCI_CONFIG_OFFSET >> 12: + virtio_config_write(s, offset, val, size_log2); + break; + case VIRTIO_PCI_NOTIFY_OFFSET >> 12: + if (val < MAX_QUEUE) + queue_notify(s, val); + break; + } +} + +void virtio_set_debug(VIRTIODevice *s, int debug) +{ + s->debug = debug; +} + +static void virtio_config_change_notify(VIRTIODevice *s) +{ + /* INT_CONFIG interrupt */ + s->int_status |= 2; + set_irq(s->irq, 1); +} + +/*********************************************************************/ +/* block device */ + +typedef struct { + uint32_t type; + uint8_t *buf; + int write_size; + int queue_idx; + int desc_idx; +} BlockRequest; + +typedef struct VIRTIOBlockDevice { + VIRTIODevice common; + BlockDevice *bs; + + BOOL req_in_progress; + BlockRequest req; /* request in progress */ +} VIRTIOBlockDevice; + +typedef struct { + uint32_t type; + uint32_t ioprio; + uint64_t sector_num; +} BlockRequestHeader; + +#define VIRTIO_BLK_T_IN 0 +#define VIRTIO_BLK_T_OUT 1 +#define VIRTIO_BLK_T_FLUSH 4 +#define VIRTIO_BLK_T_FLUSH_OUT 5 + +#define VIRTIO_BLK_S_OK 0 +#define VIRTIO_BLK_S_IOERR 1 +#define VIRTIO_BLK_S_UNSUPP 2 + +#define SECTOR_SIZE 512 + +static void virtio_block_req_end(VIRTIODevice *s, int ret) +{ + VIRTIOBlockDevice *s1 = (VIRTIOBlockDevice *)s; + int write_size; + int queue_idx = s1->req.queue_idx; + int desc_idx = s1->req.desc_idx; + uint8_t *buf, buf1[1]; + + switch(s1->req.type) { + case VIRTIO_BLK_T_IN: + write_size = s1->req.write_size; + buf = s1->req.buf; + if (ret < 0) { + buf[write_size - 1] = VIRTIO_BLK_S_IOERR; + } else { + buf[write_size - 1] = VIRTIO_BLK_S_OK; + } + memcpy_to_queue(s, queue_idx, desc_idx, 0, buf, write_size); + free(buf); + virtio_consume_desc(s, queue_idx, desc_idx, write_size); + break; + case VIRTIO_BLK_T_OUT: + if (ret < 0) + buf1[0] = VIRTIO_BLK_S_IOERR; + else + buf1[0] = VIRTIO_BLK_S_OK; + memcpy_to_queue(s, queue_idx, desc_idx, 0, buf1, sizeof(buf1)); + virtio_consume_desc(s, queue_idx, desc_idx, 1); + break; + default: + abort(); + } +} + +static void virtio_block_req_cb(void *opaque, int ret) +{ + VIRTIODevice *s = opaque; + VIRTIOBlockDevice *s1 = (VIRTIOBlockDevice *)s; + + virtio_block_req_end(s, ret); + + s1->req_in_progress = FALSE; + + /* handle next requests */ + queue_notify((VIRTIODevice *)s, s1->req.queue_idx); +} + +/* XXX: handle async I/O */ +static int virtio_block_recv_request(VIRTIODevice *s, int queue_idx, + int desc_idx, int read_size, + int write_size) +{ + VIRTIOBlockDevice *s1 = (VIRTIOBlockDevice *)s; + BlockDevice *bs = s1->bs; + BlockRequestHeader h; + uint8_t *buf; + int len, ret; + + if (s1->req_in_progress) + return -1; + + if (memcpy_from_queue(s, &h, queue_idx, desc_idx, 0, sizeof(h)) < 0) + return 0; + s1->req.type = h.type; + s1->req.queue_idx = queue_idx; + s1->req.desc_idx = desc_idx; + switch(h.type) { + case VIRTIO_BLK_T_IN: + s1->req.buf = malloc(write_size); + s1->req.write_size = write_size; + ret = bs->read_async(bs, h.sector_num, s1->req.buf, + (write_size - 1) / SECTOR_SIZE, + virtio_block_req_cb, s); + if (ret > 0) { + /* asyncronous read */ + s1->req_in_progress = TRUE; + } else { + virtio_block_req_end(s, ret); + } + break; + case VIRTIO_BLK_T_OUT: + assert(write_size >= 1); + len = read_size - sizeof(h); + buf = malloc(len); + memcpy_from_queue(s, buf, queue_idx, desc_idx, sizeof(h), len); + ret = bs->write_async(bs, h.sector_num, buf, len / SECTOR_SIZE, + virtio_block_req_cb, s); + free(buf); + if (ret > 0) { + /* asyncronous write */ + s1->req_in_progress = TRUE; + } else { + virtio_block_req_end(s, ret); + } + break; + default: + break; + } + return 0; +} + +VIRTIODevice *virtio_block_init(VIRTIOBusDef *bus, BlockDevice *bs) +{ + VIRTIOBlockDevice *s; + uint64_t nb_sectors; + + s = mallocz(sizeof(*s)); + virtio_init(&s->common, bus, + 2, 8, virtio_block_recv_request); + s->bs = bs; + + nb_sectors = bs->get_sector_count(bs); + put_le32(s->common.config_space, nb_sectors); + put_le32(s->common.config_space + 4, nb_sectors >> 32); + + return (VIRTIODevice *)s; +} + +/*********************************************************************/ +/* network device */ + +typedef struct VIRTIONetDevice { + VIRTIODevice common; + EthernetDevice *es; + int header_size; +} VIRTIONetDevice; + +typedef struct { + uint8_t flags; + uint8_t gso_type; + uint16_t hdr_len; + uint16_t gso_size; + uint16_t csum_start; + uint16_t csum_offset; + uint16_t num_buffers; +} VIRTIONetHeader; + +static int virtio_net_recv_request(VIRTIODevice *s, int queue_idx, + int desc_idx, int read_size, + int write_size) +{ + VIRTIONetDevice *s1 = (VIRTIONetDevice *)s; + EthernetDevice *es = s1->es; + VIRTIONetHeader h; + uint8_t *buf; + int len; + + if (queue_idx == 1) { + /* send to network */ + if (memcpy_from_queue(s, &h, queue_idx, desc_idx, 0, s1->header_size) < 0) + return 0; + len = read_size - s1->header_size; + buf = malloc(len); + memcpy_from_queue(s, buf, queue_idx, desc_idx, s1->header_size, len); + es->write_packet(es, buf, len); + free(buf); + virtio_consume_desc(s, queue_idx, desc_idx, 0); + } + return 0; +} + +static BOOL virtio_net_can_write_packet(EthernetDevice *es) +{ + VIRTIODevice *s = es->device_opaque; + QueueState *qs = &s->queue[0]; + uint16_t avail_idx; + + if (!qs->ready) + return FALSE; + avail_idx = virtio_read16(s, qs->avail_addr + 2); + return qs->last_avail_idx != avail_idx; +} + +static void virtio_net_write_packet(EthernetDevice *es, const uint8_t *buf, int buf_len) +{ + VIRTIODevice *s = es->device_opaque; + VIRTIONetDevice *s1 = (VIRTIONetDevice *)s; + int queue_idx = 0; + QueueState *qs = &s->queue[queue_idx]; + int desc_idx; + VIRTIONetHeader h; + int len, read_size, write_size; + uint16_t avail_idx; + + if (!qs->ready) + return; + avail_idx = virtio_read16(s, qs->avail_addr + 2); + if (qs->last_avail_idx == avail_idx) + return; + desc_idx = virtio_read16(s, qs->avail_addr + 4 + + (qs->last_avail_idx & (qs->num - 1)) * 2); + if (get_desc_rw_size(s, &read_size, &write_size, queue_idx, desc_idx)) + return; + len = s1->header_size + buf_len; + if (len > write_size) + return; + memset(&h, 0, s1->header_size); + memcpy_to_queue(s, queue_idx, desc_idx, 0, &h, s1->header_size); + memcpy_to_queue(s, queue_idx, desc_idx, s1->header_size, buf, buf_len); + virtio_consume_desc(s, queue_idx, desc_idx, len); + qs->last_avail_idx++; +} + +static void virtio_net_set_carrier(EthernetDevice *es, BOOL carrier_state) +{ +#if 0 + VIRTIODevice *s1 = es->device_opaque; + VIRTIONetDevice *s = (VIRTIONetDevice *)s1; + int cur_carrier_state; + + // printf("virtio_net_set_carrier: %d\n", carrier_state); + cur_carrier_state = s->common.config_space[6] & 1; + if (cur_carrier_state != carrier_state) { + s->common.config_space[6] = (carrier_state << 0); + virtio_config_change_notify(s1); + } +#endif +} + +VIRTIODevice *virtio_net_init(VIRTIOBusDef *bus, EthernetDevice *es) +{ + VIRTIONetDevice *s; + + s = mallocz(sizeof(*s)); + virtio_init(&s->common, bus, + 1, 6 + 2, virtio_net_recv_request); + /* VIRTIO_NET_F_MAC, VIRTIO_NET_F_STATUS */ + s->common.device_features = (1 << 5) /* | (1 << 16) */; + s->common.queue[0].manual_recv = TRUE; + s->es = es; + memcpy(s->common.config_space, es->mac_addr, 6); + /* status */ + s->common.config_space[6] = 0; + s->common.config_space[7] = 0; + + s->header_size = sizeof(VIRTIONetHeader); + + es->device_opaque = s; + es->device_can_write_packet = virtio_net_can_write_packet; + es->device_write_packet = virtio_net_write_packet; + es->device_set_carrier = virtio_net_set_carrier; + return (VIRTIODevice *)s; +} + +/*********************************************************************/ +/* console device */ + +typedef struct VIRTIOConsoleDevice { + VIRTIODevice common; + CharacterDevice *cs; +} VIRTIOConsoleDevice; + +static int virtio_console_recv_request(VIRTIODevice *s, int queue_idx, + int desc_idx, int read_size, + int write_size) +{ + VIRTIOConsoleDevice *s1 = (VIRTIOConsoleDevice *)s; + CharacterDevice *cs = s1->cs; + uint8_t *buf; + + if (queue_idx == 1) { + /* send to console */ + buf = malloc(read_size); + memcpy_from_queue(s, buf, queue_idx, desc_idx, 0, read_size); + cs->write_data(cs->opaque, buf, read_size); + free(buf); + virtio_consume_desc(s, queue_idx, desc_idx, 0); + } + return 0; +} + +BOOL virtio_console_can_write_data(VIRTIODevice *s) +{ + QueueState *qs = &s->queue[0]; + uint16_t avail_idx; + + if (!qs->ready) + return FALSE; + avail_idx = virtio_read16(s, qs->avail_addr + 2); + return qs->last_avail_idx != avail_idx; +} + +int virtio_console_get_write_len(VIRTIODevice *s) +{ + int queue_idx = 0; + QueueState *qs = &s->queue[queue_idx]; + int desc_idx; + int read_size, write_size; + uint16_t avail_idx; + + if (!qs->ready) + return 0; + avail_idx = virtio_read16(s, qs->avail_addr + 2); + if (qs->last_avail_idx == avail_idx) + return 0; + desc_idx = virtio_read16(s, qs->avail_addr + 4 + + (qs->last_avail_idx & (qs->num - 1)) * 2); + if (get_desc_rw_size(s, &read_size, &write_size, queue_idx, desc_idx)) + return 0; + return write_size; +} + +int virtio_console_write_data(VIRTIODevice *s, const uint8_t *buf, int buf_len) +{ + int queue_idx = 0; + QueueState *qs = &s->queue[queue_idx]; + int desc_idx; + uint16_t avail_idx; + + if (!qs->ready) + return 0; + avail_idx = virtio_read16(s, qs->avail_addr + 2); + if (qs->last_avail_idx == avail_idx) + return 0; + desc_idx = virtio_read16(s, qs->avail_addr + 4 + + (qs->last_avail_idx & (qs->num - 1)) * 2); + memcpy_to_queue(s, queue_idx, desc_idx, 0, buf, buf_len); + virtio_consume_desc(s, queue_idx, desc_idx, buf_len); + qs->last_avail_idx++; + return buf_len; +} + +/* send a resize event */ +void virtio_console_resize_event(VIRTIODevice *s, int width, int height) +{ + /* indicate the console size */ + put_le16(s->config_space + 0, width); + put_le16(s->config_space + 2, height); + + virtio_config_change_notify(s); +} + +VIRTIODevice *virtio_console_init(VIRTIOBusDef *bus, CharacterDevice *cs) +{ + VIRTIOConsoleDevice *s; + + s = mallocz(sizeof(*s)); + virtio_init(&s->common, bus, + 3, 4, virtio_console_recv_request); + s->common.device_features = (1 << 0); /* VIRTIO_CONSOLE_F_SIZE */ + s->common.queue[0].manual_recv = TRUE; + + s->cs = cs; + return (VIRTIODevice *)s; +} + +/*********************************************************************/ +/* input device */ + +enum { + 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, +}; + +#define VIRTIO_INPUT_EV_SYN 0x00 +#define VIRTIO_INPUT_EV_KEY 0x01 +#define VIRTIO_INPUT_EV_REL 0x02 +#define VIRTIO_INPUT_EV_ABS 0x03 +#define VIRTIO_INPUT_EV_REP 0x14 + +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 +#define BTN_GEAR_DOWN 0x150 +#define BTN_GEAR_UP 0x151 + +#define REL_X 0x00 +#define REL_Y 0x01 +#define REL_Z 0x02 +#define REL_WHEEL 0x08 + +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_Z 0x02 + +typedef struct VIRTIOInputDevice { + VIRTIODevice common; + VirtioInputTypeEnum type; + uint32_t buttons_state; +} VIRTIOInputDevice; + +static const uint16_t buttons_list[] = { + BTN_LEFT, BTN_RIGHT, BTN_MIDDLE +}; + +static int virtio_input_recv_request(VIRTIODevice *s, int queue_idx, + int desc_idx, int read_size, + int write_size) +{ + if (queue_idx == 1) { + /* led & keyboard updates */ + // printf("%s: write_size=%d\n", __func__, write_size); + virtio_consume_desc(s, queue_idx, desc_idx, 0); + } + return 0; +} + +/* return < 0 if could not send key event */ +static int virtio_input_queue_event(VIRTIODevice *s, + uint16_t type, uint16_t code, + uint32_t value) +{ + int queue_idx = 0; + QueueState *qs = &s->queue[queue_idx]; + int desc_idx, buf_len; + uint16_t avail_idx; + uint8_t buf[8]; + + if (!qs->ready) + return -1; + + put_le16(buf, type); + put_le16(buf + 2, code); + put_le32(buf + 4, value); + buf_len = 8; + + avail_idx = virtio_read16(s, qs->avail_addr + 2); + if (qs->last_avail_idx == avail_idx) + return -1; + desc_idx = virtio_read16(s, qs->avail_addr + 4 + + (qs->last_avail_idx & (qs->num - 1)) * 2); + // printf("send: queue_idx=%d desc_idx=%d\n", queue_idx, desc_idx); + memcpy_to_queue(s, queue_idx, desc_idx, 0, buf, buf_len); + virtio_consume_desc(s, queue_idx, desc_idx, buf_len); + qs->last_avail_idx++; + return 0; +} + +int virtio_input_send_key_event(VIRTIODevice *s, BOOL is_down, + uint16_t key_code) +{ + VIRTIOInputDevice *s1 = (VIRTIOInputDevice *)s; + int ret; + + if (s1->type != VIRTIO_INPUT_TYPE_KEYBOARD) + return -1; + ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_KEY, key_code, is_down); + if (ret) + return ret; + return virtio_input_queue_event(s, VIRTIO_INPUT_EV_SYN, 0, 0); +} + +/* also used for the tablet */ +int virtio_input_send_mouse_event(VIRTIODevice *s, int dx, int dy, int dz, + unsigned int buttons) +{ + VIRTIOInputDevice *s1 = (VIRTIOInputDevice *)s; + int ret, i, b, last_b; + + if (s1->type != VIRTIO_INPUT_TYPE_MOUSE && + s1->type != VIRTIO_INPUT_TYPE_TABLET) + return -1; + if (s1->type == VIRTIO_INPUT_TYPE_MOUSE) { + ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_REL, REL_X, dx); + if (ret != 0) + return ret; + ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_REL, REL_Y, dy); + if (ret != 0) + return ret; + } else { + ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_ABS, ABS_X, dx); + if (ret != 0) + return ret; + ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_ABS, ABS_Y, dy); + if (ret != 0) + return ret; + } + if (dz != 0) { + ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_REL, REL_WHEEL, dz); + if (ret != 0) + return ret; + } + + if (buttons != s1->buttons_state) { + for(i = 0; i < countof(buttons_list); i++) { + b = (buttons >> i) & 1; + last_b = (s1->buttons_state >> i) & 1; + if (b != last_b) { + ret = virtio_input_queue_event(s, VIRTIO_INPUT_EV_KEY, + buttons_list[i], b); + if (ret != 0) + return ret; + } + } + s1->buttons_state = buttons; + } + + return virtio_input_queue_event(s, VIRTIO_INPUT_EV_SYN, 0, 0); +} + +static void set_bit(uint8_t *tab, int k) +{ + tab[k >> 3] |= 1 << (k & 7); +} + +static void virtio_input_config_write(VIRTIODevice *s) +{ + VIRTIOInputDevice *s1 = (VIRTIOInputDevice *)s; + uint8_t *config = s->config_space; + int i; + + // printf("config_write: %02x %02x\n", config[0], config[1]); + switch(config[0]) { + case VIRTIO_INPUT_CFG_UNSET: + break; + case VIRTIO_INPUT_CFG_ID_NAME: + { + const char *name; + int len; + switch(s1->type) { + case VIRTIO_INPUT_TYPE_KEYBOARD: + name = "virtio_keyboard"; + break; + case VIRTIO_INPUT_TYPE_MOUSE: + name = "virtio_mouse"; + break; + case VIRTIO_INPUT_TYPE_TABLET: + name = "virtio_tablet"; + break; + default: + abort(); + } + len = strlen(name); + config[2] = len; + memcpy(config + 8, name, len); + } + break; + default: + case VIRTIO_INPUT_CFG_ID_SERIAL: + case VIRTIO_INPUT_CFG_ID_DEVIDS: + case VIRTIO_INPUT_CFG_PROP_BITS: + config[2] = 0; /* size of reply */ + break; + case VIRTIO_INPUT_CFG_EV_BITS: + config[2] = 0; + switch(s1->type) { + case VIRTIO_INPUT_TYPE_KEYBOARD: + switch(config[1]) { + case VIRTIO_INPUT_EV_KEY: + config[2] = 128 / 8; + memset(config + 8, 0xff, 128 / 8); /* bitmap */ + break; + case VIRTIO_INPUT_EV_REP: /* allow key repetition */ + config[2] = 1; + break; + default: + break; + } + break; + case VIRTIO_INPUT_TYPE_MOUSE: + switch(config[1]) { + case VIRTIO_INPUT_EV_KEY: + config[2] = 512 / 8; + memset(config + 8, 0, 512 / 8); /* bitmap */ + for(i = 0; i < countof(buttons_list); i++) + set_bit(config + 8, buttons_list[i]); + break; + case VIRTIO_INPUT_EV_REL: + config[2] = 2; + config[8] = 0; + config[9] = 0; + set_bit(config + 8, REL_X); + set_bit(config + 8, REL_Y); + set_bit(config + 8, REL_WHEEL); + break; + default: + break; + } + break; + case VIRTIO_INPUT_TYPE_TABLET: + switch(config[1]) { + case VIRTIO_INPUT_EV_KEY: + config[2] = 512 / 8; + memset(config + 8, 0, 512 / 8); /* bitmap */ + for(i = 0; i < countof(buttons_list); i++) + set_bit(config + 8, buttons_list[i]); + break; + case VIRTIO_INPUT_EV_REL: + config[2] = 2; + config[8] = 0; + config[9] = 0; + set_bit(config + 8, REL_WHEEL); + break; + case VIRTIO_INPUT_EV_ABS: + config[2] = 1; + config[8] = 0; + set_bit(config + 8, ABS_X); + set_bit(config + 8, ABS_Y); + break; + default: + break; + } + break; + default: + abort(); + } + break; + case VIRTIO_INPUT_CFG_ABS_INFO: + if (s1->type == VIRTIO_INPUT_TYPE_TABLET && config[1] <= 1) { + /* for ABS_X and ABS_Y */ + config[2] = 5 * 4; + put_le32(config + 8, 0); /* min */ + put_le32(config + 12, VIRTIO_INPUT_ABS_SCALE - 1) ; /* max */ + put_le32(config + 16, 0); /* fuzz */ + put_le32(config + 20, 0); /* flat */ + put_le32(config + 24, 0); /* res */ + } + break; + } +} + +VIRTIODevice *virtio_input_init(VIRTIOBusDef *bus, VirtioInputTypeEnum type) +{ + VIRTIOInputDevice *s; + + s = mallocz(sizeof(*s)); + virtio_init(&s->common, bus, + 18, 256, virtio_input_recv_request); + s->common.queue[0].manual_recv = TRUE; + s->common.device_features = 0; + s->common.config_write = virtio_input_config_write; + s->type = type; + return (VIRTIODevice *)s; +} + +/*********************************************************************/ +/* 9p filesystem device */ + +typedef struct { + struct list_head link; + uint32_t fid; + FSFile *fd; +} FIDDesc; + +typedef struct VIRTIO9PDevice { + VIRTIODevice common; + FSDevice *fs; + int msize; /* maximum message size */ + struct list_head fid_list; /* list of FIDDesc */ + BOOL req_in_progress; +} VIRTIO9PDevice; + +static FIDDesc *fid_find1(VIRTIO9PDevice *s, uint32_t fid) +{ + struct list_head *el; + FIDDesc *f; + + list_for_each(el, &s->fid_list) { + f = list_entry(el, FIDDesc, link); + if (f->fid == fid) + return f; + } + return NULL; +} + +static FSFile *fid_find(VIRTIO9PDevice *s, uint32_t fid) +{ + FIDDesc *f; + + f = fid_find1(s, fid); + if (!f) + return NULL; + return f->fd; +} + +static void fid_delete(VIRTIO9PDevice *s, uint32_t fid) +{ + FIDDesc *f; + + f = fid_find1(s, fid); + if (f) { + s->fs->fs_delete(s->fs, f->fd); + list_del(&f->link); + free(f); + } +} + +static void fid_set(VIRTIO9PDevice *s, uint32_t fid, FSFile *fd) +{ + FIDDesc *f; + + f = fid_find1(s, fid); + if (f) { + s->fs->fs_delete(s->fs, f->fd); + f->fd = fd; + } else { + f = malloc(sizeof(*f)); + f->fid = fid; + f->fd = fd; + list_add(&f->link, &s->fid_list); + } +} + +#ifdef DEBUG_VIRTIO + +typedef struct { + uint8_t tag; + const char *name; +} Virtio9POPName; + +static const Virtio9POPName virtio_9p_op_names[] = { + { 8, "statfs" }, + { 12, "lopen" }, + { 14, "lcreate" }, + { 16, "symlink" }, + { 18, "mknod" }, + { 22, "readlink" }, + { 24, "getattr" }, + { 26, "setattr" }, + { 30, "xattrwalk" }, + { 40, "readdir" }, + { 50, "fsync" }, + { 52, "lock" }, + { 54, "getlock" }, + { 70, "link" }, + { 72, "mkdir" }, + { 74, "renameat" }, + { 76, "unlinkat" }, + { 100, "version" }, + { 104, "attach" }, + { 108, "flush" }, + { 110, "walk" }, + { 116, "read" }, + { 118, "write" }, + { 120, "clunk" }, + { 0, NULL }, +}; + +static const char *get_9p_op_name(int tag) +{ + const Virtio9POPName *p; + for(p = virtio_9p_op_names; p->name != NULL; p++) { + if (p->tag == tag) + return p->name; + } + return NULL; +} + +#endif /* DEBUG_VIRTIO */ + +static int marshall(VIRTIO9PDevice *s, + uint8_t *buf1, int max_len, const char *fmt, ...) +{ + va_list ap; + int c; + uint32_t val; + uint64_t val64; + uint8_t *buf, *buf_end; + +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" ->"); +#endif + va_start(ap, fmt); + buf = buf1; + buf_end = buf1 + max_len; + for(;;) { + c = *fmt++; + if (c == '\0') + break; + switch(c) { + case 'b': + assert(buf + 1 <= buf_end); + val = va_arg(ap, int); +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" b=%d", val); +#endif + buf[0] = val; + buf += 1; + break; + case 'h': + assert(buf + 2 <= buf_end); + val = va_arg(ap, int); +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" h=%d", val); +#endif + put_le16(buf, val); + buf += 2; + break; + case 'w': + assert(buf + 4 <= buf_end); + val = va_arg(ap, int); +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" w=%d", val); +#endif + put_le32(buf, val); + buf += 4; + break; + case 'd': + assert(buf + 8 <= buf_end); + val64 = va_arg(ap, uint64_t); +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" d=%" PRId64, val64); +#endif + put_le64(buf, val64); + buf += 8; + break; + case 's': + { + char *str; + int len; + str = va_arg(ap, char *); +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" s=\"%s\"", str); +#endif + len = strlen(str); + assert(len <= 65535); + assert(buf + 2 + len <= buf_end); + put_le16(buf, len); + buf += 2; + memcpy(buf, str, len); + buf += len; + } + break; + case 'Q': + { + FSQID *qid; + assert(buf + 13 <= buf_end); + qid = va_arg(ap, FSQID *); +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" Q=%d:%d:%" PRIu64, qid->type, qid->version, qid->path); +#endif + buf[0] = qid->type; + put_le32(buf + 1, qid->version); + put_le64(buf + 5, qid->path); + buf += 13; + } + break; + default: + abort(); + } + } + va_end(ap); + return buf - buf1; +} + +/* return < 0 if error */ +/* XXX: free allocated strings in case of error */ +static int unmarshall(VIRTIO9PDevice *s, int queue_idx, + int desc_idx, int *poffset, const char *fmt, ...) +{ + VIRTIODevice *s1 = (VIRTIODevice *)s; + va_list ap; + int offset, c; + uint8_t buf[16]; + + offset = *poffset; + va_start(ap, fmt); + for(;;) { + c = *fmt++; + if (c == '\0') + break; + switch(c) { + case 'b': + { + uint8_t *ptr; + if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, 1)) + return -1; + ptr = va_arg(ap, uint8_t *); + *ptr = buf[0]; + offset += 1; +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" b=%d", *ptr); +#endif + } + break; + case 'h': + { + uint16_t *ptr; + if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, 2)) + return -1; + ptr = va_arg(ap, uint16_t *); + *ptr = get_le16(buf); + offset += 2; +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" h=%d", *ptr); +#endif + } + break; + case 'w': + { + uint32_t *ptr; + if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, 4)) + return -1; + ptr = va_arg(ap, uint32_t *); + *ptr = get_le32(buf); + offset += 4; +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" w=%d", *ptr); +#endif + } + break; + case 'd': + { + uint64_t *ptr; + if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, 8)) + return -1; + ptr = va_arg(ap, uint64_t *); + *ptr = get_le64(buf); + offset += 8; +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" d=%" PRId64, *ptr); +#endif + } + break; + case 's': + { + char *str, **ptr; + int len; + + if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, 2)) + return -1; + len = get_le16(buf); + offset += 2; + str = malloc(len + 1); + if (memcpy_from_queue(s1, str, queue_idx, desc_idx, offset, len)) + return -1; + str[len] = '\0'; + offset += len; + ptr = va_arg(ap, char **); + *ptr = str; +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) + printf(" s=\"%s\"", *ptr); +#endif + } + break; + default: + abort(); + } + } + va_end(ap); + *poffset = offset; + return 0; +} + +static void virtio_9p_send_reply(VIRTIO9PDevice *s, int queue_idx, + int desc_idx, uint8_t id, uint16_t tag, + uint8_t *buf, int buf_len) +{ + uint8_t *buf1; + int len; + +#ifdef DEBUG_VIRTIO + if (s->common.debug & VIRTIO_DEBUG_9P) { + if (id == 6) + printf(" (error)"); + printf("\n"); + } +#endif + len = buf_len + 7; + buf1 = malloc(len); + put_le32(buf1, len); + buf1[4] = id + 1; + put_le16(buf1 + 5, tag); + memcpy(buf1 + 7, buf, buf_len); + memcpy_to_queue((VIRTIODevice *)s, queue_idx, desc_idx, 0, buf1, len); + virtio_consume_desc((VIRTIODevice *)s, queue_idx, desc_idx, len); + free(buf1); +} + +static void virtio_9p_send_error(VIRTIO9PDevice *s, int queue_idx, + int desc_idx, uint16_t tag, uint32_t error) +{ + uint8_t buf[4]; + int buf_len; + + buf_len = marshall(s, buf, sizeof(buf), "w", -error); + virtio_9p_send_reply(s, queue_idx, desc_idx, 6, tag, buf, buf_len); +} + +typedef struct { + VIRTIO9PDevice *dev; + int queue_idx; + int desc_idx; + uint16_t tag; +} P9OpenInfo; + +static void virtio_9p_open_reply(FSDevice *fs, FSQID *qid, int err, + P9OpenInfo *oi) +{ + VIRTIO9PDevice *s = oi->dev; + uint8_t buf[32]; + int buf_len; + + if (err < 0) { + virtio_9p_send_error(s, oi->queue_idx, oi->desc_idx, oi->tag, err); + } else { + buf_len = marshall(s, buf, sizeof(buf), + "Qw", qid, s->msize - 24); + virtio_9p_send_reply(s, oi->queue_idx, oi->desc_idx, 12, oi->tag, + buf, buf_len); + } + free(oi); +} + +static void virtio_9p_open_cb(FSDevice *fs, FSQID *qid, int err, + void *opaque) +{ + P9OpenInfo *oi = opaque; + VIRTIO9PDevice *s = oi->dev; + int queue_idx = oi->queue_idx; + + virtio_9p_open_reply(fs, qid, err, oi); + + s->req_in_progress = FALSE; + + /* handle next requests */ + queue_notify((VIRTIODevice *)s, queue_idx); +} + +static int virtio_9p_recv_request(VIRTIODevice *s1, int queue_idx, + int desc_idx, int read_size, + int write_size) +{ + VIRTIO9PDevice *s = (VIRTIO9PDevice *)s1; + int offset, header_len; + uint8_t id; + uint16_t tag; + uint8_t buf[1024]; + int buf_len, err; + FSDevice *fs = s->fs; + + if (queue_idx != 0) + return 0; + + if (s->req_in_progress) + return -1; + + offset = 0; + header_len = 4 + 1 + 2; + if (memcpy_from_queue(s1, buf, queue_idx, desc_idx, offset, header_len)) { + tag = 0; + goto protocol_error; + } + //size = get_le32(buf); + id = buf[4]; + tag = get_le16(buf + 5); + offset += header_len; + +#ifdef DEBUG_VIRTIO + if (s1->debug & VIRTIO_DEBUG_9P) { + const char *name; + name = get_9p_op_name(id); + printf("9p: op="); + if (name) + printf("%s", name); + else + printf("%d", id); + } +#endif + /* Note: same subset as JOR1K */ + switch(id) { + case 8: /* statfs */ + { + FSStatFS st; + + fs->fs_statfs(fs, &st); + buf_len = marshall(s, buf, sizeof(buf), + "wwddddddw", + 0, + st.f_bsize, + st.f_blocks, + st.f_bfree, + st.f_bavail, + st.f_files, + st.f_ffree, + 0, /* id */ + 256 /* max filename length */ + ); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 12: /* lopen */ + { + uint32_t fid, flags; + FSFile *f; + FSQID qid; + P9OpenInfo *oi; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "ww", &fid, &flags)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) + goto fid_not_found; + oi = malloc(sizeof(*oi)); + oi->dev = s; + oi->queue_idx = queue_idx; + oi->desc_idx = desc_idx; + oi->tag = tag; + err = fs->fs_open(fs, &qid, f, flags, virtio_9p_open_cb, oi); + if (err <= 0) { + virtio_9p_open_reply(fs, &qid, err, oi); + } else { + s->req_in_progress = TRUE; + } + } + break; + case 14: /* lcreate */ + { + uint32_t fid, flags, mode, gid; + char *name; + FSFile *f; + FSQID qid; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wswww", &fid, &name, &flags, &mode, &gid)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) { + err = -P9_EPROTO; + } else { + err = fs->fs_create(fs, &qid, f, name, flags, mode, gid); + } + free(name); + if (err) + goto error; + buf_len = marshall(s, buf, sizeof(buf), + "Qw", &qid, s->msize - 24); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 16: /* symlink */ + { + uint32_t fid, gid; + char *name, *symgt; + FSFile *f; + FSQID qid; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wssw", &fid, &name, &symgt, &gid)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) { + err = -P9_EPROTO; + } else { + err = fs->fs_symlink(fs, &qid, f, name, symgt, gid); + } + free(name); + free(symgt); + if (err) + goto error; + buf_len = marshall(s, buf, sizeof(buf), + "Q", &qid); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 18: /* mknod */ + { + uint32_t fid, mode, major, minor, gid; + char *name; + FSFile *f; + FSQID qid; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wswwww", &fid, &name, &mode, &major, &minor, &gid)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) { + err = -P9_EPROTO; + } else { + err = fs->fs_mknod(fs, &qid, f, name, mode, major, minor, gid); + } + free(name); + if (err) + goto error; + buf_len = marshall(s, buf, sizeof(buf), + "Q", &qid); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 22: /* readlink */ + { + uint32_t fid; + char buf1[1024]; + FSFile *f; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "w", &fid)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) { + err = -P9_EPROTO; + } else { + err = fs->fs_readlink(fs, buf1, sizeof(buf1), f); + } + if (err) + goto error; + buf_len = marshall(s, buf, sizeof(buf), "s", buf1); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 24: /* getattr */ + { + uint32_t fid; + uint64_t mask; + FSFile *f; + FSStat st; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wd", &fid, &mask)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) + goto fid_not_found; + err = fs->fs_stat(fs, f, &st); + if (err) + goto error; + + buf_len = marshall(s, buf, sizeof(buf), + "dQwwwddddddddddddddd", + mask, &st.qid, + st.st_mode, st.st_uid, st.st_gid, + st.st_nlink, st.st_rdev, st.st_size, + st.st_blksize, st.st_blocks, + st.st_atime_sec, (uint64_t)st.st_atime_nsec, + st.st_mtime_sec, (uint64_t)st.st_mtime_nsec, + st.st_ctime_sec, (uint64_t)st.st_ctime_nsec, + (uint64_t)0, (uint64_t)0, + (uint64_t)0, (uint64_t)0); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 26: /* setattr */ + { + uint32_t fid, mask, mode, uid, gid; + uint64_t size, atime_sec, atime_nsec, mtime_sec, mtime_nsec; + FSFile *f; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wwwwwddddd", &fid, &mask, &mode, &uid, &gid, + &size, &atime_sec, &atime_nsec, + &mtime_sec, &mtime_nsec)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) + goto fid_not_found; + err = fs->fs_setattr(fs, f, mask, mode, uid, gid, size, atime_sec, + atime_nsec, mtime_sec, mtime_nsec); + if (err) + goto error; + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); + } + break; + case 30: /* xattrwalk */ + { + /* not supported yet */ + err = -P9_ENOTSUP; + goto error; + } + break; + case 40: /* readdir */ + { + uint32_t fid, count; + uint64_t offs; + uint8_t *buf; + int n; + FSFile *f; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wdw", &fid, &offs, &count)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) + goto fid_not_found; + buf = malloc(count + 4); + n = fs->fs_readdir(fs, f, offs, buf + 4, count); + if (n < 0) { + err = n; + goto error; + } + put_le32(buf, n); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, n + 4); + free(buf); + } + break; + case 50: /* fsync */ + { + uint32_t fid; + if (unmarshall(s, queue_idx, desc_idx, &offset, + "w", &fid)) + goto protocol_error; + /* ignored */ + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); + } + break; + case 52: /* lock */ + { + uint32_t fid; + FSFile *f; + FSLock lock; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wbwddws", &fid, &lock.type, &lock.flags, + &lock.start, &lock.length, + &lock.proc_id, &lock.client_id)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) + err = -P9_EPROTO; + else + err = fs->fs_lock(fs, f, &lock); + free(lock.client_id); + if (err < 0) + goto error; + buf_len = marshall(s, buf, sizeof(buf), "b", err); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 54: /* getlock */ + { + uint32_t fid; + FSFile *f; + FSLock lock; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wbddws", &fid, &lock.type, + &lock.start, &lock.length, + &lock.proc_id, &lock.client_id)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) + err = -P9_EPROTO; + else + err = fs->fs_getlock(fs, f, &lock); + if (err < 0) { + free(lock.client_id); + goto error; + } + buf_len = marshall(s, buf, sizeof(buf), "bddws", + &lock.type, + &lock.start, &lock.length, + &lock.proc_id, &lock.client_id); + free(lock.client_id); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 70: /* link */ + { + uint32_t dfid, fid; + char *name; + FSFile *f, *df; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wws", &dfid, &fid, &name)) + goto protocol_error; + df = fid_find(s, dfid); + f = fid_find(s, fid); + if (!df || !f) { + err = -P9_EPROTO; + } else { + err = fs->fs_link(fs, df, f, name); + } + free(name); + if (err) + goto error; + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); + } + break; + case 72: /* mkdir */ + { + uint32_t fid, mode, gid; + char *name; + FSFile *f; + FSQID qid; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wsww", &fid, &name, &mode, &gid)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) + goto fid_not_found; + err = fs->fs_mkdir(fs, &qid, f, name, mode, gid); + if (err != 0) + goto error; + buf_len = marshall(s, buf, sizeof(buf), "Q", &qid); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 74: /* renameat */ + { + uint32_t fid, new_fid; + char *name, *new_name; + FSFile *f, *new_f; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wsws", &fid, &name, &new_fid, &new_name)) + goto protocol_error; + f = fid_find(s, fid); + new_f = fid_find(s, new_fid); + if (!f || !new_f) { + err = -P9_EPROTO; + } else { + err = fs->fs_renameat(fs, f, name, new_f, new_name); + } + free(name); + free(new_name); + if (err != 0) + goto error; + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); + } + break; + case 76: /* unlinkat */ + { + uint32_t fid, flags; + char *name; + FSFile *f; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wsw", &fid, &name, &flags)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) { + err = -P9_EPROTO; + } else { + err = fs->fs_unlinkat(fs, f, name); + } + free(name); + if (err != 0) + goto error; + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); + } + break; + case 100: /* version */ + { + uint32_t msize; + char *version; + if (unmarshall(s, queue_idx, desc_idx, &offset, + "ws", &msize, &version)) + goto protocol_error; + s->msize = msize; + // printf("version: msize=%d version=%s\n", msize, version); + free(version); + buf_len = marshall(s, buf, sizeof(buf), "ws", s->msize, "9P2000.L"); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 104: /* attach */ + { + uint32_t fid, afid, uid; + char *uname, *aname; + FSQID qid; + FSFile *f; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wwssw", &fid, &afid, &uname, &aname, &uid)) + goto protocol_error; + err = fs->fs_attach(fs, &f, &qid, uid, uname, aname); + if (err != 0) + goto error; + fid_set(s, fid, f); + free(uname); + free(aname); + buf_len = marshall(s, buf, sizeof(buf), "Q", &qid); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 108: /* flush */ + { + uint16_t oldtag; + if (unmarshall(s, queue_idx, desc_idx, &offset, + "h", &oldtag)) + goto protocol_error; + /* ignored */ + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); + } + break; + case 110: /* walk */ + { + uint32_t fid, newfid; + uint16_t nwname; + FSQID *qids; + char **names; + FSFile *f; + int i; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wwh", &fid, &newfid, &nwname)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) + goto fid_not_found; + names = mallocz(sizeof(names[0]) * nwname); + qids = malloc(sizeof(qids[0]) * nwname); + for(i = 0; i < nwname; i++) { + if (unmarshall(s, queue_idx, desc_idx, &offset, + "s", &names[i])) { + err = -P9_EPROTO; + goto walk_done; + } + } + err = fs->fs_walk(fs, &f, qids, f, nwname, names); + walk_done: + for(i = 0; i < nwname; i++) { + free(names[i]); + } + free(names); + if (err < 0) { + free(qids); + goto error; + } + buf_len = marshall(s, buf, sizeof(buf), "h", err); + for(i = 0; i < err; i++) { + buf_len += marshall(s, buf + buf_len, sizeof(buf) - buf_len, + "Q", &qids[i]); + } + free(qids); + fid_set(s, newfid, f); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 116: /* read */ + { + uint32_t fid, count; + uint64_t offs; + uint8_t *buf; + int n; + FSFile *f; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wdw", &fid, &offs, &count)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) + goto fid_not_found; + buf = malloc(count + 4); + n = fs->fs_read(fs, f, offs, buf + 4, count); + if (n < 0) { + err = n; + free(buf); + goto error; + } + put_le32(buf, n); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, n + 4); + free(buf); + } + break; + case 118: /* write */ + { + uint32_t fid, count; + uint64_t offs; + uint8_t *buf1; + int n; + FSFile *f; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "wdw", &fid, &offs, &count)) + goto protocol_error; + f = fid_find(s, fid); + if (!f) + goto fid_not_found; + buf1 = malloc(count); + if (memcpy_from_queue(s1, buf1, queue_idx, desc_idx, offset, + count)) { + free(buf1); + goto protocol_error; + } + n = fs->fs_write(fs, f, offs, buf1, count); + free(buf1); + if (n < 0) { + err = n; + goto error; + } + buf_len = marshall(s, buf, sizeof(buf), "w", n); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, buf, buf_len); + } + break; + case 120: /* clunk */ + { + uint32_t fid; + + if (unmarshall(s, queue_idx, desc_idx, &offset, + "w", &fid)) + goto protocol_error; + fid_delete(s, fid); + virtio_9p_send_reply(s, queue_idx, desc_idx, id, tag, NULL, 0); + } + break; + default: + printf("9p: unsupported operation id=%d\n", id); + goto protocol_error; + } + return 0; + error: + virtio_9p_send_error(s, queue_idx, desc_idx, tag, err); + return 0; + protocol_error: + fid_not_found: + err = -P9_EPROTO; + goto error; +} + +VIRTIODevice *virtio_9p_init(VIRTIOBusDef *bus, FSDevice *fs, + const char *mount_tag) + +{ + VIRTIO9PDevice *s; + int len; + uint8_t *cfg; + + len = strlen(mount_tag); + s = mallocz(sizeof(*s)); + virtio_init(&s->common, bus, + 9, 2 + len, virtio_9p_recv_request); + s->common.device_features = 1 << 0; + + /* set the mount tag */ + cfg = s->common.config_space; + cfg[0] = len; + cfg[1] = len >> 8; + memcpy(cfg + 2, mount_tag, len); + + s->fs = fs; + s->msize = 8192; + init_list_head(&s->fid_list); + + return (VIRTIODevice *)s; +} + diff --git a/virtio.h b/virtio.h new file mode 100644 index 0000000..d53c8c4 --- /dev/null +++ b/virtio.h @@ -0,0 +1,146 @@ +/* + * VIRTIO driver + * + * Copyright (c) 2016 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef VIRTIO_H +#define VIRTIO_H + +#include + +#include "iomem.h" +#include "pci.h" + +#define VIRTIO_PAGE_SIZE 4096 + +#if defined(EMSCRIPTEN) +#define VIRTIO_ADDR_BITS 32 +#else +#define VIRTIO_ADDR_BITS 64 +#endif + +#if VIRTIO_ADDR_BITS == 64 +typedef uint64_t virtio_phys_addr_t; +#else +typedef uint32_t virtio_phys_addr_t; +#endif + +typedef struct { + /* PCI only: */ + PCIBus *pci_bus; + /* MMIO only: */ + PhysMemoryMap *mem_map; + uint64_t addr; + IRQSignal *irq; +} VIRTIOBusDef; + +typedef struct VIRTIODevice VIRTIODevice; + +#define VIRTIO_DEBUG_IO (1 << 0) +#define VIRTIO_DEBUG_9P (1 << 1) + +void virtio_set_debug(VIRTIODevice *s, int debug_flags); + +/* block device */ + +typedef void BlockDeviceCompletionFunc(void *opaque, int ret); + +typedef struct BlockDevice BlockDevice; + +struct BlockDevice { + int64_t (*get_sector_count)(BlockDevice *bs); + int (*read_async)(BlockDevice *bs, + uint64_t sector_num, uint8_t *buf, int n, + BlockDeviceCompletionFunc *cb, void *opaque); + int (*write_async)(BlockDevice *bs, + uint64_t sector_num, const uint8_t *buf, int n, + BlockDeviceCompletionFunc *cb, void *opaque); + void *opaque; +}; + +VIRTIODevice *virtio_block_init(VIRTIOBusDef *bus, BlockDevice *bs); + +/* network device */ + +typedef struct EthernetDevice EthernetDevice; + +struct EthernetDevice { + uint8_t mac_addr[6]; /* mac address of the interface */ + void (*write_packet)(EthernetDevice *net, + const uint8_t *buf, int len); + void *opaque; +#if !defined(EMSCRIPTEN) + void (*select_fill)(EthernetDevice *net, int *pfd_max, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int *pdelay); + void (*select_poll)(EthernetDevice *net, + fd_set *rfds, fd_set *wfds, fd_set *efds, + int select_ret); +#endif + /* the following is set by the device */ + void *device_opaque; + BOOL (*device_can_write_packet)(EthernetDevice *net); + void (*device_write_packet)(EthernetDevice *net, + const uint8_t *buf, int len); + void (*device_set_carrier)(EthernetDevice *net, BOOL carrier_state); +}; + +VIRTIODevice *virtio_net_init(VIRTIOBusDef *bus, EthernetDevice *es); + +/* console device */ + +typedef struct { + void *opaque; + void (*write_data)(void *opaque, const uint8_t *buf, int len); + int (*read_data)(void *opaque, uint8_t *buf, int len); +} CharacterDevice; + +VIRTIODevice *virtio_console_init(VIRTIOBusDef *bus, CharacterDevice *cs); +BOOL virtio_console_can_write_data(VIRTIODevice *s); +int virtio_console_get_write_len(VIRTIODevice *s); +int virtio_console_write_data(VIRTIODevice *s, const uint8_t *buf, int buf_len); +void virtio_console_resize_event(VIRTIODevice *s, int width, int height); + +/* input device */ + +typedef enum { + VIRTIO_INPUT_TYPE_KEYBOARD, + VIRTIO_INPUT_TYPE_MOUSE, + VIRTIO_INPUT_TYPE_TABLET, +} VirtioInputTypeEnum; + +#define VIRTIO_INPUT_ABS_SCALE 32768 + +int virtio_input_send_key_event(VIRTIODevice *s, BOOL is_down, + uint16_t key_code); +int virtio_input_send_mouse_event(VIRTIODevice *s, int dx, int dy, int dz, + unsigned int buttons); + +VIRTIODevice *virtio_input_init(VIRTIOBusDef *bus, VirtioInputTypeEnum type); + +/* 9p filesystem device */ + +#include "fs.h" + +VIRTIODevice *virtio_9p_init(VIRTIOBusDef *bus, FSDevice *fs, + const char *mount_tag); + +#endif /* VIRTIO_H */ diff --git a/vmmouse.c b/vmmouse.c new file mode 100644 index 0000000..927e510 --- /dev/null +++ b/vmmouse.c @@ -0,0 +1,162 @@ +/* + * VM mouse emulation + * + * Copyright (c) 2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "iomem.h" +#include "ps2.h" + +#define VMPORT_MAGIC 0x564D5868 + +#define REG_EAX 0 +#define REG_EBX 1 +#define REG_ECX 2 +#define REG_EDX 3 +#define REG_ESI 4 +#define REG_EDI 5 + +#define FIFO_SIZE (4 * 16) + +struct VMMouseState { + PS2MouseState *ps2_mouse; + int fifo_count, fifo_rindex, fifo_windex; + BOOL enabled; + BOOL absolute; + uint32_t fifo_buf[FIFO_SIZE]; +}; + +static void put_queue(VMMouseState *s, uint32_t val) +{ + if (s->fifo_count >= FIFO_SIZE) + return; + s->fifo_buf[s->fifo_windex] = val; + if (++s->fifo_windex == FIFO_SIZE) + s->fifo_windex = 0; + s->fifo_count++; +} + +static void read_data(VMMouseState *s, uint32_t *regs, int size) +{ + int i; + if (size > 6 || size > s->fifo_count) { + // printf("vmmouse: read error req=%d count=%d\n", size, s->fifo_count); + s->enabled = FALSE; + return; + } + for(i = 0; i < size; i++) { + regs[i] = s->fifo_buf[s->fifo_rindex]; + if (++s->fifo_rindex == FIFO_SIZE) + s->fifo_rindex = 0; + } + s->fifo_count -= size; +} + +void vmmouse_send_mouse_event(VMMouseState *s, int x, int y, int dz, + int buttons) +{ + int state; + + if (!s->enabled) { + ps2_mouse_event(s->ps2_mouse, x, y, dz, buttons); + return; + } + + if ((s->fifo_count + 4) > FIFO_SIZE) + return; + + state = 0; + if (buttons & 1) + state |= 0x20; + if (buttons & 2) + state |= 0x10; + if (buttons & 4) + state |= 0x08; + if (s->absolute) { + /* range = 0 ... 65535 */ + x *= 2; + y *= 2; + } + + put_queue(s, state); + put_queue(s, x); + put_queue(s, y); + put_queue(s, -dz); + + /* send PS/2 mouse event */ + ps2_mouse_event(s->ps2_mouse, 1, 0, 0, 0); +} + +void vmmouse_handler(VMMouseState *s, uint32_t *regs) +{ + uint32_t cmd; + + cmd = regs[REG_ECX] & 0xff; + switch(cmd) { + case 10: /* get version */ + regs[REG_EBX] = VMPORT_MAGIC; + break; + case 39: /* VMMOUSE_DATA */ + read_data(s, regs, regs[REG_EBX]); + break; + case 40: /* VMMOUSE_STATUS */ + regs[REG_EAX] = ((s->enabled ? 0 : 0xffff) << 16) | s->fifo_count; + break; + case 41: /* VMMOUSE_COMMAND */ + switch(regs[REG_EBX]) { + case 0x45414552: /* read id */ + if (s->fifo_count < FIFO_SIZE) { + put_queue(s, 0x3442554a); + s->enabled = TRUE; + } + break; + case 0x000000f5: /* disable */ + s->enabled = FALSE; + break; + case 0x4c455252: /* set relative */ + s->absolute = 0; + break; + case 0x53424152: /* set absolute */ + s->absolute = 1; + break; + } + break; + } +} + +BOOL vmmouse_is_absolute(VMMouseState *s) +{ + return s->absolute; +} + +VMMouseState *vmmouse_init(PS2MouseState *ps2_mouse) +{ + VMMouseState *s; + s = mallocz(sizeof(*s)); + s->ps2_mouse = ps2_mouse; + return s; +} diff --git a/x86_cpu.c b/x86_cpu.c new file mode 100644 index 0000000..e599ab8 --- /dev/null +++ b/x86_cpu.c @@ -0,0 +1,96 @@ +/* + * x86 CPU emulator stub + * + * Copyright (c) 2011-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "x86_cpu.h" + +X86CPUState *x86_cpu_init(PhysMemoryMap *mem_map) +{ + fprintf(stderr, "x86 emulator is not supported\n"); + exit(1); +} + +void x86_cpu_end(X86CPUState *s) +{ +} + +void x86_cpu_interp(X86CPUState *s, int max_cycles1) +{ +} + +void x86_cpu_set_irq(X86CPUState *s, BOOL set) +{ +} + +void x86_cpu_set_reg(X86CPUState *s, int reg, uint32_t val) +{ +} + +uint32_t x86_cpu_get_reg(X86CPUState *s, int reg) +{ + return 0; +} + +void x86_cpu_set_seg(X86CPUState *s, int seg, const X86CPUSeg *sd) +{ +} + +void x86_cpu_set_get_hard_intno(X86CPUState *s, + int (*get_hard_intno)(void *opaque), + void *opaque) +{ +} + +void x86_cpu_set_get_tsc(X86CPUState *s, + uint64_t (*get_tsc)(void *opaque), + void *opaque) +{ +} + +void x86_cpu_set_port_io(X86CPUState *s, + DeviceReadFunc *port_read, DeviceWriteFunc *port_write, + void *opaque) +{ +} + +int64_t x86_cpu_get_cycles(X86CPUState *s) +{ + return 0; +} + +BOOL x86_cpu_get_power_down(X86CPUState *s) +{ + return FALSE; +} + +void x86_cpu_flush_tlb_write_range_ram(X86CPUState *s, + uint8_t *ram_ptr, size_t ram_size) +{ +} diff --git a/x86_cpu.h b/x86_cpu.h new file mode 100644 index 0000000..254f7f5 --- /dev/null +++ b/x86_cpu.h @@ -0,0 +1,70 @@ +/* + * x86 CPU emulator + * + * Copyright (c) 2011-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "iomem.h" + +typedef struct X86CPUState X86CPUState; + +/* get_reg/set_reg additional constants */ +#define X86_CPU_REG_EIP 8 +#define X86_CPU_REG_CR0 9 +#define X86_CPU_REG_CR2 10 + +#define X86_CPU_SEG_ES 0 +#define X86_CPU_SEG_CS 1 +#define X86_CPU_SEG_SS 2 +#define X86_CPU_SEG_DS 3 +#define X86_CPU_SEG_FS 4 +#define X86_CPU_SEG_GS 5 +#define X86_CPU_SEG_LDT 6 +#define X86_CPU_SEG_TR 7 +#define X86_CPU_SEG_GDT 8 +#define X86_CPU_SEG_IDT 9 + +typedef struct { + uint16_t sel; + uint16_t flags; + uint32_t base; + uint32_t limit; +} X86CPUSeg; + +X86CPUState *x86_cpu_init(PhysMemoryMap *mem_map); +void x86_cpu_end(X86CPUState *s); +void x86_cpu_interp(X86CPUState *s, int max_cycles1); +void x86_cpu_set_irq(X86CPUState *s, BOOL set); +void x86_cpu_set_reg(X86CPUState *s, int reg, uint32_t val); +uint32_t x86_cpu_get_reg(X86CPUState *s, int reg); +void x86_cpu_set_seg(X86CPUState *s, int seg, const X86CPUSeg *sd); +void x86_cpu_set_get_hard_intno(X86CPUState *s, + int (*get_hard_intno)(void *opaque), + void *opaque); +void x86_cpu_set_get_tsc(X86CPUState *s, + uint64_t (*get_tsc)(void *opaque), + void *opaque); +void x86_cpu_set_port_io(X86CPUState *s, + DeviceReadFunc *port_read, DeviceWriteFunc *port_write, + void *opaque); +int64_t x86_cpu_get_cycles(X86CPUState *s); +BOOL x86_cpu_get_power_down(X86CPUState *s); +void x86_cpu_flush_tlb_write_range_ram(X86CPUState *s, + uint8_t *ram_ptr, size_t ram_size); diff --git a/x86_machine.c b/x86_machine.c new file mode 100644 index 0000000..db8f6bb --- /dev/null +++ b/x86_machine.c @@ -0,0 +1,2569 @@ +/* + * PC emulator + * + * Copyright (c) 2011-2017 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "iomem.h" +#include "virtio.h" +#include "x86_cpu.h" +#include "machine.h" +#include "pci.h" +#include "ide.h" +#include "ps2.h" + +#if defined(__linux__) && (defined(__i386__) || defined(__x86_64__)) +#define USE_KVM +#endif + +#ifdef USE_KVM +#include +#include +#include +#include +#include +#endif + +//#define DEBUG_BIOS +//#define DUMP_IOPORT + +/***********************************************************/ +/* cmos emulation */ + +//#define DEBUG_CMOS + +#define RTC_SECONDS 0 +#define RTC_SECONDS_ALARM 1 +#define RTC_MINUTES 2 +#define RTC_MINUTES_ALARM 3 +#define RTC_HOURS 4 +#define RTC_HOURS_ALARM 5 +#define RTC_ALARM_DONT_CARE 0xC0 + +#define RTC_DAY_OF_WEEK 6 +#define RTC_DAY_OF_MONTH 7 +#define RTC_MONTH 8 +#define RTC_YEAR 9 + +#define RTC_REG_A 10 +#define RTC_REG_B 11 +#define RTC_REG_C 12 +#define RTC_REG_D 13 + +#define REG_A_UIP 0x80 + +#define REG_B_SET 0x80 +#define REG_B_PIE 0x40 +#define REG_B_AIE 0x20 +#define REG_B_UIE 0x10 + +typedef struct { + uint8_t cmos_index; + uint8_t cmos_data[128]; + IRQSignal *irq; + BOOL use_local_time; + /* used for the periodic irq */ + uint32_t irq_timeout; + uint32_t irq_period; +} CMOSState; + +static void cmos_write(void *opaque, uint32_t offset, + uint32_t data, int size_log2); +static uint32_t cmos_read(void *opaque, uint32_t offset, int size_log2); + +static int to_bcd(CMOSState *s, unsigned int a) +{ + if (s->cmos_data[RTC_REG_B] & 0x04) { + return a; + } else { + return ((a / 10) << 4) | (a % 10); + } +} + +static void cmos_update_time(CMOSState *s, BOOL set_century) +{ + struct timeval tv; + struct tm tm; + time_t ti; + int val; + + gettimeofday(&tv, NULL); + ti = tv.tv_sec; + if (s->use_local_time) { + localtime_r(&ti, &tm); + } else { + gmtime_r(&ti, &tm); + } + + s->cmos_data[RTC_SECONDS] = to_bcd(s, tm.tm_sec); + s->cmos_data[RTC_MINUTES] = to_bcd(s, tm.tm_min); + if (s->cmos_data[RTC_REG_B] & 0x02) { + s->cmos_data[RTC_HOURS] = to_bcd(s, tm.tm_hour); + } else { + s->cmos_data[RTC_HOURS] = to_bcd(s, tm.tm_hour % 12); + if (tm.tm_hour >= 12) + s->cmos_data[RTC_HOURS] |= 0x80; + } + s->cmos_data[RTC_DAY_OF_WEEK] = to_bcd(s, tm.tm_wday); + s->cmos_data[RTC_DAY_OF_MONTH] = to_bcd(s, tm.tm_mday); + s->cmos_data[RTC_MONTH] = to_bcd(s, tm.tm_mon + 1); + s->cmos_data[RTC_YEAR] = to_bcd(s, tm.tm_year % 100); + + if (set_century) { + /* not set by the hardware, but easier to do it here */ + val = to_bcd(s, (tm.tm_year / 100) + 19); + s->cmos_data[0x32] = val; + s->cmos_data[0x37] = val; + } + + /* update in progress flag: 8/32768 seconds after change */ + if (tv.tv_usec < 244) { + s->cmos_data[RTC_REG_A] |= REG_A_UIP; + } else { + s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; + } +} + +CMOSState *cmos_init(PhysMemoryMap *port_map, int addr, + IRQSignal *irq, BOOL use_local_time) +{ + CMOSState *s; + + s = mallocz(sizeof(*s)); + s->use_local_time = use_local_time; + + s->cmos_index = 0; + + s->cmos_data[RTC_REG_A] = 0x26; + s->cmos_data[RTC_REG_B] = 0x02; + s->cmos_data[RTC_REG_C] = 0x00; + s->cmos_data[RTC_REG_D] = 0x80; + + cmos_update_time(s, TRUE); + + s->irq = irq; + + cpu_register_device(port_map, addr, 2, s, cmos_read, cmos_write, + DEVIO_SIZE8); + return s; +} + +#define CMOS_FREQ 32768 + +static uint32_t cmos_get_timer(CMOSState *s) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint32_t)ts.tv_sec * CMOS_FREQ + + ((uint64_t)ts.tv_nsec * CMOS_FREQ / 1000000000); +} + +static void cmos_update_timer(CMOSState *s) +{ + int period_code; + + period_code = s->cmos_data[RTC_REG_A] & 0x0f; + if ((s->cmos_data[RTC_REG_B] & REG_B_PIE) && + period_code != 0) { + if (period_code <= 2) + period_code += 7; + s->irq_period = 1 << (period_code - 1); + s->irq_timeout = (cmos_get_timer(s) + s->irq_period) & + ~(s->irq_period - 1); + } +} + +/* XXX: could return a delay, but we don't need high precision + (Windows 2000 uses it for delay calibration) */ +static void cmos_update_irq(CMOSState *s) +{ + uint32_t d; + if (s->cmos_data[RTC_REG_B] & REG_B_PIE) { + d = cmos_get_timer(s) - s->irq_timeout; + if ((int32_t)d >= 0) { + /* this is not what the real RTC does. Here we sent the IRQ + immediately */ + s->cmos_data[RTC_REG_C] |= 0xc0; + set_irq(s->irq, 1); + /* update for the next irq */ + s->irq_timeout += s->irq_period; + } + } +} + +static void cmos_write(void *opaque, uint32_t offset, + uint32_t data, int size_log2) +{ + CMOSState *s = opaque; + + if (offset == 0) { + s->cmos_index = data & 0x7f; + } else { +#ifdef DEBUG_CMOS + printf("cmos_write: reg=0x%02x val=0x%02x\n", s->cmos_index, data); +#endif + switch(s->cmos_index) { + case RTC_REG_A: + s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) | + (s->cmos_data[RTC_REG_A] & REG_A_UIP); + cmos_update_timer(s); + break; + case RTC_REG_B: + s->cmos_data[s->cmos_index] = data; + cmos_update_timer(s); + break; + default: + s->cmos_data[s->cmos_index] = data; + break; + } + } +} + +static uint32_t cmos_read(void *opaque, uint32_t offset, int size_log2) +{ + CMOSState *s = opaque; + int ret; + + if (offset == 0) { + return 0xff; + } else { + switch(s->cmos_index) { + case RTC_SECONDS: + case RTC_MINUTES: + case RTC_HOURS: + case RTC_DAY_OF_WEEK: + case RTC_DAY_OF_MONTH: + case RTC_MONTH: + case RTC_YEAR: + case RTC_REG_A: + cmos_update_time(s, FALSE); + ret = s->cmos_data[s->cmos_index]; + break; + case RTC_REG_C: + ret = s->cmos_data[s->cmos_index]; + s->cmos_data[RTC_REG_C] = 0x00; + set_irq(s->irq, 0); + break; + default: + ret = s->cmos_data[s->cmos_index]; + } +#ifdef DEBUG_CMOS + printf("cmos_read: reg=0x%02x val=0x%02x\n", s->cmos_index, ret); +#endif + return ret; + } +} + +/***********************************************************/ +/* 8259 pic emulation */ + +//#define DEBUG_PIC + +typedef void PICUpdateIRQFunc(void *opaque); + +typedef struct { + uint8_t last_irr; /* edge detection */ + uint8_t irr; /* interrupt request register */ + uint8_t imr; /* interrupt mask register */ + uint8_t isr; /* interrupt service register */ + uint8_t priority_add; /* used to compute irq priority */ + uint8_t irq_base; + uint8_t read_reg_select; + uint8_t special_mask; + uint8_t init_state; + uint8_t auto_eoi; + uint8_t rotate_on_autoeoi; + uint8_t init4; /* true if 4 byte init */ + uint8_t elcr; /* PIIX edge/trigger selection*/ + uint8_t elcr_mask; + PICUpdateIRQFunc *update_irq; + void *opaque; +} PICState; + +static void pic_reset(PICState *s); +static void pic_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2); +static uint32_t pic_read(void *opaque, uint32_t offset, int size_log2); +static void pic_elcr_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2); +static uint32_t pic_elcr_read(void *opaque, uint32_t offset, int size_log2); + +PICState *pic_init(PhysMemoryMap *port_map, int port, int elcr_port, + int elcr_mask, + PICUpdateIRQFunc *update_irq, void *opaque) +{ + PICState *s; + + s = mallocz(sizeof(*s)); + s->elcr_mask = elcr_mask; + s->update_irq = update_irq; + s->opaque = opaque; + cpu_register_device(port_map, port, 2, s, + pic_read, pic_write, DEVIO_SIZE8); + cpu_register_device(port_map, elcr_port, 1, s, + pic_elcr_read, pic_elcr_write, DEVIO_SIZE8); + pic_reset(s); + return s; +} + +static void pic_reset(PICState *s) +{ + /* all 8 bit registers */ + s->last_irr = 0; /* edge detection */ + s->irr = 0; /* interrupt request register */ + s->imr = 0; /* interrupt mask register */ + s->isr = 0; /* interrupt service register */ + s->priority_add = 0; /* used to compute irq priority */ + s->irq_base = 0; + s->read_reg_select = 0; + s->special_mask = 0; + s->init_state = 0; + s->auto_eoi = 0; + s->rotate_on_autoeoi = 0; + s->init4 = 0; /* true if 4 byte init */ +} + +/* set irq level. If an edge is detected, then the IRR is set to 1 */ +static void pic_set_irq1(PICState *s, int irq, int level) +{ + int mask; + mask = 1 << irq; + if (s->elcr & mask) { + /* level triggered */ + if (level) { + s->irr |= mask; + s->last_irr |= mask; + } else { + s->irr &= ~mask; + s->last_irr &= ~mask; + } + } else { + /* edge triggered */ + if (level) { + if ((s->last_irr & mask) == 0) + s->irr |= mask; + s->last_irr |= mask; + } else { + s->last_irr &= ~mask; + } + } +} + +static int pic_get_priority(PICState *s, int mask) +{ + int priority; + if (mask == 0) + return -1; + priority = 7; + while ((mask & (1 << ((priority + s->priority_add) & 7))) == 0) + priority--; + return priority; +} + +/* return the pic wanted interrupt. return -1 if none */ +static int pic_get_irq(PICState *s) +{ + int mask, cur_priority, priority; + + mask = s->irr & ~s->imr; + priority = pic_get_priority(s, mask); + if (priority < 0) + return -1; + /* compute current priority */ + cur_priority = pic_get_priority(s, s->isr); + if (priority > cur_priority) { + /* higher priority found: an irq should be generated */ + return priority; + } else { + return -1; + } +} + +/* acknowledge interrupt 'irq' */ +static void pic_intack(PICState *s, int irq) +{ + if (s->auto_eoi) { + if (s->rotate_on_autoeoi) + s->priority_add = (irq + 1) & 7; + } else { + s->isr |= (1 << irq); + } + /* We don't clear a level sensitive interrupt here */ + if (!(s->elcr & (1 << irq))) + s->irr &= ~(1 << irq); +} + +static void pic_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ + PICState *s = opaque; + int priority, addr; + + addr = offset & 1; +#ifdef DEBUG_PIC + console.log("pic_write: addr=" + toHex2(addr) + " val=" + toHex2(val)); +#endif + if (addr == 0) { + if (val & 0x10) { + /* init */ + pic_reset(s); + s->init_state = 1; + s->init4 = val & 1; + if (val & 0x02) + abort(); /* "single mode not supported" */ + if (val & 0x08) + abort(); /* "level sensitive irq not supported" */ + } else if (val & 0x08) { + if (val & 0x02) + s->read_reg_select = val & 1; + if (val & 0x40) + s->special_mask = (val >> 5) & 1; + } else { + switch(val) { + case 0x00: + case 0x80: + s->rotate_on_autoeoi = val >> 7; + break; + case 0x20: /* end of interrupt */ + case 0xa0: + priority = pic_get_priority(s, s->isr); + if (priority >= 0) { + s->isr &= ~(1 << ((priority + s->priority_add) & 7)); + } + if (val == 0xa0) + s->priority_add = (s->priority_add + 1) & 7; + break; + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + priority = val & 7; + s->isr &= ~(1 << priority); + break; + case 0xc0: + case 0xc1: + case 0xc2: + case 0xc3: + case 0xc4: + case 0xc5: + case 0xc6: + case 0xc7: + s->priority_add = (val + 1) & 7; + break; + case 0xe0: + case 0xe1: + case 0xe2: + case 0xe3: + case 0xe4: + case 0xe5: + case 0xe6: + case 0xe7: + priority = val & 7; + s->isr &= ~(1 << priority); + s->priority_add = (priority + 1) & 7; + break; + } + } + } else { + switch(s->init_state) { + case 0: + /* normal mode */ + s->imr = val; + s->update_irq(s->opaque); + break; + case 1: + s->irq_base = val & 0xf8; + s->init_state = 2; + break; + case 2: + if (s->init4) { + s->init_state = 3; + } else { + s->init_state = 0; + } + break; + case 3: + s->auto_eoi = (val >> 1) & 1; + s->init_state = 0; + break; + } + } +} + +static uint32_t pic_read(void *opaque, uint32_t offset, int size_log2) +{ + PICState *s = opaque; + int addr, ret; + + addr = offset & 1; + if (addr == 0) { + if (s->read_reg_select) + ret = s->isr; + else + ret = s->irr; + } else { + ret = s->imr; + } +#ifdef DEBUG_PIC + console.log("pic_read: addr=" + toHex2(addr1) + " val=" + toHex2(ret)); +#endif + return ret; +} + +static void pic_elcr_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ + PICState *s = opaque; + s->elcr = val & s->elcr_mask; +} + +static uint32_t pic_elcr_read(void *opaque, uint32_t offset, int size_log2) +{ + PICState *s = opaque; + return s->elcr; +} + +typedef struct { + PICState *pics[2]; + int irq_requested; + void (*cpu_set_irq)(void *opaque, int level); + void *opaque; +#if defined(DEBUG_PIC) + uint8_t irq_level[16]; +#endif + IRQSignal *irqs; +} PIC2State; + +static void pic2_update_irq(void *opaque); +static void pic2_set_irq(void *opaque, int irq, int level); + +PIC2State *pic2_init(PhysMemoryMap *port_map, uint32_t addr0, uint32_t addr1, + uint32_t elcr_addr0, uint32_t elcr_addr1, + void (*cpu_set_irq)(void *opaque, int level), + void *opaque, IRQSignal *irqs) +{ + PIC2State *s; + int i; + + s = mallocz(sizeof(*s)); + + for(i = 0; i < 16; i++) { + irq_init(&irqs[i], pic2_set_irq, s, i); + } + s->cpu_set_irq = cpu_set_irq; + s->opaque = opaque; + s->pics[0] = pic_init(port_map, addr0, elcr_addr0, 0xf8, pic2_update_irq, s); + s->pics[1] = pic_init(port_map, addr1, elcr_addr1, 0xde, pic2_update_irq, s); + s->irq_requested = 0; + return s; +} + +void pic2_set_elcr(PIC2State *s, const uint8_t *elcr) +{ + int i; + for(i = 0; i < 2; i++) { + s->pics[i]->elcr = elcr[i] & s->pics[i]->elcr_mask; + } +} + +/* raise irq to CPU if necessary. must be called every time the active + irq may change */ +static void pic2_update_irq(void *opaque) +{ + PIC2State *s = opaque; + int irq2, irq; + + /* first look at slave pic */ + irq2 = pic_get_irq(s->pics[1]); + if (irq2 >= 0) { + /* if irq request by slave pic, signal master PIC */ + pic_set_irq1(s->pics[0], 2, 1); + pic_set_irq1(s->pics[0], 2, 0); + } + /* look at requested irq */ + irq = pic_get_irq(s->pics[0]); +#if 0 + console.log("irr=" + toHex2(s->pics[0].irr) + " imr=" + toHex2(s->pics[0].imr) + " isr=" + toHex2(s->pics[0].isr) + " irq="+ irq); +#endif + if (irq >= 0) { + /* raise IRQ request on the CPU */ + s->cpu_set_irq(s->opaque, 1); + } else { + /* lower irq */ + s->cpu_set_irq(s->opaque, 0); + } +} + +static void pic2_set_irq(void *opaque, int irq, int level) +{ + PIC2State *s = opaque; +#if defined(DEBUG_PIC) + if (irq != 0 && level != s->irq_level[irq]) { + console.log("pic_set_irq: irq=" + irq + " level=" + level); + s->irq_level[irq] = level; + } +#endif + pic_set_irq1(s->pics[irq >> 3], irq & 7, level); + pic2_update_irq(s); +} + +/* called from the CPU to get the hardware interrupt number */ +static int pic2_get_hard_intno(PIC2State *s) +{ + int irq, irq2, intno; + + irq = pic_get_irq(s->pics[0]); + if (irq >= 0) { + pic_intack(s->pics[0], irq); + if (irq == 2) { + irq2 = pic_get_irq(s->pics[1]); + if (irq2 >= 0) { + pic_intack(s->pics[1], irq2); + } else { + /* spurious IRQ on slave controller */ + irq2 = 7; + } + intno = s->pics[1]->irq_base + irq2; + irq = irq2 + 8; + } else { + intno = s->pics[0]->irq_base + irq; + } + } else { + /* spurious IRQ on host controller */ + irq = 7; + intno = s->pics[0]->irq_base + irq; + } + pic2_update_irq(s); + +#if defined(DEBUG_PIC) + if (irq != 0 && irq != 14) + printf("pic_interrupt: irq=%d\n", irq); +#endif + return intno; +} + +/***********************************************************/ +/* 8253 PIT emulation */ + +#define PIT_FREQ 1193182 + +#define RW_STATE_LSB 0 +#define RW_STATE_MSB 1 +#define RW_STATE_WORD0 2 +#define RW_STATE_WORD1 3 +#define RW_STATE_LATCHED_WORD0 4 +#define RW_STATE_LATCHED_WORD1 5 + +//#define DEBUG_PIT + +typedef int64_t PITGetTicksFunc(void *opaque); + +typedef struct PITState PITState; + +typedef struct { + PITState *pit_state; + uint32_t count; + uint32_t latched_count; + uint8_t rw_state; + uint8_t mode; + uint8_t bcd; + uint8_t gate; + int64_t count_load_time; + int64_t last_irq_time; +} PITChannel; + +struct PITState { + PITChannel pit_channels[3]; + uint8_t speaker_data_on; + PITGetTicksFunc *get_ticks; + IRQSignal *irq; + void *opaque; +}; + +static void pit_load_count(PITChannel *pc, int val); +static void pit_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2); +static uint32_t pit_read(void *opaque, uint32_t offset, int size_log2); +static void speaker_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2); +static uint32_t speaker_read(void *opaque, uint32_t offset, int size_log2); + +PITState *pit_init(PhysMemoryMap *port_map, int addr0, int addr1, + IRQSignal *irq, + PITGetTicksFunc *get_ticks, void *opaque) +{ + PITState *s; + PITChannel *pc; + int i; + + s = mallocz(sizeof(*s)); + + s->irq = irq; + s->get_ticks = get_ticks; + s->opaque = opaque; + + for(i = 0; i < 3; i++) { + pc = &s->pit_channels[i]; + pc->pit_state = s; + pc->mode = 3; + pc->gate = (i != 2) >> 0; + pit_load_count(pc, 0); + } + s->speaker_data_on = 0; + + cpu_register_device(port_map, addr0, 4, s, pit_read, pit_write, + DEVIO_SIZE8); + + cpu_register_device(port_map, addr1, 1, s, speaker_read, speaker_write, + DEVIO_SIZE8); + return s; +} + +/* unit = PIT frequency */ +static int64_t pit_get_time(PITChannel *pc) +{ + PITState *s = pc->pit_state; + return s->get_ticks(s->opaque); +} + +static uint32_t pit_get_count(PITChannel *pc) +{ + uint32_t counter; + uint64_t d; + + d = pit_get_time(pc) - pc->count_load_time; + switch(pc->mode) { + case 0: + case 1: + case 4: + case 5: + counter = (pc->count - d) & 0xffff; + break; + default: + counter = pc->count - (d % pc->count); + break; + } + return counter; +} + +/* get pit output bit */ +static int pit_get_out(PITChannel *pc) +{ + int out; + int64_t d; + + d = pit_get_time(pc) - pc->count_load_time; + switch(pc->mode) { + default: + case 0: + out = (d >= pc->count) >> 0; + break; + case 1: + out = (d < pc->count) >> 0; + break; + case 2: + /* mode used by Linux */ + if ((d % pc->count) == 0 && d != 0) + out = 1; + else + out = 0; + break; + case 3: + out = ((d % pc->count) < (pc->count >> 1)) >> 0; + break; + case 4: + case 5: + out = (d == pc->count) >> 0; + break; + } + return out; +} + +static void pit_load_count(PITChannel *s, int val) +{ + if (val == 0) + val = 0x10000; + s->count_load_time = pit_get_time(s); + s->last_irq_time = 0; + s->count = val; +} + +static void pit_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ + PITState *pit = opaque; + int channel, access, addr; + PITChannel *s; + + addr = offset & 3; +#ifdef DEBUG_PIT + printf("pit_write: off=%d val=0x%02x\n", addr, val); +#endif + if (addr == 3) { + channel = val >> 6; + if (channel == 3) + return; + s = &pit->pit_channels[channel]; + access = (val >> 4) & 3; + switch(access) { + case 0: + s->latched_count = pit_get_count(s); + s->rw_state = RW_STATE_LATCHED_WORD0; + break; + default: + s->mode = (val >> 1) & 7; + s->bcd = val & 1; + s->rw_state = access - 1 + RW_STATE_LSB; + break; + } + } else { + s = &pit->pit_channels[addr]; + switch(s->rw_state) { + case RW_STATE_LSB: + pit_load_count(s, val); + break; + case RW_STATE_MSB: + pit_load_count(s, val << 8); + break; + case RW_STATE_WORD0: + case RW_STATE_WORD1: + if (s->rw_state & 1) { + pit_load_count(s, (s->latched_count & 0xff) | (val << 8)); + } else { + s->latched_count = val; + } + s->rw_state ^= 1; + break; + } + } +} + +static uint32_t pit_read(void *opaque, uint32_t offset, int size_log2) +{ + PITState *pit = opaque; + PITChannel *s; + int ret, count, addr; + + addr = offset & 3; + if (addr == 3) + return 0xff; + + s = &pit->pit_channels[addr]; + switch(s->rw_state) { + case RW_STATE_LSB: + case RW_STATE_MSB: + case RW_STATE_WORD0: + case RW_STATE_WORD1: + count = pit_get_count(s); + if (s->rw_state & 1) + ret = (count >> 8) & 0xff; + else + ret = count & 0xff; + if (s->rw_state & 2) + s->rw_state ^= 1; + break; + default: + case RW_STATE_LATCHED_WORD0: + case RW_STATE_LATCHED_WORD1: + if (s->rw_state & 1) + ret = s->latched_count >> 8; + else + ret = s->latched_count & 0xff; + s->rw_state ^= 1; + break; + } +#ifdef DEBUG_PIT + printf("pit_read: off=%d val=0x%02x\n", addr, ret); +#endif + return ret; +} + +static void speaker_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ + PITState *pit = opaque; + pit->speaker_data_on = (val >> 1) & 1; + pit->pit_channels[2].gate = val & 1; +} + +static uint32_t speaker_read(void *opaque, uint32_t offset, int size_log2) +{ + PITState *pit = opaque; + PITChannel *s; + int out, val; + + s = &pit->pit_channels[2]; + out = pit_get_out(s); + val = (pit->speaker_data_on << 1) | s->gate | (out << 5); +#ifdef DEBUG_PIT + // console.log("speaker_read: addr=" + toHex2(addr) + " val=" + toHex2(val)); +#endif + return val; +} + +/* set the IRQ if necessary and return the delay in ms until the next + IRQ. Note: The code does not handle all the PIT configurations. */ +static int pit_update_irq(PITState *pit) +{ + PITChannel *s; + int64_t d, delay; + + s = &pit->pit_channels[0]; + + delay = PIT_FREQ; /* could be infinity delay */ + + d = pit_get_time(s) - s->count_load_time; + switch(s->mode) { + default: + case 0: + case 1: + case 4: + case 5: + if (s->last_irq_time == 0) { + delay = s->count - d; + if (delay <= 0) { + set_irq(pit->irq, 1); + set_irq(pit->irq, 0); + s->last_irq_time = d; + } + } + break; + case 2: /* mode used by Linux */ + case 3: + delay = s->last_irq_time + s->count - d; + if (delay <= 0) { + set_irq(pit->irq, 1); + set_irq(pit->irq, 0); + s->last_irq_time += s->count; + } + break; + } + + if (delay <= 0) + return 0; + else + return delay / (PIT_FREQ / 1000); +} + +/***********************************************************/ +/* serial port emulation */ + +#define UART_LCR_DLAB 0x80 /* Divisor latch access bit */ + +#define UART_IER_MSI 0x08 /* Enable Modem status interrupt */ +#define UART_IER_RLSI 0x04 /* Enable receiver line status interrupt */ +#define UART_IER_THRI 0x02 /* Enable Transmitter holding register int. */ +#define UART_IER_RDI 0x01 /* Enable receiver data interrupt */ + +#define UART_IIR_NO_INT 0x01 /* No interrupts pending */ +#define UART_IIR_ID 0x06 /* Mask for the interrupt ID */ + +#define UART_IIR_MSI 0x00 /* Modem status interrupt */ +#define UART_IIR_THRI 0x02 /* Transmitter holding register empty */ +#define UART_IIR_RDI 0x04 /* Receiver data interrupt */ +#define UART_IIR_RLSI 0x06 /* Receiver line status interrupt */ +#define UART_IIR_FE 0xC0 /* Fifo enabled */ + +#define UART_LSR_TEMT 0x40 /* Transmitter empty */ +#define UART_LSR_THRE 0x20 /* Transmit-hold-register empty */ +#define UART_LSR_BI 0x10 /* Break interrupt indicator */ +#define UART_LSR_FE 0x08 /* Frame error indicator */ +#define UART_LSR_PE 0x04 /* Parity error indicator */ +#define UART_LSR_OE 0x02 /* Overrun error indicator */ +#define UART_LSR_DR 0x01 /* Receiver data ready */ + +#define UART_FCR_XFR 0x04 /* XMIT Fifo Reset */ +#define UART_FCR_RFR 0x02 /* RCVR Fifo Reset */ +#define UART_FCR_FE 0x01 /* FIFO Enable */ + +#define UART_FIFO_LENGTH 16 /* 16550A Fifo Length */ + +typedef struct { + uint8_t divider; + uint8_t rbr; /* receive register */ + uint8_t ier; + uint8_t iir; /* read only */ + uint8_t lcr; + uint8_t mcr; + uint8_t lsr; /* read only */ + uint8_t msr; + uint8_t scr; + uint8_t fcr; + IRQSignal *irq; + void (*write_func)(void *opaque, const uint8_t *buf, int buf_len); + void *opaque; +} SerialState; + +static void serial_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2); +static uint32_t serial_read(void *opaque, uint32_t offset, int size_log2); + +SerialState *serial_init(PhysMemoryMap *port_map, int addr, + IRQSignal *irq, + void (*write_func)(void *opaque, const uint8_t *buf, int buf_len), void *opaque) +{ + SerialState *s; + s = mallocz(sizeof(*s)); + + /* all 8 bit registers */ + s->divider = 0; + s->rbr = 0; /* receive register */ + s->ier = 0; + s->iir = UART_IIR_NO_INT; /* read only */ + s->lcr = 0; + s->mcr = 0; + s->lsr = UART_LSR_TEMT | UART_LSR_THRE; /* read only */ + s->msr = 0; + s->scr = 0; + s->fcr = 0; + + s->irq = irq; + s->write_func = write_func; + s->opaque = opaque; + + cpu_register_device(port_map, addr, 8, s, serial_read, serial_write, + DEVIO_SIZE8); + return s; +} + +static void serial_update_irq(SerialState *s) +{ + if ((s->lsr & UART_LSR_DR) && (s->ier & UART_IER_RDI)) { + s->iir = UART_IIR_RDI; + } else if ((s->lsr & UART_LSR_THRE) && (s->ier & UART_IER_THRI)) { + s->iir = UART_IIR_THRI; + } else { + s->iir = UART_IIR_NO_INT; + } + if (s->iir != UART_IIR_NO_INT) { + set_irq(s->irq, 1); + } else { + set_irq(s->irq, 0); + } +} + +#if 0 +/* send remainining chars in fifo */ +Serial.prototype.write_tx_fifo = function() +{ + if (s->tx_fifo != "") { + s->write_func(s->tx_fifo); + s->tx_fifo = ""; + + s->lsr |= UART_LSR_THRE; + s->lsr |= UART_LSR_TEMT; + s->update_irq(); + } +} +#endif + +static void serial_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ + SerialState *s = opaque; + int addr; + + addr = offset & 7; + switch(addr) { + default: + case 0: + if (s->lcr & UART_LCR_DLAB) { + s->divider = (s->divider & 0xff00) | val; + } else { +#if 0 + if (s->fcr & UART_FCR_FE) { + s->tx_fifo += String.fromCharCode(val); + s->lsr &= ~UART_LSR_THRE; + serial_update_irq(s); + if (s->tx_fifo.length >= UART_FIFO_LENGTH) { + /* write to the terminal */ + s->write_tx_fifo(); + } + } else +#endif + { + uint8_t ch; + s->lsr &= ~UART_LSR_THRE; + serial_update_irq(s); + + /* write to the terminal */ + ch = val; + s->write_func(s->opaque, &ch, 1); + s->lsr |= UART_LSR_THRE; + s->lsr |= UART_LSR_TEMT; + serial_update_irq(s); + } + } + break; + case 1: + if (s->lcr & UART_LCR_DLAB) { + s->divider = (s->divider & 0x00ff) | (val << 8); + } else { + s->ier = val; + serial_update_irq(s); + } + break; + case 2: +#if 0 + if ((s->fcr ^ val) & UART_FCR_FE) { + /* clear fifos */ + val |= UART_FCR_XFR | UART_FCR_RFR; + } + if (val & UART_FCR_XFR) + s->tx_fifo = ""; + if (val & UART_FCR_RFR) + s->rx_fifo = ""; + s->fcr = val & UART_FCR_FE; +#endif + break; + case 3: + s->lcr = val; + break; + case 4: + s->mcr = val; + break; + case 5: + break; + case 6: + s->msr = val; + break; + case 7: + s->scr = val; + break; + } +} + +static uint32_t serial_read(void *opaque, uint32_t offset, int size_log2) +{ + SerialState *s = opaque; + int ret, addr; + + addr = offset & 7; + switch(addr) { + default: + case 0: + if (s->lcr & UART_LCR_DLAB) { + ret = s->divider & 0xff; + } else { + ret = s->rbr; + s->lsr &= ~(UART_LSR_DR | UART_LSR_BI); + serial_update_irq(s); +#if 0 + /* try to receive next chars */ + s->send_char_from_fifo(); +#endif + } + break; + case 1: + if (s->lcr & UART_LCR_DLAB) { + ret = (s->divider >> 8) & 0xff; + } else { + ret = s->ier; + } + break; + case 2: + ret = s->iir; + if (s->fcr & UART_FCR_FE) + ret |= UART_IIR_FE; + break; + case 3: + ret = s->lcr; + break; + case 4: + ret = s->mcr; + break; + case 5: + ret = s->lsr; + break; + case 6: + ret = s->msr; + break; + case 7: + ret = s->scr; + break; + } + return ret; +} + +void serial_send_break(SerialState *s) +{ + s->rbr = 0; + s->lsr |= UART_LSR_BI | UART_LSR_DR; + serial_update_irq(s); +} + +#if 0 +static void serial_send_char(SerialState *s, int ch) +{ + s->rbr = ch; + s->lsr |= UART_LSR_DR; + serial_update_irq(s); +} + +Serial.prototype.send_char_from_fifo = function() +{ + var fifo; + + fifo = s->rx_fifo; + if (fifo != "" && !(s->lsr & UART_LSR_DR)) { + s->send_char(fifo.charCodeAt(0)); + s->rx_fifo = fifo.substr(1, fifo.length - 1); + } +} + +/* queue the string in the UART receive fifo and send it ASAP */ +Serial.prototype.send_chars = function(str) +{ + s->rx_fifo += str; + s->send_char_from_fifo(); +} + +#endif + +#ifdef DEBUG_BIOS +static void bios_debug_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ +#ifdef EMSCRIPTEN + static char line_buf[256]; + static int line_buf_index; + line_buf[line_buf_index++] = val; + if (val == '\n' || line_buf_index >= sizeof(line_buf) - 1) { + line_buf[line_buf_index] = '\0'; + printf("%s", line_buf); + line_buf_index = 0; + } +#else + putchar(val & 0xff); +#endif +} + +static uint32_t bios_debug_read(void *opaque, uint32_t offset, int size_log2) +{ + return 0; +} +#endif + +typedef struct PCMachine { + VirtMachine common; + uint64_t ram_size; + PhysMemoryMap *mem_map; + PhysMemoryMap *port_map; + + X86CPUState *cpu_state; + PIC2State *pic_state; + IRQSignal pic_irq[16]; + PITState *pit_state; + I440FXState *i440fx_state; + CMOSState *cmos_state; + SerialState *serial_state; + + /* input */ + VIRTIODevice *keyboard_dev; + VIRTIODevice *mouse_dev; + KBDState *kbd_state; + PS2MouseState *ps2_mouse; + VMMouseState *vm_mouse; + PS2KbdState *ps2_kbd; + +#ifdef USE_KVM + BOOL kvm_enabled; + int kvm_fd; + int vm_fd; + int vcpu_fd; + int kvm_run_size; + struct kvm_run *kvm_run; +#endif +} PCMachine; + +static void copy_kernel(PCMachine *s, const uint8_t *buf, int buf_len, + const char *cmd_line); + +static void port80_write(void *opaque, uint32_t offset, + uint32_t val64, int size_log2) +{ +} + +static uint32_t port80_read(void *opaque, uint32_t offset, int size_log2) +{ + return 0xff; +} + +static void port92_write(void *opaque, uint32_t offset, + uint32_t val, int size_log2) +{ +} + +static uint32_t port92_read(void *opaque, uint32_t offset, int size_log2) +{ + int a20 = 1; /* A20=0 is not supported */ + return a20 << 1; +} + +#define VMPORT_MAGIC 0x564D5868 +#define REG_EAX 0 +#define REG_EBX 1 +#define REG_ECX 2 +#define REG_EDX 3 +#define REG_ESI 4 +#define REG_EDI 5 + +static uint32_t vmport_read(void *opaque, uint32_t addr, int size_log2) +{ + PCMachine *s = opaque; + uint32_t regs[6]; + +#ifdef USE_KVM + if (s->kvm_enabled) { + struct kvm_regs r; + + ioctl(s->vcpu_fd, KVM_GET_REGS, &r); + regs[REG_EAX] = r.rax; + regs[REG_EBX] = r.rbx; + regs[REG_ECX] = r.rcx; + regs[REG_EDX] = r.rdx; + regs[REG_ESI] = r.rsi; + regs[REG_EDI] = r.rdi; + + if (regs[REG_EAX] == VMPORT_MAGIC) { + + vmmouse_handler(s->vm_mouse, regs); + + /* Note: in 64 bits the high parts are reset to zero + in all cases. */ + r.rax = regs[REG_EAX]; + r.rbx = regs[REG_EBX]; + r.rcx = regs[REG_ECX]; + r.rdx = regs[REG_EDX]; + r.rsi = regs[REG_ESI]; + r.rdi = regs[REG_EDI]; + ioctl(s->vcpu_fd, KVM_SET_REGS, &r); + } + } else +#endif + { + regs[REG_EAX] = x86_cpu_get_reg(s->cpu_state, 0); + regs[REG_EBX] = x86_cpu_get_reg(s->cpu_state, 3); + regs[REG_ECX] = x86_cpu_get_reg(s->cpu_state, 1); + regs[REG_EDX] = x86_cpu_get_reg(s->cpu_state, 2); + regs[REG_ESI] = x86_cpu_get_reg(s->cpu_state, 6); + regs[REG_EDI] = x86_cpu_get_reg(s->cpu_state, 7); + + if (regs[REG_EAX] == VMPORT_MAGIC) { + vmmouse_handler(s->vm_mouse, regs); + + x86_cpu_set_reg(s->cpu_state, 0, regs[REG_EAX]); + x86_cpu_set_reg(s->cpu_state, 3, regs[REG_EBX]); + x86_cpu_set_reg(s->cpu_state, 1, regs[REG_ECX]); + x86_cpu_set_reg(s->cpu_state, 2, regs[REG_EDX]); + x86_cpu_set_reg(s->cpu_state, 6, regs[REG_ESI]); + x86_cpu_set_reg(s->cpu_state, 7, regs[REG_EDI]); + } + } + return regs[REG_EAX]; +} + +static void vmport_write(void *opaque, uint32_t addr, uint32_t val, + int size_log2) +{ +} + +static void pic_set_irq_cb(void *opaque, int level) +{ + PCMachine *s = opaque; + x86_cpu_set_irq(s->cpu_state, level); +} + +static void serial_write_cb(void *opaque, const uint8_t *buf, int buf_len) +{ + PCMachine *s = opaque; + if (s->common.console) { + s->common.console->write_data(s->common.console->opaque, buf, buf_len); + } +} + +static int get_hard_intno_cb(void *opaque) +{ + PCMachine *s = opaque; + return pic2_get_hard_intno(s->pic_state); +} + +static int64_t pit_get_ticks_cb(void *opaque) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * PIT_FREQ + + ((uint64_t)ts.tv_nsec * PIT_FREQ / 1000000000); +} + +#define FRAMEBUFFER_BASE_ADDR 0xf0400000 + +static uint8_t *get_ram_ptr(PCMachine *s, uint64_t paddr) +{ + PhysMemoryRange *pr; + pr = get_phys_mem_range(s->mem_map, paddr); + if (!pr || !pr->is_ram) + return NULL; + return pr->phys_mem + (uintptr_t)(paddr - pr->addr); +} + +#ifdef DUMP_IOPORT +static BOOL dump_port(int port) +{ + return !((port >= 0x1f0 && port <= 0x1f7) || + (port >= 0x20 && port <= 0x21) || + (port >= 0xa0 && port <= 0xa1)); +} +#endif + +static void st_port(void *opaque, uint32_t port, uint32_t val, int size_log2) +{ + PCMachine *s = opaque; + PhysMemoryRange *pr; +#ifdef DUMP_IOPORT + if (dump_port(port)) + printf("write port=0x%x val=0x%x s=%d\n", port, val, 1 << size_log2); +#endif + pr = get_phys_mem_range(s->port_map, port); + if (!pr) { + return; + } + port -= pr->addr; + if ((pr->devio_flags >> size_log2) & 1) { + pr->write_func(pr->opaque, port, (uint32_t)val, size_log2); + } else if (size_log2 == 1 && (pr->devio_flags & DEVIO_SIZE8)) { + pr->write_func(pr->opaque, port, val & 0xff, 0); + pr->write_func(pr->opaque, port + 1, (val >> 8) & 0xff, 0); + } +} + +static uint32_t ld_port(void *opaque, uint32_t port1, int size_log2) +{ + PCMachine *s = opaque; + PhysMemoryRange *pr; + uint32_t val, port; + + port = port1; + pr = get_phys_mem_range(s->port_map, port); + if (!pr) { + val = -1; + } else { + port -= pr->addr; + if ((pr->devio_flags >> size_log2) & 1) { + val = pr->read_func(pr->opaque, port, size_log2); + } else if (size_log2 == 1 && (pr->devio_flags & DEVIO_SIZE8)) { + val = pr->read_func(pr->opaque, port, 0) & 0xff; + val |= (pr->read_func(pr->opaque, port + 1, 0) & 0xff) << 8; + } else { + val = -1; + } + } +#ifdef DUMP_IOPORT + if (dump_port(port1)) + printf("read port=0x%x val=0x%x s=%d\n", port1, val, 1 << size_log2); +#endif + return val; +} + +static void pc_machine_set_defaults(VirtMachineParams *p) +{ + p->accel_enable = TRUE; +} + +#ifdef USE_KVM + +static void sigalrm_handler(int sig) +{ +} + +#define CPUID_APIC (1 << 9) +#define CPUID_ACPI (1 << 22) + +static void kvm_set_cpuid(PCMachine *s) +{ + struct kvm_cpuid2 *kvm_cpuid; + int n_ent_max, i; + struct kvm_cpuid_entry2 *ent; + + n_ent_max = 128; + kvm_cpuid = mallocz(sizeof(struct kvm_cpuid2) + n_ent_max * sizeof(kvm_cpuid->entries[0])); + + kvm_cpuid->nent = n_ent_max; + if (ioctl(s->kvm_fd, KVM_GET_SUPPORTED_CPUID, kvm_cpuid) < 0) { + perror("KVM_GET_SUPPORTED_CPUID"); + exit(1); + } + + for(i = 0; i < kvm_cpuid->nent; i++) { + ent = &kvm_cpuid->entries[i]; + /* remove the APIC & ACPI to be in sync with the emulator */ + if (ent->function == 1 || ent->function == 0x80000001) { + ent->edx &= ~(CPUID_APIC | CPUID_ACPI); + } + } + + if (ioctl(s->vcpu_fd, KVM_SET_CPUID2, kvm_cpuid) < 0) { + perror("KVM_SET_CPUID2"); + exit(1); + } + free(kvm_cpuid); +} + +/* XXX: should check overlapping mappings */ +static void kvm_map_ram(PhysMemoryMap *mem_map, PhysMemoryRange *pr) +{ + PCMachine *s = mem_map->opaque; + struct kvm_userspace_memory_region region; + int flags; + + region.slot = pr - mem_map->phys_mem_range; + flags = 0; + if (pr->devram_flags & DEVRAM_FLAG_ROM) + flags |= KVM_MEM_READONLY; + if (pr->devram_flags & DEVRAM_FLAG_DIRTY_BITS) + flags |= KVM_MEM_LOG_DIRTY_PAGES; + region.flags = flags; + region.guest_phys_addr = pr->addr; + region.memory_size = pr->size; +#if 0 + printf("map slot %d: %08lx %08lx\n", + region.slot, pr->addr, pr->size); +#endif + region.userspace_addr = (uintptr_t)pr->phys_mem; + if (ioctl(s->vm_fd, KVM_SET_USER_MEMORY_REGION, ®ion) < 0) { + perror("KVM_SET_USER_MEMORY_REGION"); + exit(1); + } +} + +/* XXX: just for one region */ +static PhysMemoryRange *kvm_register_ram(PhysMemoryMap *mem_map, uint64_t addr, + uint64_t size, int devram_flags) +{ + PhysMemoryRange *pr; + uint8_t *phys_mem; + + pr = register_ram_entry(mem_map, addr, size, devram_flags); + + phys_mem = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (!phys_mem) + return NULL; + pr->phys_mem = phys_mem; + if (devram_flags & DEVRAM_FLAG_DIRTY_BITS) { + int n_pages = size >> 12; + pr->dirty_bits_size = ((n_pages + 63) / 64) * 8; + pr->dirty_bits = mallocz(pr->dirty_bits_size); + } + + if (pr->size != 0) { + kvm_map_ram(mem_map, pr); + } + return pr; +} + +static void kvm_set_ram_addr(PhysMemoryMap *mem_map, + PhysMemoryRange *pr, uint64_t addr, BOOL enabled) +{ + if (enabled) { + if (pr->size == 0 || addr != pr->addr) { + /* move or create the region */ + pr->size = pr->org_size; + pr->addr = addr; + kvm_map_ram(mem_map, pr); + } + } else { + if (pr->size != 0) { + pr->addr = 0; + pr->size = 0; + /* map a zero size region to disable */ + kvm_map_ram(mem_map, pr); + } + } +} + +static const uint32_t *kvm_get_dirty_bits(PhysMemoryMap *mem_map, + PhysMemoryRange *pr) +{ + PCMachine *s = mem_map->opaque; + struct kvm_dirty_log dlog; + + if (pr->size == 0) { + /* not mapped: we assume no modification was made */ + memset(pr->dirty_bits, 0, pr->dirty_bits_size); + } else { + dlog.slot = pr - mem_map->phys_mem_range; + dlog.dirty_bitmap = pr->dirty_bits; + if (ioctl(s->vm_fd, KVM_GET_DIRTY_LOG, &dlog) < 0) { + perror("KVM_GET_DIRTY_LOG"); + exit(1); + } + } + return pr->dirty_bits; +} + +static void kvm_free_ram(PhysMemoryMap *mem_map, PhysMemoryRange *pr) +{ + /* XXX: do it */ + munmap(pr->phys_mem, pr->org_size); + free(pr->dirty_bits); +} + +static void kvm_pic_set_irq(void *opaque, int irq_num, int level) +{ + PCMachine *s = opaque; + struct kvm_irq_level irq_level; + irq_level.irq = irq_num; + irq_level.level = level; + if (ioctl(s->vm_fd, KVM_IRQ_LINE, &irq_level) < 0) { + perror("KVM_IRQ_LINE"); + exit(1); + } +} + +static void kvm_init(PCMachine *s) +{ + int ret, i; + struct sigaction act; + struct kvm_pit_config pit_config; + uint64_t base_addr; + + s->kvm_enabled = FALSE; + s->kvm_fd = open("/dev/kvm", O_RDWR); + if (s->kvm_fd < 0) { + fprintf(stderr, "KVM not available\n"); + return; + } + ret = ioctl(s->kvm_fd, KVM_GET_API_VERSION, 0); + if (ret < 0) { + perror("KVM_GET_API_VERSION"); + exit(1); + } + if (ret != 12) { + fprintf(stderr, "Unsupported KVM version\n"); + close(s->kvm_fd); + s->kvm_fd = -1; + return; + } + s->vm_fd = ioctl(s->kvm_fd, KVM_CREATE_VM, 0); + if (s->vm_fd < 0) { + perror("KVM_CREATE_VM"); + exit(1); + } + + /* just before the BIOS */ + base_addr = 0xfffbc000; + if (ioctl(s->vm_fd, KVM_SET_IDENTITY_MAP_ADDR, &base_addr) < 0) { + perror("KVM_SET_IDENTITY_MAP_ADDR"); + exit(1); + } + + if (ioctl(s->vm_fd, KVM_SET_TSS_ADDR, (long)(base_addr + 0x1000)) < 0) { + perror("KVM_SET_TSS_ADDR"); + exit(1); + } + + if (ioctl(s->vm_fd, KVM_CREATE_IRQCHIP, 0) < 0) { + perror("KVM_CREATE_IRQCHIP"); + exit(1); + } + + memset(&pit_config, 0, sizeof(pit_config)); + pit_config.flags = KVM_PIT_SPEAKER_DUMMY; + if (ioctl(s->vm_fd, KVM_CREATE_PIT2, &pit_config)) { + perror("KVM_CREATE_PIT2"); + exit(1); + } + + s->vcpu_fd = ioctl(s->vm_fd, KVM_CREATE_VCPU, 0); + if (s->vcpu_fd < 0) { + perror("KVM_CREATE_VCPU"); + exit(1); + } + + kvm_set_cpuid(s); + + /* map the kvm_run structure */ + s->kvm_run_size = ioctl(s->kvm_fd, KVM_GET_VCPU_MMAP_SIZE, NULL); + if (s->kvm_run_size < 0) { + perror("KVM_GET_VCPU_MMAP_SIZE"); + exit(1); + } + + s->kvm_run = mmap(NULL, s->kvm_run_size, PROT_READ | PROT_WRITE, + MAP_SHARED, s->vcpu_fd, 0); + if (!s->kvm_run) { + perror("mmap kvm_run"); + exit(1); + } + + for(i = 0; i < 16; i++) { + irq_init(&s->pic_irq[i], kvm_pic_set_irq, s, i); + } + + act.sa_handler = sigalrm_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + sigaction(SIGALRM, &act, NULL); + + s->kvm_enabled = TRUE; + + s->mem_map->register_ram = kvm_register_ram; + s->mem_map->free_ram = kvm_free_ram; + s->mem_map->get_dirty_bits = kvm_get_dirty_bits; + s->mem_map->set_ram_addr = kvm_set_ram_addr; + s->mem_map->opaque = s; +} + +static void kvm_exit_io(PCMachine *s, struct kvm_run *run) +{ + uint8_t *ptr; + int i; + + ptr = (uint8_t *)run + run->io.data_offset; + // printf("port: addr=%04x\n", run->io.port); + + for(i = 0; i < run->io.count; i++) { + if (run->io.direction == KVM_EXIT_IO_OUT) { + switch(run->io.size) { + case 1: + st_port(s, run->io.port, *(uint8_t *)ptr, 0); + break; + case 2: + st_port(s, run->io.port, *(uint16_t *)ptr, 1); + break; + case 4: + st_port(s, run->io.port, *(uint32_t *)ptr, 2); + break; + default: + abort(); + } + } else { + switch(run->io.size) { + case 1: + *(uint8_t *)ptr = ld_port(s, run->io.port, 0); + break; + case 2: + *(uint16_t *)ptr = ld_port(s, run->io.port, 1); + break; + case 4: + *(uint32_t *)ptr = ld_port(s, run->io.port, 2); + break; + default: + abort(); + } + } + ptr += run->io.size; + } +} + +static void kvm_exit_mmio(PCMachine *s, struct kvm_run *run) +{ + uint8_t *data = run->mmio.data; + PhysMemoryRange *pr; + uint64_t addr; + + pr = get_phys_mem_range(s->mem_map, run->mmio.phys_addr); + if (run->mmio.is_write) { + if (!pr || pr->is_ram) + return; + addr = run->mmio.phys_addr - pr->addr; + switch(run->mmio.len) { + case 1: + if (pr->devio_flags & DEVIO_SIZE8) { + pr->write_func(pr->opaque, addr, *(uint8_t *)data, 0); + } + break; + case 2: + if (pr->devio_flags & DEVIO_SIZE16) { + pr->write_func(pr->opaque, addr, *(uint16_t *)data, 1); + } + break; + case 4: + if (pr->devio_flags & DEVIO_SIZE32) { + pr->write_func(pr->opaque, addr, *(uint32_t *)data, 2); + } + break; + case 8: + if (pr->devio_flags & DEVIO_SIZE32) { + pr->write_func(pr->opaque, addr, *(uint32_t *)data, 2); + pr->write_func(pr->opaque, addr + 4, *(uint32_t *)(data + 4), 2); + } + break; + default: + abort(); + } + } else { + if (!pr || pr->is_ram) + goto no_dev; + addr = run->mmio.phys_addr - pr->addr; + switch(run->mmio.len) { + case 1: + if (!(pr->devio_flags & DEVIO_SIZE8)) + goto no_dev; + *(uint8_t *)data = pr->read_func(pr->opaque, addr, 0); + break; + case 2: + if (!(pr->devio_flags & DEVIO_SIZE16)) + goto no_dev; + *(uint16_t *)data = pr->read_func(pr->opaque, addr, 1); + break; + case 4: + if (!(pr->devio_flags & DEVIO_SIZE32)) + goto no_dev; + *(uint32_t *)data = pr->read_func(pr->opaque, addr, 2); + break; + case 8: + if (pr->devio_flags & DEVIO_SIZE32) { + *(uint32_t *)data = + pr->read_func(pr->opaque, addr, 2); + *(uint32_t *)(data + 4) = + pr->read_func(pr->opaque, addr + 4, 2); + } else { + no_dev: + memset(run->mmio.data, 0, run->mmio.len); + } + break; + default: + abort(); + } + + } +} + +static void kvm_exec(PCMachine *s) +{ + struct kvm_run *run = s->kvm_run; + struct itimerval ival; + int ret; + + /* Not efficient but simple: we use a timer to interrupt the + execution after a given time */ + ival.it_interval.tv_sec = 0; + ival.it_interval.tv_usec = 0; + ival.it_value.tv_sec = 0; + ival.it_value.tv_usec = 10 * 1000; /* 10 ms max */ + setitimer(ITIMER_REAL, &ival, NULL); + + ret = ioctl(s->vcpu_fd, KVM_RUN, 0); + if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) { + /* timeout */ + return; + } + perror("KVM_RUN"); + exit(1); + } + // printf("exit=%d\n", run->exit_reason); + switch(run->exit_reason) { + case KVM_EXIT_HLT: + break; + case KVM_EXIT_IO: + kvm_exit_io(s, run); + break; + case KVM_EXIT_MMIO: + kvm_exit_mmio(s, run); + break; + case KVM_EXIT_FAIL_ENTRY: + fprintf(stderr, "KVM_EXIT_FAIL_ENTRY: reason=0x%" PRIx64 "\n", + (uint64_t)run->fail_entry.hardware_entry_failure_reason); +#if 0 + { + struct kvm_regs regs; + if (ioctl(s->vcpu_fd, KVM_GET_REGS, ®s) < 0) { + perror("KVM_SET_REGS"); + exit(1); + } + printf("RIP=%016" PRIx64 "\n", (uint64_t)regs.rip); + } +#endif + exit(1); + case KVM_EXIT_INTERNAL_ERROR: + fprintf(stderr, "KVM_EXIT_INTERNAL_ERROR: suberror=0x%x\n", + (uint32_t)run->internal.suberror); + exit(1); + default: + fprintf(stderr, "KVM: unsupported exit_reason=%d\n", run->exit_reason); + exit(1); + } +} +#endif + +#if defined(EMSCRIPTEN) +/* with Javascript clock_gettime() is not enough precise enough to + have a reliable TSC counter. XXX: increment the cycles during the + power down time */ +static uint64_t cpu_get_tsc(void *opaque) +{ + PCMachine *s = opaque; + uint64_t c; + c = x86_cpu_get_cycles(s->cpu_state); + return c; +} +#else + +#define TSC_FREQ 100000000 + +static uint64_t cpu_get_tsc(void *opaque) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * TSC_FREQ + + (ts.tv_nsec / (1000000000 / TSC_FREQ)); +} +#endif + +static void pc_flush_tlb_write_range(void *opaque, uint8_t *ram_addr, + size_t ram_size) +{ + PCMachine *s = opaque; + x86_cpu_flush_tlb_write_range_ram(s->cpu_state, ram_addr, ram_size); +} + +static VirtMachine *pc_machine_init(const VirtMachineParams *p) +{ + PCMachine *s; + int i, piix3_devfn; + PCIBus *pci_bus; + VIRTIOBusDef vbus_s, *vbus = &vbus_s; + + if (strcmp(p->machine_name, "pc") != 0) { + vm_error("unsupported machine: %s\n", p->machine_name); + return NULL; + } + + assert(p->ram_size >= (1 << 20)); + + s = mallocz(sizeof(*s)); + s->common.vmc = p->vmc; + s->ram_size = p->ram_size; + + s->port_map = phys_mem_map_init(); + s->mem_map = phys_mem_map_init(); + +#ifdef USE_KVM + if (p->accel_enable) { + kvm_init(s); + } +#endif + +#ifdef USE_KVM + if (!s->kvm_enabled) +#endif + { + s->cpu_state = x86_cpu_init(s->mem_map); + x86_cpu_set_get_tsc(s->cpu_state, cpu_get_tsc, s); + x86_cpu_set_port_io(s->cpu_state, ld_port, st_port, s); + + /* needed to handle the RAM dirty bits */ + s->mem_map->opaque = s; + s->mem_map->flush_tlb_write_range = pc_flush_tlb_write_range; + } + + /* set the RAM mapping and leave the VGA addresses empty */ + cpu_register_ram(s->mem_map, 0xc0000, p->ram_size - 0xc0000, 0); + cpu_register_ram(s->mem_map, 0, 0xa0000, 0); + + /* devices */ + cpu_register_device(s->port_map, 0x80, 2, s, port80_read, port80_write, + DEVIO_SIZE8); + cpu_register_device(s->port_map, 0x92, 2, s, port92_read, port92_write, + DEVIO_SIZE8); + + /* setup the bios */ + if (p->files[VM_FILE_BIOS].len > 0) { + int bios_size, bios_size1; + uint8_t *bios_buf, *ptr; + uint32_t bios_addr; + + bios_size = p->files[VM_FILE_BIOS].len; + bios_buf = p->files[VM_FILE_BIOS].buf; + assert((bios_size % 65536) == 0 && bios_size != 0); + bios_addr = -bios_size; + /* at the top of the 4GB memory */ + cpu_register_ram(s->mem_map, bios_addr, bios_size, DEVRAM_FLAG_ROM); + ptr = get_ram_ptr(s, bios_addr); + memcpy(ptr, bios_buf, bios_size); + /* in the lower 1MB memory (currently set as RAM) */ + bios_size1 = min_int(bios_size, 128 * 1024); + ptr = get_ram_ptr(s, 0x100000 - bios_size1); + memcpy(ptr, bios_buf + bios_size - bios_size1, bios_size1); +#ifdef DEBUG_BIOS + cpu_register_device(s->port_map, 0x402, 2, s, + bios_debug_read, bios_debug_write, + DEVIO_SIZE8); +#endif + } + +#ifdef USE_KVM + if (!s->kvm_enabled) +#endif + { + s->pic_state = pic2_init(s->port_map, 0x20, 0xa0, + 0x4d0, 0x4d1, + pic_set_irq_cb, s, + s->pic_irq); + x86_cpu_set_get_hard_intno(s->cpu_state, get_hard_intno_cb, s); + s->pit_state = pit_init(s->port_map, 0x40, 0x61, &s->pic_irq[0], + pit_get_ticks_cb, s); + } + + s->cmos_state = cmos_init(s->port_map, 0x70, &s->pic_irq[8], + p->rtc_local_time); + + /* various cmos data */ + { + int size; + /* memory size */ + size = min_int((s->ram_size - (1 << 20)) >> 10, 65535); + put_le16(s->cmos_state->cmos_data + 0x30, size); + if (s->ram_size >= (16 << 20)) { + size = min_int((s->ram_size - (16 << 20)) >> 16, 65535); + put_le16(s->cmos_state->cmos_data + 0x34, size); + } + s->cmos_state->cmos_data[0x14] = 0x06; /* mouse + FPU present */ + } + + s->i440fx_state = i440fx_init(&pci_bus, &piix3_devfn, s->mem_map, + s->port_map, s->pic_irq); + + s->common.console = p->console; + /* serial console */ + if (0) { + s->serial_state = serial_init(s->port_map, 0x3f8, &s->pic_irq[4], + serial_write_cb, s); + } + + memset(vbus, 0, sizeof(*vbus)); + vbus->pci_bus = pci_bus; + + if (p->console) { + /* virtio console */ + s->common.console_dev = virtio_console_init(vbus, p->console); + } + + /* block devices */ + for(i = 0; i < p->drive_count;) { + const VMDriveEntry *de = &p->tab_drive[i]; + + if (!de->device || !strcmp(de->device, "virtio")) { + virtio_block_init(vbus, p->tab_drive[i].block_dev); + i++; + } else if (!strcmp(de->device, "ide")) { + BlockDevice *tab_bs[2]; + + tab_bs[0] = p->tab_drive[i++].block_dev; + tab_bs[1] = NULL; + if (i < p->drive_count) + tab_bs[1] = p->tab_drive[i++].block_dev; + ide_init(s->port_map, 0x1f0, 0x3f6, &s->pic_irq[14], tab_bs); + piix3_ide_init(pci_bus, piix3_devfn + 1); + } + } + + /* virtio filesystem */ + for(i = 0; i < p->fs_count; i++) { + virtio_9p_init(vbus, p->tab_fs[i].fs_dev, + p->tab_fs[i].tag); + } + + if (p->display_device) { + FBDevice *fb_dev; + + fb_dev = mallocz(sizeof(*fb_dev)); + s->common.fb_dev = fb_dev; + if (!strcmp(p->display_device, "vga")) { + int bios_size; + uint8_t *bios_buf; + bios_size = p->files[VM_FILE_VGA_BIOS].len; + bios_buf = p->files[VM_FILE_VGA_BIOS].buf; + pci_vga_init(pci_bus, fb_dev, p->width, p->height, + bios_buf, bios_size); + } else if (!strcmp(p->display_device, "simplefb")) { + simplefb_init(s->mem_map, + FRAMEBUFFER_BASE_ADDR, + fb_dev, p->width, p->height); + } else { + vm_error("unsupported display device: %s\n", p->display_device); + exit(1); + } + } + + if (p->input_device) { + if (!strcmp(p->input_device, "virtio")) { + s->keyboard_dev = virtio_input_init(vbus, VIRTIO_INPUT_TYPE_KEYBOARD); + + s->mouse_dev = virtio_input_init(vbus, VIRTIO_INPUT_TYPE_TABLET); + } else if (!strcmp(p->input_device, "ps2")) { + s->kbd_state = i8042_init(&s->ps2_kbd, &s->ps2_mouse, + s->port_map, + &s->pic_irq[1], &s->pic_irq[12], 0x60); + /* vmmouse */ + cpu_register_device(s->port_map, 0x5658, 1, s, + vmport_read, vmport_write, + DEVIO_SIZE32); + s->vm_mouse = vmmouse_init(s->ps2_mouse); + } else { + vm_error("unsupported input device: %s\n", p->input_device); + exit(1); + } + } + + /* virtio net device */ + for(i = 0; i < p->eth_count; i++) { + virtio_net_init(vbus, p->tab_eth[i].net); + s->common.net = p->tab_eth[i].net; + } + + if (p->files[VM_FILE_KERNEL].buf) { + copy_kernel(s, p->files[VM_FILE_KERNEL].buf, + p->files[VM_FILE_KERNEL].len, + p->cmdline ? p->cmdline : ""); + } + + return (VirtMachine *)s; +} + +static void pc_machine_end(VirtMachine *s1) +{ + PCMachine *s = (PCMachine *)s1; + /* XXX: free all */ + if (s->cpu_state) { + x86_cpu_end(s->cpu_state); + } + phys_mem_map_end(s->mem_map); + phys_mem_map_end(s->port_map); + free(s); +} + +static void pc_vm_send_key_event(VirtMachine *s1, BOOL is_down, uint16_t key_code) +{ + PCMachine *s = (PCMachine *)s1; + if (s->keyboard_dev) { + virtio_input_send_key_event(s->keyboard_dev, is_down, key_code); + } else if (s->ps2_kbd) { + ps2_put_keycode(s->ps2_kbd, is_down, key_code); + } +} + +static BOOL pc_vm_mouse_is_absolute(VirtMachine *s1) +{ + PCMachine *s = (PCMachine *)s1; + if (s->mouse_dev) { + return TRUE; + } else if (s->vm_mouse) { + return vmmouse_is_absolute(s->vm_mouse); + } else { + return FALSE; + } +} + +static void pc_vm_send_mouse_event(VirtMachine *s1, int dx, int dy, int dz, + unsigned int buttons) +{ + PCMachine *s = (PCMachine *)s1; + if (s->mouse_dev) { + virtio_input_send_mouse_event(s->mouse_dev, dx, dy, dz, buttons); + } else if (s->vm_mouse) { + vmmouse_send_mouse_event(s->vm_mouse, dx, dy, dz, buttons); + } +} + +struct screen_info { +} __attribute__((packed)); + +/* from plex86 (BSD license) */ +struct __attribute__ ((packed)) linux_params { + /* screen_info structure */ + uint8_t orig_x; /* 0x00 */ + uint8_t orig_y; /* 0x01 */ + uint16_t ext_mem_k; /* 0x02 */ + uint16_t orig_video_page; /* 0x04 */ + uint8_t orig_video_mode; /* 0x06 */ + uint8_t orig_video_cols; /* 0x07 */ + uint8_t flags; /* 0x08 */ + uint8_t unused2; /* 0x09 */ + uint16_t orig_video_ega_bx;/* 0x0a */ + uint16_t unused3; /* 0x0c */ + uint8_t orig_video_lines; /* 0x0e */ + uint8_t orig_video_isVGA; /* 0x0f */ + uint16_t orig_video_points;/* 0x10 */ + + /* VESA graphic mode -- linear frame buffer */ + uint16_t lfb_width; /* 0x12 */ + uint16_t lfb_height; /* 0x14 */ + uint16_t lfb_depth; /* 0x16 */ + uint32_t lfb_base; /* 0x18 */ + uint32_t lfb_size; /* 0x1c */ + uint16_t cl_magic, cl_offset; /* 0x20 */ + uint16_t lfb_linelength; /* 0x24 */ + uint8_t red_size; /* 0x26 */ + uint8_t red_pos; /* 0x27 */ + uint8_t green_size; /* 0x28 */ + uint8_t green_pos; /* 0x29 */ + uint8_t blue_size; /* 0x2a */ + uint8_t blue_pos; /* 0x2b */ + uint8_t rsvd_size; /* 0x2c */ + uint8_t rsvd_pos; /* 0x2d */ + uint16_t vesapm_seg; /* 0x2e */ + uint16_t vesapm_off; /* 0x30 */ + uint16_t pages; /* 0x32 */ + uint16_t vesa_attributes; /* 0x34 */ + uint32_t capabilities; /* 0x36 */ + uint32_t ext_lfb_base; /* 0x3a */ + uint8_t _reserved[2]; /* 0x3e */ + + /* 0x040 */ uint8_t apm_bios_info[20]; // struct apm_bios_info + /* 0x054 */ uint8_t pad2[0x80 - 0x54]; + + // Following 2 from 'struct drive_info_struct' in drivers/block/cciss.h. + // Might be truncated? + /* 0x080 */ uint8_t hd0_info[16]; // hd0-disk-parameter from intvector 0x41 + /* 0x090 */ uint8_t hd1_info[16]; // hd1-disk-parameter from intvector 0x46 + + // System description table truncated to 16 bytes + // From 'struct sys_desc_table_struct' in linux/arch/i386/kernel/setup.c. + /* 0x0a0 */ uint16_t sys_description_len; + /* 0x0a2 */ uint8_t sys_description_table[14]; + // [0] machine id + // [1] machine submodel id + // [2] BIOS revision + // [3] bit1: MCA bus + + /* 0x0b0 */ uint8_t pad3[0x1e0 - 0xb0]; + /* 0x1e0 */ uint32_t alt_mem_k; + /* 0x1e4 */ uint8_t pad4[4]; + /* 0x1e8 */ uint8_t e820map_entries; + /* 0x1e9 */ uint8_t eddbuf_entries; // EDD_NR + /* 0x1ea */ uint8_t pad5[0x1f1 - 0x1ea]; + /* 0x1f1 */ uint8_t setup_sects; // size of setup.S, number of sectors + /* 0x1f2 */ uint16_t mount_root_rdonly; // MOUNT_ROOT_RDONLY (if !=0) + /* 0x1f4 */ uint16_t sys_size; // size of compressed kernel-part in the + // (b)zImage-file (in 16 byte units, rounded up) + /* 0x1f6 */ uint16_t swap_dev; // (unused AFAIK) + /* 0x1f8 */ uint16_t ramdisk_flags; + /* 0x1fa */ uint16_t vga_mode; // (old one) + /* 0x1fc */ uint16_t orig_root_dev; // (high=Major, low=minor) + /* 0x1fe */ uint8_t pad6[1]; + /* 0x1ff */ uint8_t aux_device_info; + /* 0x200 */ uint16_t jump_setup; // Jump to start of setup code, + // aka "reserved" field. + /* 0x202 */ uint8_t setup_signature[4]; // Signature for SETUP-header, ="HdrS" + /* 0x206 */ uint16_t header_format_version; // Version number of header format; + /* 0x208 */ uint8_t setup_S_temp0[8]; // Used by setup.S for communication with + // boot loaders, look there. + /* 0x210 */ uint8_t loader_type; + // 0 for old one. + // else 0xTV: + // T=0: LILO + // T=1: Loadlin + // T=2: bootsect-loader + // T=3: SYSLINUX + // T=4: ETHERBOOT + // V=version + /* 0x211 */ uint8_t loadflags; + // bit0 = 1: kernel is loaded high (bzImage) + // bit7 = 1: Heap and pointer (see below) set by boot + // loader. + /* 0x212 */ uint16_t setup_S_temp1; + /* 0x214 */ uint32_t kernel_start; + /* 0x218 */ uint32_t initrd_start; + /* 0x21c */ uint32_t initrd_size; + /* 0x220 */ uint8_t setup_S_temp2[4]; + /* 0x224 */ uint16_t setup_S_heap_end_pointer; + /* 0x226 */ uint16_t pad70; + /* 0x228 */ uint32_t cmd_line_ptr; + /* 0x22c */ uint8_t pad7[0x2d0 - 0x22c]; + + /* 0x2d0 : Int 15, ax=e820 memory map. */ + // (linux/include/asm-i386/e820.h, 'struct e820entry') +#define E820MAX 32 +#define E820_RAM 1 +#define E820_RESERVED 2 +#define E820_ACPI 3 /* usable as RAM once ACPI tables have been read */ +#define E820_NVS 4 + struct { + uint64_t addr; + uint64_t size; + uint32_t type; + } e820map[E820MAX]; + + /* 0x550 */ uint8_t pad8[0x600 - 0x550]; + + // BIOS Enhanced Disk Drive Services. + // (From linux/include/asm-i386/edd.h, 'struct edd_info') + // Each 'struct edd_info is 78 bytes, times a max of 6 structs in array. + /* 0x600 */ uint8_t eddbuf[0x7d4 - 0x600]; + + /* 0x7d4 */ uint8_t pad9[0x800 - 0x7d4]; + /* 0x800 */ uint8_t commandline[0x800]; + + uint64_t gdt_table[4]; +}; + +#define KERNEL_PARAMS_ADDR 0x00090000 + +static void copy_kernel(PCMachine *s, const uint8_t *buf, int buf_len, + const char *cmd_line) +{ + uint8_t *ram_ptr; + int setup_sects, header_len, copy_len, setup_hdr_start, setup_hdr_end; + uint32_t load_address; + struct linux_params *params; + FBDevice *fb_dev; + + if (buf_len < 1024) { + too_small: + fprintf(stderr, "Kernel too small\n"); + exit(1); + } + if (buf[0x1fe] != 0x55 || buf[0x1ff] != 0xaa) { + fprintf(stderr, "Invalid kernel magic\n"); + exit(1); + } + setup_sects = buf[0x1f1]; + if (setup_sects == 0) + setup_sects = 4; + header_len = (setup_sects + 1) * 512; + if (buf_len < header_len) + goto too_small; + if (memcmp(buf + 0x202, "HdrS", 4) != 0) { + fprintf(stderr, "Kernel too old\n"); + exit(1); + } + load_address = 0x100000; /* we don't support older protocols */ + + ram_ptr = get_ram_ptr(s, load_address); + copy_len = buf_len - header_len; + if (copy_len > (s->ram_size - load_address)) { + fprintf(stderr, "Not enough RAM\n"); + exit(1); + } + memcpy(ram_ptr, buf + header_len, copy_len); + + params = (void *)get_ram_ptr(s, KERNEL_PARAMS_ADDR); + + memset(params, 0, sizeof(struct linux_params)); + + /* copy the setup header */ + setup_hdr_start = 0x1f1; + setup_hdr_end = 0x202 + buf[0x201]; + memcpy((uint8_t *)params + setup_hdr_start, buf + setup_hdr_start, + setup_hdr_end - setup_hdr_start); + + strcpy((char *)params->commandline, cmd_line); + + params->mount_root_rdonly = 0; + params->cmd_line_ptr = KERNEL_PARAMS_ADDR + + offsetof(struct linux_params, commandline); + params->alt_mem_k = (s->ram_size / 1024) - 1024; + params->loader_type = 0x01; +#if 0 + if (initrd_size > 0) { + params->initrd_start = INITRD_LOAD_ADDR; + params->initrd_size = initrd_size; + } +#endif + params->orig_video_lines = 0; + params->orig_video_cols = 0; + + fb_dev = s->common.fb_dev; + if (fb_dev) { + + params->orig_video_isVGA = 0x23; /* VIDEO_TYPE_VLFB */ + + params->lfb_depth = 32; + params->red_size = 8; + params->red_pos = 16; + params->green_size = 8; + params->green_pos = 8; + params->blue_size = 8; + params->blue_pos = 0; + params->rsvd_size = 8; + params->rsvd_pos = 24; + + params->lfb_width = fb_dev->width; + params->lfb_height = fb_dev->height; + params->lfb_linelength = fb_dev->stride; + params->lfb_size = fb_dev->fb_size; + params->lfb_base = FRAMEBUFFER_BASE_ADDR; + } + + params->gdt_table[2] = 0x00cf9b000000ffffLL; /* CS */ + params->gdt_table[3] = 0x00cf93000000ffffLL; /* DS */ + +#ifdef USE_KVM + if (s->kvm_enabled) { + struct kvm_sregs sregs; + struct kvm_segment seg; + struct kvm_regs regs; + + /* init flat protected mode */ + + if (ioctl(s->vcpu_fd, KVM_GET_SREGS, &sregs) < 0) { + perror("KVM_GET_SREGS"); + exit(1); + } + + sregs.cr0 |= (1 << 0); /* CR0_PE */ + sregs.gdt.base = KERNEL_PARAMS_ADDR + + offsetof(struct linux_params, gdt_table); + sregs.gdt.limit = sizeof(params->gdt_table) - 1; + + memset(&seg, 0, sizeof(seg)); + seg.limit = 0xffffffff; + seg.present = 1; + seg.db = 1; + seg.s = 1; /* code/data */ + seg.g = 1; /* 4KB granularity */ + + seg.type = 0xb; /* code */ + seg.selector = 2 << 3; + sregs.cs = seg; + + seg.type = 0x3; /* data */ + seg.selector = 3 << 3; + sregs.ds = seg; + sregs.es = seg; + sregs.ss = seg; + sregs.fs = seg; + sregs.gs = seg; + + if (ioctl(s->vcpu_fd, KVM_SET_SREGS, &sregs) < 0) { + perror("KVM_SET_SREGS"); + exit(1); + } + + memset(®s, 0, sizeof(regs)); + regs.rip = load_address; + regs.rsi = KERNEL_PARAMS_ADDR; + regs.rflags = 0x2; + if (ioctl(s->vcpu_fd, KVM_SET_REGS, ®s) < 0) { + perror("KVM_SET_REGS"); + exit(1); + } + } else +#endif + { + int i; + X86CPUSeg sd; + uint32_t val; + val = x86_cpu_get_reg(s->cpu_state, X86_CPU_REG_CR0); + x86_cpu_set_reg(s->cpu_state, X86_CPU_REG_CR0, val | (1 << 0)); + + sd.base = KERNEL_PARAMS_ADDR + + offsetof(struct linux_params, gdt_table); + sd.limit = sizeof(params->gdt_table) - 1; + x86_cpu_set_seg(s->cpu_state, X86_CPU_SEG_GDT, &sd); + sd.sel = 2 << 3; + sd.base = 0; + sd.limit = 0xffffffff; + sd.flags = 0xc09b; + x86_cpu_set_seg(s->cpu_state, X86_CPU_SEG_CS, &sd); + sd.sel = 3 << 3; + sd.flags = 0xc093; + for(i = 0; i < 6; i++) { + if (i != X86_CPU_SEG_CS) { + x86_cpu_set_seg(s->cpu_state, i, &sd); + } + } + + x86_cpu_set_reg(s->cpu_state, X86_CPU_REG_EIP, load_address); + x86_cpu_set_reg(s->cpu_state, 6, KERNEL_PARAMS_ADDR); /* esi */ + } + + /* map PCI interrupts (no BIOS, so we must do it) */ + { + uint8_t elcr[2]; + static const uint8_t pci_irqs[4] = { 9, 10, 11, 12 }; + + i440fx_map_interrupts(s->i440fx_state, elcr, pci_irqs); + /* XXX: KVM support */ + if (s->pic_state) { + pic2_set_elcr(s->pic_state, elcr); + } + } +} + +/* in ms */ +static int pc_machine_get_sleep_duration(VirtMachine *s1, int delay) +{ + PCMachine *s = (PCMachine *)s1; + +#ifdef USE_KVM + if (s->kvm_enabled) { + /* XXX: improve */ + cmos_update_irq(s->cmos_state); + delay = 0; + } else +#endif + { + cmos_update_irq(s->cmos_state); + delay = min_int(delay, pit_update_irq(s->pit_state)); + if (!x86_cpu_get_power_down(s->cpu_state)) + delay = 0; + } + return delay; +} + +static void pc_machine_interp(VirtMachine *s1, int max_exec_cycles) +{ + PCMachine *s = (PCMachine *)s1; +#ifdef USE_KVM + if (s->kvm_enabled) { + kvm_exec(s); + } else +#endif + { + x86_cpu_interp(s->cpu_state, max_exec_cycles); + } +} + +const VirtMachineClass pc_machine_class = { + "pc", + pc_machine_set_defaults, + pc_machine_init, + pc_machine_end, + pc_machine_get_sleep_duration, + pc_machine_interp, + pc_vm_mouse_is_absolute, + pc_vm_send_mouse_event, + pc_vm_send_key_event, +};