From b6b7d72717bc0588c8dbf7888dc58faee1903e7c Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Tue, 22 Jul 2025 02:42:34 +1000 Subject: [PATCH] Fork http://bellard.org/tinyemu --- Changelog | 45 + MIT-LICENSE.txt | 19 + Makefile | 135 ++ Makefile.js | 65 + VERSION | 1 + aes.c | 1321 ++++++++++++++++++ aes.h | 49 + block_net.c | 531 +++++++ build_filelist.c | 311 +++++ cutils.c | 120 ++ cutils.h | 194 +++ fbuf.h | 22 + fs.c | 104 ++ fs.h | 211 +++ fs_disk.c | 659 +++++++++ fs_net.c | 2910 +++++++++++++++++++++++++++++++++++++++ fs_utils.c | 370 +++++ fs_utils.h | 95 ++ fs_wget.c | 625 +++++++++ fs_wget.h | 93 ++ ide.c | 835 +++++++++++ ide.h | 32 + iomem.c | 264 ++++ iomem.h | 148 ++ js/lib.js | 280 ++++ jsemu.c | 349 +++++ json.c | 464 +++++++ json.h | 132 ++ list.h | 94 ++ machine.c | 640 +++++++++ machine.h | 196 +++ netinit.sh | 43 + pci.c | 588 ++++++++ pci.h | 81 ++ pckbd.c | 342 +++++ ps2.c | 489 +++++++ ps2.h | 34 + readme.txt | 204 +++ riscv_cpu.c | 1377 ++++++++++++++++++ riscv_cpu.h | 118 ++ riscv_cpu_fp_template.h | 304 ++++ riscv_cpu_priv.h | 293 ++++ riscv_cpu_template.h | 1739 +++++++++++++++++++++++ riscv_machine.c | 1053 ++++++++++++++ sdl.c | 275 ++++ sha256.c | 341 +++++ sha256.h | 40 + simplefb.c | 127 ++ slirp/bootp.c | 314 +++++ slirp/bootp.h | 122 ++ slirp/cksum.c | 139 ++ slirp/debug.h | 34 + slirp/if.c | 209 +++ slirp/if.h | 25 + slirp/ip.h | 253 ++++ slirp/ip_icmp.c | 351 +++++ slirp/ip_icmp.h | 161 +++ slirp/ip_input.c | 685 +++++++++ slirp/ip_output.c | 172 +++ slirp/libslirp.h | 56 + slirp/main.h | 46 + slirp/mbuf.c | 218 +++ slirp/mbuf.h | 127 ++ slirp/misc.c | 403 ++++++ slirp/misc.h | 73 + slirp/sbuf.c | 181 +++ slirp/sbuf.h | 30 + slirp/slirp.c | 828 +++++++++++ slirp/slirp.h | 313 +++++ slirp/slirp_config.h | 188 +++ slirp/socket.c | 722 ++++++++++ slirp/socket.h | 95 ++ slirp/tcp.h | 164 +++ slirp/tcp_input.c | 1487 ++++++++++++++++++++ slirp/tcp_output.c | 492 +++++++ slirp/tcp_subr.c | 915 ++++++++++++ slirp/tcp_timer.c | 292 ++++ slirp/tcp_timer.h | 127 ++ slirp/tcp_var.h | 161 +++ slirp/tcpip.h | 77 ++ slirp/tftp.h | 43 + slirp/udp.c | 386 ++++++ slirp/udp.h | 86 ++ softfp.c | 87 ++ softfp.h | 181 +++ softfp_template.h | 1129 +++++++++++++++ softfp_template_icvt.h | 171 +++ splitimg.c | 102 ++ temu.c | 835 +++++++++++ vga.c | 804 +++++++++++ virtio.c | 2650 +++++++++++++++++++++++++++++++++++ virtio.h | 146 ++ vmmouse.c | 162 +++ x86_cpu.c | 96 ++ x86_cpu.h | 70 + x86_machine.c | 2569 ++++++++++++++++++++++++++++++++++ 96 files changed, 37739 insertions(+) create mode 100644 Changelog create mode 100644 MIT-LICENSE.txt create mode 100644 Makefile create mode 100644 Makefile.js create mode 100644 VERSION create mode 100644 aes.c create mode 100644 aes.h create mode 100644 block_net.c create mode 100644 build_filelist.c create mode 100644 cutils.c create mode 100644 cutils.h create mode 100644 fbuf.h create mode 100644 fs.c create mode 100644 fs.h create mode 100644 fs_disk.c create mode 100644 fs_net.c create mode 100644 fs_utils.c create mode 100644 fs_utils.h create mode 100644 fs_wget.c create mode 100644 fs_wget.h create mode 100644 ide.c create mode 100644 ide.h create mode 100644 iomem.c create mode 100644 iomem.h create mode 100644 js/lib.js create mode 100644 jsemu.c create mode 100644 json.c create mode 100644 json.h create mode 100644 list.h create mode 100644 machine.c create mode 100644 machine.h create mode 100755 netinit.sh create mode 100644 pci.c create mode 100644 pci.h create mode 100644 pckbd.c create mode 100644 ps2.c create mode 100644 ps2.h create mode 100644 readme.txt create mode 100644 riscv_cpu.c create mode 100644 riscv_cpu.h create mode 100644 riscv_cpu_fp_template.h create mode 100644 riscv_cpu_priv.h create mode 100644 riscv_cpu_template.h create mode 100644 riscv_machine.c create mode 100644 sdl.c create mode 100644 sha256.c create mode 100644 sha256.h create mode 100644 simplefb.c create mode 100644 slirp/bootp.c create mode 100644 slirp/bootp.h create mode 100644 slirp/cksum.c create mode 100644 slirp/debug.h create mode 100644 slirp/if.c create mode 100644 slirp/if.h create mode 100644 slirp/ip.h create mode 100644 slirp/ip_icmp.c create mode 100644 slirp/ip_icmp.h create mode 100644 slirp/ip_input.c create mode 100644 slirp/ip_output.c create mode 100644 slirp/libslirp.h create mode 100644 slirp/main.h create mode 100644 slirp/mbuf.c create mode 100644 slirp/mbuf.h create mode 100644 slirp/misc.c create mode 100644 slirp/misc.h create mode 100644 slirp/sbuf.c create mode 100644 slirp/sbuf.h create mode 100644 slirp/slirp.c create mode 100644 slirp/slirp.h create mode 100644 slirp/slirp_config.h create mode 100644 slirp/socket.c create mode 100644 slirp/socket.h create mode 100644 slirp/tcp.h create mode 100644 slirp/tcp_input.c create mode 100644 slirp/tcp_output.c create mode 100644 slirp/tcp_subr.c create mode 100644 slirp/tcp_timer.c create mode 100644 slirp/tcp_timer.h create mode 100644 slirp/tcp_var.h create mode 100644 slirp/tcpip.h create mode 100644 slirp/tftp.h create mode 100644 slirp/udp.c create mode 100644 slirp/udp.h create mode 100644 softfp.c create mode 100644 softfp.h create mode 100644 softfp_template.h create mode 100644 softfp_template_icvt.h create mode 100644 splitimg.c create mode 100644 temu.c create mode 100644 vga.c create mode 100644 virtio.c create mode 100644 virtio.h create mode 100644 vmmouse.c create mode 100644 x86_cpu.c create mode 100644 x86_cpu.h create mode 100644 x86_machine.c 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, +};