From 317426f5bfa7dbe320ad7ef791fcb9017e2cde9b Mon Sep 17 00:00:00 2001 From: Bruce Leidl Date: Mon, 16 Oct 2017 02:36:00 +0000 Subject: [PATCH] Initial commit --- Cargo.lock | 31 + Cargo.toml | 9 + kernel/Makefile | 127 + kernel/config | 2181 +++++++++++++++++ kernel/init/Makefile | 2 + kernel/init/init.c | 137 ++ kernel/keys/gregkh.key | 1162 +++++++++ kernel/keys/minipli.key | 88 + kernel/keys/torvalds.key | 1265 ++++++++++ ...ynchronize_rcu-on-single-CPU-systems.patch | 34 + ...ynchronize_rcu-if-there-is-no-old-op.patch | 38 + kernel/patches/0005-vmstats-wakeups.patch | 28 + kernel/patches/0006-pci-probe.patch | 123 + kernel/patches/0007-cgroup.patch | 107 + ...0008-smpboot-reuse-timer-calibration.patch | 45 + kernel/patches/0009-perf.patch | 28 + ...010-pci-probe-identify-known-devices.patch | 190 ++ ...1-init-no-wait-for-the-known-devices.patch | 39 + kernel/patches/0012-ksm-wakeups.patch | 32 + ...ing-user.-attributes-on-symlinks-by-.patch | 56 + ...5-crypto-allow-testmgr-to-be-skipped.patch | 25 + .../patches/0016-silence-Power-down-msg.patch | 25 + ...s-9p-fix-create-unlink-getattr-idiom.patch | 131 + kernel/patches/0018-rdrand.patch | 24 + kernel/patches/0019-reboot.patch | 34 + kernel/patches/0020-no-early-modprobe.patch | 25 + ...w-restart-information-using-info-log.patch | 28 + kernel/patches/0023-virtio-wayland.patch | 1412 +++++++++++ kernel/v4.9.56.sha256 | 2 + src/devices/mod.rs | 9 + src/devices/rtc.rs | 116 + src/devices/serial.rs | 313 +++ src/devices/virtio_9p/commands.rs | 404 +++ src/devices/virtio_9p/fid.rs | 230 ++ src/devices/virtio_9p/filesystem.rs | 412 ++++ src/devices/virtio_9p/mod.rs | 90 + src/devices/virtio_9p/pdu.rs | 322 +++ src/devices/virtio_9p/readdir.rs | 131 + src/devices/virtio_rng.rs | 45 + src/devices/virtio_serial.rs | 226 ++ src/kvm/ioctl.rs | 612 +++++ src/kvm/mod.rs | 223 ++ src/main.rs | 63 + src/memory/address.rs | 79 + src/memory/mmap.rs | 172 ++ src/memory/mod.rs | 13 + src/memory/ram.rs | 150 ++ src/system/ioctl.rs | 71 + src/system/mod.rs | 2 + src/virtio/bus.rs | 149 ++ src/virtio/chain.rs | 269 ++ src/virtio/config.rs | 132 + src/virtio/consts.rs | 120 + src/virtio/device.rs | 228 ++ src/virtio/eventfd.rs | 87 + src/virtio/mod.rs | 30 + src/virtio/pci.rs | 436 ++++ src/virtio/virtqueue.rs | 166 ++ src/virtio/vring.rs | 388 +++ src/vm/error.rs | 170 ++ src/vm/io.rs | 254 ++ src/vm/kernel_cmdline.rs | 101 + src/vm/mod.rs | 142 ++ src/vm/run.rs | 203 ++ src/vm/setup/cpu.rs | 198 ++ src/vm/setup/kernel.rs | 118 + src/vm/setup/mod.rs | 3 + src/vm/setup/mptable.rs | 214 ++ 68 files changed, 14519 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 kernel/Makefile create mode 100644 kernel/config create mode 100644 kernel/init/Makefile create mode 100644 kernel/init/init.c create mode 100644 kernel/keys/gregkh.key create mode 100644 kernel/keys/minipli.key create mode 100644 kernel/keys/torvalds.key create mode 100644 kernel/patches/0002-cpuidle-skip-synchronize_rcu-on-single-CPU-systems.patch create mode 100644 kernel/patches/0003-sysrq-skip-synchronize_rcu-if-there-is-no-old-op.patch create mode 100644 kernel/patches/0005-vmstats-wakeups.patch create mode 100644 kernel/patches/0006-pci-probe.patch create mode 100644 kernel/patches/0007-cgroup.patch create mode 100644 kernel/patches/0008-smpboot-reuse-timer-calibration.patch create mode 100644 kernel/patches/0009-perf.patch create mode 100644 kernel/patches/0010-pci-probe-identify-known-devices.patch create mode 100644 kernel/patches/0011-init-no-wait-for-the-known-devices.patch create mode 100644 kernel/patches/0012-ksm-wakeups.patch create mode 100644 kernel/patches/0014-xattr-allow-setting-user.-attributes-on-symlinks-by-.patch create mode 100644 kernel/patches/0015-crypto-allow-testmgr-to-be-skipped.patch create mode 100644 kernel/patches/0016-silence-Power-down-msg.patch create mode 100644 kernel/patches/0017-fs-9p-fix-create-unlink-getattr-idiom.patch create mode 100644 kernel/patches/0018-rdrand.patch create mode 100644 kernel/patches/0019-reboot.patch create mode 100644 kernel/patches/0020-no-early-modprobe.patch create mode 100644 kernel/patches/0022-Show-restart-information-using-info-log.patch create mode 100644 kernel/patches/0023-virtio-wayland.patch create mode 100644 kernel/v4.9.56.sha256 create mode 100644 src/devices/mod.rs create mode 100644 src/devices/rtc.rs create mode 100644 src/devices/serial.rs create mode 100644 src/devices/virtio_9p/commands.rs create mode 100644 src/devices/virtio_9p/fid.rs create mode 100644 src/devices/virtio_9p/filesystem.rs create mode 100644 src/devices/virtio_9p/mod.rs create mode 100644 src/devices/virtio_9p/pdu.rs create mode 100644 src/devices/virtio_9p/readdir.rs create mode 100644 src/devices/virtio_rng.rs create mode 100644 src/devices/virtio_serial.rs create mode 100644 src/kvm/ioctl.rs create mode 100644 src/kvm/mod.rs create mode 100644 src/main.rs create mode 100644 src/memory/address.rs create mode 100644 src/memory/mmap.rs create mode 100644 src/memory/mod.rs create mode 100644 src/memory/ram.rs create mode 100644 src/system/ioctl.rs create mode 100644 src/system/mod.rs create mode 100644 src/virtio/bus.rs create mode 100644 src/virtio/chain.rs create mode 100644 src/virtio/config.rs create mode 100644 src/virtio/consts.rs create mode 100644 src/virtio/device.rs create mode 100644 src/virtio/eventfd.rs create mode 100644 src/virtio/mod.rs create mode 100644 src/virtio/pci.rs create mode 100644 src/virtio/virtqueue.rs create mode 100644 src/virtio/vring.rs create mode 100644 src/vm/error.rs create mode 100644 src/vm/io.rs create mode 100644 src/vm/kernel_cmdline.rs create mode 100644 src/vm/mod.rs create mode 100644 src/vm/run.rs create mode 100644 src/vm/setup/cpu.rs create mode 100644 src/vm/setup/kernel.rs create mode 100644 src/vm/setup/mod.rs create mode 100644 src/vm/setup/mptable.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..adab3f4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,31 @@ +[root] +name = "pH" +version = "0.1.0" +dependencies = [ + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "termios" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d" +"checksum libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "56cce3130fd040c28df6f495c8492e5ec5808fb4c9093c310df02b0c8f030148" +"checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8247bf8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pH" +version = "0.1.0" +authors = ["Bruce Leidl "] + +[dependencies] +byteorder="1.0.0" +libc = "*" +termios = "0.2.2" diff --git a/kernel/Makefile b/kernel/Makefile new file mode 100644 index 0000000..0481634 --- /dev/null +++ b/kernel/Makefile @@ -0,0 +1,127 @@ + +LINUX_VERSION = 4.9.56 +MINIPLI_TAG = 20171013093040 + + +MINIPLI_VERSION = v$(LINUX_VERSION)-unofficial_grsec +MINIPLI_PATH = linux-unofficial_grsec/releases/download/$(MINIPLI_VERSION) +GRSEC_DIFF = $(MINIPLI_VERSION)-$(MINIPLI_TAG).diff +GRSEC_DOWNLOAD = https://github.com/minipli/$(MINIPLI_PATH)/$(GRSEC_DIFF) + +KERNEL_VERSION = linux-$(LINUX_VERSION) +KERNEL_DOWNLOAD = https://cdn.kernel.org/pub/linux/kernel/v4.x/$(KERNEL_VERSION) + + +WGET = wget +SHA256 = sha256sum +TAR = tar +PATCH = patch +LN = ln +CP = cp +MV = mv +RM = rm +RMDIR = rmdir +MKDIR = mkdir +TOUCH = touch +GPG = gpg +TOUCH = touch +UNXZ = unxz + +PWD = $(shell pwd) + +PATCH_FILES := $(shell find patches/ -name "00*.patch" | sort) +PATCHES = $(patsubst %.patch,build/.%.done,$(PATCH_FILES)) + + +GRSEC_DL_PATH = downloads/$(GRSEC_DIFF) +KERNEL_DL_PATH = downloads/$(KERNEL_VERSION).tar +KERNEL_BUILD_PATH = build/$(KERNEL_VERSION) +KERNEL_UNPACK_PATH = build/unpack/$(KERNEL_VERSION) +KERNEL_BUILD_OUTPUT = $(KERNEL_BUILD_PATH)/vmlinux + +PH_LINUX = ph_linux +PH_LINUX_VER = $(PH_LINUX)-$(KERNEL_VERSION) + +GPGTMP = verify-tmp/ +GPGOPTS = --homedir $(GPGTMP) --trust-model direct --no-autostart +GPG_IMPORT = $(GPG) $(GPGOPTS) --import +GPG_VERIFY = $(GPG) $(GPGOPTS) --verify + +SHA256_FILE = v$(KERNEL_VERSION).sha256 + +SHA256_SENTINEL = build/.sha256-verififed + +.NOTPARALLEL: + +.PHONY: all gen-sha256 + +all: $(KERNEL_BUILD_OUTPUT) + +$(GRSEC_DL_PATH): + cd downloads; $(WGET) $(GRSEC_DOWNLOAD) + cd downloads; $(WGET) $(GRSEC_DOWNLOAD).sig + +$(KERNEL_DL_PATH): + cd downloads; $(WGET) $(KERNEL_DOWNLOAD).tar.xz + cd downloads; $(UNXZ) $(KERNEL_VERSION).tar.xz + cd downloads; $(WGET) $(KERNEL_DOWNLOAD).tar.sign + + +$(KERNEL_BUILD_PATH): $(KERNEL_UNPACK_PATH) + $(PATCH) -p1 -d $(KERNEL_UNPACK_PATH) < $(GRSEC_DL_PATH) + $(CP) config $(KERNEL_UNPACK_PATH)/.config + $(MV) build/unpack/$(KERNEL_VERSION) build/ + $(MKDIR) $(KERNEL_UNPACK_PATH) + $(MKDIR) build/.patches + +$(KERNEL_UNPACK_PATH): $(KERNEL_DL_PATH) $(SHA256_SENTINEL) + $(RM) -rf build/.unpack + $(MKDIR) -p build/.unpack + $(TAR) -C build/.unpack -xvf downloads/$(KERNEL_VERSION).tar + $(MV) build/.unpack build/unpack + + +$(PATCHES): build/.%.done : | $(KERNEL_BUILD_PATH) + $(PATCH) -p1 -d build/$(KERNEL_VERSION) < $*.patch + $(TOUCH) $@ + +$(PH_LINUX_VER): $(KERNEL_BUILD_OUTPUT) + $(RM) $(PH_LINUX) + $(CP) $(KERNEL_BUILD_OUTPUT) $(PH_LINUX_VER) + $(LN) $(PWD)/$(PH_LINUX_VER) $(PH_LINUX) + + +$(KERNEL_BUILD_OUTPUT): $(PATCHES) + $(MAKE) -C build/$(KERNEL_VERSION) -j 4 + +clean: + $(RM) -rf $(KERNEL_BUILD_PATH) build/unpack build/.unpack build/.patches $(SHA256_SENTINEL) + +$(SHA256_SENTINEL): + $(SHA256) -c v$(LINUX_VERSION).sha256 + $(TOUCH) $@ + + +gen-sha256: $(SHA256_FILE) + +$(GRSEC_DL_PATH).verify: $(GRSEC_DL_PATH) + rm -rf $(GPGTMP) + $(MKDIR) -m 0700 $(GPGTMP) + $(GPG_IMPORT) < keys/minipli.key + $(GPG_VERIFY) downloads/$(GRSEC_DIFF).sig $(GRSEC_DL_PATH) 2> .out + mv .out $(GRSEC_DL_PATH).verify + rm -rf $(GPGTMP) + +$(KERNEL_DL_PATH).verify: $(KERNEL_DL_PATH) + rm -rf $(GPGTMP) + $(MKDIR) -m 0700 $(GPGTMP) + $(GPG_IMPORT) < keys/torvalds.key + $(GPG_IMPORT) < keys/gregkh.key + $(GPG_VERIFY) downloads/$(KERNEL_VERSION).tar.sign $(KERNEL_DL_PATH) 2> .out + mv .out $(KERNEL_DL_PATH).verify + rm -rf $(GPGTMP) + + +$(SHA256_FILE): $(KERNEL_DL_PATH).verify $(GRSEC_DL_PATH).verify + $(SHA256) $(KERNEL_DL_PATH) $(GRSEC_DL_PATH) > v$(LINUX_VERSION).sha256 + diff --git a/kernel/config b/kernel/config new file mode 100644 index 0000000..0efd560 --- /dev/null +++ b/kernel/config @@ -0,0 +1,2181 @@ +# +# Automatically generated file; DO NOT EDIT. +# Linux/x86 4.9.56 Kernel Configuration +# +CONFIG_64BIT=y +CONFIG_X86_64=y +CONFIG_X86=y +CONFIG_INSTRUCTION_DECODER=y +CONFIG_OUTPUT_FORMAT="elf64-x86-64" +CONFIG_ARCH_DEFCONFIG="arch/x86/configs/x86_64_defconfig" +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_MMU=y +CONFIG_ARCH_MMAP_RND_BITS_MIN=27 +CONFIG_ARCH_MMAP_RND_BITS_MAX=27 +CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=8 +CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=16 +CONFIG_NEED_DMA_MAP_STATE=y +CONFIG_NEED_SG_DMA_LENGTH=y +CONFIG_GENERIC_BUG=y +CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y +CONFIG_GENERIC_HWEIGHT=y +CONFIG_RWSEM_XCHGADD_ALGORITHM=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ARCH_HAS_CPU_RELAX=y +CONFIG_ARCH_HAS_CACHE_LINE_SIZE=y +CONFIG_HAVE_SETUP_PER_CPU_AREA=y +CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y +CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y +CONFIG_ARCH_HIBERNATION_POSSIBLE=y +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y +CONFIG_ARCH_WANT_GENERAL_HUGETLB=y +CONFIG_ZONE_DMA32=y +CONFIG_AUDIT_ARCH=y +CONFIG_ARCH_SUPPORTS_OPTIMIZED_INLINING=y +CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y +CONFIG_X86_64_SMP=y +CONFIG_ARCH_SUPPORTS_UPROBES=y +CONFIG_FIX_EARLYCON_MEM=y +CONFIG_DEBUG_RODATA=y +CONFIG_PGTABLE_LEVELS=4 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" +CONFIG_IRQ_WORK=y +CONFIG_BUILDTIME_EXTABLE_SORT=y +CONFIG_THREAD_INFO_IN_TASK=y + +# +# General setup +# +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_CROSS_COMPILE="" +# CONFIG_COMPILE_TEST is not set +CONFIG_LOCALVERSION="" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_HAVE_KERNEL_GZIP=y +CONFIG_HAVE_KERNEL_BZIP2=y +CONFIG_HAVE_KERNEL_LZMA=y +CONFIG_HAVE_KERNEL_XZ=y +CONFIG_HAVE_KERNEL_LZO=y +CONFIG_HAVE_KERNEL_LZ4=y +# CONFIG_KERNEL_GZIP is not set +# CONFIG_KERNEL_BZIP2 is not set +# CONFIG_KERNEL_LZMA is not set +# CONFIG_KERNEL_XZ is not set +# CONFIG_KERNEL_LZO is not set +CONFIG_KERNEL_LZ4=y +CONFIG_DEFAULT_HOSTNAME="pH" +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +CONFIG_POSIX_MQUEUE=y +CONFIG_POSIX_MQUEUE_SYSCTL=y +# CONFIG_CROSS_MEMORY_ATTACH is not set +# CONFIG_FHANDLE is not set +CONFIG_AUDIT=y +CONFIG_HAVE_ARCH_AUDITSYSCALL=y +CONFIG_AUDITSYSCALL=y +CONFIG_AUDIT_WATCH=y +CONFIG_AUDIT_TREE=y + +# +# IRQ subsystem +# +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_GENERIC_IRQ_SHOW=y +CONFIG_GENERIC_PENDING_IRQ=y +CONFIG_IRQ_DOMAIN=y +CONFIG_IRQ_DOMAIN_HIERARCHY=y +CONFIG_IRQ_FORCED_THREADING=y +CONFIG_SPARSE_IRQ=y +CONFIG_CLOCKSOURCE_WATCHDOG=y +CONFIG_ARCH_CLOCKSOURCE_DATA=y +CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE=y +CONFIG_GENERIC_TIME_VSYSCALL=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y +CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST=y +CONFIG_GENERIC_CMOS_UPDATE=y + +# +# Timers subsystem +# +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ_COMMON=y +# CONFIG_HZ_PERIODIC is not set +CONFIG_NO_HZ_IDLE=y +# CONFIG_NO_HZ_FULL is not set +# CONFIG_NO_HZ is not set +CONFIG_HIGH_RES_TIMERS=y + +# +# CPU/Task time and stats accounting +# +CONFIG_TICK_CPU_ACCOUNTING=y +# CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set +# CONFIG_IRQ_TIME_ACCOUNTING is not set +# CONFIG_BSD_PROCESS_ACCT is not set +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +# CONFIG_TASK_XACCT is not set + +# +# RCU Subsystem +# +CONFIG_TREE_RCU=y +# CONFIG_RCU_EXPERT is not set +CONFIG_SRCU=y +# CONFIG_TASKS_RCU is not set +CONFIG_RCU_STALL_COMMON=y +# CONFIG_RCU_EXPEDITE_BOOT is not set +# CONFIG_BUILD_BIN2C is not set +# CONFIG_IKCONFIG is not set +CONFIG_LOG_BUF_SHIFT=17 +CONFIG_LOG_CPU_MAX_BUF_SHIFT=12 +CONFIG_NMI_LOG_BUF_SHIFT=13 +CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y +CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y +CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y +CONFIG_CGROUPS=y +CONFIG_PAGE_COUNTER=y +CONFIG_MEMCG=y +# CONFIG_MEMCG_SWAP is not set +# CONFIG_BLK_CGROUP is not set +CONFIG_CGROUP_SCHED=y +CONFIG_FAIR_GROUP_SCHED=y +# CONFIG_CFS_BANDWIDTH is not set +# CONFIG_RT_GROUP_SCHED is not set +# CONFIG_CGROUP_PIDS is not set +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_HUGETLB=y +CONFIG_CPUSETS=y +CONFIG_PROC_PID_CPUSET=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +# CONFIG_CGROUP_DEBUG is not set +CONFIG_NAMESPACES=y +CONFIG_UTS_NS=y +CONFIG_IPC_NS=y +# CONFIG_USER_NS is not set +CONFIG_PID_NS=y +CONFIG_NET_NS=y +CONFIG_SCHED_AUTOGROUP=y +# CONFIG_SYSFS_DEPRECATED is not set +CONFIG_RELAY=y +# CONFIG_BLK_DEV_INITRD is not set +CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y +# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set +CONFIG_SYSCTL=y +CONFIG_ANON_INODES=y +CONFIG_SYSCTL_EXCEPTION_TRACE=y +CONFIG_HAVE_PCSPKR_PLATFORM=y +CONFIG_BPF=y +CONFIG_EXPERT=y +CONFIG_MULTIUSER=y +# CONFIG_SGETMASK_SYSCALL is not set +# CONFIG_SYSFS_SYSCALL is not set +# CONFIG_SYSCTL_SYSCALL is not set +CONFIG_KALLSYMS=y +# CONFIG_KALLSYMS_ALL is not set +CONFIG_KALLSYMS_ABSOLUTE_PERCPU=y +CONFIG_KALLSYMS_BASE_RELATIVE=y +CONFIG_PRINTK=y +CONFIG_PRINTK_NMI=y +CONFIG_BUG=y +CONFIG_ELF_CORE=y +# CONFIG_PCSPKR_PLATFORM is not set +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_TIMERFD=y +CONFIG_EVENTFD=y +# CONFIG_BPF_SYSCALL is not set +CONFIG_SHMEM=y +CONFIG_AIO=y +CONFIG_ADVISE_SYSCALLS=y +# CONFIG_PCI_QUIRKS is not set +CONFIG_MEMBARRIER=y +# CONFIG_EMBEDDED is not set +CONFIG_HAVE_PERF_EVENTS=y + +# +# Kernel Performance Events And Counters +# +CONFIG_PERF_EVENTS=y +# CONFIG_DEBUG_PERF_USE_VMALLOC is not set +# CONFIG_VM_EVENT_COUNTERS is not set +CONFIG_SLUB_DEBUG=y +# CONFIG_COMPAT_BRK is not set +# CONFIG_SLAB is not set +CONFIG_SLUB=y +# CONFIG_SLOB is not set +CONFIG_SLAB_FREELIST_RANDOM=y +CONFIG_SLUB_CPU_PARTIAL=y +# CONFIG_SYSTEM_DATA_VERIFICATION is not set +# CONFIG_PROFILING is not set +CONFIG_HAVE_OPROFILE=y +CONFIG_OPROFILE_NMI_TIMER=y +CONFIG_JUMP_LABEL=y +# CONFIG_STATIC_KEYS_SELFTEST is not set +# CONFIG_UPROBES is not set +# CONFIG_HAVE_64BIT_ALIGNED_ACCESS is not set +CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y +CONFIG_ARCH_USE_BUILTIN_BSWAP=y +CONFIG_HAVE_IOREMAP_PROT=y +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_OPTPROBES=y +CONFIG_HAVE_KPROBES_ON_FTRACE=y +CONFIG_HAVE_NMI=y +CONFIG_HAVE_ARCH_TRACEHOOK=y +CONFIG_HAVE_DMA_CONTIGUOUS=y +CONFIG_GENERIC_SMP_IDLE_THREAD=y +CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y +CONFIG_HAVE_DMA_API_DEBUG=y +CONFIG_HAVE_HW_BREAKPOINT=y +CONFIG_HAVE_MIXED_BREAKPOINTS_REGS=y +CONFIG_HAVE_USER_RETURN_NOTIFIER=y +CONFIG_HAVE_PERF_EVENTS_NMI=y +CONFIG_HAVE_PERF_REGS=y +CONFIG_HAVE_PERF_USER_STACK_DUMP=y +CONFIG_HAVE_ARCH_JUMP_LABEL=y +CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG=y +CONFIG_HAVE_ALIGNED_STRUCT_PAGE=y +CONFIG_HAVE_CMPXCHG_LOCAL=y +CONFIG_HAVE_CMPXCHG_DOUBLE=y +CONFIG_HAVE_ARCH_SECCOMP_FILTER=y +CONFIG_SECCOMP_FILTER=y +CONFIG_HAVE_GCC_PLUGINS=y +CONFIG_GCC_PLUGINS=y +# CONFIG_GCC_PLUGIN_CYC_COMPLEXITY is not set +CONFIG_GCC_PLUGIN_LATENT_ENTROPY=y +CONFIG_HAVE_CC_STACKPROTECTOR=y +CONFIG_CC_STACKPROTECTOR=y +# CONFIG_CC_STACKPROTECTOR_NONE is not set +CONFIG_CC_STACKPROTECTOR_REGULAR=y +# CONFIG_CC_STACKPROTECTOR_STRONG is not set +CONFIG_HAVE_ARCH_WITHIN_STACK_FRAMES=y +CONFIG_HAVE_CONTEXT_TRACKING=y +CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y +CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y +CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y +CONFIG_HAVE_ARCH_HUGE_VMAP=y +CONFIG_HAVE_ARCH_SOFT_DIRTY=y +CONFIG_MODULES_USE_ELF_RELA=y +CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK=y +CONFIG_ARCH_HAS_ELF_RANDOMIZE=y +CONFIG_HAVE_ARCH_MMAP_RND_BITS=y +CONFIG_HAVE_EXIT_THREAD=y +CONFIG_ARCH_MMAP_RND_BITS=27 +CONFIG_HAVE_COPY_THREAD_TLS=y +CONFIG_HAVE_STACK_VALIDATION=y +# CONFIG_HAVE_ARCH_HASH is not set +# CONFIG_ISA_BUS_API is not set +# CONFIG_CPU_NO_EFFICIENT_FFS is not set +CONFIG_HAVE_ARCH_VMAP_STACK=y + +# +# GCOV-based kernel profiling +# +CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y +# CONFIG_HAVE_GENERIC_DMA_COHERENT is not set +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +CONFIG_BASE_SMALL=0 +# CONFIG_MODULES is not set +CONFIG_MODULES_TREE_LOOKUP=y +CONFIG_BLOCK=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_BSGLIB is not set +# CONFIG_BLK_DEV_INTEGRITY is not set +# CONFIG_BLK_CMDLINE_PARSER is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_EFI_PARTITION=y +CONFIG_BLK_MQ_PCI=y + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_PADATA=y +CONFIG_INLINE_SPIN_UNLOCK_IRQ=y +CONFIG_INLINE_READ_UNLOCK=y +CONFIG_INLINE_READ_UNLOCK_IRQ=y +CONFIG_INLINE_WRITE_UNLOCK=y +CONFIG_INLINE_WRITE_UNLOCK_IRQ=y +CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y +CONFIG_MUTEX_SPIN_ON_OWNER=y +CONFIG_RWSEM_SPIN_ON_OWNER=y +CONFIG_LOCK_SPIN_ON_OWNER=y +CONFIG_ARCH_USE_QUEUED_SPINLOCKS=y +CONFIG_QUEUED_SPINLOCKS=y +CONFIG_ARCH_USE_QUEUED_RWLOCKS=y +CONFIG_QUEUED_RWLOCKS=y +CONFIG_FREEZER=y + +# +# Processor type and features +# +# CONFIG_ZONE_DMA is not set +CONFIG_SMP=y +CONFIG_X86_FEATURE_NAMES=y +CONFIG_X86_FAST_FEATURE_TESTS=y +# CONFIG_X86_X2APIC is not set +CONFIG_X86_MPPARSE=y +# CONFIG_GOLDFISH is not set +# CONFIG_X86_EXTENDED_PLATFORM is not set +CONFIG_IOSF_MBI=y +CONFIG_SCHED_OMIT_FRAME_POINTER=y +CONFIG_HYPERVISOR_GUEST=y +CONFIG_PARAVIRT=y +# CONFIG_PARAVIRT_DEBUG is not set +CONFIG_PARAVIRT_SPINLOCKS=y +# CONFIG_XEN is not set +CONFIG_KVM_GUEST=y +# CONFIG_PARAVIRT_TIME_ACCOUNTING is not set +CONFIG_PARAVIRT_CLOCK=y +CONFIG_NO_BOOTMEM=y +# CONFIG_MK8 is not set +# CONFIG_MPSC is not set +CONFIG_MCORE2=y +# CONFIG_MATOM is not set +# CONFIG_GENERIC_CPU is not set +CONFIG_X86_INTERNODE_CACHE_SHIFT=6 +CONFIG_X86_L1_CACHE_SHIFT=6 +CONFIG_X86_ALIGNMENT_16=y +CONFIG_X86_INTEL_USERCOPY=y +CONFIG_X86_USE_PPRO_CHECKSUM=y +CONFIG_X86_P6_NOP=y +CONFIG_X86_TSC=y +CONFIG_X86_CMPXCHG64=y +CONFIG_X86_CMOV=y +CONFIG_X86_MINIMUM_CPU_FAMILY=64 +CONFIG_X86_DEBUGCTLMSR=y +# CONFIG_PROCESSOR_SELECT is not set +CONFIG_CPU_SUP_INTEL=y +CONFIG_CPU_SUP_AMD=y +CONFIG_CPU_SUP_CENTAUR=y +CONFIG_HPET_TIMER=y +CONFIG_HPET_EMULATE_RTC=y +# CONFIG_DMI is not set +# CONFIG_GART_IOMMU is not set +# CONFIG_CALGARY_IOMMU is not set +CONFIG_SWIOTLB=y +CONFIG_IOMMU_HELPER=y +# CONFIG_MAXSMP is not set +CONFIG_NR_CPUS=512 +# CONFIG_SCHED_SMT is not set +CONFIG_SCHED_MC=y +# CONFIG_PREEMPT_NONE is not set +CONFIG_PREEMPT_VOLUNTARY=y +# CONFIG_PREEMPT is not set +CONFIG_X86_LOCAL_APIC=y +CONFIG_X86_IO_APIC=y +# CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS is not set +# CONFIG_X86_MCE is not set + +# +# Performance monitoring +# +# CONFIG_PERF_EVENTS_INTEL_UNCORE is not set +# CONFIG_PERF_EVENTS_INTEL_RAPL is not set +# CONFIG_PERF_EVENTS_INTEL_CSTATE is not set +# CONFIG_PERF_EVENTS_AMD_POWER is not set +# CONFIG_VM86 is not set +# CONFIG_X86_VSYSCALL_EMULATION is not set +# CONFIG_I8K is not set +# CONFIG_MICROCODE is not set +# CONFIG_X86_MSR is not set +# CONFIG_X86_CPUID is not set +CONFIG_ARCH_PHYS_ADDR_T_64BIT=y +CONFIG_ARCH_DMA_ADDR_T_64BIT=y +CONFIG_X86_DIRECT_GBPAGES=y +# CONFIG_NUMA is not set +CONFIG_ARCH_SPARSEMEM_ENABLE=y +CONFIG_ARCH_SPARSEMEM_DEFAULT=y +CONFIG_ARCH_SELECT_MEMORY_MODEL=y +CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_SPARSEMEM_MANUAL=y +CONFIG_SPARSEMEM=y +CONFIG_HAVE_MEMORY_PRESENT=y +CONFIG_SPARSEMEM_EXTREME=y +CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y +CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER=y +CONFIG_SPARSEMEM_VMEMMAP=y +CONFIG_HAVE_MEMBLOCK=y +CONFIG_HAVE_MEMBLOCK_NODE_MAP=y +CONFIG_ARCH_DISCARD_MEMBLOCK=y +# CONFIG_HAVE_BOOTMEM_INFO_NODE is not set +# CONFIG_MEMORY_HOTPLUG is not set +CONFIG_SPLIT_PTLOCK_CPUS=4 +CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK=y +CONFIG_MEMORY_BALLOON=y +CONFIG_BALLOON_COMPACTION=y +CONFIG_COMPACTION=y +CONFIG_MIGRATION=y +CONFIG_ARCH_ENABLE_HUGEPAGE_MIGRATION=y +CONFIG_PHYS_ADDR_T_64BIT=y +CONFIG_VIRT_TO_BUS=y +# CONFIG_KSM is not set +CONFIG_DEFAULT_MMAP_MIN_ADDR=65536 +CONFIG_TRANSPARENT_HUGEPAGE=y +# CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS is not set +CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y +CONFIG_TRANSPARENT_HUGE_PAGECACHE=y +# CONFIG_CLEANCACHE is not set +# CONFIG_FRONTSWAP is not set +# CONFIG_CMA is not set +# CONFIG_ZPOOL is not set +# CONFIG_ZBUD is not set +# CONFIG_ZSMALLOC is not set +CONFIG_GENERIC_EARLY_IOREMAP=y +CONFIG_ARCH_SUPPORTS_DEFERRED_STRUCT_PAGE_INIT=y +# CONFIG_IDLE_PAGE_TRACKING is not set +CONFIG_ARCH_USES_HIGH_VMA_FLAGS=y +CONFIG_ARCH_HAS_PKEYS=y +# CONFIG_X86_CHECK_BIOS_CORRUPTION is not set +CONFIG_X86_RESERVE_LOW=64 +CONFIG_MTRR=y +CONFIG_MTRR_SANITIZER=y +CONFIG_MTRR_SANITIZER_ENABLE_DEFAULT=0 +CONFIG_MTRR_SANITIZER_SPARE_REG_NR_DEFAULT=1 +CONFIG_X86_PAT=y +CONFIG_ARCH_USES_PG_UNCACHED=y +CONFIG_ARCH_RANDOM=y +CONFIG_X86_SMAP=y +# CONFIG_X86_INTEL_MPX is not set +CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS=y +CONFIG_SECCOMP=y +# CONFIG_HZ_100 is not set +# CONFIG_HZ_250 is not set +# CONFIG_HZ_300 is not set +CONFIG_HZ_1000=y +CONFIG_HZ=1000 +CONFIG_SCHED_HRTICK=y +# CONFIG_KEXEC_FILE is not set +CONFIG_CRASH_DUMP=y +CONFIG_PHYSICAL_START=0x100000 +CONFIG_RELOCATABLE=y +CONFIG_PHYSICAL_ALIGN=0x1000000 +CONFIG_HOTPLUG_CPU=y +# CONFIG_BOOTPARAM_HOTPLUG_CPU0 is not set +# CONFIG_DEBUG_HOTPLUG_CPU0 is not set +# CONFIG_LEGACY_VSYSCALL_EMULATE is not set +CONFIG_LEGACY_VSYSCALL_NONE=y +# CONFIG_CMDLINE_BOOL is not set +# CONFIG_MODIFY_LDT_SYSCALL is not set +# CONFIG_DEFAULT_MODIFY_LDT_SYSCALL is not set +CONFIG_HAVE_LIVEPATCH=y +CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y + +# +# Power management and ACPI options +# +# CONFIG_SUSPEND is not set +# CONFIG_PM is not set +# CONFIG_ACPI is not set +# CONFIG_SFI is not set + +# +# CPU Frequency scaling +# +# CONFIG_CPU_FREQ is not set + +# +# CPU Idle +# +# CONFIG_CPU_IDLE is not set +# CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED is not set + +# +# Memory power savings +# +# CONFIG_I7300_IDLE is not set + +# +# Bus options (PCI etc.) +# +CONFIG_PCI=y +CONFIG_PCI_DIRECT=y +CONFIG_PCI_DOMAINS=y +# CONFIG_PCI_CNB20LE_QUIRK is not set +# CONFIG_PCIEPORTBUS is not set +CONFIG_PCI_BUS_ADDR_T_64BIT=y +# CONFIG_PCI_MSI is not set +# CONFIG_PCI_DEBUG is not set +# CONFIG_PCI_REALLOC_ENABLE_AUTO is not set +# CONFIG_PCI_STUB is not set +# CONFIG_HT_IRQ is not set +# CONFIG_PCI_IOV is not set +# CONFIG_PCI_PRI is not set +# CONFIG_PCI_PASID is not set +# CONFIG_HOTPLUG_PCI is not set + +# +# PCI host controller drivers +# +# CONFIG_ISA_BUS is not set +# CONFIG_ISA_DMA_API is not set +CONFIG_AMD_NB=y +# CONFIG_PCCARD is not set +# CONFIG_RAPIDIO is not set +# CONFIG_X86_SYSFB is not set + +# +# Executable file formats / Emulations +# +CONFIG_BINFMT_ELF=y +CONFIG_ELFCORE=y +CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y +CONFIG_BINFMT_SCRIPT=y +# CONFIG_HAVE_AOUT is not set +# CONFIG_BINFMT_MISC is not set +CONFIG_COREDUMP=y +# CONFIG_IA32_EMULATION is not set +# CONFIG_X86_X32 is not set +CONFIG_X86_DEV_DMA_OPS=y +CONFIG_PMC_ATOM=y +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=y +CONFIG_UNIX=y +CONFIG_UNIX_DIAG=y +# CONFIG_XFRM_USER is not set +# CONFIG_NET_KEY is not set +CONFIG_INET=y +# CONFIG_IP_MULTICAST is not set +# CONFIG_IP_ADVANCED_ROUTER is not set +# CONFIG_IP_PNP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE_DEMUX is not set +# CONFIG_NET_IP_TUNNEL is not set +CONFIG_SYN_COOKIES=y +# CONFIG_NET_UDP_TUNNEL is not set +# CONFIG_NET_FOU is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +CONFIG_INET_DIAG=y +CONFIG_INET_TCP_DIAG=y +CONFIG_INET_UDP_DIAG=y +# CONFIG_INET_DIAG_DESTROY is not set +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IPV6 is not set +# CONFIG_NETLABEL is not set +# CONFIG_NETWORK_SECMARK is not set +CONFIG_NET_PTP_CLASSIFY=y +# CONFIG_NETWORK_PHY_TIMESTAMPING is not set +# CONFIG_NETFILTER is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_RDS is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_L2TP is not set +# CONFIG_BRIDGE is not set +CONFIG_HAVE_NET_DSA=y +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_PHONET is not set +# CONFIG_IEEE802154 is not set +# CONFIG_NET_SCHED is not set +# CONFIG_DCB is not set +# CONFIG_DNS_RESOLVER is not set +# CONFIG_BATMAN_ADV is not set +# CONFIG_OPENVSWITCH is not set +CONFIG_VSOCKETS=y +CONFIG_VIRTIO_VSOCKETS=y +CONFIG_VIRTIO_VSOCKETS_COMMON=y +CONFIG_NETLINK_DIAG=y +# CONFIG_MPLS is not set +# CONFIG_HSR is not set +# CONFIG_NET_SWITCHDEV is not set +# CONFIG_NET_L3_MASTER_DEV is not set +# CONFIG_NET_NCSI is not set +CONFIG_RPS=y +CONFIG_RFS_ACCEL=y +CONFIG_XPS=y +# CONFIG_SOCK_CGROUP_DATA is not set +# CONFIG_CGROUP_NET_PRIO is not set +# CONFIG_CGROUP_NET_CLASSID is not set +CONFIG_NET_RX_BUSY_POLL=y +CONFIG_BQL=y +CONFIG_NET_FLOW_LIMIT=y + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +# CONFIG_CAN is not set +# CONFIG_IRDA is not set +# CONFIG_BT is not set +# CONFIG_AF_RXRPC is not set +# CONFIG_AF_KCM is not set +# CONFIG_STREAM_PARSER is not set +# CONFIG_WIRELESS is not set +# CONFIG_WIMAX is not set +# CONFIG_RFKILL is not set +CONFIG_NET_9P=y +CONFIG_NET_9P_VIRTIO=y +CONFIG_NET_9P_DEBUG=y +# CONFIG_CAIF is not set +# CONFIG_CEPH_LIB is not set +# CONFIG_NFC is not set +# CONFIG_LWTUNNEL is not set +# CONFIG_DST_CACHE is not set +# CONFIG_NET_DEVLINK is not set +CONFIG_MAY_USE_DEVLINK=y +CONFIG_HAVE_EBPF_JIT=y + +# +# Device Drivers +# + +# +# Generic Driver Options +# +# CONFIG_UEVENT_HELPER is not set +CONFIG_DEVTMPFS=y +# CONFIG_DEVTMPFS_MOUNT is not set +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +# CONFIG_FIRMWARE_IN_KERNEL is not set +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_FW_LOADER_USER_HELPER_FALLBACK is not set +CONFIG_ALLOW_DEV_COREDUMP=y +# CONFIG_DEBUG_DRIVER is not set +# CONFIG_DEBUG_DEVRES is not set +# CONFIG_DEBUG_TEST_DRIVER_REMOVE is not set +# CONFIG_SYS_HYPERVISOR is not set +# CONFIG_GENERIC_CPU_DEVICES is not set +CONFIG_GENERIC_CPU_AUTOPROBE=y +CONFIG_REGMAP=y +CONFIG_REGMAP_I2C=y +# CONFIG_DMA_SHARED_BUFFER is not set + +# +# Bus devices +# +CONFIG_CONNECTOR=y +CONFIG_PROC_EVENTS=y +# CONFIG_MTD is not set +# CONFIG_OF is not set +CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y +# CONFIG_PARPORT is not set +# CONFIG_BLK_DEV is not set +# CONFIG_BLK_DEV_NVME is not set +# CONFIG_NVME_TARGET is not set + +# +# Misc devices +# +# CONFIG_SENSORS_LIS3LV02D is not set +# CONFIG_AD525X_DPOT is not set +# CONFIG_DUMMY_IRQ is not set +# CONFIG_IBM_ASM is not set +# CONFIG_PHANTOM is not set +# CONFIG_SGI_IOC4 is not set +# CONFIG_TIFM_CORE is not set +# CONFIG_ICS932S401 is not set +# CONFIG_ENCLOSURE_SERVICES is not set +# CONFIG_HP_ILO is not set +# CONFIG_APDS9802ALS is not set +# CONFIG_ISL29003 is not set +# CONFIG_ISL29020 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_SENSORS_BH1770 is not set +# CONFIG_SENSORS_APDS990X is not set +# CONFIG_HMC6352 is not set +# CONFIG_DS1682 is not set +# CONFIG_USB_SWITCH_FSA9480 is not set +# CONFIG_SRAM is not set +# CONFIG_C2PORT is not set + +# +# EEPROM support +# +# CONFIG_EEPROM_AT24 is not set +# CONFIG_EEPROM_LEGACY is not set +# CONFIG_EEPROM_MAX6875 is not set +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_CB710_CORE is not set + +# +# Texas Instruments shared transport line discipline +# +# CONFIG_SENSORS_LIS3_I2C is not set + +# +# Altera FPGA firmware download module +# +# CONFIG_ALTERA_STAPL is not set +# CONFIG_INTEL_MEI is not set +# CONFIG_INTEL_MEI_ME is not set +# CONFIG_INTEL_MEI_TXE is not set +# CONFIG_VMWARE_VMCI is not set + +# +# Intel MIC Bus Driver +# +# CONFIG_INTEL_MIC_BUS is not set + +# +# SCIF Bus Driver +# +# CONFIG_SCIF_BUS is not set + +# +# VOP Bus Driver +# +# CONFIG_VOP_BUS is not set + +# +# Intel MIC Host Driver +# + +# +# Intel MIC Card Driver +# + +# +# SCIF Driver +# + +# +# Intel MIC Coprocessor State Management (COSM) Drivers +# + +# +# VOP Driver +# +# CONFIG_GENWQE is not set +# CONFIG_ECHO is not set +# CONFIG_CXL_BASE is not set +# CONFIG_CXL_AFU_DRIVER_OPS is not set +CONFIG_HAVE_IDE=y +# CONFIG_IDE is not set + +# +# SCSI device support +# +CONFIG_SCSI_MOD=y +# CONFIG_RAID_ATTRS is not set +# CONFIG_SCSI is not set +# CONFIG_SCSI_DMA is not set +# CONFIG_SCSI_NETLINK is not set +# CONFIG_ATA is not set +# CONFIG_MD is not set +# CONFIG_FUSION is not set + +# +# IEEE 1394 (FireWire) support +# +# CONFIG_FIREWIRE is not set +# CONFIG_FIREWIRE_NOSY is not set +# CONFIG_MACINTOSH_DRIVERS is not set +CONFIG_NETDEVICES=y +CONFIG_NET_CORE=y +# CONFIG_BONDING is not set +# CONFIG_DUMMY is not set +# CONFIG_EQUALIZER is not set +# CONFIG_NET_TEAM is not set +# CONFIG_MACVLAN is not set +# CONFIG_VXLAN is not set +# CONFIG_MACSEC is not set +CONFIG_NETCONSOLE=y +CONFIG_NETCONSOLE_DYNAMIC=y +CONFIG_NETPOLL=y +CONFIG_NET_POLL_CONTROLLER=y +CONFIG_TUN=y +# CONFIG_TUN_VNET_CROSS_LE is not set +CONFIG_VETH=y +CONFIG_VIRTIO_NET=y +# CONFIG_NLMON is not set +# CONFIG_ARCNET is not set + +# +# CAIF transport drivers +# + +# +# Distributed Switch Architecture drivers +# +# CONFIG_ETHERNET is not set +# CONFIG_FDDI is not set +# CONFIG_HIPPI is not set +# CONFIG_PHYLIB is not set +# CONFIG_PPP is not set +# CONFIG_SLIP is not set + +# +# Host-side USB support is needed for USB Network Adapter support +# +# CONFIG_WLAN is not set + +# +# Enable WiMAX (Networking options) to see the WiMAX drivers +# +# CONFIG_WAN is not set +# CONFIG_VMXNET3 is not set +# CONFIG_ISDN is not set +# CONFIG_NVM is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_POLLDEV is not set +# CONFIG_INPUT_SPARSEKMAP is not set +# CONFIG_INPUT_MATRIXKMAP is not set + +# +# Userland interfaces +# +# CONFIG_INPUT_MOUSEDEV is not set +# CONFIG_INPUT_JOYDEV is not set +# CONFIG_INPUT_EVDEV is not set +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TABLET is not set +# CONFIG_INPUT_TOUCHSCREEN is not set +# CONFIG_INPUT_MISC is not set +# CONFIG_RMI4_CORE is not set + +# +# Hardware I/O ports +# +CONFIG_SERIO=y +CONFIG_ARCH_MIGHT_HAVE_PC_SERIO=y +CONFIG_SERIO_I8042=y +CONFIG_SERIO_SERPORT=y +# CONFIG_SERIO_CT82C710 is not set +# CONFIG_SERIO_PCIPS2 is not set +CONFIG_SERIO_LIBPS2=y +CONFIG_SERIO_RAW=y +# CONFIG_SERIO_ALTERA_PS2 is not set +# CONFIG_SERIO_PS2MULT is not set +# CONFIG_SERIO_ARC_PS2 is not set +# CONFIG_USERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_TTY=y +CONFIG_VT=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_UNIX98_PTYS=y +# CONFIG_LEGACY_PTYS is not set +# CONFIG_SERIAL_NONSTANDARD is not set +# CONFIG_NOZOMI is not set +# CONFIG_N_GSM is not set +# CONFIG_TRACE_SINK is not set +CONFIG_DEVMEM=y + +# +# Serial drivers +# +CONFIG_SERIAL_EARLYCON=y +CONFIG_SERIAL_8250=y +# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set +# CONFIG_SERIAL_8250_FINTEK is not set +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_PCI=y +CONFIG_SERIAL_8250_NR_UARTS=32 +CONFIG_SERIAL_8250_RUNTIME_UARTS=4 +# CONFIG_SERIAL_8250_EXTENDED is not set +# CONFIG_SERIAL_8250_FSL is not set +# CONFIG_SERIAL_8250_DW is not set +# CONFIG_SERIAL_8250_RT288X is not set +# CONFIG_SERIAL_8250_LPSS is not set +# CONFIG_SERIAL_8250_MID is not set +# CONFIG_SERIAL_8250_MOXA is not set + +# +# Non-8250 serial port support +# +# CONFIG_SERIAL_UARTLITE is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +# CONFIG_SERIAL_JSM is not set +# CONFIG_SERIAL_SCCNXP is not set +# CONFIG_SERIAL_SC16IS7XX is not set +# CONFIG_SERIAL_ALTERA_JTAGUART is not set +# CONFIG_SERIAL_ALTERA_UART is not set +# CONFIG_SERIAL_ARC is not set +# CONFIG_SERIAL_RP2 is not set +# CONFIG_SERIAL_FSL_LPUART is not set +# CONFIG_TTY_PRINTK is not set +CONFIG_HVC_DRIVER=y +CONFIG_VIRTIO_CONSOLE=y +# CONFIG_IPMI_HANDLER is not set +CONFIG_HW_RANDOM=y +# CONFIG_HW_RANDOM_TIMERIOMEM is not set +# CONFIG_HW_RANDOM_INTEL is not set +# CONFIG_HW_RANDOM_AMD is not set +# CONFIG_HW_RANDOM_VIA is not set +CONFIG_HW_RANDOM_VIRTIO=y +# CONFIG_NVRAM is not set +# CONFIG_R3964 is not set +# CONFIG_APPLICOM is not set +# CONFIG_MWAVE is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_HANGCHECK_TIMER is not set +# CONFIG_TCG_TPM is not set +# CONFIG_TELCLOCK is not set +# CONFIG_XILLYBUS is not set + +# +# I2C support +# +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +# CONFIG_I2C_COMPAT is not set +# CONFIG_I2C_CHARDEV is not set +# CONFIG_I2C_MUX is not set +# CONFIG_I2C_HELPER_AUTO is not set +# CONFIG_I2C_SMBUS is not set + +# +# I2C Algorithms +# +CONFIG_I2C_ALGOBIT=y +# CONFIG_I2C_ALGOPCF is not set +# CONFIG_I2C_ALGOPCA is not set + +# +# I2C Hardware Bus support +# + +# +# PC SMBus host controller drivers +# +# CONFIG_I2C_ALI1535 is not set +# CONFIG_I2C_ALI1563 is not set +# CONFIG_I2C_ALI15X3 is not set +# CONFIG_I2C_AMD756 is not set +# CONFIG_I2C_AMD8111 is not set +# CONFIG_I2C_I801 is not set +# CONFIG_I2C_ISCH is not set +# CONFIG_I2C_ISMT is not set +# CONFIG_I2C_PIIX4 is not set +# CONFIG_I2C_NFORCE2 is not set +# CONFIG_I2C_SIS5595 is not set +# CONFIG_I2C_SIS630 is not set +# CONFIG_I2C_SIS96X is not set +# CONFIG_I2C_VIA is not set +# CONFIG_I2C_VIAPRO is not set + +# +# I2C system bus drivers (mostly embedded / system-on-chip) +# +# CONFIG_I2C_DESIGNWARE_PLATFORM is not set +# CONFIG_I2C_DESIGNWARE_PCI is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_PXA_PCI is not set +# CONFIG_I2C_SIMTEC is not set +# CONFIG_I2C_XILINX is not set + +# +# External I2C/SMBus adapter drivers +# +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set + +# +# Other I2C/SMBus bus drivers +# +# CONFIG_I2C_SLAVE is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_SPI is not set +# CONFIG_SPMI is not set +# CONFIG_HSI is not set + +# +# PPS support +# +CONFIG_PPS=y +# CONFIG_PPS_DEBUG is not set + +# +# PPS clients support +# +# CONFIG_PPS_CLIENT_KTIMER is not set +# CONFIG_PPS_CLIENT_LDISC is not set +# CONFIG_PPS_CLIENT_GPIO is not set + +# +# PPS generators support +# + +# +# PTP clock support +# +CONFIG_PTP_1588_CLOCK=y + +# +# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks. +# +# CONFIG_GPIOLIB is not set +# CONFIG_W1 is not set +# CONFIG_POWER_AVS is not set +# CONFIG_POWER_RESET is not set +# CONFIG_POWER_SUPPLY is not set +# CONFIG_HWMON is not set +# CONFIG_THERMAL is not set +# CONFIG_WATCHDOG is not set +CONFIG_SSB_POSSIBLE=y + +# +# Sonics Silicon Backplane +# +# CONFIG_SSB is not set +CONFIG_BCMA_POSSIBLE=y + +# +# Broadcom specific AMBA +# +# CONFIG_BCMA is not set + +# +# Multifunction device drivers +# +CONFIG_MFD_CORE=y +# CONFIG_MFD_AS3711 is not set +# CONFIG_PMIC_ADP5520 is not set +# CONFIG_MFD_BCM590XX is not set +# CONFIG_MFD_AXP20X_I2C is not set +# CONFIG_MFD_CROS_EC is not set +# CONFIG_PMIC_DA903X is not set +# CONFIG_MFD_DA9052_I2C is not set +# CONFIG_MFD_DA9055 is not set +# CONFIG_MFD_DA9062 is not set +# CONFIG_MFD_DA9063 is not set +# CONFIG_MFD_DA9150 is not set +# CONFIG_MFD_EXYNOS_LPASS is not set +# CONFIG_MFD_MC13XXX_I2C is not set +# CONFIG_HTC_PASIC3 is not set +# CONFIG_LPC_ICH is not set +CONFIG_LPC_SCH=y +# CONFIG_MFD_INTEL_LPSS_PCI is not set +# CONFIG_MFD_JANZ_CMODIO is not set +# CONFIG_MFD_KEMPLD is not set +# CONFIG_MFD_88PM800 is not set +# CONFIG_MFD_88PM805 is not set +# CONFIG_MFD_88PM860X is not set +# CONFIG_MFD_MAX14577 is not set +# CONFIG_MFD_MAX77693 is not set +# CONFIG_MFD_MAX77843 is not set +# CONFIG_MFD_MAX8907 is not set +# CONFIG_MFD_MAX8925 is not set +# CONFIG_MFD_MAX8997 is not set +# CONFIG_MFD_MAX8998 is not set +# CONFIG_MFD_MT6397 is not set +# CONFIG_MFD_MENF21BMC is not set +# CONFIG_MFD_RETU is not set +# CONFIG_MFD_PCF50633 is not set +# CONFIG_MFD_RDC321X is not set +# CONFIG_MFD_RTSX_PCI is not set +# CONFIG_MFD_RT5033 is not set +# CONFIG_MFD_RC5T583 is not set +# CONFIG_MFD_SEC_CORE is not set +# CONFIG_MFD_SI476X_CORE is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_MFD_SKY81452 is not set +# CONFIG_MFD_SMSC is not set +# CONFIG_ABX500_CORE is not set +# CONFIG_MFD_SYSCON is not set +# CONFIG_MFD_TI_AM335X_TSCADC is not set +# CONFIG_MFD_LP3943 is not set +# CONFIG_MFD_LP8788 is not set +# CONFIG_MFD_PALMAS is not set +# CONFIG_TPS6105X is not set +# CONFIG_TPS6507X is not set +# CONFIG_MFD_TPS65086 is not set +# CONFIG_MFD_TPS65090 is not set +# CONFIG_MFD_TPS65217 is not set +# CONFIG_MFD_TI_LP873X is not set +# CONFIG_MFD_TPS65218 is not set +# CONFIG_MFD_TPS6586X is not set +# CONFIG_MFD_TPS65912_I2C is not set +# CONFIG_MFD_TPS80031 is not set +# CONFIG_TWL4030_CORE is not set +# CONFIG_TWL6040_CORE is not set +# CONFIG_MFD_WL1273_CORE is not set +# CONFIG_MFD_LM3533 is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_MFD_VX855 is not set +# CONFIG_MFD_ARIZONA_I2C is not set +# CONFIG_MFD_WM8400 is not set +# CONFIG_MFD_WM831X_I2C is not set +# CONFIG_MFD_WM8350_I2C is not set +# CONFIG_MFD_WM8994 is not set +# CONFIG_REGULATOR is not set +# CONFIG_MEDIA_SUPPORT is not set + +# +# Graphics support +# +# CONFIG_AGP is not set +# CONFIG_VGA_ARB is not set +# CONFIG_DRM is not set + +# +# ACP (Audio CoProcessor) Configuration +# + +# +# Frame buffer Devices +# +# CONFIG_FB is not set +# CONFIG_BACKLIGHT_LCD_SUPPORT is not set +# CONFIG_VGASTATE is not set + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_DUMMY_CONSOLE_COLUMNS=80 +CONFIG_DUMMY_CONSOLE_ROWS=25 +# CONFIG_SOUND is not set + +# +# HID support +# +# CONFIG_HID is not set + +# +# I2C HID support +# +# CONFIG_I2C_HID is not set + +# +# Intel ISH HID support +# +# CONFIG_INTEL_ISH_HID is not set +CONFIG_USB_OHCI_LITTLE_ENDIAN=y +# CONFIG_USB_SUPPORT is not set +# CONFIG_UWB is not set +# CONFIG_MMC is not set +# CONFIG_MEMSTICK is not set +# CONFIG_NEW_LEDS is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_INFINIBAND is not set +CONFIG_EDAC_ATOMIC_SCRUB=y +CONFIG_EDAC_SUPPORT=y +# CONFIG_EDAC is not set +CONFIG_RTC_LIB=y +CONFIG_RTC_MC146818_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_SYSTOHC is not set +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +# CONFIG_RTC_INTF_SYSFS is not set +# CONFIG_RTC_INTF_PROC is not set +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_ABB5ZES3 is not set +# CONFIG_RTC_DRV_ABX80X is not set +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_ISL12022 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8523 is not set +# CONFIG_RTC_DRV_PCF85063 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set +# CONFIG_RTC_DRV_BQ32K is not set +# CONFIG_RTC_DRV_S35390A is not set +# CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8010 is not set +# CONFIG_RTC_DRV_RX8581 is not set +# CONFIG_RTC_DRV_RX8025 is not set +# CONFIG_RTC_DRV_EM3027 is not set +# CONFIG_RTC_DRV_RV8803 is not set + +# +# SPI RTC drivers +# +CONFIG_RTC_I2C_AND_SPI=y + +# +# SPI and I2C RTC drivers +# +# CONFIG_RTC_DRV_DS3232 is not set +# CONFIG_RTC_DRV_PCF2127 is not set +# CONFIG_RTC_DRV_RV3029C2 is not set + +# +# Platform RTC drivers +# +CONFIG_RTC_DRV_CMOS=y +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1685_FAMILY is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_DS2404 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_MSM6242 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_RP5C01 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# + +# +# HID Sensor RTC drivers +# +# CONFIG_DMADEVICES is not set + +# +# DMABUF options +# +# CONFIG_SYNC_FILE is not set +# CONFIG_AUXDISPLAY is not set +# CONFIG_UIO is not set +# CONFIG_VIRT_DRIVERS is not set +CONFIG_VIRTIO=y + +# +# Virtio drivers +# +CONFIG_VIRTIO_PCI=y +# CONFIG_VIRTIO_PCI_LEGACY is not set +CONFIG_VIRTIO_BALLOON=y +CONFIG_VIRTIO_INPUT=y +# CONFIG_VIRTIO_MMIO is not set +CONFIG_VIRTIO_WL=y + +# +# Microsoft Hyper-V guest support +# +# CONFIG_STAGING is not set +# CONFIG_X86_PLATFORM_DEVICES is not set +# CONFIG_CHROME_PLATFORMS is not set + +# +# Hardware Spinlock drivers +# + +# +# Clock Source drivers +# +CONFIG_CLKEVT_I8253=y +CONFIG_CLKBLD_I8253=y +# CONFIG_ATMEL_PIT is not set +# CONFIG_SH_TIMER_CMT is not set +# CONFIG_SH_TIMER_MTU2 is not set +# CONFIG_SH_TIMER_TMU is not set +# CONFIG_EM_TIMER_STI is not set +# CONFIG_MAILBOX is not set +# CONFIG_IOMMU_SUPPORT is not set + +# +# Remoteproc drivers +# +# CONFIG_STE_MODEM_RPROC is not set + +# +# Rpmsg drivers +# + +# +# SOC (System On Chip) specific Drivers +# + +# +# Broadcom SoC drivers +# +# CONFIG_SUNXI_SRAM is not set +# CONFIG_SOC_TI is not set +# CONFIG_PM_DEVFREQ is not set +# CONFIG_EXTCON is not set +# CONFIG_MEMORY is not set +# CONFIG_IIO is not set +# CONFIG_NTB is not set +# CONFIG_VME_BUS is not set +# CONFIG_PWM is not set +CONFIG_ARM_GIC_MAX_NR=1 +# CONFIG_IPACK_BUS is not set +# CONFIG_RESET_CONTROLLER is not set +# CONFIG_FMC is not set + +# +# PHY Subsystem +# +# CONFIG_GENERIC_PHY is not set +# CONFIG_PHY_PXA_28NM_HSIC is not set +# CONFIG_PHY_PXA_28NM_USB2 is not set +# CONFIG_BCM_KONA_USB2_PHY is not set +# CONFIG_POWERCAP is not set +# CONFIG_MCB is not set + +# +# Performance monitor support +# +# CONFIG_RAS is not set +# CONFIG_THUNDERBOLT is not set + +# +# Android +# +# CONFIG_ANDROID is not set +# CONFIG_DEV_DAX is not set +# CONFIG_NVMEM is not set +# CONFIG_STM is not set +# CONFIG_INTEL_TH is not set + +# +# FPGA Configuration Support +# +# CONFIG_FPGA is not set + +# +# Firmware Drivers +# +# CONFIG_EDD is not set +# CONFIG_FIRMWARE_MEMMAP is not set +# CONFIG_DELL_RBU is not set +# CONFIG_DCDBAS is not set +# CONFIG_FW_CFG_SYSFS is not set +# CONFIG_GOOGLE_FIRMWARE is not set + +# +# File systems +# +CONFIG_DCACHE_WORD_ACCESS=y +# CONFIG_EXT2_FS is not set +# CONFIG_EXT3_FS is not set +# CONFIG_EXT4_FS is not set +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_XFS_FS is not set +# CONFIG_GFS2_FS is not set +# CONFIG_OCFS2_FS is not set +# CONFIG_BTRFS_FS is not set +# CONFIG_NILFS2_FS is not set +# CONFIG_F2FS_FS is not set +# CONFIG_FS_DAX is not set +CONFIG_FS_POSIX_ACL=y +# CONFIG_EXPORTFS_BLOCK_OPS is not set +CONFIG_FILE_LOCKING=y +# CONFIG_MANDATORY_FILE_LOCKING is not set +# CONFIG_FS_ENCRYPTION is not set +CONFIG_FSNOTIFY=y +# CONFIG_DNOTIFY is not set +CONFIG_INOTIFY_USER=y +CONFIG_FANOTIFY=y +# CONFIG_FANOTIFY_ACCESS_PERMISSIONS is not set +# CONFIG_QUOTA is not set +# CONFIG_QUOTACTL is not set +# CONFIG_AUTOFS4_FS is not set +CONFIG_FUSE_FS=y +# CONFIG_CUSE is not set +CONFIG_OVERLAY_FS=y + +# +# Caches +# +CONFIG_FSCACHE=y +# CONFIG_FSCACHE_STATS is not set +# CONFIG_FSCACHE_HISTOGRAM is not set +# CONFIG_FSCACHE_DEBUG is not set +# CONFIG_FSCACHE_OBJECT_LIST is not set +# CONFIG_CACHEFILES is not set + +# +# CD-ROM/DVD Filesystems +# +# CONFIG_ISO9660_FS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +# CONFIG_MSDOS_FS is not set +# CONFIG_VFAT_FS is not set +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +# CONFIG_PROC_KCORE is not set +CONFIG_PROC_SYSCTL=y +# CONFIG_PROC_CHILDREN is not set +CONFIG_KERNFS=y +CONFIG_SYSFS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_TMPFS_XATTR=y +CONFIG_HUGETLBFS=y +CONFIG_HUGETLB_PAGE=y +CONFIG_ARCH_HAS_GIGANTIC_PAGE=y +CONFIG_CONFIGFS_FS=y +CONFIG_MISC_FILESYSTEMS=y +# CONFIG_ORANGEFS_FS is not set +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_ECRYPT_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +# CONFIG_LOGFS is not set +# CONFIG_CRAMFS is not set +# CONFIG_SQUASHFS is not set +# CONFIG_VXFS_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_OMFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_QNX6FS_FS is not set +# CONFIG_ROMFS_FS is not set +# CONFIG_PSTORE is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +# CONFIG_NFS_FS is not set +# CONFIG_NFSD is not set +# CONFIG_CEPH_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set +CONFIG_9P_FS=y +# CONFIG_9P_FSCACHE is not set +CONFIG_9P_FS_POSIX_ACL=y +# CONFIG_9P_FS_SECURITY is not set +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="utf8" +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_737=y +CONFIG_NLS_CODEPAGE_775=y +CONFIG_NLS_CODEPAGE_850=y +CONFIG_NLS_CODEPAGE_852=y +CONFIG_NLS_CODEPAGE_855=y +CONFIG_NLS_CODEPAGE_857=y +CONFIG_NLS_CODEPAGE_860=y +CONFIG_NLS_CODEPAGE_861=y +CONFIG_NLS_CODEPAGE_862=y +CONFIG_NLS_CODEPAGE_863=y +CONFIG_NLS_CODEPAGE_864=y +CONFIG_NLS_CODEPAGE_865=y +CONFIG_NLS_CODEPAGE_866=y +CONFIG_NLS_CODEPAGE_869=y +CONFIG_NLS_CODEPAGE_936=y +CONFIG_NLS_CODEPAGE_950=y +CONFIG_NLS_CODEPAGE_932=y +CONFIG_NLS_CODEPAGE_949=y +CONFIG_NLS_CODEPAGE_874=y +CONFIG_NLS_ISO8859_8=y +CONFIG_NLS_CODEPAGE_1250=y +CONFIG_NLS_CODEPAGE_1251=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_ISO8859_2=y +CONFIG_NLS_ISO8859_3=y +CONFIG_NLS_ISO8859_4=y +CONFIG_NLS_ISO8859_5=y +CONFIG_NLS_ISO8859_6=y +CONFIG_NLS_ISO8859_7=y +CONFIG_NLS_ISO8859_9=y +CONFIG_NLS_ISO8859_13=y +CONFIG_NLS_ISO8859_14=y +CONFIG_NLS_ISO8859_15=y +CONFIG_NLS_KOI8_R=y +CONFIG_NLS_KOI8_U=y +CONFIG_NLS_MAC_ROMAN=y +CONFIG_NLS_MAC_CELTIC=y +CONFIG_NLS_MAC_CENTEURO=y +CONFIG_NLS_MAC_CROATIAN=y +CONFIG_NLS_MAC_CYRILLIC=y +CONFIG_NLS_MAC_GAELIC=y +CONFIG_NLS_MAC_GREEK=y +CONFIG_NLS_MAC_ICELAND=y +CONFIG_NLS_MAC_INUIT=y +CONFIG_NLS_MAC_ROMANIAN=y +CONFIG_NLS_MAC_TURKISH=y +CONFIG_NLS_UTF8=y +# CONFIG_DLM is not set + +# +# Kernel hacking +# +CONFIG_TRACE_IRQFLAGS_SUPPORT=y + +# +# printk and dmesg options +# +CONFIG_PRINTK_TIME=y +CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4 +# CONFIG_BOOT_PRINTK_DELAY is not set + +# +# Compile-time checks and compiler options +# +CONFIG_DEBUG_INFO=y +# CONFIG_DEBUG_INFO_REDUCED is not set +# CONFIG_DEBUG_INFO_SPLIT is not set +# CONFIG_DEBUG_INFO_DWARF4 is not set +# CONFIG_GDB_SCRIPTS is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +CONFIG_FRAME_WARN=2048 +CONFIG_STRIP_ASM_SYMS=y +# CONFIG_READABLE_ASM is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_SECTION_MISMATCH is not set +CONFIG_SECTION_MISMATCH_WARN_ONLY=y +CONFIG_ARCH_WANT_FRAME_POINTERS=y +# CONFIG_FRAME_POINTER is not set +# CONFIG_STACK_VALIDATION is not set +# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set +# CONFIG_MAGIC_SYSRQ is not set +CONFIG_DEBUG_KERNEL=y + +# +# Memory Debugging +# +# CONFIG_PAGE_EXTENSION is not set +# CONFIG_DEBUG_PAGEALLOC is not set +# CONFIG_PAGE_POISONING is not set +# CONFIG_DEBUG_OBJECTS is not set +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_SLUB_STATS is not set +CONFIG_HAVE_DEBUG_KMEMLEAK=y +# CONFIG_DEBUG_STACK_USAGE is not set +# CONFIG_DEBUG_VM is not set +# CONFIG_DEBUG_VIRTUAL is not set +CONFIG_DEBUG_MEMORY_INIT=y +# CONFIG_DEBUG_PER_CPU_MAPS is not set +CONFIG_HAVE_DEBUG_STACKOVERFLOW=y +# CONFIG_DEBUG_STACKOVERFLOW is not set +CONFIG_HAVE_ARCH_KMEMCHECK=y +# CONFIG_KMEMCHECK is not set +CONFIG_HAVE_ARCH_KASAN=y +# CONFIG_KASAN is not set +CONFIG_ARCH_HAS_KCOV=y +# CONFIG_DEBUG_SHIRQ is not set + +# +# Debug Lockups and Hangs +# +CONFIG_LOCKUP_DETECTOR=y +CONFIG_HARDLOCKUP_DETECTOR=y +# CONFIG_BOOTPARAM_HARDLOCKUP_PANIC is not set +CONFIG_BOOTPARAM_HARDLOCKUP_PANIC_VALUE=0 +# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0 +CONFIG_DETECT_HUNG_TASK=y +CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120 +# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set +CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=0 +# CONFIG_WQ_WATCHDOG is not set +# CONFIG_PANIC_ON_OOPS is not set +CONFIG_PANIC_ON_OOPS_VALUE=0 +CONFIG_PANIC_TIMEOUT=0 +CONFIG_SCHED_DEBUG=y +CONFIG_SCHED_INFO=y +# CONFIG_SCHEDSTATS is not set +# CONFIG_SCHED_STACK_END_CHECK is not set +# CONFIG_DEBUG_TIMEKEEPING is not set +CONFIG_TIMER_STATS=y + +# +# Lock Debugging (spinlocks, mutexes, etc...) +# +# CONFIG_DEBUG_RT_MUTEXES is not set +# CONFIG_DEBUG_SPINLOCK is not set +# CONFIG_DEBUG_MUTEXES is not set +# CONFIG_DEBUG_ATOMIC_SLEEP is not set +# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set +# CONFIG_LOCK_TORTURE_TEST is not set +CONFIG_STACKTRACE=y +# CONFIG_DEBUG_KOBJECT is not set +CONFIG_DEBUG_BUGVERBOSE=y +CONFIG_DEBUG_LIST=y +# CONFIG_DEBUG_PI_LIST is not set +# CONFIG_DEBUG_SG is not set +# CONFIG_DEBUG_NOTIFIERS is not set +# CONFIG_DEBUG_CREDENTIALS is not set + +# +# RCU Debugging +# +# CONFIG_PROVE_RCU is not set +# CONFIG_SPARSE_RCU_POINTER is not set +# CONFIG_TORTURE_TEST is not set +# CONFIG_RCU_PERF_TEST is not set +# CONFIG_RCU_TORTURE_TEST is not set +CONFIG_RCU_CPU_STALL_TIMEOUT=21 +# CONFIG_RCU_TRACE is not set +# CONFIG_RCU_EQS_DEBUG is not set +# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set +# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set +# CONFIG_CPU_HOTPLUG_STATE_CONTROL is not set +# CONFIG_FAULT_INJECTION is not set +CONFIG_USER_STACKTRACE_SUPPORT=y +CONFIG_HAVE_FUNCTION_TRACER=y +CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y +CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y +CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y +CONFIG_HAVE_SYSCALL_TRACEPOINTS=y +CONFIG_HAVE_FENTRY=y +CONFIG_HAVE_C_RECORDMCOUNT=y + +# +# Runtime Testing +# +# CONFIG_TEST_LIST_SORT is not set +# CONFIG_BACKTRACE_SELF_TEST is not set +# CONFIG_RBTREE_TEST is not set +# CONFIG_ATOMIC64_SELFTEST is not set +# CONFIG_TEST_HEXDUMP is not set +# CONFIG_TEST_STRING_HELPERS is not set +# CONFIG_TEST_KSTRTOX is not set +# CONFIG_TEST_PRINTF is not set +# CONFIG_TEST_BITMAP is not set +# CONFIG_TEST_UUID is not set +# CONFIG_TEST_RHASHTABLE is not set +# CONFIG_TEST_HASH is not set +# CONFIG_DMA_API_DEBUG is not set +# CONFIG_TEST_FIRMWARE is not set +# CONFIG_TEST_UDELAY is not set +# CONFIG_MEMTEST is not set +# CONFIG_SAMPLES is not set +CONFIG_HAVE_ARCH_KGDB=y +# CONFIG_KGDB is not set +CONFIG_ARCH_HAS_UBSAN_SANITIZE_ALL=y +# CONFIG_ARCH_WANTS_UBSAN_NO_NULL is not set +# CONFIG_UBSAN is not set +CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y +CONFIG_STRICT_DEVMEM=y +CONFIG_IO_STRICT_DEVMEM=y +# CONFIG_X86_VERBOSE_BOOTUP is not set +CONFIG_EARLY_PRINTK=y +# CONFIG_EARLY_PRINTK_DBGP is not set +# CONFIG_X86_PTDUMP_CORE is not set +CONFIG_DEBUG_RODATA_TEST=y +CONFIG_DOUBLEFAULT=y +# CONFIG_DEBUG_TLBFLUSH is not set +# CONFIG_IOMMU_STRESS is not set +CONFIG_HAVE_MMIOTRACE_SUPPORT=y +CONFIG_IO_DELAY_TYPE_0X80=0 +CONFIG_IO_DELAY_TYPE_0XED=1 +CONFIG_IO_DELAY_TYPE_UDELAY=2 +CONFIG_IO_DELAY_TYPE_NONE=3 +CONFIG_IO_DELAY_0X80=y +# CONFIG_IO_DELAY_0XED is not set +# CONFIG_IO_DELAY_UDELAY is not set +# CONFIG_IO_DELAY_NONE is not set +CONFIG_DEFAULT_IO_DELAY_TYPE=0 +# CONFIG_CPA_DEBUG is not set +CONFIG_OPTIMIZE_INLINING=y +# CONFIG_DEBUG_ENTRY is not set +# CONFIG_DEBUG_NMI_SELFTEST is not set +CONFIG_X86_DEBUG_FPU=y + +# +# Security options +# + +# +# Grsecurity +# +CONFIG_PAX_PER_CPU_PGD=y +CONFIG_TASK_SIZE_MAX_SHIFT=42 +CONFIG_GRKERNSEC=y +# CONFIG_GRKERNSEC_CONFIG_AUTO is not set +CONFIG_GRKERNSEC_CONFIG_CUSTOM=y + +# +# Customize Configuration +# + +# +# PaX +# +CONFIG_PAX=y + +# +# PaX Control +# +# CONFIG_PAX_SOFTMODE is not set +# CONFIG_PAX_EI_PAX is not set +CONFIG_PAX_PT_PAX_FLAGS=y +CONFIG_PAX_XATTR_PAX_FLAGS=y +CONFIG_PAX_NO_ACL_FLAGS=y +# CONFIG_PAX_HAVE_ACL_FLAGS is not set +# CONFIG_PAX_HOOK_ACL_FLAGS is not set + +# +# Non-executable pages +# +CONFIG_PAX_NOEXEC=y +CONFIG_PAX_PAGEEXEC=y +CONFIG_PAX_EMUTRAMP=y +CONFIG_PAX_MPROTECT=y +CONFIG_PAX_MPROTECT_COMPAT=y +# CONFIG_PAX_ELFRELOCS is not set +CONFIG_PAX_KERNEXEC=y +CONFIG_PAX_KERNEXEC_PLUGIN=y +# CONFIG_PAX_KERNEXEC_PLUGIN_METHOD_NONE is not set +CONFIG_PAX_KERNEXEC_PLUGIN_METHOD_BTS=y + +# +# Address Space Layout Randomization +# +CONFIG_PAX_ASLR=y +CONFIG_PAX_RANDKSTACK=y +CONFIG_PAX_RANDUSTACK=y +CONFIG_PAX_RANDMMAP=y + +# +# Miscellaneous hardening features +# +# CONFIG_PAX_MEMORY_SANITIZE is not set +# CONFIG_PAX_MEMORY_STACKLEAK is not set +# CONFIG_PAX_MEMORY_STRUCTLEAK is not set +CONFIG_PAX_MEMORY_UDEREF=y +CONFIG_PAX_REFCOUNT=y +CONFIG_PAX_USERCOPY=y +CONFIG_PAX_CONSTIFY_PLUGIN=y +# CONFIG_PAX_USERCOPY_DEBUG is not set +CONFIG_PAX_SIZE_OVERFLOW=y +CONFIG_PAX_SIZE_OVERFLOW_EXTRA=y +# CONFIG_PAX_INITIFY is not set +CONFIG_HAVE_PAX_INITIFY_INIT_EXIT=y +CONFIG_PAX_LATENT_ENTROPY=y +CONFIG_PAX_RAP=y +# CONFIG_PAX_RAP_VERBOSE is not set + +# +# Memory Protections +# +CONFIG_GRKERNSEC_KMEM=y +CONFIG_GRKERNSEC_IO=y +CONFIG_GRKERNSEC_BPF_HARDEN=y +CONFIG_GRKERNSEC_PERF_HARDEN=y +CONFIG_GRKERNSEC_RAND_THREADSTACK=y +CONFIG_GRKERNSEC_PROC_MEMMAP=y +CONFIG_GRKERNSEC_KSTACKOVERFLOW=y +CONFIG_GRKERNSEC_BRUTE=y +CONFIG_GRKERNSEC_HIDESYM=y +CONFIG_GRKERNSEC_RANDSTRUCT=y +CONFIG_GRKERNSEC_RANDSTRUCT_PERFORMANCE=y +CONFIG_GRKERNSEC_KERN_LOCKOUT=y + +# +# Role Based Access Control Options +# +# CONFIG_GRKERNSEC_NO_RBAC is not set +# CONFIG_GRKERNSEC_ACL_HIDEKERN is not set +CONFIG_GRKERNSEC_ACL_MAXTRIES=3 +CONFIG_GRKERNSEC_ACL_TIMEOUT=30 + +# +# Filesystem Protections +# +# CONFIG_GRKERNSEC_PROC is not set +CONFIG_GRKERNSEC_LINK=y +# CONFIG_GRKERNSEC_SYMLINKOWN is not set +CONFIG_GRKERNSEC_FIFO=y +CONFIG_GRKERNSEC_SYSFS_RESTRICT=y +CONFIG_GRKERNSEC_ROFS=y +CONFIG_GRKERNSEC_DEVICE_SIDECHANNEL=y +CONFIG_GRKERNSEC_CHROOT=y +CONFIG_GRKERNSEC_CHROOT_MOUNT=y +CONFIG_GRKERNSEC_CHROOT_DOUBLE=y +CONFIG_GRKERNSEC_CHROOT_PIVOT=y +CONFIG_GRKERNSEC_CHROOT_CHDIR=y +CONFIG_GRKERNSEC_CHROOT_CHMOD=y +CONFIG_GRKERNSEC_CHROOT_FCHDIR=y +CONFIG_GRKERNSEC_CHROOT_MKNOD=y +CONFIG_GRKERNSEC_CHROOT_SHMAT=y +CONFIG_GRKERNSEC_CHROOT_UNIX=y +CONFIG_GRKERNSEC_CHROOT_FINDTASK=y +CONFIG_GRKERNSEC_CHROOT_NICE=y +CONFIG_GRKERNSEC_CHROOT_SYSCTL=y +CONFIG_GRKERNSEC_CHROOT_RENAME=y +CONFIG_GRKERNSEC_CHROOT_CAPS=y + +# +# Kernel Auditing +# +# CONFIG_GRKERNSEC_AUDIT_GROUP is not set +# CONFIG_GRKERNSEC_EXECLOG is not set +CONFIG_GRKERNSEC_RESLOG=y +# CONFIG_GRKERNSEC_CHROOT_EXECLOG is not set +# CONFIG_GRKERNSEC_AUDIT_PTRACE is not set +# CONFIG_GRKERNSEC_AUDIT_CHDIR is not set +# CONFIG_GRKERNSEC_AUDIT_MOUNT is not set +CONFIG_GRKERNSEC_SIGNAL=y +# CONFIG_GRKERNSEC_FORKFAIL is not set +CONFIG_GRKERNSEC_TIME=y +CONFIG_GRKERNSEC_PROC_IPADDR=y +CONFIG_GRKERNSEC_RWXMAP_LOG=y + +# +# Executable Protections +# +CONFIG_GRKERNSEC_DMESG=y +CONFIG_GRKERNSEC_HARDEN_PTRACE=y +CONFIG_GRKERNSEC_PTRACE_READEXEC=y +CONFIG_GRKERNSEC_SETXID=y +CONFIG_GRKERNSEC_HARDEN_IPC=y +CONFIG_GRKERNSEC_HARDEN_TTY=y +# CONFIG_GRKERNSEC_TPE is not set + +# +# Network Protections +# +CONFIG_GRKERNSEC_BLACKHOLE=y +CONFIG_GRKERNSEC_NO_SIMULT_CONNECT=y +# CONFIG_GRKERNSEC_SOCKET is not set + +# +# Physical Protections +# + +# +# Sysctl Support +# +CONFIG_GRKERNSEC_SYSCTL=y +# CONFIG_GRKERNSEC_SYSCTL_DISTRO is not set +CONFIG_GRKERNSEC_SYSCTL_ON=y + +# +# Logging Options +# +CONFIG_GRKERNSEC_FLOODTIME=10 +CONFIG_GRKERNSEC_FLOODBURST=6 +CONFIG_KEYS=y +# CONFIG_PERSISTENT_KEYRINGS is not set +# CONFIG_BIG_KEYS is not set +# CONFIG_ENCRYPTED_KEYS is not set +# CONFIG_KEY_DH_OPERATIONS is not set +# CONFIG_SECURITY_DMESG_RESTRICT is not set +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y +CONFIG_SECURITY_NETWORK=y +CONFIG_SECURITY_PATH=y +CONFIG_HAVE_ARCH_HARDENED_USERCOPY=y +CONFIG_HARDENED_USERCOPY=y +# CONFIG_SECURITY_SELINUX is not set +# CONFIG_SECURITY_SMACK is not set +# CONFIG_SECURITY_TOMOYO is not set +CONFIG_SECURITY_APPARMOR=y +CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE=1 +CONFIG_SECURITY_APPARMOR_HASH=y +CONFIG_SECURITY_APPARMOR_HASH_DEFAULT=y +# CONFIG_SECURITY_LOADPIN is not set +# CONFIG_INTEGRITY is not set +CONFIG_DEFAULT_SECURITY_APPARMOR=y +# CONFIG_DEFAULT_SECURITY_DAC is not set +CONFIG_DEFAULT_SECURITY="apparmor" +CONFIG_CRYPTO=y + +# +# Crypto core or helper +# +CONFIG_CRYPTO_ALGAPI=y +CONFIG_CRYPTO_ALGAPI2=y +CONFIG_CRYPTO_AEAD=y +CONFIG_CRYPTO_AEAD2=y +CONFIG_CRYPTO_BLKCIPHER=y +CONFIG_CRYPTO_BLKCIPHER2=y +CONFIG_CRYPTO_HASH=y +CONFIG_CRYPTO_HASH2=y +CONFIG_CRYPTO_RNG=y +CONFIG_CRYPTO_RNG2=y +CONFIG_CRYPTO_RNG_DEFAULT=y +CONFIG_CRYPTO_AKCIPHER2=y +CONFIG_CRYPTO_KPP2=y +# CONFIG_CRYPTO_RSA is not set +# CONFIG_CRYPTO_DH is not set +# CONFIG_CRYPTO_ECDH is not set +CONFIG_CRYPTO_MANAGER=y +CONFIG_CRYPTO_MANAGER2=y +# CONFIG_CRYPTO_USER is not set +# CONFIG_CRYPTO_MANAGER_DISABLE_TESTS is not set +CONFIG_CRYPTO_GF128MUL=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_NULL2=y +CONFIG_CRYPTO_PCRYPT=y +CONFIG_CRYPTO_WORKQUEUE=y +CONFIG_CRYPTO_CRYPTD=y +# CONFIG_CRYPTO_MCRYPTD is not set +CONFIG_CRYPTO_AUTHENC=y +CONFIG_CRYPTO_ABLK_HELPER=y +CONFIG_CRYPTO_GLUE_HELPER_X86=y + +# +# Authenticated Encryption with Associated Data +# +CONFIG_CRYPTO_CCM=y +CONFIG_CRYPTO_GCM=y +# CONFIG_CRYPTO_CHACHA20POLY1305 is not set +CONFIG_CRYPTO_SEQIV=y +CONFIG_CRYPTO_ECHAINIV=y + +# +# Block modes +# +CONFIG_CRYPTO_CBC=y +CONFIG_CRYPTO_CTR=y +CONFIG_CRYPTO_CTS=y +CONFIG_CRYPTO_ECB=y +CONFIG_CRYPTO_LRW=y +CONFIG_CRYPTO_PCBC=y +CONFIG_CRYPTO_XTS=y +# CONFIG_CRYPTO_KEYWRAP is not set + +# +# Hash modes +# +CONFIG_CRYPTO_CMAC=y +CONFIG_CRYPTO_HMAC=y +CONFIG_CRYPTO_XCBC=y +CONFIG_CRYPTO_VMAC=y + +# +# Digest +# +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_CRC32C_INTEL=y +CONFIG_CRYPTO_CRC32=y +CONFIG_CRYPTO_CRC32_PCLMUL=y +CONFIG_CRYPTO_CRCT10DIF=y +CONFIG_CRYPTO_CRCT10DIF_PCLMUL=y +CONFIG_CRYPTO_GHASH=y +# CONFIG_CRYPTO_POLY1305 is not set +# CONFIG_CRYPTO_POLY1305_X86_64 is not set +CONFIG_CRYPTO_MD4=y +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_MICHAEL_MIC=y +CONFIG_CRYPTO_RMD128=y +CONFIG_CRYPTO_RMD160=y +CONFIG_CRYPTO_RMD256=y +CONFIG_CRYPTO_RMD320=y +CONFIG_CRYPTO_SHA1=y +CONFIG_CRYPTO_SHA1_SSSE3=y +CONFIG_CRYPTO_SHA256_SSSE3=y +CONFIG_CRYPTO_SHA512_SSSE3=y +# CONFIG_CRYPTO_SHA1_MB is not set +# CONFIG_CRYPTO_SHA256_MB is not set +# CONFIG_CRYPTO_SHA512_MB is not set +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_SHA512=y +# CONFIG_CRYPTO_SHA3 is not set +CONFIG_CRYPTO_TGR192=y +CONFIG_CRYPTO_WP512=y +CONFIG_CRYPTO_GHASH_CLMUL_NI_INTEL=y + +# +# Ciphers +# +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_AES_X86_64=y +CONFIG_CRYPTO_AES_NI_INTEL=y +CONFIG_CRYPTO_ANUBIS=y +CONFIG_CRYPTO_ARC4=y +CONFIG_CRYPTO_BLOWFISH=y +CONFIG_CRYPTO_BLOWFISH_COMMON=y +CONFIG_CRYPTO_BLOWFISH_X86_64=y +CONFIG_CRYPTO_CAMELLIA=y +CONFIG_CRYPTO_CAMELLIA_X86_64=y +CONFIG_CRYPTO_CAMELLIA_AESNI_AVX_X86_64=y +CONFIG_CRYPTO_CAMELLIA_AESNI_AVX2_X86_64=y +CONFIG_CRYPTO_CAST_COMMON=y +CONFIG_CRYPTO_CAST5=y +CONFIG_CRYPTO_CAST5_AVX_X86_64=y +CONFIG_CRYPTO_CAST6=y +CONFIG_CRYPTO_CAST6_AVX_X86_64=y +CONFIG_CRYPTO_DES=y +# CONFIG_CRYPTO_DES3_EDE_X86_64 is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_KHAZAD is not set +CONFIG_CRYPTO_SALSA20=y +CONFIG_CRYPTO_SALSA20_X86_64=y +CONFIG_CRYPTO_CHACHA20=y +CONFIG_CRYPTO_CHACHA20_X86_64=y +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_SERPENT_SSE2_X86_64 is not set +# CONFIG_CRYPTO_SERPENT_AVX_X86_64 is not set +# CONFIG_CRYPTO_SERPENT_AVX2_X86_64 is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TWOFISH is not set +# CONFIG_CRYPTO_TWOFISH_X86_64 is not set +# CONFIG_CRYPTO_TWOFISH_X86_64_3WAY is not set +# CONFIG_CRYPTO_TWOFISH_AVX_X86_64 is not set + +# +# Compression +# +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRYPTO_LZO=y +# CONFIG_CRYPTO_842 is not set +CONFIG_CRYPTO_LZ4=y +CONFIG_CRYPTO_LZ4HC=y + +# +# Random Number Generation +# +CONFIG_CRYPTO_ANSI_CPRNG=y +CONFIG_CRYPTO_DRBG_MENU=y +CONFIG_CRYPTO_DRBG_HMAC=y +# CONFIG_CRYPTO_DRBG_HASH is not set +# CONFIG_CRYPTO_DRBG_CTR is not set +CONFIG_CRYPTO_DRBG=y +CONFIG_CRYPTO_JITTERENTROPY=y +CONFIG_CRYPTO_USER_API=y +CONFIG_CRYPTO_USER_API_HASH=y +CONFIG_CRYPTO_USER_API_SKCIPHER=y +# CONFIG_CRYPTO_USER_API_RNG is not set +# CONFIG_CRYPTO_USER_API_AEAD is not set +# CONFIG_CRYPTO_HW is not set +# CONFIG_ASYMMETRIC_KEY_TYPE is not set + +# +# Certificates for signature checking +# +CONFIG_HAVE_KVM=y +# CONFIG_VIRTUALIZATION is not set +# CONFIG_BINARY_PRINTF is not set + +# +# Library routines +# +CONFIG_BITREVERSE=y +# CONFIG_HAVE_ARCH_BITREVERSE is not set +CONFIG_GENERIC_STRNCPY_FROM_USER=y +CONFIG_GENERIC_STRNLEN_USER=y +CONFIG_GENERIC_NET_UTILS=y +CONFIG_GENERIC_FIND_FIRST_BIT=y +CONFIG_GENERIC_PCI_IOMAP=y +CONFIG_GENERIC_IOMAP=y +CONFIG_GENERIC_IO=y +CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y +CONFIG_ARCH_HAS_FAST_MULTIPLIER=y +CONFIG_CRC_CCITT=y +CONFIG_CRC16=y +CONFIG_CRC_T10DIF=y +CONFIG_CRC_ITU_T=y +CONFIG_CRC32=y +# CONFIG_CRC32_SELFTEST is not set +CONFIG_CRC32_SLICEBY8=y +# CONFIG_CRC32_SLICEBY4 is not set +# CONFIG_CRC32_SARWATE is not set +# CONFIG_CRC32_BIT is not set +CONFIG_CRC7=y +CONFIG_LIBCRC32C=y +# CONFIG_CRC8 is not set +# CONFIG_AUDIT_ARCH_COMPAT_GENERIC is not set +# CONFIG_RANDOM32_SELFTEST is not set +CONFIG_ZLIB_INFLATE=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_LZO_COMPRESS=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_LZ4_COMPRESS=y +CONFIG_LZ4HC_COMPRESS=y +CONFIG_LZ4_DECOMPRESS=y +CONFIG_XZ_DEC=y +CONFIG_XZ_DEC_X86=y +# CONFIG_XZ_DEC_POWERPC is not set +# CONFIG_XZ_DEC_IA64 is not set +# CONFIG_XZ_DEC_ARM is not set +# CONFIG_XZ_DEC_ARMTHUMB is not set +# CONFIG_XZ_DEC_SPARC is not set +CONFIG_XZ_DEC_BCJ=y +# CONFIG_XZ_DEC_TEST is not set +CONFIG_RADIX_TREE_MULTIORDER=y +CONFIG_ASSOCIATIVE_ARRAY=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT_MAP=y +CONFIG_HAS_DMA=y +CONFIG_CPU_RMAP=y +CONFIG_DQL=y +CONFIG_NLATTR=y +CONFIG_CORDIC=y +# CONFIG_DDR is not set +CONFIG_IRQ_POLL=y +# CONFIG_SG_SPLIT is not set +# CONFIG_SG_POOL is not set +CONFIG_ARCH_HAS_SG_CHAIN=y +CONFIG_ARCH_HAS_PMEM_API=y +CONFIG_ARCH_HAS_MMIO_FLUSH=y +CONFIG_SBITMAP=y diff --git a/kernel/init/Makefile b/kernel/init/Makefile new file mode 100644 index 0000000..0981bc6 --- /dev/null +++ b/kernel/init/Makefile @@ -0,0 +1,2 @@ +init: init.c + gcc -o init init.c diff --git a/kernel/init/init.c b/kernel/init/init.c new file mode 100644 index 0000000..3b410a0 --- /dev/null +++ b/kernel/init/init.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *splash[] = { +"", +" ------------------------------||-------------------------------", +" [##]", +" /~~~~~~\\", +" |~~\\ /~~|", +" ==][===|___||___|===][==", +" [::] ( () ) [::]", +" ~/~~~~\\~", +" O' `o", "",NULL +}; + +static int run_shell() +{ + char *new_argv[] = { "/bin/bash", NULL }; + char *new_env[] = { "TERM=xterm-256color", "HOME=/home/user", NULL }; + char **p; + for(p = splash; *p; p++) { + printf("%s\n", *p); + } + + return execve("/bin/bash", new_argv, new_env); +} + +static void do_mkdir(const char* path) { + if(mkdir(path, 0755) < 0) { + printf("mkdir %s failed: %s\n", path, strerror(errno)); + } + +} + +static void mount_tmpfs(const char *path) { + if(mount("tmpfs", path, "tmpfs", 0, "mode=755") < 0) { + printf("mount tmpfs to %s failed: %s\n", path, strerror(errno)); + } +} + +static void pivot_root(const char *new_root, const char *put_old) { + if(syscall(SYS_pivot_root, new_root, put_old) < 0) { + printf("pivot_root failed (%s : %s) : %s\n", new_root, put_old, strerror(errno)); + } +} +static void move_mount(const char *source, const char *target) { + if(mount(source, target, "", MS_MOVE, NULL) < 0) { + printf("move mount of %s to %s failed: %s\n", source, target, strerror(errno)); + } +} + + +static void setup_overlay(void) { + mount_tmpfs("/tmp"); + mount("tmpfs", "/tmp", "tmpfs", 0, "mode=755"); + do_mkdir("/tmp/ro"); + do_mkdir("/tmp/rw"); + mount_tmpfs("/tmp/rw"); + do_mkdir("/tmp/rw/upper"); + do_mkdir("/tmp/rw/work"); + do_mkdir("/tmp/overlay"); + pivot_root("/tmp", "/tmp/ro"); + + /* + * /ro real root mounted here + * /rw tmpfs mounted here + * /rw/upper empty directory + * /rw/work empty directory + * /overlay empty directory + * + */ + if(mount("overlay", "/overlay", "overlay", 0, "lowerdir=/ro,upperdir=/rw/upper,workdir=/rw/work") < 0) { + printf("mount overlay failed: %s\n", strerror(errno)); + } + do_mkdir("/overlay/ro"); + do_mkdir("/overlay/rw"); + do_mkdir("/overlay/old-root"); + move_mount("/ro", "/overlay/ro"); + move_mount("/rw", "/overlay/rw"); + + pivot_root("/overlay", "/overlay/old-root"); + umount("/old-root"); + umount("/ro/tmp"); +} + +static void do_mounts(void) +{ + + mount("sysfs", "/sys", "sysfs", 0, NULL); + mount("proc", "/proc", "proc", 0, NULL); + mount("devtmpfs", "/dev", "devtmpfs", 0, NULL); + mkdir("/dev/pts", 0755); + mount("devpts", "/dev/pts", "devpts", 0, NULL); +} + +int main(int argc, char *argv[]) +{ + pid_t child; + int status; + + setup_overlay(); + do_mounts(); + + sethostname("airwolf", 7); + /* get session leader */ + setsid(); + + /* set controlling terminal */ + ioctl(0, TIOCSCTTY, 1); + + child = fork(); + if (child < 0) { + printf("Fatal: fork() failed with %d\n", child); + return 0; + } else if (child == 0) { + run_shell(); + } else { + pid_t corpse; + + do { + corpse = waitpid(-1, &status, 0); + } while (corpse != child); + } + + reboot(RB_AUTOBOOT); + + printf("Init failed: %s\n", strerror(errno)); + + return 0; +} diff --git a/kernel/keys/gregkh.key b/kernel/keys/gregkh.key new file mode 100644 index 0000000..810ec0a --- /dev/null +++ b/kernel/keys/gregkh.key @@ -0,0 +1,1162 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: SKS 1.1.6 +Comment: Hostname: keyserver.ntzwrk.org + +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 +=099i +-----END PGP PUBLIC KEY BLOCK----- diff --git a/kernel/keys/minipli.key b/kernel/keys/minipli.key new file mode 100644 index 0000000..2ee0d1e --- /dev/null +++ b/kernel/keys/minipli.key @@ -0,0 +1,88 @@ + +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: SKS 1.1.6 +Comment: Hostname: pgp.mit.edu + +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 +tCiqjLBC3pzX+sNjnwQ= +=bPiP +-----END PGP PUBLIC KEY BLOCK----- diff --git a/kernel/keys/torvalds.key b/kernel/keys/torvalds.key new file mode 100644 index 0000000..ff9d23a --- /dev/null +++ b/kernel/keys/torvalds.key @@ -0,0 +1,1265 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: SKS 1.1.5 +Comment: Hostname: pgp.surfnet.nl + +mQENBE55CJIBCACkn+aOLmsaq1ejUcXCAOXkO3w7eiLqjR/ziTL2KZ30p7bxP8cTUXvfM7fw +E7EnqCCkji25x2xsoKXB8AlUswIEYUFCOupj2BOsVmJ/rKZW7fCvKTOK+BguKjebDxNbgmif +39bfSnHDWrW832f5HrYmZn7a/VySDQFdul8Gl/R6gs6PHJbgjjt+K7Px6cQVMVNvY/VBWdvA +1zckO/4h6gf3kWWZN+Wlq8wv/pxft8QzNFgweH9o5bj4tnQ+wMCLCLiDsgEuVawoOAkg3dRM +ugIUoiKoBKw7b21q9Vjp4jezRvciC6Ys4kGUSFG1ZjIn3MpY3f3xZ3yuYwrxQ8JcA7KTABEB +AAG0LkxpbnVzIFRvcnZhbGRzIDx0b3J2YWxkc0BsaW51eC1mb3VuZGF0aW9uLm9yZz6IRgQQ +EQIABgUCTnkK3wAKCRAXdixGduIcu6V/AJ0Rp+b8RemV6G+OWqNjw0LDDxFMCACgs3JXPxAD +PCqQvtlrSnhFlQd70PKIRgQQEQIABgUCTnkOSgAKCRAWoajLlOL3fWgYAKCTfHVcKQlrw4vS +0KiaRXbrVFQqJQCfd6I1N7DvZ4VsQDi9UWbfPyRvCeaIRgQQEQIABgUCTnqNSgAKCRDm1nM6 +WTa4gFNkAJ0YjknyVR7lkLtCWtqd3nYEMcQXIQCdH+mN7uNQItrno4HD3VS0Laxf0QGIRgQQ +EQIABgUCTnqNogAKCRC5dIpnRUjjn5wsAJ93X39UztcXT2M63wvdxIyHGOmhLwCggz2Pux2c +3RqYFYB76AXUcRfzty+IRgQQEQIABgUCTozGugAKCRClpOmccR07YU3kAJ9Aek/rWUsvBor8 +3Yq70pJSRZhMkQCfXPPMRHFLrlGcrS4RfuadWz1Kdj6IRgQQEQIABgUCTqcbkgAKCRAxR9QN +2y37KWbnAKDLJpIZ2u1AHq+xYxudgEYjLjckaACgnK1QA2k0vTy8K794jkAv69oAACKIRgQQ +EQIABgUCTqclqQAKCRD8TeTJZT12JyKTAJ983eYj/EyNFJNoukAonVNJBfuy3gCfTU+ZpViB +j892cKStv8LMwQRB5d6IRgQQEQIABgUCTqdHeAAKCRDNBNWGXkCmOgwnAJ9ZWs9TE7XyojfG +FoPdoIi2bAP0cgCgwntXuXC8tcXTCDU25DaUj794ppaIRgQQEQIABgUCTqdeDQAKCRC8YirT +Dnq97IyxAKDAIx3ydDDSXoIV01ylCmF/rxNB2wCfbJXh14A6j3bdErtoWy6w1POXReOIRgQQ +EQIABgUCTq/kJwAKCRBnpz+Ub+7arPzbAKCB9vKOFtANTiApgqYjgEAQ6fSjAgCbBSxCbj/Z +21HVtDR0d59ixT3NJSWIRgQQEQIABgUCTwecUwAKCRCri1RFxkhKwsJFAJ98k/z2Uni1vfXd +wP4W/UOv/gg7JACbBYKhHGsaBDfG3KIqyh/YF/xgZl6IRgQQEQIABgUCTwn8qgAKCRDm3kZL +YsNF/ghBAJ9V/7DNVq45JsRL20m48GVK+VrO0QCfQGvTiOTffTUgRbka0X5o8yg6NG6IRgQQ +EQIABgUCUk7E6wAKCRDfYFuwdg8tZcsQAKCKWteH+kFbkoMQ8kI47r1F/OepgQCgn0TAAI0v +B7PCk0NvRN8S6aH0wxWIRgQQEQIABgUCWIxy8AAKCRAdJ9tLqUswA3CpAJ9EBDV1xUiHXxuw +7ojgrKBjCBKCywCfebEf6kBQBIBl+KYiifXL9cUbeCuIRgQQEQgABgUCTnpkywAKCRDtOjnj +k2dMQB5MAJ9iShqJfjYtXinhQbZKL9KeVOPyjQCguxp8wBvVLTcOeS8r8ostkWJEHs2InAQQ +AQIABgUCT1Oi7QAKCRCexXmQJxRwiHIwA/4hirFihMIFgDAscXnCNS8NBP5WOlXIPgLKobvR +nf2Fh9r6i7sTvFWO5ZoSZD4+1dTIEu7+u73v0CjJItbQtniDDm0vPBQsZTnjvQ9XyGbzPxeo +V15kvJJAptRA47G0ZkQ7KUu2BwjNJjRYrnRPY/o5xn6PPzS8dehIsZnXpvv/V4ijBBEWCgBL +FiEE5TtgSt02ilObueszqhTpYgD14AYFAlm6ZogtGmh0dHA6Ly9mb3hjcHAuZHVja2Rucy5v +cmcvcGdwLWtleS1wb2xpY3kudHh0AAoJEKoU6WIA9eAGWWwA/3CRhu1yOyBVU+bV0kc6ncYu +6nI2S9+Oy4bf0fE0RDvTAP90xiqFywNPIB2isTZzhpfqSJYMMBh1aKJ28EY7d/FJC4kBHAQQ +AQIABgUCTnkNpgAKCRDIOwdF3xiN/j6QCACOnzCoPE3XphiVGecfishxIaPXBwl54jzrxOeR +6wjnrsOgkHeAdtv0Bf68HDBC1u518oyr1HZrsqZSJbHbWQQ2zrGsQmkTM3uiiRsR7Qgl/qr+ +jZzEkd8tDLklgEgwSQXObZAr1b7INow1/8c0wSZMJjmArv6pIwE3z2T+mUPIjRc1Pv2rq+37 +Sipqm/GxszgTUsUJw3QwiJ4tSCH+c15gFurv7LJln+uvpo0WiLJJtgZ6+taGm/bLgmzHETF9 +WoyB3vgxID2ScY2t1FYeasTLGTLfOJmjaJpOUlYOSr9e7YJNUA9XLiebfAxp5zKZbVv8WzdR +EKEf6IUAgBaNPyxYiQEcBBABAgAGBQJOeo7lAAoJEGx5eAAqlgcFz5cH/R6tJ0movbqD2bda +uWGTEBRc4SYkcHXBm/JK7xAeQf16smTavQGtliP5LLyZutzf8ELg47TtK0sSO3125ZJ9iBQB +S/VBAyWGhj1xTsPY2tl+YMCXOcr3SJzQ3sXMH/iMXcljDZSvbmwrxIP+Wqqa5Bu8xaGoLrm6 +vgORmIAWkwtlbhMDFZQHXHL3UR6/22GWsnwJ5wK9vpvlz2NZ3pVb6pRSr2zEWQGoawis0Kth +uPazsZrsHP3rsdPeLtvpNm1fva0+otGZS0ybxnzBWB7ObtBvlTV09JxISINM3RIL78djlQW+ +JHY7lc4CXF1PO5oIM7gJk8tOJ+B8VMIn7WUN32iJARwEEAECAAYFAk6TIeAACgkQOFZKScC1 +6ZhJewf/RRDSk3eLN1Qp3qa0JNlnF3iSqgeBkMb7MXxRWcXrRL34HtHkOPm7qVjvDo8Fr4Md +fkZge5Iyy5tsdPWYRmnjIgNsbLvol8XV/aiAOZgb2BAJtVRFD6BY7c8ELVdoVAdhcvZgif6D +2m6ViEphk40vrXe9UPlQ0tthnydzuuDqJj4IFqyQQvzFBle3aZGLc3fkkV/pwaA7+vWuZH5e +CItljhAx4d36Qm2kshwiH/U69lkzwndb52D7jt1ljF7tYPzXHC3KRe68ZBQ6n5eeRXVFsnkh +F9TISxtCk+TRLxL/kcJT6apDG1UmLGnIHa/coCV51KSZUiiWGLRw3bjOpqSAGYkBHAQQAQIA +BgUCTqjLhgAKCRDArf/6sfscGDHoB/9CLuoXas16DBovZGAulsa7LGi10WhPPo6y8xx1xSBf +a5X+OpIq/cX2Dz/tL0aZd/q1FUbNudLLIYal8pyW6w2ZedV5uImHVxdmeaVI/E3siAs8pzWa +R3WhEIFkTMB96lT6d80ot+NG2KruTk0VGkrj81eat+Q/nWwDvIxlhG3oORCSpgZCbmtRw7mR +076dLZEC777w/iqLwZboioILoijuRGI7duUskz11lhQa34IezIy6e6qH5Regj2rT6NfL/lKl +EeiO/Cxzs6sNBhS/W0O+quGrq7YhYPiOEhYg4G9ETGRfWYiw6pj3j1ws4BoqCQJptWSJNr89 +P17qHZwLw8q1iQEcBBABAgAGBQJOqYhjAAoJEEHdSxh4DVnEll4H/12LJBOOA3jDH+APTK33 +3TRUaBmje1oD07CBlzfRhHH4eKM5DoDCIrdpoTu/ne4n7PK9HOJFD59aaFriJ1YuqmJ2NDZb +Q16ipYN6LszRNTRWxU9yc5Zc1pqGIbaKYtKze/N/kOK4NJJxazhJUmxvmEpFUX8CMPtsJtns +M1G+3v2WRzsI0FW1hfSF01Mkjg/B9e4Yp2XBvd4HP5UV4oUg94wPE8O6A3IMzNdrbhjIMsn3 +W3XO6yOKINlnLGBb214H4SpI3D+7Su2wEVpPaKoZKIPl+F76Iwi0BEp44No3p1n2Xwwz87z2 +yVv69FoJ8V8oFwHHwV4HMaoyoUW2vFJr1U2JARwEEAECAAYFAk6z0xoACgkQZFjT1H0AtYEh +fQf+O18EEvFBAg2wJrdHv//H4IbbTKrDwKWBYMEAd2lHIxcyTRo3EKch0lNmfNWBTSQWtEvd +gBdD7VFwcx/uQBwa1tsH9sGiG+hhoXGWpcJUO0ujzTxLv64L2FVhw8FvyNpYiGspeNTmrwPZ +5eBnzoG4zbb/UImxUsQNzsjJXp1NPHzEkfQDZUr2WejRgHqOPZ3mzVIQP5b+sfSKVhVE+r3Z +TtzMcWYIc9nK6k39rRHt5Dh8Ys2n5NTU87Zq64uEua3BSl4uwbZK5ZldWDoGmsfoROdZNDUc +XexJgM10ht2ZKvaLnvz6K/wy0AZqi5Fi8S2QMSsbGKLvOnKmUyqxTeGM0IkBHAQQAQIABgUC +TweX3gAKCRAOxnjm+YoBdrIMB/4osB3pgHCGU7w/gHZxPF6hRfpYgDFKvyOyA55zEoOjWcCK +IARc0colULRxitcAXd7UYaVbBj17n1Vi+3Y990JOwH5eEOIjxHuNszGPKcHp9PAk8Opt6ga1 +hA2+3jeyI9WKV843MwWwbCyqOvXhIiKY6CurkO+jSrt24yQBilLKgkSgRgTNIksUDATgS1xj +xjctMrT0SpKGb1yxIhoO6HUqbGk6ou/lw7EEadZtZVnmkzfhzyZdvUch0H9sGrBJJtMKKbP3 +4m/KEE6+bdCOlcHJNZJ0abwi9IQPGFJOitYs7f4MhrT0p7oug+Y7x23+hvv7oAESY8vF5pVd +t1PcBakGiQEcBBABAgAGBQJTnJ33AAoJEKQV7+r9ig6YQxcIAIAo6c6Mb0Akt3zjocIo8S6+ +1AEMwONkvI+mzKT1HcLxEoMolKcSu4/1Fr6XZfRJ1MgTlPiuW2ndaS0RV2Ba6mVNKYXYioEQ +TZyDeLDHvgzeTBf611D3xk/VIXK4c32w30X2OCbc/mcezuGCmAFSCjQ7e6+VOWqY8C9oo00N +YI8NlLqMcbMX5OB/RKF7H5bOUZqIARHskTS8qET8mBTRZRyYQ6QGvz8TLhCMX6EsOAVHT0DP +BpTawk8b2Z4SPHnnBno6QgNveJm6OsCdNShWOKJ2g1XXXakmHaVzjz9KX1DX/cngFrqZgMii +WLXExq4lQ2PsCseXhZd0CzDTXmdqxXeJARwEEAECAAYFAlPk/HMACgkQCkXJ55m0pIkazQf+ +N1FGC5u3nvdR828CT0IT7PrfJ9BC7zynxnOoVH0g+ZxfBv+e/pzWhbFtw/m5U9V8FqCCY2fR +FVar3BDfP9YTWI/KosYflgFsbjTJwfbJNjDF3uBhzqcqPEv7ntbQaVcSkMEolBZilDZsWb6+ +Nz+8sqmnTc9hGcgQiOaHR87PCV9hi7C1iYF7tEpBTAfMCvFJHedXlbb5j6A4agcrfXGTi5p5 +//E8yqCLTik+i4Q9J3Zk4TVg+nVnI82a1wUGBAJ3x6wYocASYS/4ylxMq4uV/4g3RHW9cX42 +BJZZYFWT9l54DrQSVecEms7L/TOujZKhOM7hvJ48Wug0bh9QeTMYUYkBHAQQAQIABgUCV7La +BQAKCRBJWjt+j9YosuD8B/4wyfNl8mD1bQe/87l7fp4tI8WPbaBBYtjSSCsMbkfW+tEPsjxk +xMQkK8k88grRcOuuqF4x08juRn99fykrnv4e+4RtlS35g8FKP6i0AaAk+hERFsmHNyWUJhVE +n8Ht4hAXIVgxF5ygaYCkfWlT2v21kHF0EvuwaJi3EvBpCJuyPLHjWShRY/o87j5gC1Oo4FA2 +bSsdy2qm5whZF+gs+Utk41Z34DKyAKdae4RmQhXwe9RlDK/RcvpgoFeRWsoQO3BXi6Oe4ave +N5wqpeOeqaXJhAnC3sOUGXudVP7lubDKl9ZA9Xv5kq7UP7D1KXLSRD0TBMIvJASNw06yHa3z +W0k7iQEcBBABAgAGBQJYMvZIAAoJEMeAwkXlEt8CxjIIANJ2I9M7Ytq+Us4Ni5DM3Su6xDVa +bY1pVfjIouwoL/P40uofFxGh9xvOcpZ8bIuLFh6uu7vRPjgMqrCvuBW4ufgdOdFwyNZEodb7 +0GY+FK0pcJPTCjAkhRxxCn7gkbL6ylYUchyqzRFc6+cZFhcRgOea9c8JniPreedfRqXf6ln/ +fXpFtR8nLga6PVh0MZTk3t2KrdNHm99I1NF6dzSYuUeNK3w9lTXpe4/c+p3eyrSwchBEh7+W +s/M99LgOrJeF407fAR59T9aGHOrf9yW4yoJtQGBG1IsdlngJjzn0N1uGJiRlgpWDD8+6jomS +KtE8FuAdhHFf4TuF/Jmx6J86e86JARwEEAEIAAYFAlcQknIACgkQfqDkQL3WbsyT9Af+IqWF +q6HEErasjnJTX+BrihxsdxCrClgbU9bSeqEzGQXqXs3P8ho0l1KL3K3Mu382WHx3l2laoWvj +KVZ/f+x0bt36Sou8mU8Vo/T+7aVxN/jBqjWv9GIrZ3tRRqUMQ63fS56kQHTndOAGlIkkrAbF +RQSMZXgvxo5xsG4Zb1jwNKe+8QI+o8MLkkhh8zfeCDHnY29OhEXn4DL/y8wi8eKfkXQoMVPN +Q8KFuJFVDPZf4q6tZw+OKg1IC3Y6qEEFe02/JzkXAo0MPIcEm+UkLfw3LrlZ8DMcTm7rJCSf +vJyHaGL8DsdQDQN+K3OGlcZ8AAEr+PCwXHuLXI8EidCjbXwh54kBHAQQAQgABgUCWWHbsgAK +CRAjRRsQeqA5QWXzB/9/kwOe7nkNP+fuHaWTJxJ2qGVvWYSbWNoZMsdeLSBopWzxtAedRAQl +j8TMGX4aSd4nG4S2lP0SMOyoqd/6JGoLgcajpBWAxf/LqOtPOkfXIdvajUv8JCWntvWk1uma +3UuhiKa0dJDKRAHACqEa04uXTWRHNi8Z/GkvFcp4oHqsVotUCh19Tio9QA9e3FSqlv5VyPC/ +LwgOZ1Q/W9i0XT+SX8pPdoUB7++cjWaUNxQyzpiNGeoF6J2/ulXu9RCUywaDSmDgMlJv2ZwA +4N0sdeM8bw7nEBPc9KzhND/3+AesrfT43CrgEtkvogYUxs2jEkY1CG/CezyidoCmUphdZUeD +iQEcBBABCgAGBQJX91rhAAoJEEQArZFSTVyKtssH/jLMhL4igyBOfkhT1ofJSfup0aKiOcaD +SZbuCLubaWl9tUyJlpG8S/V+P5rkOdN624nlqZh6qJCqVn8nt0F5Mi4BYV8q+5asir0DncVK +gr6orlJ/BmZ8bhU3Z9q+dybPXkAmqhq8pI3B5jYNRV4eclPahYRcgdzkaJe60YMLh1q5Ve06 +26Nr2UFY47Xy/TGrwmxyxs281lQUOf1AntHgchVtV0CeAlgly7ptUDQyz5PWKLRzrn5jhYyY +QCrOAfHgHFgpPwXUWdCOl0QyIF0/8QAA4Ebtnk6pfrtnKGw2KnjTAuweRXSq6OFG9FZ4FL0r +RUd3RIAGwFaiqnm7bKlC4FCJAR8EEAECAAkFAlFcazkCBwAACgkQ0zmw3Zpk2TFiJgf8DiVB +3r/5MCagIy2UjraYYBLYxukmwN4dXJ70bEFkCGf/R6q8k9MX+B6cN791b4vlkT7henbjFpWE +k7FLVmzZXscFiktDcmxGjx1JqTjqc6By4htiQyTTZIh6xwLVI8pSVD0vvmXOA1MLk+wpiy65 +ZxuXL0eBl3o9CFdx7OCTQGCFZ4iPl6ymVc0G2Q0r0tKa4qa499G/P+NcFKjs64hMmq+hN2WR +4ith5+seMfBueGNGYLgNamJx8+KCFv8B2VsKhxGNgwrIw2gOAna9CeZMclAO75s5IspVSSMr +TstPHPF4X37Q8XyhVc8jEitiUJUWJYmO6yHSm5iH6c6Iwz0pWokBIAQQAQIACgUCTqkJaAMF +AXgACgkQgUrkfCFIVNZ6EQgAg4oxyelVDx1MO+aGv791eZ6U9RokdLJ0JyIkYgb7NBqj8IG4 +vnz9hZz9S0wNM0nGPIyPFLsJ5V2EvF6lELHLalOvAOQ4OS9ElLJVyJAhrBHxwF0tzObaRolz +C1Jud89EIFgTMN6lw854/GTyJQUdwgRbVSTu1sHqw8vctoqewry6Pg0uGbfvPNS5EeP7XXAk +7bIkjpY7tEWXGUWUey2AhNJz7jwH6Pe6TTHRqr45OnyKdWq+fU947ElG9wq9a8SVn0Vp1pos +Lk4+MszG001JIiQYJ3YkxN1FaC85Z1haUb5aKKeLo/NpZXdavWcOxA+XFQ33WHPb2BX9DhVK +850EnYkBOAQTAQIAIgUCTnkIkgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQeb4+ +QwBBGIZunAf/aMw3IGrpKMWuW0TC3pzA4lqTWWSOk/kh8Ux6Z8o8fTeMG/3xhvuedUT9Wh9F +CLR6s+/NT0c+K3Oar0d1rPylNUPWc2J6OfowEdERjlPXRTwn0eiWf1LUbcJS8RRoGD5gSEfB +YDyGHEUIhFx/Po09GRkfnOL3F1cGeMusTcKmhnBWC7AJP/bkO5Kqome+IqIf4E68mL8GcdKe +NXUnvUw6lVptpNkrFDVCTCLKjspFr8ysFXgLDzsyMXWOaNr3XBY064DC/VJP5pJojTfzNyVW +psWtz2AeRN4/ejmnwMbdDsDWkT/JUtFRb7eO5wq2KiPKVZqrFfsUPRFm0i6E2xSKD4kBnAQQ +AQgABgUCVW0bywAKCRAzAYvShy+fEXs1DADBOvYKxBrmN5ye7jGBvKEqfqEtSzVMDYVEaaPd +cKikDLwUYwb1enBiWPSdRU/A0TbYHOg4xrV2NMbh5lcX/86k1NfYUBtYkrEQrA3rY/jr/Rfu +ow06Y8rTW8jpx6FI75JkJ3e40usGfH3HcErTztsxQ3uQlFu7Mjxj1wK/lO41swDo8Th+JIGX +uBjgg9m/o7b3Tecckls3b+B1hzBkpH3Ri/MI6m807VhE5EgwkqIDTT4h9JY1Yp+PNQqPZE6I +Z9No8b+Uimnw6zNmpv7TY4HXW6zyVbxAI6F6LcGNdYaMcWqM4HWdwGTkhN5TVE6Cfnv/h9LU +QV8Obvu7KmcrTPRzTRLzKoEsXYX2jfnjZs5JeBHe+nywIX2ihvRJlcN7b2aF5m07RH3ue8Wn +93njlXcvbIOelG2xiAgCWCiXMagcsQ7wt8L/TPRo1ihvqR1Ntoohkjz6KAVOqcsydRtNhLSR +gwUb5kn/FGImrfYgxt3mbY5TUZaZd91dZPVyriflA22JAZwEEAEKAAYFAleocw4ACgkQj4Tm +7fgEssaiYgwAiIOYRUUOgv+dCG/Jo3L9GsAUWgwzhAvgnphbMz6j7BhsKrCQBYmhzjlqnIhB +xkxARafzUqEOEis5Fc+Y0t4OdWnX2zMa4CFdPvGmTQRR9sqAoT/4g62E1RWP/dES5aPYmqrA +t1VZLHCmAC1rhppxHnNtWPV/uQdmIRCC0kVLR6vgiY5NrvEwgnQRZDZ3Iz5BIK1wujw8UgJm +RWMKQQ3Mj3LJh1FD3VLGYaw19tYfO8ujZLG0D0oB3lt2zSKxpRqS7PtK/fH0zVEX7dBbTMPZ +YQqMfZpef+bjX1ZUS/tLiSVYIBnpdLDpiqJ85nxlV5q6L3eSnwUUSf6Z69XH11/kR13DFpm8 +0T5/R/Aofy5Awqo2IQxTt9pjJWsidHMTtEyWivltFtcmmuIcN5QNuqImqQgri/HS0yiRG+DR +gqlZ9WsE2lcHzEjFInH5PXvlYfflP/eHoTwOCvtDhSP3yfuXHvMtoWipwJXpA9ihUryjNv+9 +kWd3+IyL1QOgyjaZg/raiQIbBBABAgAGBQJOpxh+AAoJEJuNpwkmVCGc2DAP+OhZxm/imWBD +nQIfKMwQrYlblMeZP23WQnUetcxFFCjl9spbNsokkPP8I/2YPXgwOgs0oRFhZxjKSbmDUPzq +DXHdBjFFcBTN74jYMaPm5IObUvJGiMW/knUSbbQe4qsL1WKe454myVaUYijGf2b6SqWJ/jCy +7E7wBSFSj0RHUks/Qe8Ylw3POgTaJ7XV2tP1HErVruKMQv3mNoVvGAXvN1To5vyxdpt8QE/+ +rVAsrt9wHCb/0Hli/KwDqY3jgENPBVzIBL6zY/J0+B4QSRIVQbNwigsTNQup+2nYRw+tqcHi +UqBnAyWhFaNP1JlJA9p6gewPj70IMFH9wWRPSEnSfS4SCUCGohJ8tbsRHsPTyJ18TEOEJiGj +GtxTAqHcGMCJL5WwATN1vzU4lpKOtdzmG9+1dy4Q8p+1ddepAyKgwPFTxPaaNRLhho038PFx +4dAJK+g6CihF9joP+EtdNKhJrURakLJAK88BAqY6s2uJOjX0+0iC847vlvn/ukKPrhJgmGg+ +KObvUmRCE/VasLk6ofkfVf25MOcJA9dReIEgzaQPeGV/zYOpfKhoXyUzTr35VLc8knF5Zh2i +4l8cZbX2EIW3q7xIAWmfC92wT+rsnGLJPcaU3N91wgyYVjkXVRV0s70eqlyzC3+gEEQ5zgip +DXaewS6XaHk1rXpIG960UYGJAhsEEAECAAYFAk6nziQACgkQnr8S83LZ+4xtFQ/3XpbeC+SB +aLDpxiQytXwgnpmG4CoTukJMiFksqW6Bmi8dtHnz8LxCee9HC6JdXjnYBWVHuDiG875biG5A +6hJhM4XVFmxtX6BFva6K+Ji+hJik7IC0+Z4HHQIt1VcStiiwX8tQ+JLBPRJqwbwEJkiMhQm5 +Ri1DzxSQ+GAlCTL/OfJM+l4bSDk/WIOMsnLW73tHGiQhDjzVyJasW8q/PH/S8JZirrX0HISm +x4ewnNEPJYDsJJuHcuNbAPUdZR1NT4HOthAZ/iIDUzNR7qRMukGljp5Vya95OgEzvu15RUe2 +DUvIHuObgN7GUDDMwn+by8KAoGna9EWo6CA9G6eE/x7bm3iVCtTyN8hvb5qwYQXtVmOnZrdW +3s9x0oOD/qjXFG8FQcmSARdE2mlB17hEAcVxWuVhdWJ8m+8Bm63ZfvdnqCgDiQ+00K5bGkyu +qaAzXkUlnQM3RUdTGIWJQTPyFzPp+tDP8//dsmuHjE9lQWjmUAVOUuPgFk/Ntq/oNVqZy+N3 +qD/RolhwEbzJ1TA28MNiKKw1xJyNi789Nsl0GJ3MA8rby/lsYSLAJBC3Ka2xKSthKIFo3tWV +tuy5/KuEsgd/wD21Jxci+wx4xrFRhG9BxHf6Yahv8vHeBCm8bdTf16RV6uPNq8MYv1AtNkTk +pnsh/SBDM2+E4heagyQN13a/YYkCGwQQAQIABgUCTq2kWgAKCRDp2ZFyP2B6kYLVD/j7w6HB +7BiYcXPiV+OJTNC8gJH7PLEOflG0l7fzAsHk02/i/dQcSals/DFvNIP8jO92Y2Ys41yGmYU1 +56HD4fklaPl0pC5lSKVATy84BmNd2owHE2p6/ZuEz8rvpysmesmLgK49iibg/TjY59zWgo+E ++u2R3YlCFpg87KKpCk7ag63KLjI05hmshaUx1sQ8oCmqx+IvHqpG25cpSQKIDyssRglaCd/S +9uZAtSZvT2aG/0jywKY5sLajEt2GhW/g3f5B/3KYc35zW+qCZO08xkqrqTfdk+xrOoEh+16P ++bzQj1lzn5De5obLxE5/XqN/cpDxBD6uXMBuv6AZMKwr1q8csjjJB6OeWHF3SMygVdOSHNjQ +JaU6GC3Xzoy2wDm89s5rLOd5ThYvGgJs8cMrLXI7B3wsT0Zk/FIkBnBZm80Ts/SU9tpoAf0o +6bRlktgb37WbBWMG1u0o25/vNsxvvoHEzdWxuB77P2l6+n09VM/PqoQttIZRUyel/SAldng3 +zK8ZZ3x+mvZ+4mK3SgA9GoRzwnlC5TUxLUPQoLVxSBCKhrZq+K4aOyP6sn/RUeRy1SfppqoV +eHIHP/dIJu/z5kwGbI+A+1zWaJvSKH8AkZBiNfFTuG64GhIaHYXNTSfdzUK01g8N7nCm2OsJ +68B84GjxOX1DEGHIJcNvQ4e1jQZmiQIcBBABAgAGBQJOeo0jAAoJEL2gYIVJO6zkmz0P/i/b +Ro9JKBKiPPAyXJLtDVMw0VbORAEGJojmKnF52I5I7thcNnG6QNlRb5fKLcsyA3lmgSOse/5v +vDiP4WucnE2+IlbgYk+R2nc9+h8Adap1KJxliX2yKmW72JqyeBIiBtlW/ha9/1oMO02uTKbm +eoYoV3vxjk2OBa5/G/9ppOin+bxf8yVGM/uDvzT3Ul8KQlBYMCWyqKT0mg524mynIjcSnKuj +LBkRqaMJnpYDGLujM1L6zo1R7XfvYdmv1tqaKfLKAeVwFPN0+bRB1tt+zOIqrQuyqwfG0uTZ +/MKJHu6kHJR0j9aznOKorLJ1VJW4qEzRQVuleWr9+sFzxWSHOy2tQOYgtjaJGwJ9d4Ze5Jke +erQAPQUPsdSJ6wpdN38qOR8r6bd6b1sksaJ5StpEFzjlA5xxluSulJYIPDem+9wNQLG3vdL6 +XexD9jhfhC9vBp/kpEn/vXo24xd+UYMcImjTdpInMx0UyIPHJosx6Wy7vVLE3fefCB9ZWxAW +k+fucPMwnyZca/V/VUf+hNImn7TUQL9wWHjGdcfEevjfJBLyFE6Q4pj7Z4uHZKjD5wRwQTpI +sr8G7RNqGfqoUeJrTZsjBknkjNWdoG7IS9vEnrvVCA1qimkPrCEWTlSeBw+1GH0WWWTT0BuI +Nf/NQPQh0onQR8hP9jdtJlpjFc7TkoNXiQIcBBABAgAGBQJOi/2WAAoJEMJ8kTal4Tf8hGsP +/0wWH7zkXebhxgqm5QtAZQaM0BxlVd5Fd2RRxs7ajUpEb/i8yDyI+3KvE1la9auTnQWaPpQV +qBtPPL4VQasjgLmEu2CWZHhcjIpQrycy4gPO1ZNtt2hsKaz3QANuAL3+K5PUK/41OYEqtH0B +z0DVzK+jRNePM5YLLsCLiIbQVMRigddiAEHDvJddMgva3SvlAjlU//BM8y1Kia21PB4yQlet +vbMKaHd54HgW5rw7ZtVQD+iKByK/MdvVOa4ijJ/BtJ/x3vLSKimkuJHBH9qlAmskZk/0F15A +qQmN+us8Hc/sCsGyFsG6GYdkfa/Kvv5erFL8aiSybU5H4A5v8WTJng/k1j/ET+pWkxXQNlPt +vcallgk9nAnNXgsXJaBXI6MKgKLdQ8k/z5KBcVdxkYbxmu91+zUbTNure6xZ+fMhdlyojd8E +sIpzvpNFwf3HCVWZ3QmD3GBUm7NqHMhOtIdut3XwlvMojfREfQfkQtLx/iLw6Et3yQy4WXcj +yxzkEVnUKGCEy0DvE2eGcM7BK/E5Oatgo3h07EhoarDigpveE1pA0CPfl5U7uFUU9PEZs3Bu +ssGljMOrgsI6plsCLBZKgN0WlzaDmxWIx23v0878alX3k5cOpJIW3uFQP0YVJ2XVUkq5CVVM +dk0CcdUrT73yLUuGcnwYCTmfcibeDN2sqy+6iQIcBBABAgAGBQJOjJVcAAoJEHz3bBpuLEzO +tdIP/ROc0rI8x5aJrD3LAzAZaEx6ao+l+6l+vgMPMSC0uoPEFb+Xk6EBkU8aI42d0a2WoqQX +7PW+n3qazM9/30X66YldCt7SafuMWPwKUq2y1X68noxkItaxt4Asvy/KdvpScEqV4YoeyPuj +SmybF/1bXF2Eh5VN+dn5u2zUFMU4crhU/uRS0auyDbUoceV9GF2lI6bgXyIXOMD9LG6D6hAE +aoohrlc6NTdoQlXVaZYgKAxjGHd01XGDeLHWR6BHtkj4fD5NnZAoAjO5JBG5ZcCShwMfeYTL +FO0Ueu6++N+G1lpTt3ZBYwdzvBv3pjwx8iHlCno9QghNHdn08P3frZyfoOhEegK7fIpfMO2u +WZuJ7Ubs0sC5fwUVCoNNhUMBnqHDO3TiRRgcLxQKs/vH/XXcisWqjAHUN5oGRb3X3s6+9yKj +XUfVIJ+SFEnfLJ7iGwNzsTqV9O0n2Wlg51/RwKYiPo6URBttQS/2gIXfTgh+N2aGoT4Sb4SF +JMxQx7W1ADP4omCUv+0XkgcgkmctlQw7gVZcxwErOcaAHKsH90ym5MSkLMfi3CNDaWRIxFRs +YwV+Mvj8SxbiOLtTAtc6IoGm6yESeHOl/4Db8VVmHW4VFN/wlWt/q6ApCbGAfHEP02aSx3en +sHops3QS3EUMVJ4Jnt6zqCCBj0/8EP/I8caL8O29iQIcBBABAgAGBQJOkpwXAAoJEIaOsuA1 +yqREGtcQAIQqN3Rq+F0NMTYbjipOrAOS/3lfRAE1Kh/T4lHg6vck9+g1B4yuZrrB7W1qUFYP +rifZxXuAEucQTr3N0zpfyJJV9U9R2ccdcp2jwdo0aLAAbjYQxu09aNrzuP0EVblpBEWTRxoV +8HzsDPuERdJ9vDgP+CB2b4F2UHMtZyMQu2WxjjwN+TrLRl75sa5aiegq2zETIeN3tNK/MSsp +cWC+ZPXuly6YbHVyFA/yvLVaojmPi6ik4/b/WeNojJ0bvGWrc1gUZ19fhMp307335SpA/Xlq +EmNVNi9JIPl5IHDRYjFw5N5Gg0f1vT/f6HqR5ZjNpskleukZHaBgt06IixuSBP/8SvkSB7vs +O6myScMgNQnCTVv3q4VVPu/sMD0973rvHTAuQr5s+tTlo/an0kDiU/p8QUuFgV7S+H1kQjDk +g1jb8EDYBhL8zFV/7farbZUd+wfBgI0voq/SaHGcpVVk4aFFy89uaM2eywUs3smWRenI4AWR ++mX7muM0n13PD4sDbnGHzidFKQgNqtT5RueVT6SOn8T8R3G5dCZC49qlO+/l0HL1DQOlCXrg +nA3z7MUQ0zGTv43r3lHrM1HZw/osWi16TIXUq4Fbeq2Ddt23BXRhpz6B4q/Cx5d/exsIpnGu +yuSFkFnqJEsyejpnukSBhP4GsJUcPHV3YAdWv66rEdVYiQIcBBABAgAGBQJOoD7EAAoJED6B +fG1n8l2BE6wQAK/uNyAA0SNA21BSwVi6BmTd8BQvhb1ZjF7SmIJ4rsu8N1+kncEdHedm2sIy +MBilqDfy4dwOgILEET+dJT21MbY/7T/LUP9KhPybLqmFqellv1R/o298ErUo/pq5dfaNAdL5 +CvLJYK3f01Q6ovNQjOyGr4D23Dvzq4uWgE24AHklynL++Zwa3zuYT8D8gbyFd/i/NFrNyTH+ +V41jLPFw8EBpA+Prk84ER9BOMxNSQuG47Xa5eskvZBubbW8FIwJTjw9qF/q27NTQ7z1mw9vH +8QoBAlozGDpl5al/c+joZPo5IoBn/nauDkKPtpMbeZkiSsl7NdWfWGQ2f32d5duOTcNqyw4G +14EPIaJwKlZa2OyVCPztD2KUBkUzn6vdGSwIEf8l2S51V1pe/bXKS9vj2ZXC66oJtW40gSZE +Ngk/W7QSZh3ZZVPsm6TImUSeMIlD2Fqc7T3CY8/TjdBrGmJ4FteqT427E3Tpur4QBtxf7jC9 +kh2q8xP119KI2qhtLdREGmtlYBtsR6Zc7AH0h9g3UPItNmUH1xI6va9z0c77Z6IYlN8jRutp +dtuR9xRgD/oWbQ84AWK5jfRGQH3kxgygPpHRFKD0b+lQaYZvifbxwmW4bzFtMsXytkJ/LpPG +Y2vGuGoRL1RWGiVDMqY+7fa1bkuSdUui6pjkiscy+iQjDsKviQIcBBABAgAGBQJOpsyfAAoJ +EOrjwV5ZMRf28I4P/iPZ3CwR5g5hMp24ZtHqQ0h6E8dn8n9pImbsKTrTo8xbUVC2T1NUloEf +vajZbd4GqsbBtG/eXudv0rTF72XZT/0Lgoor7SG0qJ8sY4KMtErt1QK0jQCq+HpY2PDgJMFC +3704Jh1hrmg1KYSauPWroBgrl2q6DtV92Zw5rgGPaT+KU4b2YqYYWj8FEkxjraDp+fRDyQSj +T9uTu5Eu8bUx4Qy/rARwSV9/J1FPQS31rG8L5UKhSeVdU7Wa7MznFY+5hhNX45yHthu0hSnM +acI3NQ3cv+VEJTddP6jkAT95b/bqrxq34jOqaOamQg2VP/dza+pueGS+Fj2QvKeGK9Bq7kn6 +1pv0cLwr4JY9+eb1+TIT3598wbvamqXsqvDS5DGtBQwyXpB8U7Pjiy+v1C2tGo826dO9zQkP +TqhRNCNSgz5dzvNSbDY5FBsoEgkDaOv6EDMB8tnnMg591iZ2PsEcTSJflTqi6pazTYlHQ0aa +x5Dq4DUxXH6dmfOBdpNybfyM8EtIm6IxDiBafIJC5id5OIV5Kp40gOorF96YHy+2mgAKLPKQ +DKhjP7AdX95G2R1aHhOOL3d2gSwiDrNF3jAOt7fmg3OH8auUtBRzPLIctsFYT/wBmWu0tZFX +QO2Xf9iQ39AA7TL7QaXeqS/EM0ZeiWgajfXCk0Ov85PeUpUugMH+iQIcBBABAgAGBQJOps0X +AAoJEIVhmRE6Nc5e2fIP/1iHyscgEiQGYB+emwdKvdhMPGzEh6SiTRrfkwiposx68NfB0NHd +hQTZUuCpc3xkZZW2WCL5lteAsxKh/84jHV9hyo8Z+iD4Rcm2sXbKT9Rrj89cjB4dkqXls5JH +aTd05nYh95JZNIOAz6v+dNZm8BnI1ckmQ1TAqt1u4Fm9uGd/qdN+4IWEGrlmdTn2A4HUYi2A ++ri1nXuSWIePQZHzk7NfB/wkvD6ydpbJ0G1/o3zvywC3R+Tlw17N3t4bRMWAxfqjdwlyxejD +oaBCgViKVwSnBGWvC+8Lqjf/tIshA0RPqybPQ0pIaufjj+hOn68rFXPXl1iRj4/rbW9S7HLX +FoXBhvtmEGI2/1Q0st08pxLA0aNxd5kHvGBWo3wLJ0Wlq8nSdGvP1x794fxRFN8ViyC2J/WN +asoLAPRPFGToRzbQFoYdfGNqn4DiAiMEifspqHOixY2mSzFER7wUHneksDI2PIurzSkhvuqa +iH9CEowdcAHk3WrkZu9gBDUtI/VkT8j4Et4qBYbQAOHer1iJ3VEJqhZ27+IpttWzBAN152bF +YomwKUESd/2CqTBkzl6wYDUTN+WZIDPC0J8D0Fgm8sLLHWKSbhOq90+UenrX9/nJJlGCsJqL +m9bzyBGA5r4Bg12QIbVHhNdvyRA0nFxHCaaxo2W1rzjVWgKq0Qa73ls9iQIcBBABAgAGBQJO +ps7kAAoJEAbKn10dzyZZfWQQAILYruir7/8PNXxJW/lWCB5sePVbNeEkgD2hUufQwVJ+hodr +W8H3foFZSwAotwmmKbEhdyG8gO4wgEdyMrWY7V6T48uN1EecJ4tg94J+wAcKFLZ2/L/IvKIk +OYoIbdvRTJ2C18F/YkhI1qYnAph9eElTWoYjuuFrRdH1yPxJqMsvRr9jyyMj18eKcw6u0YDq +uszJj/NOfj22P+G/eZ1/PFkWU3Kik6VMUSDsn02phCE5kSksHrkxG+1eGqkitIw0mORYap+W +qe93ZRJF8HLqJ0MIX7KlG9bVYy6yZjMNz8B6xf7dqR+OlrXpb7iPRANCf8M5io2xa4B+yKZa +6McbEa7gtN3SKfkKLcJXVh0b+1TiEICwilYxam8O63maJrPjuPDHBns15VuRYr/LOUTfZ6El +gXIDgG/4L1MXpyBHCBVxrrU279BNjYheW1I3xwfv89DE3YFW3ibblzVWfkq9d/MObV5HdFdn +gcR6YkzErgtu7rqT/u+idOwVK7WJMhjWfKP4rrUMQnH9niZvCdhuUj/BMU0ow8Bs5AtNwyzV +/v7/YbRG6VF/IF3v0VyV8demoqKeuH1qWqdJgt2pMjZ+J1noNKhgsinWR/X3LmPM3phGg2HX +vSPM2RDSvUTsm377cthKnhX69xK1APzd0tiLeoBRIqvrR3klrbIrnvtG/59MiQIcBBABAgAG +BQJOpxIlAAoJEAfQRToWtzYXCugQALQWTa4wn2oo6JdzuU+L5zIN5EPapzXQI6pJXLq9HOAw +bv02XbMxLQQuCUIv/6IdlPgon7Jrap4MNHHe5qx0d8pcLqaDwkTGm6lMhD/2bVW0ynJciKeG +gVQpoLNSCztTzKNvCmYKaB/R/QdTB6zW6NiC5y86gE1l50QJw/INoFTrmCckncppDc/plyOH +c7ocEodNFHf9H6ZRsdAE+qw+t3P+p2VChHjz3AeDLQCKe+T4H+dzmL7aljc0YVxVqUysbl7u +K4x4pgGLM2OkjTZ+1gUr2CmgxbjbMLRiW4aNHICPdyaJp3VEN6XIRjUmVIZ0au5GjvUF5aNz +iWN0jEtbnEbpUG5BsPNVmB2zIl8YQNq29KUMULut56xd81eo5azDJdIWkBLdMZ5LIxCSU0q0 +ueLs9zDZEOXtnPfb0xUeMBm9FqD1Q1Yg1wClf1tHqyvaxLYZZV83WOs2zHrvtoRrnm8HXex0 +arHSGkiLRh1xWwR5HyX6ZsDAzVYtp92/mOufEkWk3BnV4QpEyp6CrXVZYjQ5rOHs5ohYrv1o +dCXfw0iYEp6bYCzH6YxdlLLEdxr8fn6wWo4ANUOQ4PV2nafwdl6arJQMY+vc6nIbfNNC2eug +I9mg5h9EWJPFAQf2HF5nTni+eAM+cMVTFVk4SCkatYWMVpyyWPwQCXjOcVR2GRtOiQIcBBAB +AgAGBQJOpxPhAAoJEOa6n1xeVN+Cu7EP/RCajvrx8FB8g3TFJG2Ik06Ce+Pc3Ex52yPgG90Y +nPad3WmtZu6yk66Ad6BOm30Ap0MQHZ9HfYpIVPPSe5OiI8r2/8oCzvJsb84I33Wsi2LJ09KX +WdXFDLYMcTzJdOR5FmX0iuF5X+JyaTbxCkbEfPsQ54/XieAx7c4dD3GYXdhkH80rvK6beqaz +6JDyinDJWOFmC71kR5SXv43qYUnTkibEzS0lHLwZpqY+I1Ngp8VOgkCnmaqIEXMe6+C43oxi +zHEJpBqey2qdLbQPweN/y/q6SNDieczAR9hY2bVO/M1Y6Uggz/7JEVgY946yaJvVFeNJK+fj +5A/2tRpuWCI3JQqpj192MJqUGI5wsm+oMyO1jWwX9ji9UXW15Uw1EGkZ0VuRK6aPKvV6BGQa +E4owca7n+cWJJ//WRADLWJv22c/Rm3Q+qAOwxAtcxI3MejhbakmrTSmu1RoZxNzIzszwlSno +pZrgWvdlrkHR1oyhU1vc3a3VD4sz5dQOz8BSCyTu4QzMEBXcfyRfOOZywi2VNrzPkU1cHktV +p9Cpuu3+EvAp7hNNxafaZcNphviDbllbla0rtslJUw9UyBs1wVuBs33Z5Lo3neViqLqwOsLv +sb/AxSXJDAfFyZn6oA4yzFWdaDEGcRS4HAEGcpdGRQZXDtXp4zfHzNUTFgjev+Hd0DIWiQIc +BBABAgAGBQJOpxejAAoJEJjl3MgWR7cq32cP/1hXuaqvCI7eVB/d3iSQgzvGBiOgv4hwYjyj +OuYuAxl2sgOF2Y3WECXbAXZCvh6vNiga0VYCuTH4l5/oYhenxbjJ8pEFYQP8YuwBrTl2lnr2 +KIDq82v3HQdgJ8Dy90pZHFwMKqzYfKzf3T28E81BYz8SjhF8A73J/h2c59NjPtqA28b0fq3C +It7gyDX6d1i/0Ly3OdPRgi9rqGvwCuaIys8QAz7qOl6BwkoEcJUBqdzLpKkAhide2F1P9cLQ +u70wHQqwalJQQdVkTFalJbNRj//9sGND3VgLRPnKO+DxO/n6XcqHJwpT9LXrzb+1n8HfFTmI +Fg3G1qkFLs5buJ5GcDe1w0kFSXAm9W7+HsWnQkCey1KKEbS/+3aS6b72Be83NBwRZU0bBxqm +W1I2kf5WnNJaoOHB8LwEmbWwL+P2k++rV4Uk+V6AwRiKdwCNVM1WFagufnEj8XkSZaF5sQBO +sgW23pR3MnDWd/vT25xkRXPSAQOw/NgT+O3K7QhEWNaTAhrXflY53CkifZ5zVm3Q5F4kbpXd +9s5qX4tNih/a/Q3mIXw1P9yauS1dKN77eynk9gFcEwl3VVjs3lwdF34BgCte83iJbnTXwe/4 +1NuNHEjVQwPjS9K9eTI0/fKrzRduVeacob6Y9nzGAQ5YadPnVspXGaCM5Yo8SVdP/ha90AwA +iQIcBBABAgAGBQJOpxiYAAoJEMzrak5tbycxoaIP/2cyTUFhdYujC+NFa2keb0nGjcleG/Ce +CZP9GA3NReM/DOxN1JDFxRR2E+EfR6PsqRsipg/uDiWXAE4niMNfLPKAGrUg+VuDun0T/cEe +iklprzfnMEQOOpj8b8+EIduBbPFRIt2ImuKylMC24hrHEpelNwMJEuevUC/Ww3F/BNUjOnYk +nXhvUBupdjr0rMJlnEeJA63S0EAmIBC7MlwhGtU0VSQv/vPVPUCjHRWXP0JGfwv976g2KZJd +NwZExO4OL7/SB8ZsGldXFUA2ORH255Pf9W5h9+ska65pH/iXdp/1BhE0nAUIHb1KjHi5DqAX +o2/7PXAAxamYGj4kLCuxIh5bGKhjj/3t36zdqbsetNjUs6wAcVrIPkyMQq7S9UIw1HP08la8 +exy5ZFF3hw0fIidqJTiz/nL9v82vGKUf/3sn9OzPnjqkV+pHKqopgl1rOQMa/geFEG3qZPo4 +AtGhxZ+xxeiJBBRJqEBykhcGomy0RoOqKp8THDbuaDOH1nfRqeX0ES6ISP81OHvGwTGJ8Xey +KhhGcVbN7uxSj/Ld3ii9r8YtMUrmQU7yHsJidath+rwqSLj/XXI2T87gjyQzv3R1txBqLUs+ +xEHP8illUiLrY7Pr62+0QW1+xJkiGokMa2R1xsP4801gse0vM1kJi1jqhCo2+zJApAmAJMEp +axuriQIcBBABAgAGBQJOpxnQAAoJEHNBYZ7TNxYM1+AQAJH1uJfDAg2nktr2D1YpmSzxv3Pu +31FuE47NTgT+pL9vKom2m/kj2/O43RDCOgeqC79gQqrd0E6X/0kTwOhkPDanZ6GWoz41+68B +NM9cE3bSVZYalJ6VMwEDaYa0jQNhxxG35kOPeGNf+iIgVVfHtvMcmo6HyBn3p54TnzyNApI+ +dpSYUuWzKbeLu0ZaYkXXgPwLnPQnbQLtCC8osqeahDsvsS01U1xicy+LoxfpyntKpZPym1uj +iJ68IndMYoqWJt7c4QC3bMPilE1D/pKbiYH8Pb2eVxk0VklUvLT/ijiTPa1QFZxepdYxcyqy +FZPTnUU5AuSOcHQ+IaddyF1AinRE6cHU5n9ba78nNbcA4w1INE4erXZDg3651aM4UQ4jp5KG +dZi3jdOOK2/VhIL2W3BbNLLgEi692kc59D3Ydo8xatV+7rKc5j77Nu4oQ9irelW5avnL1Sf2 +yDgzn/JHV1mL0kQzCim7pOZ9X7fzJK1qBP34poQbu27cKeCWq7F0UpKN2KmlhyiXsB39XG0f +SIbJkl7YvRfBnaCAZ3GuiYLbzVeqDT61hKaxtulXRXoZKp1yEps1ydOVZOC9H0n0/1wECsP3 +2bR51XaYJ2CDk7iE6KoCVZnyYStWv2HthEgpL4gCTuXMf8DmciDRi7+GOPwFWSUqY+jWfulc +w7Tk39BFiQIcBBABAgAGBQJOpxocAAoJEEFjO5/oN/WBVlIQAK/6/4V7V8/y+A1qceAYx4zD +GmvNeK2TJtln5ftdfyloz5wDNiJxzIF4Qs3p30Q7mpib48arRnpOHXCEQUPslmwvbIAVRQP3 +bBveSdR1ebWr62m9gsp/Xujh7SN3gzFAapvaV9t/7bdy7Cc0kRy5vuEDEydEa0Hh7ftlENX8 +gZDZhslOBWsOE2toCeVE+MwaiEkyfjtPKrFCA6LysUjBqsBvyu8/nY1NfuK+dBVNq4i1BG6J +UFlVs+VitixjOXJx14CI4Ys9JeBLOob7AH271L34Hb2SJoUL/qWsn8bgIK5H9KghRSVQN3Pj +tDkUR4DVvtdzimHjeBPv6qxdYdAR8UWN/oicBDZQ0glvqD8H7xz6OLHbaRNjeLxbmgANNuL/ +5eGz6c5gljKKjev3lGOUFaRiucyIyRpQh9P6kaaAKnxrH0NuUuh6ykPCuOsdO5WKdC0fFdmU +fD9i+xwKNCjiEE87aaGsOqz9Y61OL98BZ7wJFhRJ5mN7f8DFjYqVKCnLGbJtIqYGGvmM/RFn +uEleLkKEmH1wpmFUa+iAS+pPPYWh044CvpJeAY9CLWp0XZ2eSx7H8c6U/+XV8vOTut0GY7w9 +0zc2kWyzbN16/5h/MCkIkSxeCMSHXrYjB/o2aLaC2aExpImFtTv+ZkrlKTi2PsrgeTGawNR9 +W25yslCVrOeeiQIcBBABAgAGBQJOpxuYAAoJEDjbvchgkmk+MrIQALz+gqa32mh7JATT3yt3 +1izi0nP9f/k7T+960JEgOHCNA8i6omJqUgewbUaU4UqCwWMDRcowz4Ov9b5dekVvXE0R3Fae +7BVT0ExAahuEu1odS7l7HxhvvJg/1ORD8+7Ub5KBe6N1qaMgnemL+MIfMEFMJuB6q29Z6TCO +T8In+OdEs4B/XIr0/bmEGjXxp3aperS5rxiej0En1vGqNCjyLX6IB+vbWa+t4Gos2IsT0J2P +ESARMNnZCsCQXB+ZUGX1syjIyN9LPCaNZ4ocbVgh0TdV4aaQOrH6qUJtJvNFU7Eyfnv9IFjr +sNXysvoJTLBQxUTJJmvBdwoBlxOUZpGVGCfL3nqdU0O44y8HFKcGdvfDohrlszGpG8IjsY8v +ovhv2wWKwFIC3HPQfd8GYh+rWTCWR19BYqpkuG/bp1670ngtn1p74rGx3b65Ffmi6xbFAAJk +QMOx/9N7Cq+k6i9JEsYqLYwwcBe6FoC3pHCh4IMZzHp+Jb4KmuJWDiuezKqopd4c/LEzPIsr +3KAPe845WFaYu7nav2Zaph6KJfez7nQBzLIUqhbsW2UcAktdhKvh5LkjGsIFLdGRJI9AUHq2 +wvqqi496c3Ka39U/DiS8I+ly2JmhOI8cmWf3nX0mZUuD8E4hUSzyUpPkh3xo7XRdtnC0v5Ow +xwevA3w+UsKpmfBPiQIcBBABAgAGBQJOpx0KAAoJEPu3V2unywtrF2QP/ROPwwtszeq5INTq +KGsnwEHW6pXUTS/jFtGDsWrN2WdQsP8WiyCzD7YpVyz7CfSVMOKrGbBuEmTsBq7HLf80YCOA +hYNgeFxZCmheSn53ht8ExFvAlMncHhJW0ebWhVQtT/lLOVyNX8SW+RN8b7dnjuAPTnW4pWwl +thUugcdizJBBv1JWEJkunPLt32b0TjmvYW81xvSPZlm93CQ8De6qcI/z3VeKhP/EXl+4MqkG +1QMLgVvpyrceoyFTlTeTZulhsDK1UYkpoWQWiAQqO5r5oU9kh3gkoY7JbyxTsO4iavfBKpv+ +jFwsdjdIpkum44BI5+iyE9Onlo4V+MvtjhPup1KZDo+CRnEUjZWae/GNVSVjwg2VT5Y82xIA +BOx3T6HlUEkpj3/pR+8MirX9564g30ABg9jYmpuDEag9OhJ7Zcm2kyS3LTdaKMqinkZ8PxUa +D6f/O9b8H+Mv9CQGxd+Wu8NibCUxS5m6MGgwu/DZOF/6/XJ60JLXAgNt9MLD7/t2kqYh+x74 +3zarjRi0HGlxpSKhr2Qv0FJhqMQ+7RT3KyoDKyzc4XrOuorXWqlXhLN89rtMP6NiCEndeyUD +cxHYKOacIDKVWcKKj66QxXmaARQEzvVIieFkDYbhCGVGWADyOzWMiehqOPGgu6vEfvkblDok +40/nI6/Qm2ExoAM87rJFiQIcBBABAgAGBQJOpx9rAAoJEKhOf7ml8uNszEkP/3US4Pl/2wv9 +6SwtJrNtCsW/xdA/fN1oVuM8WjBMAu6fObIEcZENTDG1LMF1+R+65I0etyQJTxE/tOb6XoTi +afLeQgQkJ3MSG/U1XRQ5733lhwXQfR1Q7D8OKNbHIk4pwVL93K4W40uhzSiijZE89SHzELTj +mrmV1L3D3qwtyY4T9Z3q/GMRrNx2dbqZIIqA7slhI0DT4ha/ny9fQeD7Q4ICLRR37SOWWfT0 +SCJdFEFVUn0pfPG0jljk/HYXuGAyiTNwPYUzcrC1aXa2xNT/g1rpoHDMkdpBbp59KsGKA42g +a5KXTBHDRUaPeF70j9eMgxYpKxvrR2rmks+vcDKG9QBLecJzrspVuXADhnPnLZsPARTroKWk +9DodCwoyd54guyy6INGQPl15Z1t8Z+6h76wE/sCnlyPK1RZy7Ihzmygp2wAjhT9HwuyDq5aL +5yMVUY3Yck/oPjlopS20ZQU/mtQGNUUDqCARrWr4JEFrg6Kql2Vt3h8pNIzV/6j1Ofoj8O/g +PKOEocbtiW4J7Nb9ZnvqsHQuMkSi1FU+eniYj6/rhz9IwgkVDVqyrN8sYdqcJasmXX3vt/GC +JAej8hKwRr7+cIeieqFm4qi5P7wuvc3iU/k+F6yjwWtTv+pdoIpkI5MDwOTGsJejpNmCY5wq +PRzTxnv+RW9xTMPJKYhPqOF/iQIcBBABAgAGBQJOpyBsAAoJEIwa5zzehBx3aTEP/ix18BDp +qcZ+sRm1gEzkRDy4ubVi1XzV1QlEfi+61zOwx0Qk3UPBtGWMvl8KXrVLa5shtdoG+2+LQNFc +PP3kzsPPmalNAsiE2ToXRuBCxfc6eVPj+bxjgNyRbsXMQxsu8ygAhzsZR+Cqj2Nq+TVDChR3 +z78jfnVBtWzo4pZNAs2k5ZCtdxy+rAINgL/NxUItV7+W7c4hdSiLiIVXvWdUsqOR+frUutvN +DsM1ogbApjUWYJVEBCR5q9jQqx6MFxDjxuNhU/UnXwlgoAK8PR0JwLYGMEowNmasqxkunlN8 +iPBQ2F7oNq9DsXYr7VeJHedJVHqIeJdGBRwybQ/Q7HmBw5oNpW5Ip3gQwj6Cd91W4HL5/T/e +Qn+2HFL464rb1+hgMbM4Dd/V5xje5CgkBMm98BmQXzVo9eiZUxcivD9yYsbE/iQgMrTjOALS +Le6sk0VbL8DoRoT3YMWq8AM+Vi0nFL3jLZR9YAV+NfT6pOdPY3p4L3XjqEzc96hQRopNQQNa +NQ+pJQ1hBLXpdmjHkPOdaMFFMe3z1pYVWL+33DRIWiawjMIuAyFJs4ybe6nKZ6T7OUwvamkm +yLXawgg2dGynLK+6EZVsAe7Oh7l53bUMrk7l5OaxOtY5mzAHoKkfHODN/zcE+g6B4PQuDXbJ +ogV+u5lYzOWLPl4HGPxhHBR5MidRiQIcBBABAgAGBQJOpy7FAAoJEC6na5wrRm2d1JgQAI4b +wCdb+4G/A0het5dlBINs1Sv3poYs5hiIFbmle3YdFGtMgblrO/wqyj6IQFFS+5A8pxzrN+pI +s86/IcWaR38tEorkCD9xAi9zAQnb4tuHorVqvuyAXaOScxIk8sZgL/l0qcyw2YUNUNVXDAF7 +X2PBDH3YEHSB3ThHyzR1HDXPjjly9nbC4fNVpVTeQu4t9+TUh4E3nsueCBFLQDHA2+b6td5Z +fw71CDN5pMLkwxDOVFX/WLdXtYieeGVKhGsxYt5ozN82xZumwsnxb2mNkI0XPz3hD504Sduw +AcEHqlIfuDd7m/YNc2frARj4pi4O2FtGZ0MaLrnUcJMgBAnVfmcRTZNs879xxpkjUT1ewDeV +fkBmE+FuqzQNoaLg73c+D+skxUiKkqCurn+zmaeHNjPeouudCQdXY85FUQE57NR+EcmfssB2 +LG+IxTFdTMSGHnYSrbFqy8YmFJxDwZKDDI3u425/ixvBHsu0DGyYJdGhB1AEM3EjMy9V3+FH +JkRbir/YzcmzZzRL7TP+f9QsGhhx+2RYr8VMhyKXClljpSB1cG3hXhNXcsrjvHrB+novd2FP +CiU7+lPUaKf6cOhwc+cnG3RAehXgVgbSba8fbsJVRGSwwbaTYWDzHJa4nUfGrSbNWjMfl4GG +duVCwCop8AN8XK4cgolK28iJfutBGnqHiQIcBBABAgAGBQJOpzeFAAoJEO7pYBp1xd49gT0Q +AJbg47OFxsJZ8xIwhytrCzNgH16oXzaq6s3H6yW+B+E/XHLKBnMVjMIoQTLAQQydPWEB5eFV +XENi5IV1d5hBAlGowQrBsRrN2iv2VDXXbgBTT0EYDtS7XpwvqdTYgPJUv8+XOxnaTPuN2a4I +cB+tYVmlYBxfU5Qn8hXjNEGEBBREa5uP8mJ0qo8LwCe0KXlKIokTiYEuJ7E+kQKohHkcKPjA +Mvy1tFJI2dv7NlhJHLgKJiC9yoZJSFXTYtkdtP57klz/alWk2sI9sdAN0punAifDpX3xntJt +z0Z90tsgwqQbbZp7u+E01uLdD1RdLfRBnNIjmKnqfW1ARo9KpUJNiIA6esySNidqRN1Sac2M +JYZb/bTpVFRn3ckIotBJBs9Z70XsvgNGgQKgpz7RmoVoy8dWOt5yQh16PYSobhrfQk51JEGx +l8dGk/JrypCdmAotdDoIOeT1Lar5LyaDk9MbNxUYIxuu45Ap+0eiF4SAGSwCLBaTXYJ6ce5x +0v1LXSvpYdXoXP74QpzTcH4XtpqEms7b+opk12dgvZWyU4ewNrVdHB3IQizHDu/1Lz16egqH +q1h0XCmxRQhCqt+INaAaPwBVHW4vKSLwuw4gqK9E4Z6kdd4dFFmqDzfS+Bh7X3O9OuucN2o2 +jihhRz1PpwFbNVVPV9ohzP6Vl9phAq5DKUtriQIcBBABAgAGBQJOp0BmAAoJEGO08Bl/PELn +7kcP+gNDFjQ2MEaNRfTMV1EYE4zZOwDQxqqXFO4gtcguurQYHKR8CwiADmZWDAnPTFUW+X1T +l9NULGQQtJfEYtUuG6oFVwsq2hkSGf1CZu+W0mKYJMbxAMgnRhRDb/BlsRrbY3IBR5kqFljo +kzCDl7LSTJIaXHdTb3723hyeO9GyZkgXUxpvU7Gyoih4DYDy/e8iyMH4WiGjdBIX3M+jHYjy +L/eclTMYrsNuDdPc+2Yn2DKlV5dFiG7QmYYu7l9DG0F6jJ3Jy3GRncCbK5G44Jz9SrEs0x4W +71t3Jhh8Tej9JuGD7eO647gomgNvabq1ORgXiiyPJstzMIqVG7PEKFzp3xN2c499OxDYNZHW +Vad92H4FX/zU0upLXL0KLyepA6IqYII3K0MsMc3h/FAGgJsoo8558zqZplF4tMCAeGdNts4k +Kr73/NZ0gY0S36MAQBz8p7cUtFAfcx3CtZ+c54Bb0bu+JnZHDjOnKn8uM1SxmS/ggXwIsjNw +PNkOG1KJ+z+rpD8niNHAEI91rEhKkqOpVhdj9bnYL3clhACf4nMFSm/NG/xsj70Rlf6lXSWI +6/j1R8jtejOz+QITnfrnpMkKESWyqu9bsYIn/qoHXge0nAlASpgDJDh2Vydid1rG424UFD1e +Tq5E0aIuMpc8kI2KlSLIt4SVgJU30qPijvAlmz+MiQIcBBABAgAGBQJOp1yEAAoJEBzlCpMI +GLqcnO8P/1/Rn1M7/kxMUPilxwNtaIXhNgLaWucKUvKzAnZW7Hbb6KZOYEXw+b5CCMAmQa2r +hXCpSRZSXL60rBfSe6nQ27esKbrofZyWuI6eKR9QUIOqN/aInp/2MalUjlFKhVPOMxttjT16 +beJfbWGN9qFkYfhLmwiWZncoFkYuDDSSSX7FqD2ezd9Biy+eBXm48tsCpQi/esaF5D/8FCvK +HisP5wFwT3MmKYmQPXNCL4tWgPiRJflOu7FUXxj6Qiez6rpQ0FsZ+CGERs5JRctzPhUS00A/ +JJ0lFEmM8Zy2CEk548TUpWSPwiLfOYrgx3goIv8Eqsc/kZUhc+5YqcNP9ZuvkjgrlJyxAuTc ++/03ZBO5uC+EOwaFGmzPKNya474LvgBB/oEZRKFSOimAqwoIpP7fIAYCpezsnymEsaNysJZi +GzWs0ws+9qLl6kt/tQ1dyNKj6f9V5WlGl4pXmIVH8R762NQqFRsyzHKY/I5/jiFhrp/J0QT7 +9RZ7JW1IfxiPd9zDDlMPbuIdXtQer/8bJGGfXjWwww3BipOKWDCx4y1ZPatRyaxWUb5LyIEq +JipRPwqD0EoHDGF6VbEBo4riMiMRYaYk5gsNA1YQGtDvZxm0uk6xg7LSfkswiZMXsB9udBPH +FtY+wIXWy/l4zU/lwtPDfJLQs5Foatamdmt9ytRWm2+KiQIcBBABAgAGBQJOp14DAAoJEGcL +54qWCgDyZcEP/3mEHTKiMnD/FEkgxZ27IdvVIV5CtURNeWIjilI2Nb1Prz1kVNg9px1rN9K/ +kge82kX+ik78tVcfYFcsacw1EryxkLMccbuaDfmRdn+2yiXUmcdqwKbgwXbUwPAdVUZKsfHy +yH+Csiu9DMW0lznCWx9oqH2T166b5LbUj/1SjTZiw6ckU0pVG5oRDHp6LjPBwF/pGjHob4z2 +Zz31HdXPCrdqwumDv1tepDQ7sjgEZpiAyRsr6EM8empXxZ3xZN5EqVtSK4Smxcyi2cM7K4Ob +29R0PI8n9iqcpVqAobuQ8x/C3h9ihrB2DQArL7LLHeKMmgdhqHexojgWj9ltEiJlJCfkX/n1 +qTBI1DryCzvSdA7jWrTW/EkPIsA/LONLSVi+RA++nCxnJQ5PAkMf5Jn/IR2TmIgWEfWDnQ6W +yKZfVevBIleQ6NYt/la/LuTpj02ZM6PD39j9/+WRNMiFdekhLAGvo7RHYpB9hqSuxW5UQH1C +PsbjyEPud2eOEZDZI0AoL660h8yjwCuYJx20IFyA7YwS0tr3Px8LUla6bfGSCX0c29HKvI6O +HsCaLAtv8IUFRHgYSDVCDtgP8pEPckTCtMmeiXTkZmlbiRAAfI+Og25j0SA5+C9zgRBcZQAl +vrItcxZFdDLA3Jx643Io9sN+LN0ahbW6xB8uGs3twsAY74zxiQIcBBABAgAGBQJOp6fyAAoJ +EBW8gzRDA42M2l0QALC+cAQ5hHRvIcSt7s718l6+uiZXL8JBNT/+I3j9osZxs2E4laxcAWOF +/kt5M2WqK9+iLaLjmtYhP2rMTmztmM1oAtgx4QTolF0Z5Se4A/+/rcHT3TGb/mVN5XNiuY6S +a9wVtQWaZ3WTdlxjoWHBbw6a7HFC7GiOw+QXUqxYD1IMgrK6xazLJ6VL0893uQy6zT+NOuIn +Sa3aG6l8tC07hV1PI6k7fK8wpMcE5Yrdtvze0diSmXcPb9kcU+wnA04p6a4pw7iKFPomEdRB +J/HXrX3bcPOmQZgJ0uJmwQlAXOE6yoZ9lfxChZ1/1XdxGAozeuOjQz0BdiP+EfiBYHD5tFT6 +0unGHhmKX0jEvWoqngvq/Riq1WetygXQzNPStTOkweMHT6E9NXcMWCQGOBHbVNsoTfeR379Z +8RwzuuRL6pIOW7dzXQHodoGkOWjxl92Tc5GdqwFdnX5Iscq15O65yYapuL+PNpU8vvinccG7 +riOdtSBKzP79bo/S6/Nkhp4eCfEdGvZObD8K0SFRg3sZQgyYeanUMgWNylLKxzcVfsrpH3lt +K99WSelNJymV3sxjsajtWuThuNH3YFtXc8FXBbsmN0weWShj6QqtWyy5PLqWKStviLX+j7OL +xlD3CggYCa2pytRMVLklIAaafxXP+VaJ5Hj3TLBL38b7UQZAxIDKiQIcBBABAgAGBQJOp9cW +AAoJELerFe1CuppEDD8QAJ1tLSlPqmetUiVMZRv2a1pfajX+mPyLfpa47dqQKQZiWPC8RVb9 +TIHmHQ+Xf96YEwkxl7kFSNHrvb7bPxoIGistfmPfyHrWAcFfFJi9ymp+sIPq2iYzkB3HnV0k +Jr6lhkWjlso4v1GXl+AJAoCkERe8NHk46eTWuqXAGUP3zNkDDAWAZmcHfgfbwNUbgMffaV1U +0WHSfiGL1/++A60RUecUPVjkV+Gg4gKzoRTAco/+J7b3g0+UGds3Ej4SdWK/EyMT9iLtJxBU +wJJogE7QeDIOZcne1QtfpV19inHpLtdFMWPP9dVwZFFjE3rrVJWfN4xeyUB2qTl1g+TSTX6O +0wiAp6XEiF0W9cv8Km/4UDd+ted928jtYG8KKPgMdZCjSfroGpoq+Jl+Lz9wSNOp0p1WM/kY +hDM92VPOfQ3vnC5mfVIB4kjYJrL51Rv0SO+w/Z1FiWlkE7jpU2yQGTV37qnM8gFk46xzPo34 +Oa49eZr8ur4uiyL8gi7GoC+4iQVhrtAlL9KNwGC8BtoFJd0VM7mzPoUOTNATHK/lEXJqB43u +09Br7qX9EC8x2iXniZ3WK3nPqg5sYPOEK/kVnT/mXKJjsX+LBW5jkEC2uu6u3DDCj7Ho/DeM +oY5lFHY9v06LZhqlR1Mq5LBvYSuk/xv+NX/+Z2XdqvUeYfYB1eOka9rZiQIcBBABAgAGBQJO +p+UlAAoJEFrSQhHAYNHIOj8P/1P8jHlk6nu2X+pRQZH22rAwoCiQFiy6I5q5rnW5SuqjPqw4 +Co4jRXzvEbB70041b2Ht8WpV70RFuoSSr0qIEXH2svsVOwnJUOrFmiTyLrSV+mRI4i0KzszC +aacNsdyzWrygpFM4S/UBGKwurTEpKSh36wowcYx5ROX9oMGY19u7E4bz8RLzresyPzltmAhz +fPtw0BizhFT8e4Q7ySGyI07Rwttwod8BM5n/0PmaxPVXrO+bXVzofjgbw46zulqpj/wZbr1g +KDaAXNtiqdmJwrGGgoL0N+bukWJTb03jzwctg2F2F/yu6nDrmZisI9ytkAvC0x2cd6jlX1fW +zuWIy9/NReShcZL7/Z14K9DWj5uHISCQJ79xKpPoCLkrHsZup2bRKOmzXjok9s3acg17ZGox +t+OILzGIW4pEUxEzQQto3kxmK3W2H+X1Hk2tJztfFKBz6spuWs3gxe9uazSYRpCfRuJ+IaBX +xKRXKkpdJSU9pZgouQ5FXLqKTq2F7anzpiwY8sR2hfzAghX00VK5Zky92f7IcxfTJqPkYBkQ +p4U07MUNvyXSSQOCERtl2w4CAB4dAwc5zsFmNNMf+SSq9miu2XSHGeACQIAye4kGcaRi7jkx +ywEr2zaB1rbHa+Ua5aVtS2nQ3qdCWLT+bygR3CSlQasguPl0jGsYX9BfoDrZiQIcBBABAgAG +BQJOp+VTAAoJEECxmPOUX5FEf2YQAI/uF6Nrda7qVIOH+52yQGdFkKp8AmtVHkHTEd81bVsq +0lurRJhS62OQccNCSSswTYeXjVMmeHnl4SYkP7LvXIcAh+FUWwwL9hNsQ4fvTTVkegPFLT9x +PoTMU9jQYHDPLwSqiNQlsd80yMFF0EN+p0DCpDXM3bXEc2yEoX0i8Sl7yzOYiVWzKBWKuICW +v5/WAzemInOi48wUYk7hpt1qWW5cd7EdxC/pwPYHYd/171UFXmavET9bsP8ECF2flTiRMkNO +QmTBLmQlvygBrTEYrCz3ws1Z8Yxk+LmOT1xDbord5mMRBm4k33A19fUQcK85n7ppOJS6R50+ +xR3XQCkkgQ37P1OfZhxtRXg7nE5zUaenq2dA58W33JYy1quTJC+8AFZPxtA+dVUtCfIZ0Lg6 +nkI5OksvzMECBEb0Aok9872SBebkkQKAev84P597rUeoxp+vjBe8+zMYyN/Xb/mpEdHCxbwO +/PCJfj5SaPcXVsCXftJx3JvkjOLe1WQ5nGOT/cy7N6Do6J4gJ8ih6lkmEU1CgdY2tBvE/fe6 +i7AcNc6A4jOYH5YNvbzJARZqJMCR64nuiydiesjjPAUvElBiud7Ec42qCPLjowkzU4h0D+0R +iYi+F3i+weC5nX3jho+Ak8xcc1pqJ45moLQV9NUg6sCeNzl6neEdTWQ2Ek4GvrQ7iQIcBBAB +AgAGBQJOqANRAAoJEAx081l5xIa+p90P/iA0fHVmiVTVBvudBFHi6Aj1tYRZxn5Pw3k68of3 +51Hfrt7Opj8i5tosXlvRWWznUzLXiIG7vOqxmS0/EgAaV5vsfbP6d0kP2daJT4dqkNURIlTo +KA4Lqm3tKzjORpyG80m9nY04c2r2p6VyoUX/QCjg924yUqLSCng4bjzwXSSk2XpqP58d/yq7 +qZ4Yf8kEZgUzV52PY9Mpd1fZSvN/zciG//0BdpkrtqZB70CewlLT2NUudnPnRpU5cLEA+1UW +MH7D4JfBW8SnF0NdjtXUBzfweVEDkrCrhgt/a3jyLmVnB3C0ZxulYR5/D/DWtfMLEPvVDIAt +UIZBuKlYHqLaTMSOzX6yxJESfGTFuB/fFyuS8pgjuWPXgq5Jkg33vZAv63hpuiSHd6LxWF8n +0DTuRR2YSwabjHnvylQDNDkhOGxLu8zuCdultnv6xtqgxqkob0FPg4X89nozheTWNMwd1OrT +rfqGd43rn+0OeCv/dHG5+RzN7dWlHQlCMlh2/aaBrsvBdLh4D0LDPOGdSctgVxVeQVT4QFcs +j50DmRUlaMiNQkx3AfCfcCN2GjHLbQ2A7NrDXhzcDcqvZU4rXSIB64eA0e7HLoh0sO9SdhAb +Q2QN9CWFvEcQG2rbzofS8RgdIqxOw4MBwJe5gsYWg8Kqx+mCU8xS+sFGCBPMYHpxxvMOiQIc +BBABAgAGBQJOqE3+AAoJEOt1FFj6EYMgF1kQAIfcqs2HeRa1llrbmDBa0p5IAczRh2bu4Txh +dZXhB8Ivn7xXuDYmMVm84iLn7LXD2/NjhtHKS/NBwKD2T1rWpacshuJZRgbzZOwmWs335rah +mfBts6CtePdDRjFYvSWlZW75TlPzjw2Ks7XGYx/JFiYk8A+anLo95QTwPrfyWkkuuM9OaMd3 +FgY7KjzB/qp5IXy+vMrsgNY7x7qMSxEBnVMmqd4xR2tSnuHoaJkxNY7tZEYYJb8IGa5pFHxd +PMoeBcVEqQkGy7X0z4BAOVOmeP4mKodVqBjDFFNZLu4nCheKpApLZ5zrMsjzC9vVJWCEiyzq +M2cNSkISppJ5153j3GtnTWFzmYpnupTCK8RT0Rhxe9nT2fYWhc6ObCQKV/wC+L+8DLJ7NU2i +Ncnh8XhUFW0ZL+wtZtGxDz0SI9iTuLbnj5PvwOZDp3iSX7yev9kDLE2CO6uIMXiK24AsZndD +Hf8zRhdS1DQmF9jDW3EyduXCkCdt7uAuebEn48SqF7QDtGhtwHyjZOKavQzo97+u4KcUZZZ+ +ah6txAGmworGFewJ72Oho9JxZNWThcvKMtamCDYyL4SIAZQNxxWOBsUeB7sVV09OctZ7oPCT +5kZzMxX+J60nOT9FcpZBOyYEvDnPl5e3mXaKQ0xiHk3890bK7zhudL0a/w2/iK/XL8Su3t6w +iQIcBBABAgAGBQJOqGe6AAoJEHzG/DNEskfi9aQP/2BuFw81FXU1aY3wa3FUu64N/ZJdMIYp +b7T4G4UEfUKH/I5RPFMn2Q+tAMTSXdrEBlCJXb700S5zTltaYykoJlW5oCmc7ossR11nzpZJ +cNWyh0Nq70lpH/a8oHu0d2gI3b/O3PzPbdk9ECJxq7AkWeUadAipFE+ehARQXh0S4Zi/o+VB +dQ+omDVFjOJI/VUdmH9Ku8fC4dKRenQKMO/EE9FEO99H2ASjuUoMGz6IaDeelBOxwHQU6l0k +eCJmr9SSUCRwFdOt3wNf0k8PH294GAZfl16tzuZrPqrW4gispuls8eAySl4hrW7VhqAZYJb2 +DwcvFoy5lzfElTlNVlk5R3Rek2YPcHPTB1CSIScGRrW5UttJzch5yTgvE3S6uS49TGJfsdqQ +sv6P58b2NXpYa6nbmJqsURWH4UUybt37CKgtnOps6FwVj6oDd1OWRFLELVl66rvJFgsW2+MD +eqzi7krd6w9rbK8fsmg8wg0yeFKehYzic2UTR0woUy7oZl/e8wHhG5OSkhFMpRHDbEriZcqA +VAw3Vd9Mvv2Hfibl3sjzQbLcQRaByuSqaLJ8oNPp13aBUVtamcmQUywu9RU5dVq3/Movkgl3 +6djIsnD/iadTmyxBxcY7xehac8zRnYf8RvxiY3C4hCuIbbdHhANxjFA/QqpgVtm4hUOFfrJk +y5SmiQIcBBABAgAGBQJOqR8aAAoJEBvUPslcq6VzRaMQAMqQIihPrM45RkSK+5pN6y0r8a0j +z1B5dBHdUnWPGVbd0XmiJYeafgDgo31gllrUW9dQzF/kHiFseMaAd2YyR1rnDXYXfTggTAe9 +fI1xz+KQ6NRwmhopabLmdOXsAvdwgqwt8LgMMPbBLDcLkzFDZ1IvnoJJ7xGJy9rNXEGDyq/W +liiRn8XJ3ikDt+wdKXQDtWKN298XCF0p2neM6JE1ifAVz+TTK1+rEl3vA8/kfrDX/i+gS9gd +XoVguWdZAlqsHoEnUezpmQV/s3hui97kyHeMKxfkdx7eKdXGHqG4IU/044sY0WFM1uhx4Yd4 +CGQfeLOrogj0ow2nTu0ybQXrrkJsLzNYsRsDiC8X1ZyQCZuOtH3jJWzoO0rE7C0m2mgMbN6A +yyP9sdhXFE+9JZW/BTQTR8WzSM95eQlvKOVv6qLJpIrNmOIMmxUvnGD0i4sZEpFoe4r62Mvt +zCoTBkPPLANdMP5bF6/sLKZcFxy6NkSpJRXD4XtdWc3gxzPZsGkrLqq9qNdfcDVYHqc81GMR +9qNheoHOLD5e8V2DVaLZ0Pbak6fCfG97xRiqSxaNAE7G9Qt2z+IbyCcNfecSQSV1+LhNSvw1 +ceZOCgWRJW0n5oG5q3+NiVmIhZ9YvyDE6uayvLnqaiYT7K76iepls5eGmt9Zl1+dCjE4bVUq +Amqp5ASniQIcBBABAgAGBQJOqSHUAAoJEGy6iiQ5GWj+OtEQAJfnuzFudcndbGtffS/hB9Sc +rFAm4X4IMV1lktAzPzHERCdtcsEgH34mMYnhvbzBGL4dOcZaIoK8C7DaOw+gvvPOCSBjdPFy +QlHvumJpBFEwRTXeYxhaKDqwR1avelbVpkbFqSbCYn5pbVBArnwgSDVC4KVkTrdCiOLVGJYL +yfZqjeoyZoahmi83buatYwb8BN8INwWoc3/E9cHju/qFXiI2Fpc8jrqfptEhXdFNtMn2Aeke +rkDLGu/xMavW+v3JPAWh5EBw8lakXSQkSNyfYU1BSWYGTQnpgFZtuwq10Yv0upH/xcFbBaks +9QfN0DCe4GYBO8zI0PVNXEAvAGyf1vDlWECR5no4wetCJ79d5WwSyMy38stMgo8HjHWLGgmA +kGn89M+fCr9EY9Xvy0UKUJS+e4TZx/QiftdARB2lY7cNb0snuZy40HXePLWscvKmtTb1zsfY +Kk9fAeAlMi04w44KOL70sttkG2O869PSxNDRn0xD0GHZagkKvWrzhsP8kd6S8ksz2Maspx7d +AFuTVspa7jV+yqO5sYGFGhBj+xTTHmb4vckQ4S15lj9fEA7PU3iEPdY044D6h0r6ItuP8xDP +FsDt0FE4YpXnTTXzQgm/Jn5nKC2cLRnuG8Vrv+GRSJUjuW79v5TPxK18eyqihFfEUxpIBZe7 +bfFcALhfkO/0iQIcBBABAgAGBQJOqSd/AAoJECvKgwp+S8JaNfAP/3KKPjfnvAwgKTJmMi/P +M9cnC71n68FHF72fU6bNSoINz4UdbPufz4KXeX3aHLptzvDGEfTOX8IdCtsK0et5Vx9370Yx +Ng6KCg2vZi/eFUdJILRrEE9NLvK/ORiFZK+NaN/u0S6aLSZn4huvm47Te7q63c9uQ8e5Obmk +409kc0k05avtvI6SuxeQpFTKUf0kLaSqR81+v3rWZXDfxmmUG9rn9Q4nrXn2+VfLsj98dcmc +3gbIUz1yqLBIEBjleNkmn/GJ2Jm9+7sg+6oac2RB4gl6YVgnvXKmpS+ZGOzA6MtYSPks7RCc +qJRWLIjYoI7dBS01394OSxyU97AOKg6VvfHTAr5026Qg7SD2bxCA8y6yHR+dCcxkY/invt2W +c8vBydX+A0valTsp/XGPQlfkn6C6dGtzojYgM3Kq7BPYQz1CdIIDls/UPIWUgWvodCy983ZG +27F915nNH3PjZ0FH/M593rW8AQX7IUB4JIrWP/8pKusfAGhZvqIBtph4GuR6O0Stzm9itbts +d9cYtI+8yceDvPm1yTJM0nNncg4AYAHDaRmsRUtsgQpGHa7peme4ofX80Jb8ijysD4CEWcmx +0bMI23pUmxJO/kuzeI4qLhqZJcbmVyTW4+qv0pNlwR/mDUAy35CaWmFiFzJI6j8/6UlDuEpW +52ow3gJOu5wK7D+NiQIcBBABAgAGBQJOqXxqAAoJEGwxgFQ9KSmkvHkQALWa7v7ffFhZXiSc +ZVrgh2vNDmuco5kmmoItBUBouTpu5UKW6ba4g61UCsLfGZk1vRozpuMmD4j0+IqLOsDQaAvp +WrnnQFJJ+yKdwYLOM0WmNwFYFP+MhcbB6/ile2rVdxMoGTP0ROhGgTpECv+izFNljUnoEfyk +CMMVQJdCC8RPIlL/qTQtAF4+SvhZDk6wYhKLc7GyYKWIKr1oqbOcpVDm27hUX3gTkMJTe2R4 +y2Ne0RN51WO15VGEoP9poOsOO/OaTm0OpFzaG1TdyuD3eS/Mr1eoM+RM5jQAyhJ+SDbyhsLF +gH8jP9nPSy5P4DwOcP1D10KhbhTg3Ad73jTR5jCFcSSlYKRwnorcKKKWxw+cakUXN/fVGuHS +bsPQwLFdTc1mIzDdpDHX728AazLJUs5wvRGvVbBM5TsEeto208C/jYhKAm/zxUxOf7ltIkxk +9eocLCIAauCS1lznvpniN3aysI4kMMKEb3/VCb4NW2YryWvVwam4wsCNYw6EZRK+sdTUw4sf +1rmOUB1iAVxWxziyEBOxMTGSD2+VhmzPvM5es4yiTH3tB6swEpKatQSBzpI39iIiwqaVXNfY +UMRsx1dY81U8Yz4coSza/oyUmITPMTrv9IfvF2yexIu2rvsWAEq1cWVHeE24eGlTdeUfsYRO +PcehL89KIJcL1adh3KXXiQIcBBABAgAGBQJOqphnAAoJELvpdr8mrl3SpbEP/3wzr6or+6Pa +mFZGvvXg8kvQJgNurqPIedA9JzNyqHupzSZbsGelH//oxohT1+xCplC8y2Eo+5MS8RGfX79a +GDoC17QO/jdoW0K998Vb6jcRXVUCQKtky7BGufms1LQ6fIt8TrxtUfRvEDnmDceg54JA/HW+ +cHYvkhoExYtbshBlyhRgIBhxg/LX8CNJUyEzIaxP1wz8qlD6XygnuOOa+zQMqSb6ciEG7/lq +pnLEkyJaMFdLsARkwL2qZJCVSPfBOPP8Sl40WxePQaaWcbTmc0bnI2CszoUgIi8OECQBtlsu +ol1jzGbZ1dUoNxsYVXOiUTMBDrdsCnBf15nb8P3jDG7Jzg2IP3pIDlfiTbRM2nDVSnZoGsFs +yc/Hs8iU7H7ZCYwk4C9QJju0Qicu6D38OgwUYXWD20s6E3EgLtEJwhWPXnDLi+3zVNI9Lha2 +K0+ZSxEcAUin649sCK9bHaMhY2qDPYjwH2h2Hkz9MGaZrMqStoBxNJGuIk3S4Kmfs3zpI8Kg +MBfX9G1Te25KJ/R7HM0mfLn3eX1vO3j0rYj7XreXTKLkEZpJh+cto0LFMuzCTOwVKW11eTob +WI1iSNUprO0UYZvhJqkfkcsY7W826f8xzZLWCoUhfbA7+/uYT38XPL/YQz1UWzAifdCGx2Sb +mKY+8IL1hgSyPv6RjkqzNkXsiQIcBBABAgAGBQJOqp9qAAoJEKVSa5uzzU5qtEsP/2oxOYw7 +dQdB8PZRFTR2nsqz7612ZfusQrMkoCFhOAQ5szJ8nX5qYK4V+Ua0EUo2Mk3d58jgHvv+kPCM +AOIzkKBi+SQx8+Jg3boxB8YkzCpkO4C41l3b/0dHgcHoVfMQvr+IiSXCkStp9qt0t8pSmf6g +bkoZ15OeXHrfaeEB8SUdru8h2VvSFRKwUlZwmJaJGXeBimmWSMhOok3RLdID1bbCeAf5zWvg +ZcFsIdJvRvizf81NjqGbQTwhAAgA5ncAhH0UjnMatc/VSOm3pYO2YGMwSnY8ejx1jqRfw6sr +nTeqnFIj53a+JCyXx4YamE9WehL7l2HF/OGVfe6YH3daNqLwEzLOPNfmcUmHJGCXhoUHcyeY +beQIBacLbXgwLXlwGlzFnNuk9kQtGAD1vm2pQWb1Wx7kPFFp5SGEiNsuLw0Fho4LH/A5RUpW +y/VwSU3pJdIhEvTSpg4Ze57JeLBVO4fVt2ehzK9X3crc9delOzPw3/o44dpOS/CDrRqtTIqe +8eibAyv+tb4A0bJgweE8lNNTvdgR6n7E5JQfdPcbFvcD00TyG6ADyrsUWonC4o61t8Cq26FB +qLYgckENkO4KPu3n7hYqrUaIMisPELJBAZFxr3fs9EPZdoSNGAh0zNipEoXQ22nQVWZK8ALS +tCEU2ldu5KikeDtsvrfcZ7Qu0rRRiQIcBBABAgAGBQJOrKgsAAoJEMGkYHJbUcvP1h4QAIjj +qCSW7DOo+OGf5ByCw1BNmIDa8PQ1JcMWaq2qzHOUAZDu5o1hfCEj7OO8wXVZkc8KJUJnIBrH +JPFiiPvp0tPqwEHajdbRuP8WyhZxG8MH0YEt2/FYK65mnrsts4s3PWuw9fE0ujBCEccGNJzy +qi+9fSNXUanfTU0NZC7g3pzRS1CtPckCvtE013ZJTBv/v9zTqhSQhvxjvAix+7usRpx7qA4q +Jj/sJcK4HebnRjahceR6yQUvBTB6VO9h2yAs7bF+s0ZKnkW7OuorultNLZBmqZklClzbBNQx +PpN4XctWVqPV1eYBgtyL06FH85m5cMs2zpPZp7vKl1wXkZnNV34jOJFCeQQ1xnFkqVtiQh8w +YJKUx68Bnr15p60zXOlCiiSAJnMMYCksfYAsEwPArxoURLJRZpFELjx2213uf6HvYRQUoe/k +dRccZ5FTNxNv/t9pPjgR7QQRR0upExdsESnTn1OzDSDjie5hLTUHAzVoXxYI4d6T7ByuwpP2 +Y3FU9r3719LNMiANJatv3w6G1ZtzbkXshSH9AHCP4nny3niZYiXoQgWV3B0bhYME/OGWjVw/ +cyfz406P3Hf8K5GLjGA4qoKmmQsbckZ7q0p99nmTLC4RngOsWP0G6FJ1pbkdpWnHvKNYTUnx +GAI6meuZGKfn4amXR9q4OiWEvpJzd1naiQIcBBABAgAGBQJOrayLAAoJEI7yEDeUysxl9DYP +/Ao8kxFKlhKPFPcaIz1A7uDRekJoXWF7QCebDwtqSSYN4FfDb7or/2u1g8ATJtLfCsMiRmmU +zECMsC01U4H+G4oZv4XWdbtpZ80qZ4A2ybDkdIl6s0uStXbf8s1EXT3b58hnafdemQ3J6Obz +FSr9upKfNZIlCygQsM6lQNJ/iwTAnqWHSXEDt82UVq6P1BvWJ87e1XvrPlMALBohGWoc0WQL +RvjD+xtuNzarCbNSYo4AZVARsOgeTS98EIfCVNID9nv9o6bMe33otyU5SdS4JjgjYdjyzJcW +mtVCJg8gQYSpLkHZLuqziF8WNE96M3jNQQPEGa882dImKxQfN0ZL5iZuMpyjdfJT4WwH0XUP +/Qkg/g2AwyI0YNyqtXIgBMH+zwIPHtGCJuu/DJwU30FdRjUB4U1JyGKJdC9s7rIojRiEFEND +BirTEJ6V0GKcZl+ARXc8HEM2Eyl8AU3EwBHXFxCB6PZQv/EEkhGbvYW1JNjqtibIvpO/sG7l +LcLpYdRfbSeDyw8MDd0fHdLdL4d9eRPfqpY0twJWeZWNkvLHMDXY2Bn8YEQmd/cTQ7fwv4gl +zgonbzcX1wVHelm7O/pwsiFZZphgw/ENW6yOJ2QrRr4ibhqQ2R9wFXgPCrbdORjl9qbkGSZs +C3VxDu47xUz7XHL4MzokrAJFUhuI8C/THNopiQIcBBABAgAGBQJOrb+lAAoJELLolMlTRIoM ++wwP/04JQKSbiCu4EZu3yNC9+GbQTWkxhPZxp2H/KWNBzesYVAefKm8QIHgE3dMH6ZaneQzI +YWkzLv1l052ltQzBkiaRKGbOVcbO4CYfbnAXC8Jrbn+TYq0CudI/x1qWLwDDmW/M+OS5xxQS +wI5vB4Ae0wDb8GXQONEHu1jt3S0ryYDQaMS7dKTlxitO3dxuRUSfYYrdvAfswck1UGHMw+5t +0P+VaWxFgd/awo3Mz4co/bIT/z7RCPrqZpUeBoCbXJiMsPSngksWiGaYARUqPlgePRA3w63R +Gw6aPJKJNOQFYutcpagO2H75QbD3Y/ofYNAf84yt09ao6oqkKx3f9KEy6SfXf8NrNgnGOaoS +gSI1vsVsNZzb9iTcDEg8l2psqOYCu9x/WndJZG+D2Mlt/uznkD6PpdzB82uKcSEzae60jLub +UNnto0KiN+pF7ZbgtFteWLnJVT8vDCDKiDmad3mU4t8Le22gu/zE1k5eV3/+BdoHFTWzqFXT +Ugy3kZntzjrpUFIb/iFfGIrZnITEODHkX/yMwJdxoEmz4NfkHq53rOMmC3A0ZQYkGooq5jKi +PCiARnarF1XdD8fKxDGL9XFe+R4oXml73IswwWbUDo2CnjOYmDTLz5O4RZ8a6zxHOmag7Ptd +KOTMEepkRCyoeDyKP9STONrjDtRS6VVpA7ARHwrNiQIcBBABAgAGBQJOrdEGAAoJEOvCbNta +Vt5zk5AQAKR+VfKuPkN+7mcNYvI6fdDU8QwQ26vQdX4qCjuw8BAgxObvX/eeCtJTn7eImnHd +TtsMU/fJButQpD6tH8j4tkBOLw3tjjQFUQSP0jYapRuYtoQxCQCozlBXrgD/8J7G4gX+D5uD +bKNAkQSZ7wbgAF3q/Awz8OTDgebaK5EplLTfbICaPAW/pzeISvwt03nRt04/TOItJHxMTFF2 +O1i6s+w5FER2qTQ4SQW5NKNN3igO38r7zEoPvwnWtyvH5t/Vi4DxuKEARTCCA6SzC4+a/oXn +jav/5hLTSsBgithRIwqQNveHXASbkuhBVru8uBqhGgjQH1bISXy0RPdcY3Ntl/px/MfQJZfv +DIDi9BmbGPRchAhq+tlYWv9XpdEQm6RCzQiaAttX6MNCDCGezYUMU+2eg7iCEDguGmh6DlYY +ZtrMYNoJJ6DuCzLqIwgnNQbneZSqAsbC9LUJMJwGIHQUM9Upg53RP4OnEqh91Y683i9iePg4 +tddSnppgdmhzXIqNlcWKKbQ2ElzIGHkpFPUhCM3WA2hHBABSOV9HBZuUMJSeraE5zgPpgWYN +8X/k5F/OIsaiA0XcvtOL2nmvRSk5uIkaz05Rsc1rjW07nD45TifaeoJl4Cyj0e0uTMfCUR04 +CLBZpAW5tCGByxifuZ2ccxrGSqJ/PO4hWtRLrsHuS1F7iQIcBBABAgAGBQJOrf3NAAoJEEFn +Bt12D9kBuJ0P/iuExyKpEu0UfpFWpk5ARYVsrvswuOTaNcm/Iy36FzvUyfekd3PyVJdwXYkx +46KMZ9lYuUXOiT5WM9ODniVTwul2uDtrGA/eQKmo/ddhWzm1BfGVgh9YU/26eFYwl6I7n5Xf +kxw7a0tI+GjqnufRrnzSzF+RndI6hqAkCxeXFnbOiPkDw6c/9DSfLwt61rJERhBe6/USlbfQ +l4oigEJqU5I3uHz836/Z3QjxmGfC3Ozeg3iAgxQPFqobvGS5YRkXEtMMNaCuM/XdKbyROKBn +U/pEj32FmFe3cTD8enSRJxxeBT2aD6jelfKFK1OzQZpnlmFbIbNQj8C7HShruSDEWovtZUWf +r9UbYDl7nGP+W/8Pv1+RRNIzU4xIIDqsoxUz+o08Kr3la2CuDdWHcQ1DpKrCM1krtRIw2WvM +gASVg+g0DBkJgVjiI3rBGDFkplNK5pHKr54om6N9CMNZOgO8sGeO4ZwcduuUVyXY/U29ZSof +V/lMtCrtkzP/jqeY7G1mKQBwdR8vZu3LxsvvrwUMjvrfBDPEMSUx73XjFen2GkLsRFm3/Sbh +zKigs7pVU5veQsNeSHXlI1uP8Y6a4B4wyth6YHQWL6F60U6AjV7Dg1JGQC9htKIlKQwMyZ/f +un03Mj5aJ1kGol9XkPtkJPby6fUec4PzX4myaB9idN1BtJzViQIcBBABAgAGBQJOrlDZAAoJ +EA7nDidz8wdzNtgP/0yjd3AgDIU83VSJLHiCmqea3HyaKnSr7rlEFePZ2ZqVuWEeK0nHquwR +UWoIf8muJ3+8ygvvLQYQWIztT7PLX8VXG/4VuJX8t9Wzw3L8oKcNru6X3l4hmRwbHwq7utRt +HJa+gQ8in6fOVxKk0Te1rUIfqnEPYrRjdRNKlupfkEtWlrrzgk/6wTwTscQXI+3+TPUV0X2E +hUVLF64Ze9mnRtk2GYR9v0XO3EuenlBI7zQTBkUPCLfjACkOWCkrJMxoA2BNSTTJDCPHPrvl +uzAlvz02FzXq4A7QWM82kE8E/5EZ4A0kgD6LiMaEM6/f1bPxH8z+QCinD4FpbHYUVRSW4ME/ +5IGzTfTikEJLx1Lk7QjXzydpJgaG4Z8ri0FvpqUDQMN6VywtUme3Tn5TyJU0Kf0Fphif9Bw6 +ChcmVq4FRqBVYcX1PHNB+ZMR1woUOWKFWQ7UFWRkvb8bOy6W8jzHXrohLL6/lgDTXHkqO8+a +s36RxHWPjvT4BFCcQ+QUiGWwYlBukb2u43vhUAdyg0bngPlYXgwx4As9hJT7p1Gt3H/xO7Az +WkXgKW23LYC4yMpKdcs27aosnWd8+3QwxwxRAAXz2MZYrwlWOBDyl0c6uE7GSHWvUIDl1u+h +iYUsgoyn0rAXgD5XZ0y3WWYmcBb+HhxyAXQ5Z1762bFyM6Egb6sfiQIcBBABAgAGBQJOrl3W +AAoJEH6AstsF7SNtQm8P/0ue/sayWr5Zmev6lWPsrrgPrLXp6uidnfdyaofsxh4mcyWD4YV+ +Zui6+RH2ZxHyl16fS8qRPVq8Mae4391+13/lAgNv4Q8vI+0GG1dW144f1XX5+o9wvV6rOXFt +49fjoyuTus30qA6TA13KEv+RjEviPHuU59sH7bHgFn+2l5+9Y0l9s+Ze05WASn8eGuMJe1BU +VIq0ezHWox/6/OmTcj4MtWgGRsKmxCl2aD8rtS05ocszbyxRbixJ2hhg5BYC594+dapBVJJS +bLcHt6Wcf1dii5bTG/HmU5JBTPbMr3V1880/R2xx1ydVJTKOutBZWpfndnDrH9HXGJpQs7TT +sFVJqhANciDN/QBLs/oaEV0Hc41EvMWOTKTUAS+Y415VEuz/gYE0R7qfHMS+Av5IMVKkV8fc +SPzWFn7wbPg3OCjODhQQ5KY+PxLMVJbedlxncrsIK+sHa8Vz686iCXE2eTe4AbLX/RPHs/EP +1cUardGJA4YLcueKAueouAV7jGSGBRArZ8k1BbrB5QTC/5qykKfTY19EPtDkY81O2Ol/xq7x +LXNO+O9/pt0eYeHfgQhfSwEekewA3cnSLGy6h4WPLFAvU83q0WBqTJ0HUHYTcKdyIZ8GebyF +RMG+ANCAhbvqHXYjDvoGziB4rOmnOb4D2kJUSzTFqa7jOsPFVoF5EWkeiQIcBBABAgAGBQJO +rl3gAAoJEJndTFDBf6hD9kYQALcpzjztEOAiOrDFR8jR6GnYp133pz2Ufza3ENtGSkcxB4rd +jHmLdVTR7DggbxBd0mdB5MdKdmwOb2ZsPQbuWigCSMMp284rXzrmUZYl7pKRdflzW/z2H68j +xA1qyVAg2cq1f9u3fLQBKJBT03GQKeoHsVWcTgh1fpyShnkah7uK0ZhscQW0sG4M1KtavzYE +y4hGBJLj8Qeg2VaK2zeb8MQYDe9oVST3Wrpk3o4WIcmNk3Sz1nE6yA2xgG2/vQUU49Pw9uEP +u7Lbg+NFNO6OFu55F6bBWmCGU5onN9f+hGgBGhL3adz16Yc16rJSVX47Kddk0Z5VWGchGZN/ +7HAp3twQ5v8DezS5xXvA75fjMeqxAfYY4tRYmeGU3FldOqDN3RINFMH94yXI204klnch3V5V +fjju81Gx4lE1m4k6kks7p4w9kSPHANsZc9i8te7cHI4EQ+HiPce8YjmHWhS/f2xnHIIOaH12 +HBaTEObe3ZTaUeR+GsqGLMCpvMl5BMFo6p6CMqyqqxIyNSYa6QpUXKVWsGu6+jzAWH+1Q2+Z +KlZYHGwFFWkgPGm8Tyqs2WL4OUha2xkaYDSlnbOiX9273+1kkR6VcVQv5etU01IMD8d9zqLg +GMxHVGNvEC/etfSIhWNihKUP0sWsrP0hRBuFVxkUN8VJ7ZJNGqPB20b+36/riQIcBBABAgAG +BQJOrmrfAAoJEMcnHQpJsYunkYQQAIZZ3p2FeQ6zxDw/zJH1MW82A2hiDtNO/7+afzgpVav2 +LRnmxcJMv73T/w94Br6XtqBCzGlFnmFNuGwdpqE6kTdlfX1ggY1cl/1f4xK1KL1JeiwNGgNn +jhasZXXvkrdekPM4Mx9G6ybgEd/8VH2SSAw3ESyrRyY7Xts/oULZdT2039QAqtOELh0W5c7d +PA98oFuPBjL6weNd1XmCxDMf+bE3XVgpChStl+1bitjA6QuFvuPTu5EYQEURJtYMFuRrBGI/ +8DS4dxvvDjdevx2M0oqS5n8SdLpncQEyDJJ7cl/ljv36PE+VyhLLahmnz94LtgSvLvPBsCpl +O/iAGSd9D8moXgLhksB5ju7P1zz1u0UBkOMWIWXBHFdYHR0aq13HQKf2lyxACDFbyIlfquoE +8m3VQpRFylFSzqMBmXapsZ0R1n7lTYV3xXqnu1qMo5Nv6+pGSGkKtnriPkjjAPok/G2+sNs9 +s0TsPdikmKtotOX/ZE2/k2Zmw6VRP+jRGdBWvYUUTn6MAXU0uYL+kfJjvnjQpH1OVNQj3YGT +VP+4UWR6otNQi4smSyBCKWy/v7SzA9F8sVbRUOMMD9aRZ7a/yCbRF0Oha078TJ7Vo4DYzQkw +cqzbHFwwRqhKaIm0sN//7ZwWpUJphRdu3yOZfvXLA8Hd+7IQ6kUa8RgWCBUU3359iQIcBBAB +AgAGBQJOrpstAAoJEKnBdsx/rH1W9/4P/0AHXQfZOPPjIMhj5DHeLMKpaMIw7UXzWDFxn/+u +8mR8bkMCjlaOjHV4654elFFnyzMmseyIKEpHchJB6z0c0i2lcjn0T9vrhChAfplTG6P98Iqk +LQK3q4eEtjluPhTYxSw18KV+z4wUrfwkllW8tz2d+Hv+U8QsXNBog/ZzUIYWWLVI8OzDQnOr +nVNLdHtsrM3ofUqQl+wl3GnM51r1nQYfmyo8HLq1+BxF2Pgji4L4vKgbrglSQspe4ZhDFhhc +qid55Krl674UxcwAw7Zrdis/6AuhM4iVkGT+TmIEO23oKYOgMHiFs9C3z3eyNAm+VtDc/GbQ +78ee9EhCVsOhuR/DTrR1fVkCHM7UetKMKb9FLP4ZxNANjFc+01OgPWjZpEzJjiZ3QQ1btNzM +B4Y7fi90tW52qonfXM3qdte9g/fhRY3f80GCUxFE10FxShUqE/WXrQDR8HncmU7iJWZMfCPV +7ns/y/rFxDZpoXlsGH5MoPIbeAkrlmE6deT5hQK19GSQV1r8BeClr2mE5T8ijvLn4kdFON+H +4ZYVlYCZMcH4pXYm3HyUnbszzLQ66TkAeWqGMZ/QrWvKwrI7TtlmiucTb0eJ+IllTXZEXaZM +0GXouInzflZ9fTq/xDNEY9hEF/PVTJJp7wzJfAuUOSugSIiIl1PL/nY05QiF662XAOHXiQIc +BBABAgAGBQJOryW0AAoJEI3ONVYwIuV6j2IQANFp2OeOsc03YPhWpOmgxqrnesNI3+qeUXlW +XTUuvoIc4344Oi2tQJAKNcKhwGWJyapWa7eDxp1jyAW/WZaFTalGskXKYjKUI0TaCss75FIe +qlWuVsRRpYZfUXqWfKikI93KF6O6xVODa6fqdZoQoMkBdfmKG6KCo3daAWCgbsSKI5H90BkQ +l25PBqQS3LGyu4BAilGMNvxH5du2A3mpxBqqggNBjhUDrGqkuV4+Iw3iY0PwW/IQdllRBOkt +RXVLG0cWxB99uO7Qffm73lb3repjxMLcaLupvnLZdyIQpsnWCNzTh8xgVCjaNLB0XGbDX9+I +/cSfPFvVAxSaeTGs33hlujF9RDBVpIK72ruUTFnRBCL58fuZ1z+WNSJqpRLEAjzVbLNogIUV +E8tBVCXwgEAO8KbryFojhxtRsurHbPlH3NSWnaKYyNTfw0QP1TgZGwm7jjbhMzgMoenCFrDS +y4oe6u0CiRi6Z5OzpamAPWZyNCqdOYHTqDc2rXakB4l1buVTIqWVSZUA+ASwt877MpK18GW2 +seMhMLr0UgQUkJqCTZgfxxLLcWxDswsQ0zvNOpQ7XI9uSYxQGAruk/FGd04MFst5GvJ1LH15 +C5Secl3GvHDFM+XwNPORLH4uu4h5dPZMNMIhNN8GqL/82uMeB2Whsp1+a3oKSCCwOHhQQARC +iQIcBBABAgAGBQJOsI4vAAoJEKFvmrgMstOVSfsQALfcGvJJ/QHXoAiRaxBgwz5sV51vYB/0 +L6frycSo/mRWu6c2ui4uo9nUJbqzZPXpBQjbWLyn2UOHnVhCUMMpfTrCEQ/FLhvyzNb2Yt3+ +XA7oWIwqJfrSd8CH4xYTvr5od6yOFPy4TxSphmDlBrqSis5WvfJh7XXuksH1iN6TwNB9Wsig +IiobUcmMFHLQM5JPl3OSmWYa/3fduW9EKOPAKQUJ9rQqv3ya3fnYh8acYuh2Cq4c2Y6ElAVL +B8qvKS0QgJucn9oqpbJ/CmVdLsF96wTXZYg1Fw3kvNdSSosvQZ8UAU70JHxOqpLkX/2L4nwf +uPmzEnjrdcg08Pvr7fbWGaf5fsQr9aNeFxgmtXvwt6Z1TdmPB3EiZIvTbCz/+9jw/cBiT6yU +DExWuesQstVrIohDUmJKVjKYoIB4ZQm3vQShVEgyeybCci80dvrx4IahnG55z96LnJtFREMG +816JVkbb645qYCRgJYzBt7VkP43iEfbb9QseoEK/iQXV1MCP3f3izc1DXcVZSo7HKghsFEEi +2VU2nU8CfyNt4FlGW22zz+W7xW2USjYu7lreJJtq5B/PlhkjWQgBgAZynZ2Z/58fMRPa6Ov3 +XaVfcvWCmaEHDVK9DuTbVGfgiKFi+Nk3AKhyRfOWjI46jOw2MLzqPw1G3DsaC+vci9YK+rp3 +xo0RiQIcBBABAgAGBQJOsaLOAAoJEJctW/TcYTgGvgUP/3T7c/+lxiCuULuhUxVZDZzEiDWk +zl0FK/x+Y9Lz02ZS08xOsY5QoL67Dp0ubH3jgwXSLrnP8mPNsMbqhaHN9Gzg+ezObK76MIcw +uh43CxMFawpG7XxKKId6x2lGWgagF5nf0XJwarOhge4ihBz0cusi8R34Gk1AizfW3rdHE6zl +EJ5woij5+vTZ3alOcNrvnfIOXl4DLHIhDYrnG0S4XHrMYofnB4oHxbIQxjLXH0qTCAT3vidN +aRSMSNLRvxJuPj6gNDV3Ge+VAUJC1D909Kyb1D/aJ3c+SbzXXxcS/2E/3Zf3X1IU53TFszcI +7HcERLQS6BK9+U3XuV2YLfmCwOJd60vHr9fSaZ9cYsqbI6urg24eTYBUqP++GCMyfVOuKHgx +ciU4l1Vel/3xHNp9G0Sx/f350maGLnTC94FFtJiQ8SG19wPmOq4HT+rb4xl4gyzpFZehK+iP +SVEz1Vpg8OQWTQnQSqi04XSibjZIdxVplrkeAg8WanqKvPf+TX3/BwBjSXspGVBhhzKkMHOl +UbK+Vtt0q/e3QD18Bx+y/GB7J4HpIiKtHSS0Xl0wOYuSz/pkRpyTaD9nSXZCUtG/im0GdkNu +FnNDy4la4nHa0/wXIpFwXmS1jOg8MwVA9kY+wTJB0W4g7xusXl2pmGcduO/UAZLGnUADq8aI +4LrMuwy2iQIcBBABAgAGBQJOtXS9AAoJEBCfCYUG/wsU+Y0P/R7vbrui2yYYAmGoFwVw2Cvs +0dQBJqjTlg3yOeub3XoyNTcE9bDRRKY649BndPJ2PkjBMv26gZ3Ph46VUkq16+7ofK3GTTg5 +gVLxxom4Qlnkwu3Uez9W2/RDTIZAn2L1PNpHJA1q5eVc3NO6+qiqbf4yUf6ZfF6PPkSNwLkH +zVix0Hc6vhFNQ9zh5uFBrGLf8gwrOw3Af3Ikd6YxuEQuQcR4KHUR7pgBxLLf3IXmKh1sLQWV +NXEdFYyJi/iw62FrsrHSrXgnACOLqQI/G/suqUR9SY2jZTIjOcXKUO+YqYpW9pMKF/cRTZCt +aMz5/ETwFll+q8CLD8e4WXzgLittprrVxqt2s2pRrDUUQjOWi0cuGlMNWknmkvpCIz9B76Qk +napgdiSfuz/iCjcueadtDoGmQIfHED81YWfqjwvQ320utC0TfPGJNR/XRob9dpSYtAfGBRRz +78TWY4A2SnaCG2t5QUfzaog7BFcI37tGg8+uvzu6J3f0TjCEboVgs5vQdln29W38zTx5evAn +wujEaLI4RaqpaT/WPWcogJYuyjZje7ARh4QT8HBl6WB9cm1IHn7imxJDBv+RUjqEwtRa9Hvt +rZjX95pq/pVpIc1hU7yQZKM1bTWwZwjGnjvTe6B8QuKUzS60qw0erS0PALveEOIeh00tf6Wz +ufWkNLs1G/LDiQIcBBABAgAGBQJOu6PpAAoJEH7rwJW1FrrvN/UP+wa858MKSXOSwIZ66pLz +aId4uyXYn48s6OEMkkTKNq/j8tGLPcwoB9pucUKJAKIz/37oi0bq8RB57Gnt9NhcR6+yA2nF +t/09lKNIozoaUHkWcSoQ2WGt+2OY7L8p+mH3YuU6o9GAqSWPcfY/gIOeTCIHkbEQCamebFmE +nl+XLkaSzaj+K83kMis7t4QKfrcaBR9hI6B2cZYAzjNZDXNSaYUn8S5yQX4Ppq2LAoOINFBt +cRtfqay1I+NcwOvZUyyEmFNGyhlNJ1vUMfbQFUnIYh9VQPNjrlogyVYfRIAIJ4rki4V8W7mE +EkNKeIsOodOGjQxbKyUb761/0HuDJCtc6rsZmkW50bmbDEsKhZcmj9rVG72LmuQjlZlj+H8F +xUh7xOuDnScNSoVBkPpWvoPJ5WeV4bn7JMzQiS4WA5rsuf8nCMr77GdLCekQs9Yw2pkevYDw +nNajUkjB7Z1AAauYdZkc0IGo2elcJvO49IMDq4wHpMNnPMhSl9Ii0TQ4QPxsXsPVlAiNVmYZ +yM7vT3e/tHZKhSffR4OAs9Atp+yfFVTfUOmkTISJrw9Oj2P7i0IZjN0CoFIQu3yYffYkqF3s +9g89DRY/TdY5ZqXsCKLRJUqQNC/MCBZuwmNGzEIofJ6irIpApK1ksAvbTi1cIrAEVIL56VMW +HQ2biqeWdVIJqaWUiQIcBBABAgAGBQJOvSeeAAoJECjFhQQ/IuOSzcAP/RlrEnBUOQzS/+h2 +3MgxRIH5uX7oYX4D9hEjtnYvz+RG+th2MzfqrHFyIHgJHxCXMAO2cjdlbRcHyYwCr2i3+/si +L+l0LtlXHghOvqPSB6MTYaRYMlzEnM5iUE+HDhnwDlkIQ4r7NhFZW4KDNbuoD9rIq6b+mUp5 +RlZ1NQnzapsn+llgF7KP135xgQO7y/WmtVxBgCJJh37cs5A8E61EHRmtKKIjWzsFJKeGHsOc +npZUFOD38Un5LlQ+pYgtJ1UVSN0bkG5fm3h4BlDUN+nOKRwaRo5wJdF/K5biIcFOTqsFBaVH +S2FDtFW6SueSq7rFybzA43nfRHW6m67r0X+Pf9j2OLkbsnUTdmdHkROm+31MCX0feEZKvMBv +GAIG/NbXJ/FVjjsJixPSZAw5UYHsmZ5hWnYyDYWRP96Ymtf7PBbr3n2mcvlCVOexC7ZPiaaC +lIjiB44Re82W4YGGfdmPTWX+r8YyCobWhvLHuIiLOABNQ1SBEVPO+/rO8GtHhvHvZDCMbEWW +bb8sIFVfx9ZfiQk3iJokIa1/v43+xLyi/faB/5nQB/+QA1QO/IkeDIyg67iieqj/ma4Hh3ZD +I63w9guzhPVqM3R3J0MbR8oTTluxOxlPIVhQ5KPgxcCFWdFoWgUGB669cf3WsZFgl/DTMnJr +YNMPs9HImfs97PahIomniQIcBBABAgAGBQJOzlUVAAoJEHZH4aRLwOS6SMoP/0ertgCcxup/ +YIJHU2A5QLtGNUNg0nvL6lzgQ75znn8k8kO9w4E0Exh7ZlSJKBfEseTIhzjXiLfv/wiUdDKq +mKo6of5/AizIaXdWZYgjv8bphL4JjaFw3oTtBx6TOYGSRMiF5D0EIMr167oB3gdrEmCkJRQ7 +lsJ7gcbux8mgH27/R4ZKAiWYK+hcXI9SqdQM6m8904U4MBKjFVtSDLA+cFSXxg93yU8NFXyH +iUVDqGRWhAC0t2mr3ep+/EohGvZkWsA/cElKtwMMKJ5XtysdojCicnBKniBfrBVK5AH+fvhr +NGv0LVvYSQ/1BkWiJ4AHqSoW2qmG/8iu+1/Bj5DG6RZbq+bwk1P/JAzy+apRp8Hvwtm9CzQg +4Djkz+jxNNu52xzYKSXwH/5Oe1rt9F6Dr7fIMU4JSMJNVCPc9Na5ZSWkZ1bVe/6YukXyzkU1 +rU7DzdWIwYQdloYaGOwcYhcciPPYIY3NHm4Mn5JUAdFNpKqiJRPWYVbmqxp5g23kw7bwWZB4 +QT/DHxsEyVRn7/Tau/OshtBTMpHo7mmT45Wh26hdG+lfBNP233ULImI2cHWhUsy2YqQRVFEk +NXFgOSu1CN04lZHyx8xsrOp3F+Sb7XxXmPJV9rTkglgTCWOq/Ly5dfo/6CLPBRr5cEPVflPz +sxD9xYQ7YZ1rhpoPG2IYE+sSiQIcBBABAgAGBQJO2tq2AAoJEBNMYgJ+Z7uihR8P/iv6I6ct +7F10zsoaN9VAdI5vnD4+gumwwE1XtFTDn/Ty2KV74tes3ySCsEl61YrMo5RbOckKSj/57aOe +iN03bRoDpWV0U1HgbWFO66WUqHngG+IZq/ppJu2vg2EgyG14QChsik21MPOa1CP82JPUoHOz +NX5i7mLS1b0Uly1+pwpqtBYzVKNIS0f3vk9HiXqBSdnjoJUfSCUz7eW59Eidu+HDKxQ/SNUY +wUipMGvk3c8hbLfPjH6besO92YAtkQi60iZvAvOd5F5opVwElxwQ1CNV6yuxnQSgW9XrCN41 +6Y9NaGKIB2LzGOYrXphBdEWsOyVuxSiXI9Ijr5oTMmiLlA9n5SAKygamSBHeUhi2zzR35idm +yPOTrLdSkWJahaGomuCiLEMDVpI0ERTGGdbEYnID1cqo9H+Tw41eNYvek0U9PMBMXINNc1lX +phytr2EivL8Lg6Ywg9derktmb88/4YYV7PllRD0qAhWI/T2MiGEu3BUPO29CwNRiTmGk2ARH +/8wpaWlqJ2dhca34mQNiFopHTBXCcC8XL3yI/5r9mIpIwvAVmExpcsPdhiN2xS4bIgvENSvl ++b43OSWMvxQODaR7cY6UpTk/79rKmyxM19E+hAGbsYS5ZvwqJEAZHWOZGewkTbjWuL2haDHj +26IAJbmj49/8ROv5FIB/NMqcVLqliQIcBBABAgAGBQJO2tyHAAoJEMqKupFZNIp6q9AP/jXv +0N0FzHa0AyXujxINDD7YUc3rv/SZfzLE0nBSejY8n+XO9GyBx8oAJUm02/DBADWgc4EYCTs7 +hffreEqmA3evSUGRZwnQNr8jhIuxmfjp/TUVVuRC7JJ4A5RqxXf2dTeCldwJ68RJVwyPx9rM +y54dg9HTO3Ep+lJmQEsu05a2GeP81khuvjC2RzOZWgtknM9IVdBoE+/Li68B/XV1teO9KRGW +hzPPFK3Q9TZIioqIsqs6jP3mKEjKNMN1Bj2f3mkqjyDZS9WbimsIKHpsIaDeZO3A6Fi0A8kY +PcpvSMxtme2qQzUFtLBkU/6WtB21wXtj5BbEIBz7o8KpXc/dbLJTgh8ls0Vc3BvrgAUWR2dK +QM3YMcX1QBTOGwoOKVj3C9qPyJu+hkXAFZsj4QZtZRC/wo3a5Dz7fAbQh6Pc9O7tL18gCMVX +TDP0aYCjlHZ2aE5Ai0LWwdec85M4CbVto5UWytzDdWso22Vo4F1XZMAOh7Rdqjjo47YOpmRc +pEQfRJvIe/vXBkSKVsDMoXE81dLZ8cWxJTTK45uCwfBvqYityd29NFHNcmkT2FSYCpJdnLj9 +RWd1j3ig3D4Kx4I9Zi8+Ajqqy9LQ4//ZosrKHuQwOrmk9osxIB5Fy2LdE55mZUWK4K1S62Dv +KJfx8ZR9BPFzg5y8B83pCA1YiH907apEiQIcBBABAgAGBQJO/H7xAAoJEBDE8YO64Efa/OMP +/RLWbhbp6zX57YXfHpfeBKMhrwSAg8Kt+hXSUpk4kMXsKJW6/5sOvvV/EamGrMM9egLsOcUG +qFlvdQcs8Ixt5z19J7tNf3Cdyi98gSd+RhLX0Q9vY1jxqJTkwi77X90WtjSdDGLIrMaU1eVf +NOhL1A8gFOqwelXKsoyHKPFV0H/5CJAxB9eyAoNmsBdFFtynt94pD/85dYZVYz+ZFy7+NJR7 +Ca25Q3UQFSZ4oiHF1i7wH37f1a9CgbSBZa0fTzww90OATiLJAEKDBjPV3DzJLcrDoEJ2BGOk +uxnkFeFALIj+9VaPnTxWh3DlCT7ex/JRZyyJYhwmDLkUGhUDKxTF5Zp9xGml5WI+HT5GQAT8 +/cmchW6QNwsVsa7dfF24ao7TG671sG3Sr6zAhYPfjT5ZV+eN3300SS2F6lvK7kvF9Oxm1lJ4 +cgS2FM94gnvvFEwr1aMzAAchaulKAd9Ki9lJtIzd3XmzCunbjWXyIYISsd/EIWEwgSQvorKZ +tBzzPmCzO4FYqBTehtmrlhFcCH3Q0LTyD//J9VMeieUVbr6ukOvmRXEtCA3T/XgwgvJPDkOc +xCB6BJ9RYHShWZdG+FUxCbXcDpbuMyYRJRuhaHuztDQSaevrRJDzBj8iuwpoGwdMZEtRcKje +DCG1aOxZ4+UyRGbjfwcXzUp2jxvvN81DRxIZiQIcBBABAgAGBQJPCfzKAAoJEGCrR//JCVIn +ZMQQAIQe5w/jPbljRkQhILyl35gijavHkm0ERHi/QjazVieSKDoGvmmjLCLyuliQ3Yf2//OW +dcdZj1Az9QLwvTLecQhm1doJcd9TyKyuA0Ul48a+AosEerXcEErpfNT9RmXqHOWrblUGJGj+ +zfhYjEJ5kblJQ4bIni/zxrzRLRvfJ+5n2Yq7FcD8gmKSIBb8SQU+fsjgVjKsVqina/Nm57ej +WOtMkapIfbLQAYtPYtqn2PJelqS/T29gI/D3M7HjyXWBcDwKr8uC/xQxGRNLA1dfrG0IBztH ++s4dMckpdVrXvh2secZnzhq+3y7LYM5N/Lj/7yTXPte9Uhay/jxjQwZ2KzZ9tKX0qXfvsasY +obBI0GAa35mowluhSq0BzDQjQXZwJQVfeMnRBGAYgoOZ+EwhaadDodHIENAC14M3pV89yFPv +P/Nms58piRslrj+1QEaFStTL/MBt2aaRRW77AM9JA8PD0XSs4WuyqLPn4E54i8/j7gGV9Agc +5dJe9utwocWLfnjFq5Ld55q/TGDPtRIwIckKj9w5+uTh27xFMGht8DRSUcwchLN18MbQ6Oh3 +yku1H0V24sicFt9sBMuPsYmzFqWZgDHMR6AI82fqiny8kkEt/aBSwlPAfe4d02XnI/8107ux +GS8NN3YBTfGlIhCUG/SHooytm03lru0mfFHRQ8g8iQIcBBABAgAGBQJPt3p7AAoJEGLEp1EE +6Vg2b7oQAJ9k8Noed5NtPeB/cdYjEVxYrADA9DP7JboCEiE379UVDXElBeEeon/l8WWQUXIS +tQ0UunoxqqXfF+cpIM+qZN/56FZl5UvBY9NQZ3EK0KupII3aMuHmYL2+s9nz9WBWo9fDgZAZ +ucNnrdq72jCFx94NPwfTdo4PW54PCjATcRBfI6Kqf1LNXPSzI+n4qCmuDWwQBylEg8jli2ZA +x4Zwbatq8HRdUpqj6ASOkrN0+0wnDWLQwYPCNoyfqeL0kNG14DBHJpke+aH1gX/dpbnd27ch +DzTwzbxM/KbqBbMzdfdyDN6DZlnBVrwSYc10w2O8Z7VXxA6x6mYn0kA2iqrh7JCtkahPmUQB +Foh1SzNyexGoIGwoZ03YTzHlLVzuXsXC6BAVeMzXOt4KQUfUtyTdILvKIjEF/znGA3DZgt28 +4bpOQ8NVyFqHedVKTowr2si23/YZt0sMKxvCgB2+jwxC1wP5e5UemJ8c6s9Sxp3L1KDKKv/9 +zMqI3806v5KmOfX1YFbI17Eub+Z7QiYRQ6rFp3NBhhEdsI8u7FXbBMxgNbXbja0QAIEfTlqt +Iq/Pd0X9JDWuHDeAGbrakDfKfBnv+DmG9H7HdQ86ydK8uk+zllnnIVZylWMw70MgeATg+9Gg +/H30OVBcAKMyqNzTclC9LZvep9iorHtQsrEzvnR13BfBiQIcBBABAgAGBQJQQcO8AAoJEF1c +q2F9qlbJzm0QAKHmUviKhEemL+4WHsIORoF5w9f1JjgtpiI/Aq8zBX9RP9Ql1SBPh4r2ycMv +lrCOocvjiaLyqmeIiaG82G+W+uGTFVgOceAoYPmr9PKbZm2wXcxc8a1Z96R9rDAxdvQgwTrJ +31osaN2itN5oHumjSOrvHkL6oJ995DyjLf9OFS8YfnYkGwSRPZF+GJ93ZgHzb3gfJke/uBCG +Scct6VJ9bds8f+zEUCI4nrCvSUJ28qYBktcaSTIKvG1+AsOsdjfGm0W8IehjYFlLyUXJisHR +htY2yqVZiW1KjpSORgakPND4evZtchcP4Vo1kYVudLxXcVFBH4xdZd3hSrlBNVVZCY8Re0r2 +CyzYy5Qpwwfc2zjn3QaLvzaMXgzE4I6jhNs4xu0uEh6LLqooqH/+ECA0WqMEDoJmwh/5MruJ +kCN30gHC8wm95U1kunYB9UAFsdYX7EOSZuDCGUeMKUqDXNi1fhuIG6ofZQ5MGk2wKYoMQVEz +lDMYHdN9C57NoSvzSmvElAla6iiU6sHRHZSsqyZyOC68slWa2EIbC5ouW70HqpQM9eqWUUjP ++h16/Ot+6qRwlONn7ZplSoPuNT1kNu/j5J6xTAo2Z0y27ZpL1Zd7M26k07gsBGxD/6a8htaB +tqyDf71BEWaE3sO6c+DLV+8ukcSB2NOQDAstQQS/0bIxn/tdiQIcBBABAgAGBQJQey92AAoJ +EHQHM8emHeoxMTkP/RNMv3gTyBzOmW0WXoQGf8eEkKxyJGuuU78VEBi4+l/hqdteIkkBrfXe +6tOgg1pakqubspYGfs0MzCG6qy622I0TMybRPjlfiKpxmDAYPQdgKB2m1wHfKRTAubUca8e9 +upZa3ds0YLDu4dYeQeRL9d8+wot1AJeu02qyjnKdiEv8QvHW7ikJ9chnsKWCvu4+4ilsi83Q +0lvEZXb0ZzdWWk3inCf+kPDQuH9rf80Femn14deZIAbm3D8l2I9J1FYY12cmca2DRXBNI8w4 +euuQj+fVIphKRYRENny36s6hEQeJoM+CSPvx3v89fXfqoO9JySOFh1d2mG0XzXWz3uhxUn5B +k748juCk00YiLFkH6Vovv/Tf5uSt7vfrSkcGxqBBdrkyjIRU0n0BuxY+8AWPNPFVJ1dbHReO +bYXdz3UuwUVFeFgwzC2qIc5l7YW1UmP9cvtpIWMV38wG2UQqH6/NLpw6i2gGcmExBsBC24bl +i2gvlsXalJlOY4iTgbmwcGd+ki2z7XHnwyecjenfDXZQz2Y4s5msv/CAZLGXwdN+g7DRX47N +J+Z6e/7puz7QYHom06GZHHneA9VevdwTAblF7eJed35jttew1oHMR9NPSKLh2VHNJUR2UZt8 +DrNwJMMpZuw6iTxtnxnIzXTVD7ruNM2ZgF9lA1sAf/EuKJNX51KaiQIcBBABAgAGBQJSX3lU +AAoJEJz2q6aG5VRzj0sQALy7TxNB/47EQ++RdTkZk46818Gvr1Kg/h8H2NJf7urc4F5sGJdb +Cqhu5iYiF4v/pXMPFaPhlZFkYc/3b21JdhcuMvLJodReYAFgzZDF/ZiHdrFKUT441+bGKwA3 +ZfX6ngueeZYYZt15yc0k0P5KzoV454xYE5UkvAstCvqi0HMCl98U7BC44r8433DAySYb2gAO +4MWC5zu7dpUOdhngLAfMD7baO9yUp7RV+tKgNmfPi/JYLVpzfwgwv9oFDwlrn7OMQMd9BpU5 +/jN39Pgtd5vxsXE2H/0zsrkHDV/cn7KiWXRpbFnDkbIjk3hcM6CpewbdLbsO0iqXLcPfRL/p +rglIbQ4v3rVWNganu8oKmos/+CQsSoPeKA+vCzlt1V7uX8x4ctKq4vVbLTWuogIaATn4Jn/G +tVjtg+IMTVLbV2Lw4nQS07Bvh27n1zuiE9UHBciVdsbehCfo9/IK2aehDXF7ND5EcgRjN56v +B1peihwyo72i3kEB6QfS/wayv1YqnRxF8LYaWcHrmaBYz7eViSeiLEcfApnhInO4TP8BFyDq +TWYC9iWrdCd2QPRtvw5XG8PQ0ayJ/vrTiGg/L7JxjAMenZPAz0CSFn9A+H4JhYcUdmxjTemj +sq+sxpgvofRMQaKhzsCJgfuL0OAsxR72qIz5qLQoYKFPB4dhM/dDyIUZiQIcBBABAgAGBQJT +YzA/AAoJENzbS0i6QO0UdikP/0BGjMVuxrKLBzr5iuI/4o3X44D2PJy7+OmXcLeN4L8x5zU2 +44DDuIYi2fWAvCLcuLDwyF5UjccX2h2MQmyhdW8Fy2LuOUsAJxfFloUxYvkeAXF4kLcGOEZK +3X8byBA/TG9y+T5R6KzC5Tc0pGQG0xVCYgzZg6copg6BjN7sRiG6dvD3/+fRZFl+HQwF+mzT +N7CIhXTSUo7VYbBokvB7AopmWt7N+YNn1ENd3P8BEYXOfxoAany3CoFRRhk1AsGcvNlc86zY +WL4blCQPCpeH7OW+IjTNJBJEFsMQRueuEUfX9CoUmj8zGOIXXFFv2fRSl4B3Bj7Kc7/hb9j5 +1SwltyVXEsA9nzie7cvMgCtM5PUoAIofJv6Du3NOLy4GKqcwthAwD0pvpaGnEKy23ZjverHj +oJHj5SLxdyhi1F3VdMwFBcO8O6AipY3s2lyQIRt3u+fxztjHrZ/7NbEsrnCL4Av5maFWCn+p +QPjIaWO5m7A3ysCy4nmqBFvz7afjsNlB1h1SRY9cXxmI/Npi18BttKjNANea7nPmxbjFUc7D +Dt1OF7K4lGnHh1M3BbOwjhQDmxFEzzkWESBd9Bj5TuoLipIXeztU07jccsAecpr7UYecayz1 +jAGlJ0lMX0XxBNSoaaGkfI4/4CIiLgAG94oubF0+198HLALyjQdPhaHrMGP0iQIcBBABAgAG +BQJThw1iAAoJEBpAr5SpAPoW5cEP+wQoYXbljqE0Or4sKILK0d0G3rCKvsqKq10LxYmWD9iO +/E/js0NvXSLViTMhcklniYL11qbd5EtqF8p3aCtVLw/+CoHWYfexNstGs5qXExwy1dBOWac4 +aKTTVoiZZcP8YvVvVEgNhcRLYmDY1PQSGQvjgx3Fwyyf04JWXYbbeaqY6cMK9zeYpfbAcg9x +qC8w2Y92bXhRGkG7oUJAGtV/5CLbCcwnXXLqT7TqR/2koTSV33WvwAsgO/mpgaZp2e1DbV57 +tOeipnVeoOkLqMUnTV9xSK4bZ88DUXHHmodjsLnTXVtwgujdXKSfdxgPw1bnQWA6PmwKDN34 +aoJ3clBCxUFqXnNiCkhWXg3Hsvs0ChWEdWdLp9YN5+bOeioUqFfbmjZN9gHrlK2gCbnhgtWr +7VasaljHGDWvzSoyAkebhdhSH7iYtioODlJ+pOX+OwSUn03p5QoqsGtrZG4wnhZt6niDeXR2 +gNdAuARrw2e/oYRnfSiHXe2t9vPby+oP7J3a4BJez8meol3f6EpeZ+Y94VS3w/3mEKi3TiYC +efrKVoIfqMJhYMy3pP8Ry433iSnZRzK1sdr9BAuaWh9MNcfBO7iGr/S5mgmfkxVh+s7vZOB3 +0TL4f8O15ZVDiCe2Optl9Rjk5/z8cxpsFTQAujw2s4yiyK/xeyyDsyCpGvDFXWruiQIcBBAB +AgAGBQJUcA3TAAoJEH06bFpHzzhCPs4QAJI30jG8ATrF0XCJHtsOZF0khqB8SpSM1ljzD3Od +h6daacpyACwdav6FC07Ksyw3Wnpjb6maH2EoteVWLIWmXDFKF8cNhq4HC6sPBjc3WgRa5FKG +VDgk4L7A5gRPttDXekzWSPt8a2srImGwJilw7Lj2gjzdTbfB018BLwCZZAf6sptbAv4AukAJ +zmP8rw9zRpjx1dSSI95YikkdHNbR8AgZrMtpZnUZcijGBjl5ek9JgB7gm4ex96Bhhi71qV/4 +P0P7DuouRsEMpDfiLwTePtyhl9OLfmyJ6SeO4pYZbOzdcqxWtZtJKyz2juYWpVTEKjN+KRtw +Nl3hUulN1Dl15o6nsMOkQB003YaPgVJ+8TRUw9COf3UCoEMV816og9rB+PKqDeWsHbWA2/u2 +M4J94AnxKGkDvd6grm5LdVeaBxqpJr6p4wET1I/SC3819+YGsFf76zmCnbhHssmkod6LfVkc +3QG6BnMOQCWkKmtzVQECI4eyvUJy0oyyj9/REWpNxCqZ3f6KnuFyUtK8vWC2PPT7U5wJ/s+U +giN6Tc/ZiaTvatxXa/BRpCMOvBtukfRoWLjrSQ+OVK/iH83AeoEOUFCofoTD9vsoSa46HKPv +/fIEQlC1VaxOlwqncKh9F44yt1ig14+ehzPcmvKuzhfVLfSNOAWPpsFMfTA7d4OsbCc2iQIc +BBABAgAGBQJUcG4rAAoJEH06bFpHzzhCBL8P/3MeSkfgiGcOI1JBHmW9VhEP/M3zPljWI92O +kGX0xvw3pJf2S3uQUgQNHEpuMQ8x62NkVnahUGdRMcTLtCQGJugM1zeAIBIvsJDNRyVZXmJx +eKMLT3oXxskpbmZlsA1kuNwF9CkWxNAiFCuJL7Q1eYwb31LKa1WvepAQ1zeM8yNBUh4+LPzL +xBWTXdXrxQbzWBFsiNpGyaewUwaqj0mWXswApZM3CnHmD7kdT0E5gd4sJRJeEy7gMBFXY7dU +3mseC2AD1FXn6d9iZArempxLlZyMRI4BYZVFxwVuW3vlGTtVNOgXygtMWq7ROn5CAIDpDJsM +eQFMihR6IgY/OPu+2fUJosgj1iOMHAUkKRxtFmEx0rCPRc3wBJBFBwZtR6wC8pDrxlT3JqTw +HVl+eKCFwzbjaslHxbe27N3puFFKeXZyiyZrv6TbKSlzSOZC62koTwraRD+poYIYQMX1p+1r +GltOKRdSQJeFymoIPxl+boVSPyHnAIqWyQ/ucApwrtrXtArRLGp1VtQaH7ocd2BbbLpxXHX9 ++vZ8X/kWrupTiSod6c0VH/TTZSsNsGOxfdhrNa4U3LQoyhlw40W+HjvYgwXivCXgFPZHs1vV +362IIjrrdiflAPkILmdxE0ii7tkjt9vkqdfqqMYYtLr/Ba67jEIewMJvi/9t3rxWDpLraOUr +iQIcBBABAgAGBQJUcG7XAAoJEH06bFpHzzhC5XEQAIo0JSP9O0utc2VQV29fF4oeSS5rW7Qh +AxumViCasKre+5s7pg+kqMsshGG2u5qCMNDjeV3dRVz/RLYI7rPAP3NUHWBlZZ9qWM7wi6Ea +pmgA5yTUcJNHrS941cOeAsma/NwizBhl7fVXp2pILDBjUWg4tc9wxuOjc2LTuRK3crE5L9/l +6nfvX9LZa3HJnVnVCPDMzL3hbswjJ+NK4C7aCxFsNeZsZ/NI3dk1/77tN/+rGBP2HS6OZiZ5 +EpJwQcbL8O5kqg8S7Jq6h9haGk4d8Z5vp0rSZNp8andTYwdwd4MT9FDh7ujkCh/6uo453EB7 +auK193bbb75z4Hv1tuqZLlyeeR5W+qrgQXhZ/N8nWPcVE/LAwVslrURIz4fy/me+f1JormZz +WmYNPvHvnmGynkSPJZe++KCXWLj3hgarlQT9ZSmPc29umoNY1u+F/jJXLYPeyKjCy4VA0tLE +CavbDXc7VnelVZZTXeoPQ+Y+pqrvQWm5b2BGCBeKPN2JzhDTyAxbPi7PMhdxzq3LbPmuvIus +NHpLjVQ/+Utrg+pm3sb6PO/Hvr3tU1NHzcxiAOIhh/tLNn33jRGxw+lnwX/ponvrVqhDsg59 +RhBdlm9lGWQtfn9QgnERF0h6IuFjRxgHO2s7ClHiwvg8LJb7G0R/8oxSfOsTgRSyVfKmwEv+ +7dYGiQIcBBABAgAGBQJWGZqOAAoJEBmSJ04Sm690EXAQALl4B0fpxoTCPB/7ZiqgVXl0VZF8 +GHuhpD6ahfvWk5CV4l/5hmPKwPfb6IMwWP25d9F3VanQZfoU+BPNeLcMxMj3hPJnCW9O3BJP +lRCo/CMvFN9onQHtaCfLNXvR5kSRN9M4sSquLD1Db9fg3yMkFQ++mB0qs1A0Dg2Ldy6v2vGP +7dk/d2USB1jFp0jKXakv9v8UiRiheNUBmwLedmN3CUVywN9aE9/pBtRqWsZfMUF854iwlhuS +082aZVRXZJk00WBmYCKKPNWiWFYm9euV3cwKPVWNttlHnUncOKedp8v0o8PGobEcWaGF3GFm +m3anCq9PV7wUL7hpitF++JdgABuKZcC0IsYo7bzK5y9aKrQWSXHv37DFBypwCEvQZcMcg9iu +RYgE0H4jj88aj8E2/+2v/Ztqlctoujcb1UOpVgu+R9EXHbvarS306DbXRjmfiXx4tpbrM7ws +/QoPUI76w+mTmeXvNT67JSqXUnE0Tc+wv6Ty9LDXc6wz6Y1Qmpa3pUAuVT2r/N7yGA1rXfJf +5fIpu0FbERhp38OI8Z30oksFJPdp4VtXBvnbpZ9eQT+Mv9nZqO7mgIvgrYBOjgclpQ7CoQjY +r56xENgig2jhmKrJtWAyffx3x7pTF0A+yRGfjZEVpiQTFAyWhtc6YSSCZTQnJTkCNcRHreGa +qlypdysuiQIcBBABAgAGBQJWa1YWAAoJEFgrGcq/xCTJXaEQAK2G1sHC+CJ80T+C4X9KWAXp +1RervrpbYmYztMjWCtcf2roK+Oc2wiq2HwQr4pEbWAt2khog3NqsM9j0b+Uvx0A/HNwe7f73 +gjOQeYu9K+wVqJqXxQ67vuIPvjAHnE5fbS3RgVNSHjGRysju6tD6RJMjFiRkQ8rjGvoDYWJE +YSohE1TJbgbhcSwfn7gPDFyYBL6/TwAsAJv9Rq/fGIlNhy3pB2JfQF37GK4X3ArvbxEcOBCV +1qxWMIx077PCDKnWUFOcw2PCI6r7Dhzp2pa6GwB4hvjlTh5pDhoWPCfA0KAYgH0xybUkvnBR +THGf1ij84nM7XLv4npxmq/dU+UBOOBQxMrm91sH8CYskl9aX/Spjg+vSRjfXO6l2yDRMYWTA +aDr5PpHayDwCMrnPj/5aObn7R35MYa0JSCISqNJivSiBG1Q9d2LrhqtrKNeuyBtu3sXHdrdN +AKydtHn/B96vSIs6ndshrGkJ1Lw5ZUiewEL23U6C2DXwN4wX6Cf7cf8qigoX9fZnOg1KNM64 +IY0WrQzSJotJDpmmaU6CVHClWn4ytvlgk/31UesiKmQ4uWwsKN51P66vkDf6yH1sb6q8HcMo +PahqHVGSB4yQeyh3vf/NIEdiryNTDWFutiYvz5CLHWYB84xCrGMoF8Ut9hwfV8F5jvMO4j/a +1eSGXK1JfTkgiQIcBBABAgAGBQJXb+N9AAoJEOAERBA3RRmCekEQALMQmOSscSfLUz963Wgl +sY1+/QiqLp3IBlVAZt5Tt7oA4PIeBBQMNyP1VBFOckSkuzJnuejNnqfOGTK3Xhi1J1kHZvZz +eTR+3dZV5mMUJpjD6ENr27bQhTCOIcT8jeROhe0sB2KRrJjgJmsOfqUvFGabjH1oO9wMEXcT +4C4mkiD2aPdM2+vrRbyWsT7R60vP6EFVtYxElZprOQvEOUWezVscanbszvLHHsZPTN/9b3D0 +oj8fjeeWkU9ebmXLW4wdyElLbFO4HcwGO8keWqvsURdV1n6Cc7NlfuGrwVKCsvJvYR+WpUqZ +D6fhadjEfYYmt0Qt4Z80Y8qttGGfYw5mHt9ExOU3q47uvoW88yGQ8zuiBmhj9WuCAmCczY+V +JT6G3lP0shJ4zTvuv5Te895ug38wFOqy80xWs3i5gjfkh05ksDTvk4EInRRfhUiz459XCD48 ++/VU3uqDv5jcCqusWESI5Zk9S8DjfJyeBbQk5VseYH2KUt8+gt6bFFZ8jbUrUWfucI6i2z6K +KcDL1GrCRuW5xeXUr2l8MTHdE5L5fnNxjcvQL2i9YpJMbucTM57wfiUWRBuoSkz90Q8in5Hf +LHEo71G5rm21Et+jBfpJmGOhPiJ+JZfQf0pXWQz25Ny7cHYGdfgP3FYYMzLpCnrOEulIQJjI +aBtt38NXee2PhlyBiQIcBBABAgAGBQJYyYy4AAoJEAVe+f3Q286YtkEP/i/vp7jRXEu2SEE3 +Rr8QcjiGeXdUtvrC3J35lm4Kt+UEmNBxRlD98jeKpV9yHZelUY6Fn9dsuNKpF6j6ZKjzvjzo +IimOf3qUI2uCFRvtMP4MMPChY4cI54HyEK2Kb8k90Se3CSlz+OKhQOKWemmamLYr1LzOlZeo +n7VCAnJ57o4ZE8J/oP/p+yDIqYWVAqCqp872GY2QveciP6a0DTZrjGVHXoOlmwepbzKN1Vmf +mmlWCVbU9CiVgcGnQrXhEAWS3Slw28Wc1y1fWOC9WjbS/OnKLek8eY8I/LyOBYNI0RpeQmq7 +AH3ocHymMSxLMB1wvOELbfnYDOe1rx9FOfC/ur0UDkYOTV5W5HgerybSt1Th6b1IkX2UFnSV +kZQR/b68bij/mcPxKGTmYd2utjJp8kxB1GHw6dfE3jxBuqQongr6NwoaST4dvpVZcUsVXrMB +Rr2C2dOqm5IkNAcx9NJiwk+fi7hsojWUBFdJZx6Dh2Gzh+q88MafA7NN4ouLDaui3kHOzxpX +ksSEByaU0iVdU4J8FMID86SoRCPEfuxP/pjihh7VPuhdXYkixeV0mIIhueBDPT276xL/Cp+o +6wXsy/uzloOUc5Cph69c4F6lwtISqkGfSROLZ92mzWbi/LX+1KENc+NxIWNvSTDttD2qIam+ +Hj73iF43R+sT8TCjbj4uiQIcBBABCAAGBQJOemVSAAoJENNvdpvBGATwllgP/RYfiT+2KbEE +IZbcRE3pwLI47OqPnZx1kFfDLNqht4Dqbv93n4bOma9WocZ7cCtvg+hAfovjraIfWIIJsq8r +uWEcoKa1VczqitxuR9lOLUhjLaU4BOxFfU4pYvc71T/AqFS3DralAQG3QUgVkt3TQlqaHmJP +czFSFytPhlaVeMLhx8nz2/M6O+kuzWGeGrH238d88PAyxsZ7jQ/EasvOrakunBL8vczvTckZ +MJuWsdMttWYZZ8ukwGynaDcvlXn4lIecjTCBisX1L6VOQ5JsWYhuZi7BokcJoa1VN3czHcxQ +mylm5P06Pgx4HRr9f77xhtJcDu4qTOosW/RiiC0lh3PJOh9wypTMD4+AT7cPSSCZgFyA1BHH +uHVw7u6bp8/lQUO3rYYB9NzIf7EO1iMw5ONX4yF6SAxrG/gRfV23K3ea+IwL4A6X+DpMobdg +9xjPQKKOnye2j0HE93zGGZNTvbMc4JzGTAY8WVz1rfhuprfBtj5Xv+gk3Yd1+3wF25OvzLfl +H/F1ySIYqD6EuPRYD5Y6w6h7So/rugNqdIXRXWzthrmXSlzQm9LyMxaBHtTi9Awa45F6gQKk +7yA1RavbICEd6Wb4D/wvVSfyrtLp4lp8F4KyDxYPV5ECm8PvpwJWIN9vKh5JT+FbGK0HU51X +UXN1sZrv91C5hQGaLwy0UBniiQIcBBABCAAGBQJOemVSAAoJENNvdpvBGATwllgP/RYfiT+2 +KbEEIZbcRE3pwLI47OqPnZx1kFfDLNqht4Dqbv93n4bOma9WocZ7cCtvg+hAfovjraIfWIIJ +sq8ruWEcoKa1VczqitxuR9lOLUhjLaU4BOxFfU4pYvc71T/AqFS3DralAQG3QUgVkt3TQlqa +HmJPczFSFytPhlaVeMLhx8nz2/M6O+kuzWGeGrH238d88PAyxsZ7jQ/EasvOrakunBL8vczv +TckZMJuWsdMttWYZZ8ukwGynaDcvlXn4lIecjTCBisX1L6VOQ5JsWYhuZi7BokcJoa1VN3cz +HcxQmylm5P06Pgx4HRr9f77xhtJcDu4qTOosW/Ri//////////////////////////////// +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +////////////////////////////iQIcBBABCAAGBQJOpuWAAAoJEOe/yOyVhhEJ3yYQAJCe +rJ2Bc36bK+fS5ll6JzvBKpsc5ctwb3PZ9LjSm6MC+ZMR47T33g4Gu98z3kFFwsFyH1mkcrlq +y/ChjL2eddDRGfXqV0pqFh31Oln54lSiEhEjmAqQiLAPmeai34U1gxAsPRI6flYc+4g0iDvZ +MruhQqhsI/tNtvkq/ut/ZWaqlwJxjA1exDWUw+dHeaxmU26J2WtmEBLtEy9vTx5RgCyjcodH +SytCioaRhZl2NCeePhV6vxjir1EQSkPDruviXGnksEKAEZjvxzi1tNVlrsR9rnMDbzb6/tuT +OhoyAnGoOezrV17cB/yydQSkIVMFaSoIwaRaI3HvRk19fdnXPdypratAvrrH0smHF/FwvMac +HcRUTN3UM5BCXSWbPir28sm12m/KRd4UhtsaWoa/PHlFU2veBYpp3NyCTdLzVSk3mvq/lP9b +u8b/BKzwnjuAKPzZkNriw2D1FxpYAVfvW5xnJul1E7fchIt4D2LVVlG4zcjYD2S45VSj7bwY +Uk5jgELVtizez86/cdKHhBrHMKZ0UVDozhGmcAbCufAvgf7AxjnB6tdggDHIHkaBEiOB3/rm +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 +YrylZH+MwNDlB5u3I138 +=LTno +-----END PGP PUBLIC KEY BLOCK----- diff --git a/kernel/patches/0002-cpuidle-skip-synchronize_rcu-on-single-CPU-systems.patch b/kernel/patches/0002-cpuidle-skip-synchronize_rcu-on-single-CPU-systems.patch new file mode 100644 index 0000000..61ffcca --- /dev/null +++ b/kernel/patches/0002-cpuidle-skip-synchronize_rcu-on-single-CPU-systems.patch @@ -0,0 +1,34 @@ +From e86a8d2060f5551bc3bbb1621cce206954340cc1 Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Wed, 11 Feb 2015 16:19:26 -0600 +Subject: [PATCH 02/22] cpuidle: skip synchronize_rcu() on single CPU systems + +synchronize_rcu() is pretty expensive, and on single CPU systems we don't need +it in this specific case, so skip it. + +Signed-off-by: Arjan van de Ven +Signed-off-by: Miguel Bernal Marin +--- + drivers/cpuidle/cpuidle.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c +index c73207ab..224cefc0 100644 +--- a/drivers/cpuidle/cpuidle.c ++++ b/drivers/cpuidle/cpuidle.c +@@ -307,8 +307,11 @@ void cpuidle_uninstall_idle_handler(void) + /* + * Make sure external observers (such as the scheduler) + * are done looking at pointed idle states. ++ * This is only relevant if there is more than one cpu, ++ * if there is only one CPU, that is us... and we're ++ * coherent to ourselves. + */ +- synchronize_rcu(); ++ + } + + /** +-- +2.11.0 + diff --git a/kernel/patches/0003-sysrq-skip-synchronize_rcu-if-there-is-no-old-op.patch b/kernel/patches/0003-sysrq-skip-synchronize_rcu-if-there-is-no-old-op.patch new file mode 100644 index 0000000..791499c --- /dev/null +++ b/kernel/patches/0003-sysrq-skip-synchronize_rcu-if-there-is-no-old-op.patch @@ -0,0 +1,38 @@ +From 7b5447090b8fbb80a85320c880934f35acbf68a7 Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Wed, 11 Feb 2015 16:25:16 -0600 +Subject: [PATCH 03/22] sysrq: skip synchronize_rcu() if there is no old op + +synchronize_rcu() is expensive. Currently it is called as part of the sysrq +registration/unregistration, which happens during boot several times. +Now, the reason for the synchronize_rcu() is to allow an old registered +operation to expire properly... which is pointless if the old operation +is NULL... +So we can save the common case of the old operation being NULL a lot of time +by just checking for non-NULL prior to the synchronize_rcu() + +Signed-off-by: Arjan van de Ven +Signed-off-by: Miguel Bernal Marin +--- + drivers/tty/sysrq.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c +index 701c085b..c60c7ba5 100644 +--- a/drivers/tty/sysrq.c ++++ b/drivers/tty/sysrq.c +@@ -1065,8 +1065,10 @@ static int __sysrq_swap_key_ops(int key, struct sysrq_key_op *insert_op_p, + * A concurrent __handle_sysrq either got the old op or the new op. + * Wait for it to go away before returning, so the code for an old + * op is not freed (eg. on module unload) while it is in use. ++ * This is only relevant if the old op is not NULL of course. + */ +- synchronize_rcu(); ++ if (remove_op_p) ++ synchronize_rcu(); + + return retval; + } +-- +2.11.0 + diff --git a/kernel/patches/0005-vmstats-wakeups.patch b/kernel/patches/0005-vmstats-wakeups.patch new file mode 100644 index 0000000..57c73e1 --- /dev/null +++ b/kernel/patches/0005-vmstats-wakeups.patch @@ -0,0 +1,28 @@ +From 43e288fed0ccb8bf17cfea69d032425e6d224b96 Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Wed, 11 Feb 2015 16:47:20 -0600 +Subject: [PATCH 05/22] vmstats: wakeups + +Author: Arjan van de Ven + +Signed-off-by: Miguel Bernal Marin +--- + mm/vmstat.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/mm/vmstat.c b/mm/vmstat.c +index 604f26a4..bfbbcd76 100644 +--- a/mm/vmstat.c ++++ b/mm/vmstat.c +@@ -1549,7 +1549,7 @@ static const struct file_operations proc_vmstat_file_operations = { + #ifdef CONFIG_SMP + static struct workqueue_struct *vmstat_wq; + static DEFINE_PER_CPU(struct delayed_work, vmstat_work); +-int sysctl_stat_interval __read_mostly = HZ; ++int sysctl_stat_interval __read_mostly = 8 * HZ; + + #ifdef CONFIG_PROC_FS + static void refresh_vm_stats(struct work_struct *work) +-- +2.11.0 + diff --git a/kernel/patches/0006-pci-probe.patch b/kernel/patches/0006-pci-probe.patch new file mode 100644 index 0000000..1858d21 --- /dev/null +++ b/kernel/patches/0006-pci-probe.patch @@ -0,0 +1,123 @@ +From 9c7e56e2621a12943055442a2b4b963ee1604e4a Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Wed, 11 Feb 2015 16:53:08 -0600 +Subject: [PATCH 06/22] pci: probe + +Author: Arjan van de Ven + +Signed-off-by: Miguel Bernal Marin +--- + drivers/pci/probe.c | 43 ++++++++++++++++++++++++++++++++++++++++--- + 1 file changed, 40 insertions(+), 3 deletions(-) + +diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c +index d266d800..73ebc222 100644 +--- a/drivers/pci/probe.c ++++ b/drivers/pci/probe.c +@@ -182,6 +182,10 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, + + mask = type ? PCI_ROM_ADDRESS_MASK : ~0; + ++ res->name = pci_name(dev); ++ ++ printk("clr: Starting probe for %s\n", res->name); ++ + /* No printks while decoding is disabled! */ + if (!dev->mmio_always_on) { + pci_read_config_word(dev, PCI_COMMAND, &orig_cmd); +@@ -191,8 +195,6 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, + } + } + +- res->name = pci_name(dev); +- + pci_read_config_dword(dev, pos, &l); + pci_write_config_dword(dev, pos, l | mask); + pci_read_config_dword(dev, pos, &sz); +@@ -324,6 +326,8 @@ static void pci_read_bases(struct pci_dev *dev, unsigned int howmany, int rom) + if (dev->non_compliant_bars) + return; + ++ printk("clr: pci_read_bases start\n"); ++ + for (pos = 0; pos < howmany; pos++) { + struct resource *res = &dev->resource[pos]; + reg = PCI_BASE_ADDRESS_0 + (pos << 2); +@@ -332,11 +336,13 @@ static void pci_read_bases(struct pci_dev *dev, unsigned int howmany, int rom) + + if (rom) { + struct resource *res = &dev->resource[PCI_ROM_RESOURCE]; ++ printk("clr: rom path\n"); + dev->rom_base_reg = rom; + res->flags = IORESOURCE_MEM | IORESOURCE_PREFETCH | + IORESOURCE_READONLY | IORESOURCE_SIZEALIGN; + __pci_read_base(dev, pci_bar_mem32, res, rom); + } ++ printk("clr: pci_read_bases end\n"); + } + + static void pci_read_bridge_io(struct pci_bus *child) +@@ -1193,6 +1199,28 @@ static void pci_msi_setup_pci_dev(struct pci_dev *dev) + pci_msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_ENABLE, 0); + } + ++static int guess_bar_count(int class) ++{ ++ if (class == 0x068000) ++ return 0; ++ if (class == 0x020000) ++ return 2; ++ if (class == 0x010000) ++ return 2; ++ if (class == 0x00ff00) ++ return 1; ++ return 6; ++} ++ ++static int has_rom(int class, int rom) ++{ ++ if (class == 0x020000) ++ return 0; ++ if (class == 0x010000 || class == 0x00ff00) ++ return 0; ++ return rom; ++} ++ + /** + * pci_setup_device - fill in class and map information of a device + * @dev: the device structure to fill +@@ -1211,6 +1239,9 @@ int pci_setup_device(struct pci_dev *dev) + int pos = 0; + struct pci_bus_region region; + struct resource *res; ++ int maxbar; ++ ++ printk("clr: pci_setup_device start\n"); + + if (pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type)) + return -EIO; +@@ -1265,7 +1296,11 @@ int pci_setup_device(struct pci_dev *dev) + if (class == PCI_CLASS_BRIDGE_PCI) + goto bad; + pci_read_irq(dev); +- pci_read_bases(dev, 6, PCI_ROM_ADDRESS); ++ ++ maxbar = guess_bar_count(dev->class); ++ ++ if (class != PCI_CLASS_STORAGE_IDE) ++ pci_read_bases(dev, maxbar, has_rom(dev->class, PCI_ROM_ADDRESS)); + pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor); + pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device); + +@@ -1350,6 +1385,8 @@ int pci_setup_device(struct pci_dev *dev) + dev->class = PCI_CLASS_NOT_DEFINED << 8; + } + ++ printk("clr: pci_setup_device end\n"); ++ + /* We found a fine healthy device, go go go... */ + return 0; + } +-- +2.11.0 + diff --git a/kernel/patches/0007-cgroup.patch b/kernel/patches/0007-cgroup.patch new file mode 100644 index 0000000..1a2aeba --- /dev/null +++ b/kernel/patches/0007-cgroup.patch @@ -0,0 +1,107 @@ +From a672fb44791bab2e0b7cd519fbb55751fb2fbe16 Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Fri, 28 Aug 2015 11:00:36 -0500 +Subject: [PATCH 07/22] cgroup + +Author: Arjan van de Ven + +Signed-off-by: Miguel Bernal Marin +Signed-off-by: Jose Carlos Venegas Munoz +--- + include/linux/cgroup-defs.h | 2 +- + kernel/cgroup.c | 24 ++++++++++++++---------- + 2 files changed, 15 insertions(+), 11 deletions(-) + +diff --git a/include/linux/cgroup-defs.h b/include/linux/cgroup-defs.h +index 6fb1c34c..f5ffee9d 100644 +--- a/include/linux/cgroup-defs.h ++++ b/include/linux/cgroup-defs.h +@@ -137,7 +137,7 @@ struct cgroup_subsys_state { + + /* percpu_ref killing and RCU release */ + struct rcu_head rcu_head; +- struct work_struct destroy_work; ++ struct delayed_work destroy_work; + }; + + /* +diff --git a/kernel/cgroup.c b/kernel/cgroup.c +index 4c233437..27a71a9e 100644 +--- a/kernel/cgroup.c ++++ b/kernel/cgroup.c +@@ -73,7 +73,7 @@ + * Expiring in the middle is a performance problem not a correctness one. + * 1 sec should be enough. + */ +-#define CGROUP_PIDLIST_DESTROY_DELAY HZ ++#define CGROUP_PIDLIST_DESTROY_DELAY round_jiffies_relative(HZ) + + #define CGROUP_FILE_NAME_MAX (MAX_CGROUP_TYPE_NAMELEN + \ + MAX_CFTYPE_NAME + 2) +@@ -4987,8 +4987,9 @@ static struct cftype cgroup_legacy_base_files[] = { + */ + static void css_free_work_fn(struct work_struct *work) + { ++ struct delayed_work *dwork = to_delayed_work(work); + struct cgroup_subsys_state *css = +- container_of(work, struct cgroup_subsys_state, destroy_work); ++ container_of(dwork, struct cgroup_subsys_state, destroy_work); + struct cgroup_subsys *ss = css->ss; + struct cgroup *cgrp = css->cgroup; + +@@ -5037,14 +5038,15 @@ static void css_free_rcu_fn(struct rcu_head *rcu_head) + struct cgroup_subsys_state *css = + container_of(rcu_head, struct cgroup_subsys_state, rcu_head); + +- INIT_WORK(&css->destroy_work, css_free_work_fn); +- queue_work(cgroup_destroy_wq, &css->destroy_work); ++ INIT_DELAYED_WORK(&css->destroy_work, css_free_work_fn); ++ queue_delayed_work(cgroup_destroy_wq, &css->destroy_work, CGROUP_PIDLIST_DESTROY_DELAY); + } + + static void css_release_work_fn(struct work_struct *work) + { ++ struct delayed_work *dwork = to_delayed_work(work); + struct cgroup_subsys_state *css = +- container_of(work, struct cgroup_subsys_state, destroy_work); ++ container_of(dwork, struct cgroup_subsys_state, destroy_work); + struct cgroup_subsys *ss = css->ss; + struct cgroup *cgrp = css->cgroup; + +@@ -5087,8 +5089,9 @@ static void css_release(struct percpu_ref *ref) + struct cgroup_subsys_state *css = + container_of(ref, struct cgroup_subsys_state, refcnt); + +- INIT_WORK(&css->destroy_work, css_release_work_fn); +- queue_work(cgroup_destroy_wq, &css->destroy_work); ++ INIT_DELAYED_WORK(&css->destroy_work, css_release_work_fn); ++ queue_delayed_work(cgroup_destroy_wq, &css->destroy_work, CGROUP_PIDLIST_DESTROY_DELAY); ++ + } + + static void init_and_link_css(struct cgroup_subsys_state *css, +@@ -5367,8 +5370,9 @@ static int cgroup_mkdir(struct kernfs_node *parent_kn, const char *name, + */ + static void css_killed_work_fn(struct work_struct *work) + { ++ struct delayed_work *dwork = to_delayed_work(work); + struct cgroup_subsys_state *css = +- container_of(work, struct cgroup_subsys_state, destroy_work); ++ container_of(dwork, struct cgroup_subsys_state, destroy_work); + + mutex_lock(&cgroup_mutex); + +@@ -5389,8 +5393,8 @@ static void css_killed_ref_fn(struct percpu_ref *ref) + container_of(ref, struct cgroup_subsys_state, refcnt); + + if (atomic_dec_and_test(&css->online_cnt)) { +- INIT_WORK(&css->destroy_work, css_killed_work_fn); +- queue_work(cgroup_destroy_wq, &css->destroy_work); ++ INIT_DELAYED_WORK(&css->destroy_work, css_killed_work_fn); ++ queue_delayed_work(cgroup_destroy_wq, &css->destroy_work, CGROUP_PIDLIST_DESTROY_DELAY); + } + } + +-- +2.11.0 + diff --git a/kernel/patches/0008-smpboot-reuse-timer-calibration.patch b/kernel/patches/0008-smpboot-reuse-timer-calibration.patch new file mode 100644 index 0000000..c1513a9 --- /dev/null +++ b/kernel/patches/0008-smpboot-reuse-timer-calibration.patch @@ -0,0 +1,45 @@ +From a312877651e18175726e33e4530307880d249aa5 Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Wed, 11 Feb 2015 17:28:14 -0600 +Subject: [PATCH 08/22] smpboot: reuse timer calibration + +NO point recalibrating for known-constant tsc... saves 200ms+ of boot time. + +Author: Arjan van de Ven + +Signed-off-by: Miguel Bernal Marin +--- + arch/x86/kernel/smpboot.c | 2 +- + arch/x86/kernel/tsc.c | 3 +++ + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/smpboot.c b/arch/x86/kernel/smpboot.c +index 36171bcd..7f4e9cdf 100644 +--- a/arch/x86/kernel/smpboot.c ++++ b/arch/x86/kernel/smpboot.c +@@ -731,7 +731,7 @@ wakeup_secondary_cpu_via_init(int phys_apicid, unsigned long start_eip) + pr_debug("Waiting for send to finish...\n"); + send_status = safe_apic_wait_icr_idle(); + +- udelay(init_udelay); ++ udelay(100); + + pr_debug("Deasserting INIT\n"); + +diff --git a/arch/x86/kernel/tsc.c b/arch/x86/kernel/tsc.c +index 6e57edf3..94ab049f 100644 +--- a/arch/x86/kernel/tsc.c ++++ b/arch/x86/kernel/tsc.c +@@ -1390,6 +1390,9 @@ unsigned long calibrate_delay_is_known(void) + if (!mask) + return 0; + ++ if (cpu !=0) ++ return cpu_data(0).loops_per_jiffy; ++ + sibling = cpumask_any_but(mask, cpu); + if (sibling < nr_cpu_ids) + return cpu_data(sibling).loops_per_jiffy; +-- +2.11.0 + diff --git a/kernel/patches/0009-perf.patch b/kernel/patches/0009-perf.patch new file mode 100644 index 0000000..60ad272 --- /dev/null +++ b/kernel/patches/0009-perf.patch @@ -0,0 +1,28 @@ +From f1af807278f9b52c68b32e604aa4c23f1456f5d5 Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Wed, 4 Nov 2015 15:17:10 -0600 +Subject: [PATCH 09/22] perf + +Author: Arjan van de Ven + +Signed-off-by: Miguel Bernal Marin +--- + arch/x86/events/intel/core.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c +index f0f197f4..9d1f4060 100644 +--- a/arch/x86/events/intel/core.c ++++ b/arch/x86/events/intel/core.c +@@ -4028,7 +4028,7 @@ __init int intel_pmu_init(void) + */ + if (x86_pmu.extra_regs) { + for (er = x86_pmu.extra_regs; er->msr; er++) { +- er->extra_msr_access = check_msr(er->msr, 0x11UL); ++ er->extra_msr_access = false; + /* Disable LBR select mapping */ + if ((er->idx == EXTRA_REG_LBR) && !er->extra_msr_access) + x86_pmu.lbr_sel_map = NULL; +-- +2.11.0 + diff --git a/kernel/patches/0010-pci-probe-identify-known-devices.patch b/kernel/patches/0010-pci-probe-identify-known-devices.patch new file mode 100644 index 0000000..b818ab9 --- /dev/null +++ b/kernel/patches/0010-pci-probe-identify-known-devices.patch @@ -0,0 +1,190 @@ +From 0f320ebfefc339814bc7efe46a83550cc6ee1453 Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Sat, 14 Feb 2015 09:49:41 -0600 +Subject: [PATCH 10/22] pci: probe: identify known devices + +Author: Arjan van de Ven +Modify-by: Miguel Bernal Marin + +Signed-off-by: Miguel Bernal Marin +--- + drivers/pci/probe.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 156 insertions(+) + +diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c +index 73ebc222..d693b6b8 100644 +--- a/drivers/pci/probe.c ++++ b/drivers/pci/probe.c +@@ -163,6 +163,159 @@ static inline unsigned long decode_bar(struct pci_dev *dev, u32 bar) + + #define PCI_COMMAND_DECODE_ENABLE (PCI_COMMAND_MEMORY | PCI_COMMAND_IO) + ++/* shortcut version of __pci_read_base where we know the sizes already */ ++int __pci_read_base_shortcut(struct pci_dev *dev, enum pci_bar_type type, ++ struct resource *res, unsigned int pos, u32 sz_in, u32 sz2_in) ++{ ++ u32 l, sz; ++ u64 l64, sz64, mask64; ++ struct pci_bus_region region, inverted_region; ++ ++ res->name = pci_name(dev); ++ ++ pci_read_config_dword(dev, pos, &l); ++ ++ sz = sz_in; ++ ++ /* ++ * All bits set in sz means the device isn't working properly. ++ * If the BAR isn't implemented, all bits must be 0. If it's a ++ * memory BAR or a ROM, bit 0 must be clear; if it's an io BAR, bit ++ * 1 must be clear. ++ * Here we set the size and is not 0xffffffff ++ */ ++ ++ /* ++ * I don't know how l can have all bits set. Copied from old code. ++ * Maybe it fixes a bug on some ancient platform. ++ */ ++ if (l == 0xffffffff) ++ l = 0; ++ ++ if (type == pci_bar_unknown) { ++ res->flags = decode_bar(dev, l); ++ res->flags |= IORESOURCE_SIZEALIGN; ++ if (res->flags & IORESOURCE_IO) { ++ l64 = l & PCI_BASE_ADDRESS_IO_MASK; ++ sz64 = sz & PCI_BASE_ADDRESS_IO_MASK; ++ mask64 = PCI_BASE_ADDRESS_IO_MASK & (u32)IO_SPACE_LIMIT; ++ } else { ++ l64 = l & PCI_BASE_ADDRESS_MEM_MASK; ++ sz64 = sz & PCI_BASE_ADDRESS_MEM_MASK; ++ mask64 = (u32)PCI_BASE_ADDRESS_MEM_MASK; ++ } ++ } else { ++ res->flags |= (l & IORESOURCE_ROM_ENABLE); ++ l64 = l & PCI_ROM_ADDRESS_MASK; ++ sz64 = sz & PCI_ROM_ADDRESS_MASK; ++ mask64 = (u32)PCI_ROM_ADDRESS_MASK; ++ } ++ ++ if (res->flags & IORESOURCE_MEM_64) { ++ pci_read_config_dword(dev, pos + 4, &l); ++ sz = sz2_in; ++ ++ l64 |= ((u64)l << 32); ++ sz64 |= ((u64)sz << 32); ++ mask64 |= ((u64)~0 << 32); ++ } ++ ++ if (!sz64) ++ goto fail; ++ ++ sz64 = pci_size(l64, sz64, mask64); ++ if (!sz64) { ++ dev_info(&dev->dev, FW_BUG "reg 0x%x: invalid BAR (can't size)\n", ++ pos); ++ goto fail; ++ } ++ ++ if (res->flags & IORESOURCE_MEM_64) { ++ if ((sizeof(dma_addr_t) < 8 || sizeof(resource_size_t) < 8) && ++ sz64 > 0x100000000ULL) { ++ res->flags |= IORESOURCE_UNSET | IORESOURCE_DISABLED; ++ res->start = 0; ++ res->end = 0; ++ dev_err(&dev->dev, "reg 0x%x: can't handle BAR larger than 4GB (size %#010llx)\n", ++ pos, (unsigned long long)sz64); ++ goto out; ++ } ++ ++ if ((sizeof(dma_addr_t) < 8) && l) { ++ /* Above 32-bit boundary; try to reallocate */ ++ res->flags |= IORESOURCE_UNSET; ++ res->start = 0; ++ res->end = sz64; ++ dev_info(&dev->dev, "reg 0x%x: can't handle BAR above 4GB (bus address %#010llx)\n", ++ pos, (unsigned long long)l64); ++ goto out; ++ } ++ } ++ ++ region.start = l64; ++ region.end = l64 + sz64; ++ ++ pcibios_bus_to_resource(dev->bus, res, ®ion); ++ pcibios_resource_to_bus(dev->bus, &inverted_region, res); ++ ++ /* ++ * If "A" is a BAR value (a bus address), "bus_to_resource(A)" is ++ * the corresponding resource address (the physical address used by ++ * the CPU. Converting that resource address back to a bus address ++ * should yield the original BAR value: ++ * ++ * resource_to_bus(bus_to_resource(A)) == A ++ * ++ * If it doesn't, CPU accesses to "bus_to_resource(A)" will not ++ * be claimed by the device. ++ */ ++ if (inverted_region.start != region.start) { ++ res->flags |= IORESOURCE_UNSET; ++ res->start = 0; ++ res->end = region.end - region.start; ++ dev_info(&dev->dev, "reg 0x%x: initial BAR value %#010llx invalid\n", ++ pos, (unsigned long long)region.start); ++ } ++ ++ goto out; ++ ++ ++fail: ++ res->flags = 0; ++out: ++ if (res->flags) ++ dev_printk(KERN_DEBUG, &dev->dev, "reg 0x%x: %pR\n", pos, res); ++ ++ return (res->flags & IORESOURCE_MEM_64) ? 1 : 0; ++} ++ ++static int is_known_device(struct pci_dev *dev, int pos, int *sz) ++{ ++ /* Red Hat, Inc : Virtio network device */ ++ if (dev->vendor == 0x1af4 && dev->device == 0x1000) { ++ if (pos == 0x10) { ++ *sz = 0xffffffe1; ++ return 1; ++ } ++ if (pos == 0x14) { ++ *sz = 0xfffff000; ++ return 1; ++ } ++ } ++ /* Red Hat, Inc : Virtio block device */ ++ if (dev->vendor == 0x1af4 && dev->device == 0x1001) { ++ if (pos == 0x10) { ++ *sz = 0xffffffc1; ++ return 1; ++ } ++ if (pos == 0x14) { ++ *sz = 0xfffff000; ++ return 1; ++ } ++ } ++ return 0; ++} ++ + /** + * pci_read_base - read a PCI BAR + * @dev: the PCI device +@@ -182,6 +335,9 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, + + mask = type ? PCI_ROM_ADDRESS_MASK : ~0; + ++ if (is_known_device(dev, pos, &sz)) ++ return __pci_read_base_shortcut(dev, type, res, pos, sz, 0); ++ + res->name = pci_name(dev); + + printk("clr: Starting probe for %s\n", res->name); +-- +2.11.0 + diff --git a/kernel/patches/0011-init-no-wait-for-the-known-devices.patch b/kernel/patches/0011-init-no-wait-for-the-known-devices.patch new file mode 100644 index 0000000..aea4a1c --- /dev/null +++ b/kernel/patches/0011-init-no-wait-for-the-known-devices.patch @@ -0,0 +1,39 @@ +From 11a42057e43d77c04317eb3a3f40989b6f57864d Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Mon, 22 Jun 2015 09:33:33 -0500 +Subject: [PATCH 11/22] init: no wait for the known devices + +No wait for the known devices to complete their probing + +Author: Arjan van de Ven + +Signed-off-by: Miguel Bernal Marin +--- + init/do_mounts.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/init/do_mounts.c b/init/do_mounts.c +index dea5de95..da840946 100644 +--- a/init/do_mounts.c ++++ b/init/do_mounts.c +@@ -28,6 +28,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -563,7 +564,8 @@ void __init prepare_namespace(void) + * For example, it is not atypical to wait 5 seconds here + * for the touchpad of a laptop to initialize. + */ +- wait_for_device_probe(); ++ //wait_for_device_probe(); ++ async_synchronize_full(); + + md_run_setup(); + +-- +2.11.0 + diff --git a/kernel/patches/0012-ksm-wakeups.patch b/kernel/patches/0012-ksm-wakeups.patch new file mode 100644 index 0000000..e743ec5 --- /dev/null +++ b/kernel/patches/0012-ksm-wakeups.patch @@ -0,0 +1,32 @@ +From d8056696038fd33187ca41e25832ed3960c3ec7f Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Mon, 14 Mar 2016 11:06:46 -0600 +Subject: [PATCH 12/22] ksm-wakeups + +reduce wakeups in ksm +--- + mm/ksm.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/mm/ksm.c b/mm/ksm.c +index caa54a55..b043f871 100644 +--- a/mm/ksm.c ++++ b/mm/ksm.c +@@ -1724,8 +1724,12 @@ static int ksm_scan_thread(void *nothing) + try_to_freeze(); + + if (ksmd_should_run()) { +- schedule_timeout_interruptible( +- msecs_to_jiffies(ksm_thread_sleep_millisecs)); ++ if (ksm_thread_sleep_millisecs >= 1000) ++ schedule_timeout_interruptible( ++ msecs_to_jiffies(round_jiffies_relative(ksm_thread_sleep_millisecs))); ++ else ++ schedule_timeout_interruptible( ++ msecs_to_jiffies(ksm_thread_sleep_millisecs)); + } else { + wait_event_freezable(ksm_thread_wait, + ksmd_should_run() || kthread_should_stop()); +-- +2.11.0 + diff --git a/kernel/patches/0014-xattr-allow-setting-user.-attributes-on-symlinks-by-.patch b/kernel/patches/0014-xattr-allow-setting-user.-attributes-on-symlinks-by-.patch new file mode 100644 index 0000000..41f4042 --- /dev/null +++ b/kernel/patches/0014-xattr-allow-setting-user.-attributes-on-symlinks-by-.patch @@ -0,0 +1,56 @@ +From c03fac43a4294098b01d6a0eadd824e6c79e70e6 Mon Sep 17 00:00:00 2001 +From: Alan Cox +Date: Thu, 10 Mar 2016 15:11:28 +0000 +Subject: [PATCH 14/22] xattr: allow setting user.* attributes on symlinks by + owner + +Kvmtool and clear containers supports using user attributes to label host +files with the virtual uid/guid of the file in the container. This allows an +end user to manage their files and a complete uid space without all the ugly +namespace stuff. + +The one gap in the support is symlinks because an end user can change the +ownership of a symbolic link. We support attributes on these files as you +can already (as root) set security attributes on them. + +The current rules seem slightly over-paranoid and as we have a use case this +patch enables updating the attributes on a symbolic link IFF you are the +owner of the synlink (as permissions are not usually meaningful on the link +itself). + +Signed-off-by: Alan Cox +--- + fs/xattr.c | 14 ++++++++------ + 1 file changed, 8 insertions(+), 6 deletions(-) + +diff --git a/fs/xattr.c b/fs/xattr.c +index ed8c3745..f48d608e 100644 +--- a/fs/xattr.c ++++ b/fs/xattr.c +@@ -118,15 +118,17 @@ xattr_permission(struct inode *inode, const char *name, int mask) + } + + /* +- * In the user.* namespace, only regular files and directories can have +- * extended attributes. For sticky directories, only the owner and +- * privileged users can write attributes. ++ * In the user.* namespace, only regular files, symbolic links, and ++ * directories can have extended attributes. For symbolic links and ++ * sticky directories, only the owner and privileged users can write ++ * attributes. + */ + if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) { +- if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) ++ if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode) && !S_ISLNK(inode->i_mode)) + return (mask & MAY_WRITE) ? -EPERM : -ENODATA; +- if (S_ISDIR(inode->i_mode) && (inode->i_mode & S_ISVTX) && +- (mask & MAY_WRITE) && !inode_owner_or_capable(inode)) ++ if (((S_ISDIR(inode->i_mode) && (inode->i_mode & S_ISVTX)) ++ || S_ISLNK(inode->i_mode)) && (mask & MAY_WRITE) ++ && !inode_owner_or_capable(inode)) + return -EPERM; + } + +-- +2.11.0 + diff --git a/kernel/patches/0015-crypto-allow-testmgr-to-be-skipped.patch b/kernel/patches/0015-crypto-allow-testmgr-to-be-skipped.patch new file mode 100644 index 0000000..a0db6b8 --- /dev/null +++ b/kernel/patches/0015-crypto-allow-testmgr-to-be-skipped.patch @@ -0,0 +1,25 @@ +From ec4c5378faf4ab331d56706d58e112c14ec45396 Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Wed, 7 Dec 2016 17:50:05 -0600 +Subject: [PATCH 15/22] crypto: allow testmgr to be skipped + +--- + crypto/testmgr.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/crypto/testmgr.c b/crypto/testmgr.c +index 62dffa00..197e9c53 100644 +--- a/crypto/testmgr.c ++++ b/crypto/testmgr.c +@@ -36,7 +36,7 @@ + + #include "internal.h" + +-static bool notests; ++static bool notests = true; + module_param(notests, bool, 0644); + MODULE_PARM_DESC(notests, "disable crypto self-tests"); + +-- +2.11.0 + diff --git a/kernel/patches/0016-silence-Power-down-msg.patch b/kernel/patches/0016-silence-Power-down-msg.patch new file mode 100644 index 0000000..3023ed5 --- /dev/null +++ b/kernel/patches/0016-silence-Power-down-msg.patch @@ -0,0 +1,25 @@ +From 5b5f050eec3a244002b1a729627b7b610c86a4e5 Mon Sep 17 00:00:00 2001 +From: Jose Carlos Venegas Munoz +Date: Wed, 7 Dec 2016 17:53:45 -0600 +Subject: [PATCH 16/22] silence "Power down" msg + +--- + kernel/reboot.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/kernel/reboot.c b/kernel/reboot.c +index bd30a973..eb6f8f6e 100644 +--- a/kernel/reboot.c ++++ b/kernel/reboot.c +@@ -261,7 +261,7 @@ void kernel_power_off(void) + pm_power_off_prepare(); + migrate_to_reboot_cpu(); + syscore_shutdown(); +- pr_emerg("Power down\n"); ++ pr_info("Power down\n"); + kmsg_dump(KMSG_DUMP_POWEROFF); + machine_power_off(); + } +-- +2.11.0 + diff --git a/kernel/patches/0017-fs-9p-fix-create-unlink-getattr-idiom.patch b/kernel/patches/0017-fs-9p-fix-create-unlink-getattr-idiom.patch new file mode 100644 index 0000000..b6a4df3 --- /dev/null +++ b/kernel/patches/0017-fs-9p-fix-create-unlink-getattr-idiom.patch @@ -0,0 +1,131 @@ +From 3db64f7f26d077d4c05e413ac21269bc5a897c6a Mon Sep 17 00:00:00 2001 +From: Eric Van Hensbergen +Date: Tue, 21 Apr 2015 12:46:29 -0700 +Subject: [PATCH 17/22] fs/9p: fix create-unlink-getattr idiom + +Fixes several outstanding bug reports of not being able to getattr from an +open file after an unlink. This patch cleans up transient fids on an unlink +and will search open fids on a client if it detects a dentry that appears to +have been unlinked. This search is necessary because fstat does not pass fd +information through the VFS API to the filesystem, only the dentry which for +9p has an imperfect match to fids. + +Inherent in this patch is also a fix for the qid handling on create/open +which apparently wasn't being set correctly and was necessary for the search +to succeed. + +A possible optimization over this fix is to include accounting of open fids +with the inode in the private data (in a similar fashion to the way we track +transient fids with dentries). This would allow a much quicker search for +a matching open fid. + +Signed-off-by: Eric Van Hensbergen +--- + fs/9p/fid.c | 30 ++++++++++++++++++++++++++++++ + fs/9p/vfs_inode.c | 4 ++++ + net/9p/client.c | 5 ++++- + 3 files changed, 38 insertions(+), 1 deletion(-) + +diff --git a/fs/9p/fid.c b/fs/9p/fid.c +index 60fb4746..e19c9cf7 100644 +--- a/fs/9p/fid.c ++++ b/fs/9p/fid.c +@@ -54,6 +54,33 @@ void v9fs_fid_add(struct dentry *dentry, struct p9_fid *fid) + } + + /** ++ * v9fs_fid_find_global - search for a fid off of the client list ++ * @inode: return a fid pointing to a specific inode ++ * @uid: return a fid belonging to the specified user ++ * ++ */ ++ ++static struct p9_fid *v9fs_fid_find_inode(struct inode *inode, kuid_t uid) ++{ ++ struct p9_client *clnt = v9fs_inode2v9ses(inode)->clnt; ++ struct p9_fid *fid, *fidptr, *ret = NULL; ++ unsigned long flags; ++ ++ p9_debug(P9_DEBUG_VFS, " inode: %p\n", inode); ++ ++ spin_lock_irqsave(&clnt->lock, flags); ++ list_for_each_entry_safe(fid, fidptr, &clnt->fidlist, flist) { ++ if (uid_eq(fid->uid, uid) && ++ (inode->i_ino == v9fs_qid2ino(&fid->qid))) { ++ ret = fid; ++ break; ++ } ++ } ++ spin_unlock_irqrestore(&clnt->lock, flags); ++ return ret; ++} ++ ++/** + * v9fs_fid_find - retrieve a fid that belongs to the specified uid + * @dentry: dentry to look for fid in + * @uid: return fid that belongs to the specified user +@@ -80,6 +107,9 @@ static struct p9_fid *v9fs_fid_find(struct dentry *dentry, kuid_t uid, int any) + } + } + spin_unlock(&dentry->d_lock); ++ } else { ++ if (dentry->d_inode) ++ ret = v9fs_fid_find_inode(dentry->d_inode, uid); + } + + return ret; +diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c +index 30ca770c..c00487ea 100644 +--- a/fs/9p/vfs_inode.c ++++ b/fs/9p/vfs_inode.c +@@ -624,6 +624,10 @@ static int v9fs_remove(struct inode *dir, struct dentry *dentry, int flags) + + v9fs_invalidate_inode_attr(inode); + v9fs_invalidate_inode_attr(dir); ++ ++ /* invalidate all fids associated with dentry */ ++ /* NOTE: This will not include open fids */ ++ dentry->d_op->d_release(dentry); + } + return retval; + } +diff --git a/net/9p/client.c b/net/9p/client.c +index cf129fec..8284ad03 100644 +--- a/net/9p/client.c ++++ b/net/9p/client.c +@@ -1208,7 +1208,7 @@ struct p9_fid *p9_client_walk(struct p9_fid *oldfid, uint16_t nwname, + if (nwname) + memmove(&fid->qid, &wqids[nwqids - 1], sizeof(struct p9_qid)); + else +- fid->qid = oldfid->qid; ++ memmove(&fid->qid, &oldfid->qid, sizeof(struct p9_qid)); + + kfree(wqids); + return fid; +@@ -1261,6 +1261,7 @@ int p9_client_open(struct p9_fid *fid, int mode) + p9_is_proto_dotl(clnt) ? "RLOPEN" : "ROPEN", qid.type, + (unsigned long long)qid.path, qid.version, iounit); + ++ memmove(&fid->qid, &qid, sizeof(struct p9_qid)); + fid->mode = mode; + fid->iounit = iounit; + +@@ -1306,6 +1307,7 @@ int p9_client_create_dotl(struct p9_fid *ofid, char *name, u32 flags, u32 mode, + (unsigned long long)qid->path, + qid->version, iounit); + ++ memmove(&ofid->qid, qid, sizeof(struct p9_qid)); + ofid->mode = mode; + ofid->iounit = iounit; + +@@ -1351,6 +1353,7 @@ int p9_client_fcreate(struct p9_fid *fid, char *name, u32 perm, int mode, + (unsigned long long)qid.path, + qid.version, iounit); + ++ memmove(&fid->qid, &qid, sizeof(struct p9_qid)); + fid->mode = mode; + fid->iounit = iounit; + +-- +2.11.0 + diff --git a/kernel/patches/0018-rdrand.patch b/kernel/patches/0018-rdrand.patch new file mode 100644 index 0000000..9a9b3b6 --- /dev/null +++ b/kernel/patches/0018-rdrand.patch @@ -0,0 +1,24 @@ +From 9ac45f2a4f0de76365e7b12414500f3a12bf028e Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Tue, 29 Mar 2016 14:29:24 -0600 +Subject: [PATCH 18/22] rdrand + +--- + arch/x86/kernel/cpu/rdrand.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/arch/x86/kernel/cpu/rdrand.c b/arch/x86/kernel/cpu/rdrand.c +index cfa97ff6..cd7aa166 100644 +--- a/arch/x86/kernel/cpu/rdrand.c ++++ b/arch/x86/kernel/cpu/rdrand.c +@@ -48,6 +48,7 @@ void x86_init_rdrand(struct cpuinfo_x86 *c) + if (!cpu_has(c, X86_FEATURE_RDRAND)) + return; + ++ return; + for (i = 0; i < SANITY_CHECK_LOOPS; i++) { + if (!rdrand_long(&tmp)) { + clear_cpu_cap(c, X86_FEATURE_RDRAND); +-- +2.11.0 + diff --git a/kernel/patches/0019-reboot.patch b/kernel/patches/0019-reboot.patch new file mode 100644 index 0000000..133a5e3 --- /dev/null +++ b/kernel/patches/0019-reboot.patch @@ -0,0 +1,34 @@ +From 1629faac6cc01351c26608c3ea8c669c8b87459b Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Thu, 11 Feb 2016 11:06:26 -0600 +Subject: [PATCH 19/22] reboot + +--- + kernel/reboot.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/kernel/reboot.c b/kernel/reboot.c +index eb6f8f6e..b367c1e0 100644 +--- a/kernel/reboot.c ++++ b/kernel/reboot.c +@@ -309,7 +309,7 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, + * halt when pm_power_off is not set do it the easy way. + */ + if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off) +- cmd = LINUX_REBOOT_CMD_HALT; ++ cmd = LINUX_REBOOT_CMD_RESTART; + + mutex_lock(&reboot_mutex); + switch (cmd) { +@@ -326,7 +326,7 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, + break; + + case LINUX_REBOOT_CMD_HALT: +- kernel_halt(); ++ kernel_restart(NULL); + do_exit(0); + panic("cannot halt"); + +-- +2.11.0 + diff --git a/kernel/patches/0020-no-early-modprobe.patch b/kernel/patches/0020-no-early-modprobe.patch new file mode 100644 index 0000000..2342ee4 --- /dev/null +++ b/kernel/patches/0020-no-early-modprobe.patch @@ -0,0 +1,25 @@ +From 9424467a5d1fb1f971076187c07c78a81e9cf661 Mon Sep 17 00:00:00 2001 +From: Arjan van de Ven +Date: Thu, 11 Feb 2016 11:07:54 -0600 +Subject: [PATCH 20/22] no early modprobe + +--- + kernel/kmod.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/kernel/kmod.c b/kernel/kmod.c +index 0277d121..20b5777f 100644 +--- a/kernel/kmod.c ++++ b/kernel/kmod.c +@@ -557,7 +557,7 @@ int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait) + DECLARE_COMPLETION_ONSTACK(done); + int retval = 0; + +- if (!sub_info->path) { ++ if (!sub_info->path || system_state == SYSTEM_BOOTING) { + call_usermodehelper_freeinfo(sub_info); + return -EINVAL; + } +-- +2.11.0 + diff --git a/kernel/patches/0022-Show-restart-information-using-info-log.patch b/kernel/patches/0022-Show-restart-information-using-info-log.patch new file mode 100644 index 0000000..02238c6 --- /dev/null +++ b/kernel/patches/0022-Show-restart-information-using-info-log.patch @@ -0,0 +1,28 @@ +From f209324e2d027b432870b64847122e55ba805ee9 Mon Sep 17 00:00:00 2001 +From: Dimitri John Ledkov +Date: Thu, 11 Feb 2016 13:14:53 -0600 +Subject: [PATCH 22/22] Show restart information using info log + +--- + kernel/reboot.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/kernel/reboot.c b/kernel/reboot.c +index b367c1e0..cfd60137 100644 +--- a/kernel/reboot.c ++++ b/kernel/reboot.c +@@ -217,9 +217,9 @@ void kernel_restart(char *cmd) + migrate_to_reboot_cpu(); + syscore_shutdown(); + if (!cmd) +- pr_emerg("Restarting system\n"); ++ pr_info("Restarting system\n"); + else +- pr_emerg("Restarting system with command '%s'\n", cmd); ++ pr_info("Restarting system with command '%s'\n", cmd); + kmsg_dump(KMSG_DUMP_RESTART); + machine_restart(cmd); + } +-- +2.11.0 + diff --git a/kernel/patches/0023-virtio-wayland.patch b/kernel/patches/0023-virtio-wayland.patch new file mode 100644 index 0000000..b5de698 --- /dev/null +++ b/kernel/patches/0023-virtio-wayland.patch @@ -0,0 +1,1412 @@ +From ee3ae46ee497e111f6f4afbe3635f461e991c1e9 Mon Sep 17 00:00:00 2001 +From: Zach Reizner +Date: Mon, 14 Aug 2017 17:16:55 -0700 +Subject: [PATCH] virtwl: add virtwl driver + +TEST=emerge-tatl chromeos-kernel-4_4 +BUG=chromium:738638 + +Change-Id: I6e8e128a5548c915a9561938cbb066edc8c42747 +Reviewed-on: https://chromium-review.googlesource.com/567299 +Commit-Ready: Zach Reizner +Tested-by: Zach Reizner +Reviewed-by: Zach Reizner +--- + +diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig +index cab9f3f..968e737 100644 +--- a/drivers/virtio/Kconfig ++++ b/drivers/virtio/Kconfig +@@ -79,4 +79,12 @@ + + If unsure, say 'N'. + ++config VIRTIO_WL ++ bool "Virtio Wayland driver" ++ depends on VIRTIO ++ ---help--- ++ This driver supports proxying of a wayland socket from host to guest. ++ ++ If unsure, say 'N'. ++ + endmenu +diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile +index 41e30e3..bd941ca 100644 +--- a/drivers/virtio/Makefile ++++ b/drivers/virtio/Makefile +@@ -5,3 +5,4 @@ + virtio_pci-$(CONFIG_VIRTIO_PCI_LEGACY) += virtio_pci_legacy.o + obj-$(CONFIG_VIRTIO_BALLOON) += virtio_balloon.o + obj-$(CONFIG_VIRTIO_INPUT) += virtio_input.o ++obj-$(CONFIG_VIRTIO_WL) += virtio_wl.o +diff --git a/drivers/virtio/virtio_wl.c b/drivers/virtio/virtio_wl.c +new file mode 100644 +index 0000000..6cec6ae +--- /dev/null ++++ b/drivers/virtio/virtio_wl.c +@@ -0,0 +1,1198 @@ ++/* ++ * Wayland Virtio Driver ++ * Copyright (C) 2017 Google, Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ */ ++ ++/* ++Virtio Wayland (virtio_wl or virtwl) is a virtual device that allows a guest ++virtual machine to use a wayland server on the host transparently (to the host). ++This is done by proxying the wayland protocol socket stream verbatim between the ++host and guest over 2 (recv and send) virtio queues. The guest can request new ++wayland server connections to give each guest wayland client a different server ++context. Each host connection's file descriptor is exposed to the guest as a ++virtual file descriptor (VFD). Additionally, the guest can request shared memory ++file descriptors which are also exposed as VFDs. These shared memory VFDs are ++directly writable by the guest via device memory injected by the host. Each VFD ++is sendable along a connection context VFD and will appear as ancillary data to ++the wayland server, just like a message from an ordinary wayland client. When ++the wayland server sends a shared memory file descriptor to the client (such as ++when sending a keymap), a VFD is allocated by the device automatically and its ++memory is injected into as device memory. ++ ++This driver is intended to be paired with the `virtwl_guest_proxy` program which ++is run in the guest system and acts like a wayland server. It accepts wayland ++client connections and converts their socket messages to ioctl messages exposed ++by this driver via the `/dev/wl` device file. While it would be possible to ++expose a unix stream socket from this driver, the user space helper is much ++cleaner to write. ++*/ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define VFD_ILLEGAL_SIGN_BIT 0x80000000 ++#define VFD_HOST_VFD_ID_BIT 0x40000000 ++ ++struct virtwl_vfd_qentry { ++ struct list_head list; ++ struct virtio_wl_ctrl_hdr *hdr; ++ unsigned int len; /* total byte length of ctrl_vfd_* + vfds + data */ ++ unsigned int vfd_offset; /* int offset into vfds */ ++ unsigned int data_offset; /* byte offset into data */ ++}; ++ ++struct virtwl_vfd { ++ struct kobject kobj; ++ struct mutex lock; ++ ++ struct virtwl_info *vi; ++ uint32_t id; ++ uint32_t flags; ++ uint64_t pfn; ++ uint32_t size; ++ ++ struct list_head in_queue; /* list of virtwl_vfd_qentry */ ++ wait_queue_head_t in_waitq; ++}; ++ ++struct virtwl_info { ++ dev_t dev_num; ++ struct device *dev; ++ struct class *class; ++ struct cdev cdev; ++ ++ struct mutex vq_locks[VIRTWL_QUEUE_COUNT]; ++ struct virtqueue *vqs[VIRTWL_QUEUE_COUNT]; ++ struct work_struct in_vq_work; ++ struct work_struct out_vq_work; ++ ++ wait_queue_head_t out_waitq; ++ ++ struct mutex vfds_lock; ++ struct idr vfds; ++}; ++ ++static struct virtwl_vfd *virtwl_vfd_alloc(struct virtwl_info *vi); ++static void virtwl_vfd_free(struct virtwl_vfd *vfd); ++ ++static struct file_operations virtwl_vfd_fops; ++ ++static int virtwl_resp_err(unsigned int type) ++{ ++ switch (type) { ++ case VIRTIO_WL_RESP_OK: ++ case VIRTIO_WL_RESP_VFD_NEW: ++ return 0; ++ case VIRTIO_WL_RESP_ERR: ++ return -ENODEV; /* Device is no longer reliable */ ++ case VIRTIO_WL_RESP_OUT_OF_MEMORY: ++ return -ENOMEM; ++ case VIRTIO_WL_RESP_INVALID_ID: ++ case VIRTIO_WL_RESP_INVALID_TYPE: ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int vq_return_inbuf_locked(struct virtqueue *vq, void *buffer) ++{ ++ int ret; ++ struct scatterlist sg[1]; ++ sg_init_one(sg, buffer, PAGE_SIZE); ++ ++ ret = virtqueue_add_inbuf(vq, sg, 1, buffer, GFP_KERNEL); ++ if (ret) { ++ printk("virtwl: failed to give inbuf to host: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int vq_queue_out(struct virtwl_info *vi, struct scatterlist *out_sg, ++ struct scatterlist *in_sg, ++ struct completion *finish_completion, ++ bool nonblock) ++{ ++ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_OUT]; ++ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_OUT]; ++ struct scatterlist *sgs[] = { out_sg, in_sg }; ++ int ret = 0; ++ ++ mutex_lock(vq_lock); ++ while ((ret = virtqueue_add_sgs(vq, sgs, 1, 1, finish_completion, ++ GFP_KERNEL)) == -ENOSPC) { ++ mutex_unlock(vq_lock); ++ if (nonblock) ++ return -EAGAIN; ++ if (!wait_event_timeout(vi->out_waitq, vq->num_free > 0, HZ)) ++ return -EBUSY; ++ mutex_lock(vq_lock); ++ } ++ if (!ret) ++ virtqueue_kick(vq); ++ mutex_unlock(vq_lock); ++ ++ return ret; ++} ++ ++static int vq_fill_locked(struct virtqueue *vq) ++{ ++ void *buffer; ++ int ret = 0; ++ ++ while (vq->num_free > 0) { ++ buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); ++ if (!buffer) { ++ ret = -ENOMEM; ++ goto clear_queue; ++ } ++ ++ ret = vq_return_inbuf_locked(vq, buffer); ++ if (ret) ++ goto clear_queue; ++ } ++ ++ return 0; ++ ++clear_queue: ++ while ((buffer = virtqueue_detach_unused_buf(vq))) { ++ kfree(buffer); ++ } ++ return ret; ++} ++ ++static bool vq_handle_new(struct virtwl_info *vi, ++ struct virtio_wl_ctrl_vfd_new *new, unsigned int len) ++{ ++ struct virtwl_vfd *vfd; ++ u32 id = new->vfd_id; ++ int ret; ++ ++ if (id == 0) ++ return true; /* return the inbuf to vq */ ++ ++ if (!(id & VFD_HOST_VFD_ID_BIT) || (id & VFD_ILLEGAL_SIGN_BIT)) { ++ printk("virtwl: received a vfd with invalid id: %u\n", id); ++ return true; /* return the inbuf to vq */ ++ } ++ ++ vfd = virtwl_vfd_alloc(vi); ++ if (!vfd) ++ return true; /* return the inbuf to vq */ ++ ++ mutex_lock(&vi->vfds_lock); ++ ret = idr_alloc(&vi->vfds, vfd, id, id + 1, GFP_KERNEL); ++ mutex_unlock(&vi->vfds_lock); ++ ++ if (ret <= 0) { ++ virtwl_vfd_free(vfd); ++ printk("virtwl: failed to place received vfd: %d\n", ret); ++ return true; /* return the inbuf to vq */ ++ } ++ ++ vfd->id = id; ++ vfd->size = new->size; ++ vfd->pfn = new->pfn; ++ vfd->flags = new->flags; ++ ++ return true; /* return the inbuf to vq */ ++} ++ ++static bool vq_handle_recv(struct virtwl_info *vi, ++ struct virtio_wl_ctrl_vfd_recv *recv, ++ unsigned int len) ++{ ++ struct virtwl_vfd *vfd; ++ struct virtwl_vfd_qentry *qentry; ++ ++ mutex_lock(&vi->vfds_lock); ++ vfd = idr_find(&vi->vfds, recv->vfd_id); ++ if (vfd) ++ mutex_lock(&vfd->lock); ++ mutex_unlock(&vi->vfds_lock); ++ ++ if (!vfd) { ++ printk("virtwl: recv for unknown vfd_id %u\n", recv->vfd_id); ++ return true; /* return the inbuf to vq */ ++ } ++ ++ qentry = kzalloc(sizeof(*qentry), GFP_KERNEL); ++ if (!qentry) { ++ mutex_unlock(&vfd->lock); ++ printk("virtwl: failed to allocate qentry for vfd\n"); ++ return true; /* return the inbuf to vq */ ++ } ++ ++ qentry->hdr = &recv->hdr; ++ qentry->len = len; ++ ++ list_add_tail(&qentry->list, &vfd->in_queue); ++ wake_up_interruptible(&vfd->in_waitq); ++ mutex_unlock(&vfd->lock); ++ ++ return false; /* no return the inbuf to vq */ ++} ++ ++static bool vq_dispatch_hdr(struct virtwl_info *vi, unsigned int len, ++ struct virtio_wl_ctrl_hdr *hdr) ++{ ++ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_IN]; ++ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_IN]; ++ bool return_vq = true; ++ int ret; ++ ++ switch (hdr->type) { ++ case VIRTIO_WL_CMD_VFD_NEW: ++ return_vq = vq_handle_new(vi, ++ (struct virtio_wl_ctrl_vfd_new *)hdr, ++ len); ++ break; ++ case VIRTIO_WL_CMD_VFD_RECV: ++ return_vq = vq_handle_recv(vi, ++ (struct virtio_wl_ctrl_vfd_recv *)hdr, len); ++ break; ++ default: ++ printk("virtwl: unhandled ctrl command: %u\n", hdr->type); ++ break; ++ } ++ ++ if (!return_vq) ++ return false; /* no kick the vq */ ++ ++ mutex_lock(vq_lock); ++ ret = vq_return_inbuf_locked(vq, hdr); ++ mutex_unlock(vq_lock); ++ if (ret) { ++ printk("virtwl: failed to return inbuf to host: %d\n", ret); ++ kfree(hdr); ++ } ++ ++ return true; /* kick the vq */ ++} ++ ++static void vq_in_work_handler(struct work_struct *work) ++{ ++ struct virtwl_info *vi = container_of(work, struct virtwl_info, ++ in_vq_work); ++ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_IN]; ++ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_IN]; ++ void *buffer; ++ unsigned int len; ++ bool kick_vq = false; ++ ++ mutex_lock(vq_lock); ++ while ((buffer = virtqueue_get_buf(vq, &len)) != NULL) { ++ struct virtio_wl_ctrl_hdr *hdr = buffer; ++ mutex_unlock(vq_lock); ++ kick_vq |= vq_dispatch_hdr(vi, len, hdr); ++ mutex_lock(vq_lock); ++ } ++ mutex_unlock(vq_lock); ++ ++ if (kick_vq) ++ virtqueue_kick(vq); ++} ++ ++static void vq_out_work_handler(struct work_struct *work) ++{ ++ struct virtwl_info *vi = container_of(work, struct virtwl_info, ++ out_vq_work); ++ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_OUT]; ++ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_OUT]; ++ unsigned int len; ++ struct completion *finish_completion; ++ bool wake_waitq = false; ++ ++ mutex_lock(vq_lock); ++ while ((finish_completion = virtqueue_get_buf(vq, &len)) != NULL) { ++ wake_waitq = true; ++ complete(finish_completion); ++ } ++ mutex_unlock(vq_lock); ++ ++ if (wake_waitq) ++ wake_up_interruptible(&vi->out_waitq); ++} ++ ++static void vq_in_cb(struct virtqueue *vq) ++{ ++ struct virtwl_info *vi = vq->vdev->priv; ++ schedule_work(&vi->in_vq_work); ++} ++ ++static void vq_out_cb(struct virtqueue *vq) ++{ ++ struct virtwl_info *vi = vq->vdev->priv; ++ schedule_work(&vi->out_vq_work); ++} ++ ++static struct virtwl_vfd *virtwl_vfd_alloc(struct virtwl_info *vi) ++{ ++ struct virtwl_vfd *vfd = kzalloc(sizeof(struct virtwl_vfd), GFP_KERNEL); ++ if (!vfd) ++ return ERR_PTR(-ENOMEM); ++ ++ vfd->vi = vi; ++ ++ mutex_init(&vfd->lock); ++ INIT_LIST_HEAD(&vfd->in_queue); ++ init_waitqueue_head(&vfd->in_waitq); ++ ++ return vfd; ++} ++ ++/* Locks the vfd and unlinks its id from vi */ ++static void virtwl_vfd_lock_unlink(struct virtwl_vfd *vfd) ++{ ++ struct virtwl_info *vi = vfd->vi; ++ /* this order is important to avoid deadlock */ ++ mutex_lock(&vi->vfds_lock); ++ mutex_lock(&vfd->lock); ++ idr_remove(&vi->vfds, vfd->id); ++ mutex_unlock(&vi->vfds_lock); ++} ++ ++/* ++ * Only used to free a vfd that is not referenced any place else and contains ++ * no queed virtio buffers. This must not be called while vfd is included in a ++ * vi->vfd. ++ */ ++static void virtwl_vfd_free(struct virtwl_vfd *vfd) ++{ ++ kfree(vfd); ++} ++ ++/* ++ * Thread safe and also removes vfd from vi as well as any queued virtio buffers ++ */ ++static void virtwl_vfd_remove(struct virtwl_vfd *vfd) ++{ ++ struct virtwl_info *vi = vfd->vi; ++ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_IN]; ++ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_IN]; ++ struct virtwl_vfd_qentry *qentry, *next; ++ virtwl_vfd_lock_unlink(vfd); ++ ++ mutex_lock(vq_lock); ++ list_for_each_entry_safe(next, qentry, &vfd->in_queue, list) { ++ vq_return_inbuf_locked(vq, qentry->hdr); ++ list_del(&qentry->list); ++ kfree(qentry); ++ } ++ mutex_unlock(vq_lock); ++ ++ virtwl_vfd_free(vfd); ++} ++ ++static void vfd_qentry_free_if_empty(struct virtwl_vfd *vfd, ++ struct virtwl_vfd_qentry *qentry) ++{ ++ struct virtwl_info *vi = vfd->vi; ++ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_IN]; ++ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_IN]; ++ ++ if (qentry->hdr->type == VIRTIO_WL_CMD_VFD_RECV) { ++ struct virtio_wl_ctrl_vfd_recv *recv = ++ (struct virtio_wl_ctrl_vfd_recv *)qentry->hdr; ++ ssize_t data_len = ++ (ssize_t)qentry->len - (ssize_t)sizeof(*recv) - ++ (ssize_t)recv->vfd_count * (ssize_t)sizeof(__le32); ++ ++ if (qentry->vfd_offset < recv->vfd_count) ++ return; ++ ++ if ((s64)qentry->data_offset < data_len) ++ return; ++ } ++ ++ mutex_lock(vq_lock); ++ vq_return_inbuf_locked(vq, qentry->hdr); ++ mutex_unlock(vq_lock); ++ list_del(&qentry->list); ++ kfree(qentry); ++ virtqueue_kick(vq); ++} ++ ++static ssize_t vfd_out_locked(struct virtwl_vfd *vfd, char __user *buffer, ++ size_t len) ++{ ++ struct virtwl_vfd_qentry *qentry, *next; ++ ssize_t read_count = 0; ++ ++ list_for_each_entry_safe(qentry, next, &vfd->in_queue, list) { ++ struct virtio_wl_ctrl_vfd_recv *recv = ++ (struct virtio_wl_ctrl_vfd_recv *)qentry->hdr; ++ size_t recv_offset = sizeof(*recv) + recv->vfd_count * ++ sizeof(__le32) + qentry->data_offset; ++ u8 *buf = (u8 *)recv + recv_offset; ++ ssize_t to_read = (ssize_t)qentry->len - (ssize_t)recv_offset; ++ if (read_count >= len) ++ break; ++ if (to_read <= 0) ++ continue; ++ if (qentry->hdr->type != VIRTIO_WL_CMD_VFD_RECV) ++ continue; ++ ++ if ((to_read + read_count) > len) ++ to_read = len - read_count; ++ ++ if (copy_to_user(buffer + read_count, buf, to_read)) { ++ /* return error unless we have some data to return */ ++ if (read_count == 0) ++ read_count = -EFAULT; ++ break; ++ } ++ ++ read_count += to_read; ++ ++ qentry->data_offset += to_read; ++ vfd_qentry_free_if_empty(vfd, qentry); ++ } ++ ++ return read_count; ++} ++ ++static size_t vfd_out_vfds_locked(struct virtwl_vfd *vfd, ++ struct virtwl_vfd **vfds, size_t count) ++{ ++ struct virtwl_info *vi = vfd->vi; ++ struct virtwl_vfd_qentry *qentry, *next; ++ size_t i; ++ size_t read_count = 0; ++ ++ list_for_each_entry_safe(qentry, next, &vfd->in_queue, list) { ++ struct virtio_wl_ctrl_vfd_recv *recv = ++ (struct virtio_wl_ctrl_vfd_recv *)qentry->hdr; ++ size_t vfd_offset = sizeof(*recv) + qentry->vfd_offset * ++ sizeof(__le32); ++ __le32 *vfds_le = (__le32 *)((void *)recv + vfd_offset); ++ ssize_t vfds_to_read = recv->vfd_count - qentry->vfd_offset; ++ if (read_count >= count) ++ break; ++ if (vfds_to_read <= 0) ++ continue; ++ if (qentry->hdr->type != VIRTIO_WL_CMD_VFD_RECV) ++ continue; ++ ++ if ((vfds_to_read + read_count) > count) ++ vfds_to_read = count - read_count; ++ ++ for (i = 0; i < vfds_to_read; i++) { ++ uint32_t vfd_id = le32_to_cpu(vfds_le[i]); ++ /* ++ This is an inversion of the typical locking order ++ (vi->vfds_lock before vfd->lock). The reason this is ++ safe from deadlocks is because the lock held as a ++ precondition of this function call is always for a ++ different vfd than the one received on this vfd's queue. ++ */ ++ mutex_lock(&vi->vfds_lock); ++ vfds[read_count] = idr_find(&vi->vfds, vfd_id); ++ mutex_unlock(&vi->vfds_lock); ++ if (vfds[read_count]) { ++ read_count++; ++ } else { ++ printk("virtwl: received a vfd with unrecognized id: %u\n", ++ vfd_id); ++ } ++ qentry->vfd_offset++; ++ } ++ ++ vfd_qentry_free_if_empty(vfd, qentry); ++ } ++ ++ return read_count; ++} ++ ++/* this can only be called if the caller has unique ownership of the vfd */ ++static int do_vfd_close(struct virtwl_vfd *vfd) ++{ ++ struct virtio_wl_ctrl_vfd *ctrl_close; ++ struct virtwl_info *vi = vfd->vi; ++ struct completion finish_completion; ++ struct scatterlist out_sg; ++ struct scatterlist in_sg; ++ int ret = 0; ++ ++ ctrl_close = kzalloc(sizeof(*ctrl_close), GFP_KERNEL); ++ if (!ctrl_close) ++ return -ENOMEM; ++ ++ ctrl_close->hdr.type = VIRTIO_WL_CMD_VFD_CLOSE; ++ ctrl_close->vfd_id = vfd->id; ++ ++ sg_init_one(&in_sg, &ctrl_close->hdr, sizeof(struct virtio_wl_ctrl_vfd)); ++ sg_init_one(&out_sg, &ctrl_close->hdr, sizeof(struct virtio_wl_ctrl_hdr)); ++ ++ init_completion(&finish_completion); ++ ret = vq_queue_out(vi, &out_sg, &in_sg, &finish_completion, ++ false /* block */); ++ if (ret) { ++ printk("virtwl: failed to queue close vfd id %u: %d\n", vfd->id, ++ ret); ++ goto free_ctrl_close; ++ } ++ ++ wait_for_completion(&finish_completion); ++ virtwl_vfd_remove(vfd); ++ ++free_ctrl_close: ++ kfree(ctrl_close); ++ return ret; ++} ++ ++static ssize_t virtwl_vfd_recv(struct file *filp, char __user *buffer, ++ size_t len, struct virtwl_vfd **vfds, ++ size_t *vfd_count) ++{ ++ struct virtwl_vfd *vfd = filp->private_data; ++ ssize_t read_count = 0; ++ size_t vfd_read_count = 0; ++ ++ mutex_lock(&vfd->lock); ++ ++ while (read_count == 0 && vfd_read_count == 0) { ++ while (list_empty(&vfd->in_queue)) { ++ mutex_unlock(&vfd->lock); ++ if (filp->f_flags & O_NONBLOCK) ++ return -EAGAIN; ++ ++ if (wait_event_interruptible(vfd->in_waitq, ++ !list_empty(&vfd->in_queue))) ++ return -ERESTARTSYS; ++ ++ mutex_lock(&vfd->lock); ++ } ++ ++ read_count = vfd_out_locked(vfd, buffer, len); ++ if (read_count < 0) ++ goto out_unlock; ++ if (vfds && vfd_count && *vfd_count) ++ vfd_read_count = vfd_out_vfds_locked(vfd, vfds, ++ *vfd_count); ++ } ++ ++ *vfd_count = vfd_read_count; ++ ++out_unlock: ++ mutex_unlock(&vfd->lock); ++ return read_count; ++} ++ ++static int virtwl_vfd_mmap(struct file *filp, struct vm_area_struct *vma) ++{ ++ struct virtwl_vfd *vfd = filp->private_data; ++ unsigned long vm_size = vma->vm_end - vma->vm_start; ++ int ret = 0; ++ ++ mutex_lock(&vfd->lock); ++ ++ if (!(vfd->flags & VIRTIO_WL_VFD_MAP)) { ++ ret = -EACCES; ++ goto out_unlock; ++ } ++ ++ if ((vma->vm_flags & VM_WRITE) && !(vfd->flags & VIRTIO_WL_VFD_WRITE)) { ++ ret = -EACCES; ++ goto out_unlock; ++ } ++ ++ if (vm_size + (vma->vm_pgoff << PAGE_SHIFT) > PAGE_ALIGN(vfd->size)) { ++ ret = -EINVAL; ++ goto out_unlock; ++ } ++ ++ ret = io_remap_pfn_range(vma, vma->vm_start, vfd->pfn, vm_size, ++ vma->vm_page_prot); ++ if (ret) ++ goto out_unlock; ++ ++ vma->vm_flags |= VM_PFNMAP | VM_IO | VM_DONTEXPAND | VM_DONTDUMP; ++ ++out_unlock: ++ mutex_unlock(&vfd->lock); ++ return ret; ++} ++ ++static unsigned int virtwl_vfd_poll(struct file *filp, ++ struct poll_table_struct *wait) ++{ ++ struct virtwl_vfd *vfd = filp->private_data; ++ struct virtwl_info *vi = vfd->vi; ++ unsigned int mask = 0; ++ ++ mutex_lock(&vi->vq_locks[VIRTWL_VQ_OUT]); ++ poll_wait(filp, &vi->out_waitq, wait); ++ if (vi->vqs[VIRTWL_VQ_OUT]->num_free) ++ mask |= POLLOUT | POLLWRNORM; ++ mutex_unlock(&vi->vq_locks[VIRTWL_VQ_OUT]); ++ ++ mutex_lock(&vfd->lock); ++ poll_wait(filp, &vfd->in_waitq, wait); ++ if (!list_empty(&vfd->in_queue)) ++ mask |= POLLIN | POLLRDNORM; ++ mutex_unlock(&vfd->lock); ++ ++ return mask; ++} ++ ++static int virtwl_vfd_release(struct inode *inodep, struct file *filp) ++{ ++ struct virtwl_vfd *vfd = filp->private_data; ++ uint32_t vfd_id = vfd->id; ++ ++ /* ++ * if release is called, filp must be out of references and we have the ++ * last reference ++ */ ++ int ret = do_vfd_close(vfd); ++ if (ret) ++ printk("virtwl: failed to release vfd id %u: %d\n", vfd_id, ++ ret); ++ return 0; ++} ++ ++static int virtwl_open(struct inode *inodep, struct file *filp) ++{ ++ struct virtwl_info *vi = container_of(inodep->i_cdev, ++ struct virtwl_info, cdev); ++ ++ filp->private_data = vi; ++ ++ return 0; ++} ++ ++static int do_send(struct virtwl_vfd *vfd, const char __user *buffer, u32 len, ++ int *vfd_fds, bool nonblock) ++{ ++ struct virtwl_info *vi = vfd->vi; ++ struct fd vfd_files[VIRTWL_SEND_MAX_ALLOCS] = { { 0 } }; ++ struct virtwl_vfd *vfds[VIRTWL_SEND_MAX_ALLOCS] = { 0 }; ++ size_t vfd_count = 0; ++ size_t post_send_size; ++ struct virtio_wl_ctrl_vfd_send *ctrl_send; ++ __le32 *vfd_ids; ++ u8 *out_buffer; ++ unsigned long remaining; ++ struct completion finish_completion; ++ struct scatterlist out_sg; ++ struct scatterlist in_sg; ++ int ret; ++ int i; ++ ++ if (vfd_fds) { ++ for (i = 0; i < VIRTWL_SEND_MAX_ALLOCS; i++) { ++ struct fd vfd_file; ++ int fd = vfd_fds[i]; ++ if (fd < 0) ++ break; ++ ++ vfd_file = fdget(vfd_fds[i]); ++ if (!vfd_file.file) { ++ ret = -EBADFD; ++ goto put_files; ++ } ++ vfd_files[i] = vfd_file; ++ ++ if (vfd_file.file->f_op != &virtwl_vfd_fops) { ++ ret = -EINVAL; ++ goto put_files; ++ } ++ ++ vfds[i] = vfd_file.file->private_data; ++ if (!vfds[i] || !vfds[i]->id) { ++ ret = -EINVAL; ++ goto put_files; ++ } ++ ++ vfd_count++; ++ } ++ } ++ ++ post_send_size = vfd_count * sizeof(__le32) + len; ++ ctrl_send = kzalloc(sizeof(*ctrl_send) + post_send_size, GFP_KERNEL); ++ if (!ctrl_send) { ++ ret = -ENOMEM; ++ goto put_files; ++ } ++ ++ vfd_ids = (__le32 *)((u8*)ctrl_send + sizeof(*ctrl_send)); ++ out_buffer = (u8*)vfd_ids + vfd_count * sizeof(__le32); ++ ++ ctrl_send->hdr.type = VIRTIO_WL_CMD_VFD_SEND; ++ ctrl_send->vfd_id = vfd->id; ++ ctrl_send->vfd_count = vfd_count; ++ for (i = 0; i < vfd_count; i++) { ++ vfd_ids[i] = cpu_to_le32(vfds[i]->id); ++ } ++ ++ remaining = copy_from_user(out_buffer, buffer, len); ++ if (remaining) ++ goto free_ctrl_send; ++ ++ init_completion(&finish_completion); ++ sg_init_one(&out_sg, ctrl_send, sizeof(*ctrl_send) + post_send_size); ++ sg_init_one(&in_sg, ctrl_send, sizeof(struct virtio_wl_ctrl_hdr)); ++ ++ ret = vq_queue_out(vi, &out_sg, &in_sg, &finish_completion, nonblock); ++ if (ret) ++ goto free_ctrl_send; ++ ++ wait_for_completion(&finish_completion); ++ ++ ret = virtwl_resp_err(ctrl_send->hdr.type); ++ ++free_ctrl_send: ++ kfree(ctrl_send); ++put_files: ++ for (i = 0; i < VIRTWL_SEND_MAX_ALLOCS; i++) { ++ if (!vfd_files[i].file) ++ continue; ++ fdput(vfd_files[i]); ++ } ++ return ret; ++} ++ ++static struct virtwl_vfd *do_new(struct virtwl_info *vi, uint32_t type, ++ uint32_t size, bool nonblock) ++{ ++ struct virtio_wl_ctrl_vfd_new *ctrl_new; ++ struct virtwl_vfd *vfd; ++ struct completion finish_completion; ++ struct scatterlist out_sg; ++ struct scatterlist in_sg; ++ int ret = 0; ++ ++ if (type != VIRTWL_IOCTL_NEW_CTX && type != VIRTWL_IOCTL_NEW_ALLOC) ++ return ERR_PTR(-EINVAL); ++ ++ ctrl_new = kzalloc(sizeof(*ctrl_new), GFP_KERNEL); ++ if (!ctrl_new) ++ return ERR_PTR(-ENOMEM); ++ ++ vfd = virtwl_vfd_alloc(vi); ++ if (!vfd) { ++ ret = -ENOMEM; ++ goto free_ctrl_new; ++ } ++ ++ /* ++ * Take the lock before adding it to the vfds list where others might ++ * reference it. ++ */ ++ mutex_lock(&vfd->lock); ++ ++ mutex_lock(&vi->vfds_lock); ++ ret = idr_alloc(&vi->vfds, vfd, 1, VIRTWL_MAX_ALLOC, GFP_KERNEL); ++ mutex_unlock(&vi->vfds_lock); ++ if (ret <= 0) ++ goto free_vfd; ++ ++ vfd->id = ret; ++ ret = 0; ++ ++ ctrl_new->vfd_id = vfd->id; ++ switch (type) { ++ case VIRTWL_IOCTL_NEW_CTX: ++ ctrl_new->hdr.type = VIRTIO_WL_CMD_VFD_NEW_CTX; ++ ctrl_new->flags = VIRTIO_WL_VFD_CONTROL; ++ ctrl_new->size = 0; ++ break; ++ case VIRTWL_IOCTL_NEW_ALLOC: ++ ctrl_new->hdr.type = VIRTIO_WL_CMD_VFD_NEW; ++ ctrl_new->flags = VIRTIO_WL_VFD_WRITE | VIRTIO_WL_VFD_MAP; ++ ctrl_new->size = size; ++ break; ++ default: ++ ret = -EINVAL; ++ goto remove_vfd; ++ } ++ ++ init_completion(&finish_completion); ++ sg_init_one(&out_sg, ctrl_new, sizeof(*ctrl_new)); ++ sg_init_one(&in_sg, ctrl_new, sizeof(*ctrl_new)); ++ ++ ret = vq_queue_out(vi, &out_sg, &in_sg, &finish_completion, nonblock); ++ if (ret) ++ goto remove_vfd; ++ ++ wait_for_completion(&finish_completion); ++ ++ ret = virtwl_resp_err(ctrl_new->hdr.type); ++ if (ret) ++ goto remove_vfd; ++ ++ vfd->size = ctrl_new->size; ++ vfd->pfn = ctrl_new->pfn; ++ vfd->flags = ctrl_new->flags; ++ ++ mutex_unlock(&vfd->lock); ++ ++ kfree(ctrl_new); ++ return vfd; ++ ++remove_vfd: ++ /* unlock the vfd to avoid deadlock when unlinking it */ ++ mutex_unlock(&vfd->lock); ++ virtwl_vfd_lock_unlink(vfd); ++free_vfd: ++ virtwl_vfd_free(vfd); ++free_ctrl_new: ++ kfree(ctrl_new); ++ return ERR_PTR(ret); ++} ++ ++static long virtwl_ioctl_send(struct file *filp, unsigned long arg) ++{ ++ struct virtwl_vfd *vfd = filp->private_data; ++ struct virtwl_ioctl_send ioctl_send; ++ void __user *user_data = (void __user *)arg + ++ sizeof(struct virtwl_ioctl_send); ++ int ret; ++ ++ ret = copy_from_user(&ioctl_send, (void __user *)arg, ++ sizeof(struct virtwl_ioctl_send)); ++ if (ret) ++ return -EFAULT; ++ ++ /* Early check for user error; do_send still uses copy_from_user. */ ++ ret = !access_ok(VERIFY_READ, user_data, ioctl_send.len); ++ if (ret) ++ return -EFAULT; ++ ++ return do_send(vfd, user_data, ioctl_send.len, ioctl_send.fds, ++ filp->f_flags & O_NONBLOCK); ++} ++ ++static long virtwl_ioctl_recv(struct file *filp, unsigned long arg) ++{ ++ struct virtwl_ioctl_recv ioctl_recv; ++ void __user *user_data = (void __user *)arg + ++ sizeof(struct virtwl_ioctl_recv); ++ int __user *user_fds = (int __user *)arg; ++ size_t vfd_count = VIRTWL_SEND_MAX_ALLOCS; ++ struct virtwl_vfd *vfds[VIRTWL_SEND_MAX_ALLOCS] = { 0 }; ++ int fds[VIRTWL_SEND_MAX_ALLOCS]; ++ size_t i; ++ int ret = 0; ++ ++ ++ for (i = 0; i < VIRTWL_SEND_MAX_ALLOCS; i++) ++ fds[i] = -1; ++ ++ ret = copy_from_user(&ioctl_recv, (void __user *)arg, ++ sizeof(struct virtwl_ioctl_recv)); ++ if (ret) ++ return -EFAULT; ++ ++ /* Early check for user error. */ ++ ret = !access_ok(VERIFY_WRITE, user_data, ioctl_recv.len); ++ if (ret) ++ return -EFAULT; ++ ++ ret = virtwl_vfd_recv(filp, user_data, ioctl_recv.len, vfds, ++ &vfd_count); ++ if (ret < 0) ++ return ret; ++ ++ ret = copy_to_user(&((struct virtwl_ioctl_recv __user *)arg)->len, &ret, ++ sizeof(ioctl_recv.len)); ++ if (ret) { ++ ret = -EFAULT; ++ goto free_vfds; ++ } ++ ++ for (i = 0; i < vfd_count; i++) { ++ ret = anon_inode_getfd("[virtwl_vfd]", &virtwl_vfd_fops, ++ vfds[i], O_CLOEXEC | O_RDWR); ++ if (ret < 0) { ++ do_vfd_close(vfds[i]); ++ goto free_vfds; ++ } ++ vfds[i] = NULL; ++ fds[i] = ret; ++ } ++ ++ ret = copy_to_user(user_fds, fds, sizeof(int) * VIRTWL_SEND_MAX_ALLOCS); ++ if (ret) { ++ ret = -EFAULT; ++ goto free_vfds; ++ } ++ ++ return 0; ++ ++free_vfds: ++ for (i = 0; i < vfd_count; i++) { ++ if (vfds[i]) ++ do_vfd_close(vfds[i]); ++ if (fds[i] >= 0) ++ __close_fd(current->files, fds[i]); ++ } ++ return ret; ++} ++ ++static long virtwl_vfd_ioctl(struct file *filp, unsigned int cmd, ++ unsigned long arg) ++{ ++ switch (cmd) { ++ case VIRTWL_IOCTL_SEND: ++ return virtwl_ioctl_send(filp, arg); ++ case VIRTWL_IOCTL_RECV: ++ return virtwl_ioctl_recv(filp, arg); ++ default: ++ return -ENOTTY; ++ } ++} ++ ++static long virtwl_ioctl_new(struct file *filp, unsigned long arg) ++{ ++ struct virtwl_info *vi = filp->private_data; ++ struct virtwl_vfd *vfd; ++ struct virtwl_ioctl_new ioctl_new; ++ int ret; ++ ++ ret = copy_from_user(&ioctl_new, (void __user *)arg, ++ sizeof(struct virtwl_ioctl_new)); ++ if (ret) ++ return -EFAULT; ++ ++ ioctl_new.size = PAGE_ALIGN(ioctl_new.size); ++ ++ vfd = do_new(vi, ioctl_new.type, ioctl_new.size, ++ filp->f_flags & O_NONBLOCK); ++ if (IS_ERR(vfd)) ++ return PTR_ERR(vfd); ++ ++ ret = anon_inode_getfd("[virtwl_vfd]", &virtwl_vfd_fops, vfd, ++ O_CLOEXEC | O_RDWR); ++ if (ret < 0) { ++ do_vfd_close(vfd); ++ return ret; ++ } ++ ++ ioctl_new.fd = ret; ++ ret = copy_to_user((void __user *)arg, &ioctl_new, ++ sizeof(struct virtwl_ioctl_new)); ++ if (ret) { ++ /* The release operation will handle freeing this alloc */ ++ sys_close(ioctl_new.fd); ++ return -EFAULT; ++ } ++ ++ return 0; ++} ++ ++static long virtwl_ioctl(struct file *filp, unsigned int cmd, ++ unsigned long arg) ++{ ++ int err = 0; ++ ++ if (_IOC_TYPE(cmd) != VIRTWL_IOCTL_BASE) ++ return -ENOTTY; ++ if (_IOC_NR(cmd) > VIRTWL_IOCTL_MAXNR) ++ return -ENOTTY; ++ ++ if (_IOC_DIR(cmd) & _IOC_READ) { ++ err = !access_ok(VERIFY_WRITE, (void __user *)arg, ++ _IOC_SIZE(cmd)); ++ } else if (_IOC_DIR(cmd) & _IOC_WRITE) { ++ err = !access_ok(VERIFY_READ, (void __user *)arg, ++ _IOC_SIZE(cmd)); ++ } ++ ++ if (err) ++ return -EFAULT; ++ ++ if (filp->f_op == &virtwl_vfd_fops) ++ return virtwl_vfd_ioctl(filp, cmd, arg); ++ ++ switch (cmd) { ++ case VIRTWL_IOCTL_NEW: ++ return virtwl_ioctl_new(filp, arg); ++ default: ++ return -ENOTTY; ++ } ++} ++ ++static int virtwl_release(struct inode *inodep, struct file *filp) ++{ ++ return 0; ++} ++ ++static struct file_operations virtwl_fops = ++{ ++ .open = virtwl_open, ++ .unlocked_ioctl = virtwl_ioctl, ++ .release = virtwl_release, ++}; ++ ++static struct file_operations virtwl_vfd_fops = ++{ ++ .mmap = virtwl_vfd_mmap, ++ .poll = virtwl_vfd_poll, ++ .unlocked_ioctl = virtwl_ioctl, ++ .release = virtwl_vfd_release, ++}; ++ ++static int probe_common(struct virtio_device *vdev) ++{ ++ int i; ++ int ret; ++ struct virtwl_info *vi = NULL; ++ vq_callback_t *vq_callbacks[] = { vq_in_cb, vq_out_cb }; ++ const char *vq_names[] = { "in", "out" }; ++ ++ vi = kzalloc(sizeof(struct virtwl_info), GFP_KERNEL); ++ if (!vi) { ++ printk("virtwl: failed to alloc virtwl_info struct\n"); ++ return -ENOMEM; ++ } ++ ++ vdev->priv = vi; ++ ++ ret = alloc_chrdev_region(&vi->dev_num, 0, 1, "wl"); ++ if (ret) { ++ ret = -ENOMEM; ++ printk("virtwl: failed to allocate wl chrdev region: %d\n", ++ ret); ++ goto free_vi; ++ } ++ ++ vi->class = class_create(THIS_MODULE, "wl"); ++ if (IS_ERR(vi->class)) { ++ ret = PTR_ERR(vi->class); ++ printk("virtwl: failed to create wl class: %d\n", ret); ++ goto unregister_region; ++ ++ } ++ ++ vi->dev = device_create(vi->class, NULL, vi->dev_num, vi, "wl%d", 0); ++ if (IS_ERR(vi->dev)) { ++ ret = PTR_ERR(vi->dev); ++ printk("virtwl: failed to create wl0 device: %d\n", ret); ++ goto destroy_class; ++ } ++ ++ cdev_init(&vi->cdev, &virtwl_fops); ++ ret = cdev_add(&vi->cdev, vi->dev_num, 1); ++ if (ret) { ++ printk("virtwl: failed to add virtio wayland character device to system: %d\n", ++ ret); ++ goto destroy_device; ++ } ++ ++ for (i = 0; i < VIRTWL_QUEUE_COUNT; i++) ++ mutex_init(&vi->vq_locks[i]); ++ ++ ret = vdev->config->find_vqs(vdev, VIRTWL_QUEUE_COUNT, vi->vqs, ++ vq_callbacks, vq_names); ++ if (ret) { ++ printk("virtwl: failed to find virtio wayland queues: %d\n", ++ ret); ++ goto del_cdev; ++ } ++ ++ INIT_WORK(&vi->in_vq_work, vq_in_work_handler); ++ INIT_WORK(&vi->out_vq_work, vq_out_work_handler); ++ init_waitqueue_head(&vi->out_waitq); ++ ++ mutex_init(&vi->vfds_lock); ++ idr_init(&vi->vfds); ++ ++ /* lock is unneeded as we have unique ownership */ ++ ret = vq_fill_locked(vi->vqs[VIRTWL_VQ_IN]); ++ if (ret) { ++ printk("virtwl: failed to fill in virtqueue: %d", ret); ++ goto del_cdev; ++ } ++ ++ virtio_device_ready(vdev); ++ virtqueue_kick(vi->vqs[VIRTWL_VQ_IN]); ++ ++ ++ return 0; ++ ++del_cdev: ++ cdev_del(&vi->cdev); ++destroy_device: ++ put_device(vi->dev); ++destroy_class: ++ class_destroy(vi->class); ++unregister_region: ++ unregister_chrdev_region(vi->dev_num, 0); ++free_vi: ++ kfree(vi); ++ return ret; ++} ++ ++static void remove_common(struct virtio_device *vdev) ++{ ++ struct virtwl_info *vi = vdev->priv; ++ ++ cdev_del(&vi->cdev); ++ put_device(vi->dev); ++ class_destroy(vi->class); ++ unregister_chrdev_region(vi->dev_num, 0); ++ kfree(vi); ++} ++ ++static int virtwl_probe(struct virtio_device *vdev) ++{ ++ return probe_common(vdev); ++} ++ ++static void virtwl_remove(struct virtio_device *vdev) ++{ ++ remove_common(vdev); ++} ++ ++static void virtwl_scan(struct virtio_device *vdev) ++{ ++} ++ ++ ++static struct virtio_device_id id_table[] = { ++ { VIRTIO_ID_WL, VIRTIO_DEV_ANY_ID }, ++ { 0 }, ++}; ++ ++static struct virtio_driver virtio_wl_driver = { ++ .driver.name = KBUILD_MODNAME, ++ .driver.owner = THIS_MODULE, ++ .id_table = id_table, ++ .probe = virtwl_probe, ++ .remove = virtwl_remove, ++ .scan = virtwl_scan, ++}; ++ ++module_virtio_driver(virtio_wl_driver); ++MODULE_DEVICE_TABLE(virtio, id_table); ++MODULE_DESCRIPTION("Virtio wayland driver"); ++MODULE_LICENSE("GPL"); +diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild +index efa2a9c..6d1095b 100644 +--- a/include/uapi/linux/Kbuild ++++ b/include/uapi/linux/Kbuild +@@ -449,6 +449,8 @@ + header-y += virtio_scsi.h + header-y += virtio_types.h + header-y += virtio_vsock.h ++header-y += virtio_wl.h ++header-y += virtwl.h + header-y += vm_sockets.h + header-y += vt.h + header-y += vtpm_proxy.h +diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h +index 3228d58..68a2a8d 100644 +--- a/include/uapi/linux/virtio_ids.h ++++ b/include/uapi/linux/virtio_ids.h +@@ -42,5 +42,6 @@ + #define VIRTIO_ID_GPU 16 /* virtio GPU */ + #define VIRTIO_ID_INPUT 18 /* virtio input */ + #define VIRTIO_ID_VSOCK 19 /* virtio vsock transport */ ++#define VIRTIO_ID_WL 30 /* virtio wayland */ + + #endif /* _LINUX_VIRTIO_IDS_H */ +diff --git a/include/uapi/linux/virtio_wl.h b/include/uapi/linux/virtio_wl.h +new file mode 100644 +index 0000000..b3cdadc +--- /dev/null ++++ b/include/uapi/linux/virtio_wl.h +@@ -0,0 +1,86 @@ ++#ifndef _LINUX_VIRTIO_WL_H ++#define _LINUX_VIRTIO_WL_H ++/* This header is BSD licensed so anyone can use the definitions to implement ++ * compatible drivers/servers. */ ++#include ++#include ++#include ++ ++ ++#define VIRTWL_IN_BUFFER_SIZE 4096 ++#define VIRTWL_OUT_BUFFER_SIZE 4096 ++#define VIRTWL_VQ_IN 0 ++#define VIRTWL_VQ_OUT 1 ++#define VIRTWL_QUEUE_COUNT 2 ++#define VIRTWL_MAX_ALLOC 0x800 ++#define VIRTWL_PFN_SHIFT 12 ++ ++struct virtio_wl_config { ++ ++}; ++ ++/* ++ * The structure of each of these is virtio_wl_ctrl_hdr or one of its subclasses ++ * where noted. */ ++enum virtio_wl_ctrl_type { ++ VIRTIO_WL_CMD_VFD_NEW = 0x100, /* virtio_wl_ctrl_vfd_new */ ++ VIRTIO_WL_CMD_VFD_CLOSE, /* virtio_wl_ctrl_vfd */ ++ VIRTIO_WL_CMD_VFD_SEND, /* virtio_wl_ctrl_vfd_send + data */ ++ VIRTIO_WL_CMD_VFD_RECV, /* virtio_wl_ctrl_vfd_recv + data */ ++ VIRTIO_WL_CMD_VFD_NEW_CTX, /* virtio_wl_ctrl_vfd */ ++ ++ VIRTIO_WL_RESP_OK = 0x1000, ++ VIRTIO_WL_RESP_VFD_NEW = 0x1001, /* virtio_wl_ctrl_vfd_new */ ++ ++ VIRTIO_WL_RESP_ERR = 0x1100, ++ VIRTIO_WL_RESP_OUT_OF_MEMORY, ++ VIRTIO_WL_RESP_INVALID_ID, ++ VIRTIO_WL_RESP_INVALID_TYPE, ++}; ++ ++struct virtio_wl_ctrl_hdr { ++ __le32 type; /* one of virtio_wl_ctrl_type */ ++ __le32 flags; /* always 0 */ ++}; ++ ++enum virtio_wl_vfd_flags { ++ VIRTIO_WL_VFD_WRITE = 0x1, /* indicates if mapped area is writable */ ++ VIRTIO_WL_VFD_MAP = 0x2, /* indicates a fixed size and mapping into a pfn range */ ++ VIRTIO_WL_VFD_CONTROL = 0x4, /* indicates if send/recv can transmit VFDs */ ++}; ++ ++struct virtio_wl_ctrl_vfd { ++ struct virtio_wl_ctrl_hdr hdr; ++ __le32 vfd_id; ++}; ++ ++/* ++ * If this command is sent to the guest, it indicates that the VFD has been ++ * created and the fields indicate the properties of the VFD being offered. ++ * ++ * If this command is sent to the host, it represents a request to create a VFD ++ * of the given properties. The pfn field is ignored by the host. ++ */ ++struct virtio_wl_ctrl_vfd_new { ++ struct virtio_wl_ctrl_hdr hdr; ++ __le32 vfd_id; /* MSB indicates device allocated vfd */ ++ __le32 flags; /* virtio_wl_vfd_flags */ ++ __le64 pfn; /* first guest physical page frame number if VIRTIO_WL_VFD_MAP */ ++ __le32 size; /* size in bytes if VIRTIO_WL_VFD_MAP */ ++}; ++ ++struct virtio_wl_ctrl_vfd_send { ++ struct virtio_wl_ctrl_hdr hdr; ++ __le32 vfd_id; ++ __le32 vfd_count; /* struct is followed by this many IDs */ ++ /* the remainder is raw data */ ++}; ++ ++struct virtio_wl_ctrl_vfd_recv { ++ struct virtio_wl_ctrl_hdr hdr; ++ __le32 vfd_id; ++ __le32 vfd_count; /* struct is followed by this many IDs */ ++ /* the remainder is raw data */ ++}; ++ ++#endif /* _LINUX_VIRTIO_WL_H */ +diff --git a/include/uapi/linux/virtwl.h b/include/uapi/linux/virtwl.h +new file mode 100644 +index 0000000..7a43ce2 +--- /dev/null ++++ b/include/uapi/linux/virtwl.h +@@ -0,0 +1,45 @@ ++#ifndef _LINUX_VIRTWL_H ++#define _LINUX_VIRTWL_H ++ ++#include ++ ++#define VIRTWL_SEND_MAX_ALLOCS 16 ++ ++#define VIRTWL_IOCTL_BASE 'w' ++#define VIRTWL_IO(nr) _IO(VIRTWL_IOCTL_BASE,nr) ++#define VIRTWL_IOR(nr,type) _IOR(VIRTWL_IOCTL_BASE,nr,type) ++#define VIRTWL_IOW(nr,type) _IOW(VIRTWL_IOCTL_BASE,nr,type) ++#define VIRTWL_IOWR(nr,type) _IOWR(VIRTWL_IOCTL_BASE,nr,type) ++ ++enum virtwl_ioctl_new_type { ++ VIRTWL_IOCTL_NEW_CTX, // struct virtwl_ioctl_new ++ VIRTWL_IOCTL_NEW_ALLOC, // struct virtwl_ioctl_new_alloc ++}; ++ ++struct virtwl_ioctl_new { ++ uint32_t type; // always 0 ++ int fd; // return fd ++ uint32_t flags; // always 0 ++ size_t size; // only for VIRTWL_IOCTL_NEW_ALLOC ++}; ++ ++struct virtwl_ioctl_send { ++ int fds[VIRTWL_SEND_MAX_ALLOCS]; ++ uint32_t len; ++ uint8_t data[0]; ++}; ++ ++struct virtwl_ioctl_recv { ++ int fds[VIRTWL_SEND_MAX_ALLOCS]; ++ uint32_t len; ++ uint8_t data[0]; ++}; ++ ++#define VIRTWL_IOCTL_NEW VIRTWL_IOWR(0x00, struct virtwl_ioctl_new) ++#define VIRTWL_IOCTL_SEND VIRTWL_IOR(0x01, struct virtwl_ioctl_send) ++#define VIRTWL_IOCTL_RECV VIRTWL_IOW(0x02, struct virtwl_ioctl_recv) ++#define VIRTWL_IOCTL_MAXNR 3 ++ ++ ++#endif /* _LINUX_VIRTWL_H */ ++ diff --git a/kernel/v4.9.56.sha256 b/kernel/v4.9.56.sha256 new file mode 100644 index 0000000..f218ef5 --- /dev/null +++ b/kernel/v4.9.56.sha256 @@ -0,0 +1,2 @@ +66df3d3c25d3627da87b612db9f0158b652d949b5e8b27ac38c6c5957fa2bf72 downloads/linux-4.9.56.tar +5b0d7ac640b6c1f20524cf6042ad39bb5d8f2184ca5ef7a9cd309e5b8a5c9b60 downloads/v4.9.56-unofficial_grsec-20171013093040.diff diff --git a/src/devices/mod.rs b/src/devices/mod.rs new file mode 100644 index 0000000..4d3fd01 --- /dev/null +++ b/src/devices/mod.rs @@ -0,0 +1,9 @@ +pub mod serial; +pub mod rtc; +pub mod virtio_9p; +pub mod virtio_serial; +pub mod virtio_rng; + +pub use self::virtio_serial::VirtioSerial; +pub use self::virtio_9p::VirtioP9; +pub use self::virtio_rng::VirtioRandom; diff --git a/src/devices/rtc.rs b/src/devices/rtc.rs new file mode 100644 index 0000000..6aa57e0 --- /dev/null +++ b/src/devices/rtc.rs @@ -0,0 +1,116 @@ +use std::sync::{Arc,RwLock}; +use std::mem; +use libc; + +use vm::io::{IoDispatcher,IoPortOps}; + +const RTC_SECONDS: u8 = 0x00; +const RTC_MINUTES: u8 = 0x02; +const RTC_HOURS: u8 = 0x04; +const RTC_DAY_OF_WEEK: u8 = 0x06; +const RTC_DAY_OF_MONTH: u8 = 0x07; +const RTC_MONTH: u8 = 0x08; +const RTC_YEAR: u8 = 0x09; +const RTC_CENTURY: u8 = 0x32; + +const RTC_REG_C: u8 = 0x0C; +const RTC_REG_D: u8 = 0x0D; + +pub struct Rtc { + idx: u8, + data: [u8; 128] +} + +impl IoPortOps for Rtc { + fn io_in(&mut self, port: u16, _size: usize) -> u32 { + if port == 0x0071 { + self.data_in() as u32 + } else { + 0 + } + } + + fn io_out(&mut self, port: u16, _size: usize, val: u32) { + if port == 0x0070 { + self.index_out(val as u8); + } else if port == 0x0071 { + self.data_out(val as u8) + } + } +} + +impl Rtc { + pub fn register(io: Arc) { + let rtc = Arc::new(RwLock::new(Rtc::new())); + io.register_ioports(0x0070, 2, rtc); + } + + fn new() -> Rtc { + Rtc { + idx:0, + data: [0; 128] + } + } + + fn index_out(&mut self, data: u8) { + let _nmi_disable = data & 0x80; + self.idx = data & 0x7f; + } + + fn data_in(&mut self) -> u8 { + let now = RtcTime::now(); + match self.idx { + RTC_SECONDS => now.seconds, + RTC_MINUTES => now.minutes, + RTC_HOURS => now.hours, + RTC_DAY_OF_WEEK => now.wday, + RTC_DAY_OF_MONTH => now.mday, + RTC_MONTH => now.month, + RTC_YEAR => now.year, + RTC_CENTURY => now.century, + _ => { self.data[self.idx as usize]}, + } + } + + fn data_out(&mut self, data: u8) { + if self.idx == RTC_REG_C || self.idx == RTC_REG_D { + return; + } + self.data[self.idx as usize] = data; + } +} + +struct RtcTime { + seconds: u8, + minutes: u8, + hours: u8, + wday: u8, + mday: u8, + month: u8, + year: u8, + century: u8, +} + +impl RtcTime { + fn now() -> RtcTime { + fn bcd(val: i32) -> u8 { + (((val/10) << 4) + (val % 10)) as u8 + } + unsafe { + let mut tm: libc::tm = mem::zeroed(); + let mut time: libc::time_t = 0; + libc::time(&mut time as *mut _); + libc::gmtime_r(&time, &mut tm as *mut _); + RtcTime { + seconds: bcd(tm.tm_sec), + minutes: bcd(tm.tm_min), + hours: bcd(tm.tm_hour), + wday: bcd(tm.tm_wday + 1), + mday: bcd(tm.tm_mday), + month: bcd(tm.tm_mon + 1), + year: bcd(tm.tm_year % 100), + century: bcd(tm.tm_year / 100), + } + } + } +} diff --git a/src/devices/serial.rs b/src/devices/serial.rs new file mode 100644 index 0000000..989cc8b --- /dev/null +++ b/src/devices/serial.rs @@ -0,0 +1,313 @@ +use std::sync::{Arc, RwLock}; +use std::io::{self, Write}; + +use vm::io::{IoPortOps,IoDispatcher}; +use kvm::Kvm; + +const UART_TX: u16 = 0; +const UART_RX: u16 = 0; + +const UART_IER: u16 = 1; +const UART_IER_RDI: u8 = 0x01; +const UART_IER_THRI: u8 = 0x02; + +const UART_IIR: u16 = 2; +const UART_IIR_NO_INT: u8 = 0x01; +const UART_IIR_THRI: u8 = 0x02; +const UART_IIR_RDI: u8 = 0x04; +const UART_IIR_TYPE_BITS: u8 = 0xc0; + +const UART_FCR: u16 = 2; +const UART_FCR_CLEAR_RCVR: u8 = 0x02; +const UART_FCR_CLEAR_XMIT: u8 = 0x04; + +const UART_LCR: u16 = 3; +const UART_LCR_DLAB: u8 = 0x80; + +const UART_MCR: u16 = 4; +const UART_MCR_LOOP: u8 = 0x10; +const UART_MCR_OUT2: u8 = 0x08; + +const UART_LSR: u16 = 5; +const UART_LSR_TEMT: u8 = 0x40; +const UART_LSR_THRE: u8 = 0x20; +const UART_LSR_BI: u8 = 0x10; +const UART_LSR_DR: u8 = 0x01; + +const UART_MSR: u16 = 6; +const UART_MSR_DCD: u8 = 0x80; +const UART_MSR_DSR: u8 = 0x20; +const UART_MSR_CTS: u8 = 0x10; + +const UART_SCR: u16 = 7; + +const FIFO_LEN: usize = 64; + + + +trait Bits { + fn set(&mut self, flag: Self); + fn clear(&mut self, flag: Self); + fn is_set(&self, flag: Self) -> bool; +} + +impl Bits for u8 { + fn set(&mut self, flag: u8) { + *self |= flag; + } + + fn clear(&mut self, flag: u8) { + *self &= !flag; + } + + fn is_set(&self, flag: u8) -> bool { + *self & flag == flag + } +} + +pub struct SerialDevice { + iobase: u16, + kvm: Kvm, + irq: u8, + irq_state: u8, + txcnt: usize, + rxcnt: usize, + rxdone: usize, + txbuf: [u8; FIFO_LEN], + rxbuf: [u8; FIFO_LEN], + dll: u8, + dlm: u8, + iir: u8, + ier: u8, + fcr: u8, + lcr: u8, + mcr: u8, + lsr: u8, + msr: u8, + scr: u8, +} + +impl IoPortOps for SerialDevice { + fn io_in(&mut self, port: u16, _size: usize) -> u32 { + let off = port - self.iobase; + self.serial_in(off) as u32 + } + + fn io_out(&mut self, port: u16, _size: usize, val: u32) { + let off = port - self.iobase; + self.serial_out(off, val as u8); + } +} + +impl SerialDevice { + fn flush_tx(&mut self) { + self.lsr.set(UART_LSR_TEMT | UART_LSR_THRE); + if self.txcnt > 0 { + io::stdout().write(&self.txbuf[..self.txcnt]).unwrap(); + self.txcnt = 0; + } + } + + fn update_irq(&mut self) { + let mut iir = 0u8; + if self.lcr.is_set(UART_FCR_CLEAR_RCVR) { + self.lcr.clear(UART_FCR_CLEAR_RCVR); + self.rxcnt = 0; + self.rxdone = 0; + self.lsr.clear(UART_LSR_DR); + } + + if self.lcr.is_set(UART_FCR_CLEAR_XMIT) { + self.lcr.clear(UART_FCR_CLEAR_XMIT); + self.txcnt = 0; + self.lsr.set(UART_LSR_TEMT|UART_LSR_THRE); + } + + if self.ier.is_set(UART_IER_RDI) && self.lsr.is_set(UART_LSR_DR) { + iir |= UART_IIR_RDI; + } + + if self.ier.is_set(UART_IER_THRI) && self.lsr.is_set(UART_LSR_TEMT) { + iir |= UART_IIR_THRI; + } + + if iir == 0 { + self.iir = UART_IIR_NO_INT; + if self.irq_state != 0 { + self.kvm.irq_line(self.irq as u32, 0).unwrap(); + } + } else { + self.iir = iir; + if self.irq_state == 0 { + self.kvm.irq_line(self.irq as u32, 1).unwrap(); + } + } + self.irq_state = iir; + + if !self.ier.is_set(UART_IER_THRI) { + self.flush_tx(); + } + } + + fn tx(&mut self, data: u8) { + if self.lcr.is_set(UART_LCR_DLAB) { + self.dll = data; + return; + } + + if self.mcr.is_set(UART_MCR_LOOP) { + if self.rxcnt < FIFO_LEN { + self.rxbuf[self.rxcnt] = data; + self.rxcnt += 1; + self.lsr.set(UART_LSR_DR); + } + return; + } + + if self.txcnt < FIFO_LEN { + self.txbuf[self.txcnt] = data; + self.txcnt += 1; + self.lsr.clear(UART_LSR_TEMT); + if self.txcnt == FIFO_LEN / 2 { + self.lsr.clear(UART_LSR_THRE); + } + self.flush_tx(); + } else { + self.lsr.clear(UART_LSR_TEMT | UART_LSR_THRE); + } + } + + fn serial_out(&mut self, port: u16, data: u8) { + match port { + UART_TX => { + self.tx(data); + }, + UART_IER => { + if self.lcr.is_set(UART_LCR_DLAB) { + self.ier = data & 0x0f; + } else { + self.dlm = data; + } + }, + UART_FCR => { + self.fcr = data; + }, + UART_LCR => { + self.lcr = data; + }, + UART_MCR => { + self.mcr = data; + }, + UART_LSR => {}, + UART_MSR => {}, + UART_SCR => { + self.scr = data; + }, + _ => {} + } + self.update_irq(); + } + + fn serial_in(&mut self, port: u16) -> u8 { + let mut data = 0u8; + match port { + UART_RX => { + if self.lcr.is_set(UART_LCR_DLAB) { + data = self.dll; + } else { + self.rx(&mut data); + } + }, + UART_IER => { + if self.lcr.is_set(UART_LCR_DLAB) { + data = self.dlm; + } else { + data = self.ier; + } + }, + UART_IIR => { + data = self.iir & UART_IIR_TYPE_BITS; + }, + UART_LCR => { + data = self.lcr; + }, + UART_MCR => { + data = self.mcr; + }, + UART_LSR => { + data = self.lsr; + }, + UART_MSR => { + data = self.msr; + }, + UART_SCR => { + data = self.scr; + }, + _ => {}, + } + self.update_irq(); + data + } + + + fn rx(&mut self, data: &mut u8) { + if self.rxdone == self.rxcnt { + return; + } + + if self.lsr.is_set(UART_LSR_BI) { + self.lsr.clear(UART_LSR_BI); + *data = 0; + return; + } + + *data = self.rxbuf[self.rxdone]; + self.rxdone += 1; + if self.rxdone == self.rxcnt { + self.lsr.clear(UART_LSR_DR); + self.rxdone = 0; + self.rxcnt = 0; + } + } + + pub fn register(kvm: Kvm, io: Arc, id: u8) { + if let Some((base,irq)) = SerialDevice::base_irq_for_id(id) { + let dev = SerialDevice::new(kvm, base, irq); + io.register_ioports(base, 8, Arc::new(RwLock::new(dev))); + } + } + + fn base_irq_for_id(id: u8) -> Option<(u16, u8)> { + match id { + 0 => Some((0x3f8, 4)), + 1 => Some((0x2f8, 3)), + 2 => Some((0x3e8, 4)), + 3 => Some((0x2e8, 3)), + _ => None, + } + } + + fn new(kvm: Kvm, iobase: u16, irq: u8) -> SerialDevice { + SerialDevice { + iobase, + kvm, + irq, + irq_state: 0, + txcnt: 0, + rxcnt: 0, + rxdone:0, + txbuf: [0; FIFO_LEN], + rxbuf: [0; FIFO_LEN], + dll: 0, + dlm: 0, + iir: UART_IIR_NO_INT, + ier: 0, + fcr: 0, + lcr: 0, + mcr: UART_MCR_OUT2, + lsr: UART_LSR_TEMT | UART_LSR_THRE, + msr: UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS, + scr: 0, + } + } +} diff --git a/src/devices/virtio_9p/commands.rs b/src/devices/virtio_9p/commands.rs new file mode 100644 index 0000000..c20c26e --- /dev/null +++ b/src/devices/virtio_9p/commands.rs @@ -0,0 +1,404 @@ + +use std::path::PathBuf; +use std::io; +use std::path::Path; +use std::fs; + +use libc; + +use memory::GuestRam; +use super::pdu::{PduParser,P9Attr}; +use super::fid::FidCache; +use super::filesystem::{FileSystem,FsTouch,FileSystemOps}; + + + +const P9_TSTATFS: u8 = 8; +const P9_TLOPEN: u8 = 12; +const P9_TLCREATE: u8 = 14; +const P9_TSYMLINK: u8 = 16; +//const P9_TMKNOD: u8 = 18; +//const P9_TRENAME: u8 = 20; +const P9_TREADLINK: u8 = 22; +const P9_TGETATTR: u8 = 24; +const P9_TSETATTR: u8 = 26; +const P9_TXATTRWALK: u8 = 30; +const P9_TXATTRCREATE: u8 = 32; +const P9_TREADDIR: u8 = 40; +const P9_TFSYNC: u8 = 50; +const P9_TLOCK: u8 = 52; +const P9_TGETLOCK: u8 = 54; +//const P9_TLINK: u8 = 70; +//const P9_TMKDIR: u8 = 72; +//const P9_TRENAMEAT: u8 = 74; +//const P9_TUNLINKAT: u8 = 76; +const P9_TVERSION:u8 = 100; +const P9_TATTACH :u8 = 104; +//const P9_TFLUSH: u8 = 108; +const P9_TWALK :u8 = 110; +const P9_TREAD: u8 = 116; +//const P9_TWRITE: u8 = 118; +const P9_TCLUNK: u8 = 120; +//const P9_REMOVE: u8 = 122; + +const P9_LOCK_SUCCESS:u32 = 0; +const F_UNLCK: u8 = 2; +const P9_VERSION_DOTL:&str = "9P2000.L"; + +pub struct Commands { + filesystem: FileSystem, + fids: FidCache, + root_dir: PathBuf, + _memory: GuestRam, +} + +impl Commands { + pub fn new(root_dir: PathBuf, init_path: PathBuf, memory: GuestRam) -> Commands { + let fsys = FileSystem::new(root_dir.clone(), init_path,true); + Commands { + filesystem: fsys.clone(), + fids: FidCache::new(fsys.clone()), + root_dir, _memory: memory, + } + } + + fn handle_io_result(&self, cmd: u8, result: io::Result<()>) { + match result { + Ok(()) => (), + Err(e) => println!("io error in 9p command {} processing: {:?}",cmd, e), + } + } + + pub fn handle(&mut self, pp: &mut PduParser) { + match pp.command() { + Ok(cmd) => { + let res = self.dispatch(cmd, pp); + self.handle_io_result(cmd,res); + }, + Err(e) => self.handle_io_result(0,Err(e)), + } + } + + fn dispatch(&mut self, cmd: u8, pp: &mut PduParser) -> io::Result<()> { + match cmd { + P9_TSTATFS => self.p9_statfs(pp)?, + P9_TLOPEN => self.p9_open(pp)?, + P9_TLCREATE => self.p9_create(pp)?, + P9_TSYMLINK => self.p9_symlink(pp)?, + //P9_TMKNOD => self.p9_mknod(pp)?, + //P9_TRENAME => self.p9_rename(pp)?, + P9_TREADLINK => self.p9_readlink(pp)?, + P9_TGETATTR => self.p9_getattr(pp)?, + P9_TSETATTR => self.p9_setattr(pp)?, + P9_TXATTRWALK => self.p9_unsupported(pp)?, + P9_TXATTRCREATE => self.p9_unsupported(pp)?, + P9_TREADDIR => self.p9_readdir(pp)?, + P9_TFSYNC => self.p9_fsync(pp)?, + P9_TLOCK => self.p9_lock(pp)?, + P9_TGETLOCK => self.p9_getlock(pp)?, + //P9_TLINK => self.p9_link(pp)?, + //P9_TMKDIR=> self.p9_mkdir(pp)?, + //P9_TRENAMEAT => self.p9_renameat(pp)?, + //P9_UNLINKAT => self.p9_unlinkat(pp)?, + P9_TVERSION => self.p9_version(pp)?, + P9_TATTACH => self.p9_attach(pp)?, + //P9_FLUSH => self.p9_flush(pp)?, + P9_TWALK => self.p9_walk(pp)?, + P9_TREAD => self.p9_read(pp)?, + //P9_WRITE => self.p9_write(pp)?, + P9_TCLUNK => self.p9_clunk(pp)?, + //P9_REMOVE => self.p9_remove(pp)?, + n => println!("unhandled 9p command: {}", n), + } + Ok(()) + } + + fn p9_unsupported(&self, pp: &mut PduParser) -> io::Result<()> { + pp.read_done()?; + pp.bail_err(io::Error::from_raw_os_error(libc::EOPNOTSUPP)) + + } + + fn p9_statfs(&mut self, pp: &mut PduParser) -> io::Result<()> { + let fid = pp.r32()?; + pp.read_done()?; + match self.fids.statfs(fid) { + Ok(statfs) => { + pp.write_statfs(statfs)?; + pp.write_done() + }, + Err(err) => pp.bail_err(err), + } + } + + fn p9_version(&self, pp: &mut PduParser) -> io::Result<()> { + let msize = pp.r32()?; + let version = pp.read_string()?; + pp.read_done()?; + + pp.w32(msize)?; + if version == P9_VERSION_DOTL { + pp.write_string(&version)?; + } else { + pp.write_string("unknown")?; + } + + pp.write_done() + } + + fn p9_attach(&mut self, pp: &mut PduParser) -> io::Result<()> { + let fid_val = pp.r32()?; + let _afid = pp.r32()?; + let _uname = pp.read_string()?; + let _aname = pp.read_string()?; + let uid = pp.r32()?; + pp.read_done()?; + + self.fids.with_fid_mut(fid_val, |fid| { + fid.uid = uid; + fid.path.push("/"); + }); + + match fs::metadata(&self.root_dir) { + Ok(ref meta) => { + pp.write_qid(meta)?; + pp.write_done() + } + Err(e) => pp.bail_err(e), + } + } + + fn p9_open(&mut self, pp: &mut PduParser) -> io::Result<()> { + let fid = pp.r32()?; + let flags = pp.r32()?; + pp.read_done()?; + + if let Err(err) = self.fids.open(fid, flags) { + return pp.bail_err(err); + } + + let meta = match self.fids.metadata(fid) { + Ok(meta) => meta, + Err(err) => { + return pp.bail_err(err); + } + }; + + pp.write_qid(&meta)?; + // XXX iounit goes here + pp.w32(0)?; + pp.write_done() + } + + fn p9_create(&mut self, pp: &mut PduParser) -> io::Result<()> { + let dfid = pp.r32()?; + let name = pp.read_string()?; + let flags = pp.r32()?; + let mode = pp.r32()?; + let gid = pp.r32()?; + pp.read_done()?; + + match self.fids.create(dfid, name, flags, mode, gid) { + Ok(meta) => { + pp.write_statl(&meta)?; + pp.write_done()?; + + }, + Err(err) => return pp.bail_err(err), + } + Ok(()) + } + + fn p9_symlink(&mut self, pp: &mut PduParser) -> io::Result<()> { + let _fid = pp.r32()?; + let _name = pp.read_string()?; + let _old_path = pp.read_string()?; + let _gid = pp.r32()?; + pp.read_done()?; + // XXX + pp.write_done() + } + + fn p9_read(&mut self, pp: &mut PduParser) -> io::Result<()> { + let id = pp.r32()?; + let off = pp.r64()?; + let cnt = pp.r32()?; + pp.read_done()?; + + // space for size field + pp.w32(0)?; + + match self.fids.fid_mut(id).read(off, cnt as usize, pp) { + Ok(nread) => { + // write nread in space reserved earlier + pp.w32_at(0, nread as u32); + pp.write_done()?; + } + Err(err) => { + println!("oops error on read: {:?}", err); + return pp.bail_err(err) + }, + }; + Ok(()) + } + + fn p9_readdir(&mut self, pp: &mut PduParser) -> io::Result<()> { + let id = pp.r32()?; + let off = pp.r64()?; + let cnt = pp.r32()?; + pp.read_done()?; + + self.fids.readdir(id,off, cnt as usize, pp) + } + + fn p9_clunk(&mut self, pp: &mut PduParser) -> io::Result<()> { + let id = pp.r32()?; + pp.read_done()?; + self.fids.clunk(id); + pp.write_done() + } + + fn p9_readlink(&mut self, pp: &mut PduParser) -> io::Result<()> { + let id = pp.r32()?; + pp.read_done()?; + let link = self.fids.readlink(id)?; + pp.write_os_string(&link)?; + pp.write_done() + } + + fn p9_getattr(&mut self, pp: &mut PduParser) -> io::Result<()> { + let id = pp.r32()?; + let _mask = pp.r64()?; + pp.read_done()?; + + let meta = match self.fids.metadata(id) { + Ok(meta) => meta, + Err(e) => return pp.bail_err(e), + }; + + pp.write_statl(&meta)?; + pp.write_done() + } + + fn do_setattr(&mut self, fid: u32, attr: P9Attr) -> io::Result<()> { + if attr.has_mode() { + self.fids.chmod(fid, attr.mode())? + } + if attr.has_atime() { + if attr.has_atime_set() { + self.fids.touch(fid, FsTouch::Atime,attr.atime())? + } else { + self.fids.touch(fid, FsTouch::AtimeNow,(0,0))? + } + } + + if attr.has_mtime() { + if attr.has_mtime_set() { + self.fids.touch(fid, FsTouch::Mtime,attr.mtime())? + } else { + self.fids.touch(fid, FsTouch::MtimeNow,(0,0))? + } + } + + if attr.has_chown() { + let (uid, gid) = attr.chown_ids(); + self.fids.chown(fid, uid, gid)?; + } + + if attr.has_size() { + self.fids.truncate(fid, attr.size())?; + } + + Ok(()) + } + + fn p9_setattr(&mut self, pp: &mut PduParser) -> io::Result<()> { + let fid = pp.r32()?; + let attr = pp.read_attr()?; + pp.read_done()?; + + if let Err(err) = self.do_setattr(fid, attr) { + return pp.bail_err(err) + } + + pp.write_done() + } + + // XXX look at walk in qemu + fn p9_walk(&mut self, pp: &mut PduParser) -> io::Result<()> { + let fid_id = pp.r32()?; + let new_fid_id = pp.r32()?; + let nwname = pp.r16()?; + + self.fids.dup_fid(fid_id, new_fid_id); + + let mut cur = self.fids.fid(new_fid_id).path.clone(); + let mut metalist = Vec::new(); + for _ in 0..nwname { + let s = pp.read_string()?; + let p = Path::new(&s); + if p.components().count() != 1 { + println!("uh..."); + } + cur.push(p); + match self.filesystem.stat(&cur) { + Ok(m) => metalist.push(m), + Err(e) => { + pp.read_done()?; + return pp.bail_err(e) + }, + } + } + self.fids.with_fid_mut(new_fid_id, |fid| { + fid.path = cur; + }); + + pp.read_done()?; + pp.w16(metalist.len() as u16)?; + for meta in metalist { + pp.write_qid(&meta)?; + } + pp.write_done() + } + + fn p9_fsync(&mut self, pp: &mut PduParser) -> io::Result<()> { + let fid = pp.r32()?; + let dsync = pp.r32()?; + pp.read_done()?; + if let Err(err) = self.fids.fsync(fid, dsync != 0) { + return pp.bail_err(err); + } + pp.write_done() + } + + fn p9_lock(&mut self, pp: &mut PduParser) -> io::Result<()> { + let _ = pp.r32()?; + let _ = pp.r8()?; + let _ = pp.r32()?; + let _ = pp.r64()?; + let _ = pp.r64()?; + let _ = pp.r32()?; + let _ = pp.read_string()?; + pp.read_done()?; + + pp.w32(P9_LOCK_SUCCESS)?; + pp.write_done() + } + + fn p9_getlock(&mut self, pp: &mut PduParser) -> io::Result<()> { + let _fid = pp.r32()?; + let _type = pp.r8()?; + let glock_start = pp.r64()?; + let glock_len = pp.r64()?; + let glock_proc_id = pp.r32()?; + let glock_client_id = pp.read_string()?; + pp.read_done()?; + + pp.w8(F_UNLCK)?; + pp.w64(glock_start)?; + pp.w64(glock_len)?; + pp.w32(glock_proc_id)?; + pp.write_string(&glock_client_id)?; + pp.write_done() + } +} diff --git a/src/devices/virtio_9p/fid.rs b/src/devices/virtio_9p/fid.rs new file mode 100644 index 0000000..b63493a --- /dev/null +++ b/src/devices/virtio_9p/fid.rs @@ -0,0 +1,230 @@ +use std::fs::Metadata; +use std::collections::HashMap; +use std::path::PathBuf; +use std::io::{self, Seek,Write}; +use std::os::unix::io::AsRawFd; +use std::ffi::OsString; + +use libc; +use super::pdu::PduParser; +use super::readdir::DirEntry; +use super::filesystem::{FileSystem,FileDescriptor,StatFs,FileSystemOps,FsTouch}; + +pub struct FidCache { + filesystem: FileSystem, + fidmap: HashMap, +} + +impl FidCache { + pub fn new(filesystem: FileSystem) -> FidCache { + FidCache { + filesystem, + fidmap: HashMap::new(), + } + } + + fn add_if_absent(&mut self, id: u32) { + if !self.fidmap.contains_key(&id) { + self.fidmap.insert(id, Fid::new()); + } + } + + pub fn fid(&mut self, id: u32) -> &Fid { + self.add_if_absent(id); + self.fidmap.get(&id).expect("fidmap does not have element") + } + + pub fn _fid(&self, id: u32) -> &Fid { + self.fidmap.get(&id).expect("fidmap does not have element") + } + + pub fn fid_mut(&mut self, id: u32) -> &mut Fid { + self.add_if_absent(id); + self.fidmap.get_mut(&id).expect("fidmap does not have element") + } + + pub fn with_fid_mut(&mut self, id: u32, f: F) -> U + where F: FnOnce(&mut Fid) -> U { + self.add_if_absent(id); + f(self.fid_mut(id)) + } + + #[allow(dead_code)] + pub fn with_fid(&mut self, id: u32, f: F) -> U + where F: FnOnce(&Fid) -> U { + self.add_if_absent(id); + f(self.fid(id)) + } + + pub fn dup_fid(&mut self, old_id: u32, new_id: u32) { + self.fid_mut(new_id).path = self.fid(old_id).path.clone(); + self.fid_mut(new_id).uid = self.fid(old_id).uid; + } + + pub fn clunk(&mut self, id: u32) { + match self.fidmap.remove(&id) { + Some(ref mut fid) => fid.close(), + None => (), + } + } + + pub fn open(&mut self, id: u32, flags: u32) -> io::Result<()> { + let path = self.fid(id).path.clone(); + let fd = self.filesystem.open(&path, flags)?; + self.fid_mut(id).desc = fd; + Ok(()) + } + + fn fid_dir_join(&mut self, id: u32, name: &str) -> io::Result { + let meta = self.metadata(id)?; + if !meta.is_dir() { + return Err(io::Error::from_raw_os_error(libc::EBADF)); + } + + let fname = PathBuf::from(name); + if fname.is_absolute() || fname.components().count() != 1 { + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + } + let mut path = self.fid(id).path.clone(); + path.push(fname); + Ok(path) + } + + pub fn create(&mut self, id: u32, name: String, flags: u32, mode: u32, gid: u32) -> io::Result { + let path = self.fid_dir_join(id,&name)?; + + self.filesystem.create(&path, flags, mode)?; + + let uid = self.fid(id).uid; + self.filesystem.chown(&path, uid, gid)?; + + self.filesystem.stat(&path) + } + + pub fn readlink(&mut self, id: u32) -> io::Result { + let path = self.fid(id).path.clone(); + self.filesystem.readlink(&path) + } + + pub fn metadata(&mut self, id: u32) -> io::Result { + let path = self.fid(id).path.clone(); + self.filesystem.stat(&path) + } + + pub fn readdir(&mut self, id: u32, off: u64, len: usize, pp: &mut PduParser) -> io::Result<()> { + //let is_dir = self.fid(id).desc.is_dir(); + if off != 0 { + //self.fid_mut(id).desc.borrow_dir().unwrap().seek(off as i64); + } + self.fid_mut(id).readdir(len, pp) + } + + pub fn chmod(&mut self, id: u32, mode: u32) -> io::Result<()> { + let path = self.fid(id).path.clone(); + self.filesystem.chmod(&path, mode) + } + + pub fn chown(&mut self, id: u32, uid: u32, gid: u32) -> io::Result<()> { + let path = self.fid(id).path.clone(); + self.filesystem.chown(&path, uid, gid) + } + + pub fn touch(&mut self, id: u32, which: FsTouch, tv: (u64,u64)) -> io::Result<()> { + let path = self.fid(id).path.clone(); + self.filesystem.touch(&path, which, tv) + } + + pub fn truncate(&mut self, _id: u32, _size: u64) -> io::Result<()> { + Ok(()) + } + + pub fn statfs(&mut self, fid: u32) -> io::Result { + let path = self.fid(fid).path.clone(); + self.filesystem.statfs(&path) + } + + pub fn fsync(&mut self, fid: u32, datasync: bool) -> io::Result<()> { + match self.fid(fid).desc { + FileDescriptor::File(ref file) => { + let fd = file.as_raw_fd(); + unsafe { + let res = if datasync { + libc::fdatasync(fd) + } else { + libc::fsync(fd) + }; + if res < 0 { + return Err(io::Error::last_os_error()); + } + } + }, + FileDescriptor::Dir(ref dir) => { return dir.fsync(); }, + FileDescriptor::None => { return Err(io::Error::from_raw_os_error(libc::EBADF))}, + }; + Ok(()) + + } +} + +pub struct Fid { + pub uid: u32, + pub path: PathBuf, + desc: FileDescriptor, +} + +impl Fid { + fn new() -> Fid { + Fid { + uid: 0, path: PathBuf::new(), desc: FileDescriptor::None, + } + } + + pub fn read(&mut self, offset: u64, len: usize, pp: &mut PduParser) -> io::Result<(usize)> { + self.desc.borrow_file()?.seek(io::SeekFrom::Start(offset))?; + pp.chain.copy_from_reader(self.desc.borrow_file()?, len) + } + + fn dirent_len(dent: &DirEntry) -> usize { + // qid + offset + type + strlen + str + return 13 + 8 + 1 + 2 + dent.name_bytes().len() + } + + fn write_dirent(dent: &DirEntry, pp: &mut PduParser) -> io::Result<()> { + pp.write_qid_path_only(dent.ino())?; + pp.w64(dent.offset())?; + pp.w8(dent.file_type())?; + pp.w16(dent.name_bytes().len() as u16)?; + pp.chain.write(&dent.name_bytes())?; + Ok(()) + } + + pub fn readdir(&mut self, len: usize, pp: &mut PduParser) -> io::Result<()> { + let mut write_len = 0_usize; + pp.w32(0)?; + + + while let Some(entry) = self.desc.borrow_dir()?.next() { + match entry { + Ok(ref dent) => { + let dlen = Fid::dirent_len(dent); + if write_len + dlen > len { + self.desc.borrow_dir()?.restore_last_pos(); + break; + } + write_len += dlen; + Fid::write_dirent(dent, pp)?; + } + Err(err) => return pp.bail_err(err), + } + } + + + pp.w32_at(0, write_len as u32); + pp.write_done() + } + + pub fn close(&mut self) { + self.desc = FileDescriptor::None; + } +} + diff --git a/src/devices/virtio_9p/filesystem.rs b/src/devices/virtio_9p/filesystem.rs new file mode 100644 index 0000000..103c60c --- /dev/null +++ b/src/devices/virtio_9p/filesystem.rs @@ -0,0 +1,412 @@ +use std::mem; +use std::ffi::CString; +use std::ffi::OsString; +use std::os::unix::ffi::OsStrExt; +use std::fs::{self,File,Metadata,OpenOptions}; + +use std::io; +use std::path::{PathBuf,Path,Component}; + +use std::os::unix::fs::OpenOptionsExt; + +use libc; + +use super::readdir::ReadDir; + +const MAX_SYMLINKS: usize = 16; +const PATH_MAX: usize = 1024; // it's actually 4096 on linux + +const O_RDONLY: u32 = 0; +const O_WRONLY: u32 = 1; +const O_RDWR: u32 = 2; +const O_ACCMODE: u32 = 0x3; +const ALLOWED_FLAGS: u32 = (libc::O_APPEND | libc::O_TRUNC | libc::O_LARGEFILE + | libc::O_DIRECTORY | libc::O_DSYNC | libc::O_NOFOLLOW + | libc::O_SYNC) as u32; + +#[derive(Default)] +pub struct StatFs { + pub f_type: u32, + pub f_bsize: u32, + pub f_blocks: u64, + pub f_bfree: u64, + pub f_bavail: u64, + pub f_files: u64, + pub f_ffree: u64, + pub fsid: u64, + pub f_namelen: u32, +} +impl StatFs { + fn new() -> StatFs { + StatFs { ..Default::default() } + } +} + +pub enum FsTouch { + Atime, + AtimeNow, + Mtime, + MtimeNow, +} + +pub trait FileSystemOps { + fn open(&self, path: &Path, flags: u32) -> io::Result; + fn open_dir(&self, path: &Path) -> io::Result; + fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result; + fn stat(&self, path: &Path) -> io::Result; + fn statfs(&self, path: &Path) -> io::Result; + fn chown(&self, path: &Path, uid: u32, gid: u32) -> io::Result<()>; + fn chmod(&self, path: &Path, mode: u32) -> io::Result<()>; + fn touch(&self, path: &Path, which: FsTouch, tv: (u64, u64)) -> io::Result<()>; + fn truncate(&self, path: &Path, size: u64) -> io::Result<()>; + fn readlink(&self, path: &Path) -> io::Result; + // fn symlink(&self, target: &Path, linkpath: &Path) -> io::Result<()>; +} + +#[derive(Clone)] +pub struct FileSystem { + init_path: PathBuf, + resolver: PathResolver, + readonly: bool, +} + +pub enum FileDescriptor { + None, + Dir(ReadDir), + File(File), +} + +impl FileDescriptor { + #[allow(dead_code)] + pub fn is_file(&self) -> bool { + match *self { + FileDescriptor::File(..) => true, + _ => false, + } + } + + #[allow(dead_code)] + pub fn is_dir(&self) -> bool { + match *self { + FileDescriptor::Dir(..) => true, + _ => false, + } + } + + pub fn borrow_file(&mut self) -> io::Result<&mut File> { + match *self { + FileDescriptor::File(ref mut file_ref) => Ok(file_ref), + _ => Err(os_err(libc::EBADF)), + } + } + + pub fn borrow_dir(&mut self) -> io::Result<&mut ReadDir> { + match *self { + FileDescriptor::Dir(ref mut dir_ref) => Ok(dir_ref), + _ => Err(os_err(libc::EBADF)), + } + } +} + +impl FileSystem { + pub fn new(root: PathBuf, init_path: PathBuf, readonly: bool) -> FileSystem { + FileSystem { resolver: PathResolver::new(root), init_path, readonly } + } + + fn fullpath(&self, path: &Path) -> io::Result { + if path.to_str().unwrap() == "/phinit" { + return Ok(self.init_path.clone()) + } + self.resolver.fullpath(path) + } + + + fn flags_to_open_options(&self, flags: u32) -> io::Result { + let acc = flags & O_ACCMODE; + let mut oo = OpenOptions::new(); + + if self.readonly && acc != O_RDONLY { + return Err(io::Error::from_raw_os_error(libc::EACCES)); + } + + match acc { + O_RDONLY => { oo.read(true).write(false); } + O_WRONLY => { oo.read(false).write(true); } + O_RDWR => { oo.read(true).write(true); } + _ => return Err(os_err(libc::EINVAL)) + } + + + // There should never be a symlink in path but add O_NOFOLLOW anyways + let custom = libc::O_NOFOLLOW | (flags & ALLOWED_FLAGS) as i32; + oo.custom_flags(custom); + Ok(oo) + } +} + +/// +/// Resolves paths into a canonical path which is always no higher +/// than the `root` path. +#[derive(Clone)] +struct PathResolver { + root: PathBuf, +} + +impl PathResolver { + fn new(root: PathBuf) -> PathResolver { + // root must be absolute path + PathResolver{ root } + } + + + /// + /// Canonicalize `path` so that .. segments in both in + /// the path itself and any symlinks in the path do + /// not escape. The returned path will not contain any + /// symlinks and refers to a path which is a subdirectory + /// of `self.root` + fn resolve_path(&self, path: &Path) -> io::Result { + let mut buf = PathBuf::from(path); + let mut nlinks = 0_usize; + while self._resolve(&mut buf)? { + nlinks += 1; + if nlinks > MAX_SYMLINKS { + return Err(io::Error::from_raw_os_error(libc::ELOOP)) + } + if buf.as_os_str().len() > PATH_MAX { + return Err(io::Error::from_raw_os_error(libc::ENAMETOOLONG)) + } + } + Ok(buf) + } + + fn is_path_symlink(path: &Path) -> bool { + match path.symlink_metadata() { + Ok(meta) => meta.file_type().is_symlink(), + Err(..) => false + } + } + + fn fullpath(&self, path: &Path) -> io::Result { + let resolved = self.resolve_path(path)?; + Ok(self.realpath(&resolved)) + } + + fn realpath(&self, path: &Path) -> PathBuf { + let mut cs = path.components(); + if path.is_absolute() { + cs.next(); + } + self.root.join(cs.as_path()) + } + + fn resolve_symlink(&self, path: &mut PathBuf) -> io::Result { + let realpath = self.realpath(path); + if PathResolver::is_path_symlink(&realpath) { + path.pop(); + path.push(realpath.read_link()?); + return Ok(true) + } + Ok(false) + } + + fn resolve_component(&self, c: Component, pathbuf: &mut PathBuf) -> io::Result { + match c { + Component::RootDir => pathbuf.push("/"), + Component::CurDir | Component::Prefix(..) => (), + Component::ParentDir => { pathbuf.pop(); }, + Component::Normal(name) => { + pathbuf.push(name); + let link = self.resolve_symlink(pathbuf)?; + return Ok(link) + } + }; + Ok(false) + } + + fn _resolve(&self, path: &mut PathBuf) -> io::Result { + let copy = (*path).clone(); + let mut components = copy.components(); + + path.push("/"); + + while let Some(c) = components.next() { + if self.resolve_component(c, path)? { + let tmp = path.join(components.as_path()); + path.push(tmp); + return Ok(true) + } + } + Ok(false) + } +} + +fn cstr(path: &Path) -> io::Result { + Ok(CString::new(path.as_os_str().as_bytes())?) +} + +impl FileSystemOps for FileSystem { + fn open(&self, path: &Path, flags: u32) -> io::Result { + let fullpath = self.fullpath(path)?; + let meta = fullpath.metadata()?; + if meta.is_dir() { + let read_dir = ReadDir::open(&fullpath)?; + return Ok(FileDescriptor::Dir(read_dir)) + } + + let options = self.flags_to_open_options(flags)?; + let file = options.open(&fullpath)?; + return Ok(FileDescriptor::File(file)) + } + + fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result { + let fullpath = self.fullpath(path)?; + let mut options = self.flags_to_open_options(flags)?; + options.create(true); + options.mode(mode & 0o777); + let file = options.open(&fullpath)?; + return Ok(FileDescriptor::File(file)) + } + + fn open_dir(&self, path: &Path) -> io::Result { + let fullpath = self.fullpath(path)?; + let read_dir = ReadDir::open(&fullpath)?; + return Ok(FileDescriptor::Dir(read_dir)) + } + + fn stat(&self, path: &Path) -> io::Result { + let fullpath = self.fullpath(path)?; + let meta = fullpath.metadata()?; + Ok(meta) + } + + fn statfs(&self, path: &Path) -> io::Result { + let fullpath = self.fullpath(path)?; + let path_cstr = cstr(&fullpath)?; + let mut stat: LibcStatFs; + unsafe { + stat = mem::zeroed(); + let ret = statfs(path_cstr.as_ptr(), &mut stat); + if ret < 0 { + return Err(io::Error::last_os_error()); + } + } + let mut statfs = StatFs::new(); + statfs.f_type = stat.f_type as u32; + statfs.f_bsize = stat.f_bsize as u32; + statfs.f_blocks = stat.f_blocks; + statfs.f_bfree = stat.f_bfree; + statfs.f_bavail = stat.f_bavail; + statfs.f_files = stat.f_files; + statfs.f_ffree = stat.f_ffree; + statfs.f_namelen = stat.f_namelen as u32; + statfs.fsid = stat.f_fsid.val[0] as u64 | ((stat.f_fsid.val[1] as u64) << 32); + + Ok(statfs) + + } + fn chown(&self, path: &Path, uid: u32, gid: u32) -> io::Result<()> { + let fullpath = self.fullpath(path)?; + let path_cstr = cstr(&fullpath)?; + unsafe { + if libc::chown(path_cstr.as_ptr(), uid, gid) < 0 { + return Err(io::Error::last_os_error()); + } + Ok(()) + } + } + + fn chmod(&self, path: &Path, mode: u32) -> io::Result<()> { + // XXX see std::os::unix::fs::PermissionsExt for a better way + let fullpath = self.fullpath(path)?; + let path_cstr = cstr(&fullpath)?; + unsafe { + if libc::chmod(path_cstr.as_ptr(), mode) < 0 { + return Err(io::Error::last_os_error()); + } + Ok(()) + } + } + + fn touch(&self, path: &Path, which: FsTouch, tv: (u64, u64)) -> io::Result<()> { + let fullpath = self.fullpath(path)?; + let path_cstr = cstr(&fullpath)?; + + let tval = libc::timespec { + tv_sec: tv.0 as i64, + tv_nsec: tv.1 as i64, + }; + let omit = libc::timespec { + tv_sec: 0, + tv_nsec: libc::UTIME_OMIT, + }; + let now = libc::timespec { + tv_sec: 0, + tv_nsec: libc::UTIME_NOW, + }; + + let times = match which { + FsTouch::Atime => [tval, omit], + FsTouch::AtimeNow => [ now, omit ], + FsTouch::Mtime => [omit, tval ], + FsTouch::MtimeNow => [omit, now], + }; + unsafe { + // XXX this could be wildly wrong but libc has wrong type + if libc::utimensat(-1, path_cstr.as_ptr(), ×.as_ptr() as *const _ as *const libc::timespec, 0) < 0 { + return Err(io::Error::last_os_error()); + } + } + Ok(()) + } + + fn truncate(&self, path: &Path, size: u64) -> io::Result<()> { + let fullpath = self.fullpath(path)?; + let path_cstr = cstr(&fullpath)?; + unsafe { + if libc::truncate64(path_cstr.as_ptr(), size as i64) < 0 { + return Err(io::Error::last_os_error()); + } + } + Ok(()) + } + + // XXX + fn readlink(&self, path: &Path) -> io::Result { + let fullpath = self.fullpath(path)?; + fs::read_link(&fullpath).map(|pbuf| pbuf.into_os_string()) + } +} + + + +#[repr(C)] +pub struct LibcStatFs { + f_type: u64, + f_bsize: u64, + f_blocks: u64, + f_bfree: u64, + f_bavail: u64, + + f_files: u64, + f_ffree: u64, + f_fsid: FsidT, + + f_namelen: u64, + f_frsize: u64, + f_spare: [u64; 5], +} + +#[repr(C)] +struct FsidT{ + val: [libc::c_int; 2], +} +extern { + pub fn statfs(path: *const libc::c_char, buf: *mut LibcStatFs) -> libc::c_int; +} + +fn os_err(errno: i32) -> io::Error { + io::Error::from_raw_os_error(errno) +} + + diff --git a/src/devices/virtio_9p/mod.rs b/src/devices/virtio_9p/mod.rs new file mode 100644 index 0000000..2c92f0e --- /dev/null +++ b/src/devices/virtio_9p/mod.rs @@ -0,0 +1,90 @@ +use std::sync::{Arc,RwLock}; +use std::thread; + +use std::path::{Path,PathBuf}; + +use memory::GuestRam; +use virtio::{self,VirtioBus,VirtioDeviceOps, VirtQueue}; +use vm::Result; + +mod fid; +mod pdu; +mod commands; +mod readdir; +mod filesystem; + +use self::pdu::PduParser; +use self::commands::Commands; + +const VIRTIO_ID_9P: u16 = 9; +const VIRTIO_9P_MOUNT_TAG: u64 = 0x1; + + +pub struct VirtioP9 { + root_dir: PathBuf, + init_path: PathBuf, + feature_bits: u64, + config: Vec, +} + +impl VirtioP9 { + fn create_config(tag_name: &str) -> Vec { + let tag_len = tag_name.len() as u16; + let mut config = Vec::with_capacity(tag_name.len() + 3); + config.push(tag_len as u8); + config.push((tag_len >> 8) as u8); + config.append(&mut tag_name.as_bytes().to_vec()); + config.push(0); + config + } + + fn new(tag_name: &str, root_dir: &str, init_path: &Path) -> Arc> { + Arc::new(RwLock::new(VirtioP9 { + root_dir: PathBuf::from(root_dir), + init_path: init_path.to_path_buf(), + feature_bits: 0, + config: VirtioP9::create_config(tag_name), + })) + } + + pub fn create(vbus: &mut VirtioBus, tag_name: &str, root_dir: &str, init_path: &Path) -> Result<()> { + vbus.new_virtio_device(VIRTIO_ID_9P, VirtioP9::new(tag_name, root_dir, init_path)) + .set_num_queues(1) + .set_features(VIRTIO_9P_MOUNT_TAG) + .set_config_size(tag_name.len() + 3) + .register() + } +} + +impl VirtioDeviceOps for VirtioP9 { + fn reset(&mut self) { + println!("Reset called"); + } + + fn enable_features(&mut self, bits: u64) -> bool { + self.feature_bits = bits; + true + } + + fn read_config(&mut self, offset: usize, size: usize) -> u64 { + virtio::read_config_buffer(&self.config, offset, size) + } + + + fn start(&mut self, memory: GuestRam, mut queues: Vec) { + let vq = queues.pop().unwrap(); + let root_dir = self.root_dir.clone(); + let init_path = self.init_path.clone(); + thread::spawn(|| run_device(memory, vq, root_dir, init_path)); + } +} + +fn run_device(memory: GuestRam, vq: VirtQueue, root_dir: PathBuf, init_path: PathBuf) { + let mut commands = Commands::new(root_dir,init_path,memory.clone()); + + vq.on_each_chain(|mut chain| { + let mut pp = PduParser::new(&mut chain, memory.clone()); + commands.handle(&mut pp); + }); +} + diff --git a/src/devices/virtio_9p/pdu.rs b/src/devices/virtio_9p/pdu.rs new file mode 100644 index 0000000..0c59d19 --- /dev/null +++ b/src/devices/virtio_9p/pdu.rs @@ -0,0 +1,322 @@ +use std::fs::Metadata; +const P9_RLERROR: u8 = 7; +use byteorder::{LittleEndian,ReadBytesExt,WriteBytesExt}; +use std::io::{self,Read,Write}; +use std::os::linux::fs::MetadataExt; +use std::os::unix::ffi::OsStrExt; +use std::ffi::OsStr; +use memory::GuestRam; +use virtio::Chain; + +use super::filesystem::StatFs; + +use libc; + +const P9_STATS_BASIC: u64 = 0x000007ff; + +const P9_HEADER_LEN: usize = 7; + +const P9_QTFILE: u8 = 0x00; +const P9_QTLINK: u8 = 0x01; +const _P9_QTSYMLINK: u8 = 0x02; + +const P9_QTDIR: u8 = 0x80; + +pub struct PduParser<'a> { + memory: GuestRam, + pub chain: &'a mut Chain, + + size: u32, + cmd: u8, + tag: u16, + reply_start_addr: u64, +} + +#[derive(Default)] +pub struct P9Attr { + valid: u32, + mode: u32, + uid: u32, + gid: u32, + size: u64, + atime_sec: u64, + atime_nsec: u64, + mtime_sec: u64, + mtime_nsec: u64, +} + +impl P9Attr { + const MODE: u32 = (1 << 0); + const UID: u32 = (1 << 1); + const GID: u32 = (1 << 2); + const SIZE: u32 = (1 << 3); + const ATIME: u32 = (1 << 4); + const MTIME: u32 = (1 << 5); + const CTIME: u32 = (1 << 6); + const ATIME_SET: u32 = (1 << 7); + const MTIME_SET: u32 = (1 << 8); + const MASK: u32 = 127; + const NO_UID: u32 = 0xFFFFFFFF; + + fn new() -> P9Attr { + P9Attr { ..Default::default() } + } + + fn is_valid(&self, flag: u32) -> bool { + self.valid & flag != 0 + } + + pub fn has_mode(&self) -> bool { self.is_valid(P9Attr::MODE) } + pub fn has_atime(&self) -> bool { self.is_valid(P9Attr::ATIME) } + pub fn has_atime_set(&self) -> bool { self.is_valid(P9Attr::ATIME_SET) } + pub fn has_mtime(&self) -> bool { self.is_valid(P9Attr::MTIME) } + pub fn has_mtime_set(&self) -> bool { self.is_valid(P9Attr::MTIME_SET) } + pub fn has_chown(&self) -> bool { + self.valid & P9Attr::MASK == P9Attr::CTIME|| + self.is_valid(P9Attr::UID|P9Attr::GID) + } + pub fn has_size(&self) -> bool { self.is_valid(P9Attr::SIZE) } + + pub fn mode(&self) -> u32 { + self.mode + } + + pub fn size(&self) -> u64 { + self.size + } + + pub fn chown_ids(&self) -> (u32, u32) { + let uid = if self.is_valid(P9Attr::UID) + { self.uid } else { P9Attr::NO_UID }; + let gid = if self.is_valid(P9Attr::GID) + { self.gid } else { P9Attr::NO_UID }; + (uid, gid) + } + + pub fn atime(&self) -> (u64, u64) { + (self.atime_sec, self.atime_nsec) + } + + pub fn mtime(&self) -> (u64, u64) { + (self.mtime_sec, self.mtime_nsec) + } + + fn parse(&mut self, pp: &mut PduParser) -> io::Result<()> { + self.valid = pp.r32()?; + self.mode = pp.r32()?; + self.uid = pp.r32()?; + self.gid = pp.r32()?; + self.size = pp.r64()?; + self.atime_sec = pp.r64()?; + self.atime_nsec = pp.r64()?; + self.mtime_sec = pp.r64()?; + self.mtime_nsec = pp.r64()?; + Ok(()) + } +} + + +impl <'a> PduParser<'a> { + pub fn new(chain: &'a mut Chain, memory: GuestRam) -> PduParser<'a> { + PduParser{ memory, chain, size: 0, cmd: 0, tag: 0, reply_start_addr: 0 } + } + + pub fn command(&mut self) -> io::Result { + self.size = self.r32()?; + self.cmd = self.r8()?; + self.tag = self.r16()?; + Ok(self.cmd) + } + + pub fn read_done(&mut self) -> io::Result<()> { + // XXX unwrap + self.reply_start_addr = self.chain.current_write_address(8).unwrap(); + // reserve header + self.w32(0)?; + self.w8(0)?; + self.w16(0)?; + Ok(()) + } + + pub fn bail_err(&mut self, error: io::Error) -> io::Result<()> { + if self.reply_start_addr == 0 { + self.read_done()?; + } + + let err = match error.raw_os_error() { + Some(errno) => errno as u32, + None => 0, + }; + + self._w32_at(0,P9_HEADER_LEN as u32 + 4); + self._w8_at(4, P9_RLERROR); + self._w16_at(5, self.tag); + self._w32_at(7, err); + self.chain.flush_chain(); + Ok(()) + } + + #[allow(dead_code)] + pub fn w8_at(&self, offset: usize, val: u8) { + self._w8_at(offset + P9_HEADER_LEN, val); + } + + pub fn _w8_at(&self, offset: usize, val: u8) { + self.memory.write_int::(self.reply_start_addr + offset as u64, val).unwrap(); + } + + #[allow(dead_code)] + pub fn w16_at(&self, offset: usize, val: u16) { + self._w16_at(offset + P9_HEADER_LEN, val); + } + + pub fn _w16_at(&self, offset: usize, val: u16) { + self.memory.write_int::(self.reply_start_addr + offset as u64, val).unwrap(); + } + + pub fn w32_at(&self, offset: usize, val: u32) { + self._w32_at(offset + P9_HEADER_LEN, val); + } + + pub fn _w32_at(&self, offset: usize, val: u32) { + self.memory.write_int::(self.reply_start_addr + offset as u64, val).unwrap(); + } + + + pub fn write_done(&mut self) -> io::Result<()> { + self._w32_at(0, self.chain.get_wlen() as u32); + let cmd = self.cmd + 1; + self._w8_at(4, cmd); + let tag = self.tag; + self._w16_at(5, tag); + self.chain.flush_chain(); + Ok(()) + } + + pub fn read_string(&mut self) -> io::Result { + let len = self.r16()?; + if len == 0 { + return Ok(String::new()); + } + let mut buf = vec![0u8; len as usize]; + self.chain.read_exact(&mut buf)?; + let s = String::from_utf8(buf) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "bad 9p string"))?; + Ok(s) + } + + pub fn read_attr(&mut self) -> io::Result { + let mut attr = P9Attr::new(); + attr.parse(self)?; + Ok(attr) + } + + pub fn write_string(&mut self, str: &str) -> io::Result<()> { + self.w16(str.len() as u16)?; + self.chain.write_all(str.as_bytes()) + } + + pub fn write_os_string(&mut self, str: &OsStr) -> io::Result<()> { + self.w16(str.len() as u16)?; + self.chain.write_all(str.as_bytes()) + } + + + fn is_lnk(meta: &Metadata) -> bool { + meta.st_mode() & libc::S_IFMT == libc::S_IFLNK + } + + fn meta_to_qtype(meta: &Metadata) -> u8 { + if meta.is_dir() { + P9_QTDIR + } else if PduParser::is_lnk(meta) { + P9_QTLINK + } else { + P9_QTFILE + } + } + + pub fn write_qid(&mut self, meta: &Metadata) -> io::Result<()> { + // type + self.w8(PduParser::meta_to_qtype(meta))?; + // version + self.w32(meta.st_mtime() as u32 ^ (meta.st_size() << 8) as u32)?; + // path + self.w64(meta.st_ino()) + } + + pub fn write_qid_path_only(&mut self, ino: u64) -> io::Result<()> { + self.w8(0)?; + self.w32(0)?; + self.w64(ino) + } + + pub fn write_statl(&mut self, st: &Metadata) -> io::Result<()> { + self.w64(P9_STATS_BASIC)?; + self.write_qid(&st)?; + self.w32(st.st_mode())?; + self.w32(st.st_uid())?; + self.w32(st.st_gid())?; + self.w64(st.st_nlink())?; + self.w64(st.st_rdev())?; + self.w64(st.st_size())?; + self.w64(st.st_blksize())?; + self.w64(st.st_blocks())?; + self.w64(st.st_atime() as u64)?; + self.w64(st.st_atime_nsec() as u64)?; + self.w64(st.st_mtime() as u64)?; + self.w64(st.st_mtime_nsec() as u64)?; + self.w64(st.st_ctime() as u64)?; + self.w64(st.st_ctime_nsec() as u64)?; + self.w64(0)?; + self.w64(0)?; + self.w64(0)?; + self.w64(0)?; + Ok(()) + } + + pub fn write_statfs(&mut self, statfs: StatFs) -> io::Result<()> { + self.w32(statfs.f_type)?; + self.w32(statfs.f_bsize)?; + self.w64(statfs.f_blocks)?; + self.w64(statfs.f_bfree)?; + self.w64(statfs.f_bavail)?; + self.w64(statfs.f_files)?; + self.w64(statfs.f_ffree)?; + self.w64(statfs.fsid)?; + self.w32(statfs.f_namelen)?; + Ok(()) + } + + pub fn r8(&mut self) -> io::Result { + self.chain.read_u8() + } + + pub fn r16(&mut self) -> io::Result { + self.chain.read_u16::() + } + + pub fn r32(&mut self) -> io::Result { + self.chain.read_u32::() + } + + pub fn r64(&mut self) -> io::Result { + self.chain.read_u64::() + } + + pub fn w8(&mut self, val: u8) -> io::Result<()> { + self.chain.write_u8(val) + } + + pub fn w16(&mut self, val: u16) -> io::Result<()> { + self.chain.write_u16::(val) + } + + pub fn w32(&mut self, val: u32) -> io::Result<()> { + self.chain.write_u32::(val) + } + pub fn w64(&mut self, val: u64) -> io::Result<()> { + self.chain.write_u64::(val) + } +} + diff --git a/src/devices/virtio_9p/readdir.rs b/src/devices/virtio_9p/readdir.rs new file mode 100644 index 0000000..4932a02 --- /dev/null +++ b/src/devices/virtio_9p/readdir.rs @@ -0,0 +1,131 @@ +use std::path::Path; +use std::mem; +use std::ptr; +use std::io; +use std::ffi::{OsStr,CStr,CString}; +use std::os::unix::ffi::OsStrExt; + +use libc; + +struct Dir(*mut libc::DIR); + +pub struct ReadDir { + dirp: Dir, + last_pos: i64, +} + +pub struct DirEntry { + entry: libc::dirent64, +} + + +fn cstr(path: &Path) -> io::Result { + Ok(CString::new(path.as_os_str().as_bytes())?) +} + +impl ReadDir { + pub fn open(path: &Path) -> io::Result { + let p = cstr(path)?; + unsafe { + let ptr = libc::opendir(p.as_ptr()); + if ptr.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(ReadDir{ dirp: Dir(ptr), last_pos: 0 }) + } + } + } + + pub fn tell(&self) -> io::Result { + unsafe { + let loc = libc::telldir(self.dirp.0); + if loc == -1 { + return Err(io::Error::last_os_error()); + } + Ok(loc) + } + } + + pub fn seek(&self, loc: i64) { + unsafe { libc::seekdir(self.dirp.0, loc)} + } + + pub fn fsync(&self) -> io::Result<()> { + unsafe { + if libc::fsync(libc::dirfd(self.dirp.0)) < 0 { + return Err(io::Error::last_os_error()); + } + } + Ok(()) + } + + fn save_current_pos(&mut self) { + match self.tell() { + Ok(loc) => self.last_pos = loc, + Err(_) => (), + }; + } + + pub fn restore_last_pos(&mut self) { + self.seek(self.last_pos) + } +} + + +impl Iterator for ReadDir { + type Item = io::Result; + + fn next(&mut self) -> Option> { + self.save_current_pos(); + unsafe { + let mut ret = DirEntry { + entry: mem::zeroed(), + }; + let mut entry_ptr = ptr::null_mut(); + loop { + if libc::readdir64_r(self.dirp.0, &mut ret.entry, &mut entry_ptr) != 0 { + return Some(Err(io::Error::last_os_error())) + } + if entry_ptr.is_null() { + return None + } + if ret.name_bytes() != b"." && ret.name_bytes() != b".." { + return Some(Ok(ret)) + } + } + } + } +} + +impl Drop for Dir { + fn drop(&mut self) { + let _ = unsafe { libc::closedir(self.0) }; + } +} + + +impl DirEntry { + #[allow(dead_code)] + pub fn file_name(&self) -> &OsStr { + OsStr::from_bytes(self.name_bytes()) + } + + pub fn offset(&self) -> u64 { + self.entry.d_off as u64 + } + + pub fn file_type(&self) -> u8 { + self.entry.d_type + } + + pub fn ino(&self) -> u64 { + self.entry.d_ino as u64 + } + + pub fn name_bytes(&self) -> &[u8] { + unsafe { + CStr::from_ptr(self.entry.d_name.as_ptr()).to_bytes() + } + } +} + diff --git a/src/devices/virtio_rng.rs b/src/devices/virtio_rng.rs new file mode 100644 index 0000000..bf4089a --- /dev/null +++ b/src/devices/virtio_rng.rs @@ -0,0 +1,45 @@ + +use std::sync::{Arc,RwLock}; +use std::thread; +use std::fs::File; + +use virtio::{VirtioDeviceOps,VirtioBus,VirtQueue}; +use memory::GuestRam; +use vm::Result; + + +const VIRTIO_ID_RANDOM: u16 = 4; + +pub struct VirtioRandom; + +impl VirtioRandom { + fn new() -> VirtioRandom { VirtioRandom } + + pub fn create(vbus: &mut VirtioBus) -> Result<()> { + let dev = Arc::new(RwLock::new(VirtioRandom::new())); + vbus.new_virtio_device(VIRTIO_ID_RANDOM, dev) + .set_num_queues(1) + .register() + } +} + +impl VirtioDeviceOps for VirtioRandom { + + fn start(&mut self, _memory: GuestRam, mut queues: Vec) { + thread::spawn(move|| { + run(queues.pop().unwrap()) + }); + } +} + +fn run(q: VirtQueue) { + let random = File::open("/dev/urandom").unwrap(); + + loop { + q.on_each_chain(|mut chain| { + while !chain.is_end_of_chain() { + let _ = chain.copy_from_reader(&random, 256).unwrap(); + } + }); + } +} \ No newline at end of file diff --git a/src/devices/virtio_serial.rs b/src/devices/virtio_serial.rs new file mode 100644 index 0000000..056a0a1 --- /dev/null +++ b/src/devices/virtio_serial.rs @@ -0,0 +1,226 @@ +use std::sync::{Arc,RwLock}; +use std::io::{self,Write,Read}; +use std::thread::spawn; +use termios::*; + +use virtio::{VirtioDeviceOps,VirtioBus, VirtQueue}; +use memory::GuestRam; +use vm::Result; + +const VIRTIO_ID_CONSOLE: u16 = 3; + +const VIRTIO_CONSOLE_F_SIZE: u64 = 0x1; +const VIRTIO_CONSOLE_F_MULTIPORT: u64 = 0x2; + +const VIRTIO_CONSOLE_DEVICE_READY: u16 = 0; +const VIRTIO_CONSOLE_DEVICE_ADD: u16 = 1; +const _VIRTIO_CONSOLE_DEVICE_REMOVE: u16 = 2; +const VIRTIO_CONSOLE_PORT_READY: u16 = 3; +const VIRTIO_CONSOLE_CONSOLE_PORT: u16 = 4; +const VIRTIO_CONSOLE_RESIZE: u16 = 5; +const VIRTIO_CONSOLE_PORT_OPEN: u16 = 6; +const _VIRTIO_CONSOLE_PORT_NAME: u16 = 7; + +pub struct VirtioSerial { + feature_bits: u64, +} + +impl VirtioSerial { + fn new() -> VirtioSerial { + VirtioSerial{feature_bits:0} + } + + pub fn create(vbus: &mut VirtioBus) -> Result<()> { + let dev = Arc::new(RwLock::new(VirtioSerial::new())); + vbus.new_virtio_device(VIRTIO_ID_CONSOLE, dev) + .set_num_queues(4) + .set_device_class(0x0700) + .set_config_size(12) + .set_features(VIRTIO_CONSOLE_F_MULTIPORT|VIRTIO_CONSOLE_F_SIZE) + .register() + } + + fn start_console(&self, _memory: GuestRam, q: VirtQueue) { + spawn(move || { + loop { + q.wait_ready().unwrap(); + for mut chain in q.iter() { + io::copy(&mut chain, &mut io::stdout()).unwrap(); + io::stdout().flush().unwrap(); + } + } + }); + } + + fn multiport(&self) -> bool { + self.feature_bits & VIRTIO_CONSOLE_F_MULTIPORT != 0 + } +} + +use system::ioctl; + +#[repr(C)] +#[derive(Default)] +struct WinSz { + ws_row: u16, + ws_col: u16, + ws_xpixel: u16, + ws_ypixel: u16, +} + +const TIOCGWINSZ: u64 = 0x5413; + +impl VirtioDeviceOps for VirtioSerial { + fn reset(&mut self) { + println!("Reset called"); + } + + fn enable_features(&mut self, bits: u64) -> bool { + self.feature_bits = bits; + true + } + + + fn start(&mut self, memory: GuestRam, mut queues: Vec) { + let mut term = Terminal::create(queues.remove(0)); + self.start_console(memory, queues.remove(0)); + + spawn( move || { + term.read_loop(); + }); + + if self.multiport() { + let mut control = Control::new(queues.remove(0), queues.remove(0)); + spawn(move || { + control.run(); + + }); + } + } + + fn read_config(&mut self, offset: usize, _size: usize) -> u64 { + if offset == 4 { + return 1; + } + 0 + } +} + +struct Control { + rx_vq: VirtQueue, + tx_vq: VirtQueue, +} + +use byteorder::{LittleEndian,ReadBytesExt,WriteBytesExt}; +impl Control { + fn new(rx: VirtQueue, tx: VirtQueue) -> Control { + Control { rx_vq: rx, tx_vq: tx } + } + + fn run(&mut self) { + let mut rx = self.rx_vq.clone(); + self.tx_vq.on_each_chain(|mut chain| { + let _id = chain.read_u32::().unwrap(); + let event = chain.read_u16::().unwrap(); + let _value = chain.read_u16::().unwrap(); + if event == VIRTIO_CONSOLE_DEVICE_READY { + Control::send_msg(&mut rx,0, VIRTIO_CONSOLE_DEVICE_ADD, 1).unwrap(); + } + if event == VIRTIO_CONSOLE_PORT_READY { + Control::send_msg(&mut rx,0, VIRTIO_CONSOLE_CONSOLE_PORT, 1).unwrap(); + Control::send_msg(&mut rx,0, VIRTIO_CONSOLE_PORT_OPEN, 1).unwrap(); + Control::send_resize(&mut rx, 0).unwrap(); + } + chain.flush_chain(); + }); + + } + + fn send_msg(vq: &mut VirtQueue, id: u32, event: u16, val: u16) -> io::Result<()> { + let mut chain = vq.wait_next_chain().unwrap(); + chain.write_u32::(id)?; + chain.write_u16::(event)?; + chain.write_u16::(val)?; + chain.flush_chain(); + Ok(()) + } + + fn send_resize(vq: &mut VirtQueue, id: u32) -> io::Result<()> { + let (cols, rows) = Control::stdin_terminal_size()?; + let mut chain = vq.wait_next_chain().unwrap(); + chain.write_u32::(id)?; + chain.write_u16::(VIRTIO_CONSOLE_RESIZE)?; + chain.write_u16::(0)?; + chain.write_u16::(rows)?; + chain.write_u16::(cols)?; + chain.flush_chain(); + Ok(()) + } + + fn stdin_terminal_size() -> io::Result<(u16, u16)> { + let mut wsz = WinSz{..Default::default()}; + unsafe { + if let Err(err) = ioctl::ioctl_with_mut_ref(0, TIOCGWINSZ, &mut wsz) { + println!("Got error calling TIOCGWINSZ on stdin: {:?}", err); + return Err(io::Error::last_os_error()); + } + } + Ok((wsz.ws_col, wsz.ws_row)) + } + +} + +struct Terminal { + saved: Termios, + vq: VirtQueue, +} + +impl Terminal { + fn create(vq: VirtQueue) -> Terminal { + let termios = Termios::from_fd(0).unwrap(); + Terminal { + saved: termios, + vq, + } + } + + fn setup_term(&self) { + let mut termios = self.saved.clone(); + termios.c_iflag &= !(ICRNL); + termios.c_lflag &= !(ISIG|ICANON|ECHO); + let _ = tcsetattr(0, TCSANOW, &termios); + } + + fn read_loop(&mut self) { + self.setup_term(); + let mut abort_cnt = 0; + let mut buf = vec![0u8; 32]; + loop { + let n = io::stdin().read(&mut buf).unwrap(); + + if n > 0 { + // XXX write_all + let mut chain = self.vq.wait_next_chain().unwrap(); + chain.write_all(&mut buf[..n]).unwrap(); + chain.flush_chain(); + if n > 1 || buf[0] != 3 { + abort_cnt = 0; + } else { + abort_cnt += 1; + } + } + + if abort_cnt == 3 { + let _ = tcsetattr(0, TCSANOW, &self.saved); + } + + } + + } +} + +impl Drop for Terminal { + fn drop(&mut self) { + let _ = tcsetattr(0, TCSANOW, &self.saved); + } +} diff --git a/src/kvm/ioctl.rs b/src/kvm/ioctl.rs new file mode 100644 index 0000000..bfd9fd4 --- /dev/null +++ b/src/kvm/ioctl.rs @@ -0,0 +1,612 @@ +use libc::{self, c_char, c_ulong}; +use std::os::unix::io::RawFd; +use std::ffi::CString; +use std::fmt; + +use system::ioctl::{ioctl_with_val,ioctl_with_ref,ioctl_with_mut_ref}; + +use vm::{Result,Error,ErrorKind}; + + +const KVMIO: u64 = 0xAE; + +const KVM_GET_API_VERSION: c_ulong = io! (KVMIO, 0x00); +const KVM_CREATE_VM: c_ulong = io! (KVMIO, 0x01); +const KVM_CHECK_EXTENSION: c_ulong = io! (KVMIO, 0x03); +const KVM_GET_SUPPORTED_CPUID: c_ulong = iorw! (KVMIO, 0x05, 8); +const KVM_SET_TSS_ADDR: c_ulong = io! (KVMIO, 0x47); +const KVM_CREATE_IRQCHIP: c_ulong = io! (KVMIO, 0x60); +const KVM_CREATE_PIT2: c_ulong = iow! (KVMIO, 0x77, 64); +const KVM_GET_VCPU_MMAP_SIZE: c_ulong = io! (KVMIO, 0x04); +const KVM_CREATE_VCPU: c_ulong = io! (KVMIO, 0x41); +const KVM_SET_USER_MEMORY_REGION: c_ulong = iow! (KVMIO, 0x46, 32); +const KVM_IRQ_LINE: c_ulong = iow! (KVMIO, 0x61, 8); +const KVM_IRQFD: c_ulong = iow! (KVMIO, 0x76, 32); +const KVM_IOEVENTFD: c_ulong = iow! (KVMIO, 0x79, 64); +const KVM_RUN: c_ulong = io! (KVMIO, 0x80); +const KVM_GET_REGS: c_ulong = ior! (KVMIO, 0x81, 144); +const KVM_SET_REGS: c_ulong = iow! (KVMIO, 0x82, 144); +const KVM_GET_SREGS: c_ulong = ior! (KVMIO, 0x83, 312); +const KVM_SET_SREGS: c_ulong = iow! (KVMIO, 0x84, 312); +const KVM_SET_MSRS: c_ulong = iow! (KVMIO, 0x89, 8); +const KVM_SET_FPU: c_ulong = iow! (KVMIO, 0x8d, 416); +const KVM_GET_LAPIC: c_ulong = ior! (KVMIO, 0x8e, 1024); +const KVM_SET_LAPIC: c_ulong = iow! (KVMIO, 0x8f, 1024); +const KVM_SET_CPUID2: c_ulong = iow! (KVMIO, 0x90, 8); + + +struct InnerFd(RawFd); +impl InnerFd { + fn raw(&self) -> RawFd { self.0 } +} + +impl Drop for InnerFd { + fn drop(&mut self) { + let _ = unsafe { libc::close(self.0) }; + } +} + +pub struct SysFd(InnerFd); + +fn raw_open_kvm() -> Result { + let path = CString::new("/dev/kvm").unwrap(); + let fd = unsafe { libc::open(path.as_ptr() as *const c_char, libc::O_RDWR) }; + if fd < 0 { + return Err(Error::from_last_errno()); + } + Ok(fd) +} + +impl SysFd { + pub fn open() -> Result { + match raw_open_kvm() { + Ok(fd) => Ok(SysFd(InnerFd(fd))), + Err(e) => Err(Error::new(ErrorKind::OpenDeviceFailed, e)), + } + } + + fn raw(&self) -> RawFd { self.0.raw() } +} + +pub struct VmFd(InnerFd); + +impl VmFd { + fn new(fd: RawFd) -> VmFd { + VmFd( InnerFd(fd) ) + } + fn raw(&self) -> RawFd { self.0.raw() } +} + +pub struct VcpuFd(InnerFd); + +impl VcpuFd { + fn new(fd: RawFd) -> VcpuFd { + VcpuFd( InnerFd(fd) ) + } + pub fn raw(&self) -> RawFd { self.0.raw() } +} + + +pub fn kvm_check_extension(sysfd: &SysFd, extension: u32) -> Result { + unsafe { + ioctl_with_val(sysfd.raw(), KVM_CHECK_EXTENSION, extension as c_ulong) + .map_err(|e| ioctl_err("KVM_CHECK_EXTENSION", e)) + } +} + +pub fn kvm_get_api_version(sysfd: &SysFd) -> Result { + unsafe { + ioctl_with_val(sysfd.raw(), KVM_GET_API_VERSION, 0) + .map_err(|e| ioctl_err("KVM_GET_API_VERSION", e)) + } +} + +pub fn kvm_create_vm(sysfd: &SysFd) -> Result { + let fd = unsafe { + ioctl_with_val(sysfd.raw(), KVM_CREATE_VM, 0) + .map_err(|e| ioctl_err("KVM_CREATE_VM", e))? + }; + Ok(VmFd::new(fd as RawFd)) +} + +pub fn kvm_get_vcpu_mmap_size(sysfd: &SysFd) -> Result { + unsafe { + ioctl_with_val(sysfd.raw(), KVM_GET_VCPU_MMAP_SIZE, 0) + .map_err(|e| ioctl_err("KVM_GET_VCPU_MMAP_SIZE", e)) + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct KvmCpuIdEntry { + pub function: u32, + pub index: u32, + pub flags: u32, + pub eax: u32, + pub ebx: u32, + pub ecx: u32, + pub edx: u32, + padding: [u32; 3] +} + +const KVM_CPUID_MAX_ENTRIES:usize = 256; + +#[repr(C)] +pub struct KvmCpuId2 { + nent: u32, + padding: u32, + entries: [KvmCpuIdEntry; KVM_CPUID_MAX_ENTRIES] +} + +impl KvmCpuId2 { + pub fn new() -> KvmCpuId2 { + KvmCpuId2 { + nent: KVM_CPUID_MAX_ENTRIES as u32, + padding: 0, + entries: [Default::default(); KVM_CPUID_MAX_ENTRIES], + } + } + + pub fn new_from_entries(entries: Vec) -> KvmCpuId2 { + let mut cpuid = KvmCpuId2::new(); + let sz = entries.len(); + assert!(sz <= KVM_CPUID_MAX_ENTRIES, "Too many cpuid entries"); + for i in 0..sz { + cpuid.entries[i] = entries[i]; + } + cpuid.nent = sz as u32; + cpuid + } + + pub fn get_entries(&self) -> Vec { + let mut entries = Vec::new(); + let sz = self.nent as usize; + for i in 0..sz { + entries.push(self.entries[i]); + } + entries + } +} + +pub fn kvm_get_supported_cpuid(sysfd: &SysFd, cpuid: &mut KvmCpuId2) -> Result { + unsafe { + ioctl_with_mut_ref(sysfd.raw(), KVM_GET_SUPPORTED_CPUID, cpuid) + .map_err(|e| ioctl_err("KVM_GET_SUPPORTED_CPUID", e)) + } +} + +pub fn kvm_set_cpuid2(cpufd: &VcpuFd, cpuid: &KvmCpuId2) -> Result { + unsafe { + ioctl_with_ref(cpufd.raw(), KVM_SET_CPUID2, cpuid) + .map_err(|e| ioctl_err("KVM_SET_CPUID2", e)) + } +} + +#[repr(C)] +pub struct KvmUserspaceMemoryRegion { + slot: u32, + flags: u32, + guest_phys_addr: u64, + memory_size: u64, + userspace_addr: u64, +} + +impl KvmUserspaceMemoryRegion { + pub fn new(slot: u32, guest_address: u64, host_address: u64, size: u64) -> KvmUserspaceMemoryRegion { + KvmUserspaceMemoryRegion { + slot, + flags: 0, + guest_phys_addr: guest_address, + memory_size: size, + userspace_addr: host_address, + } + } +} + +pub fn kvm_set_user_memory_region(vmfd: &VmFd, region: &KvmUserspaceMemoryRegion) -> Result { + unsafe { + ioctl_with_ref(vmfd.raw(), KVM_SET_USER_MEMORY_REGION, region) + .map_err(|e| ioctl_err("KVM_SET_USER_MEMORY_REGION", e)) + } +} + +#[repr(C)] +pub struct KvmPitConfig { + flags: u32, + padding: [u32; 15], +} + +impl KvmPitConfig { + pub fn new(flags: u32) -> KvmPitConfig { + KvmPitConfig { flags, padding: [0; 15] } + } +} + +pub fn kvm_create_pit2(vmfd: &VmFd, config: &KvmPitConfig) -> Result { + unsafe { + ioctl_with_ref(vmfd.raw(), KVM_CREATE_PIT2, config) + .map_err(|e| ioctl_err("KVM_CREATE_PIT2", e)) + } +} + +pub fn kvm_create_irqchip(vmfd: &VmFd) -> Result { + unsafe { + ioctl_with_val(vmfd.raw(), KVM_CREATE_IRQCHIP, 0) + .map_err(|e| ioctl_err("KVM_CREATE_IRQCHIP", e)) + } +} + +pub fn kvm_set_tss_addr(vmfd: &VmFd, addr: u32) -> Result { + unsafe { + ioctl_with_val(vmfd.raw(), KVM_SET_TSS_ADDR, addr as c_ulong) + .map_err(|e| ioctl_err("KVM_SET_TSS_ADDR", e)) + } +} + +pub fn kvm_create_vcpu(vmfd: &VmFd, cpu_id: u32) -> Result { + let fd = unsafe { + ioctl_with_val(vmfd.raw(), KVM_CREATE_VCPU, cpu_id as c_ulong) + .map_err(|e| ioctl_err("KVM_CREATE_VCPU", e))? + }; + Ok(VcpuFd::new(fd as RawFd)) +} + +#[repr(C)] +pub struct KvmIrqLevel { + irq: u32, + level: u32, +} + +impl KvmIrqLevel { + pub fn new(irq: u32, level: u32) -> KvmIrqLevel { + KvmIrqLevel { irq, level } + } +} + +pub fn kvm_irq_line(vmfd: &VmFd, level: &KvmIrqLevel) -> Result { + unsafe { + ioctl_with_ref(vmfd.raw(), KVM_IRQ_LINE, level) + .map_err(|e| ioctl_err("KVM_IRQ_LINE", e)) + } +} + +#[repr(C)] +pub struct KvmIrqFd { + fd: u32, + gsi: u32, + flags: u32, + resample_fd: u32, + pad1: u64, + pad2: u64, +} + +impl KvmIrqFd { + pub fn new(fd: u32, gsi: u32) -> KvmIrqFd { + KvmIrqFd{fd, gsi, flags:0, resample_fd: 0, pad1: 0, pad2: 0} + } +} + +pub fn kvm_irqfd(vmfd: &VmFd, irqfd: &KvmIrqFd) -> Result { + unsafe { + ioctl_with_ref(vmfd.raw(), KVM_IRQFD, irqfd) + .map_err(|e| ioctl_err("KVM_IRQFD", e)) + } +} + +pub const IOEVENTFD_FLAG_DATAMATCH: u32 = 1; +pub const _IOEVENTFD_FLAG_PIO : u32 = 2; +pub const IOEVENTFD_FLAG_DEASSIGN: u32 = 4; + +#[repr(C)] +pub struct KvmIoEventFd { + datamatch: u64, + addr: u64, + len: u32, + fd: u32, + flags: u32, + padding: [u8; 36], +} + +impl KvmIoEventFd { + pub fn new_with_addr_fd(addr: u64, fd: RawFd) -> KvmIoEventFd { + KvmIoEventFd::new(0, addr, 0, fd as u32, 0) + } + + fn new(datamatch: u64, addr: u64, len: u32, fd: u32, flags: u32) -> KvmIoEventFd { + KvmIoEventFd{datamatch, addr, len, fd, flags, padding: [0;36]} + } + + #[allow(dead_code)] + pub fn set_datamatch(&mut self, datamatch: u64, len: u32) { + self.flags |= IOEVENTFD_FLAG_DATAMATCH; + self.datamatch = datamatch; + self.len = len; + } + + pub fn set_deassign(&mut self) { + self.flags |= IOEVENTFD_FLAG_DEASSIGN; + } +} + +pub fn kvm_ioeventfd(vmfd: &VmFd, ioeventfd: &KvmIoEventFd) -> Result { + unsafe { + ioctl_with_ref(vmfd.raw(), KVM_IOEVENTFD, ioeventfd) + .map_err(|e| ioctl_err("KVM_IOEVENTFD", e)) + } +} + + +#[repr(C)] +pub struct KvmLapicState { + pub regs: [u8; 1024] +} + +impl KvmLapicState { + pub fn new() -> KvmLapicState { + KvmLapicState { regs: [0; 1024] } + } +} + +pub fn kvm_get_lapic(cpufd: &VcpuFd, lapic_state: &mut KvmLapicState) -> Result { + unsafe { + ioctl_with_mut_ref(cpufd.raw(), KVM_GET_LAPIC, lapic_state) + .map_err(|e| ioctl_err("KVM_GET_LAPIC", e)) + } +} + +pub fn kvm_set_lapic(cpufd: &VcpuFd, lapic_state: &KvmLapicState) -> Result { + unsafe { + ioctl_with_ref(cpufd.raw(), KVM_SET_LAPIC, lapic_state) + .map_err(|e| ioctl_err("KVM_SET_LAPIC", e)) + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct KvmSegment { + base: u64, + limit: u32, + selector: u16, + stype: u8, + present: u8, + dpl: u8, + db: u8, + s: u8, + l: u8, + g: u8, + avl: u8, + unusable: u8, + padding: u8, +} + +impl KvmSegment { + pub fn new(base: u64, limit: u32, selector: u16, flags: u16) -> KvmSegment { + let mut seg = KvmSegment{ ..Default::default() }; + seg.setup(base, limit, selector, flags); + seg + } + + pub fn setup(&mut self, base: u64, limit: u32, selector: u16, flags: u16) { + self.base = base; + self.limit = limit; + self.selector = selector; + self.stype = (flags & 0xF) as u8; + self.present = ((flags >> 7) & 0x1) as u8; + self.dpl = ((flags >> 5) & 0x3) as u8; + self.db = ((flags >> 14) & 0x1) as u8; + self.s = ((flags >> 4) & 0x1) as u8; + self.l = ((flags >> 13) & 0x1) as u8; + self.g = ((flags >> 15) & 0x1) as u8; + self.avl = ((flags >> 12) & 0x1) as u8; + self.unusable = if self.present == 1 { 0 } else { 1 } + } +} + +impl fmt::Debug for KvmSegment { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "(base: {:x} limit {:x} selector: {:x} type: {:x} p: {} dpl: {} db: {} s: {} l: {} g: {} avl: {} unuse: {})", + self.base, self.limit, self.selector, self.stype, self.present, self.dpl, self.db, self.s, self.l, self.g, self.avl, self.unusable) + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct KvmDtable { + pub base: u64, + pub limit: u16, + padding: [u16; 3], +} +impl fmt::Debug for KvmDtable { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "(base: {:x} limit {:x})", self.base, self.limit) + } +} + + + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct KvmSRegs { + pub cs: KvmSegment, + pub ds: KvmSegment, + pub es: KvmSegment, + pub fs: KvmSegment, + pub gs: KvmSegment, + pub ss: KvmSegment, + pub tr: KvmSegment, + pub ldt: KvmSegment, + pub gdt: KvmDtable, + pub itd: KvmDtable, + pub cr0: u64, + pub cr2: u64, + pub cr3: u64, + pub cr4: u64, + pub cr8: u64, + pub efer: u64, + pub apic_base: u64, + pub interrupt_bitmap: [u64; 4], +} +impl fmt::Debug for KvmSRegs { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "cs: {:?}\nds: {:?}\nes: {:?}\nfs: {:?}\n", self.cs, self.ds, self.es, self.fs)?; + write!(f, "gs: {:?}\nss: {:?}\ntr: {:?}\nldt: {:?}\n", self.gs, self.ss, self.tr, self.ldt)?; + write!(f, "gdt: {:?} itd: {:?}\n", self.gdt, self.itd)?; + write!(f, "cr0: {:x} cr2: {:x} cr3: {:x} cr4: {:x}\n", self.cr0, self.cr2, self.cr3, self.cr4)?; + write!(f, "efer: {:x} apic_base: {:x}\n", self.efer, self.apic_base) + } +} + +impl KvmSRegs { + pub fn new() -> KvmSRegs { + KvmSRegs { ..Default::default() } + } +} + +pub fn kvm_get_sregs(cpufd: &VcpuFd, sregs: &mut KvmSRegs) -> Result { + unsafe { + ioctl_with_mut_ref(cpufd.raw(), KVM_GET_SREGS, sregs) + .map_err(|e| ioctl_err("KVM_GET_SREGS", e)) + } +} + +pub fn kvm_set_sregs(cpufd: &VcpuFd, sregs: &KvmSRegs) -> Result { + unsafe { + ioctl_with_ref(cpufd.raw(), KVM_SET_SREGS, sregs) + .map_err(|e| ioctl_err("KVM_SET_SREGS", e)) + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub struct KvmRegs { + pub rax: u64, pub rbx: u64, pub rcx: u64, pub rdx: u64, + pub rsi: u64, pub rdi: u64, pub rsp: u64, pub rbp: u64, + pub r8: u64, pub r9: u64, pub r10: u64, pub r11: u64, + pub r12: u64, pub r13: u64, pub r14: u64, pub r15: u64, + pub rip: u64, pub rflags: u64, +} + +impl fmt::Debug for KvmRegs { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "rax 0x{:x} rbx 0x{:x} rcx 0x{:x} rdx 0x{:x}\n", self.rax, self.rbx, self.rcx, self.rdx)?; + write!(f, "rsi 0x{:x} rdi 0x{:x} rsp 0x{:x} rbp 0x{:x}\n", self.rsi, self.rdi, self.rsp, self.rbp)?; + write!(f, "r8 0x{:x} r9 0x{:x} r10 0x{:x} r11 0x{:x}\n", self.r8, self.r9, self.r10, self.r11)?; + write!(f, "r12 0x{:x} r13 0x{:x} r14 0x{:x} r15 0x{:x}\n", self.r12, self.r13, self.r14, self.r15)?; + write!(f, "rip 0x{:x} rflags 0x{:x}\n", self.rip, self.rflags) + } + +} + +impl KvmRegs { + pub fn new() -> KvmRegs { + KvmRegs { ..Default::default() } + } +} + + +pub fn kvm_get_regs(cpufd: &VcpuFd, regs: &mut KvmRegs) -> Result { + unsafe { + ioctl_with_mut_ref(cpufd.raw(), KVM_GET_REGS, regs) + .map_err(|e| ioctl_err("KVM_GET_REGS", e)) + } +} + +pub fn kvm_set_regs(cpufd: &VcpuFd, regs: &KvmRegs) -> Result { + unsafe { + ioctl_with_ref(cpufd.raw(), KVM_SET_REGS, regs) + .map_err(|e| ioctl_err("KVM_SET_REGS", e)) + } +} + +#[derive(Copy)] +#[repr(C)] +pub struct KvmFpu { + fpr: [u8; 128], + pub fcw: u16, + fsw: u16, + ftwx: u8, + pad1: u8, + last_opcode: u16, + last_ip: u64, + last_dp: u64, + xmm: [u8; 256], + pub mxcsr: u32, + pad2: u32, +} + +impl Clone for KvmFpu { + fn clone(&self) -> KvmFpu { *self } +} +impl KvmFpu { + pub fn new() -> KvmFpu { + KvmFpu { + fpr: [0; 128], + fcw: 0, + fsw: 0, + ftwx: 0, pad1: 0, + last_opcode: 0, + last_ip: 0, + last_dp: 0, + xmm: [0; 256], + mxcsr: 0, + pad2: 0 + } + } +} + +pub fn kvm_set_fpu(cpufd: &VcpuFd, fpu: &KvmFpu) -> Result { + unsafe { + ioctl_with_ref(cpufd.raw(), KVM_SET_FPU, fpu ) + .map_err(|e| ioctl_err("KVM_SET_FPU", e)) + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +struct KvmMsrEntry { + index: u32, + reserved: u32, + data: u64 +} + +#[repr(C)] +pub struct KvmMsrs { + nent: u32, + padding: u32, + entries: [KvmMsrEntry; 100] +} + +impl KvmMsrs { + pub fn new() -> KvmMsrs { + KvmMsrs{ nent: 0, padding: 0, entries: [Default::default(); 100]} + } + + pub fn add(&mut self, index: u32, data: u64) { + self.entries[self.nent as usize].index = index; + self.entries[self.nent as usize].data = data; + self.nent += 1; + } +} + +pub fn kvm_set_msrs(cpufd: &VcpuFd, msrs: &KvmMsrs) -> Result { + unsafe { + ioctl_with_ref(cpufd.raw(), KVM_SET_MSRS, msrs) + .map_err(|e| ioctl_err("KVM_SET_MSRS", e)) + } +} + +pub fn kvm_run(cpufd: &VcpuFd) -> Result { + unsafe { + ioctl_with_val(cpufd.raw(), KVM_RUN, 0) + .map_err(|e| ioctl_err("KVM_RUN", e)) + } +} + +pub fn ioctl_err(ioctl_name: &'static str, e: Error) -> Error { + if e.is_interrupted() { + e + } else { + Error::new(ErrorKind::IoctlFailed(ioctl_name), e) + } +} + diff --git a/src/kvm/mod.rs b/src/kvm/mod.rs new file mode 100644 index 0000000..db5a9bd --- /dev/null +++ b/src/kvm/mod.rs @@ -0,0 +1,223 @@ +use std::os::unix::io::RawFd; +use std::sync::Arc; + +mod ioctl; + +use vm::{Result,Error,ErrorKind}; +pub use self::ioctl::{KvmCpuIdEntry,KvmLapicState, KvmSRegs, KvmRegs, KvmFpu, KvmMsrs, KvmSegment}; + +pub const KVM_CAP_IRQCHIP: u32 = 0; +pub const KVM_CAP_HLT: u32 = 1; +pub const KVM_CAP_USER_MEMORY: u32 = 3; +pub const KVM_CAP_SET_TSS_ADDR: u32 = 4; +pub const KVM_CAP_EXT_CPUID: u32 = 7; +pub const KVM_CAP_IRQ_ROUTING: u32 = 25; +pub const KVM_CAP_IRQ_INJECT_STATUS: u32 = 26; +pub const KVM_CAP_PIT2: u32 = 33; +pub const KVM_CAP_IOEVENTFD: u32 = 36; + +#[derive(Clone)] +pub struct Kvm { + sysfd: Arc, + vmfd: Arc, + vcpus: Vec, +} + +fn check_extensions(sysfd: &ioctl::SysFd, extensions: &[u32]) -> Result<()> { + for e in extensions { + if ioctl::kvm_check_extension(&sysfd, *e)? == 0 { + return Err(Error::from(ErrorKind::MissingRequiredExtension(*e))); + } + } + Ok(()) +} + +fn check_version(sysfd: &ioctl::SysFd) -> Result<()> { + if ioctl::kvm_get_api_version(&sysfd)? != 12 { + return Err(Error::from(ErrorKind::BadVersion)); + } + Ok(()) +} + +impl Kvm { + pub fn open(required_extensions: &[u32]) -> Result { + let sysfd = ioctl::SysFd::open()?; + + check_version(&sysfd)?; + check_extensions(&sysfd, &required_extensions)?; + + let vmfd= ioctl::kvm_create_vm(&sysfd) + .map_err(|_| Error::from(ErrorKind::CreateVmFailed))?; + + Ok(Kvm{ + sysfd: Arc::new(sysfd), + vmfd: Arc::new(vmfd), + vcpus: Vec::new(), + }) + } + + pub fn add_memory_region(&self, slot: usize, guest_address: u64, host_address: u64, size: usize) -> Result<()> { + let region = ioctl::KvmUserspaceMemoryRegion::new(slot as u32, guest_address, host_address, size as u64); + ioctl::kvm_set_user_memory_region(&self.vmfd, ®ion)?; + Ok(()) + } + + pub fn create_pit2(&self) -> Result<()> { + let pit_config = ioctl::KvmPitConfig::new(0); + ioctl::kvm_create_pit2(&self.vmfd, &pit_config)?; + Ok(()) + } + + pub fn create_irqchip(&self) -> Result<()> { + ioctl::kvm_create_irqchip(&self.vmfd)?; + Ok(()) + } + + pub fn set_tss_addr(&self, addr: u32) -> Result<()> { + ioctl::kvm_set_tss_addr(&self.vmfd, addr)?; + Ok(()) + } + + pub fn irq_line(&self, irq: u32, level: u32) -> Result<()> { + let irq_level = ioctl::KvmIrqLevel::new(irq, level); + ioctl::kvm_irq_line(&self.vmfd, &irq_level)?; + Ok(()) + } + + pub fn irqfd(&self, fd: u32, gsi: u32) -> Result<()> { + let irqfd = ioctl::KvmIrqFd::new(fd, gsi); + ioctl::kvm_irqfd(&self.vmfd, &irqfd)?; + Ok(()) + } + + pub fn ioeventfd_add(&self, address: u64, fd: RawFd) -> Result<()> { + // XXX check for zero length capability + let ioeventfd = ioctl::KvmIoEventFd::new_with_addr_fd(address, fd); + ioctl::kvm_ioeventfd(&self.vmfd, &ioeventfd)?; + Ok(()) + } + + pub fn ioeventfd_del(&self, address: u64, fd: RawFd) -> Result<()> { + let mut ioeventfd = ioctl::KvmIoEventFd::new_with_addr_fd(address, fd); + ioeventfd.set_deassign(); + ioctl::kvm_ioeventfd(&self.vmfd, &ioeventfd)?; + Ok(()) + } + + pub fn create_vcpus(&mut self, ncpus: usize) -> Result<()> { + for id in 0..ncpus { + let vcpu = self.new_vcpu(id)?; + vcpu.setup_lapic()?; + self.vcpus.push(vcpu); + } + Ok(()) + } + + fn new_vcpu(&self, id: usize) -> Result { + let cpufd = ioctl::kvm_create_vcpu(&self.vmfd, id as u32)?; + Ok(KvmVcpu::new(id, Arc::new(cpufd), self.sysfd.clone())) + } + + pub fn get_vcpus(&self) -> Vec { + self.vcpus.clone() + } +} + +#[derive(Clone)] +pub struct KvmVcpu { + id: usize, + cpufd: Arc, + sysfd: Arc, + +} + +const APIC_MODE_EXTINT: u8 = 0x7; +const APIC_MODE_NMI: u8 = 0x4; +const APIC_LVT_LINT0_OFFSET: usize = 0x350; +const APIC_LVT_LINT1_OFFSET: usize = 0x360; + +impl KvmVcpu { + fn new(id: usize, cpufd: Arc, sysfd: Arc) -> KvmVcpu { + KvmVcpu { id, cpufd, sysfd } + } + + pub fn raw_fd(&self) -> RawFd { + self.cpufd.raw() + } + + pub fn get_supported_cpuid(&self) -> Result> { + let mut cpuid = ioctl::KvmCpuId2::new(); + ioctl::kvm_get_supported_cpuid(&self.sysfd, &mut cpuid)?; + Ok(cpuid.get_entries()) + } + + pub fn set_cpuid2(&self, entries: Vec) -> Result<()> { + let cpuid = ioctl::KvmCpuId2::new_from_entries(entries); + ioctl::kvm_set_cpuid2(&self.cpufd, &cpuid)?; + Ok(()) + } + + pub fn get_lapic(&self) -> Result { + let mut lapic = KvmLapicState::new(); + ioctl::kvm_get_lapic(&self.cpufd, &mut lapic)?; + Ok(lapic) + } + + pub fn set_lapic(&self, lapic_state: &KvmLapicState) -> Result<()> { + ioctl::kvm_set_lapic(&self.cpufd, &lapic_state)?; + Ok(()) + } + + pub fn get_sregs(&self) -> Result { + let mut sregs = KvmSRegs::new(); + ioctl::kvm_get_sregs(&self.cpufd, &mut sregs)?; + Ok(sregs) + } + + pub fn set_sregs(&self, sregs: &KvmSRegs) -> Result<()> { + ioctl::kvm_set_sregs(&self.cpufd, &sregs)?; + Ok(()) + } + + pub fn get_regs(&self) -> Result { + let mut regs = KvmRegs::new(); + ioctl::kvm_get_regs(&self.cpufd, &mut regs)?; + Ok(regs) + } + + pub fn set_regs(&self, regs: &KvmRegs) -> Result<()> { + ioctl::kvm_set_regs(&self.cpufd, regs)?; + Ok(()) + } + + pub fn run(&self) -> Result<()> { + ioctl::kvm_run(&self.cpufd)?; + Ok(()) + } + + pub fn set_fpu(&self, fpu: &KvmFpu) -> Result<()> { + ioctl::kvm_set_fpu(&self.cpufd, &fpu)?; + Ok(()) + } + + pub fn set_msrs(&self, msrs: &KvmMsrs) -> Result<()> { + ioctl::kvm_set_msrs(&self.cpufd, &msrs)?; + Ok(()) + } + + pub fn get_vcpu_mmap_size(&self) -> Result { + Ok(ioctl::kvm_get_vcpu_mmap_size(&self.sysfd)? as usize) + } + + pub fn setup_lapic(&self) -> Result<()> { + let mut lapic = self.get_lapic()?; + // delivery mode + lapic.regs[APIC_LVT_LINT0_OFFSET + 1] &= 0xF8; + lapic.regs[APIC_LVT_LINT0_OFFSET + 1] |= APIC_MODE_EXTINT; + lapic.regs[APIC_LVT_LINT1_OFFSET + 1] &= 0xF8; + lapic.regs[APIC_LVT_LINT1_OFFSET + 1] |= APIC_MODE_NMI; + self.set_lapic(&lapic)?; + Ok(()) + } +} + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..32d10e3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,63 @@ +#![allow(non_snake_case)] + +extern crate libc; +extern crate byteorder; +extern crate termios; + + +mod vm; +mod memory; +#[macro_use] +mod system; +mod devices; +mod kvm; +mod virtio; + + +use std::env; +use std::path::{Path,PathBuf}; +fn main() { + + let mut config = vm::VmConfig::new(); + config.ram_size_megs(1024); + match find_kernel() { + Some(path) => config.kernel_path(&path), + None => { println!("Could not find kernel"); return; } + } + match find_init() { + Some(path) => config.init_path(&path), + None => { println!("Could not find init"); return; } + } + match vm::Vm::open(config) { + Ok(vm) => { + vm.start().unwrap(); + }, + Err(e) => println!("error :( {}", e) + } +} + +fn find_init() -> Option { + match find_kernel_base() { + Some(buf) => Some(buf.join("init/init")), + None => None, + } +} +fn find_kernel() -> Option { + match find_kernel_base() { + Some(buf) => Some(buf.join("build/linux-4.9.56/vmlinux")), + None => None, + } +} + +fn find_kernel_base() -> Option { + let mut cwd = env::current_dir().unwrap(); + if try_kernel_base(&cwd) { + cwd.push("kernel"); + return Some(cwd); + } + None +} + +fn try_kernel_base(path: &Path) -> bool { + path.join("kernel/build/linux-4.9.56/vmlinux").exists() +} diff --git a/src/memory/address.rs b/src/memory/address.rs new file mode 100644 index 0000000..620953f --- /dev/null +++ b/src/memory/address.rs @@ -0,0 +1,79 @@ +use std::fmt; + +#[derive(Copy,Clone,Debug)] +pub struct AddressRange { + start: u64, // inclusive + end: u64, // exclusive +} + +impl fmt::Display for AddressRange { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "AddressRange(0x{:x} - 0x{:x}) [size: {}]", self.start, self.end - 1, self.size()) + } +} + +impl AddressRange { + pub fn checked_new(base: u64, size: usize) -> Option { + match base.checked_add(size as u64) { + Some(end) if size > 0 => Some(AddressRange{ start:base, end }), + _ => None, + } + } + + pub fn new(base: u64, size: usize) -> AddressRange { + assert!(size > 0, "cannot construct address range with size = 0"); + AddressRange::checked_new(base, size) + .expect(format!("Address range overflows base: {:x} size: {}", base, size).as_str()) + } + + pub fn contains_address(&self, address: u64) -> bool { + address >= self.start && address < self.end + } + + pub fn contains(&self, address: u64, size: usize) -> bool { + assert!(size > 0, "size cannot be 0, use contains_address() for single address test"); + match address.checked_add(size as u64) { + Some(end) => self.contains_address(address) && self.contains_address(end - 1), + None => false, + } + } + + pub fn checked_offset_of(&self, address: u64) -> Option { + if self.contains_address(address) { + Some((address - self.start) as usize) + } else { + None + } + } + + pub fn offset_of(&self, address: u64) -> usize { + self.checked_offset_of(address).expect("range does not contain address for call to offset_into()") + } + + pub fn checked_offset_into(&self, offset: usize) -> Option { + match self.start.checked_add(offset as u64) { + Some(addr) if self.contains_address(addr) => Some(addr), + _ => None, + } + } + + pub fn subrange(&self, offset: usize, size: usize) -> Option { + match self.checked_offset_into(offset) { + Some(base) if self.contains(base, size) => Some(AddressRange::new(base, size)), + _ => None, + } + } + + pub fn base(&self) -> u64 { self.start } + + pub fn size(&self) -> usize { (self.end - self.start) as usize } + + pub fn is_base2_sized(&self) -> bool { + let sz = self.size(); + sz & (sz - 1) == 0 + } + + pub fn is_naturally_aligned(&self) -> bool { + self.is_base2_sized() && (self.base() % (self.size() as u64) == 0) + } +} diff --git a/src/memory/mmap.rs b/src/memory/mmap.rs new file mode 100644 index 0000000..e758a26 --- /dev/null +++ b/src/memory/mmap.rs @@ -0,0 +1,172 @@ +use libc; +use std::ptr; +use std::slice; +use std::mem; +use std::io::Write; +use std::os::unix::io::RawFd; + +use vm::{Result,Error,ErrorKind}; + +pub struct Mapping { + ptr: *mut u8, + size: usize, +} + +/// Marks types that can be passed to `write_int` and returned from `read_int` +pub unsafe trait Serializable: Copy+Send+Sync {} + +unsafe impl Serializable for u8 {} +unsafe impl Serializable for u16 {} +unsafe impl Serializable for u32 {} +unsafe impl Serializable for u64 {} + +unsafe impl Send for Mapping {} +unsafe impl Sync for Mapping {} + +/// A block of memory returned from the mmap() system call. Provides safe access to the raw +/// memory region. +impl Mapping { + + /// Creates a new anonymous mapping of `size` bytes. + /// + /// # Errors + /// Returns [`Err`] if the `mmap()` system call fails and returns an `Error` representing + /// the system error which occurred. + /// + pub fn new(size: usize) -> Result { + Mapping::_new(size,libc::MAP_ANONYMOUS | libc::MAP_SHARED | libc::MAP_NORESERVE, -1) + } + + /// Creates a new mapping of `size` bytes from the object referenced by file descriptor `fd` + /// + /// # Errors + /// Returns [`Err`] if the `mmap()` system call fails and returns an `Error` representing + /// the system error which occurred. + /// + pub fn new_from_fd(fd: RawFd, size: usize) -> Result { + Mapping::_new(size, libc::MAP_SHARED, fd) + } + + + fn _new(size: usize, flags: libc::c_int, fd: RawFd) -> Result { + let p = unsafe { mmap_allocate(size, flags, fd)? }; + Ok(Mapping { + ptr: p, + size + }) + } + + /// Ensure that `offset` is not larger than this allocation. + /// + /// # Errors + /// + /// Returns [`Err`] of kind `InvalidMappingOffset` if passed an + /// illegal `offset` + /// + fn check_offset(&self, offset: usize) -> Result<()> { + if offset > self.size { + Err(Error::from(ErrorKind::InvalidMappingOffset(offset))) + } else { + Ok(()) + } + } + + /// Return the pointer address of this allocation. + pub fn address(&self) -> u64 { + self.ptr as u64 + } + + /// Read and return an integer value in native byte order from `offset` into the memory allocation + /// + /// # Errors + /// Returns [`Err`] of kind `InvalidMappingOffset` if passed an + /// illegal `offset` + /// + pub fn read_int(&self, offset: usize) -> Result { + self.check_offset(offset + mem::size_of::())?; + unsafe { + Ok(ptr::read_volatile(&self.as_slice()[offset..] as *const _ as *const T)) + } + } + + /// Write the integer `val` in native byte order at `offset` into the memory allocation + /// + /// # Errors + /// Returns [`Err`] of kind `InvalidMappingOffset` if passed an + /// illegal `offset` + /// + pub fn write_int(&self, offset: usize, val: T) -> Result<()> { + self.check_offset(offset + mem::size_of::())?; + unsafe { ptr::write_volatile(&mut self.as_mut_slice()[offset..] as *mut _ as *mut T, val); } + Ok(()) + } + + pub fn write_bytes(&self, offset: usize, bytes: &[u8]) -> Result<()> { + self.check_offset(offset + bytes.len())?; + unsafe { + let mut slice: &mut [u8] = &mut self.as_mut_slice()[offset..]; + slice.write_all(bytes).map_err(|_| Error::from(ErrorKind::InvalidMappingOffset(offset))) + } + } + + pub fn read_bytes(&self, offset: usize, mut bytes: &mut [u8]) -> Result<()> { + self.check_offset(offset + bytes.len())?; + unsafe { + let slice: &[u8] = &self.as_slice()[offset..]; + bytes.write(slice).unwrap(); + Ok(()) + } + } + + pub fn slice(&self, offset: usize, size: usize) -> Result<&[u8]> { + self.check_offset(offset + size)?; + unsafe { + let x = &self.as_slice()[offset..offset+size]; + Ok(x) + } + } + + pub fn mut_slice(&self, offset: usize, size: usize) -> Result<&mut [u8]> { + self.check_offset(offset + size)?; + unsafe { + let x = &mut self.as_mut_slice()[offset..offset+size]; + Ok(x) + } + } + + #[allow(dead_code)] + pub fn set_mergeable(&self) -> Result<()> { + unsafe { + if libc::madvise(self.ptr as *mut libc::c_void, self.size, libc::MADV_MERGEABLE) == -1 { + return Err(Error::from_last_errno()); + } + } + Ok(()) + } + + unsafe fn as_slice(&self) -> &[u8] { + slice::from_raw_parts(self.ptr, self.size) + } + unsafe fn as_mut_slice(&self) -> &mut [u8] { + slice::from_raw_parts_mut(self.ptr, self.size) + } +} + +impl Drop for Mapping { + fn drop(&mut self) { + unsafe { + libc::munmap(self.ptr as *mut libc::c_void, self.size); + } + } +} + +unsafe fn mmap_allocate(size: usize, flags: libc::c_int, fd: libc::c_int) -> Result<*mut u8> { + let p = libc::mmap(ptr::null_mut(), + size, libc::PROT_READ|libc::PROT_WRITE, + flags, fd, 0); + + if p.is_null() || p == libc::MAP_FAILED { + return Err(Error::from_last_errno()); + } + Ok(p as *mut u8) +} \ No newline at end of file diff --git a/src/memory/mod.rs b/src/memory/mod.rs new file mode 100644 index 0000000..972fa69 --- /dev/null +++ b/src/memory/mod.rs @@ -0,0 +1,13 @@ +mod ram; +mod mmap; +mod address; + +pub use self::address::AddressRange; +pub use self::mmap::Mapping; +pub use self::ram::GuestRam; +pub use self::ram::{PCI_MMIO_RESERVED_BASE,HIMEM_BASE}; + +pub const KVM_KERNEL_LOAD_ADDRESS: u64 = 0x1000000; +pub const KERNEL_CMDLINE_ADDRESS: u64 = 0x20000; +pub const KERNEL_ZERO_PAGE: u64 = 0x7000; + diff --git a/src/memory/ram.rs b/src/memory/ram.rs new file mode 100644 index 0000000..517d99f --- /dev/null +++ b/src/memory/ram.rs @@ -0,0 +1,150 @@ +use std::sync::Arc; +use std::cmp; +use std::mem; + +use memory::Mapping; +use memory::mmap::Serializable; +use memory::AddressRange; + +use kvm::Kvm; +use vm::{Result,Error,ErrorKind}; + +pub const HIMEM_BASE: u64 = (1 << 32); +pub const PCI_MMIO_RESERVED_SIZE: usize = (512 << 20); +pub const PCI_MMIO_RESERVED_BASE: u64 = HIMEM_BASE - PCI_MMIO_RESERVED_SIZE as u64; + +#[derive(Clone)] +pub struct GuestRam { + ram_size: usize, + regions: Arc>, +} + +impl GuestRam { + pub fn new(ram_size: usize, kvm: &Kvm) -> Result { + Ok(GuestRam { + ram_size, + regions: Arc::new(create_regions(kvm, ram_size)?), + }) + } + + pub fn ram_size(&self) -> usize { + self.ram_size + } + + pub fn write_bytes(&self, guest_address: u64, bytes: &[u8]) -> Result<()> { + let region = self.find_region(guest_address, bytes.len())?; + region.write_bytes(guest_address, bytes) + } + + pub fn read_bytes(&self, guest_address: u64, bytes: &mut [u8]) -> Result<()> { + let region = self.find_region(guest_address, bytes.len())?; + region.read_bytes(guest_address, bytes) + } + + #[allow(dead_code)] + pub fn slice(&self, guest_address: u64, size: usize) -> Result<&[u8]> { + let region = self.find_region(guest_address, size)?; + region.slice(guest_address, size) + } + + pub fn mut_slice(&self, guest_address: u64, size: usize) -> Result<&mut[u8]> { + let region = self.find_region(guest_address, size)?; + region.mut_slice(guest_address, size) + } + + pub fn write_int(&self, guest_address: u64, val: T) -> Result<()> { + let region = self.find_region(guest_address, mem::size_of::())?; + region.write_int(guest_address, val) + } + pub fn read_int(&self, guest_address: u64) -> Result { + let region = self.find_region(guest_address, mem::size_of::())?; + region.read_int(guest_address) + } + + pub fn is_valid_range(&self, guest_address: u64, size: usize) -> bool { + self.find_region(guest_address, size).is_ok() + } + + fn find_region(&self, guest_address: u64, size: usize) -> Result<&MemoryRegion> { + self.regions.iter() + .find(|r| r.contains(guest_address, size)) + .ok_or_else(|| Error::from(ErrorKind::InvalidAddress(guest_address))) + } +} + +fn add_region(regions: &mut Vec, base: u64, size: usize, kvm: &Kvm) -> Result<()> { + let slot = regions.len(); + let mr = MemoryRegion::new(base, size)?; + kvm.add_memory_region(slot, base, mr.mapping.address(), size) + .map_err(|e| Error::new(ErrorKind::RegisterMemoryFailed, e))?; + regions.push(mr); + Ok(()) +} + +fn create_regions(kvm: &Kvm, ram_size: usize) -> Result> { + let mut regions = Vec::new(); + + let lowmem_sz = cmp::min(ram_size, PCI_MMIO_RESERVED_BASE as usize); + add_region(&mut regions, 0, lowmem_sz, &kvm)?; + + if lowmem_sz < ram_size { + let himem_sz = ram_size - lowmem_sz; + add_region(&mut regions, HIMEM_BASE, himem_sz, &kvm)?; + } + + Ok(regions) +} + +struct MemoryRegion { + guest_range: AddressRange, + mapping: Mapping, +} + +impl MemoryRegion { + fn new(guest_base: u64, size: usize) -> Result { + Ok(MemoryRegion{ + guest_range: AddressRange::new(guest_base, size), + mapping: Mapping::new(size)?, + }) + } + + fn contains(&self, guest_addr: u64, size: usize) -> bool { self.guest_range.contains(guest_addr, size) } + + fn checked_offset(&self, guest_addr: u64, size: usize) -> Result { + if self.contains(guest_addr, size) { + Ok(self.guest_range.offset_of(guest_addr)) + } else { + Err(Error::from(ErrorKind::InvalidAddress(guest_addr))) + } + } + + pub fn write_bytes(&self, guest_address: u64, bytes: &[u8]) -> Result<()> { + let offset = self.checked_offset(guest_address, bytes.len())?; + self.mapping.write_bytes(offset, bytes) + } + + pub fn read_bytes(&self, guest_address: u64, bytes: &mut [u8]) -> Result<()> { + let offset = self.checked_offset(guest_address, bytes.len())?; + self.mapping.read_bytes(offset, bytes) + } + + pub fn slice(&self, guest_address: u64, size: usize) -> Result<&[u8]> { + let offset = self.checked_offset(guest_address, size)?; + self.mapping.slice(offset, size) + } + + pub fn mut_slice(&self, guest_address: u64, size: usize) -> Result<&mut [u8]> { + let offset = self.checked_offset(guest_address, size)?; + self.mapping.mut_slice(offset, size) + } + + pub fn write_int(&self, guest_address: u64, val: T) -> Result<()> { + let offset = self.checked_offset(guest_address, mem::size_of::())?; + self.mapping.write_int(offset, val) + } + + pub fn read_int(&self, guest_address: u64) -> Result { + let offset = self.checked_offset(guest_address, mem::size_of::())?; + self.mapping.read_int(offset) + } +} diff --git a/src/system/ioctl.rs b/src/system/ioctl.rs new file mode 100644 index 0000000..eb3624e --- /dev/null +++ b/src/system/ioctl.rs @@ -0,0 +1,71 @@ +use libc::{self, c_ulong, c_void}; +use std::os::unix::io::RawFd; +use vm::{Error,Result}; + +pub const IOC_SIZEBITS: u64 = 14; +pub const IOC_DIRBITS: u64 = 2; + +pub const IOC_NONE: u64 = 0; +pub const IOC_READ: u64 = 2; +pub const IOC_WRITE: u64 = 1; +pub const IOC_RDWR: u64 = IOC_READ | IOC_WRITE; +pub const IOC_NRBITS: u64 = 8; +pub const IOC_TYPEBITS: u64 = 8; +pub const IOC_NRSHIFT: u64 = 0; +pub const IOC_TYPESHIFT: u64 = IOC_NRSHIFT + IOC_NRBITS; +pub const IOC_SIZESHIFT: u64 = IOC_TYPESHIFT + IOC_TYPEBITS; +pub const IOC_DIRSHIFT: u64 = IOC_SIZESHIFT + IOC_SIZEBITS; + +pub const IOC_NRMASK: u64 = (1 << IOC_NRBITS) - 1; +pub const IOC_TYPEMASK: u64 = (1 << IOC_TYPEBITS) - 1; +pub const IOC_SIZEMASK: u64 = (1 << IOC_SIZEBITS) - 1; +pub const IOC_DIRMASK: u64 = (1 << IOC_DIRBITS) - 1; + +macro_rules! ioc { + ($dir:expr, $ty:expr, $nr:expr, $sz:expr) => ( + ((($dir as u64 & $crate::system::ioctl::IOC_DIRMASK) << $crate::system::ioctl::IOC_DIRSHIFT) | + (($ty as u64 & $crate::system::ioctl::IOC_TYPEMASK) << $crate::system::ioctl::IOC_TYPESHIFT) | + (($nr as u64 & $crate::system::ioctl::IOC_NRMASK) << $crate::system::ioctl::IOC_NRSHIFT) | + (($sz as u64 & $crate::system::ioctl::IOC_SIZEMASK) << $crate::system::ioctl::IOC_SIZESHIFT)) as c_ulong) +} + +macro_rules! io { + ($ty:expr, $nr:expr) => (ioc!($crate::system::ioctl::IOC_NONE, $ty, $nr, 0)) +} + +macro_rules! iow { + ($ty:expr, $nr:expr, $sz:expr) => (ioc!($crate::system::ioctl::IOC_WRITE, $ty, $nr, $sz)) +} + +macro_rules! ior { + ($ty:expr, $nr:expr, $sz:expr) => (ioc!($crate::system::ioctl::IOC_READ, $ty, $nr, $sz)) +} + +macro_rules! iorw { + ($ty:expr, $nr:expr, $sz:expr) => (ioc!($crate::system::ioctl::IOC_RDWR, $ty, $nr, $sz)) +} + +pub unsafe fn ioctl_with_val(fd: RawFd, request: c_ulong, val: c_ulong) -> Result { + let ret = libc::ioctl(fd, request, val); + if ret < 0 { + return Err(Error::from_last_errno()); + } + Ok(ret as u32) +} + +pub unsafe fn ioctl_with_ref(fd: RawFd, request: c_ulong, arg: &T) -> Result { + let ret = libc::ioctl(fd, request, arg as *const T as *const c_void); + if ret < 0 { + return Err(Error::from_last_errno()); + } + Ok(ret as u32) +} + +pub unsafe fn ioctl_with_mut_ref(fd: RawFd, request: c_ulong, arg: &mut T) -> Result { + let ret = libc::ioctl(fd, request, arg as *mut T as *mut c_void); + if ret < 0 { + return Err(Error::from_last_errno()); + } + Ok(ret as u32) +} + diff --git a/src/system/mod.rs b/src/system/mod.rs new file mode 100644 index 0000000..819765a --- /dev/null +++ b/src/system/mod.rs @@ -0,0 +1,2 @@ +#[macro_use] +pub mod ioctl; diff --git a/src/virtio/bus.rs b/src/virtio/bus.rs new file mode 100644 index 0000000..f664488 --- /dev/null +++ b/src/virtio/bus.rs @@ -0,0 +1,149 @@ +use std::sync::{Arc,RwLock}; +use vm::io::IoDispatcher; +use kvm::Kvm; +use memory::{GuestRam,AddressRange}; +use super::{VirtioDevice,VirtioDeviceOps,PciIrq}; +use super::consts::*; +use super::pci::PciBus; +use vm::Result; + + +pub struct VirtioBus { + kvm: Kvm, + memory: GuestRam, + io_dispatcher: Arc, + pci_bus: Arc>, + devices: Vec>>, +} + +impl VirtioBus { + pub fn new(memory: GuestRam, io_dispatcher: Arc, kvm: Kvm) -> VirtioBus { + VirtioBus { + kvm, + memory, + io_dispatcher: io_dispatcher.clone(), + pci_bus: PciBus::new(&io_dispatcher), + devices: Vec::new(), + } + } + + pub fn new_virtio_device(&mut self, device_type: u16, ops: Arc>) -> VirtioDeviceConfig { + VirtioDeviceConfig::new(self, device_type, ops) + } + + pub fn pci_irqs(&self) -> Vec { + self.pci_bus.read().unwrap().pci_irqs() + } +} + +pub struct VirtioDeviceConfig<'a> { + virtio_bus: &'a mut VirtioBus, + device_type: u16, + irq: u8, + kvm: Kvm, + ops: Arc>, + mmio: AddressRange, + num_queues: usize, + config_size: usize, + device_class: u16, + features: u64, + +} + +impl <'a> VirtioDeviceConfig<'a> { + fn new(virtio_bus: &mut VirtioBus, device_type: u16, ops: Arc>) -> VirtioDeviceConfig { + let kvm = virtio_bus.kvm.clone(); + let mmio = virtio_bus.pci_bus.write().unwrap().allocate_mmio_space(VIRTIO_MMIO_AREA_SIZE); + VirtioDeviceConfig { + virtio_bus, + device_type, + irq: 0, + kvm, + ops, + mmio, + num_queues: 0, + config_size: 0, + features: 0, + device_class: 0x0880, + } + } + + pub fn kvm(&self) -> &Kvm { &self.kvm } + + pub fn ops(&self) -> Arc> { + self.ops.clone() + } + pub fn irq(&self) -> u8 { self.irq } + + pub fn common_cfg_mmio(&self) -> AddressRange { + self.mmio.subrange(VIRTIO_MMIO_OFFSET_COMMON_CFG, VIRTIO_MMIO_COMMON_CFG_SIZE).unwrap() + } + + pub fn notify_mmio(&self) -> AddressRange { + self.mmio.subrange(VIRTIO_MMIO_OFFSET_NOTIFY, VIRTIO_MMIO_NOTIFY_SIZE).unwrap() + } + + pub fn isr_mmio(&self) -> AddressRange { + self.mmio.subrange(VIRTIO_MMIO_OFFSET_ISR, VIRTIO_MMIO_ISR_SIZE).unwrap() + } + + pub fn device_cfg_mmio(&self) -> Option { + if self.config_size > 0 { + Some(self.mmio.subrange(VIRTIO_MMIO_OFFSET_DEV_CFG, self.config_size).unwrap()) + } else { + None + } + } + + pub fn feature_bits(&self) -> u64 { + self.features + } + + pub fn num_queues(&self) -> usize { + self.num_queues + } + + #[allow(dead_code)] + pub fn config_size(&self) -> usize { + self.config_size + } + + pub fn set_num_queues(&mut self, n: usize) -> &'a mut VirtioDeviceConfig { + self.num_queues = n; + self + } + + pub fn set_config_size(&mut self, sz: usize) -> &'a mut VirtioDeviceConfig { + self.config_size = sz; + self + } + + pub fn set_device_class(&mut self, cl: u16) -> &'a mut VirtioDeviceConfig { + self.device_class = cl; + self + } + + pub fn set_features(&mut self, features: u64) -> &'a mut VirtioDeviceConfig { + self.features = features; + self + } + + pub fn register(&mut self) -> Result<()> { + self.create_pci_device(); + self.features |= VIRTIO_F_VERSION_1; + //self.features |= VIRTIO_F_EVENT_IDX; + let dev = VirtioDevice::new(self.virtio_bus.memory.clone(), &self)?; + self.virtio_bus.io_dispatcher.register_mmio(self.mmio, dev.clone()); + self.virtio_bus.devices.push(dev); + Ok(()) + } + + fn create_pci_device(&mut self) { + let mut pci_bus = self.virtio_bus.pci_bus.write().unwrap(); + let mut pci = pci_bus.create_device(PCI_VENDOR_ID_REDHAT, PCI_VIRTIO_DEVICE_ID_BASE + self.device_type, self.device_class); + pci.add_virtio_caps(self.config_size); + pci.set_mmio_bar(VIRTIO_MMIO_BAR, self.mmio); + self.irq = pci.get_irq(); + pci_bus.store_device(pci); + } +} \ No newline at end of file diff --git a/src/virtio/chain.rs b/src/virtio/chain.rs new file mode 100644 index 0000000..769c5e9 --- /dev/null +++ b/src/virtio/chain.rs @@ -0,0 +1,269 @@ + +use std::io::{self,Read,Write}; + +use memory::GuestRam; +use super::VirtQueue; +use super::vring::Descriptor; + +pub struct Chain { + + memory: GuestRam, + + vq: VirtQueue, + + /// Number of remaining descriptors allowed in this chain. + ttl: u16, + + /// Current descriptor or `None` if at end of chain + current: Option, + + /// Offset for read/write into current descriptor + offset: usize, + + /// Saved head index to place in used ring. Set to `None` + /// after writing to used ring. + head_idx: Option, + + /// Number of bytes written into writeable descriptors + /// in this chain. Will be written into used ring later. + wlen: usize, +} + + +impl Chain { + pub fn new(memory: GuestRam, vq: VirtQueue, head: u16, ttl: u16) -> Chain { + let first = vq.load_descriptor(head); + Chain { + memory, + vq, ttl, head_idx: Some(head), + current: first, + offset: 0, wlen: 0, + } + } + + /// Applies a function to the current descriptor (if `Some`) or + /// returns default parameter `d` (if `None`). + pub fn with_current_descriptor(&self, d: U, f: F) -> U + where F: FnOnce(&Descriptor) -> U { + match self.current { + Some(ref desc) => f(desc), + None => d, + } + } + + /// Load and return next descriptor from chain. + /// + /// If `self.current` + /// + /// 1) holds a descriptor (`self.current.is_some()`) + /// 2) that descriptor has a next field (`desc.has_next()`) + /// 3) time-to-live is not zero (`self.ttl > 0`) + /// + /// then load and return the descriptor pointed to by the current + /// descriptor. Returns `None` otherwise. + /// + fn next_desc(&self) -> Option { + self.with_current_descriptor(None, |desc| { + if desc.has_next() && self.ttl > 0 { + self.vq.load_descriptor(desc.next) + } else { + None + } + }) + } + + /// Load next descriptor in chain into `self.current`. + /// + /// Set `self.current` to the next descriptor in chain or `None` if + /// at end of chain. + /// + pub fn load_next_descriptor(&mut self) { + self.current = self.next_desc(); + // Only decrement ttl if a new descriptor was loaded + if self.current.is_some() { + self.ttl -= 1; + } + self.offset = 0; + } + + /// + /// Return `true` if current descriptor exists and is readable, otherwise + /// `false`. + /// + pub fn is_current_readable(&self) -> bool { + self.with_current_descriptor(false, |desc| !desc.is_write()) + } + + /// + /// If `current` is a writeable descriptor, keep loading new descriptors until + /// a readable descriptor is found or end of chain is reached. After this + /// call `current` will either be a readable descriptor or `None` if the + /// end of chain was reached. + /// + pub fn skip_readable(&mut self) { + while self.is_current_readable() { + self.load_next_descriptor(); + } + } + + /// Return `true` if the end of the descriptor chain has been reached. + /// + /// When at end of chain `self.current` is `None`. + pub fn is_end_of_chain(&self) -> bool { + self.current.is_none() + } + + /// + /// Length field of current descriptor is returned or 0 if + /// at end of chain. + /// + fn current_size(&self) -> usize { + self.with_current_descriptor(0, |desc| desc.len as usize) + } + + /// + /// Increment `self.offset` with the number of bytes + /// read or written from `current` descriptor and + /// load next descriptor if `current` descriptor + /// has been fully consumed. + /// + fn inc_offset(&mut self, sz: usize) { + self.offset += sz; + if self.offset >= self.current_size() { + self.load_next_descriptor(); + } + } + + /// + /// Read from the `current` readable descriptor and return + /// the number of bytes read. + /// + /// If this read exhausts the `current` descriptor then the + /// next descriptor in chain will be loaded into `current`. + /// + /// Assumes that current is a readable descriptor so caller must + /// call `self.is_current_readable()` before calling this. + /// + fn read_current(&mut self, bytes: &mut[u8]) -> usize { + assert!(self.is_current_readable()); + + let nread = self.with_current_descriptor(0, |desc| { + desc.read_from(&self.memory, self.offset, bytes) + }); + self.inc_offset(nread); + nread + } + + /// + /// Write into the `current` writeable descriptor if it exists + /// and return the number of bytes read or 0 if at end of chain. + /// + /// If this write exausts the `current` descriptor then the + /// next descriptor in chain will be loaded into `current` + /// + /// Assumes that `current` is a writeable descriptor or `None` + /// so caller must call `self.skip_readable()` before calling this. + /// + fn write_current(&mut self, bytes: &[u8]) -> usize { + assert!(!self.is_current_readable()); + let sz = self.with_current_descriptor(0, |desc| { + desc.write_to(&self.memory, self.offset, bytes) + }); + self.inc_offset(sz); + sz + } + + /// + /// Write this chain head index (`self.head_idx`) and bytes written (`self.wlen`) + /// into used ring. Consumes `self.head_idx` so that used ring cannot + /// accidentally be written more than once. Since we have returned this + /// chain to the guest, it is no longer valid to access any descriptors in + /// this chain so `self.current` is set to `None`. + /// + pub fn flush_chain(&mut self) { + match self.head_idx { + Some(idx) => self.vq.put_used(idx, self.wlen as u32), + None => (), + } + self.current = None; + self.head_idx = None; + } + + pub fn current_write_address(&mut self, size: usize) -> Option { + self.skip_readable(); + self.with_current_descriptor(None, |desc| { + if desc.len as usize - self.offset < size { + None + } else { + Some(desc.addr + self.offset as u64) + } + }) + } + + pub fn get_wlen(&self) -> usize { + self.wlen + } + + #[allow(dead_code)] + pub fn debug(&self) { + self.with_current_descriptor((), |desc| { + println!("offset: {} desc: {:?}", self.offset, desc); + }); + } + + pub fn copy_from_reader(&mut self, r: R, size: usize) -> io::Result { + self.skip_readable(); + assert!(!self.is_current_readable()); + + let res = self.with_current_descriptor(Ok(0usize), |desc| { + desc.write_from_reader(&self.memory, self.offset,r, size) + }); + if let Ok(nread) = res { + self.inc_offset(nread); + self.wlen += nread; + } + res + } + + /* + pub fn copy_to_writer(&mut self, w: W, size: usize) -> io::Result { + unimplemented!() + + } + */ + +} + +impl Drop for Chain { + fn drop(&mut self) { + self.flush_chain(); + } +} + +impl Read for Chain { + // nb: does not fail, but can read short + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut nread = 0usize; + while self.is_current_readable() && nread < buf.len() { + nread += self.read_current(&mut buf[nread..]); + } + Ok(nread) + } +} + +impl Write for Chain { + // nb: does not fail, but can write short + fn write(&mut self, buf: &[u8]) -> io::Result { + self.skip_readable(); + let mut nwrote = 0usize; + while !self.is_end_of_chain() && nwrote < buf.len() { + nwrote += self.write_current(&buf[nwrote..]); + } + self.wlen += nwrote; + Ok(nwrote) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/src/virtio/config.rs b/src/virtio/config.rs new file mode 100644 index 0000000..e789091 --- /dev/null +++ b/src/virtio/config.rs @@ -0,0 +1,132 @@ +use memory::GuestRam; +use std::sync::Arc; + +use vm::Result; + +use super::VirtQueue; +use super::eventfd::IoEventFd; +use super::vring::Vring; +use super::virtqueue::InterruptLine; +use super::bus::VirtioDeviceConfig; +use super::consts::DEFAULT_QUEUE_SIZE; + +/// +/// Manages a set of virtqueues during device intitialization. +/// +pub struct VirtQueueConfig { + num_queues: usize, + selected_queue: u16, + enabled_features: u64, + vrings: Vec, + interrupt: Arc, + events: Vec>, +} + +impl VirtQueueConfig { + pub fn new(memory: &GuestRam, dev_config: &VirtioDeviceConfig) -> Result { + Ok(VirtQueueConfig { + num_queues: dev_config.num_queues(), + selected_queue: 0, + enabled_features: 0, + vrings: create_vrings(memory,dev_config.num_queues()), + interrupt: InterruptLine::from_config(&dev_config)?, + events: create_ioeventfds(&dev_config)?, + }) + } + + pub fn isr_read(&self) -> u64 { + self.interrupt.isr_read() + } + + pub fn notify_config(&self) { + self.interrupt.notify_config(); + } + + + pub fn enable_features(&mut self, features: u64) { + self.enabled_features = features; + } + + pub fn reset(&mut self) { + self.selected_queue = 0; + let _ = self.interrupt.isr_read(); + for vr in &mut self.vrings { + vr.reset(); + } + } + + pub fn num_queues(&self) -> u16 { + self.num_queues as u16 + } + + pub fn selected_queue(&self) -> u16 { + self.selected_queue + } + + pub fn select_queue(&mut self, q: u16) { + self.selected_queue = q; + } + + pub fn with_vring(&self, d: U, f: F) -> U + where F: FnOnce(&Vring) -> U + { + match self.vrings.get(self.selected_queue as usize) { + Some(vr) => f(vr), + None => d, + } + } + + pub fn with_vring_mut(&mut self, f: F) + where F: FnOnce(&mut Vring) + { + match self.vrings.get_mut(self.selected_queue as usize) { + Some(vr) => if !vr.is_enabled() { f(vr) }, + None => (), + } + } + + pub fn vring_get_size(&self) -> u16 { self.with_vring(0, |vr| vr.size() ) } + pub fn vring_set_size(&mut self, sz: u16) { self.with_vring_mut(|vr| vr.set_size(sz)) } + pub fn vring_enable(&mut self) { self.with_vring_mut(|vr| vr.enable() ) } + pub fn vring_is_enabled(&self) -> bool { self.with_vring(false, |vr| vr.is_enabled() ) } + + pub fn notify(&self, vq: u16) { + match self.events.get(vq as usize) { + Some(ref ev) => ev.write(1).expect("ioeventfd write failed in notify"), + None => (), + } + } + + fn create_vq(&self, memory: &GuestRam, idx: usize) -> Result { + let vring = self.vrings[idx].clone(); + vring.validate()?; + Ok(VirtQueue::new(memory.clone(), vring, self.interrupt.clone(), self.events[idx].clone())) + } + + pub fn create_queues(&self, memory: &GuestRam) -> Result> { + let mut v = Vec::with_capacity(self.num_queues); + for i in 0..self.num_queues { + v.push(self.create_vq(memory, i)?); + } + Ok(v) + } +} + +fn create_ioeventfds(conf: &VirtioDeviceConfig) -> Result>> { + let mut v = Vec::with_capacity(conf.num_queues()); + let notify_base = conf.notify_mmio().base(); + + for i in 0..conf.num_queues() { + let evt = IoEventFd::new(conf.kvm(), notify_base + (4 * i as u64))?; + v.push(Arc::new(evt)); + } + Ok(v) +} + +fn create_vrings(memory: &GuestRam, n: usize) -> Vec { + let mut v = Vec::with_capacity(n); + for _ in 0..n { + v.push(Vring::new(memory.clone(), DEFAULT_QUEUE_SIZE)); + } + v +} diff --git a/src/virtio/consts.rs b/src/virtio/consts.rs new file mode 100644 index 0000000..d245d2a --- /dev/null +++ b/src/virtio/consts.rs @@ -0,0 +1,120 @@ + +// Maximum number of logical devices on a PCI bus + +pub const PCI_MAX_DEVICES: usize = 32; + +// IO Port addresses for PCI configuration access + +pub const PCI_CONFIG_ADDRESS: u16 = 0xcf8; +pub const PCI_CONFIG_DATA: u16 = 0xcfc; + +// Vendor specific PCI capabilities + +pub const PCI_CAP_ID_VENDOR: u8 = 0x09; + +pub const PCI_CONFIG_SPACE_SIZE: usize = 256; +pub const PCI_CAP_BASE_OFFSET: usize = 0x40; + +pub const PCI_VENDOR_ID: usize = 0x00; +pub const PCI_DEVICE_ID: usize = 0x02; +pub const PCI_COMMAND: usize = 0x04; +pub const PCI_COMMAND_IO: u16 = 0x01; +pub const PCI_COMMAND_MEMORY: u16 = 0x02; +pub const PCI_COMMAND_INTX_DISABLE: u16 = 0x400; +pub const PCI_STATUS: usize = 0x06; +pub const PCI_STATUS_CAP_LIST: u16 = 0x10; +pub const PCI_CLASS_REVISION: usize = 0x08; +pub const PCI_CLASS_DEVICE: usize = 0x0a; +pub const PCI_CACHE_LINE_SIZE: usize = 0x0c; +pub const PCI_LATENCY_TIMER: usize = 0x0d; + +pub const _PCI_SUBSYSTEM_VENDOR_ID: usize = 0x2c; +pub const PCI_SUBSYSTEM_ID: usize = 0x2e; +pub const PCI_CAPABILITY_LIST: usize = 0x34; +pub const PCI_INTERRUPT_LINE: usize = 0x3C; +pub const PCI_INTERRUPT_PIN: usize = 0x3D; + +// Virtio PCI capability types + +pub const VIRTIO_PCI_CAP_COMMON_CFG : u8 = 1; +pub const VIRTIO_PCI_CAP_NOTIFY_CFG : u8 = 2; +pub const VIRTIO_PCI_CAP_ISR_CFG : u8 = 3; +pub const VIRTIO_PCI_CAP_DEVICE_CFG : u8 = 4; + +// Indicates that no MSIX vector is configured + +pub const VIRTIO_NO_MSI_VECTOR: u16 = 0xFFFF; + +// Bar number 0 is used for Virtio MMIO area + +pub const VIRTIO_MMIO_BAR: usize = 0; + +// Virtio MMIO area is one page + +pub const VIRTIO_MMIO_AREA_SIZE: usize = 4096; + +// Offsets and sizes for each structure in MMIO area + +pub const VIRTIO_MMIO_OFFSET_COMMON_CFG : usize = 0; // Common configuration offset +pub const VIRTIO_MMIO_OFFSET_ISR : usize = 56; // ISR register offset +pub const VIRTIO_MMIO_OFFSET_NOTIFY : usize = 0x400; // Notify area offset +pub const VIRTIO_MMIO_OFFSET_DEV_CFG : usize = 0x800; // Device specific configuration offset + +pub const VIRTIO_MMIO_COMMON_CFG_SIZE: usize = 56; // Common configuration size +pub const VIRTIO_MMIO_NOTIFY_SIZE : usize = 0x400; // Notify area size +pub const VIRTIO_MMIO_ISR_SIZE : usize = 4; // ISR register size + +// Common configuration header offsets + +pub const VIRTIO_PCI_COMMON_DFSELECT : usize = 0; +pub const VIRTIO_PCI_COMMON_DF : usize = 4; +pub const VIRTIO_PCI_COMMON_GFSELECT : usize = 8; +pub const VIRTIO_PCI_COMMON_GF : usize = 12; +pub const VIRTIO_PCI_COMMON_MSIX : usize = 16; +pub const VIRTIO_PCI_COMMON_NUMQ : usize = 18; +pub const VIRTIO_PCI_COMMON_STATUS : usize = 20; +pub const VIRTIO_PCI_COMMON_CFGGENERATION : usize = 21; +pub const VIRTIO_PCI_COMMON_Q_SELECT : usize = 22; +pub const VIRTIO_PCI_COMMON_Q_SIZE : usize = 24; +pub const VIRTIO_PCI_COMMON_Q_MSIX : usize = 26; +pub const VIRTIO_PCI_COMMON_Q_ENABLE : usize = 28; +pub const VIRTIO_PCI_COMMON_Q_NOFF : usize = 30; +pub const VIRTIO_PCI_COMMON_Q_DESCLO : usize = 32; +pub const VIRTIO_PCI_COMMON_Q_DESCHI : usize = 36; +pub const VIRTIO_PCI_COMMON_Q_AVAILLO : usize = 40; +pub const VIRTIO_PCI_COMMON_Q_AVAILHI : usize = 44; +pub const VIRTIO_PCI_COMMON_Q_USEDLO : usize = 48; +pub const VIRTIO_PCI_COMMON_Q_USEDHI : usize = 52; + +// Common configuration status bits + +pub const _VIRTIO_CONFIG_S_ACKNOWLEDGE : u8 = 1; +pub const _VIRTIO_CONFIG_S_DRIVER : u8 = 2; +pub const VIRTIO_CONFIG_S_DRIVER_OK : u8 = 4; +pub const VIRTIO_CONFIG_S_FEATURES_OK : u8 = 8; +pub const VIRTIO_CONFIG_S_NEEDS_RESET : u8 = 0x40; +pub const _VIRTIO_CONFIG_S_FAILED : u8 = 0x80; + +pub const _VRING_USED_F_NO_NOTIFY: u16 = 1; +pub const _VRING_AVAIL_F_NO_INTERRUPT: u16 = 1; +pub const _VIRTIO_F_INDIRECT_DESC: u64 = (1 << 28); +pub const VIRTIO_F_EVENT_IDX: u64 = (1 << 29); +pub const VIRTIO_F_VERSION_1: u64 = (1 << 32); + +pub const VRING_DESC_F_NEXT: u16 = 1; +pub const VRING_DESC_F_WRITE: u16 = 2; +pub const VRING_DESC_F_INDIRECT: u16 = 4; + +pub const DEFAULT_QUEUE_SIZE: u16 = 128; +pub const MAX_QUEUE_SIZE: u16 = 1024; + +// PCI Vendor id for Virtio devices + +pub const PCI_VENDOR_ID_REDHAT: u16 = 0x1af4; + +// Base PCI device id for Virtio devices + +pub const PCI_VIRTIO_DEVICE_ID_BASE: u16 = 0x1040; + +pub const PCI_VENDOR_ID_INTEL: u16 = 0x8086; +pub const PCI_CLASS_BRIDGE_HOST: u16 = 0x0600; diff --git a/src/virtio/device.rs b/src/virtio/device.rs new file mode 100644 index 0000000..5bccbbf --- /dev/null +++ b/src/virtio/device.rs @@ -0,0 +1,228 @@ +use std::sync::{Arc,RwLock}; +use std::ops::DerefMut; + +use memory::{GuestRam,AddressRange}; +use super::bus::VirtioDeviceConfig; +use super::VirtQueue; +use super::config::VirtQueueConfig; +use super::consts::*; +use vm::io::MmioOps; +use vm::Result; + +pub trait VirtioDeviceOps: Send+Sync { + fn reset(&mut self) {} + fn enable_features(&mut self, bits: u64) -> bool { let _ = bits; true } + fn write_config(&mut self, offset: usize, size: usize, val: u64) { let (_,_,_) = (offset, size, val); } + fn read_config(&mut self, offset: usize, size: usize) -> u64 { let (_,_) = (offset, size); 0 } + fn start(&mut self, memory: GuestRam, queues: Vec); +} + +pub struct VirtioDevice { + memory: GuestRam, + vq_config: VirtQueueConfig, + common_cfg_mmio: AddressRange, + isr_mmio: AddressRange, + notify_mmio: AddressRange, + device_cfg_mmio: Option, + device_ops: Arc>, + dfselect: u32, + gfselect: u32, + device_features: u64, + guest_features: u64, + status: u8, +} + +const MASK_LOW_32: u64 = (1u64 << 32) - 1; +const MASK_HI_32: u64 = MASK_LOW_32 << 32; + +fn set_lo32(val: &mut u64, low32: u32) { *val = (*val & MASK_HI_32) | (low32 as u64) } +fn set_hi32(val: &mut u64, hi32: u32) { *val = ((hi32 as u64) << 32) | (*val & MASK_LOW_32) } +fn get_lo32(val: u64) -> u32 { val as u32 } +fn get_hi32(val: u64) -> u32 { (val >> 32) as u32 } + + + +impl VirtioDevice { + pub fn new(memory: GuestRam, config: &VirtioDeviceConfig) -> Result>> { + Ok(Arc::new(RwLock::new(VirtioDevice { + memory: memory.clone(), + vq_config: VirtQueueConfig::new(&memory.clone(),&config)?, + common_cfg_mmio: config.common_cfg_mmio(), + isr_mmio: config.isr_mmio(), + notify_mmio: config.notify_mmio(), + device_cfg_mmio: config.device_cfg_mmio(), + + device_ops: config.ops(), + dfselect: 0, + gfselect: 0, + + device_features: config.feature_bits(), + guest_features: 0, + status: 0, + }))) + } + + fn reset(&mut self) { + self.dfselect = 0; + self.gfselect = 0; + self.guest_features = 0; + self.status = 0; + self.vq_config.reset(); + } + + fn status_write(&mut self, val: u8) { + + // 4.1.4.3.1 The device MUST reset when 0 is written to device status + if val == 0 { + self.reset(); + return; + } + // 2.1.1 The driver MUST NOT clear a device status bit + if self.status & !val != 0 { + return; + } + + let new_bits = val & !self.status; + + if new_bits & VIRTIO_CONFIG_S_DRIVER_OK != 0 { + match self.vq_config.create_queues(&self.memory) { + Ok(queues) => self.with_ops(|ops| ops.start(self.memory.clone(), queues)), + Err(e) => { + println!("creating virtqueues failed {}", e); + self.status |= VIRTIO_CONFIG_S_NEEDS_RESET; + self.vq_config.notify_config(); + return; + } + } + } + + if new_bits & VIRTIO_CONFIG_S_FEATURES_OK != 0 { + if !self.with_ops(|ops| ops.enable_features(self.guest_features)) { + self.vq_config.enable_features(self.guest_features); + return; + } + } + + self.status |= new_bits; + } + + fn common_config_write(&mut self, offset: usize, _size: usize, val: u32) { + match offset { + VIRTIO_PCI_COMMON_DFSELECT => self.dfselect = val, + VIRTIO_PCI_COMMON_GFSELECT => self.gfselect = val, + VIRTIO_PCI_COMMON_GF => { + match self.gfselect { + 0 => set_lo32(&mut self.guest_features, val), + 1 => set_hi32(&mut self.guest_features, val), + _ => {}, + } + // 2.2.1 + // The driver MUST NOT accept a feature which the device did + // not offer. + self.guest_features &= self.device_features; + }, + VIRTIO_PCI_COMMON_STATUS => self.status_write(val as u8), + VIRTIO_PCI_COMMON_Q_SELECT=> self.vq_config.select_queue(val as u16), + VIRTIO_PCI_COMMON_Q_SIZE => self.vq_config.vring_set_size(val as u16), + VIRTIO_PCI_COMMON_Q_ENABLE=> if val == 1 { self.vq_config.vring_enable() } , + VIRTIO_PCI_COMMON_Q_DESCLO=> self.vq_config.with_vring_mut(|vr| set_lo32(&mut vr.descriptors, val)), + VIRTIO_PCI_COMMON_Q_DESCHI=> self.vq_config.with_vring_mut(|vr| set_hi32(&mut vr.descriptors, val)), + VIRTIO_PCI_COMMON_Q_AVAILLO=> self.vq_config.with_vring_mut(|vr| set_lo32(&mut vr.avail_ring, val)), + VIRTIO_PCI_COMMON_Q_AVAILHI=> self.vq_config.with_vring_mut(|vr| set_hi32(&mut vr.avail_ring, val)), + VIRTIO_PCI_COMMON_Q_USEDLO=> self.vq_config.with_vring_mut(|vr| set_lo32(&mut vr.used_ring, val)), + VIRTIO_PCI_COMMON_Q_USEDHI=> self.vq_config.with_vring_mut(|vr| set_hi32(&mut vr.used_ring, val)), + _ => {}, + } + } + + fn common_config_read(&mut self, offset: usize, _size: usize) -> u32 { + match offset { + VIRTIO_PCI_COMMON_DFSELECT => self.dfselect, + VIRTIO_PCI_COMMON_DF=> match self.dfselect { + 0 => get_lo32(self.device_features), + 1 => get_hi32(self.device_features), + _ => 0, + }, + VIRTIO_PCI_COMMON_GFSELECT => { self.gfselect }, + VIRTIO_PCI_COMMON_GF => match self.gfselect { + 0 => get_lo32(self.guest_features), + 1 => get_hi32(self.guest_features), + _ => 0, + }, + VIRTIO_PCI_COMMON_MSIX => VIRTIO_NO_MSI_VECTOR as u32, + VIRTIO_PCI_COMMON_NUMQ => self.vq_config.num_queues() as u32, + VIRTIO_PCI_COMMON_STATUS => self.status as u32, + VIRTIO_PCI_COMMON_CFGGENERATION => 0, + VIRTIO_PCI_COMMON_Q_SELECT => self.vq_config.selected_queue() as u32, + VIRTIO_PCI_COMMON_Q_SIZE => self.vq_config.vring_get_size() as u32, + VIRTIO_PCI_COMMON_Q_MSIX => VIRTIO_NO_MSI_VECTOR as u32, + VIRTIO_PCI_COMMON_Q_ENABLE => if self.vq_config.vring_is_enabled() {1} else {0}, + VIRTIO_PCI_COMMON_Q_NOFF => self.vq_config.selected_queue() as u32, + VIRTIO_PCI_COMMON_Q_DESCLO => self.vq_config.with_vring(0, |vr| get_lo32(vr.descriptors)), + VIRTIO_PCI_COMMON_Q_DESCHI => self.vq_config.with_vring(0, |vr| get_hi32(vr.descriptors)), + VIRTIO_PCI_COMMON_Q_AVAILLO => self.vq_config.with_vring(0, |vr| get_lo32(vr.avail_ring)), + VIRTIO_PCI_COMMON_Q_AVAILHI => self.vq_config.with_vring(0, |vr| get_hi32(vr.avail_ring)), + VIRTIO_PCI_COMMON_Q_USEDLO => self.vq_config.with_vring(0, |vr| get_lo32(vr.used_ring)), + VIRTIO_PCI_COMMON_Q_USEDHI => self.vq_config.with_vring(0, |vr| get_hi32(vr.used_ring)), + _ => 0, + } + } + + fn notify_read(&mut self, _offset: usize, _size: usize) -> u64 { + 0 + } + + fn notify_write(&mut self, offset: usize, _size: usize, _val: u64) { + let vq = (offset / 4) as u16; + self.vq_config.notify(vq); + } + + fn isr_read(&mut self) -> u64 { + self.vq_config.isr_read() + } + + fn with_ops(&self, f: F) -> U + where F: FnOnce(&mut VirtioDeviceOps) -> U { + let mut ops = self.device_ops.write().unwrap(); + f(ops.deref_mut()) + } +} + +impl MmioOps for VirtioDevice { + fn mmio_read(&mut self, address: u64, size: usize) -> u64 { + if self.common_cfg_mmio.contains(address, size) { + let offset = self.common_cfg_mmio.offset_of(address); + self.common_config_read(offset,size) as u64 + + } else if self.notify_mmio.contains(address, size) { + let offset = self.notify_mmio.offset_of(address); + self.notify_read(offset, size) as u64 + + } else if self.isr_mmio.contains(address, size) { + self.isr_read() + + } else if let Some(ref dev_cfg_mmio) = self.device_cfg_mmio { + let offset = dev_cfg_mmio.offset_of(address); + self.with_ops(|ops| ops.read_config(offset, size)) + + } else { + 0 + } + } + + fn mmio_write(&mut self, address: u64, size: usize, val: u64) { + if self.common_cfg_mmio.contains(address, size) { + let offset = self.common_cfg_mmio.offset_of(address); + self.common_config_write(offset,size, val as u32) + + } else if self.notify_mmio.contains(address, size) { + let offset = self.notify_mmio.offset_of(address); + self.notify_write(offset, size, val) + + } else if let Some(ref dev_cfg_mmio) = self.device_cfg_mmio { + let offset = dev_cfg_mmio.offset_of(address); + self.with_ops(|ops| ops.write_config(offset, size, val)) + } + } +} + diff --git a/src/virtio/eventfd.rs b/src/virtio/eventfd.rs new file mode 100644 index 0000000..6fa3bab --- /dev/null +++ b/src/virtio/eventfd.rs @@ -0,0 +1,87 @@ +use std::sync::Arc; +use std::os::unix::io::RawFd; + +use libc; + +use vm::{Result,Error,ErrorKind}; +use kvm::Kvm; + +pub struct EventFd(RawFd); + +const U64_SZ: usize = 8; + +impl EventFd { + pub fn new() -> Result { + let fd = unsafe { libc::eventfd(0, 0) }; + if fd < 0 { + return Err(Error::from_last_errno()); + } + Ok(EventFd(fd)) + } + + pub fn raw_fd(&self) -> RawFd { + self.0 + } + + pub fn write(&self, v: u64) -> Result<()> { + let ret = unsafe { libc::write(self.0, &v as *const _ as *const libc::c_void, U64_SZ) }; + if ret as usize != U64_SZ { + if ret < 0 { + return Err(Error::new(ErrorKind::EventFdError, Error::from_last_errno())); + } + return Err(Error::new(ErrorKind::EventFdError, "write failed")); + } + Ok(()) + } + + pub fn read(&self) -> Result { + let mut v = 0u64; + let ret = unsafe { libc::read(self.0, &mut v as *mut _ as *mut libc::c_void, U64_SZ) }; + if ret as usize != U64_SZ { + if ret < 0 { + return Err(Error::new(ErrorKind::EventFdError, Error::from_last_errno())); + } + return Err(Error::new(ErrorKind::EventFdError, "read failed")); + } + Ok((v)) + } +} + +impl Drop for EventFd { + fn drop(&mut self) { + let _ = unsafe { libc::close(self.0) }; + } +} + +pub struct IoEventFd { + kvm: Kvm, + addr: u64, + evt: Arc +} + +impl IoEventFd { + pub fn new(kvm: &Kvm, address: u64) -> Result { + let evt = Arc::new(EventFd::new()?); + kvm.ioeventfd_add(address, evt.raw_fd())?; + Ok(IoEventFd { + kvm: kvm.clone(), + addr: address, + evt, + }) + } + pub fn read(&self) -> Result { + self.evt.read() + } + + pub fn write(&self, v: u64) -> Result<()> { + self.evt.write(v) + } + +} + +impl Drop for IoEventFd { + fn drop(&mut self) { + let _ = self.kvm.ioeventfd_del(self.addr, self.evt.raw_fd()); + } +} + diff --git a/src/virtio/mod.rs b/src/virtio/mod.rs new file mode 100644 index 0000000..466e5ec --- /dev/null +++ b/src/virtio/mod.rs @@ -0,0 +1,30 @@ +mod bus; +mod chain; +mod config; +mod consts; +mod device; +mod eventfd; +mod pci; +mod virtqueue; +mod vring; + +pub use self::virtqueue::VirtQueue; +pub use self::pci::PciIrq; +pub use self::bus::VirtioBus; +pub use self::device::{VirtioDevice,VirtioDeviceOps}; +pub use self::chain::Chain; + +use byteorder::{ByteOrder,LittleEndian}; + +pub fn read_config_buffer(config: &[u8], offset: usize, size: usize) -> u64 { + if offset + size > config.len() { + return 0; + } + match size { + 1 => config[offset] as u64, + 2 => LittleEndian::read_u16(&config[offset..]) as u64, + 4 => LittleEndian::read_u32(&config[offset..]) as u64, + 8 => LittleEndian::read_u64(&config[offset..]) as u64, + _ => 0, + } +} diff --git a/src/virtio/pci.rs b/src/virtio/pci.rs new file mode 100644 index 0000000..79b4cfb --- /dev/null +++ b/src/virtio/pci.rs @@ -0,0 +1,436 @@ +use std::sync::{Arc,RwLock}; +use byteorder::{ByteOrder,LittleEndian}; + +use vm::io::{IoDispatcher,IoPortOps}; +use memory::PCI_MMIO_RESERVED_BASE; +use memory::AddressRange; +use super::consts::*; + +struct PciConfigAddress(u32); + +impl PciConfigAddress { + fn new() -> PciConfigAddress { PciConfigAddress(0) } + fn set(&mut self, n: u32) { self.0 = n } + fn get(&self) -> u32 { self.0 } + fn bus(&self) -> u32 { self.bits(16, 8) } + fn function(&self) -> u32 { self.bits(8, 3) } + fn device(&self) -> usize { self.bits(11, 5) as usize } + fn offset(&self) -> usize { (self.bits(0, 8) & !0x3) as usize } + fn bits(&self, offset: u32, size: u32) -> u32 { + let mask = (1u32 << size) - 1; + (self.0 >> offset) & mask + } +} + +pub struct PciIrq { + pci_id: u8, + int_pin: u8, + irq: u8, +} + +impl PciIrq { + fn new(pci: &PciDevice) -> PciIrq { + PciIrq { + pci_id: pci.id, + int_pin: 1, + irq: pci.irq, + } + } + + pub fn src_bus_irq(&self) -> u8 { + (self.pci_id << 2) | (self.int_pin - 1) + } + + pub fn irq_line(&self) -> u8 { + self.irq + } +} + +pub struct PciBus { + devices: Vec>, + mmio_next_alloc: u32, + next_irq: u8, + next_dev: u8, + config_address: PciConfigAddress, +} + +impl PciBus { + pub fn new(io: &IoDispatcher) -> Arc> { + let bus = Arc::new(RwLock::new(PciBus { + devices: PciBus::create_device_vec(PCI_MAX_DEVICES), + mmio_next_alloc: PCI_MMIO_RESERVED_BASE as u32, + next_irq: 5, + next_dev: 1, + config_address: PciConfigAddress::new(), + })); + + io.register_ioports(PCI_CONFIG_ADDRESS, 8, bus.clone()); + let pci = PciDevice::new(0, 0, PCI_VENDOR_ID_INTEL, 0, PCI_CLASS_BRIDGE_HOST); + bus.write().unwrap().store_device(pci); + bus + } + + pub fn pci_irqs(&self) -> Vec { + let mut v = Vec::new(); + for d in &self.devices { + match *d { + Some(ref dev) => v.push(PciIrq::new(dev)), + None => (), + } + } + v + } + + fn allocate_irq(&mut self) -> u8 { + let irq = self.next_irq; + self.next_irq += 1; + irq + } + + fn allocate_id(&mut self) -> u8 { + let id = self.next_dev; + self.next_dev += 1; + id + } + + pub fn create_device(&mut self, vendor: u16, device: u16, class_id: u16) -> PciDevice { + let irq = self.allocate_irq(); + let id = self.allocate_id(); + let pci = PciDevice::new(id, irq, vendor, device, class_id); + pci + } + + pub fn store_device(&mut self, pci: PciDevice) { + let id = pci.id as usize; + self.devices[id] = Some(pci) + } + + fn create_device_vec(sz: usize) -> Vec> { + let mut v = Vec::with_capacity(sz); + for _ in 0..sz { + v.push(None) + } + v + } + + pub fn allocate_mmio_space(&mut self, sz: usize) -> AddressRange { + let mask = (sz - 1) as u32; + let aligned = (self.mmio_next_alloc + mask) & !mask; + self.mmio_next_alloc = aligned + (sz as u32); + AddressRange::new(aligned as u64, sz) + } + + fn is_in_range(base: u16, port: u16, len: usize) -> bool { + let end = port + len as u16; + port >= base && end <= (base + 4) + } + + fn is_config_address(&self, port: u16, len: usize) -> bool { + PciBus::is_in_range(PCI_CONFIG_ADDRESS, port, len) + } + + fn is_config_data(&self, port: u16, len: usize) -> bool { + PciBus::is_in_range(PCI_CONFIG_DATA, port, len) + } + + fn config_address_in(&self, _: usize) -> u32 { + self.config_address.get() + } + + fn current_config_device(&mut self) -> Option<&mut PciDevice> { + let b = self.config_address.bus(); + let d = self.config_address.device(); + let f = self.config_address.function(); + + if b != 0 || f != 0 || d >= self.devices.len() { + return None; + } + + self.devices[d].as_mut() + } + + fn config_address_out(&mut self, _offset: u16, size: usize, data: u32) { + if size == 4 { + self.config_address.set(data); + } + } + + #[allow(dead_code)] + fn valid_config_access(&self, offset: u16, len: usize) -> bool { + (offset as usize) + len <= 4 + } + + fn config_data_in(&mut self, offset: usize, size: usize) -> u32 { + let off = self.config_address.offset() + offset; + match self.current_config_device() { + Some(dev) => { dev.read_config(off, size)}, + None => 0xFFFFFFFF, + } + } + + fn config_data_out(&mut self, offset: u16, size: usize, data: u32) { + let off = self.config_address.offset() + offset as usize; + if let Some(dev) = self.current_config_device() { + dev.write_config(off, size,data) + } + } +} + +impl IoPortOps for PciBus { + fn io_in(&mut self, port: u16, size: usize) -> u32 { + if self.is_config_address(port, size) { + return self.config_address_in(size) + } + if self.is_config_data(port, size) { + return self.config_data_in((port - PCI_CONFIG_DATA) as usize, size) + } + return 0; + } + + fn io_out(&mut self, port: u16, size: usize, val: u32) { + if self.is_config_address(port, size) { + self.config_address_out(port - PCI_CONFIG_ADDRESS,size, val) + } + if self.is_config_data(port, size) { + self.config_data_out(port - PCI_CONFIG_DATA, size, val) + } + } +} + + +pub struct PciDevice { + next_cap: usize, + last_cap: usize, + id: u8, + irq: u8, + config_buffer: [u8; PCI_CONFIG_SPACE_SIZE], + bar_write_masks: [u32; 6], +} + +impl PciDevice { + pub fn new(id: u8, irq: u8, vendor: u16, device: u16, class_id: u16) -> PciDevice { + let mut d = PciDevice { + next_cap: PCI_CAP_BASE_OFFSET, + last_cap: 0, + id, + irq, + config_buffer: [0; PCI_CONFIG_SPACE_SIZE], + bar_write_masks: [0; 6], + }; + d.w16(PCI_VENDOR_ID, vendor); + d.w16(PCI_DEVICE_ID, device); + d.w16(PCI_COMMAND, PCI_COMMAND_IO | PCI_COMMAND_MEMORY); + d.w8(PCI_CLASS_REVISION, 1); + d.w16(PCI_CLASS_DEVICE, class_id); + d.w8(PCI_INTERRUPT_PIN, 1); + d.w8(PCI_INTERRUPT_LINE, irq); + d.w16(PCI_SUBSYSTEM_ID, 0x40); + d + } + + pub fn get_irq(&self) -> u8 { + self.irq + } + + fn is_valid_write(&self, offset: usize, size: usize) -> bool { + if offset + size > PCI_CONFIG_SPACE_SIZE { + return false; + } + // check alignment of write + let mod4 = offset % 4; + match size { + 4 if mod4 == 0 => true, + 2 if mod4 == 0 || mod4 == 2 => true, + 1 => true, + _ => false, + } + } + + fn write_bar(&mut self, offset: usize, size: usize, data: u32) { + assert!(is_bar_offset(offset), "not a bar offset in write_bar()"); + + let bar = offset_to_bar(offset); + let write_mask = self.bar_write_masks[bar]; + + if write_mask == 0 { + // no writable bits + return; + } + + let mod4 = offset % 4; + + match size { + 4 => self.w32(offset, data), + 2 => self.w16(offset+ mod4, data as u16), + 1 => self.w8(offset+ mod4, data as u8), + _ => (), + }; + + // apply write mask to whatever was written + let v = self.r32(offset); + self.w32(offset, v & write_mask); + } + + fn write_config(&mut self, offset: usize, size: usize, data: u32) { + if !self.is_valid_write(offset, size) { + return; + } + + if is_bar_offset(offset) { + self.write_bar(offset, size, data); + return; + } + + match offset { + PCI_COMMAND if size == 2 => self.w16(PCI_COMMAND, data as u16), + PCI_STATUS if size == 2 => self.w16(PCI_STATUS, data as u16), + PCI_CACHE_LINE_SIZE if size == 1 => self.w8(PCI_CACHE_LINE_SIZE, data as u8), + PCI_LATENCY_TIMER if size == 1 => self.w8(PCI_LATENCY_TIMER, data as u8), + _ => (), + } + } + + fn w32(&mut self, off: usize, val: u32) { LittleEndian::write_u32(&mut self.config_buffer[off..], val); } + fn w16(&mut self, off: usize, val: u16) { LittleEndian::write_u16(&mut self.config_buffer[off..], val); } + fn w8(&mut self, off: usize, val: u8) { self.config_buffer[off] = val; } + + fn r32(&self, off: usize) -> u32 { LittleEndian::read_u32(&self.config_buffer[off..]) } + fn r16(&self, off: usize) -> u16 { LittleEndian::read_u16(&self.config_buffer[off..]) } + fn r8(&self, off: usize) -> u8 { self.config_buffer[off] } + + fn read_config(&self, offset: usize, size: usize) -> u32 { + if offset + size > PCI_CONFIG_SPACE_SIZE { + return 0xFFFFFFFF; + } + match size { + 1 => self.r8(offset) as u32, + 2 => self.r16(offset) as u32, + 4 => self.r32(offset), + _ => 0xFFFFFFFF + } + } + + #[allow(dead_code)] + pub fn is_irq_disabled(&self) -> bool { + self.r16(PCI_COMMAND) & PCI_COMMAND_INTX_DISABLE != 0 + } + + pub fn set_mmio_bar(&mut self, bar: usize, range: AddressRange) { + assert!(range.is_naturally_aligned(), "cannot set_mmio_bar() because mmio range is not naturally aligned"); + assert!(bar < 5, "bar is invalid value in set_mmio_bar()"); + self.bar_write_masks[bar] = !((range.size() as u32) - 1); + self.w32(bar_to_offset(bar), range.base() as u32); + } + + pub fn add_virtio_caps(&mut self, config_size: usize) { + self.new_virtio_cap(VIRTIO_PCI_CAP_COMMON_CFG) + .set_mmio_range(VIRTIO_MMIO_OFFSET_COMMON_CFG, VIRTIO_MMIO_COMMON_CFG_SIZE).add(self); + + self.new_virtio_cap(VIRTIO_PCI_CAP_ISR_CFG) + .set_mmio_range(VIRTIO_MMIO_OFFSET_ISR, VIRTIO_MMIO_ISR_SIZE).add(self); + + self.new_virtio_cap(VIRTIO_PCI_CAP_NOTIFY_CFG) + .set_mmio_range(VIRTIO_MMIO_OFFSET_NOTIFY, VIRTIO_MMIO_NOTIFY_SIZE) + .set_extra_word(4).add(self); + + if config_size > 0 { + self.new_virtio_cap(VIRTIO_PCI_CAP_DEVICE_CFG) + .set_mmio_range(VIRTIO_MMIO_OFFSET_DEV_CFG,config_size).add(self); + } + } + + pub fn new_virtio_cap(&mut self, vtype: u8) -> VirtioCap { + VirtioCap::new(self.next_cap, vtype) + } + + fn inc_cap(&mut self, size: usize) { + let next = self.next_cap as u8; + let last = self.last_cap; + if self.last_cap == 0 { + self.w8(PCI_CAPABILITY_LIST, next); + let status = self.r16(PCI_STATUS) | PCI_STATUS_CAP_LIST; + self.w16(PCI_STATUS, status); + } else { + self.w8(last + 1, next); + } + self.last_cap = self.next_cap; + let aligned = (size + 3) & !3; + self.next_cap += aligned; + } +} + +fn is_bar_offset(offset: usize) -> bool { + offset >= 0x10 && offset < 0x28 +} + +fn bar_to_offset(bar: usize) -> usize { + 0x10 + (bar * 4) +} + +fn offset_to_bar(offset: usize) -> usize { + assert!(offset >= 0x10 && offset < 0x28, "not a valid bar offset"); + (offset - 0x10) / 4 +} + + +pub struct VirtioCap { + offset: usize, + vtype: u8, + size: u8, + mmio_offset: u32, + mmio_len: u32, + extra_word: Option, +} + +impl VirtioCap { + fn new(offset: usize, vtype: u8) -> VirtioCap { + VirtioCap { + vtype, + offset, + size: 16, + mmio_offset: 0, + mmio_len: 0, + extra_word: None, + } + } + + pub fn set_mmio_range(&mut self, offset: usize, len: usize) -> &mut VirtioCap { + self.mmio_offset = offset as u32; + self.mmio_len = len as u32; + self + } + + pub fn set_extra_word(&mut self, val: u32) -> &mut VirtioCap { + self.size += 4; + self.extra_word = Some(val); + self + } + + pub fn add(&mut self, dev: &mut PciDevice) { + /* + * struct virtio_pci_cap { + * u8 cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */ + * u8 cap_next; /* Generic PCI field: next ptr. */ + * u8 cap_len; /* Generic PCI field: capability length */ + * u8 cfg_type; /* Identifies the structure. */ + * u8 bar; /* Where to find it. */ + * u8 padding[3]; /* Pad to full dword. */ + * le32 offset; /* Offset within bar. */ + * le32 length; /* Length of the structure, in bytes. */ + * }; + */ + dev.w8(self.offset, PCI_CAP_ID_VENDOR); + dev.w8(self.offset + 2, self.size); + dev.w8(self.offset + 3, self.vtype); + dev.w8(self.offset + 4, VIRTIO_MMIO_BAR as u8); + if self.mmio_len > 0 { + dev.w32(self.offset + 8, self.mmio_offset); + dev.w32(self.offset + 12, self.mmio_len); + } + if let Some(word) = self.extra_word { + dev.w32(self.offset + 16, word); + } + + dev.inc_cap(self.size as usize); + } +} diff --git a/src/virtio/virtqueue.rs b/src/virtio/virtqueue.rs new file mode 100644 index 0000000..eede2af --- /dev/null +++ b/src/virtio/virtqueue.rs @@ -0,0 +1,166 @@ +use std::sync::atomic::{Ordering, AtomicUsize, AtomicBool}; +use std::sync::Arc; + +use memory::GuestRam; +use kvm::Kvm; +use vm::Result; + +use super::eventfd::{EventFd,IoEventFd}; +use super::consts::*; +use super::vring::{Vring,Descriptor}; +use super::bus::VirtioDeviceConfig; +use super::chain::Chain; + +#[derive(Clone)] +pub struct VirtQueue { + memory: GuestRam, + vring: Vring, + features: u64, + ioeventfd: Arc, + interrupt: Arc, + closed: Arc, +} + +impl VirtQueue { + pub fn new(memory: GuestRam, vring: Vring, interrupt: Arc, ioeventfd: Arc) -> VirtQueue { + VirtQueue { + memory, + vring, + features: 0, + ioeventfd, + interrupt, + closed: Arc::new(AtomicBool::new(false)), + } + } + + #[allow(dead_code)] + pub fn set_closed(&self) { + self.closed.store(true, Ordering::SeqCst); + self.ioeventfd.write(1).unwrap(); + } + + #[allow(dead_code)] + pub fn is_closed(&self) -> bool { + self.closed.load(Ordering::SeqCst) + } + + fn use_event_idx(&self) -> bool { + self.features & VIRTIO_F_EVENT_IDX != 0 + } + + pub fn wait_ready(&self) -> Result<()> { + if self.vring.is_empty() { + let _ = self.ioeventfd.read()?; + } + Ok(()) + } + + pub fn wait_next_chain(&self) -> Result { + loop { + self.wait_ready()?; + if let Some(idx) = self.pop_avail_entry() { + return Ok(Chain::new(self.memory.clone(), self.clone(), idx, self.vring.size())); + } + } + } + + pub fn on_each_chain(&self, mut f: F) + where F: FnMut(Chain) { + loop { + self.wait_ready().unwrap(); + for chain in self.iter() { + f(chain); + } + } + } + + pub fn iter(&self) -> QueueIter { + QueueIter { vq: self.clone() } + } + + fn need_interrupt(&self, first_used: u16, used_count: usize) -> bool { + if used_count == 0 { + return false; + } + if self.use_event_idx() { + let event = self.vring.read_used_event(); + // Minimum count needed to traverse event idx + let span = ((event - first_used) + 1) as usize; + return used_count >= span; + } + !self.vring.read_avail_no_interrupt() + } + + pub fn put_used(&self, idx: u16, len: u32) { + let used = self.vring.next_used(); + self.vring.put_used(idx, len); + if self.need_interrupt(used, 1) { + self.interrupt.notify_queue(); + } + } + + fn pop_avail_entry(&self) -> Option { + if let Some(idx) = self.vring.pop_avail_entry() { + if self.use_event_idx() { + self.vring.write_avail_event(self.vring.next_avail()); + } + return Some(idx) + } + None + } + + pub fn load_descriptor(&self, idx: u16) -> Option { + self.vring.load_descriptor(idx) + } +} + +pub struct QueueIter { + vq: VirtQueue, +} + +impl Iterator for QueueIter { + type Item = Chain; + + fn next(&mut self) -> Option { + self.vq.pop_avail_entry().map(|idx| { + Chain::new(self.vq.memory.clone(),self.vq.clone(),idx, self.vq.vring.size()) + }) + } +} + + +pub struct InterruptLine { + irqfd: EventFd, + isr: AtomicUsize, +} + +impl InterruptLine { + pub fn from_config(conf: &VirtioDeviceConfig) -> Result> { + InterruptLine::new(conf.kvm(), conf.irq()) + } + + fn new(kvm: &Kvm, irq: u8) -> Result> { + let irqfd = EventFd::new()?; + kvm.irqfd(irqfd.raw_fd() as u32, irq as u32)?; + Ok(Arc::new(InterruptLine{ + irqfd, + isr: AtomicUsize::new(0) + })) + } + + pub fn isr_read(&self) -> u64 { + self.isr.swap(0, Ordering::SeqCst) as u64 + } + + pub fn notify_queue(&self) { + self.isr.fetch_or(0x1, Ordering::SeqCst); + self.irqfd.write(1).unwrap(); + } + + pub fn notify_config(&self) { + self.isr.fetch_or(0x2, Ordering::SeqCst); + self.irqfd.write(1).unwrap(); + } +} + + diff --git a/src/virtio/vring.rs b/src/virtio/vring.rs new file mode 100644 index 0000000..a55c6eb --- /dev/null +++ b/src/virtio/vring.rs @@ -0,0 +1,388 @@ +use std::sync::atomic::{self,AtomicUsize,Ordering}; +use std::sync::Arc; +use std::fmt; +use std::cmp; +use std::io::{self, Read}; + +use memory::GuestRam; +use super::consts::*; + +use vm::{Result,Error,ErrorKind}; + +/// +/// A convenience wrapper around `AtomicUsize` +/// +#[derive(Clone)] +struct SharedIndex(Arc); + +impl SharedIndex { + fn new() -> SharedIndex { + SharedIndex(Arc::new(AtomicUsize::new(0))) + } + fn get(&self) -> u16 { + self.0.load(Ordering::SeqCst) as u16 + } + fn inc(&self) { + self.0.fetch_add(1, Ordering::SeqCst); + } + fn set(&self, v: u16) { + self.0.store(v as usize, Ordering::SeqCst); + } +} + +/// +/// Access to the low-level memory structure of a Virtqueue. +/// +#[derive(Clone)] +pub struct Vring { + memory: GuestRam, + /// Default queue_size for this virtqueue + default_size: u16, + /// Number of elements in the virtqueue ring + queue_size: u16, + /// Guest address for base of descriptor table + pub descriptors: u64, + /// Guest address for base of available ring + pub avail_ring: u64, + /// Guest address for base of used ring + pub used_ring: u64, + + /// Has this virtqueue been enabled? + enabled: bool, + + /// The index in the used ring where the next used entry will be placed + next_used_idx: SharedIndex, + /// last seen avail_idx loaded from guest memory + cached_avail_idx: SharedIndex, + /// The index in the avail ring where the next available entry will be read + next_avail: SharedIndex, +} + +impl Vring { + + pub fn new(memory: GuestRam, default_size: u16) -> Vring { + Vring { + memory, + default_size, + queue_size: default_size, + descriptors:0, + avail_ring: 0, + used_ring: 0, + enabled: false, + + next_used_idx: SharedIndex::new(), + cached_avail_idx: SharedIndex::new(), + next_avail: SharedIndex::new(), + } + + } + + /// + /// Set `Vring` into the enabled state. + /// + pub fn enable(&mut self) { + self.enabled = true; + } + + /// + /// Return `true` if this `Vring` has been enabled. + /// + pub fn is_enabled(&self) -> bool { + self.enabled + } + + /// + /// Queue size of this `Vring` + /// + pub fn size(&self) -> u16 { + self.queue_size + } + + /// + /// Set the queue size of this `Vring`. If `sz` is an invalid value + /// ignore the request. It is illegal to change the queue size after + /// a virtqueue has been enabled, so ignore requests if enabled. + /// + /// Valid sizes are less than or equal to `MAX_QUEUE_SIZE` and must + /// be a power of 2. + /// + pub fn set_size(&mut self, sz: u16) { + if self.enabled || sz > MAX_QUEUE_SIZE || (sz & (sz - 1) != 0) { + return; + } + self.queue_size = sz; + } + + /// + /// Reset `Vring` to the initial state. `queue_size` is set to the `default_size` + /// and all other fields are cleared. `enabled` is set to false. + /// + pub fn reset(&mut self) { + self.queue_size = self.default_size; + self.descriptors = 0; + self.avail_ring = 0; + self.used_ring = 0; + self.enabled = false; + self.next_used_idx.set(0); + self.cached_avail_idx.set(0); + self.next_avail.set(0); + } + + + /// + /// Does `Vring` currently have available entries? + /// + /// Queue is empty if `next_avail` is same value as + /// `avail_ring.idx` value in guest memory If `cached_avail_idx` + /// currently matches `next_avail` it is reloaded from + /// memory in case guest has updated field since last + /// time it was loaded. + /// + pub fn is_empty(&self) -> bool { + let next_avail = self.next_avail.get(); + if self.cached_avail_idx.get() != next_avail { + return false; + } + next_avail == self.load_avail_idx() + } + + /// + /// Write an entry into the Used ring. + /// + /// The entry is written into the ring structure at offset + /// `next_used_idx % queue_size`. The value of `next_used_idx` + /// is then incremented and the new value is written into + /// guest memory into the `used_ring.idx` field. + /// + pub fn put_used(&self, idx: u16, len: u32) { + if idx >= self.queue_size { + return; + } + + let used_idx = (self.next_used_idx.get() % self.queue_size) as u64; + let elem_addr = self.used_ring + (4 + used_idx * 8); + // write descriptor index to 'next used' slot in used ring + self.memory.write_int(elem_addr, idx as u32).unwrap(); + // write length to 'next used' slot in ring + self.memory.write_int(elem_addr + 4, len as u32).unwrap(); + + self.next_used_idx.inc(); + atomic::fence(Ordering::Release); + // write updated next_used + self.memory.write_int(self.used_ring + 2, self.next_used_idx.get()).unwrap(); + } + + + /// + /// Load `avail_ring.idx` from guest memory and store it in `cached_avail_idx`. + /// + pub fn load_avail_idx(&self) -> u16 { + let avail_idx = self.memory.read_int::(self.avail_ring + 2).unwrap(); + self.cached_avail_idx.set(avail_idx); + avail_idx + } + + /// + /// Read from guest memory and return the Avail ring entry at + /// index `ring_idx % queue_size`. + /// + fn load_avail_entry(&self, ring_idx: u16) -> u16 { + let offset = (4 + (ring_idx % self.queue_size) * 2) as u64; + self.memory.read_int(self.avail_ring + offset).unwrap() + } + + /// + /// If queue is not empty, read and return the next Avail ring entry + /// and increment `next_avail`. If queue is empty return `None` + /// + pub fn pop_avail_entry(&self) -> Option { + if self.is_empty() { + return None + } + let next_avail = self.next_avail.get(); + let avail_entry = self.load_avail_entry(next_avail); + self.next_avail.inc(); + Some(avail_entry) + } + + pub fn next_avail(&self) -> u16 { + self.next_avail.get() % self.queue_size + } + + /// + /// Read and return the `used_event` field from the Avail ring. + /// + pub fn read_used_event(&self) -> u16 { + let addr = self.avail_ring + 4 + (self.queue_size as u64 * 2); + self.memory.read_int::(addr).unwrap() + } + + /// + /// Read the `flags` field from the Avail ring and return `true` if + /// `NO_INTERRUPT` bit is set. + /// + pub fn read_avail_no_interrupt(&self) -> bool { + let flags = self.memory.read_int::(self.avail_ring).unwrap(); + flags & 0x01 != 0 + } + + /// + /// Write `val` to the `avail_event` field of Used ring. + /// + /// If `val` is not a valid index for this virtqueue this + /// function does nothing. + /// + pub fn write_avail_event(&self, val: u16) { + if val > self.queue_size { + return; + } + let addr = self.used_ring + 4 + (self.queue_size as u64 * 8); + self.memory.write_int::(addr, val).unwrap(); + atomic::fence(Ordering::Release); + } + + /// + /// Set or clear the `NO_NOTIFY` bit in flags field of Used ring + /// + #[allow(dead_code)] + pub fn write_used_no_notify(&self, val: bool) { + let flag = if val { 0x1 } else { 0x0 }; + self.memory.write_int::(self.used_ring, flag).unwrap(); + } + + /// + /// Load the descriptor table entry at `idx` from guest memory and return it. + /// + pub fn load_descriptor(&self, idx: u16) -> Option { + if idx >= self.queue_size { + panic!("load_descriptor called with index larger than queue size"); + } + let head = self.descriptors + (idx as u64 * 16); + + let addr = self.memory.read_int::(head).unwrap(); + let len= self.memory.read_int::(head + 8).unwrap(); + let flags = self.memory.read_int::(head + 12).unwrap(); + let next = self.memory.read_int::(head + 14).unwrap(); + + if self.memory.is_valid_range(addr, len as usize) && next < self.queue_size { + return Some(Descriptor::new(idx, addr, len, flags, next)); + } + None + } + + pub fn next_used(&self) -> u16 { + self.next_used_idx.get() + } + + pub fn validate(&self) -> Result<()> { + fn vring_err(msg: T) -> Result<()> { + Err(Error::new(ErrorKind::InvalidVring, msg.to_string())) + } + + if !self.enabled { + return vring_err("vring is not enabled"); + } + let qsz = self.queue_size as usize; + let desc_table_sz = 16 * qsz; + let avail_ring_sz = 6 + 2 * qsz; + let used_ring_sz = 6 + 8 * qsz; + if !self.memory.is_valid_range(self.descriptors, desc_table_sz) { + return vring_err(format!("descriptor table range is invalid 0x{:x}", self.descriptors)); + } + if !self.memory.is_valid_range(self.avail_ring, avail_ring_sz) { + return vring_err(format!("avail ring range is invalid 0x{:x}", self.avail_ring)); + } + if !self.memory.is_valid_range(self.used_ring, used_ring_sz) { + return vring_err(format!("used ring range is invalid 0x{:x}", self.used_ring)); + } + Ok(()) + } +} + +/// +/// An entry read from the descriptor table +/// +#[derive(Copy,Clone)] +pub struct Descriptor { + pub idx: u16, + pub addr: u64, + pub len: u32, + pub flags: u16, + pub next: u16, +} + +impl Descriptor { + fn new(idx: u16, addr: u64, len: u32, flags: u16, next:u16) -> Descriptor { + Descriptor{ idx, addr, len, flags, next } + } + + /// + /// Test if `flag` is set in `self.flags` + /// + fn has_flag(&self, flag: u16) -> bool { + self.flags & flag == flag + } + + /// + /// Is VRING_DESC_F_NEXT set in `self.flags`? + /// + pub fn has_next(&self) -> bool { + self.has_flag(VRING_DESC_F_NEXT) + } + + /// + /// Is VRING_DESC_F_WRITE set in `self.flags`? + /// + pub fn is_write(&self) -> bool { + self.has_flag(VRING_DESC_F_WRITE) + } + + /// + /// Is VRING_DESC_F_INDIRECT set in `self.flags`? + /// + #[allow(dead_code)] + pub fn is_indirect(&self) -> bool { + self.has_flag(VRING_DESC_F_INDIRECT) + } + + fn remaining(&self, offset: usize) -> usize { + if offset >= self.len as usize { + 0 + } else { + self.len as usize - offset + } + } + + pub fn read_from(&self, memory: &GuestRam, offset: usize, buf: &mut[u8]) -> usize { + let sz = cmp::min(buf.len(), self.remaining(offset)); + if sz > 0 { + memory.read_bytes(self.addr + offset as u64, buf).unwrap(); + } + sz + } + + pub fn write_to(&self, memory: &GuestRam, offset: usize, buf: &[u8]) -> usize { + let sz = cmp::min(buf.len(), self.remaining(offset)); + if sz > 0 { + memory.write_bytes(self.addr + offset as u64, buf).unwrap(); + } + sz + } + + pub fn write_from_reader(&self, memory: &GuestRam, offset: usize, mut r: R, size: usize) -> io::Result { + let sz = cmp::min(size, self.remaining(offset)); + if sz > 0 { + let slice = memory.mut_slice(self.addr + offset as u64, sz).unwrap(); + return r.read(slice); + } + Ok(0) + } +} + +impl fmt::Debug for Descriptor { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Descriptor{{ idx: {} addr: {:x} len: {} flags: {:x} next: {} }}", self.idx, self.addr, self.len, self.flags, self.next) + } +} + + diff --git a/src/vm/error.rs b/src/vm/error.rs new file mode 100644 index 0000000..1c16368 --- /dev/null +++ b/src/vm/error.rs @@ -0,0 +1,170 @@ +use std::result; +use std::error; +use std::fmt; +use std::str; +use std::ffi::CStr; +use libc; + +pub type Result = result::Result; + +#[derive(Debug)] +pub enum ErrorKind { + InvalidAddress(u64), + InvalidMappingOffset(usize), + RegisterMemoryFailed, + ReadKernelFailed, + Interrupted, + InvalidVring, + IoctlFailed(&'static str), + MissingRequiredExtension(u32), + OpenDeviceFailed, + CreateVmFailed, + BadVersion, + EventFdError, +} + +impl ErrorKind { + fn as_str(&self) -> &'static str { + match *self { + ErrorKind::InvalidAddress(..) => "Invalid guest memory address", + ErrorKind::InvalidMappingOffset(..) => "Invalid memory mapping offset", + ErrorKind::RegisterMemoryFailed => "Failed to register memory region", + ErrorKind::ReadKernelFailed => "Failed to load kernel from disk", + ErrorKind::Interrupted => "System call interrupted", + ErrorKind::InvalidVring => "Invalid Vring", + ErrorKind::IoctlFailed(..) => "Ioctl failed", + ErrorKind::MissingRequiredExtension(..) => "kernel does not support requred kvm extension", + ErrorKind::OpenDeviceFailed => "could not open /dev/kvm", + ErrorKind::CreateVmFailed => "call to create vm failed", + ErrorKind::BadVersion => "unexpected kvm api version", + ErrorKind::EventFdError => "eventfd error", + } + } +} + +impl fmt::Display for ErrorKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ErrorKind::InvalidAddress(addr) => write!(f, "{}: 0x{:x}", self.as_str(), addr), + ErrorKind::InvalidMappingOffset(offset) => write!(f, "{}: 0x{:x}", self.as_str(), offset), + ErrorKind::IoctlFailed(name) => write!(f, "Ioctl {} failed", name), + _ => write!(f, "{}", self.as_str()), + } + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Error { + Error { repr: Repr::Simple(kind) } + } +} + +enum Repr { + Errno(i32), + Simple(ErrorKind), + General(Box), +} + +#[derive(Debug)] +struct General { + kind: ErrorKind, + error: Box, +} + +#[derive(Debug)] +pub struct Error { + repr: Repr, +} + +impl Error { + pub fn new(kind: ErrorKind, error: E) -> Error + where E: Into> { + Self::_new(kind, error.into()) + } + + fn _new(kind: ErrorKind, error: Box) -> Error { + Error { + repr: Repr::General(Box::new(General{ + kind, error + })) + } + } + + pub fn from_last_errno() -> Error { + let errno = unsafe { *libc::__errno_location() }; + Error::from_errno(errno) + } + + pub fn from_errno(errno: i32) -> Error { + if errno == libc::EINTR { + Error { repr: Repr::Simple(ErrorKind::Interrupted) } + } else { + Error { repr: Repr::Errno(errno) } + } + } + + pub fn is_interrupted(&self) -> bool { + match self.repr { + Repr::Simple(ErrorKind::Interrupted) => true, + _ => false, + } + } +} + +fn error_string(errno: i32) -> String { + let mut buf = [0 as libc::c_char; 256]; + let p = buf.as_mut_ptr(); + unsafe { + if libc::strerror_r(errno as libc::c_int, p, buf.len()) < 0 { + panic!("strerror_r failed in error_string"); + } + let p = p as *const _; + str::from_utf8(CStr::from_ptr(p).to_bytes()).unwrap().to_owned() + } +} + +impl fmt::Debug for Repr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Repr::Errno(ref errno) => + f.debug_struct("Errno").field("errno", errno) + .field("message", &error_string(*errno)).finish(), + Repr::General(ref c) => f.debug_tuple("General").field(c).finish(), + Repr::Simple(ref kind) => f.debug_tuple("Kind").field(kind).finish(), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.repr { + Repr::Errno(errno) => { + let detail = error_string(errno); + write!(f, "{} (errno: {})", detail, errno) + } + Repr::General(ref c) => { + write!(f, "{}: {}", c.kind, c.error) + }, + Repr::Simple(ref kind) => kind.fmt(f), + } + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + match self.repr { + Repr::Errno(..) => "Errno Error", + Repr::Simple(ref kind) => kind.as_str(), + Repr::General(ref c) => c.error.description(), + } + } + + fn cause(&self) -> Option<&error::Error> { + match self.repr { + Repr::Errno(..) => None, + Repr::Simple(..) => None, + Repr::General(ref c) => c.error.cause(), + } + } +} + diff --git a/src/vm/io.rs b/src/vm/io.rs new file mode 100644 index 0000000..7b3c90b --- /dev/null +++ b/src/vm/io.rs @@ -0,0 +1,254 @@ +use std::sync::{Arc,RwLock,RwLockWriteGuard}; +use memory::AddressRange; + +pub trait IoPortOps: Send+Sync { + fn io_in(&mut self, port: u16, size: usize) -> u32 { + let (_,_) = (port, size); + 0 + } + fn io_out(&mut self, port: u16, size: usize, val: u32) { + let (_,_,_) = (port,size,val); + } +} + +pub trait MmioOps: Send+Sync { + fn mmio_read(&mut self, address: u64, size: usize) -> u64 { + let (_,_) = (address, size); + 0 + } + + fn mmio_write(&mut self, address: u64, size: usize, val: u64) { + let (_,_,_) = (address, size, val); + } +} + +struct IoPortDummy; + +impl IoPortOps for IoPortDummy {} + +struct IoPortPS2Control; + +impl IoPortOps for IoPortPS2Control { + fn io_in(&mut self, _port: u16, _size: usize) -> u32 { 0x02 } +} + +struct IoPortFakeI8042(bool); + +impl IoPortOps for IoPortFakeI8042 { + fn io_in(&mut self, port: u16, _size: usize) -> u32 { + if port == 0x64 { + 0x1 + } else if port == 0x61 { + 0x20 + } else { + 0 + } + } + fn io_out(&mut self, port: u16, _size: usize, val: u32) { + if port == 0x64 && val == 0xfe && !self.0 { + self.0 = true; + println!("Reset signal!"); + } + } +} + +struct IoPortEntry { + port: u16, + count: usize, + device: Arc>, +} + +impl IoPortEntry { + fn new(port: u16, count: usize, device: Arc>) -> IoPortEntry { + IoPortEntry{ port: port, count: count, device: device } + } + + fn contains(&self, port: u16) -> bool { + port >= self.port && port < (self.port + self.count as u16) + } + + fn io_in(&mut self, port: u16, size: usize) -> u32 { + let mut d = self.device.write().unwrap(); + d.io_in(port, size) + } + + fn io_out(&mut self, port: u16, size: usize, val: u32) { + let mut d = self.device.write().unwrap(); + d.io_out(port, size, val) + } +} + +struct MmioEntry { + range: AddressRange, + device: Arc>, +} + +impl MmioEntry { + fn new(range: AddressRange, device: Arc>) -> MmioEntry { + MmioEntry{ range, device } + } + + fn contains_range(&self, address: u64, length: usize) -> bool { + self.range.contains(address, length) + } + + fn read(&mut self, address: u64, size: usize) -> u64 { + self.device.write().unwrap().mmio_read(address, size) + } + + fn write(&mut self, address: u64, size: usize, val: u64) { + self.device.write().unwrap().mmio_write(address, size, val) + } +} + + +pub struct IoDispatcher { + state: RwLock, +} + +impl IoDispatcher { + pub fn new() -> Arc { + Arc::new(IoDispatcher{ + state: RwLock::new(IoDispatcherState::new()), + }) + } + + fn state_mut(&self) -> RwLockWriteGuard { + self.state.write().unwrap() + } + + pub fn register_ioports(&self, port: u16, count: usize, dev: Arc>) { + self.state_mut().register_ioports(port, count, dev) + } + + pub fn register_mmio(&self, range: AddressRange, device: Arc>) { + self.state_mut().register_mmio(range, device); + } + + pub fn emulate_io_in(&self, port: u16, size: usize) -> u32 { + self.state_mut().emulate_io_in(port, size) + + } + pub fn emulate_io_out(&self, port: u16, size: usize, val: u32) { + self.state_mut().emulate_io_out(port, size, val) + } + + pub fn emulate_mmio_read(&self, address: u64, size: usize) -> u64 { + self.state_mut().emulate_mmio_read(address, size) + } + + pub fn emulate_mmio_write(&self, address: u64, size: usize, val: u64) { + self.state_mut().emulate_mmio_write(address, size, val) + } +} + +struct IoDispatcherState { + last_unhandled_port: u16, + ioport_entries: Vec, + mmio_entries: Vec, +} + +impl IoDispatcherState { + pub fn new() -> IoDispatcherState { + let mut st = IoDispatcherState { + last_unhandled_port: 0, + ioport_entries: Vec::new(), + mmio_entries: Vec::new(), + }; + st.setup_ioports(); + st + } + + fn register_ioports(&mut self, port: u16, count: usize, dev: Arc>) { + self.ioport_entries.push(IoPortEntry::new(port, count, dev)); + } + + fn register_mmio(&mut self, range: AddressRange, device: Arc>) { + self.mmio_entries.push(MmioEntry::new(range, device)); + } + + fn mmio_for(&mut self, address: u64, size: usize) -> Option<&mut MmioEntry> { + for e in &mut self.mmio_entries { + if e.contains_range(address, size) { + return Some(e); + } + } + None + } + + fn ioports_for(&mut self, port: u16) -> Option<&mut IoPortEntry> { + for e in &mut self.ioport_entries { + if e.contains(port) { + return Some(e); + } + } + None + } + + fn emulate_io_in(&mut self, port: u16, size: usize) -> u32 { + if let Some(entry) = self.ioports_for(port) { + return entry.io_in(port, size); + } + self.debug_port(port, true); + 0 + } + + fn emulate_io_out(&mut self, port: u16, size: usize, val: u32) { + if let Some(entry) = self.ioports_for(port) { + entry.io_out(port, size as usize, val); + return; + } + self.debug_port(port, false); + } + + fn debug_port(&mut self, port: u16, is_in: bool) { + if self.last_unhandled_port != port { + self.last_unhandled_port = port; + let s = if is_in { "IN" } else { "OUT "}; + println!("unhandled io {} on port {:x}", s, port); + } + } + + pub fn emulate_mmio_write(&mut self, address: u64, size: usize, val: u64) { + match self.mmio_for(address, size) { + Some(d) => { d.write(address, size, val) }, + None => { println!("unhandled mmio write on address {:x}", address) } + } + } + + fn emulate_mmio_read(&mut self, address: u64, size: usize) -> u64 { + match self.mmio_for(address, size) { + Some(d) => { d.read(address, size) }, + None => { println!("unhandled mmio read on address {:x}", address); 0 } + } + } + + fn register_dummy(&mut self, port: u16, count: usize) { + self.register_ioports(port, count, Arc::new(RwLock::new(IoPortDummy))); + } + + fn setup_ioports(&mut self) { + /* 0000 - 001F - DMA1 controller */ + self.register_dummy(0x0000, 32); + /* 0020 - 003F - 8259A PIC 1 */ + self.register_dummy(0x0020, 2); + /* 0060 - 0068 - i8042 */ + self.register_ioports(0x0060, 8, Arc::new(RwLock::new(IoPortFakeI8042(false)))); + /* 0040 - 005F - PIT (8253,8254) */ + self.register_dummy(0x0040, 4); + /* 0092 - PS/2 system control port A */ + self.register_ioports(0x0092, 1, Arc::new(RwLock::new(IoPortPS2Control))); + /* 00A0 - 00AF - 8259A PIC 1 */ + self.register_dummy(0x00A0, 2); + /* 00C0 - 00CF - DMA1 controller */ + self.register_dummy(0x00C0, 32); + /* 00F0 - 00FF - Math co-processor */ + self.register_dummy(0x00F0, 2); + /* 0278 - 027A - Parallel printer port */ + self.register_dummy(0x0278, 3); + /* 0378 - 037A - Parallel printer port */ + self.register_dummy(0x0378, 3); + /* 03D4 - 03D5 - CRT Control registers */ + self.register_dummy(0x03D4, 2); + } +} diff --git a/src/vm/kernel_cmdline.rs b/src/vm/kernel_cmdline.rs new file mode 100644 index 0000000..828c076 --- /dev/null +++ b/src/vm/kernel_cmdline.rs @@ -0,0 +1,101 @@ +use std::ffi::OsString; +use std::os::unix::ffi::OsStrExt; + +use memory::{GuestRam,KERNEL_CMDLINE_ADDRESS}; +use super::Result; + + +fn add_defaults(cmdline: &mut KernelCmdLine, rdonly_root: bool, verbose: bool) { + let root_mount_type = if rdonly_root { "ro" } else { "rw" }; + + let output = if verbose {"earlyprintk=serial"} else {"quiet"}; + + cmdline + .push("noapic") + .push("noacpi") + // keyboard reboot + .push("reboot=k") + .push_set_true("panic") + .push_set_val("tsc", "reliable") + .push("no_timer_check") + // faster rcu updates + .push_set_true("rcuupdate.rcu_expedited") + // then restore to normal after booting + .push_set_true("rcuupdate.rcu_normal_after_boot") + .push_set_val("console", "hvc0") + + .push(root_mount_type) + .push_set_val("rootfstype", "9p") + .push_set_val("rootflags", "trans=virtio,version=9p2000.L,cache=loose") + + .push_set_true("i8042.direct") + .push_set_true("i8042.dumbkbd") + .push_set_true("i8042.nopnp") + .push_set_true("i8042.noaux") + .push("noreplace-smp") + //.push("initcall_debug") + .push_set_val("iommu", "off") + .push("cryptomgr.notests") + + .push(output) + + .push_set_val("8250.nr_uarts", "0") + //.push_set_val("init", "/home/user/virt/init"); + .push_set_val("init", "/phinit"); +} + + +pub struct KernelCmdLine { + address: u64, + buffer: OsString, +} + +impl KernelCmdLine { + pub fn new() -> KernelCmdLine { + KernelCmdLine { address: KERNEL_CMDLINE_ADDRESS, buffer: OsString::new() } + } + + pub fn new_default(verbose: bool) -> KernelCmdLine { + let mut cmdline = KernelCmdLine::new(); + add_defaults(&mut cmdline, true, verbose); + cmdline + } + + + pub fn push(&mut self, option: &str) -> &mut Self { + if !self.buffer.is_empty() { + self.buffer.push(" "); + } + self.buffer.push(option); + self + } + + pub fn push_set_true(&mut self, flag_option: &str) -> &mut Self { + self.push(&format!("{}=1", flag_option)) + } + + pub fn push_set_val(&mut self, var: &str, val: &str) -> &mut Self { + self.push(&format!("{}={}", var, val)) + } + + pub fn address(&self) -> u64 { + self.address + } + + pub fn size(&self) -> usize { + (&self.buffer).as_bytes().len() + 1 + } + + pub fn write_to_memory(&self, memory: &GuestRam) -> Result<()> { + let bs = self.buffer.as_bytes(); + let len = bs.len(); + //println!("Kernel CmdLine: {:?}", self.buffer); + //println!("writing {} command line bytes to 0x{:x}", len + 1, KERNEL_CMDLINE_ADDRESS); + memory.write_bytes(KERNEL_CMDLINE_ADDRESS, bs)?; + memory.write_int(KERNEL_CMDLINE_ADDRESS + len as u64, 0u8)?; + Ok(()) + } + + + +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs new file mode 100644 index 0000000..3ae8991 --- /dev/null +++ b/src/vm/mod.rs @@ -0,0 +1,142 @@ +use std::sync::Arc; +use std::thread; +use std::path::{PathBuf,Path}; +use std::env; + +use self::io::IoDispatcher; + +use virtio::VirtioBus; +use devices; + +use memory::{GuestRam,KVM_KERNEL_LOAD_ADDRESS}; +use kvm::*; + + +mod run; +pub mod io; +mod setup; +mod error; +mod kernel_cmdline; + +pub use self::error::{Result,Error,ErrorKind}; + + +use self::run::KvmRunArea; + +use self::kernel_cmdline::KernelCmdLine; + +pub struct VmConfig { + ram_size: usize, + ncpus: usize, + kernel_path: PathBuf, + init_path: PathBuf, +} + +#[allow(dead_code)] +impl VmConfig { + pub fn new() -> VmConfig { + VmConfig { + ram_size: 256 * 1024 * 1024, + ncpus: 1, + kernel_path: PathBuf::new(), + init_path: PathBuf::new(), + } + } + + pub fn ram_size_megs(&mut self, megs: usize) { + self.ram_size = megs * 1024 * 1024; + } + + pub fn num_cpus(&mut self, ncpus: usize) { + self.ncpus = ncpus; + } + + pub fn kernel_path(&mut self, path: &Path) { + self.kernel_path = path.to_path_buf(); + } + + pub fn init_path(&mut self, path: &Path) { + self.init_path = path.to_path_buf(); + } + + +} +pub struct Vm { + kvm: Kvm, + memory: GuestRam, + io_dispatcher: Arc, + _virtio: VirtioBus, +} + +static REQUIRED_EXTENSIONS: &[u32] = &[ + KVM_CAP_IRQCHIP, + KVM_CAP_HLT, + KVM_CAP_USER_MEMORY, + KVM_CAP_SET_TSS_ADDR, + KVM_CAP_EXT_CPUID, + KVM_CAP_IRQ_ROUTING, + KVM_CAP_IRQ_INJECT_STATUS, + KVM_CAP_PIT2, + KVM_CAP_IOEVENTFD, +]; + +impl Vm { + pub fn open(config: VmConfig) -> Result { + let mut kvm = Kvm::open(&REQUIRED_EXTENSIONS)?; + + kvm.set_tss_addr(0xFFFbd000)?; + kvm.create_pit2()?; + + let memory = GuestRam::new(config.ram_size, &kvm)?; + + kvm.create_irqchip()?; + + let verbose = env::args().any(|arg| arg == "-v"); + let cmdline = KernelCmdLine::new_default(verbose); + + cmdline.write_to_memory(&memory)?; + let path = PathBuf::from(&config.kernel_path); + setup::kernel::load_pm_kernel(&memory, &path, cmdline.address(), cmdline.size())?; + + let io_dispatch = IoDispatcher::new(); + + kvm.create_vcpus(config.ncpus)?; + + devices::rtc::Rtc::register(io_dispatch.clone()); + + if verbose { + devices::serial::SerialDevice::register(kvm.clone(),io_dispatch.clone(), 0); + } + + let mut virtio = VirtioBus::new(memory.clone(), io_dispatch.clone(), kvm.clone()); + devices::VirtioSerial::create(&mut virtio)?; + devices::VirtioRandom::create(&mut virtio)?; + devices::VirtioP9::create(&mut virtio, "/dev/root", "/", &config.init_path)?; + + setup::mptable::setup_mptable(&memory, config.ncpus, virtio.pci_irqs())?; + + Ok(Vm { + kvm, + memory, + io_dispatcher: io_dispatch, + _virtio: virtio, + }) + } + + pub fn start(&self) -> Result<()> { + let mut handles = Vec::new(); + for vcpu in self.kvm.get_vcpus() { + setup::cpu::setup_protected_mode(&vcpu, KVM_KERNEL_LOAD_ADDRESS + 0x200, &self.memory)?; + let mut run_area = KvmRunArea::new(vcpu, self.io_dispatcher.clone())?; + let h = thread::spawn(move || run_area.run()); + handles.push(h); + } + + for h in handles { + h.join().expect("..."); + } + Ok(()) + } + +} + diff --git a/src/vm/run.rs b/src/vm/run.rs new file mode 100644 index 0000000..240e12f --- /dev/null +++ b/src/vm/run.rs @@ -0,0 +1,203 @@ +use std::sync::Arc; + +use kvm::KvmVcpu; +use memory::Mapping; +use super::Result; +use super::io::IoDispatcher; + +const KVM_EXIT_UNKNOWN:u32 = 0; +const KVM_EXIT_IO:u32 = 2; +const KVM_EXIT_MMIO:u32 = 6; +const KVM_EXIT_INTR:u32 = 10; +const KVM_EXIT_SHUTDOWN:u32 = 8; +const KVM_EXIT_INTERNAL_ERROR: u32 = 17; +const KVM_EXIT_SYSTEM_EVENT:u32 = 24; + +pub struct KvmRunArea { + vcpu: KvmVcpu, + io: Arc, + mapping: Mapping, +} + +pub struct IoExitData { + dir_out: bool, + size: usize, + port: u16, + count: usize, + offset: usize, +} + +pub struct MmioExitData { + phys: u64, + size: usize, + write: bool, +} + +impl KvmRunArea { + pub fn new(vcpu: KvmVcpu, io_dispatcher: Arc) -> Result { + let size = vcpu.get_vcpu_mmap_size()?; + let mapping = Mapping::new_from_fd(vcpu.raw_fd(), size)?; + Ok(KvmRunArea{ + vcpu, + io: io_dispatcher, + mapping, + }) + } + + fn r8(&self, offset: usize) -> u8 { self.mapping.read_int(offset).unwrap() } + fn r16(&self, offset: usize) -> u16 { self.mapping.read_int(offset).unwrap() } + fn r32(&self, offset: usize) -> u32 { self.mapping.read_int(offset).unwrap() } + fn r64(&self, offset: usize) -> u64 { self.mapping.read_int(offset).unwrap() } + fn w8(&self, offset: usize, val: u8) { self.mapping.write_int(offset, val).unwrap() } + fn w16(&self, offset: usize, val: u16) { self.mapping.write_int(offset, val).unwrap() } + fn w32(&self, offset: usize, val: u32) { self.mapping.write_int(offset, val).unwrap() } + fn w64(&self, offset: usize, val: u64) { self.mapping.write_int(offset, val).unwrap() } + + fn exit_reason(&self) -> u32 { + self.r32(8) + } + + fn suberror(&self) -> u32 { + self.r32(32) + } + + fn get_io_exit(&self) -> IoExitData { + let d = self.r8(32) != 0; + let size = self.r8(33) as usize; + let port = self.r16(34); + let count = self.r32(36) as usize; + let offset = self.r64(40) as usize; + + IoExitData{ + dir_out: d, + size, + port, + count, + offset, + } + } + + fn get_mmio_exit(&self) -> MmioExitData { + let phys = self.r64(32); + let size = self.r32(48) as usize; + assert!(size <= 8); + let write = self.r8(52) != 0; + MmioExitData { + phys, size, write + } + } + + pub fn run(&mut self) { + loop { + if let Err(err) = self.vcpu.run() { + if !err.is_interrupted() { + println!("KVM_RUN returned error, bailing: {:?}", err); + return; + } + } else { + self.handle_exit(); + } + } + } + + fn handle_exit(&mut self) { + match self.exit_reason() { + KVM_EXIT_UNKNOWN => {println!("unknown")}, + KVM_EXIT_IO => { self.handle_exit_io() }, + KVM_EXIT_MMIO => { self.handle_exit_mmio() }, + KVM_EXIT_INTR => { println!("intr")}, + KVM_EXIT_SHUTDOWN => { println!("shut"); + self.handle_problem(); + }, + KVM_EXIT_SYSTEM_EVENT => { println!("event")}, + KVM_EXIT_INTERNAL_ERROR => { + let sub = self.suberror(); + println!("internal error: {}", sub); + println!("{:?}", self.vcpu.get_regs().unwrap()); + println!("{:?}", self.vcpu.get_sregs().unwrap()); + } + n => { println!("unhandled exit: {}", n);}, + } + } + + fn handle_problem(&mut self) { + let regs = self.vcpu.get_regs().unwrap(); + let sregs = self.vcpu.get_sregs().unwrap(); + println!("REGS:\n{:?}", regs); + println!("SREGS:\n{:?}", sregs); + panic!(":("); + + } + + fn handle_exit_io(&mut self) { + let exit = self.get_io_exit(); + if exit.dir_out { + self.handle_exit_io_out(&exit); + } else { + self.handle_exit_io_in(&exit); + } + } + + fn handle_exit_io_in(&mut self, exit: &IoExitData) { + for i in 0..exit.count { + let v = self.io.emulate_io_in(exit.port, exit.size); + match exit.size { + 1 => self.w8(exit.offset + i, v as u8), + 2 => self.w16(exit.offset + i * 2, v as u16), + 4 => self.w32(exit.offset + i * 4, v as u32), + _ => {}, + } + } + } + + fn handle_exit_io_out(&self, exit: &IoExitData) { + for i in 0..exit.count { + let v = match exit.size { + 1 => self.r8(exit.offset + i) as u32, + 2 => self.r16(exit.offset + i * 2) as u32, + 4 => self.r32(exit.offset + i * 4) as u32, + _ => 0, + }; + self.io.emulate_io_out(exit.port, exit.size, v); + } + } + + fn handle_exit_mmio(&mut self) { + let exit = self.get_mmio_exit(); + if exit.write { + self.handle_mmio_write(exit.phys, exit.size) + } else { + self.handle_mmio_read(exit.phys, exit.size) + } + } + + fn handle_mmio_write(&self, address: u64, size: usize) { + if let Some(val) = self.data_to_val64(size) { + self.io.emulate_mmio_write(address, size, val) + } + } + + fn handle_mmio_read(&self, address: u64, size: usize) { + if size == 1 || size == 2 || size == 4 || size == 8 { + let val = self.io.emulate_mmio_read(address, size); + match size { + 1 => self.w8(40, val as u8), + 2 => self.w16(40, val as u16), + 4 => self.w32(40, val as u32), + 8 => self.w64(40, val), + _ => (), + } + } + } + + fn data_to_val64(&self, size: usize) -> Option { + match size { + 1 => { Some(self.r8(40) as u64)} + 2 => { Some(self.r16(40) as u64)} + 4 => { Some(self.r32(40) as u64)} + 8 => { Some(self.r64(40))} + _ => { None } + } + } +} + diff --git a/src/vm/setup/cpu.rs b/src/vm/setup/cpu.rs new file mode 100644 index 0000000..1d75ac3 --- /dev/null +++ b/src/vm/setup/cpu.rs @@ -0,0 +1,198 @@ +use vm::Result; + +use kvm::{KvmVcpu,KvmRegs,KvmFpu, KvmMsrs, KvmSegment}; +use memory::{GuestRam,KERNEL_ZERO_PAGE}; + + +const MSR_IA32_SYSENTER_CS: u32 = 0x00000174; +const MSR_IA32_SYSENTER_ESP: u32 = 0x00000175; +const MSR_IA32_SYSENTER_EIP: u32 = 0x00000176; +const MSR_STAR: u32 = 0xc0000081; +const MSR_LSTAR: u32 = 0xc0000082; +const MSR_CSTAR: u32 = 0xc0000083; +const MSR_SYSCALL_MASK: u32 = 0xc0000084; +const MSR_KERNEL_GS_BASE: u32 = 0xc0000102; +const MSR_IA32_TSC: u32 = 0x00000010; +const MSR_IA32_MISC_ENABLE: u32 = 0x000001a0; + +const MSR_IA32_MISC_ENABLE_FAST_STRING: u64 = 0x01; + + +const EBX_CLFLUSH_CACHELINE: u32 = 8; // Flush a cache line size. +const EBX_CLFLUSH_SIZE_SHIFT: u32 = 8; // Bytes flushed when executing CLFLUSH. +const _EBX_CPU_COUNT_SHIFT: u32 = 16; // Index of this CPU. +const EBX_CPUID_SHIFT: u32 = 24; // Index of this CPU. +const _ECX_EPB_SHIFT: u32 = 3; // "Energy Performance Bias" bit. +const _ECX_HYPERVISOR_SHIFT: u32 = 31; // Flag to be set when the cpu is running on a hypervisor. +const _EDX_HTT_SHIFT: u32 = 28; // Hyper Threading Enabled. + +fn setup_cpuid(vcpu: &KvmVcpu) -> Result<()> { + let mut cpuid = vcpu.get_supported_cpuid()?; + let cpu_id = 0u32; // first vcpu + + for e in &mut cpuid { + match e.function { + 0 => { + e.ebx = 0x67627553; + e.ecx = 0x20487020; + e.edx = 0x68706172; + } + 1 => { + if e.index == 0 { + e.ecx |= 1<<31; + } + e.ebx = (cpu_id << EBX_CPUID_SHIFT) as u32 | + (EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT); + /* + if cpu_count > 1 { + entry.ebx |= (cpu_count as u32) << EBX_CPU_COUNT_SHIFT; + entry.edx |= 1 << EDX_HTT_SHIFT; + } + */ + } + 6 => { + e.ecx &= !(1<<3); + + } + 10 => { + if e.eax > 0 { + let version = e.eax & 0xFF; + let ncounters = (e.eax >> 8) & 0xFF; + if version != 2 || ncounters == 0 { + e.eax = 0; + } + } + + } + _ => {} + } + } + vcpu.set_cpuid2(cpuid)?; + Ok(()) +} + +fn setup_fpu(vcpu: &KvmVcpu) -> Result<()> { + let mut fpu = KvmFpu::new(); + fpu.fcw = 0x37f; + fpu.mxcsr = 0x1f80; + vcpu.set_fpu(&fpu)?; + Ok(()) +} + +fn setup_msrs(vcpu: &KvmVcpu) -> Result<()> { + let mut msrs = KvmMsrs::new(); + msrs.add(MSR_IA32_SYSENTER_CS, 0); + msrs.add(MSR_IA32_SYSENTER_ESP, 0); + msrs.add(MSR_IA32_SYSENTER_EIP, 0); + msrs.add(MSR_STAR, 0); + msrs.add(MSR_CSTAR, 0); + msrs.add(MSR_KERNEL_GS_BASE, 0); + msrs.add(MSR_SYSCALL_MASK, 0); + msrs.add(MSR_LSTAR, 0); + msrs.add(MSR_IA32_TSC, 0); + msrs.add(MSR_IA32_MISC_ENABLE, MSR_IA32_MISC_ENABLE_FAST_STRING); + vcpu.set_msrs(&msrs)?; + Ok(()) +} + + +pub fn gdt_entry(flags: u16, base: u32, limit: u32) -> u64 { + ((((base as u64) & 0xff000000u64) << (56 - 24)) | (((flags as u64) & 0x0000f0ffu64) << 40) | + (((limit as u64) & 0x000f0000u64) << (48 - 16)) | + (((base as u64) & 0x00ffffffu64) << 16) | ((limit as u64) & 0x0000ffffu64)) +} +const BOOT_GDT_OFFSET: usize = 0x500; +const BOOT_IDT_OFFSET: usize = 0x520; + +const BOOT_STACK: u64 = 0x8000; +const BOOT_PML4: u64 = 0x9000; +const BOOT_PDPTE: u64 = 0xA000; +const BOOT_PDE: u64 = 0xB000; + + +const X86_CR0_PE: u64 = 0x1; +const X86_CR0_PG: u64 = 0x80000000; +const X86_CR4_PAE: u64 = 0x20; + +const EFER_LME: u64 = 0x100; + +fn setup_boot_pagetables(memory: &GuestRam) -> Result<()> { + memory.write_int::(BOOT_PML4, BOOT_PDPTE | 0x3)?; + memory.write_int::(BOOT_PDPTE, BOOT_PDE | 0x3)?; + for i in 0..512_u64 { + let entry = (i << 21) | 0x83; + memory.write_int::(BOOT_PDE + (i * 8), entry)?; + } + Ok(()) +} + +fn write_gdt_table(table: &[u64], memory: &GuestRam) -> Result<()> { + for i in 0..table.len() { + memory.write_int((BOOT_GDT_OFFSET + i * 8) as u64, table[i])?; + } + Ok(()) +} + +pub fn setup_pm_sregs(vcpu: &KvmVcpu, memory: &GuestRam) -> Result<()> { + let table = [ + gdt_entry(0,0,0), + gdt_entry(0xa09b,0,0xfffff), + gdt_entry(0xc093,0,0xfffff), + gdt_entry(0x808b,0,0xfffff), + ]; + write_gdt_table(&table, memory)?; + + memory.write_int::(BOOT_IDT_OFFSET as u64, 0u64)?; + + let code = KvmSegment::new(0, 0xfffff, 1 * 8, 0xa09b); + let data = KvmSegment::new(0, 0xfffff, 2 * 8, 0xc093); + let tss = KvmSegment::new(0, 0xfffff, 3 * 8, 0x808b); + + let mut regs = vcpu.get_sregs()?; + + regs.gdt.base = BOOT_GDT_OFFSET as u64; + regs.gdt.limit = 32 - 1; + + regs.itd.base = BOOT_IDT_OFFSET as u64; + regs.itd.limit = 8 - 1; + + regs.cs = code; + regs.ds = data; + regs.es = data; + regs.fs = data; + regs.gs = data; + regs.ss = data; + regs.tr = tss; + + // protected mode + regs.cr0 |= X86_CR0_PE; + regs.efer |= EFER_LME; + + setup_boot_pagetables(&memory)?; + regs.cr3 = BOOT_PML4; + regs.cr4 |= X86_CR4_PAE; + regs.cr0 |= X86_CR0_PG; + + vcpu.set_sregs(®s)?; + Ok(()) +} + +pub fn setup_pm_regs(vcpu: &KvmVcpu, kernel_entry: u64) -> Result<()> { + let mut regs = KvmRegs::new(); + regs.rflags = 0x0000000000000002; + regs.rip = kernel_entry; + regs.rsp = BOOT_STACK; + regs.rbp = BOOT_STACK; + regs.rsi = KERNEL_ZERO_PAGE; + vcpu.set_regs(®s)?; + Ok(()) +} + +pub fn setup_protected_mode(vcpu: &KvmVcpu, kernel_entry: u64, memory: &GuestRam) -> Result<()> { + setup_cpuid(&vcpu)?; + setup_pm_sregs(&vcpu, memory)?; + setup_pm_regs(&vcpu, kernel_entry)?; + setup_fpu(&vcpu)?; + setup_msrs(&vcpu)?; + Ok(()) +} diff --git a/src/vm/setup/kernel.rs b/src/vm/setup/kernel.rs new file mode 100644 index 0000000..d4e79ce --- /dev/null +++ b/src/vm/setup/kernel.rs @@ -0,0 +1,118 @@ + +use std::path::Path; +use std::fs::{File}; +use std::io::{self, Read,SeekFrom,Seek}; +use byteorder::{LittleEndian,ReadBytesExt}; + +use memory::{self,GuestRam,KERNEL_ZERO_PAGE}; +use vm::{Result,Error,ErrorKind}; + + +// Documentation/x86/boot.txt + +const HDR_BOOT_FLAG: u64 = 0x1fe; // u16 +const HDR_HEADER: u64 = 0x202; // u32 +const HDR_TYPE_LOADER: u64 = 0x210; // u8 +const HDR_CMDLINE_PTR: u64 = 0x228; // u32 +const HDR_CMDLINE_SIZE: u64 = 0x238; // u32 +const HDR_KERNEL_ALIGNMENT: u64 = 0x230; // u32 + +// Documentation/x86/zero-page.txt + +const BOOT_PARAM_E820_ENTRIES: u64 = 0x1e8; +const BOOT_PARAM_E820_MAP: u64 = 0x2d0; + +const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55; +const EBDA_START: u64 = 0x0009fc00; +const KERNEL_HDR_MAGIC: u32 = 0x53726448; +const KERNEL_LOADER_OTHER: u8 = 0xff; +const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; + +const E820_RAM: u32 = 1; + +fn setup_e820(memory: &GuestRam, base: u64) -> Result<()> { + let ram_size = memory.ram_size() as u64; + + let mut e820_ranges = Vec::new(); + e820_ranges.push((0u64, EBDA_START)); + + if ram_size < memory::PCI_MMIO_RESERVED_BASE { + e820_ranges.push((memory::KVM_KERNEL_LOAD_ADDRESS, ram_size - memory::KVM_KERNEL_LOAD_ADDRESS)); + } else { + e820_ranges.push((memory::KVM_KERNEL_LOAD_ADDRESS, memory::PCI_MMIO_RESERVED_BASE - memory::KVM_KERNEL_LOAD_ADDRESS)); + e820_ranges.push((memory::HIMEM_BASE, ram_size - memory::HIMEM_BASE)); + } + memory.write_int::(base + BOOT_PARAM_E820_ENTRIES, e820_ranges.len() as u8)?; + for i in 0..e820_ranges.len() { + let entry_base = base + BOOT_PARAM_E820_MAP + (i as u64 * 20); + memory.write_int::(entry_base, e820_ranges[i].0)?; + memory.write_int::(entry_base + 8, e820_ranges[i].1)?; + memory.write_int::(entry_base + 16, E820_RAM)?; + } + Ok(()) +} + +fn setup_zero_page(memory: &GuestRam, cmdline_addr: u64, cmdline_size: usize) -> Result<()> { + let base = KERNEL_ZERO_PAGE; + memory.write_int::(base + HDR_BOOT_FLAG, KERNEL_BOOT_FLAG_MAGIC)?; + memory.write_int::(base + HDR_HEADER, KERNEL_HDR_MAGIC)?; + memory.write_int::(base + HDR_TYPE_LOADER, KERNEL_LOADER_OTHER)?; + memory.write_int::(base + HDR_CMDLINE_PTR, cmdline_addr as u32)?; + memory.write_int::(base + HDR_CMDLINE_SIZE, cmdline_size as u32)?; + memory.write_int::(base + HDR_KERNEL_ALIGNMENT, KERNEL_MIN_ALIGNMENT_BYTES)?; + + setup_e820(memory, base) +} + +pub fn load_pm_kernel(memory: &GuestRam, path: &Path, cmdline_addr: u64, cmdline_size: usize) -> Result<()> { + load_elf_kernel(memory, path).map_err(|_| Error::from(ErrorKind::ReadKernelFailed))?; + setup_zero_page(memory, cmdline_addr, cmdline_size) +} + +pub fn load_elf_kernel(memory: &GuestRam, path: &Path) -> io::Result<()> { + let mut f = File::open(&path)?; + f.seek(SeekFrom::Start(32))?; + let phoff = f.read_u64::()?; + f.seek(SeekFrom::Current(16))?; + let phnum = f.read_u16::()?; + f.seek(SeekFrom::Start(phoff))?; + let mut v = Vec::new(); + for _ in 0..phnum { + let hdr = load_phdr(&f)?; + if hdr.p_type == 1 { + v.push(hdr); + } + } + + for h in v { + f.seek(SeekFrom::Start(h.p_offset))?; + let slice = memory.mut_slice(memory::KVM_KERNEL_LOAD_ADDRESS + h.p_paddr, h.p_filesz as usize).unwrap(); + f.read_exact(slice)?; + } + Ok(()) +} + +fn load_phdr(mut r: R) -> io::Result { + let mut phdr: ElfPhdr = Default::default(); + phdr.p_type = r.read_u32::()?; + phdr.p_flags = r.read_u32::()?; + phdr.p_offset = r.read_u64::()?; + phdr.p_vaddr = r.read_u64::()?; + phdr.p_paddr = r.read_u64::()?; + phdr.p_filesz = r.read_u64::()?; + phdr.p_memsz = r.read_u64::()?; + phdr.p_align = r.read_u64::()?; + Ok(phdr) +} + +#[derive(Default,Debug)] +struct ElfPhdr { + pub p_type: u32, + pub p_flags: u32, + pub p_offset: u64, + pub p_vaddr: u64, + pub p_paddr: u64, + pub p_filesz: u64, + pub p_memsz: u64, + pub p_align: u64, +} \ No newline at end of file diff --git a/src/vm/setup/mod.rs b/src/vm/setup/mod.rs new file mode 100644 index 0000000..39f40e8 --- /dev/null +++ b/src/vm/setup/mod.rs @@ -0,0 +1,3 @@ +pub mod cpu; +pub mod kernel; +pub mod mptable; \ No newline at end of file diff --git a/src/vm/setup/mptable.rs b/src/vm/setup/mptable.rs new file mode 100644 index 0000000..7a81c24 --- /dev/null +++ b/src/vm/setup/mptable.rs @@ -0,0 +1,214 @@ +use byteorder::{LittleEndian, WriteBytesExt}; +use std::io::Write; +use std::iter; + +use memory::GuestRam; +use virtio::PciIrq; +use vm::Result; + +const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000; +const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000; + +const MP_PROCESSOR: u8 = 0; +const MP_BUS: u8 = 1; +const MP_IOAPIC: u8 = 2; +const MP_INTSRC: u8 = 3; +const MP_LINTSRC: u8 = 4; + +const MP_IRQ_SRC_INT: u8 = 0; +const MP_IRQ_SRC_NMI: u8 = 1; + +const MP_IRQ_DEFAULT: u16 = 0; + +const MPC_APIC_USABLE: u8 = 0x01; + +const KVM_APIC_VER: u8 = 0x14; + +const CPU_ENABLED: u8 = 1; +const CPU_BOOTPROCESSOR: u8 = 2; + +const CPU_STEPPING: u32 = 0x600; +const CPU_FEATURE_APIC: u32 = 0x200; +const CPU_FEATURE_FPU: u32 = 0x001; + + +const PCI_BUSID: u8 = 0; +const PCI_BUSTYPE: &[u8] = b"PCI "; +const ISA_BUSID: u8 = 1; +const ISA_BUSTYPE: &[u8] = b"ISA "; + + +struct Buffer { + vec: Vec, + count: usize, +} + +impl Buffer { + fn new() -> Buffer { + Buffer { + vec: Vec::new(), + count: 0, + } + } + + fn write_all_mpc_cpu(&mut self, ncpus: usize) -> &mut Self { + for i in 0..ncpus { + self.write_mpc_cpu(i as u8); + } + self + } + + fn write_mpc_cpu(&mut self, cpuid: u8) -> &mut Self { + self.count += 1; + let flag = CPU_ENABLED | if cpuid == 0 { CPU_BOOTPROCESSOR } else { 0 }; + let featureflag = CPU_FEATURE_APIC | CPU_FEATURE_FPU; + self.w8(MP_PROCESSOR) // type + .w8(cpuid) // Local APIC number + .w8(KVM_APIC_VER) // APIC version + .w8(flag) // cpuflag + .w32(CPU_STEPPING) // cpufeature + .w32(featureflag) // CPUID feature value + .w32(0).w32(0) // reserved[2] + } + + fn write_mpc_ioapic(&mut self, ioapicid: u8) -> &mut Self { + self.count += 1; + self.w8(MP_IOAPIC) // type + .w8(ioapicid) // Local APIC number + .w8(KVM_APIC_VER) // APIC version + .w8(MPC_APIC_USABLE) // flags + .w32(IO_APIC_DEFAULT_PHYS_BASE) // apic addr + } + + fn write_mpc_bus(&mut self, busid: u8, bustype: &[u8]) -> &mut Self { + assert!(bustype.len() == 6); + self.count += 1; + self.w8(MP_BUS) + .w8(busid) + .bytes(bustype) + } + + fn write_mpc_intsrc(&mut self, ioapicid: u8, srcbusirq: u8, dstirq: u8) -> &mut Self { + self.count += 1; + self.w8(MP_INTSRC) + .w8(MP_IRQ_SRC_INT) // irq type + .w16(MP_IRQ_DEFAULT) // irq flag + .w8(PCI_BUSID) // src bus id + .w8(srcbusirq) // src bus irq + .w8(ioapicid) // dest apic id + .w8(dstirq) // dest irq + } + + fn write_all_mpc_intsrc(&mut self, ioapicid: u8, pci_irqs: &Vec) -> &mut Self { + for irq in pci_irqs { + self.write_mpc_intsrc(ioapicid, irq.src_bus_irq(), irq.irq_line()); + } + self + } + + fn write_mpc_lintsrc(&mut self, irqtype: u8, dstirq: u8) -> &mut Self { + self.count += 1; + self.w8(MP_LINTSRC) + .w8(irqtype) // irq type + .w16(MP_IRQ_DEFAULT) // irq flag + .w8(ISA_BUSID) // src bus id + .w8(0) // src bus irq + .w8(0) // dest apic id + .w8(dstirq) // dest apid lint + } + + fn write_mpf_intel(&mut self, address: u32) -> &mut Self { + let start = self.vec.len(); + self.align(16) + .bytes(b"_MP_") // Signature + .w32(address) // Configuration table address + .w8(1) // Our length (paragraphs) + .w8(4) // Specification version + .w8(0) // checksum (offset 10) + .pad(5) // feature1 - feature5 + .checksum(start, 16, 10) + } + + fn write_mpctable(&mut self, ncpus: u16, body: &Buffer) -> &mut Self { + let len = 44 + body.vec.len(); + self.bytes(b"PCMP") // 0 Signature + .w16(len as u16) // 4 length + .w8(4) // 6 Specification version + .w8(0) // 7 checksum + .bytes(b"SUBGRAPH") // 8 oem[8] + .bytes(b"0.1 ") // 16 productid[12] + .w32(0) // 28 oem ptr (0 if not present) + .w16(body.count as u16) // 32 oem size + .w16(ncpus) // 34 oem count + .w32(APIC_DEFAULT_PHYS_BASE) // 36 APIC address + .w32(0) // 40 reserved + .bytes(&body.vec) + .checksum(0, len, 7) + } + + fn w8(&mut self, val: u8) -> &mut Self { + self.vec.push(val); + self + } + fn w16(&mut self, data: u16) -> &mut Self { + self.vec.write_u16::(data).unwrap(); + self + } + fn w32(&mut self, data: u32) -> &mut Self { + self.vec.write_u32::(data).unwrap(); + self + } + + fn bytes(&mut self, data: &[u8]) -> &mut Self { + self.vec.write(data).unwrap(); + self + } + + fn pad(&mut self, count: usize) -> &mut Self { + if count > 0 { + self.vec.extend(iter::repeat(0).take(count)); + } + self + } + + fn align(&mut self, n: usize) -> &mut Self { + let aligned = align(self.vec.len(), n); + let padlen = aligned - self.vec.len(); + self.pad(padlen) + } + + fn checksum(&mut self, start: usize, len: usize, csum_off: usize) -> &mut Self { + { + let slice = &mut self.vec[start..start + len]; + let csum = slice.iter().fold(0i32, |acc, &x| acc.wrapping_add(x as i32)); + let b = (-csum & 0xFF) as u8; + slice[csum_off] = b; + } + self + } +} + +fn align(sz: usize, n: usize) -> usize { + (sz + (n - 1)) & !(n - 1) +} + +pub fn setup_mptable(memory: &GuestRam, ncpus: usize, pci_irqs: Vec) -> Result<()> { + let ioapicid = (ncpus + 1) as u8; + //let address= align(BIOS_BEGIN as usize + BIOS_BIN.len(), 16) as u32; + let mut body = Buffer::new(); + let address = 0; + + body.write_all_mpc_cpu(ncpus) + .write_mpc_bus(PCI_BUSID, PCI_BUSTYPE) + .write_mpc_bus(ISA_BUSID, ISA_BUSTYPE) + .write_mpc_ioapic(ioapicid) + .write_all_mpc_intsrc(ioapicid, &pci_irqs) + .write_mpc_lintsrc(MP_IRQ_SRC_INT, 0) + .write_mpc_lintsrc(MP_IRQ_SRC_NMI, 1) + .write_mpf_intel(address); + + let mut table = Buffer::new(); + table.write_mpctable(ncpus as u16, &body); + //memory.write_bytes(address as u64, &table.vec) + memory.write_bytes(address as u64, &table.vec) +} \ No newline at end of file