diff --git a/.gitignore b/.gitignore index fc509a3..683ea4e 100644 --- a/.gitignore +++ b/.gitignore @@ -36,17 +36,15 @@ dist/ # Build output under projects/ -- these are programs that run on SchOS, and # their binaries were tracked by accident. -projects/schcom/schcom -projects/schcom/schcom-demo # A built ISO dropped next to the site for download.html. 62 MB; not source. docs/Website/releases/ -# Kernel modules, ever. docs/plan/ldls.md rule 1: LDLS ships the layer and -# never the thing it loads, and that applies to this tree as well as to an -# image. The loader's test input is built from ldls/test/refmod.c, which is -# ours; a .ko here would be someone else's code, with its own provenance, in -# a tree that is otherwise all first-party or attributed in NOTICE. +# Kernel modules are build artefacts, not source: they are staged into the +# image by `make ldls-r8169` / `make ldls-stage` from a Linux build and ship +# in the ISO (docs/architecture/ldls.md rule 1), but a binary does not belong +# in git. The loader's test input is built from ldls/test/refmod.c, which is +# ours. *.ko *.ko.xz diff --git a/Makefile b/Makefile index ef58aff..07a4669 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ KERN_CORE = $(addprefix $(BUILD)/kernel/core/, \ KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \ bio.o blkdev.o cdev.o device.o disk.o fb.o ide.o input.o irq.o kbd.o keymap.o mouse.o \ pci.o pciuser.o text.o ttf.o uart.o gfx.o vt.o ramdisk.o procfs.o \ - dbg.o netuser.o audio.o dmi.o) + dbg.o netuser.o audio.o dmi.o pty.o kdev.o) # Drivers for hardware that has to be *found* rather than assumed. Anything # discovered by walking a bus belongs here; the fixed-address legacy devices @@ -38,17 +38,34 @@ KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \ # already eight files with four called usb_something, and each driver added made # the directory harder to read. The compile rule uses `mkdir -p $(dir $@)`, so # the depth costs nothing. +# Which of them are compiled in is config/drivers.conf: y or n per driver, y by +# default, overridable on the command line (make DRV_E1000=n). Only the drivers +# that nothing else calls can be left out; the rest are always linked. +-include config/drivers.conf +DRV_LIST = VGA_PCI VIRTIO_GPU E1000 RTL8139 R8169 AC97 AHCI NVME +DRV_DEFAULT_R8169 = n +$(foreach d,$(DRV_LIST),$(eval DRV_$(d) ?= $(if $(DRV_DEFAULT_$(d)),$(DRV_DEFAULT_$(d)),y))) +$(foreach d,$(DRV_LIST),$(if $(filter y n,$(DRV_$(d))),,$(error config/drivers.conf: DRV_$(d) must be y or n, not '$(DRV_$(d))'))) +DRV_DEFS = $(foreach d,$(DRV_LIST),$(if $(filter y,$(DRV_$(d))),-DCONFIG_DRV_$(d)=1)) +# Rewritten only when the choice changes, so registry.o is rebuilt then and not otherwise. +DRV_STAMP = $(BUILD)/kernel/drivers/.config +$(DRV_STAMP): FORCE + @mkdir -p $(dir $@) + @echo '$(DRV_DEFS)' | cmp -s - $@ || echo '$(DRV_DEFS)' > $@ +$(BUILD)/kernel/drivers/registry.o: $(DRV_STAMP) +$(BUILD)/kernel/drivers/registry.o: CFLAGS += $(DRV_DEFS) + KERN_DRIVERS = $(BUILD)/kernel/drivers/registry.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/vga/vga_pci.o KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/vga/vga_fb.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/virtio_gpu/virtio_gpu.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/net/e1000/e1000.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/net/r8169/r8169.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/net/rtl8139/rtl8139.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/audio/ac97.o +KERN_DRIVERS += $(if $(filter y,$(DRV_VGA_PCI)),$(BUILD)/kernel/drivers/gpu/vga/vga_pci.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_VIRTIO_GPU)),$(BUILD)/kernel/drivers/gpu/virtio_gpu/virtio_gpu.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_E1000)),$(BUILD)/kernel/drivers/net/e1000/e1000.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_R8169)),$(BUILD)/kernel/drivers/net/r8169/r8169.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_RTL8139)),$(BUILD)/kernel/drivers/net/rtl8139/rtl8139.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_AC97)),$(BUILD)/kernel/drivers/audio/ac97.o) KERN_DRIVERS += $(BUILD)/kernel/drivers/bus/xhci/xhci.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/storage/ahci/ahci.o -KERN_DRIVERS += $(BUILD)/kernel/drivers/storage/nvme/nvme.o +KERN_DRIVERS += $(if $(filter y,$(DRV_AHCI)),$(BUILD)/kernel/drivers/storage/ahci/ahci.o) +KERN_DRIVERS += $(if $(filter y,$(DRV_NVME)),$(BUILD)/kernel/drivers/storage/nvme/nvme.o) KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/core/usb.o KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hid/usb_hid.o KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/storage/usb_storage.o @@ -115,8 +132,11 @@ KERN_ZSTD = $(addprefix $(BUILD)/zstd/, \ decompress/zstd_decompress_block.o) KERN_ZSTD += $(BUILD)/kernel/scff/scff.o +# GMMOS, what runs after a panic, and its micro drivers. See include/gmmos/gmmos.h. +KERN_GMMOS = $(addprefix $(BUILD)/kernel/gmmos/, gmmos.o hidkeys.o ps2.o i2c.o xhci.o) + KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \ - proc.o) + proc.o mapname.o) KERN_SYS = $(addprefix $(BUILD)/kernel/sys/, \ syscall.o sys_proc.o sys_fs.o sys_mem.o sys_net.o \ @@ -126,7 +146,8 @@ KERN_SYS = $(addprefix $(BUILD)/kernel/sys/, \ KERN_ARCH += $(BUILD)/kernel/arch/x86_64/vectors.o KERNOBJS = $(KERN_ARCH) $(KERN_CORE) $(KERN_DEV) $(KERN_DRIVERS) \ - $(KERN_FS) $(KERN_NET) $(KERN_ACPI) $(KERN_ZSTD) $(KERN_PROC) $(KERN_SYS) + $(KERN_FS) $(KERN_NET) $(KERN_ACPI) $(KERN_ZSTD) $(KERN_GMMOS) \ + $(KERN_PROC) $(KERN_SYS) # entry.o must be first in link order; remove it from KERNOBJS for the rule KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS)) @@ -140,7 +161,7 @@ KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS)) # Third-party software does NOT go here — see ports/. # ----------------------------------------------------------------------- ULIB = $(addprefix $(BUILD)/lib/, start.o glue.o usys.o) -CMDS = schinit schinitd fbinfo shmtest socktest inputtest nettest reflink schattr ping chvt loadkeys sigtest memfdtest pcitest vgpud schdbg schdbgd resolvconf httpd schmode netecho ldlsd tone hwreport dynhello evtest xready locktest +CMDS = schinit schinitd fbinfo shmtest socktest inputtest nettest reflink schattr ping chvt loadkeys sigtest memfdtest pcitest vgpud schdbg schdbgd resolvconf httpd schmode netecho ldlsd tone hwreport dynhello evtest xready locktest su passwd CMDBINS = $(addprefix $(BUILD)/cmd/, $(CMDS)) # ----------------------------------------------------------------------- @@ -524,17 +545,24 @@ endif # # Evaluated when the Makefile is read, so the wipe happens before anything is # compiled against the wrong setting. +# +# Only the two objects that read RAMDISK_ROOT are dropped: kernel/proc/proc.c +# (ROOTDEV is used nowhere else) and kernel/dev/ramdisk.c. The whole of +# build/kernel, build/lib and build/cmd used to go, which made every `make iso` +# recompile the kernel and every command, rebuild the root image from the new +# binaries and compress it again at zstd -19 -- minutes, for a one-line change. +# If another file ever starts to use ROOTDEV or RAMDISK_ROOT, add it here. RAMROOT_STATE := $(if $(RAMROOT),on,off) RAMROOT_STAMP := $(BUILD)/ramroot.state $(shell mkdir -p $(BUILD); \ if [ "$$(cat $(RAMROOT_STAMP) 2>/dev/null)" != "$(RAMROOT_STATE)" ]; then \ - rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd; \ + rm -f $(BUILD)/kernel/proc/proc.o $(BUILD)/kernel/dev/ramdisk.o; \ printf '%s' '$(RAMROOT_STATE)' > $(RAMROOT_STAMP); \ fi) # ----------------------------------------------------------------------- # The root filesystem is an Alpine root that apk manages, with SchOS installed -# into it as the package schos-base -- see docs/plan/alpine.md and "The +# into it as the package schos-base -- see docs/architecture/packages.md and "The # apk-managed root" below. Both images are made from it: schfs.img, which # `make qemu` boots, and the ramdisk inside the kernel, which `make iso` # boots. sysroot/ is what schos-base is packaged from, not a root of its own. @@ -625,7 +653,7 @@ UEFI_CFLAGS = $(UEFI_DEBUG_FLAGS) --target=x86_64-unknown-windows -ffreestanding # Hence the check and the message. It is worth the four lines. $(BUILD)/uefi/BOOTX64.EFI: boot/uefi/main.c boot/uefi/efi.h include/arch/schboot.h \ boot/common/ui.c boot/common/ui.h \ - include/gfx/logo.h include/gfx/theme.h include/gfx/font.h + include/gfx/logo.h include/gfx/theme.h include/gfx/font.h include/gfx/nvfont.h @mkdir -p $(BUILD)/uefi @command -v clang >/dev/null 2>&1 || { \ echo ""; \ @@ -734,7 +762,6 @@ qemu-uefi: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS) # silently reused for the other, leaving ROOTDEV pointing at the wrong device. # Dropping the compiled objects first makes the switch always correct. ramroot: - rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd $(MAKE) RAMROOT=1 cmd: $(CMDBINS) @@ -752,7 +779,7 @@ ports: # # Staged into $(SYSROOT) so the next image build picks them up; run # `make projects && make` to get them onto a disk. -PROJECTS ?= schcom +PROJECTS ?= projects: @test -d $(MUSLPREFIX)/include || { \ @@ -777,7 +804,7 @@ projects: # CFLAGS and make cannot see that. # ----------------------------------------------------------------------- iso: - rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd + @$(MAKE) --no-print-directory ldls-r8169-if-present $(MAKE) RAMROOT=1 schos.iso # One El Torito entry, pointing at a FAT image holding the loader and kernel. @@ -852,13 +879,13 @@ $(BUILD)/kernel/kernel: $(BUILD)/kernel/kernel.pass1 $(BUILD)/kernel/ksym.o $(BUILD)/fs-ram.img: $(MKSCHFS) $(ROOT_STAMP) @mkdir -p $(BUILD) @rm -f $@ - @used=$$($(MKSCHFS) $@ --size 262144 --label schos \ + @used=$$($(MKSCHFS) $@ --size 262144 --label schos --owners $(AROOT_DIR)/owners \ $(RAMDISK_EXCLUDE_ARGS) --sysroot $(ROOT_TREE) | \ sed -n 's/^ *\([0-9][0-9]*\) of [0-9][0-9]* blocks used.*/\1/p'); \ test -n "$$used" || { echo "fs-ram.img: mkschfs did not say how much it used" >&2; exit 1; }; \ rm -f $@; \ $(MKSCHFS) $@ --size $$((used + used / 8 + $(RAMDISK_SLACK))) --label schos \ - $(RAMDISK_EXCLUDE_ARGS) --sysroot $(ROOT_TREE) + --owners $(AROOT_DIR)/owners $(RAMDISK_EXCLUDE_ARGS) --sysroot $(ROOT_TREE) # The image goes into the kernel as SCFF (include/fs/scff.h): 128 KB chunks, # each its own zstd frame, unpacked by the kernel the first time a block in it @@ -963,6 +990,10 @@ $(BUILD)/kernel/dev/%.o: kernel/dev/%.c @mkdir -p $(BUILD)/kernel/dev $(CC) $(CFLAGS) -c $< -o $@ +$(BUILD)/kernel/gmmos/%.o: kernel/gmmos/%.c + @mkdir -p $(BUILD)/kernel/gmmos + $(CC) $(CFLAGS) -c $< -o $@ + # `$(dir $@)` rather than a fixed path: drivers are grouped into one directory # per device class (gpu/, net/, bus/, usb/), so the output directory varies with # the target and a hardcoded mkdir only creates the top one. @@ -1089,22 +1120,30 @@ $(BUILD)/lib/%.o: lib/%.S # own -- a specific rule beats the pattern one below. ldls/ko.c, btf.c and # stub.c are freestanding and build unchanged for either side, which is the # whole point of having written them that way. +# A Linux module is compiled with an 8-byte-aligned stack and, being kernel +# code, never sets %al for a variadic call -- so it arrives in this code with +# the stack off by eight and %al garbage. Any function here that takes `...` +# then opens with `movaps %xmm0, -0xb0(%rbp)`, which faults on a misaligned +# address: that was the general-protection fault in ldlsd on real hardware, in +# the log function, the moment the first driver message was printed. +# -mstackrealign makes every function re-align its own stack on entry. +LDLS_ALIGN = -mstackrealign LDLS_GUEST = $(addprefix $(BUILD)/ldls/guest/, ko.o btf.o stub.o) \ - $(addprefix $(BUILD)/ldls/guest/kpi/, kpi.o pci.o io.o net.o skb.o string.o work.o thunk.o) + $(addprefix $(BUILD)/ldls/guest/kpi/, kpi.o pci.o io.o net.o skb.o string.o work.o thunk.o base.o) $(BUILD)/ldls/guest/%.o: ldls/%.c ldls/ko.h ldls/btf.h ldls/stub.h @mkdir -p $(BUILD)/ldls/guest - $(CC) $(CFLAGS) -c $< -o $@ + $(CC) $(CFLAGS) $(LDLS_ALIGN) -c $< -o $@ # The KPI -- the Linux symbols, implemented. Guest-only: it reaches /dev/pci # and /dev/netdev, which exist on SchOS and nowhere else. -$(BUILD)/ldls/guest/kpi/%.o: ldls/kpi/%.c ldls/kpi/kpi.h +$(BUILD)/ldls/guest/kpi/%.o: ldls/kpi/%.c ldls/kpi/kpi.h ldls/kpi/link_modes.inc @mkdir -p $(BUILD)/ldls/guest/kpi - $(CC) $(CFLAGS) -I./ldls -c $< -o $@ + $(CC) $(CFLAGS) $(LDLS_ALIGN) -I./ldls -c $< -o $@ $(BUILD)/cmd/ldlsd: cmd/ldlsd/main.c $(ULIB) $(LDLS_GUEST) @mkdir -p $(BUILD)/cmd - $(CC) $(CFLAGS) -I./ldls -c $< -o $(BUILD)/cmd/ldlsd.o + $(CC) $(CFLAGS) $(LDLS_ALIGN) -I./ldls -c $< -o $(BUILD)/cmd/ldlsd.o $(LD) $(LDFLAGS) $(LD_LOWBASE) -e _start -Ttext 0x4000 -o $@ \ $(BUILD)/cmd/ldlsd.o $(LDLS_GUEST) $(ULIB) $(OBJCOPY) -S $@ @@ -1135,6 +1174,22 @@ $(BUILD)/cmd/xready: cmd/xready/main.c $(MUSLPREFIX)/lib/libc.so $(LINUXHDR) $(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o $(BUILD)/cmd/xready.o \ -L$(MUSLPREFIX)/lib -lc $(PORTEND) +# su and passwd: musl, for getpwnam and crypt, and static -- they run setuid root, +# so there is no loader or library path to be led astray. +$(BUILD)/cmd/su: cmd/su/main.c $(MUSLPREFIX)/lib/libc.so + @mkdir -p $(BUILD)/cmd + $(DYNCC) -fno-pic -c $< -o $(BUILD)/cmd/su.o + $(DYNCC) -static -no-pie -nostdlib -o $@ \ + $(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o $(BUILD)/cmd/su.o \ + -L$(MUSLPREFIX)/lib -lc $(PORTEND) + +$(BUILD)/cmd/passwd: cmd/passwd/main.c $(MUSLPREFIX)/lib/libc.so + @mkdir -p $(BUILD)/cmd + $(DYNCC) -fno-pic -c $< -o $(BUILD)/cmd/passwd.o + $(DYNCC) -static -no-pie -nostdlib -o $@ \ + $(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o $(BUILD)/cmd/passwd.o \ + -L$(MUSLPREFIX)/lib -lc $(PORTEND) + $(BUILD)/cmd/locktest: cmd/locktest/main.c $(MUSLPREFIX)/lib/libc.so @mkdir -p $(BUILD)/cmd $(DYNCC) -fno-pic -c $< -o $(BUILD)/cmd/locktest.o @@ -1308,7 +1363,7 @@ crashqr: $(PYTHON) tools/python/crashqr.py # ----------------------------------------------------------------------- -# LDLS -- the Linux Driver Layer. docs/plan/ldls.md. +# LDLS -- the Linux Driver Layer. docs/architecture/ldls.md. # # ldls/ko.c is freestanding and builds on both sides: here as part of a host # tool, and later as part of the SchOS-side host. Same discipline as SchFS, @@ -1333,7 +1388,7 @@ $(LDLS_RUN): ldls/host/run.c ldls/ko.c ldls/stub.c ldls/btf.c \ $(HOSTCC) $(HOSTWARN) -O1 -fno-strict-aliasing -o $@ \ ldls/host/run.c ldls/ko.c ldls/stub.c ldls/btf.c -# The test object, compiled and never linked. Ours, because docs/plan/ldls.md +# The test object, compiled and never linked. Ours, because docs/architecture/ldls.md # rule 1 means no .ko is committed here -- see the header of refmod.c. # # -fno-pic matters: a PIC object routes its data through the GOT, and a GOT is @@ -1356,13 +1411,12 @@ ldls: $(LDLS_INSPECT) $(LDLS_RUN) $(LDLS_TYPES) # # make ldls-stage KO=/path/to/8139too.ko BTF=/sys/kernel/btf/vmlinux # -# This is the model docs/plan/ldls.md describes, carried out on the machine -# that is doing the testing: the user obtains a module and loads it on their -# own machine. Deliberately **not** a dependency of `all`, so an ordinary build -# never picks it up, and `.gitignore` refuses `*.ko` so it cannot be committed -# by accident. An image built after this has somebody else's GPL code in it and -# must not be redistributed -- which is exactly what licensing.md §6 says about -# the combination, and why it is one command you have to type on purpose. +# For trying any module, not just the one that ships. Deliberately **not** a +# dependency of `all`, so an ordinary build never picks it up, and `.gitignore` +# refuses `*.ko` so a binary cannot be committed by accident. An image with a +# module in it is a distribution of that module's code, under its own licence +# (docs/licensing.md §6) -- fine for the ones SchOS ships (r8169, below), and +# a thing to check for any other. ldls-stage: @test -n "$(KO)" || { echo "usage: make ldls-stage KO= [BTF=]"; exit 1; } @mkdir -p $(SYSROOT)/lib/modules @@ -1370,6 +1424,55 @@ ldls-stage: @test -z "$(BTF)" || cp "$(BTF)" $(SYSROOT)/lib/modules/btf @echo "staged into $(SYSROOT)/lib/modules -- this image is now yours alone" +# Linux's r8169 and what it needs, from the kernel this machine runs, for +# etc/schinit/ldls-r8169.job. The module set is r8169's dependency closure less +# what LDLS answers itself (led-class, of_mdio, fwnode_mdio, fixed_phy), in load +# order; the firmware is the RTL8168/8111 PHY patches. These are the modules and +# firmware SchOS ships in its ISO (docs/licensing.md §6, NOTICE): run this before +# `make iso` for an image that has the laptop's network. +LDLS_KDIR ?= /run/current-system/kernel-modules/lib/modules/$(shell uname -r)/kernel +LDLS_FWDIR ?= /run/current-system/firmware +LDLS_R8169 = drivers/net/phy/mdio-bus drivers/net/phy/libphy \ + drivers/net/phy/mdio_devres drivers/net/phy/realtek/realtek \ + drivers/net/ethernet/realtek/r8169 +# Every file is replaced only if its contents differ. sysroot.stamp records the +# mtime of each staged file, `make iso` runs this every time, and rewriting the +# same bytes moved every mtime -- which rebuilt the root image and compressed it +# at zstd -19 (a minute) after a one-line kernel change. + +ldls-r8169: + @mkdir -p $(SYSROOT)/lib/modules $(SYSROOT)/lib/firmware/rtl_nic + @set -e; list=; for m in $(LDLS_R8169); do \ + f=$$(ls $(LDLS_KDIR)/$$m.ko* | head -1); n=$$(basename $$m).ko; \ + t=$(SYSROOT)/lib/modules/$$n; \ + case $$f in *.xz) xz -dc $$f;; *.zst) zstd -qdc $$f;; *) cat $$f;; esac > $$t.new; \ + if cmp -s $$t.new $$t; then rm -f $$t.new; else mv -f $$t.new $$t; fi; \ + list=$$list$${list:+,}/lib/modules/$$n; done; \ + if ! cmp -s /sys/kernel/btf/vmlinux $(SYSROOT)/lib/modules/btf; then \ + cp /sys/kernel/btf/vmlinux $(SYSROOT)/lib/modules/btf; fi; \ + printf '#!/bin/sh\nmkdir -p /var/log\nexec /bin/ldlsd %s /lib/modules/btf > /var/log/ldls-r8169.log 2>&1\n' \ + "$$list" > $(SYSROOT)/lib/modules/ldls-r8169.sh.new; \ + if cmp -s $(SYSROOT)/lib/modules/ldls-r8169.sh.new $(SYSROOT)/lib/modules/ldls-r8169.sh; then \ + rm -f $(SYSROOT)/lib/modules/ldls-r8169.sh.new; \ + else mv -f $(SYSROOT)/lib/modules/ldls-r8169.sh.new $(SYSROOT)/lib/modules/ldls-r8169.sh; fi + @for f in $(LDLS_FWDIR)/rtl_nic/rtl8168*.fw*; do \ + n=$$(basename $$f); case $$n in \ + *.zst) out=$${n%.zst}; zstd -qdc $$f > $(SYSROOT)/lib/firmware/rtl_nic/$$out.new;; \ + *.xz) out=$${n%.xz}; xz -dc $$f > $(SYSROOT)/lib/firmware/rtl_nic/$$out.new;; \ + *) out=$$n; cp $$f $(SYSROOT)/lib/firmware/rtl_nic/$$out.new;; esac; \ + d=$(SYSROOT)/lib/firmware/rtl_nic/$$out; \ + if cmp -s $$d.new $$d; then rm -f $$d.new; else mv -f $$d.new $$d; fi; done + @echo "staged Linux r8169 ($(shell uname -r)) into $(SYSROOT)" + +# `make iso` runs this first: the ISO ships Linux's r8169 set (NOTICE), so stage it whenever +# this machine has the modules and the BTF to stage it from, and say so when it does not. +ldls-r8169-if-present: + @if ls $(LDLS_KDIR)/drivers/net/ethernet/realtek/r8169.ko* >/dev/null 2>&1 && [ -r /sys/kernel/btf/vmlinux ]; then \ + $(MAKE) --no-print-directory ldls-r8169; \ + else \ + echo "iso: no Linux r8169 modules or BTF on this machine ($(LDLS_KDIR)) -- this ISO will not carry them"; \ + fi + ldls-unstage: rm -rf $(SYSROOT)/lib/modules @echo "removed" @@ -1423,19 +1526,27 @@ LOGO_ASCII = docs/branding/Ascii/ascii_50.txt # proves the same thing far less clearly. SRV_FILES = $(wildcard srv/*) +# cp -p, not cp: sysroot.stamp lists every staged file with its mtime, and the +# Alpine root, the root image and its zstd -19 compression are all rebuilt when +# it changes. A plain cp gave every copy a new mtime, so every `make iso` +# rebuilt all of them -- over a minute of CPU for no change at all. etc-install: $(SCHINIT_JOBS) $(ETC_PLAIN) $(ETC_ZSH) $(LOGO_ASCII) $(SRV_FILES) @mkdir -p $(SYSROOT)/etc/schinit $(SYSROOT)/etc/zsh $(SYSROOT)/usr/share/schos $(SYSROOT)/srv - @cp $(ETC_PLAIN) $(SYSROOT)/etc/ - @cp $(SCHINIT_JOBS) $(SYSROOT)/etc/schinit/ - @cp $(ETC_ZSH) $(SYSROOT)/etc/zsh/ - @cp $(LOGO_ASCII) $(SYSROOT)/usr/share/schos/logo.txt - @cp $(SRV_FILES) $(SYSROOT)/srv/ + @cp -p $(ETC_PLAIN) $(SYSROOT)/etc/ + @cp -p $(SCHINIT_JOBS) $(SYSROOT)/etc/schinit/ + @cp -p $(ETC_ZSH) $(SYSROOT)/etc/zsh/ + @cp -p $(LOGO_ASCII) $(SYSROOT)/usr/share/schos/logo.txt + @cp -p $(SRV_FILES) $(SYSROOT)/srv/ @# The device-name databases, where lspci and lsusb look for them: the @# hwdata package's directory. They were at /usr/share/usb.ids, which no @# tool searches, so lsusb printed numbers only. @mkdir -p $(SYSROOT)/usr/share/hwdata @rm -f $(SYSROOT)/usr/share/usb.ids - @for f in usb.ids pci.ids; do [ -f dist/$$f ] && cp dist/$$f $(SYSROOT)/usr/share/hwdata/; done; true + @for f in usb.ids pci.ids; do [ -f dist/$$f ] && cp -p dist/$$f $(SYSROOT)/usr/share/hwdata/; done; true + @# The licences travel with the image: SchOS's, xv6's, and NOTICE, which carries the + @# third-party ones -- including Linux's r8169 modules, whose source offer it records. + @mkdir -p $(SYSROOT)/usr/share/licenses/schos + @cp -p LICENSE LICENSE.MIT NOTICE $(SYSROOT)/usr/share/licenses/schos/ @echo "installed $(words $(ETC_PLAIN)) /etc file(s), $(words $(ETC_ZSH)) zsh file(s) and $(words $(SCHINIT_JOBS)) schinit manifest(s)" # The mount points. Created here rather than by init, because a mkdir on a @@ -1549,15 +1660,6 @@ $(BUILD)/fs.img: $(MKFS) $(BUILD)/sysroot.stamp # it gets worse -- because stage2 loads the kernel by PIO one sector at a time, # and under KVM every one of those port accesses is a VM exit, where under TCG # it is cheap. -# Schcom, end to end, checked from outside the machine. -# -# Boots, starts the compositor and a client, and then screendumps the -# framebuffer and asserts the pixels on the host. That last part is the whole -# value: the compositor saying "I composited it" proves nothing, and a -# screenshot nobody inspects proves less. Needs `make projects` first. -schcom-test: esp $(BUILD)/schfs.img - sh tools/posix/schcom_test.sh - # The firmware and the disks, in one place. Every run target below boots the # same way, because there is only one way to boot now: OVMF, the staged ESP as # the first disk, the SchFS volume as the second. Keeping the argument list in @@ -1626,7 +1728,7 @@ SCHFS_BLOCKS ?= 262144 $(BUILD)/schfs.img: $(MKSCHFS) $(ROOT_STAMP) @mkdir -p $(BUILD) @rm -f $@ - ./$(MKSCHFS) $@ --size $(SCHFS_BLOCKS) --label schos --sysroot $(ROOT_TREE) + ./$(MKSCHFS) $@ --size $(SCHFS_BLOCKS) --label schos --owners $(AROOT_DIR)/owners --sysroot $(ROOT_TREE) # --- The apk-managed root ------------------------------------------------------ # @@ -1693,6 +1795,13 @@ LOGO_PX = 128 logo: $(PYTHON) tools/python/mklogo.py "$(LOGO_SRC)" include/gfx/logo.h $(LOGO_PX) +# include/gfx/nvfont.h: the boot manager's typeface (Instrument Sans, from +# docs/Website/niveus/fonts), rasterised once and committed, same arrangement as +# logo.h. Needs Pillow, so it is run by hand: nix-shell -p 'python3.withPackages +# (p: [p.pillow])' --run "make nvfont" +nvfont: + $(PYTHON) tools/python/mknvfont.py docs/Website/niveus/fonts/static include/gfx/nvfont.h + # The USB vendor/product name database, from linux-usb.org. # # Dual-licensed GPL-2.0-or-later **or** 3-clause BSD; SchOS takes the BSD @@ -1754,10 +1863,10 @@ qrtest: $(QRTEST) qrcheck: $(QRTEST) $(PYTHON) tools/python/qrcheck.py -.PHONY: disk all kernel ramroot cmd ports projects sysroot-install qemu qemu-console qemu-kvm schcom-test \ +.PHONY: disk all kernel ramroot cmd ports projects sysroot-install qemu qemu-console qemu-kvm \ schfs-test schfs-objtest schfs-image-test schfs-check kmem-test mkschfs \ schfs-fsck crashshot qemu-schfs iso qemu-iso clean distclean \ - logo qrtest qrcheck crashqr usb-ids pci-ids hwdata ovmf esp uefi + logo nvfont qrtest qrcheck crashqr usb-ids pci-ids hwdata ovmf esp uefi .PRECIOUS: $(BUILD)/%.o # 'clean' removes generated output only. dist/ (upstream tarballs) survives, @@ -1781,3 +1890,6 @@ distclean: clean # build output: Alpine's usr/lib/sysctl.d is a directory, and including it # stopped every make with "Is a directory". -include $(shell find $(BUILD) \( -path $(BUILD)/aroot -o -path $(BUILD)/iso -o -path $(BUILD)/ports \) -prune -o -type f -name '*.d' -print 2>/dev/null) + +FORCE: +.PHONY: FORCE ldls-r8169-if-present diff --git a/NOTICE b/NOTICE index 4578dce..f533227 100644 --- a/NOTICE +++ b/NOTICE @@ -191,6 +191,36 @@ the whole library on the host to write it. zstd is dual-licensed; SchOS takes it under the BSD licence above. The glue written for SchOS (kernel/scff/) is GPL-2.0-or-later and is not part of zstd; zstd's own sources are unmodified. +-------------------------------------------------------------------------------- +Linux kernel drivers (r8169 and its modules) GPL-2.0-only +-------------------------------------------------------------------------------- +Copyright (c) the Linux kernel contributors + +The ISO carries these Linux kernel modules, unmodified, as built for the +kernel version that `make ldls-r8169` took them from (see +/var/log/ldls-r8169.log on a running system for the version): + + drivers/net/phy/mdio-bus.ko drivers/net/phy/libphy.ko + drivers/net/phy/mdio_devres.ko drivers/net/phy/realtek/realtek.ko + drivers/net/ethernet/realtek/r8169.ko + +They are loaded into an ordinary process by ldlsd (ldls/, cmd/ldlsd), which is +GPL-2.0-or-later and part of SchOS, and not linked into the kernel. The +licence of the modules is the GNU General Public License, version 2 only; the +corresponding source is the same release of the Linux kernel, available from +https://www.kernel.org/ . The BTF type data staged beside them +(/lib/modules/btf) is generated from that kernel build. + +-------------------------------------------------------------------------------- +Realtek RTL8168/8111 PHY firmware (rtl_nic/rtl8168*.fw) Realtek / linux-firmware +-------------------------------------------------------------------------------- +Copyright (c) Realtek Semiconductor Corp. + +Redistributed unmodified from the linux-firmware collection, under the terms +in its WHENCE file and the Realtek licence there: redistribution in binary +form is permitted provided the copyright notice is reproduced. Staged by +`make ldls-r8169` into /lib/firmware/rtl_nic and read by the PHY driver. + -------------------------------------------------------------------------------- Intel 8254x device identifiers BSD 3-Clause -------------------------------------------------------------------------------- diff --git a/README.md b/README.md index 87f9dfc..e8244db 100644 --- a/README.md +++ b/README.md @@ -4,48 +4,50 @@ # SchOS -**A 64-bit operating system, written from the metal up.** +**A 64-bit operating system, written from the metal up — and it runs Linux's software.** -SMP kernel · copy-on-write filesystem · Linux syscall ABI · Alpine packages · X11 on real hardware +SMP kernel · copy-on-write filesystem · Linux syscall ABI · Alpine packages · Xorg · Linux's own drivers --- -SchOS began as [xv6](https://github.com/mit-pdos/xv6-public) and is now mostly -something else. The scheduler, the memory manager and the trap path have been -rewritten for x86-64 and for more than one processor; the filesystem has been -replaced outright; the syscall table speaks Linux numbering, so real Linux -software runs on it unmodified — statically or dynamically linked, straight -out of Alpine's package repositories. +SchOS is a hobby operating system built by one person. It began as +[xv6](https://github.com/mit-pdos/xv6-public) and almost nothing of that is left: +the scheduler, the memory manager, the trap path and every driver have been +rewritten for x86-64 and for more than one processor, and the filesystem is a +different one. The syscall table speaks Linux's numbering, so **real Linux +programs run on it unmodified** — statically or dynamically linked, straight out +of Alpine's package repositories — and a desktop (Xorg and Window Maker) and a +web browser (NetSurf) come up on the framebuffer. -It is a hobby system built by one person, and it is honest about that: what -works below has been *run*, not inferred, and the gaps are listed as plainly as -the features. +It is honest about being a hobby system: what is listed below has been *run*, +not inferred, and [the gaps](#not-there-yet) are listed as plainly as the +features. It boots from a USB stick on three different real machines. ## In numbers | | | |---|---| -| **First-party code** | 61,367 lines, plus 21,128 lines of comments (26%) in 272 files | -| **Kernel** (`kernel/`, `include/`) | 45,207 lines of code, 16,111 of comments | -| **Syscalls** | 208, of which 184 on Linux numbering | +| **First-party code** | 72,766 lines, plus 23,670 lines of comments (25%) in 307 files | +| **Kernel** (`kernel/`, `include/`) | 51,893 lines of code, 17,213 of comments | +| **Syscalls** | 215, of which 193 on Linux numbering | | **Drivers** | 15 in the kernel | | **First-party programs** | 29 in `cmd/` | -| **Alpine packages in the root** | 88, from 11 asked for in `ports/alpine/world` | -| **History** | 253 commits since 2026-08-09 (76 days) | +| **Alpine packages in the root** | 179, from 29 asked for in `ports/alpine/world` | +| **History** | 292 commits since 2026-08-09 (78 days) |
Lines by language | Language | Code | Comments | |---|--:|--:| -| C | 56,580 | 19,427 | +| C | 67,615 | 21,904 | | Assembly | 1,750 | 336 | | PowerShell | 1,362 | 638 | -| Python | 759 | 147 | -| Shell | 716 | 469 | +| Python | 943 | 150 | +| Shell | 896 | 531 | | Perl | 129 | 45 | | Linker script | 71 | 66 | @@ -55,78 +57,74 @@ the features. ports/ and generated tables are not counted. -## The pieces +## What it is made of | Layer | Name | What it is | |---|---|---| | System | **SchOS** | The distribution — kernel, userland, tooling | -| Kernel | **SchIFT** | Multi-core x86-64 kernel with a modular VFS | +| Kernel | **SchIFT** | Multi-core x86-64 kernel with a modular VFS and Linux's syscall ABI | | Filesystem | **SchFS** | Transactional, copy-on-write, the root filesystem | | Image format | **SCFF** | The SchOS Compressed File Format the live root ships in | -| API | **SchAPI** | System programming interface — the stable surface | -| Compositor | **SchCom** | Display compositor *(planned)* | -| Window manager | **SchWM** | *(planned)* | -| Desktop | **SchDE** | *(planned)* | +| Packages | **apk** | Alpine's package manager manages the root; SchOS itself is the package `schos-base` | +| Desktop | **Xorg + Window Maker** | Alpine's, on the framebuffer | +| Crash screen | **PSOGM / GMMOS** | The Purple Screen of Guru Meditation, and the micro OS that runs under it | +| API | **SchAPI** | The long-term native interface — *designed, not built* | `uname -s` reports `SchIFT`, the way every Unix reports its kernel rather than its distribution. ## What works -**Kernel** — Long mode, 4-level paging, preemptive multitasking. **SMP** up to -64 processors, enumerated from the ACPI MADT. ACPI through -[uACPI](https://github.com/uACPI/uACPI): interrupt routing from `_PRT`, the -power button, clean power-off and reset. Memory sized from the E820/UEFI map. +**Kernel** — Long mode, 4-level paging, preemptive multitasking, a fair-share +scheduler with `nice`. **SMP** up to 64 processors, enumerated from the ACPI +MADT. **Threads** (`clone`, `futex`, TLB shootdowns), **demand paging** for the +huge reservations browsers make, and dynamic linking. ACPI through +[uACPI](https://github.com/uACPI/uACPI): interrupt routing, the power button, +clean power-off and reset, batteries and AC through a laptop's embedded +controller, and deep idle states. Memory sized from the firmware's map, up to 256 GB. -**Filesystem** — **SchFS** is the root: a copy-on-write B-tree with a -uberblock ring, so a crash leaves either the old tree or the new one and there -is nothing to repair. The live image ships as **[SCFF](docs/architecture/scff.md)**: -the 358 MB root compresses to 102 MB with zstd and is unpacked a chunk at a -time, the first time something reads it. `/proc` has `/proc//fd`, -`/proc/self` and the rest that `ps`, `top` and Xorg read. +**Filesystem** — **SchFS** is the root: a copy-on-write B-tree with an uberblock +ring, so a crash leaves either the old tree or the new one and there is nothing +to repair. The live image ships as **[SCFF](docs/architecture/scff.md)**: the +root compresses with zstd to about a third of its size and is unpacked a chunk at +a time, the first time something reads it. `/proc`, `/sys` and `/dev` are +synthesised, in Linux's layouts, because that is what the programs read. -**Processes** — fork, exec, wait, pipes, signals, sessions and job control, -`mmap` (anonymous and file-backed), POSIX shared memory, `memfd`, Unix domain -sockets with descriptor passing (`SCM_RIGHTS`), `poll`, `epoll`, `eventfd`, -`timerfd`, `signalfd`. **Dynamic linking**: `exec` loads `ld-musl`, so -binaries come from Alpine as they are. +**Linux software** — `fork`, `clone`, `exec`, pipes, signals, sessions and job +control, `mmap`, POSIX and `memfd` shared memory, Unix sockets with descriptor and +credential passing, `poll`, `epoll`, `eventfd`, `timerfd`, `signalfd`, `inotify`, +pseudo-terminals. The root is managed by Alpine's **apk**: `ports/alpine/world` +says what is installed, `world.lock` pins it, and `apk add` fetches more over HTTPS. -**Packages** — the root is managed by Alpine's **apk**. `ports/alpine/world` -says what is installed; the build resolves it once into `world.lock` and every -image after that is reproducible. SchOS itself is one more package in it. +**Desktop** — **Xorg with Window Maker**, `xterm`, the classic X clients, SDL games +and the NetSurf browser, on the framebuffer (`/dev/fb0`, Linux fbdev) with input +as Linux evdev. Twelve virtual terminals, switched with Ctrl+Alt+F1–F12, with X on +a terminal of its own. Sound through ALSA (over OSS and an AC'97 driver). -**Graphics** — **Xorg with Window Maker**, preinstalled, on the framebuffer -(`/dev/fb0`, Linux fbdev). Six virtual terminals, switched with -Ctrl+Alt+F1–F6 or Alt+F1–F6, with X on a terminal of its own; Ctrl+Alt+Backspace -ends the session. Input as Linux evdev on `/dev/input/event*`. - -**Devices** — PCI with a bus-agnostic device model at `/proc/devices`. +**Drivers** — written from the specifications, and **Linux's own, unmodified, +through [LDLS](docs/architecture/ldls.md)**: a `.ko` runs in an ordinary process +and reaches the hardware through `/dev/pci`, so a crashed driver costs an interface, +not the machine. The laptop's Realtek NIC is driven that way. | | | |---|---| | Storage | AHCI (SATA), NVMe, IDE, USB mass storage | -| Network | Intel e1000, Realtek r8169 and RTL8139 | +| Network | Intel e1000, Realtek RTL8139 and r8169 (the latter also as Linux's driver in a process) | | USB | xHCI, hubs, HID keyboards and mice | -| Input | PS/2, I2C-HID laptop touchpads (DesignWare I2C, found through ACPI) | +| Input | PS/2, I2C-HID laptop touchpads and keyboards (DesignWare I2C, found through ACPI) | | Display | UEFI GOP framebuffer, virtio-gpu, Bochs/stdvga | | Audio | AC'97 | -**Networking** — ARP, IPv4, ICMP, UDP, TCP and DHCP on lwIP. DNS, `wget` -over **HTTPS**, `ping`, and an `httpd` serving a directory back. +**Networking** — ARP, IPv4, ICMP, UDP, TCP and DHCP on lwIP; DNS, `wget` and `curl` +over **HTTPS**, `ping`, `httpd`, netlink for `getifaddrs`. -**Userland** — musl, toybox, mksh and zsh, plus whatever Alpine has: `ls`, -`ps`, `top`, `awk`, `diff`, `xterm`, `fastfetch` behave the way you expect, -because they are the real programs. +**When it dies** — PSOGM, the Purple Screen of Guru Meditation: stop code, fault +address, call chain, and the log as QR codes. Under it runs **GMMOS**, a micro OS with +its own keyboard drivers, so a dead machine can still show its log, take a few +commands, and restart or power off. -**Text** — a TrueType rasteriser (`glyf` outlines, composite glyphs, cmap -format 4). JetBrains Mono for the terminal, Roboto for the desktop to come. - -**When it dies** — PSOGM, the Purple Screen of Guru Meditation: stop code, -fault address, call chain, and a QR code containing a text copy of the whole -report. - -**On real hardware** — booted on an AMD B550 desktop (Ryzen 5 4600G), an ASUS -TUF A15 laptop and an Ivy Bridge HP all-in-one, from a USB stick. +**On real hardware** — booted from a USB stick on an AMD B550 desktop (Ryzen 5 +4600G), an ASUS TUF A15 laptop and an Ivy Bridge HP all-in-one. ## Quick start @@ -134,19 +132,19 @@ TUF A15 laptop and an Ivy Bridge HP all-in-one, from a USB stick. make # kernel + UEFI loader + root filesystem make qemu # build it and boot it make iso # a bootable ISO for VMware or real hardware +./write.sh sdX # write that ISO to a USB stick (Linux; it checks what it is overwriting) ``` -The ISO is a hybrid image: write it to a USB stick with -`dd if=schos.iso of=/dev/sdX bs=4M` (or Rufus/Etcher in DD mode) and boot it -on any UEFI machine. +The ISO is a hybrid image: it boots on any UEFI machine from a stick written with +`write.sh`, `dd if=schos.iso of=/dev/sdX bs=4M`, or Rufus/Etcher in DD mode. **It runs +from RAM and touches none of the machine's disks.** Log in on the console, and type +`startx` for the desktop. Builds on **Linux, macOS, FreeBSD/OpenBSD/NetBSD and Windows** (Git Bash). The toolchain is clang + lld everywhere — it is a cross-compiler by construction, so one LLVM install targets the bare-metal kernel, the 16-bit boot code and musl -userland from any host. See **[docs/guides/building.md](docs/guides/building.md)** for -prerequisites per platform, and -**[docs/history/moving-to-linux.md](docs/history/moving-to-linux.md)** for moving a checkout -between machines. +userland from any host; `nix-shell` supplies all of it. See +**[docs/guides/building.md](docs/guides/building.md)** for prerequisites per platform. ## Testing @@ -160,28 +158,35 @@ tools/windows/bootcheck.ps1 -Tty -Ps # terminal behaviour, process enumeration make schfs-check # SchFS B-tree, object and image suites ``` -`bootcheck` is PowerShell because `pwsh` runs on every supported host. - -The numbers at the top are measured, not typed: `python3 tools/python/stats.py` -rewrites them here and on the website. +`bootcheck` is PowerShell because `pwsh` runs on every supported host. Several +programs in `cmd/` (`socktest`, `memfdtest`, `xready`, …) exist to be run *on* the +system and report which of their own checks failed. See +**[docs/guides/testing.md](docs/guides/testing.md)** for what each proves. The numbers at +the top are measured, not typed: `python3 tools/python/stats.py` rewrites them here +and on the website. ## Layout ``` -kernel/ arch/ core/ dev/ drivers/ fs/ net/ proc/ sys/ +kernel/ arch/ core/ dev/ drivers/ fs/ net/ proc/ sys/ acpi/ gmmos/ scff/ fs/schfs/ the filesystem -- same source the host tools compile +ldls/ the Linux driver layer: the .ko loader, BTF reader, Linux symbols include/ kernel and userland headers, by subsystem (arch/ core/ fs/ dev/ ...) boot/ uefi/ (PE loader) common/ (shared loader UI) lib/ cmd/ first-party userland runtime and programs -ports/ third-party software built from source +etc/ what the image starts with: schinit's job manifests, motd, ... +ports/ third-party software built from source; ports/alpine is the apk root tools/ windows/ (.ps1) posix/ (.sh) python/ (.py) and .c/.pl +projects/ things that run ON SchOS (an installer, SchFS for Linux: both frozen) docs/ architecture/ (what it is) plan/ (what's next) guides/ (how to work on it) history/ (why it's like this) + Website/ (the site, and the docs reader) ``` **[docs/README.md](docs/README.md)** is the index. If you are reading the tree for the first time, [docs/architecture/README.md](docs/architecture/README.md) -is the system layer by layer. +is the system layer by layer, and [docs/plan/1.0.md](docs/plan/1.0.md) says +where it stands against its finish line. One rule worth knowing: **the kernel and the host tools compile the same SchFS source**. `mkschfs` cannot disagree with the kernel about the on-disk format, @@ -193,19 +198,24 @@ Another: **syscalls are declared in exactly one place**, ## Not there yet -- **No threads.** `clone` makes processes, not threads (`CLONE_VM` is - refused), so multi-threaded programs run on one thread or not at all. Xorg - runs with `-dumbSched` for this reason. -- **No udev.** Xorg is told its input devices by a config file instead of - finding them; `/sys` and the device-event socket are what is missing. +- **No installer, and nothing persists.** The live image runs from RAM and + writes nothing to the machine's disks, so what you install or save is gone at + reboot. This is the one thing between the system and its 1.0 definition. +- **No GPU acceleration.** Xorg draws in software; the plan is Linux's `amdgpu` + through LDLS ([docs/plan/gpu.md](docs/plan/gpu.md)). There is no IOMMU either, + so a driver in a process is isolated against its bugs, not against malice. +- **udev sees the kernel's own devices only.** eudev runs and Xorg finds its input devices + through it; PCI and USB devices are not yet in the `/sys/devices` tree. +- **WebKit browsers do not start.** NetSurf does; Epiphany's web process dies at + start-up, and it has not been run to ground. - **USB 2.0 controllers (EHCI)** have no driver. On older machines the ports wired only to EHCI do nothing; xHCI ports work. - **Touchpads run in mouse mode**: pointer, click and tap, but no two-finger scroll yet. - **No page cache**, so writable `MAP_SHARED` of a file is refused rather than - faked. SysV shared memory is missing too. -- **No installer.** SchOS runs live from the USB stick; nothing is written to - the machine's disks. + faked, and `mprotect` is accepted and ignored (nothing has NX). +- **No Wi-Fi, no HDA audio**, and the Realtek NIC on the laptop has reached the + point of finding its PHY; whether it carries traffic there is not confirmed. - SchFS has no Linux driver yet; the plan is on-disk format version first, then FUSE, then a kernel module. - CFF/OpenType outlines and TrueType hinting are deliberately out of scope. @@ -215,14 +225,16 @@ Another: **syscalls are declared in exactly one place**, **GPL-2.0-or-later**, copyright Nexoniarz. See [LICENSE](LICENSE). The kernel derives from xv6, which is MIT — see [LICENSE.MIT](LICENSE.MIT). -Third-party code redistributed in a built image (musl, toybox, mksh, zsh, kilo, -the fonts) keeps its own licence; [NOTICE](NOTICE) carries the attributions. -See [docs/licensing.md](docs/licensing.md). +Third-party code redistributed in a built image (musl, toybox, mksh, zstd, the +fonts, and Alpine's packages, each under its own licence) keeps that licence; +[NOTICE](NOTICE) carries the attributions. See [docs/licensing.md](docs/licensing.md). Drivers may be adapted from BSD sources and, since the move to the GPL, from Linux — GPL-2.0-only code combines with GPL-2.0-or-later, and the result ships -under GPLv2. Best of all is still the public specification — xHCI, virtio, -AHCI, e1000 — which carries no licence on what you write from it. +under GPLv2. Linux's own drivers run unmodified through LDLS, and the ISO ships the +Realtek `r8169` set and its firmware; their licences and source are in [NOTICE](NOTICE). Best of +all is still the public specification — xHCI, virtio, AHCI, e1000 — which carries +no licence on what you write from it. --- diff --git a/boot/common/ui.c b/boot/common/ui.c index 7723faa..694441a 100644 --- a/boot/common/ui.c +++ b/boot/common/ui.c @@ -13,6 +13,7 @@ #include "../../include/gfx/theme.h" #include "../../include/gfx/font.h" #include "../../include/gfx/logo.h" +#include "../../include/gfx/nvfont.h" /* "Draw the glyph, leave the background alone." Not a colour anyone can mean: * the palette is 24-bit, so the high byte is free for a sentinel. */ @@ -21,6 +22,25 @@ static struct ui_fb FB; static int ACTIVE; +/* What the mark is blended against: the splash ground, or a window body. */ +static ui_u32 LOGO_BG = SCH_BG; + +/* The boot manager's scale and faces. 2 on a screen big enough that a 15 px + * face would be a speck; the faces are rasterised at both sizes, not doubled. */ +static int SC = 1; +static const struct nvfont *F_BODY, *F_SMALL, *F_TITLE; +static int CUR_ON; /* the pointer is drawn right now */ + +static void +ui_set_scale(void) +{ + SC = (FB.height >= 1400 || FB.width >= 2400) ? 2 : 1; + F_BODY = SC == 2 ? &nvfont_body2 : &nvfont_body1; + F_SMALL = SC == 2 ? &nvfont_small2 : &nvfont_small1; + F_TITLE = SC == 2 ? &nvfont_title2 : &nvfont_title1; + CUR_ON = 0; /* a new framebuffer holds no pointer */ +} + /* Progress state. Kept here rather than passed around because the caller ticks * it from inside a read loop that has no business carrying UI state. */ static ui_u32 PROG_TOTAL, PROG_DONE; @@ -101,6 +121,7 @@ ui_init(const struct ui_fb *fb) if (!FB.stride) FB.stride = FB.width * 4; ACTIVE = 1; + ui_set_scale(); } int ui_active(void) { return ACTIVE; } @@ -236,7 +257,7 @@ ui_logo(int y, int w) /* Coverage blends the shaded metal against the ground, so the mark * has soft edges and a lit face at the same time. */ - put(x0 + dx, y + dy, mix(SCH_BG, shade, (int)cov, 15)); + put(x0 + dx, y + dy, mix(LOGO_BG, shade, (int)cov, 15)); } } } @@ -244,121 +265,555 @@ ui_logo(int y, int w) /* Where the bar goes. Derived, not stored, so nothing can set it and then have * the screen change underneath it. * - * The bar is exactly the mark's width and sits directly beneath it: the two are - * one object, and a bar wider than the mark it belongs to reads as a separate - * thing that happens to be nearby. + * The track is wider than the mark and sits beneath it: a mark with a recessed + * track and one line of type, centred, on the same graphite the boot manager + * uses. Laid out from the middle outwards so it reads the same at 800x600 and + * at 4K. S() is defined further down with the rest of the Niveus code, so the + * scale is applied through SC directly. */ static void bar_geometry(void) { - PROG_W = ui_mark_width(); - PROG_H = BAR_H; + PROG_W = 220 * SC; + PROG_H = 6 * SC; PROG_X = ((int)FB.width - PROG_W) / 2; - int mark_h = (LOGO_H * PROG_W) / LOGO_W; - int group_h = mark_h + MARK_GAP + PROG_H; + int mw = ui_mark_width() * SC; + if (mw > LOGO_W) mw = LOGO_W; + int mark_h = (LOGO_H * mw) / LOGO_W; + int gap = 34 * SC; + int group_h = mark_h + gap + PROG_H; int top = ((int)FB.height - group_h) / 2; - PROG_Y = top + mark_h + MARK_GAP; + PROG_Y = top + mark_h + gap; } +static ui_u32 bg_at(int y); +static void nv_backdrop(void); +static int nv_width(const struct nvfont *f, const char *s); +static void nv_text(const struct nvfont *f, int x, int base, const char *s, + ui_u32 fg, ui_u32 bgtop, ui_u32 bgbot, int y0, int h); +static void rrect(int x, int y, int w, int h, int r, ui_u32 top, ui_u32 bot, + ui_u32 bgtop, ui_u32 bgbot, int bgy, int bgh); + void ui_splash(const char *subtitle) { if (!ACTIVE) return; - ui_clear(); + nv_backdrop(); bar_geometry(); - int w = PROG_W; + int w = ui_mark_width() * SC; + if (w > LOGO_W) w = LOGO_W; int mark_h = (LOGO_H * w) / LOGO_W; - int top = PROG_Y - MARK_GAP - mark_h; + int top = PROG_Y - 34 * SC - mark_h; + ui_u32 saved = LOGO_BG; + LOGO_BG = bg_at(top + mark_h / 2); ui_logo(top, w); + LOGO_BG = saved; if (subtitle) - ui_text_centered(PROG_Y + PROG_H + SUB_GAP, subtitle, SCH_DIM); + ui_progress_label(subtitle); } -/* --- panels --------------------------------------------------------------- +/* --- Niveus ----------------------------------------------------------------- * - * A panel is a plate: a slightly lighter ground than the screen, one lighter - * row along the top and a darker border, which is the whole of the bevel. Two - * rows of shading do more for the impression of a raised surface than any - * amount of gradient inside it, and cost nothing to draw. + * The boot manager is drawn in the same language as the rest of the system + * (docs/Website/niveus): windows are solid, controls are + * bevelled keys with light falling across them, fields are wells cut into the + * surface, and the ground under all of it is warm graphite rather than blue. + * + * Every shape here is a rounded rectangle with a vertical gradient, which is + * all that skeuomorphism needs at this size. Corners are anti-aliased by + * supersampling the corner pixels only, and the AA blends against a colour the + * caller *names*, never against what is in the framebuffer: reading the + * framebuffer back is uncached on real hardware and ruinously slow. + * + * The typeface is Instrument Sans, rasterised at build time (nvfont.h). */ -static void -panel(int x, int y, int w, int h) + +#define NV_BG_TOP 0x1B1B1Eu /* the ground, light at the top */ +#define NV_BG_BOT 0x0B0B0Du +#define NV_WIN_BODY 0x232326u +#define NV_WIN_EDGE 0x050507u +#define NV_TITLE_TOP 0x45444Au +#define NV_TITLE_BOT 0x2C2B30u +#define NV_HILITE 0x5E5D64u /* the lit upper edge of a raised thing */ +#define NV_KEY_TOP 0x3F3E44u +#define NV_KEY_BOT 0x2D2C32u +#define NV_KEY_EDGE 0x111113u +#define NV_KEY_SHADOW 0x141416u +#define NV_WELL_TOP 0x101012u +#define NV_WELL_BOT 0x171719u +#define NV_WELL_EDGE 0x0A0A0Bu +#define NV_WELL_LIP 0x3A393Eu /* light catching the lower lip of a well */ +#define NV_TXT 0xECEAE5u +#define NV_TXT2 0xA9A7A1u +#define NV_TXT3 0x74726Du + +#define S(n) ((n) * SC) +#define WHITE 0xFFFFFFu +#define BLACK 0x000000u + +static ui_u32 +bg_at(int y) { - ui_fill_rect(x, y, w, h, PANEL_BG); - ui_fill_rect(x, y, w, 1, PANEL_TOP); /* lit top edge */ - ui_fill_rect(x, y + h - 1, w, 1, METAL_EDGE); /* shadowed bottom */ - ui_fill_rect(x, y, 1, h, METAL_EDGE); - ui_fill_rect(x + w - 1, y, 1, h, METAL_EDGE); + return mix(NV_BG_TOP, NV_BG_BOT, y, (int)FB.height > 1 ? (int)FB.height - 1 : 1); } -/* A heading in metal: the same shading the mark uses, applied per row of the - * glyph cell, so a title looks struck out of the same material. */ static void -metal_text_centered(int y, const char *s) +nv_backdrop(void) { - int len = slen(s); - int x = ((int)FB.width - len * GLYPH_W) / 2; + for (int y = 0; y < (int)FB.height; y++) + ui_fill_rect(0, y, (int)FB.width, 1, bg_at(y)); +} - for (int row = 0; row < GLYPH_H; row++) { - ui_u32 shade = metal(row, GLYPH_H); - for (int i = 0; i < len; i++) { - ui_u8 bits = font_data[(ui_u8)s[i]][row]; - for (int col = 0; col < GLYPH_W; col++) - if (bits & (0x80 >> col)) - put(x + i * GLYPH_W + col, y + row, shade); +static int +nv_glyph_index(unsigned c) +{ + if (c < NVFONT_FIRST || c > NVFONT_LAST) + c = '?'; + return (int)(c - NVFONT_FIRST); +} + +static int +nv_width(const struct nvfont *f, const char *s) +{ + int w = 0; + for (; *s && *s != '\n'; s++) + w += f->g[nv_glyph_index((ui_u8)*s)].adv; + return (w + 32) >> 6; +} + +static int +nv_height(const struct nvfont *f) +{ + return f->ascent + f->descent; +} + +/* Draw `s` (up to a newline) with its origin on baseline `base`. The colour + * under it is a vertical gradient from bgtop at row y0 to bgbot at y0+h. */ +static void +nv_text(const struct nvfont *f, int x, int base, const char *s, ui_u32 fg, + ui_u32 bgtop, ui_u32 bgbot, int y0, int h) +{ + int pen = 0; /* 1/64 px: the face's own spacing */ + for (; *s && *s != '\n'; s++) { + const struct nvglyph *g = &f->g[nv_glyph_index((ui_u8)*s)]; + const ui_u8 *bits = f->bits + g->off; + int gx = x + ((pen + 32) >> 6); + for (int r = 0; r < g->h; r++) { + int py = base + g->oy + r; + ui_u32 bg = mix(bgtop, bgbot, py - y0, h > 1 ? h - 1 : 1); + for (int c = 0; c < g->w; c++) { + ui_u8 a = bits[r * g->w + c]; + if (a) + put(gx + g->ox + c, py, mix(bg, fg, a, 255)); + } + } + pen += g->adv; + } +} + +/* How much of the pixel at (dx,dy) from a corner's centre lies inside a circle + * of radius r, in sixteenths. */ +static int +corner_cov(int dx, int dy, int r) +{ + int n = 0, R = r * 8; + for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) { + int sx = dx * 8 + 2 * i + 1, sy = dy * 8 + 2 * j + 1; + if (sx * sx + sy * sy <= R * R) + n++; + } + return n; +} + +static void +rrect(int x, int y, int w, int h, int r, ui_u32 top, ui_u32 bot, + ui_u32 bgtop, ui_u32 bgbot, int bgy, int bgh) +{ + if (w <= 0 || h <= 0) + return; + if (r * 2 > h) r = h / 2; + if (r * 2 > w) r = w / 2; + + for (int rr = 0; rr < h; rr++) { + int yy = y + rr; + ui_u32 col = mix(top, bot, rr, h > 1 ? h - 1 : 1); + int cy, corner = 1; + if (rr < r) cy = y + r; + else if (rr >= h - r) cy = y + h - r; + else corner = 0; + + if (!corner) { + ui_fill_rect(x, yy, w, 1, col); + continue; + } + ui_u32 bg = mix(bgtop, bgbot, yy - bgy, bgh > 1 ? bgh - 1 : 1); + ui_fill_rect(x + r, yy, w - 2 * r, 1, col); + for (int k = 0; k < r; k++) { + int dy = yy - cy; + int cl = corner_cov(k - r, dy, r); + int cr = corner_cov(k, dy, r); + if (cl) put(x + k, yy, mix(bg, col, cl, 16)); + if (cr) put(x + w - r + k, yy, mix(bg, col, cr, 16)); } } } -/* Panel metrics. - * - * TITLE_H is the whole title band -- the padding above the heading, the - * heading itself, and the air beneath it before the rule. It was 34 while the - * heading alone needs 18 + 16 = 34, so the rule at TITLE_H - 6 was drawn - * *through* the lettering and the first row started on the heading's baseline. - * Derived from its parts now, so it cannot drift from them again. - */ -#define ROW_H 24 -#define PANEL_PAD 18 -#define TITLE_GAP 14 /* heading to the rule */ -#define TITLE_H (PANEL_PAD + GLYPH_H + TITLE_GAP) /* = 48 */ -#define ROW_LIFT ((ROW_H - GLYPH_H) / 2) /* highlight overhang */ +/* A raised object: an edge, a gradient face, and a bright line just inside the + * top where the light lands. */ +static void +bevel(int x, int y, int w, int h, int r, ui_u32 top, ui_u32 bot, ui_u32 edge, + ui_u32 hi, ui_u32 bgtop, ui_u32 bgbot, int bgy, int bgh) +{ + rrect(x, y, w, h, r, edge, edge, bgtop, bgbot, bgy, bgh); + rrect(x + SC, y + SC, w - 2 * SC, h - 2 * SC, r > SC ? r - SC : r, + top, bot, edge, edge, 0, 1); + if (hi) + ui_fill_rect(x + r, y + SC, w - 2 * r, SC, hi); +} + +/* A recess: the same thing with the light reversed. Darker at the top where the + * rim shades it, with a pale lip along the bottom edge. */ +static void +well(int x, int y, int w, int h, ui_u32 bgcol) +{ + int r = S(8); + bevel(x, y, w, h, r, NV_WELL_TOP, NV_WELL_BOT, NV_WELL_EDGE, 0, bgcol, bgcol, 0, 1); + ui_fill_rect(x + r, y + h - SC, w - 2 * r, SC, NV_WELL_LIP); +} + +/* The soft shadow beneath a window, as stacked translucent layers laid down + * from the outermost in, each one blended against the backdrop's own gradient. */ +static void +shadow(int x, int y, int w, int h, int r) +{ + int spread = S(16), dy = S(8); + for (int i = spread; i > 0; i -= SC) { + int t = spread - i; + int a = 60 * t * t / (spread * spread); /* percent black */ + int ty = y - i + dy, th = h + 2 * i; + ui_u32 top = mix(bg_at(ty), BLACK, a, 100); + ui_u32 bot = mix(bg_at(ty + th), BLACK, a, 100); + rrect(x - i, ty, w + 2 * i, th, r + i, top, bot, + bg_at(ty), bg_at(ty + th), ty, th); + } +} + +/* --- the pointer ----------------------------------------------------------- */ + +#define CUR_W 12 +#define CUR_H 19 +static const char *const CUR_SHAPE[CUR_H] = { + "X ", + "XX ", + "X.X ", + "X..X ", + "X...X ", + "X....X ", + "X.....X ", + "X......X ", + "X.......X ", + "X........X ", + "X.........X ", + "X......XXXXX", + "X...X..X ", + "X..X X..X ", + "X.X X..X ", + "XX X..X ", + "X X..X ", + " X..X ", + " XXX ", +}; +static ui_u32 CUR_SAVE[CUR_W * 2 * CUR_H * 2]; +static int CUR_X, CUR_Y, CUR_WANT; static void -panel_frame(const char *title, int rows, const char *hint, - int *out_x, int *out_y, int *out_w) +cursor_hide(void) { - int w = (int)FB.width / 2; - if (w < 360) w = 360; - if (w > (int)FB.width - 60) w = (int)FB.width - 60; + if (!CUR_ON || !ACTIVE) + return; + int w = CUR_W * SC, h = CUR_H * SC; + for (int r = 0; r < h; r++) + for (int c = 0; c < w; c++) { + int px = CUR_X + c, py = CUR_Y + r; + if (px >= 0 && py >= 0 && (ui_u32)px < FB.width && (ui_u32)py < FB.height) + *(ui_u32 *)(FB.base + (ui_u64)py * FB.stride + (ui_u64)px * 4) = + CUR_SAVE[r * w + c]; + } + CUR_ON = 0; +} - int h = TITLE_H + rows * ROW_H + PANEL_PAD * 2; - int x = ((int)FB.width - w) / 2; - int y = ((int)FB.height - h) / 2; +static void +cursor_show(void) +{ + if (!CUR_WANT || CUR_ON || !ACTIVE) + return; + int w = CUR_W * SC, h = CUR_H * SC; + for (int r = 0; r < h; r++) + for (int c = 0; c < w; c++) { + int px = CUR_X + c, py = CUR_Y + r; + if (px < 0 || py < 0 || (ui_u32)px >= FB.width || (ui_u32)py >= FB.height) + continue; + ui_u32 *p = (ui_u32 *)(FB.base + (ui_u64)py * FB.stride + (ui_u64)px * 4); + CUR_SAVE[r * w + c] = *p; + char ch = CUR_SHAPE[r / SC][c / SC]; + if (ch == 'X') *p = BLACK; + else if (ch == '.') *p = WHITE; + } + CUR_ON = 1; +} - ui_clear(); +void +ui_cursor(int x, int y, int show) +{ + cursor_hide(); + CUR_X = x; CUR_Y = y; CUR_WANT = show; + cursor_show(); +} - /* The mark, small, above the plate: it is the same screen, not a new one. */ - int mw = ui_mark_width() * 2 / 3; - int mh = (LOGO_H * mw) / LOGO_W; - if (y - mh - 24 > 0) - ui_logo(y - mh - 24, mw); +/* --- window chrome --------------------------------------------------------- */ - panel(x, y, w, h); - metal_text_centered(y + PANEL_PAD, title); - ui_fill_rect(x + PANEL_PAD, y + PANEL_PAD + GLYPH_H + TITLE_GAP / 2, - w - PANEL_PAD * 2, 1, METAL_EDGE); +#define TB_H S(34) +#define BODY_PAD S(18) +#define HINT_H S(36) - if (hint) - ui_text_centered(y + h + 16, hint, SCH_DIM); +static void +window(int x, int y, int w, int h, const char *title) +{ + int r = S(12), tb = TB_H; - *out_x = x; *out_y = y + TITLE_H; *out_w = w; + shadow(x, y, w, h, r); + rrect(x, y, w, h, r, NV_WIN_EDGE, NV_WIN_EDGE, bg_at(y), bg_at(y + h), y, h); + rrect(x + SC, y + SC, w - 2 * SC, h - 2 * SC, r - SC, NV_WIN_BODY, NV_WIN_BODY, + NV_WIN_EDGE, NV_WIN_EDGE, 0, 1); + + /* The titlebar is rounded on top only: draw it tall enough that its bottom + * corners fall below the line, then lay the body colour over the excess. */ + rrect(x + SC, y + SC, w - 2 * SC, tb + r, r - SC, NV_TITLE_TOP, NV_TITLE_BOT, + NV_WIN_EDGE, NV_WIN_EDGE, 0, 1); + ui_fill_rect(x + SC, y + SC + tb, w - 2 * SC, r, NV_WIN_BODY); + ui_fill_rect(x + SC, y + SC + tb - SC, w - 2 * SC, SC, NV_WIN_EDGE); + ui_fill_rect(x + r, y + SC, w - 2 * r, SC, NV_HILITE); + + int tw = nv_width(F_BODY, title); + int base = y + SC + (tb - nv_height(F_BODY)) / 2 + F_BODY->ascent; + nv_text(F_BODY, x + (w - tw) / 2, base, title, NV_TXT2, + NV_TITLE_TOP, NV_TITLE_BOT, y + SC, tb); +} + +static void +footer(int x, int y, int w, int h, const char *hint) +{ + if (!hint) + return; + int fy = y + h - HINT_H; + ui_fill_rect(x + S(14), fy, w - S(28), SC, 0x1A1A1Cu); + ui_fill_rect(x + S(14), fy + SC, w - S(28), SC, 0x2E2E32u); + int tw = nv_width(F_SMALL, hint); + int base = fy + (HINT_H - nv_height(F_SMALL)) / 2 + F_SMALL->ascent + SC; + nv_text(F_SMALL, x + (w - tw) / 2, base, hint, NV_TXT3, + NV_WIN_BODY, NV_WIN_BODY, fy, HINT_H); +} + +/* --- keys ------------------------------------------------------------------ */ + +#define MAXKEYS 40 + +static struct { int x, y, w, h; } KEY[MAXKEYS]; + +static struct { + const char *title, *hint; + const char *const *items; + int n, sel, scroll, vis, kind; /* kind: 0 a list of keys, 1 a panel */ + int has_detail, kh, gap, logo; + int wx, wy, ww, wh; + int lx, ly, lw; /* the list's box */ + int ypos[MAXKEYS]; +} M; + +static int +has_nl(const char *s) +{ + for (; *s; s++) + if (*s == '\n') + return 1; + return 0; +} + +static const char * +label_of(const char *s) +{ + return s[0] == 1 ? s + 1 : s; +} + +static const char * +detail_of(const char *s) +{ + for (; *s; s++) + if (*s == '\n') + return s + 1; + return 0; +} + +static void +paint_key(int i, int sel) +{ + if (i < M.scroll || i >= M.scroll + M.vis || i >= MAXKEYS) + return; + + int x = KEY[i].x, y = KEY[i].y, w = KEY[i].w, h = KEY[i].h, r = S(8); + const char *item = M.items[i]; + const char *label = label_of(item), *detail = detail_of(item); + + /* Wipe the ring/shadow a previous state left, then draw this one. */ + ui_fill_rect(x - S(3), y - S(3), w + S(6), h + S(7), NV_WIN_BODY); + + ui_u32 top, bot, edge, hi, fg, fg2; + if (sel) { + ui_u32 ring = mix(NV_WIN_BODY, SCH_ACCENT, 70, 100); + rrect(x - S(2), y - S(2), w + S(4), h + S(4), r + S(2), ring, ring, + NV_WIN_BODY, NV_WIN_BODY, 0, 1); + top = mix(SCH_ACCENT, WHITE, 20, 100); + bot = mix(SCH_ACCENT, BLACK, 24, 100); + edge = mix(SCH_ACCENT, BLACK, 62, 100); + hi = mix(top, WHITE, 45, 100); + fg = WHITE; fg2 = 0xE2DBFFu; + } else { + rrect(x, y + SC, w, h, r, NV_KEY_SHADOW, NV_KEY_SHADOW, + NV_WIN_BODY, NV_WIN_BODY, 0, 1); + top = NV_KEY_TOP; bot = NV_KEY_BOT; edge = NV_KEY_EDGE; hi = NV_HILITE; + fg = NV_TXT; fg2 = NV_TXT3; + } + bevel(x, y, w, h, r, top, bot, edge, hi, NV_WIN_BODY, NV_WIN_BODY, 0, 1); + + int ty0 = y + SC, th = h - 2 * SC, tx = x + S(18); + if (detail) { + int lh = nv_height(F_BODY), dh = nv_height(F_SMALL); + int total = lh + S(2) + dh; + int t = y + (h - total) / 2; + nv_text(F_BODY, tx, t + F_BODY->ascent, label, fg, top, bot, ty0, th); + nv_text(F_SMALL, tx, t + lh + S(2) + F_SMALL->ascent, detail, + sel ? fg2 : NV_TXT2, top, bot, ty0, th); + } else { + int base = y + (h - nv_height(F_BODY)) / 2 + F_BODY->ascent; + nv_text(F_BODY, tx, base, label, fg, top, bot, ty0, th); + } + + if (M.kind == 0 && M.n <= 9 && M.vis == M.n) { + char num[2] = { (char)('1' + i), 0 }; + int nw = nv_width(F_SMALL, num); + int base = y + (h - nv_height(F_SMALL)) / 2 + F_SMALL->ascent; + nv_text(F_SMALL, x + w - S(18) - nw, base, num, sel ? fg2 : NV_TXT3, + top, bot, ty0, th); + } +} + +static void +menu_layout(void) +{ + int n = M.n > MAXKEYS ? MAXKEYS : M.n; + M.n = n; + M.has_detail = 0; + for (int i = 0; i < n; i++) + if (has_nl(M.items[i])) + M.has_detail = 1; + M.gap = S(8); + M.kh = M.has_detail ? S(50) : S(34); + M.ww = S(520); + if (M.ww > (int)FB.width - S(24)) + M.ww = (int)FB.width - S(24); + + int maxh = (int)FB.height - S(24); + int chrome = TB_H + BODY_PAD * 2 + HINT_H + 2 * SC; + int logoh = S(56) + S(16); + int full; + + for (int pass = 0; pass < 2; pass++) { + int y = 0; + for (int i = 0; i < n; i++) { + if (pass == 0 && i && M.items[i][0] == 1) + y += S(14); + M.ypos[i] = y; + y += M.kh + M.gap; + } + full = n ? y - M.gap : 0; + if (pass == 0 && chrome + full <= maxh) + break; + if (pass == 0 && chrome + logoh + full <= maxh) + break; + } + + M.logo = 1; + M.vis = n; + int list_h = full; + if (chrome + logoh + full > maxh) + M.logo = 0; + if (chrome + full > maxh) { + int avail = maxh - chrome; + M.vis = (avail + M.gap) / (M.kh + M.gap); + if (M.vis < 1) M.vis = 1; + if (M.vis > n) M.vis = n; + list_h = M.vis * M.kh + (M.vis - 1) * M.gap; + } + M.wh = chrome + (M.logo ? logoh : 0) + list_h; + M.wx = ((int)FB.width - M.ww) / 2; + M.wy = ((int)FB.height - M.wh) / 2; + if (M.wy < 0) M.wy = 0; + M.lx = M.wx + S(20); + M.lw = M.ww - S(40) - (M.vis < n ? S(14) : 0); + M.ly = M.wy + SC + TB_H + BODY_PAD + (M.logo ? logoh : 0); + + if (M.sel < M.scroll) M.scroll = M.sel; + if (M.sel >= M.scroll + M.vis) M.scroll = M.sel - M.vis + 1; + if (M.scroll < 0) M.scroll = 0; + if (M.scroll > n - M.vis) M.scroll = n - M.vis; + if (M.scroll < 0) M.scroll = 0; + + for (int i = 0; i < n; i++) { + KEY[i].x = M.lx; + KEY[i].w = M.lw; + KEY[i].h = M.kh; + KEY[i].y = M.ly + M.ypos[i] - M.ypos[M.scroll]; + } +} + +static void +menu_paint(void) +{ + cursor_hide(); + nv_backdrop(); + window(M.wx, M.wy, M.ww, M.wh, M.title); + + if (M.logo) { + ui_u32 saved = LOGO_BG; + LOGO_BG = NV_WIN_BODY; + ui_logo(M.wy + SC + TB_H + BODY_PAD, S(56)); + LOGO_BG = saved; + } + + for (int i = M.scroll; i < M.scroll + M.vis && i < M.n; i++) + paint_key(i, i == M.sel); + + if (M.vis < M.n) { + int tx = M.lx + M.lw + S(8), tw = S(4); + int th = M.vis * M.kh + (M.vis - 1) * M.gap; + rrect(tx, M.ly, tw, th, tw / 2, NV_WELL_TOP, NV_WELL_BOT, + NV_WIN_BODY, NV_WIN_BODY, 0, 1); + int thumb = th * M.vis / M.n; + if (thumb < S(16)) thumb = S(16); + int ty = M.ly + (th - thumb) * M.scroll / (M.n - M.vis); + rrect(tx, ty, tw, thumb, tw / 2, NV_KEY_TOP, NV_KEY_BOT, + NV_WIN_BODY, NV_WIN_BODY, 0, 1); + } + + footer(M.wx, M.wy, M.ww, M.wh, M.hint); + cursor_show(); } void @@ -367,46 +822,129 @@ ui_menu(const char *title, const char *const *items, int n, int selected, { if (!ACTIVE) return; + M.title = title; M.items = items; M.n = n; M.sel = selected; M.hint = hint; + M.scroll = 0; M.kind = 0; + menu_layout(); + menu_paint(); +} - int x, y, w; - panel_frame(title, n, hint, &x, &y, &w); +void +ui_menu_select(int selected) +{ + if (!ACTIVE || M.kind != 0 || selected < 0 || selected >= M.n || selected == M.sel) + return; + int old = M.sel; + M.sel = selected; + if (selected < M.scroll || selected >= M.scroll + M.vis) { + menu_layout(); + menu_paint(); + return; + } + cursor_hide(); + paint_key(old, 0); + paint_key(selected, 1); + cursor_show(); +} + +int +ui_menu_hit(int x, int y) +{ + if (!ACTIVE) + return -1; + for (int i = M.scroll; i < M.scroll + M.vis && i < M.n; i++) + if (x >= KEY[i].x && x < KEY[i].x + KEY[i].w && + y >= KEY[i].y && y < KEY[i].y + KEY[i].h) + return i; + return -1; +} + +/* A panel: label/value rows in a well, and one button underneath. With no + * values, the labels are a message and are centred. */ +static void +box(const char *title, const char *const *labels, const char *const *values, + int n, const char *button, const char *hint) +{ + if (!ACTIVE) + return; + int rowh = S(28), wpad = S(12); + int well_h = n * rowh + 2 * wpad; + int btn_h = S(34), btn_w = S(180); + + M.title = title; M.hint = hint; M.kind = 1; + M.items = &button; M.n = 1; M.sel = 0; M.scroll = 0; M.vis = 1; + M.ww = S(520); + if (M.ww > (int)FB.width - S(24)) + M.ww = (int)FB.width - S(24); + M.wh = TB_H + 2 * SC + BODY_PAD + well_h + S(18) + btn_h + BODY_PAD + HINT_H; + M.wx = ((int)FB.width - M.ww) / 2; + M.wy = ((int)FB.height - M.wh) / 2; + if (M.wy < 0) M.wy = 0; + + cursor_hide(); + nv_backdrop(); + window(M.wx, M.wy, M.ww, M.wh, title); + + int wx = M.wx + S(20), ww = M.ww - S(40); + int wy = M.wy + SC + TB_H + BODY_PAD; + well(wx, wy, ww, well_h, NV_WIN_BODY); for (int i = 0; i < n; i++) { - int ry = y + i * ROW_H; - - if (i == selected) { - /* The selected row is the only lit thing on the plate. Filling it - * with the accent and putting the label in the ground colour reads - * at a glance; an outline does not, at this size. */ - /* Centred on the glyph box: (ROW_H - GLYPH_H) / 2 above it, the - * same below. Off-by-a-few here is what makes a highlight look - * like it is sitting on the text rather than around it. */ - ui_fill_rect(x + PANEL_PAD / 2, ry - ROW_LIFT, w - PANEL_PAD, ROW_H, - SCH_ACCENT); - ui_text(x + PANEL_PAD, ry, items[i], PANEL_BG, SCH_BG_NONE); + int ry = wy + wpad + i * rowh; + int base = ry + (rowh - nv_height(F_BODY)) / 2 + F_BODY->ascent; + if (i) + ui_fill_rect(wx + S(14), ry, ww - S(28), SC, 0x1E1E21u); + if (values) { + nv_text(F_BODY, wx + S(16), base, labels[i], NV_TXT2, + NV_WELL_TOP, NV_WELL_BOT, wy, well_h); + int vw = nv_width(F_BODY, values[i]); + nv_text(F_BODY, wx + ww - S(16) - vw, base, values[i], NV_TXT, + NV_WELL_TOP, NV_WELL_BOT, wy, well_h); } else { - ui_text(x + PANEL_PAD, ry, items[i], SCH_FG, SCH_BG_NONE); + int lw = nv_width(F_BODY, labels[i]); + nv_text(F_BODY, wx + (ww - lw) / 2, base, labels[i], NV_TXT, + NV_WELL_TOP, NV_WELL_BOT, wy, well_h); } } + + KEY[0].w = btn_w; KEY[0].h = btn_h; + KEY[0].x = M.wx + (M.ww - btn_w) / 2; + KEY[0].y = wy + well_h + S(18); + M.has_detail = 0; + paint_key(0, 1); + footer(M.wx, M.wy, M.ww, M.wh, hint); + cursor_show(); } void ui_panel(const char *title, const char *const *labels, const char *const *values, int n, const char *hint) { - if (!ACTIVE) - return; + box(title, labels, values, n, "Back", hint); +} - int x, y, w; - panel_frame(title, n, hint, &x, &y, &w); +void +ui_notice(const char *title, const char *const *lines, int n, + const char *button, const char *hint) +{ + box(title, lines, 0, n, button, hint); +} - for (int i = 0; i < n; i++) { - int ry = y + i * ROW_H; - ui_text(x + PANEL_PAD, ry, labels[i], SCH_DIM, SCH_BG_NONE); +/* The track is a recess, the fill is the accent, lit from above -- the same two + * materials as a well and a selected key in the boot manager. The fill is + * redrawn from the left edge on every tick rather than extended, because its + * rounded end moves; the bar is small enough that this costs nothing. */ +static void +progress_paint(int px) +{ + int r = PROG_H / 2; + ui_u32 bg = bg_at(PROG_Y + PROG_H / 2); - /* Values right-aligned, so the column reads down rather than ragged. */ - int vw = slen(values[i]) * GLYPH_W; - ui_text(x + w - PANEL_PAD - vw, ry, values[i], SCH_FG, SCH_BG_NONE); + rrect(PROG_X, PROG_Y, PROG_W, PROG_H, r, 0x0A0A0Bu, 0x151517u, bg, bg, 0, 1); + if (px >= PROG_H) { + ui_u32 top = mix(SCH_ACCENT, 0xFFFFFFu, 28, 100); + ui_u32 bot = mix(SCH_ACCENT, 0x000000u, 24, 100); + rrect(PROG_X + 1, PROG_Y + 1, px - 2, PROG_H - 2, r - 1, top, bot, + 0x0A0A0Bu, 0x151517u, PROG_Y, PROG_H); } } @@ -422,10 +960,7 @@ ui_progress_begin(const char *label, ui_u32 total) PROG_LAST_PX = 0; PROG_ACTIVE = 1; - /* The unfilled track: a recess, not a colour. Drawn once -- repainting it - * on every tick is what makes a progress bar flicker. */ - ui_fill_rect(PROG_X, PROG_Y, PROG_W, PROG_H, 0x1B1F28u); - ui_fill_rect(PROG_X, PROG_Y, PROG_W, 1, 0x101319u); + progress_paint(0); if (label) ui_progress_label(label); } @@ -436,11 +971,16 @@ ui_progress_label(const char *label) if (!ACTIVE || !label) return; - /* Clear the line first. A shorter label after a longer one would otherwise - * leave the tail of the old one behind. */ - int y = PROG_Y + PROG_H + SUB_GAP; - ui_fill_rect(0, y, (int)FB.width, GLYPH_H, SCH_BG); - ui_text_centered(y, label, SCH_DIM); + /* Repaint the backdrop under the line first. A shorter label after a longer + * one would otherwise leave the tail of the old one behind. */ + int h = nv_height(F_BODY) + 4 * SC; + int y = PROG_Y + PROG_H + 18 * SC; + for (int r = 0; r < h; r++) + ui_fill_rect(0, y + r, (int)FB.width, 1, bg_at(y + r)); + + int tw = nv_width(F_BODY, label); + nv_text(F_BODY, ((int)FB.width - tw) / 2, y + F_BODY->ascent, label, NV_TXT3, + bg_at(y), bg_at(y + h), y, h); } void @@ -454,16 +994,8 @@ ui_progress_tick(ui_u32 done) PROG_DONE = done; int px = (int)(((ui_u64)PROG_DONE * (ui_u64)PROG_W) / PROG_TOTAL); - - /* Only the newly filled part. Filling from zero each time is O(width) per - * tick and visibly slower than the thing it is reporting on. */ if (px > PROG_LAST_PX) { - /* The fill is metal too, shaded across the bar's height, so it looks - * like the same material as the mark rather than a coloured rectangle - * that happens to be underneath it. */ - for (int r = 0; r < PROG_H; r++) - ui_fill_rect(PROG_X + PROG_LAST_PX, PROG_Y + r, - px - PROG_LAST_PX, 1, metal(r, PROG_H)); + progress_paint(px); PROG_LAST_PX = px; } } diff --git a/boot/common/ui.h b/boot/common/ui.h index 05fc39e..088ff5c 100644 --- a/boot/common/ui.h +++ b/boot/common/ui.h @@ -77,6 +77,21 @@ void ui_splash(const char *subtitle); void ui_menu(const char *title, const char *const *items, int n, int selected, const char *hint); +/* Repaint only the keys that changed when the highlight moves (a full paint + * redraws the screen). Returns without effect on a panel. */ +void ui_menu_select(int selected); + +/* Which key is under a point, or -1. A panel has one key, index 0. */ +int ui_menu_hit(int x, int y); + +/* The pointer: drawn on top of everything, with what is under it saved and put + * back. Menus hide it while they repaint and show it again afterwards. */ +void ui_cursor(int x, int y, int show); + +/* A message in the same window as the panels, with one button. */ +void ui_notice(const char *title, const char *const *lines, int n, + const char *button, const char *hint); + /* A panel of label/value rows -- the system information screen. */ void ui_panel(const char *title, const char *const *labels, const char *const *values, int n, const char *hint); diff --git a/boot/uefi/README.md b/boot/uefi/README.md index c368517..7b9fa90 100644 --- a/boot/uefi/README.md +++ b/boot/uefi/README.md @@ -27,3 +27,11 @@ What this will need, in rough order: The image goes at `EFI/BOOT/BOOTX64.EFI` on a FAT ESP, which also means the ISO carries a single El Torito EFI entry. + +## The boot manager + +`F8` (or `o` on a serial line) opens a Niveus-styled boot manager, drawn by +`../common/ui.c` in Instrument Sans (`include/gfx/nvfont.h`, regenerate with +`make nvfont`). It takes keyboard and pointer, lists other systems it finds on +the disks and can chain-load them, and has working Firmware Setup, Restart and +Shut Down. See [docs/architecture/boot.md](../../docs/architecture/boot.md#the-boot-manager). diff --git a/boot/uefi/efi.h b/boot/uefi/efi.h index f1df84d..f90ca7b 100644 --- a/boot/uefi/efi.h +++ b/boot/uefi/efi.h @@ -153,17 +153,22 @@ typedef struct { void *LocateDevicePath; void *InstallConfigurationTable; - void *LoadImage; - void *StartImage; + EFI_STATUS (*LoadImage)(BOOLEAN BootPolicy, EFI_HANDLE ParentImage, + void *DevicePath, void *SourceBuffer, + UINTN SourceSize, EFI_HANDLE *ImageHandle); + EFI_STATUS (*StartImage)(EFI_HANDLE ImageHandle, UINTN *ExitDataSize, + CHAR16 **ExitData); void *Exit; - void *UnloadImage; + EFI_STATUS (*UnloadImage)(EFI_HANDLE ImageHandle); EFI_STATUS (*ExitBootServices)(EFI_HANDLE ImageHandle, UINTN MapKey); void *GetNextMonotonicCount; EFI_STATUS (*Stall)(UINTN Microseconds); void *SetWatchdogTimer; - void *ConnectController; + /* Recursive = TRUE asks for every driver that will bind, down the tree. */ + EFI_STATUS (*ConnectController)(EFI_HANDLE Controller, EFI_HANDLE *DriverImage, + void *RemainingDevicePath, BOOLEAN Recursive); void *DisconnectController; void *OpenProtocol; @@ -181,6 +186,43 @@ typedef struct { void **Interface); } EFI_BOOT_SERVICES; +// --- runtime services ----------------------------------------------------- +// +// Only the two things the boot manager needs: variables, to ask the firmware to +// show its own setup on the next start, and ResetSystem, to restart or power +// off. The slots before them are spelled out as padding so the offsets hold. + +#define EfiResetCold 0 +#define EfiResetShutdown 2 + +#define EFI_VARIABLE_NON_VOLATILE 0x1 +#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x2 +#define EFI_VARIABLE_RUNTIME_ACCESS 0x4 + +#define EFI_GLOBAL_VARIABLE_GUID \ + { 0x8BE4DF61, 0x93CA, 0x11D2, { 0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C } } + +/* OsIndications bit 0: "boot into the firmware's own user interface". */ +#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x1ULL + +typedef struct { + char Hdr[24]; + void *GetTime; + void *SetTime; + void *GetWakeupTime; + void *SetWakeupTime; + void *SetVirtualAddressMap; + void *ConvertPointer; + EFI_STATUS (*GetVariable)(CHAR16 *Name, EFI_GUID *Vendor, UINT32 *Attributes, + UINTN *DataSize, void *Data); + void *GetNextVariableName; + EFI_STATUS (*SetVariable)(CHAR16 *Name, EFI_GUID *Vendor, UINT32 Attributes, + UINTN DataSize, void *Data); + void *GetNextHighMonotonicCount; + void (*ResetSystem)(UINTN ResetType, EFI_STATUS Status, UINTN DataSize, + void *ResetData); +} EFI_RUNTIME_SERVICES; + typedef struct { char Hdr[24]; CHAR16 *FirmwareVendor; @@ -191,7 +233,7 @@ typedef struct { EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; EFI_HANDLE StandardErrorHandle; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr; - void *RuntimeServices; + EFI_RUNTIME_SERVICES *RuntimeServices; EFI_BOOT_SERVICES *BootServices; UINTN NumberOfTableEntries; void *ConfigurationTable; @@ -348,4 +390,42 @@ typedef struct EFI_GRAPHICS_OUTPUT_PROTOCOL { EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode; } EFI_GRAPHICS_OUTPUT_PROTOCOL; +// --- pointers ------------------------------------------------------------- +// +// Two protocols, because firmware offers whichever the device is: a mouse or +// touchpad reports movement (SIMPLE_POINTER), a tablet -- QEMU's, and most +// touchscreens -- reports where it is (ABSOLUTE_POINTER). Both are polled. + +#define EFI_SIMPLE_POINTER_PROTOCOL_GUID \ + { 0x31878C87, 0x0B75, 0x11D5, { 0x9A, 0x4F, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } } +#define EFI_ABSOLUTE_POINTER_PROTOCOL_GUID \ + { 0x8D59D32B, 0xC655, 0x4AE9, { 0x9B, 0x15, 0xF2, 0x59, 0x04, 0x99, 0x2A, 0x43 } } + +#define EFI_NOT_READY ((EFI_STATUS)(0x8000000000000000ULL | 6)) + +typedef struct { int RelativeMovementX, RelativeMovementY, RelativeMovementZ; + BOOLEAN LeftButton, RightButton; } EFI_SIMPLE_POINTER_STATE; +typedef struct { UINT64 ResolutionX, ResolutionY, ResolutionZ; + BOOLEAN LeftButton, RightButton; } EFI_SIMPLE_POINTER_MODE; +typedef struct EFI_SIMPLE_POINTER_PROTOCOL { + EFI_STATUS (*Reset)(struct EFI_SIMPLE_POINTER_PROTOCOL *This, BOOLEAN Extended); + EFI_STATUS (*GetState)(struct EFI_SIMPLE_POINTER_PROTOCOL *This, + EFI_SIMPLE_POINTER_STATE *State); + void *WaitForInput; + EFI_SIMPLE_POINTER_MODE *Mode; +} EFI_SIMPLE_POINTER_PROTOCOL; + +typedef struct { UINT64 CurrentX, CurrentY, CurrentZ; UINT32 ActiveButtons; } + EFI_ABSOLUTE_POINTER_STATE; +typedef struct { UINT64 AbsoluteMinX, AbsoluteMinY, AbsoluteMinZ, + AbsoluteMaxX, AbsoluteMaxY, AbsoluteMaxZ; + UINT32 Attributes; } EFI_ABSOLUTE_POINTER_MODE; +typedef struct EFI_ABSOLUTE_POINTER_PROTOCOL { + EFI_STATUS (*Reset)(struct EFI_ABSOLUTE_POINTER_PROTOCOL *This, BOOLEAN Extended); + EFI_STATUS (*GetState)(struct EFI_ABSOLUTE_POINTER_PROTOCOL *This, + EFI_ABSOLUTE_POINTER_STATE *State); + void *WaitForInput; + EFI_ABSOLUTE_POINTER_MODE *Mode; +} EFI_ABSOLUTE_POINTER_PROTOCOL; + #endif /* SCHOS_EFI_H */ diff --git a/boot/uefi/main.c b/boot/uefi/main.c index 1bf7932..f8a6e97 100644 --- a/boot/uefi/main.c +++ b/boot/uefi/main.c @@ -81,6 +81,8 @@ struct elf64_phdr { #define PT_LOAD 1 static EFI_SYSTEM_TABLE *ST; +static EFI_HANDLE SELF; /* this image */ +static EFI_HANDLE BOOT_VOL; /* the volume it was loaded from */ static EFI_BOOT_SERVICES *BS; // --------------------------------------------------------------------------- @@ -183,7 +185,20 @@ serial_item(const char *const *items, int i, int sel, int numbered) } else if (numbered) { serial_puts(" "); /* keep the column */ } - serial_puts(items[i]); + /* The label, and its detail in brackets. Items carry both, with a newline + * between and a leading \001 where a group begins: the screen uses those + * for layout and a serial line has no use for either. */ + const char *t = items[i]; + if (t[0] == 1) + t++; + for (; *t && *t != '\n'; t++) + serial_putc(*t); + if (*t == '\n') { + serial_puts(" ("); + for (t++; *t; t++) + serial_putc(*t); + serial_putc(')'); + } serial_puts("\n"); } @@ -360,6 +375,7 @@ open_boot_volume(EFI_HANDLE image) // is not a thing to do by accident. st = BS->HandleProtocol(image, &li_guid, (void **)&li); if (EFI_ERROR(st)) die(L"cannot get LOADED_IMAGE", st); + BOOT_VOL = li->DeviceHandle; self_base = (UINT64)(UINTN)li->ImageBase; self_size = li->ImageSize; @@ -904,6 +920,237 @@ u64_to_dec(UINT64 v, char *out, int cap) out[i] = 0; } +/* --- pointers, and one place to wait for input ------------------------------ + * + * The boot manager is operated by keyboard, mouse, touchpad or touchscreen, and + * every screen waits through here so that all of them behave the same way. + * + * Both pointer protocols are polled rather than waited on: the loop is already + * polling the keyboard, and an event per device would mean a wait-for-any call + * that most firmware implements badly. A machine with no pointer simply never + * shows a cursor. + */ +#define MAX_PTR 4 +static EFI_SIMPLE_POINTER_PROTOCOL *PTR_REL[MAX_PTR]; +static EFI_ABSOLUTE_POINTER_PROTOCOL *PTR_ABS[MAX_PTR]; +static int PTR_NREL, PTR_NABS; +static int PTR_X, PTR_Y, PTR_DOWN; +static int PTR_RESCANS, PTR_TICKS; +static int PTR_ACTIVE; /* a real event has arrived; until then there is no cursor */ +static void u64_to_dec(UINT64 v, char *out, int cap); + +#define EV_NONE 0 +#define EV_KEY 1 +#define EV_HOVER 2 /* the pointer moved onto a key: *item */ +#define EV_CLICK 3 /* a key was clicked: *item */ + +#define PE_MOVED 1 +#define PE_DOWN 2 +#define PE_UP 4 /* wheel */ +#define PE_DOWNW 8 + +/* Bind every driver to every controller. + * + * The firmware connects only what it needs to boot: the devices that carry a + * console, and the disks. A USB mouse or tablet is neither, so on most + * machines its driver has not been started and no pointer protocol exists yet. + * Doing it here, when the menu opens, costs a second or so on a real machine + * and nothing on a boot that never shows a menu. */ +static void +connect_all(void) +{ + EFI_HANDLE *h = 0; + UINTN n = 0; + + if (EFI_ERROR(BS->LocateHandleBuffer(0 /* AllHandles */, 0, 0, &n, &h)) || !h) + return; + for (UINTN i = 0; i < n; i++) + BS->ConnectController(h[i], 0, 0, 1); + BS->FreePool(h); +} + +static void +pointers_find(void) +{ + EFI_GUID g1 = EFI_SIMPLE_POINTER_PROTOCOL_GUID; + EFI_GUID g2 = EFI_ABSOLUTE_POINTER_PROTOCOL_GUID; + EFI_HANDLE *h = 0; + UINTN n = 0; + + if (!EFI_ERROR(BS->LocateHandleBuffer(ByProtocol, &g1, 0, &n, &h)) && h) { + for (UINTN i = 0; i < n && PTR_NREL < MAX_PTR; i++) { + void *p = 0; + if (!EFI_ERROR(BS->HandleProtocol(h[i], &g1, &p)) && p) { + EFI_SIMPLE_POINTER_PROTOCOL *sp = p; + int seen = 0; + for (int k = 0; k < PTR_NREL; k++) + if (PTR_REL[k] == sp) seen = 1; + if (!seen) { + sp->Reset(sp, 0); + PTR_REL[PTR_NREL++] = sp; + } + } + } + BS->FreePool(h); + } + h = 0; n = 0; + if (!EFI_ERROR(BS->LocateHandleBuffer(ByProtocol, &g2, 0, &n, &h)) && h) { + for (UINTN i = 0; i < n && PTR_NABS < MAX_PTR; i++) { + void *p = 0; + if (!EFI_ERROR(BS->HandleProtocol(h[i], &g2, &p)) && p) { + EFI_ABSOLUTE_POINTER_PROTOCOL *ap = p; + int seen = 0; + for (int k = 0; k < PTR_NABS; k++) + if (PTR_ABS[k] == ap) seen = 1; + if (!seen) { + ap->Reset(ap, 0); + PTR_ABS[PTR_NABS++] = ap; + } + } + } + BS->FreePool(h); + } + if (PTR_X == 0 && PTR_Y == 0) + PTR_X = PTR_Y = -1; /* centred on first use, when the size is known */ +} + +static int +pointer_poll(void) +{ + int ev = 0, w = (int)ui_width(), h = (int)ui_height(); + int down = PTR_DOWN; + + for (int i = 0; i < PTR_NREL; i++) { + EFI_SIMPLE_POINTER_STATE st; + if (EFI_ERROR(PTR_REL[i]->GetState(PTR_REL[i], &st))) + continue; + int dx = st.RelativeMovementX, dy = st.RelativeMovementY; + /* A little acceleration: the counts are the device's, not pixels, and + * a fast flick has to be able to cross a 4K panel. */ + if (dx > 6 || dx < -6) dx *= 2; + if (dy > 6 || dy < -6) dy *= 2; + if (dx || dy) { + PTR_X += dx; PTR_Y += dy; + ev |= PE_MOVED; + } + if (st.RelativeMovementZ) + ev |= st.RelativeMovementZ < 0 ? PE_UP : PE_DOWNW; + down = st.LeftButton; + } + for (int i = 0; i < PTR_NABS; i++) { + EFI_ABSOLUTE_POINTER_STATE st; + EFI_ABSOLUTE_POINTER_MODE *m = PTR_ABS[i]->Mode; + if (EFI_ERROR(PTR_ABS[i]->GetState(PTR_ABS[i], &st)) || !m) + continue; + UINT64 rx = m->AbsoluteMaxX - m->AbsoluteMinX; + UINT64 ry = m->AbsoluteMaxY - m->AbsoluteMinY; + if (rx && ry) { + PTR_X = (int)(((st.CurrentX - m->AbsoluteMinX) * (UINT64)(w - 1)) / rx); + PTR_Y = (int)(((st.CurrentY - m->AbsoluteMinY) * (UINT64)(h - 1)) / ry); + ev |= PE_MOVED; + } + down = (int)(st.ActiveButtons & 1); + } + + if (PTR_X < 0) PTR_X = 0; + if (PTR_Y < 0) PTR_Y = 0; + if (PTR_X >= w) PTR_X = w - 1; + if (PTR_Y >= h) PTR_Y = h - 1; + if (down && !PTR_DOWN) + ev |= PE_DOWN; + PTR_DOWN = down; + return ev; +} + +/* Wait for a key, a click on a key, or the pointer moving onto one. + * + * The cursor is shown whenever a pointer device is present: hiding it until + * something moved left a machine whose pointer sends no events to the + * loader with nothing on screen to say a pointer was even looked for. + * + * `budget` is a countdown in milliseconds that survives across calls: a + * caller that returns here after a hover continues the same ten seconds + * instead of being given a fresh ten. NULL waits for ever. + */ +static int +wait_event(EFI_INPUT_KEY *key, int *item, UINTN *budget) +{ + int have = PTR_NREL + PTR_NABS; + int last = -2; + + if (PTR_X < 0) { + PTR_X = (int)ui_width() / 2; + PTR_Y = (int)ui_height() / 2; + } + if (have) + PTR_ACTIVE = 1; + ui_cursor(PTR_X, PTR_Y, PTR_ACTIVE); + for (;;) { + if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, key))) + return EV_KEY; + /* USB devices appear some time after their controller is connected, + * and the menu is already on screen by then: look again, twice a + * second for the first ten. */ + if (PTR_RESCANS < 20 && ++PTR_TICKS >= 250) { + PTR_TICKS = 0; + PTR_RESCANS++; + pointers_find(); + have = PTR_NREL + PTR_NABS; + if (have && !PTR_ACTIVE) { + PTR_ACTIVE = 1; + ui_cursor(PTR_X, PTR_Y, 1); + } + } + if (have) { + int e = pointer_poll(); + if ((e & (PE_MOVED | PE_DOWN)) && !PTR_ACTIVE) + PTR_ACTIVE = 1; + if (e & PE_MOVED || ((e & PE_DOWN) && PTR_ACTIVE)) + ui_cursor(PTR_X, PTR_Y, PTR_ACTIVE); + if (e & (PE_UP | PE_DOWNW)) { + key->UnicodeChar = 0; + key->ScanCode = (e & PE_UP) ? 0x01 : 0x02; + return EV_KEY; + } + int hit = ui_menu_hit(PTR_X, PTR_Y); + if ((e & PE_DOWN) && hit >= 0) { + *item = hit; + return EV_CLICK; + } + if ((e & PE_MOVED) && hit >= 0 && hit != last) { + last = hit; + *item = hit; + return EV_HOVER; + } + if (hit < 0) + last = -2; + } + if (budget) { + if (*budget < 2) + return EV_NONE; + *budget -= 2; + } + BS->Stall(2000); + } +} + +/* A message and a button. Any key or a click dismisses it. */ +static void +notice(const char *title, const char *const *lines, int n, const char *button, + const char *hint) +{ + EFI_INPUT_KEY key; + int item = 0; + + ui_notice(title, lines, n, button, hint); + mirrors_show(); + for (;;) { + int e = wait_event(&key, &item, 0); + if (e == EV_KEY || e == EV_CLICK) + return; + } +} + static void res_row(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, char *out, int cap) { @@ -916,6 +1163,8 @@ res_row(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, char *out, int cap) for (char *p = wa; *p && i < cap - 1; p++) out[i++] = *p; if (i < cap - 1) out[i++] = 'x'; for (char *p = ha; *p && i < cap - 1; p++) out[i++] = *p; + for (const char *p = "\nChange the screen size"; *p && i < cap - 1; p++) + out[i++] = *p; out[i] = 0; } @@ -1038,16 +1287,16 @@ res_read_number(const char *prompt) shown[i] = 0; const char *rows[1] = { shown }; - ui_menu("Custom resolution", rows, 1, 0, "digits, Enter to accept"); + ui_notice("Custom resolution", rows, 1, "Accept", "digits, Enter to accept"); mirrors_show(); EFI_INPUT_KEY key; - for (;;) { - if (ST->ConIn && - !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key))) - break; - BS->Stall(10000); - } + int item = 0, e; + do + e = wait_event(&key, &item, 0); + while (e == EV_HOVER); + if (e == EV_CLICK) + return v; if (key.UnicodeChar == L'\r' || key.UnicodeChar == L'\n') return v; if (key.UnicodeChar >= L'0' && key.UnicodeChar <= L'9') @@ -1069,6 +1318,47 @@ res_read_number(const char *prompt) * cannot prove a mode is right, but they can prove one is wrong, and a wrong * one is refused here, in the menu, where there is still a screen to say so on. */ +/* "Keep this resolution?", drawn in the new mode, and Enter within ten + * seconds to keep it -- the question every desktop asks after a mode change, + * for the same reason. The numbers the firmware reports for a mode are no + * proof the screen shows it: AMD's GOP on a Ryzen APU and on the ASUS TUF + * "sets" the smaller modes without leaving the panel's native timing, and + * reports a row length that is the mode's rather than the panel's. Every + * check above passes and the picture comes out as three or four squashed + * copies side by side. Nobody can read the question on a screen like that, + * so nobody presses Enter, and the old mode comes back on its own. */ +static int +mode_confirmed(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) +{ + struct ui_fb uifb; + EFI_INPUT_KEY key; + const char *msg[2] = { "Keep this resolution?", + "If you can read this, press Enter" }; + + uifb.base = (ui_u8 *)(UINTN)gop->Mode->FrameBufferBase; + uifb.width = gop->Mode->Info->HorizontalResolution; + uifb.height = gop->Mode->Info->VerticalResolution; + uifb.stride = gop->Mode->Info->PixelsPerScanLine * 4; + ui_init(&uifb); + ui_notice("Resolution", msg, 2, "Keep", "going back in 10 seconds"); + + if (ST->ConIn) + while (!EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key))) + ; /* the key that chose the mode is not an answer */ + UINTN left = 10000; /* one countdown, however many hovers */ + for (;;) { + int item = 0; + int e = wait_event(&key, &item, &left); + if (e == EV_NONE) + break; + if (e == EV_CLICK) + return 1; + if (e == EV_KEY) + return key.UnicodeChar == L'\r' || key.UnicodeChar == L'\n'; + } + return 0; +} + static int mode_set_checked(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, UINT32 mode) { @@ -1087,10 +1377,11 @@ mode_set_checked(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, UINT32 mode) got->VerticalResolution == want->VerticalResolution && got->PixelsPerScanLine >= got->HorizontalResolution && gop->Mode->FrameBufferBase && - gop->Mode->FrameBufferSize >= need) + gop->Mode->FrameBufferSize >= need && + mode_confirmed(gop)) return 1; - describe_gop(gop, L"schos: mode does not add up, going back:"); + describe_gop(gop, L"schos: mode not kept, going back:"); gop->SetMode(gop, was); return 0; } @@ -1100,21 +1391,14 @@ static void res_refused(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) { struct ui_fb uifb; - EFI_INPUT_KEY key; - const char *msg[1] = { "The display refused that size -- keeping this one" }; + const char *msg[1] = { "Not kept -- back to the size before" }; uifb.base = (ui_u8 *)(UINTN)gop->Mode->FrameBufferBase; uifb.width = gop->Mode->Info->HorizontalResolution; uifb.height = gop->Mode->Info->VerticalResolution; uifb.stride = gop->Mode->Info->PixelsPerScanLine * 4; ui_init(&uifb); - ui_menu("Resolution", msg, 1, 0, "any key to go back"); - mirrors_show(); - for (;;) { - if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key))) - break; - BS->Stall(10000); - } + notice("Resolution", msg, 1, "OK", "any key to go back"); } static void @@ -1122,6 +1406,8 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) { int sel = 0; int echoed = -1; + static const char hint[] = + "W/S or arrows to choose, Enter to apply, Esc or Q to go back"; for (;;) { struct ui_fb uifb; @@ -1138,41 +1424,53 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) items[i] = RES_TEXT[i]; if (sel > RES_N) sel = RES_N; - ui_menu("Resolution", items, RES_N + 1, sel, - "W/S to choose, Enter to apply, Esc or Q to go back"); + ui_menu("Resolution", items, RES_N + 1, sel, hint); - if (echoed < 0) { - serial_menu("Resolution", items, RES_N + 1, sel, - "W/S to choose, Enter to apply, Esc or Q to go back", 0); - } else if (echoed != sel) { - serial_item(items, sel, sel, 0); - } + if (echoed < 0) + serial_menu("Resolution", items, RES_N + 1, sel, hint, 0); echoed = sel; mirrors_show(); - EFI_INPUT_KEY key; for (;;) { - if (ST->ConIn && - !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key))) - break; - BS->Stall(10000); - } + EFI_INPUT_KEY key; + int item = -1, act = 0; + int e = wait_event(&key, &item, 0); + + if (e == EV_HOVER || e == EV_CLICK) { + if (item != sel) { + sel = item; + ui_menu_select(sel); + serial_item(items, sel, sel, 0); + mirrors_show(); + } + act = e == EV_CLICK; + } else { + CHAR16 c = key.UnicodeChar; + /* Q as well as Esc, for the same reason f8_window() accepts + * 'o' as well as F8: on a serial line a lone ESC byte is + * indistinguishable from the start of an escape sequence, so + * the terminal driver holds it back waiting for the rest and + * it never arrives as a key. Esc is the obvious thing to + * press on a screen and unreachable on a cable. */ + if (key.ScanCode == 0x17 || c == 27 || c == L'q' || c == L'Q') + return; + int to = sel; + if (key.ScanCode == 0x01 || c == L'w' || c == L'W') + to = (sel + RES_N) % (RES_N + 1); + else if (key.ScanCode == 0x02 || c == L's' || c == L'S') + to = (sel + 1) % (RES_N + 1); + else if (c == L'\r' || c == L'\n') + act = 1; + if (to != sel) { + sel = to; + ui_menu_select(sel); + serial_item(items, sel, sel, 0); + mirrors_show(); + } + } + if (!act) + continue; - CHAR16 c = key.UnicodeChar; - /* Q as well as Esc, for the same reason f8_window() accepts 'o' as - * well as F8: on a serial line a lone ESC byte is indistinguishable - * from the start of an escape sequence, so the terminal driver holds - * it back waiting for the rest and it never arrives as a key. Esc is - * the obvious thing to press on a screen and unreachable on a cable. */ - if (key.ScanCode == 0x17 || c == 27 || c == L'q' || c == L'Q') - return; - if (key.ScanCode == 0x01 || c == L'w' || c == L'W') { - sel = (sel + RES_N) % (RES_N + 1); continue; - } - if (key.ScanCode == 0x02 || c == L's' || c == L'S') { - sel = (sel + 1) % (RES_N + 1); continue; - } - if (c == L'\r' || c == L'\n') { if (sel < RES_N) { if (!mode_set_checked(gop, RES_MODE[sel])) { res_refused(gop); @@ -1206,14 +1504,7 @@ res_choose(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) /* Nothing matched. Saying so beats silently keeping the old mode * and letting the user wonder whether the key registered. */ const char *msg[1] = { "No such mode -- keeping this one" }; - ui_menu("Resolution", msg, 1, 0, "any key to go back"); - mirrors_show(); - for (;;) { - if (ST->ConIn && - !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key))) - break; - BS->Stall(10000); - } + notice("Resolution", msg, 1, "OK", "any key to go back"); return; } } @@ -1469,19 +1760,205 @@ sys_info(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) mirrors_show(); EFI_INPUT_KEY key; + int item = 0; for (;;) { - if (ST->ConIn && !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key))) + int e = wait_event(&key, &item, 0); + if (e == EV_KEY || e == EV_CLICK) return; - BS->Stall(10000); } } +/* --- other systems ------------------------------------------------------------ + * + * A boot manager that can only boot itself is a menu with one item. Other + * operating systems on this machine leave their loader at a known path on an + * EFI system partition, and chain-loading one is the firmware's own + * LoadImage and StartImage: no ExitBootServices of ours is involved, and if the + * other loader gives control back the menu is still here. + * + * Only paths that are well known are probed. Walking every directory of every + * volume would find more and would also find things that are not an operating + * system, and a boot menu that lists a firmware updater is not an improvement. + */ +#define MAX_OTHERS 6 + +static const struct { const CHAR16 *path; const char *name; } KNOWN_LOADERS[] = { + { L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi", "Windows Boot Manager" }, + { L"\\EFI\\ubuntu\\shimx64.efi", "Ubuntu" }, + { L"\\EFI\\debian\\shimx64.efi", "Debian" }, + { L"\\EFI\\fedora\\shimx64.efi", "Fedora" }, + { L"\\EFI\\arch\\grubx64.efi", "Arch Linux" }, + { L"\\EFI\\opensuse\\grubx64.efi", "openSUSE" }, + { L"\\EFI\\systemd\\systemd-bootx64.efi", "systemd-boot" }, + { L"\\EFI\\refind\\refind_x64.efi", "rEFInd" }, + { L"\\EFI\\BOOT\\BOOTX64.EFI", "Another EFI loader" }, +}; + +static struct { + EFI_HANDLE vol; + const CHAR16 *path; + char text[96]; /* "name\ndetail", as the menu wants it */ +} OTHERS[MAX_OTHERS]; +static int OTHER_N; + +static void +others_find(void) +{ + EFI_GUID fs_guid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID; + EFI_HANDLE *h = 0; + UINTN n = 0; + + OTHER_N = 0; + if (EFI_ERROR(BS->LocateHandleBuffer(ByProtocol, &fs_guid, 0, &n, &h)) || !h) + return; + + for (UINTN i = 0; i < n && OTHER_N < MAX_OTHERS; i++) { + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs = 0; + EFI_FILE_PROTOCOL *root = 0; + if (EFI_ERROR(BS->HandleProtocol(h[i], &fs_guid, (void **)&fs)) || !fs) + continue; + if (EFI_ERROR(fs->OpenVolume(fs, &root)) || !root) + continue; + + for (UINTN k = 0; k < sizeof KNOWN_LOADERS / sizeof KNOWN_LOADERS[0] && + OTHER_N < MAX_OTHERS; k++) { + /* Our own volume is probed too -- SchOS shares the ESP with + * Windows on a laptop -- but the fallback path there is this + * loader, and offering to start ourselves again is no help. */ + if (h[i] == BOOT_VOL && k == sizeof KNOWN_LOADERS / sizeof KNOWN_LOADERS[0] - 1) + continue; + EFI_FILE_PROTOCOL *f = 0; + if (EFI_ERROR(root->Open(root, &f, (CHAR16 *)KNOWN_LOADERS[k].path, + EFI_FILE_MODE_READ, 0)) || !f) + continue; + f->Close(f); + + int o = OTHER_N++; + OTHERS[o].vol = h[i]; + OTHERS[o].path = KNOWN_LOADERS[k].path; + int c = 0; + for (const char *q = KNOWN_LOADERS[k].name; *q && c < 40; q++) + OTHERS[o].text[c++] = *q; + OTHERS[o].text[c++] = '\n'; + for (const char *q = "Start it from "; *q; q++) + OTHERS[o].text[c++] = *q; + char pn[48]; + narrow(KNOWN_LOADERS[k].path, pn, sizeof pn); + for (const char *q = pn; *q && c < (int)sizeof OTHERS[o].text - 1; q++) + OTHERS[o].text[c++] = *q; + OTHERS[o].text[c] = 0; + } + root->Close(root); + } + BS->FreePool(h); +} + +/* Start another loader. The device path for LoadImage is the volume's own path + * with one more node on the end naming the file: copy the volume's nodes up to + * its terminator, append a file-path node, and terminate again. */ +static void +chain_load(int o) +{ + EFI_GUID dp_guid = EFI_DEVICE_PATH_PROTOCOL_GUID; + static UINT8 dp[512]; + UINT8 *vol = 0; + UINTN len = 0; + + if (EFI_ERROR(BS->HandleProtocol(OTHERS[o].vol, &dp_guid, (void **)&vol)) || !vol) + return; + + for (;;) { + UINTN nl = (UINTN)vol[len + 2] | ((UINTN)vol[len + 3] << 8); + if (nl < 4 || len + nl > sizeof dp - 64) + return; + if (vol[len] == 0x7F && vol[len + 1] == 0xFF) /* End of Entire Path */ + break; + len += nl; + } + copy(dp, vol, len); + + UINTN chars = 0; + while (OTHERS[o].path[chars]) + chars++; + UINTN nl = 4 + (chars + 1) * 2; + if (len + nl + 4 > sizeof dp) + return; + dp[len] = 4; dp[len + 1] = 4; /* media, file path */ + dp[len + 2] = (UINT8)nl; dp[len + 3] = (UINT8)(nl >> 8); + copy(dp + len + 4, OTHERS[o].path, (chars + 1) * 2); + len += nl; + dp[len] = 0x7F; dp[len + 1] = 0xFF; dp[len + 2] = 4; dp[len + 3] = 0; + + EFI_HANDLE img = 0; + if (EFI_ERROR(BS->LoadImage(0, SELF, dp, 0, 0, &img)) || !img) { + const char *msg[1] = { "The firmware would not load it." }; + notice("Start another system", msg, 1, "OK", "any key to go back"); + return; + } + BS->StartImage(img, 0, 0); + BS->UnloadImage(img); +} + +/* Ask the firmware to open its own setup on the next start, then restart. + * + * This used to halt with "reset to reach firmware setup", which is the screen + * of a loader that never learned the protocol. The firmware publishes a + * variable for exactly this: OsIndicationsSupported says what it can do and + * OsIndications is where the request goes. Not every firmware has it, and the + * ones that do not are told so rather than restarted for nothing. */ +static void +firmware_setup(void) +{ + EFI_RUNTIME_SERVICES *rt = ST->RuntimeServices; + EFI_GUID gv = EFI_GLOBAL_VARIABLE_GUID; + UINT64 supported = 0, cur = 0; + UINTN sz = sizeof supported; + UINT32 attr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; + + if (EFI_ERROR(rt->GetVariable(L"OsIndicationsSupported", &gv, 0, &sz, + &supported)) || + !(supported & EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) { + const char *msg[2] = { "This firmware cannot be asked to open its", + "setup. Use its own key when powering on." }; + notice("Firmware Setup", msg, 2, "OK", "any key to go back"); + return; + } + + UINT32 a = 0; + sz = sizeof cur; + if (EFI_ERROR(rt->GetVariable(L"OsIndications", &gv, &a, &sz, &cur))) + cur = 0; + cur |= EFI_OS_INDICATIONS_BOOT_TO_FW_UI; + if (EFI_ERROR(rt->SetVariable(L"OsIndications", &gv, attr, sizeof cur, &cur))) { + const char *msg[1] = { "The firmware refused the request." }; + notice("Firmware Setup", msg, 1, "OK", "any key to go back"); + return; + } + rt->ResetSystem(EfiResetCold, 0, 0, 0); + for (;;) __asm__ volatile("hlt"); +} + +static void +power(UINTN how) +{ + ST->RuntimeServices->ResetSystem(how, 0, 0, 0); + for (;;) __asm__ volatile("hlt"); +} + +enum { A_BOOT, A_VERBOSE, A_OTHER, A_DISPLAY, A_INFO, A_FIRMWARE, A_RESTART, + A_SHUTDOWN, A_DEBUG }; + static int boot_menu(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) { int selected = 0; int echoed = -1; // the serial copy: header once, then moves - char resbuf[48]; + char resbuf[96]; + + connect_all(); + pointers_find(); + others_find(); for (;;) { /* Re-read the framebuffer every pass: choosing a resolution moves it, @@ -1496,78 +1973,97 @@ boot_menu(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) res_row(gop, resbuf, sizeof resbuf); - // Six entries in a debug build, five in a release one. - // // The debugger entry is compiled out rather than hidden, because a // menu item that is present and refuses is an invitation to work out // how to make it not refuse. An ISO built for anybody else has no way // to turn a network debugger on, because the code that would set the // flag is not in it. - const char *items[6]; - int nitems = 5; - items[0] = "Boot SchOS"; - items[1] = "Boot SchOS (verbose)"; - items[2] = resbuf; - items[3] = "System Information"; - items[4] = "Reboot to firmware"; + // + // An item is a label, then a newline and a line of detail; a leading + // \001 starts a new group, drawn with a gap above it. `what` says what + // each row does, because the number of rows is not fixed: other + // systems found on the disks are listed after the two ways of + // starting this one. + const char *items[8 + MAX_OTHERS]; + int what[8 + MAX_OTHERS], arg[8 + MAX_OTHERS]; + int nitems = 0; +#define ITEM(text, a, x) do { items[nitems] = (text); what[nitems] = (a); arg[nitems] = (x); nitems++; } while (0) + ITEM("Boot SchOS\nStart the system", A_BOOT, 0); + ITEM("Boot SchOS (verbose)\nShow kernel messages while starting", A_VERBOSE, 0); + for (int o = 0; o < OTHER_N; o++) + ITEM(OTHERS[o].text, A_OTHER, o); + ITEM(resbuf, A_DISPLAY, 0); + ITEM("System Information\nFirmware, processor and memory", A_INFO, 0); + ITEM("\001Firmware Setup\nRestart into the UEFI settings", A_FIRMWARE, 0); + ITEM("Restart\nReboot the machine", A_RESTART, 0); + ITEM("Shut Down\nPower the machine off", A_SHUTDOWN, 0); #ifdef SCHOS_DEBUG - items[5] = DEBUG_BOOT ? "Remote debugging: on (schdbgd, port 4444)" - : "Remote debugging: off"; - nitems = 6; + ITEM(DEBUG_BOOT ? "Remote debugging: on\nschdbgd, port 4444" + : "Remote debugging: off\nschdbgd, port 4444", A_DEBUG, 0); #endif + const char *hint = "W/S or arrows to choose, Enter to select - number keys jump"; + if (selected >= nitems) selected = 0; - ui_menu("Boot Manager", items, nitems, selected, - nitems == 6 ? "W/S or 1-6 to choose, Enter to select" - : "W/S or 1-5 to choose, Enter to select"); + ui_menu("SchOS Boot Manager", items, nitems, selected, hint); // The same menu on the serial line: the whole thing once, then one // line per *move*. Reprinting on every keypress would be correct and // unreadable -- a serial terminal has no cursor to paint over, so an // unrecognised key would scroll the selection past again for nothing. if (echoed < 0) { - serial_menu("Boot Manager", items, nitems, selected, - nitems == 6 ? "W/S or 1-6 to choose, Enter to select" - : "W/S or 1-5 to choose, Enter to select", 1); - } else if (echoed != selected) { - serial_item(items, selected, selected, 1); + serial_menu("Boot Manager", items, nitems, selected, hint, 1); } echoed = selected; mirrors_show(); - EFI_INPUT_KEY key; - CHAR16 c = 0; - UINT16 scan = 0; for (;;) { - if (ST->ConIn && - !EFI_ERROR(ST->ConIn->ReadKeyStroke(ST->ConIn, &key))) { - c = key.UnicodeChar; scan = key.ScanCode; break; + EFI_INPUT_KEY key; + int item = -1, act = 0; + int e = wait_event(&key, &item, 0); + int to = selected; + + if (e == EV_HOVER || e == EV_CLICK) { + to = item; + act = e == EV_CLICK; + } else { + CHAR16 c = key.UnicodeChar; + if (key.ScanCode == 0x01 || c == L'w' || c == L'W') + to = (selected + nitems - 1) % nitems; + else if (key.ScanCode == 0x02 || c == L's' || c == L'S') + to = (selected + 1) % nitems; + else if (c >= L'1' && c <= L'9' && (int)(c - L'1') < nitems) + to = (int)(c - L'1'); + else if (c == L'\r' || c == L'\n') + act = 1; } - BS->Stall(10000); - } - if (scan == 0x01) { selected = (selected + nitems - 1) % nitems; continue; } - if (scan == 0x02) { selected = (selected + 1) % nitems; continue; } - if (c == L'w' || c == L'W') { selected = (selected + nitems - 1) % nitems; continue; } - if (c == L's' || c == L'S') { selected = (selected + 1) % 5; continue; } - if (c >= L'1' && c <= L'0' + nitems) { selected = (int)(c - L'1'); continue; } + if (to != selected) { + selected = to; + ui_menu_select(selected); + serial_item(items, selected, selected, 1); + echoed = selected; + mirrors_show(); + } + if (!act) + continue; - if (c == L'\r' || c == L'\n') { - if (selected == 0) return 0; - if (selected == 1) return 1; - if (selected == 2) { res_choose(gop); continue; } - if (selected == 3) { sys_info(gop); continue; } + switch (what[selected]) { + case A_BOOT: return 0; + case A_VERBOSE: return 1; + case A_OTHER: chain_load(arg[selected]); break; + case A_DISPLAY: res_choose(gop); break; + case A_INFO: sys_info(gop); break; + case A_FIRMWARE: firmware_setup(); break; + case A_RESTART: power(EfiResetCold); break; + case A_SHUTDOWN: power(EfiResetShutdown); break; #ifdef SCHOS_DEBUG // A toggle rather than an action: picking it changes what the // entry says and leaves you in the menu, so the state you booted // with is the state you can see before you boot. - if (selected == 5) { DEBUG_BOOT = !DEBUG_BOOT; echoed = -1; continue; } + case A_DEBUG: DEBUG_BOOT = !DEBUG_BOOT; echoed = -1; break; #endif - if (selected == 4) { - ui_clear(); - ui_text_centered((int)ui_height() / 2, "Halted -- reset to reach firmware setup", - 0xB9BEC7u); - for (;;) __asm__ volatile("hlt"); } + break; // repaint: the thing that ran may have changed the screen } } } @@ -2146,6 +2642,7 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) { ST = systab; BS = systab->BootServices; + SELF = image; print(L"SchOS UEFI loader\r\n"); diff --git a/cmd/chvt/main.c b/cmd/chvt/main.c index aa78fc6..08a9b99 100644 --- a/cmd/chvt/main.c +++ b/cmd/chvt/main.c @@ -19,13 +19,14 @@ // guest cannot detect this: a key the host swallowed is indistinguishable from // a key nobody pressed. So it is a setting. // -// chvt -c altgr+F AltGr+F1..F6 (the default) -// chvt -c ctrl+shift+F Ctrl+Shift+F1..F6 -// chvt -c alt+1 Alt+1..6 +// chvt -c ctrl+alt+F Ctrl+Alt+F1..F12 (the default) +// chvt -c altgr+F AltGr+F1..F12 +// chvt -c ctrl+shift+F Ctrl+Shift+F1..F12 +// chvt -c alt+1 Alt+1..9 // chvt -c off no chord at all // // Whatever the modifiers are, they also bind minus and equals to the previous -// and next terminal in use -- AltGr+- and AltGr+= by default. +// and next terminal in use -- Ctrl+Alt+- and Ctrl+Alt+= by default. #include "core/types.h" #include "uapi/user.h" diff --git a/cmd/ldlsd/main.c b/cmd/ldlsd/main.c index 379ace4..24918da 100644 --- a/cmd/ldlsd/main.c +++ b/cmd/ldlsd/main.c @@ -9,10 +9,10 @@ // // ldlsd [vmlinux-btf] // -// **Neither file ships with SchOS.** Both are the user's: a module they -// obtained, and the BTF of the kernel it was built for. docs/licensing.md §6 -// is the whole argument, and `.gitignore` refusing `*.ko` is how it is kept -// mechanically rather than by intention. +// The module and the BTF of the kernel it was built for come from a Linux build +// (`make ldls-stage`, `make ldls-r8169`). SchOS ships Linux's r8169 set in its +// ISO; docs/licensing.md §6 has the licensing, and `.gitignore` refusing `*.ko` +// only keeps binaries out of git. // // What it does *not* yet do is implement any of the symbols the driver asks // for -- every one is still a stub that reports itself and returns zero, which @@ -185,6 +185,32 @@ ldls_stub(long idx, unsigned long *args) struct rctx { struct ko *k; struct stubs *s; }; +// Modules loaded before the one being relocated. A module set is loaded in +// dependency order -- libphy before r8169, ttm before amdgpu -- and what an +// earlier one defines is what a later one imports from it. +#define MAXMOD 32 +static struct ko *g_mods[MAXMOD]; +static int g_nmods; + +static unsigned long long +find_global(struct ko *k, const char *want) +{ + int i; + for (i = 0; i < k->nsym; i++) { + if ((k->sym[i].st_info >> 4) == 0) // STB_LOCAL: not for others + continue; + if (k->sym[i].st_shndx == KO_SHN_UNDEF || + k->sym[i].st_shndx >= (unsigned short)k->nsh) + continue; + if (!k->sec[k->sym[i].st_shndx].alloc) + continue; + if (!streq(k->str + k->sym[i].st_name, want)) + continue; + return k->sec[k->sym[i].st_shndx].addr + k->sym[i].st_value; + } + return 0; +} + // The KPI first, a logging stub second. // // An unimplemented symbol still resolves -- to something that reports itself @@ -208,6 +234,14 @@ resolve(void *ctx, const char *name, unsigned long long *out) return 0; } + for (i = 0; i < g_nmods; i++) { + unsigned long long a = find_global(g_mods[i], name); + if (a) { + *out = a; + return 0; + } + } + for (i = 0; i < r->k->nundef; i++) if (streq(r->k->undef[i], name)) { *out = stub_addr(r->s, i); @@ -230,7 +264,7 @@ resolve(void *ctx, const char *name, unsigned long long *out) // bargain as the logging stubs, and the same caveat -- see the memcpy note in // docs/architecture/ldls.md. -#define PERCPU_SLOTS 8 +#define PERCPU_SLOTS 16 #define PERCPU_SIZE (16 * 1024) static char percpu_mem[PERCPU_SLOTS][PERCPU_SIZE]; @@ -260,6 +294,14 @@ percpu_for(void *ctx, const char *name) *(unsigned long long *)percpu_mem[i] = (unsigned long long)(void *)(percpu_mem[i] + 64); puts_(" per-CPU "); puts_(name); puts_(" -- a task of zeroes\n"); + } else if (streq(name, "__preempt_count")) { + // x86 keeps PREEMPT_NEED_RESCHED inverted in this word, so that + // `decl %gs:__preempt_count; je resched` is one instruction. Zero + // means "count zero *and* a reschedule wanted", and every + // preempt_enable() then called preempt_schedule -- see the note on + // __SCT__preempt_schedule in ldls/kpi/base.c for what that cost. + *(unsigned int *)percpu_mem[i] = 0x80000000u; + puts_(" per-CPU "); puts_(name); puts_(" -- PREEMPT_ENABLED\n"); } else { puts_(" per-CPU "); puts_(name); puts_(" -- 16K of zeroes\n"); } @@ -284,6 +326,33 @@ find_symbol(struct ko *k, const char *want) return 0; } +// "r8169: rtl_open.cold+0x4f" for an address in a loaded module, so that a +// photograph of the screen is enough to find the line. Modules are named by +// the file they were loaded from, with the directory and ".ko" dropped. +static const char *g_modname[MAXMOD]; + +static void +where(unsigned long addr) +{ + for (int m = 0; m < g_nmods; m++) { + struct ko *k = g_mods[m]; + for (int i = 0; i < k->nsym; i++) { + const struct ko_sym *s = &k->sym[i]; + if (s->st_shndx == KO_SHN_UNDEF || s->st_shndx >= (unsigned short)k->nsh || + !k->sec[s->st_shndx].alloc || s->st_size == 0 || (s->st_info & 0xf) != 2) + continue; + unsigned long long a = k->sec[s->st_shndx].addr + s->st_value; + if (addr >= a && addr < a + s->st_size) { + puts_(g_modname[m]); puts_(": "); + puts_(k->str + s->st_name); puts_("+"); + puthex(addr - a); + return; + } + } + } + puthex(addr); +} + // --- reading and writing a struct at an offset BTF gave us -------------------- static unsigned int @@ -430,6 +499,9 @@ kpi_layout_from_btf(struct btf *b) GET(nd, "dev_addr", kpi_l.nd_dev_addr); GET(nd, "perm_addr", kpi_l.nd_perm_addr); GET(nd, "addr_len", kpi_l.nd_addr_len); + // Optional: older kernels do not have it, and 0 is how that is said. + if (btf_member_off(b, nd, "priv_len", &off, &size) == 0) + kpi_l.nd_priv_len = off; GET(nd, "mc", kpi_l.nd_mc); GET(nd, "uc", kpi_l.nd_uc); GET(nd, "_tx", kpi_l.nd_tx); @@ -445,8 +517,42 @@ kpi_layout_from_btf(struct btf *b) GET(pd, "dev", kpi_l.pd_dev); GET(dv, "kobj", kpi_l.dev_kobj); GET(ko, "name", kpi_l.kobj_name); + kpi_l.dev_kobj_ok = 1; } } + // The driver core and struct firmware. Optional: a driver that never + // registers a bus or loads firmware does not need them, so a BTF without + // one of these turns the feature off rather than refusing to run. + { + int dv = btf_find(b, "device", BTF_KIND_STRUCT); + int dd = btf_find(b, "device_driver", BTF_KIND_STRUCT); + int bt = btf_find(b, "bus_type", BTF_KIND_STRUCT); + int fw = btf_find(b, "firmware", BTF_KIND_STRUCT); + int ok = dv && dd && bt; +#define OPT(id, name, field) \ + do { if (btf_member_off(b, id, name, &off, &size) < 0) ok = 0; \ + else field = off; } while (0) + if (ok) { + OPT(dv, "bus", kpi_l.dev_bus); + OPT(dv, "driver", kpi_l.dev_driver); + OPT(dd, "bus", kpi_l.drv_bus); + OPT(dd, "probe", kpi_l.drv_probe); + OPT(dd, "name", kpi_l.drv_name); + OPT(bt, "match", kpi_l.bus_match); + OPT(bt, "probe", kpi_l.bus_probe); + } + kpi_l.core_ok = ok; + if (fw) { + ok = 1; + OPT(fw, "size", kpi_l.fw_size); + OPT(fw, "data", kpi_l.fw_data); + kpi_l.fw_size_of = btf_type_size(b, fw); + kpi_l.fw_ok = ok; + } +#undef OPT + puts_(" kpi driver core "); puts_(kpi_l.core_ok ? "on" : "off"); + puts_(", firmware "); puts_(kpi_l.fw_ok ? "on" : "off"); puts_("\n"); + } { int nq = btf_find(b, "netdev_queue", BTF_KIND_STRUCT); if (!nq) { puts_("ldlsd: no struct netdev_queue\n"); return -1; } @@ -682,8 +788,11 @@ drive_probe(const char *btfpath) { struct pciuser_info in; + // No device: not a failure worth restarting for. schinit's + // KeepAlive would otherwise start this again every few seconds on + // every machine without the NIC. See SCHINIT_EXIT_NORESTART. if (claim_for(idtab, id_size, id_vendor, id_device, &in) < 0) - return; + exit_group(78); pdev = (unsigned long)getmem(dev_size); if (!pdev) { puts_("ldlsd: no memory for a pci_dev\n"); return; } @@ -714,6 +823,26 @@ drive_probe(const char *btfpath) (unsigned char)(q >> (i * 8)); } + // pdev->bus. pci_domain_nr() and pci_dev_id() are inlines that + // dereference it -- r8169 names its MDIO bus with both -- and a null + // one is a fault at the first line of MDIO setup. + { + unsigned int pd_bus = lay(&b, "pci_dev", "bus"); + unsigned int pb_num = lay(&b, "pci_bus", "number"); + unsigned int pb_sys = lay(&b, "pci_bus", "sysdata"); + unsigned int pb_size = lay(&b, "pci_bus", 0); + if (pd_bus != 0xffffffffu && pb_num != 0xffffffffu && + pb_sys != 0xffffffffu && pb_size != 0xffffffffu) { + unsigned long bus = (unsigned long)getmem(pb_size); + unsigned long sys = (unsigned long)getmem(4096); + if (bus && sys) { + ((unsigned char *)bus)[pb_num] = (unsigned char)in.bus; + bcopy_((void *)(bus + pb_sys), &sys, 8); + bcopy_((void *)(pdev + pd_bus), &bus, 8); + } + } + } + wr16(pdev, dev_vendor, in.vendor); wr16(pdev, dev_device, in.device); wr32(pdev, dev_devfn, (unsigned int)((in.slot << 3) | in.func)); @@ -795,21 +924,145 @@ drive_probe(const char *btfpath) } } -// --- main ---------------------------------------------------------------------- - -int -main(int argc, char *argv[]) +// Read the BTF and fill in the KPI's layouts, before any module runs. +static void +early_layout(const char *btfpath) { - struct ko k; + static struct btf b; + unsigned long long blen; + unsigned char *bimg = slurp(btfpath, &blen); + long nt; + unsigned int *boff; + + if (!bimg) return; + nt = btf_count(bimg, blen); + if (nt < 0) return; + boff = getmem(((unsigned long long)nt + 2) * 4); + if (!boff || btf_open(&b, bimg, blen, boff, (unsigned int)nt + 2) < 0) + return; + { + extern unsigned int kpi_netdev_size; + int nd = btf_find(&b, "net_device", BTF_KIND_STRUCT); + kpi_netdev_size = nd ? btf_type_size(&b, nd) : 0; + } + kpi_layout_from_btf(&b); +} + +// One module: read, laid out, relocated, its %gs references fixed, and its +// init_module called. Kept loaded for the ones after it to link against. +static void +load_module(const char *path) +{ + struct ko *k; struct ko_section *sec; - struct stubs stubs; + struct stubs *stubs; struct rctx rc; unsigned char *img, *mem, *code; unsigned long long len, span, entry; int nsec, i; + k = getmem(sizeof(*k)); + stubs = getmem(sizeof(*stubs)); + if (!k || !stubs) { puts_("ldlsd: out of memory\n"); exit(); } + + img = slurp(path, &len); + if (!img) { puts_("ldlsd: cannot read "); puts_(path); puts_("\n"); exit(); } + + nsec = ko_sections_needed(img, len); + if (nsec < 0) { + puts_("ldlsd: that is not an ELF64 relocatable object\n"); + exit(); + } + sec = getmem((unsigned long long)(nsec + 1) * sizeof(struct ko_section)); + if (!sec) { puts_("ldlsd: out of memory\n"); exit(); } + + if (ko_open(k, img, len, sec, nsec) < 0) { + puts_("ldlsd: "); puts_(ko_err(k)); puts_("\n"); + exit(); + } + + puts_(path); puts_("\n"); + puts_(" module "); + puts_(ko_name(k) ? ko_name(k) : "(not a kernel module)"); puts_("\n"); + puts_(" license "); + puts_(ko_modinfo(k, "license") ? ko_modinfo(k, "license") + : "(none declared)"); puts_("\n"); + puts_(" wants "); putn(k->nundef); puts_(" symbols\n"); + + span = ko_span(k); + mem = getmem(span ? span : 4096); + code = getmem((unsigned long long)(k->nundef + 1) * STUB_SIZE); + if (!mem || !code) { puts_("ldlsd: out of memory\n"); exit(); } + + stub_build_from(stubs, code, k->nundef, g_count); + for (i = 0; i < k->nundef; i++) { + g_names[g_count + i] = k->undef[i]; + if (stub_is_bare(k->undef[i])) + stub_make_bare(code + (unsigned long long)i * STUB_SIZE); + } + g_count += k->nundef; + k_nundef_global = g_count; + + if (ko_layout(k, mem, span, (unsigned long long)mem) < 0) { + puts_("ldlsd: layout: "); puts_(ko_err(k)); puts_("\n"); + exit(); + } + + rc.k = k; rc.s = stubs; + if (ko_relocate(k, resolve, &rc) < 0) { + puts_("ldlsd: relocate: "); puts_(ko_err(k)); puts_("\n"); + exit(); + } + // The stack canary is fetched through %gs, which belongs to the kernel + // here. See ko_defuse_gs(). + { + int g = ko_defuse_gs(k, "__ref_stack_chk_guard"); + if (g < 0) g = 0; + if (g == 0) g = ko_defuse_gs(k, "__stack_chk_guard"); + if (g > 0) { puts_(" defused "); putn(g); puts_(" %gs stack-guard load(s)\n"); } + } + // Whatever %gs references are left name real per-CPU variables, and those + // cannot be defused -- the driver dereferences what it reads. + { + int p = ko_fixup_percpu(k, percpu_for, 0); + if (p < 0) { + puts_("ldlsd: per-CPU: "); puts_(ko_err(k)); puts_("\n"); + exit(); + } + if (p > 0) { puts_(" rehomed "); putn(p); puts_(" %gs per-CPU reference(s)\n"); } + } + + puts_(" loaded "); putn(span); puts_(" bytes at "); puthex((unsigned long long)mem); + puts_("\n"); + + entry = find_symbol(k, "init_module"); + if (g_nmods < MAXMOD) { + const char *b = path, *q; + for (q = path; *q; q++) + if (*q == '/') b = q + 1; + g_modname[g_nmods] = b; + g_mods[g_nmods++] = k; + kpi_where = where; + } + if (!entry) { puts_(" no init_module -- nothing to call\n"); return; } + + puts_("\n calling init_module\n\n"); + { + int (*init)(void) = (int (*)(void))(void *)entry; + int r = init(); + puts_("\n init_module returned "); putn(r); + puts_(" after "); putn(g_calls); puts_(" stub call(s)\n"); + } + +} + +// --- main ---------------------------------------------------------------------- + +int +main(int argc, char *argv[]) +{ if (argc < 2) { - puts_("usage: ldlsd [vmlinux-btf] [bus:slot.func]\n"); + puts_("usage: ldlsd [,...] [vmlinux-btf] [bus:slot.func]\n"); puts_(" neither file ships with SchOS; both are yours.\n"); exit(); } @@ -837,85 +1090,32 @@ main(int argc, char *argv[]) puthexn(sel_slot, 2); puts_("."); puthexn(sel_func, 1); puts_("\n"); } - img = slurp(argv[1], &len); - if (!img) { puts_("ldlsd: cannot read "); puts_(argv[1]); puts_("\n"); exit(); } - - nsec = ko_sections_needed(img, len); - if (nsec < 0) { - puts_("ldlsd: that is not an ELF64 relocatable object\n"); - exit(); - } - sec = getmem((unsigned long long)(nsec + 1) * sizeof(struct ko_section)); - if (!sec) { puts_("ldlsd: out of memory\n"); exit(); } - - if (ko_open(&k, img, len, sec, nsec) < 0) { - puts_("ldlsd: "); puts_(ko_err(&k)); puts_("\n"); - exit(); - } - - puts_(argv[1]); puts_("\n"); - puts_(" module "); - puts_(ko_name(&k) ? ko_name(&k) : "(not a kernel module)"); puts_("\n"); - puts_(" license "); - puts_(ko_modinfo(&k, "license") ? ko_modinfo(&k, "license") - : "(none declared)"); puts_("\n"); - puts_(" wants "); putn(k.nundef); puts_(" symbols\n"); - - span = ko_span(&k); - mem = getmem(span ? span : 4096); - code = getmem((unsigned long long)(k.nundef + 1) * STUB_SIZE); - if (!mem || !code) { puts_("ldlsd: out of memory\n"); exit(); } - - stub_build(&stubs, code, k.nundef); - g_names = k.undef; - g_count = k.nundef; - k_nundef_global = k.nundef; - kpi.netfd = -1; - for (i = 0; i < k.nundef; i++) - if (stub_is_bare(k.undef[i])) - stub_make_bare(code + (unsigned long long)i * STUB_SIZE); - - if (ko_layout(&k, mem, span, (unsigned long long)mem) < 0) { - puts_("ldlsd: layout: "); puts_(ko_err(&k)); puts_("\n"); - exit(); - } - - rc.k = &k; rc.s = &stubs; - if (ko_relocate(&k, resolve, &rc) < 0) { - puts_("ldlsd: relocate: "); puts_(ko_err(&k)); puts_("\n"); - exit(); - } - // The stack canary is fetched through %gs, which belongs to the kernel - // here. See ko_defuse_gs(). + // argv[1] is one module, or a set in dependency order separated by + // commas: libphy.ko,realtek.ko,r8169.ko. Each is loaded, relocated + // against the KPI and the ones before it, and initialised before the next. { - int g = ko_defuse_gs(&k, "__ref_stack_chk_guard"); - if (g < 0) g = 0; - if (g == 0) g = ko_defuse_gs(&k, "__stack_chk_guard"); - if (g > 0) { puts_(" defused "); putn(g); puts_(" %gs stack-guard load(s)\n"); } - } - // Whatever %gs references are left name real per-CPU variables, and those - // cannot be defused -- the driver dereferences what it reads. - { - int p = ko_fixup_percpu(&k, percpu_for, 0); - if (p < 0) { - puts_("ldlsd: per-CPU: "); puts_(ko_err(&k)); puts_("\n"); - exit(); + static char list[4096]; + char *p = list, *name; + int n = 0; + while (argv[1][n] && n < (int)sizeof(list) - 1) { list[n] = argv[1][n]; n++; } + list[n] = 0; + // The layouts first: init_module is where a module registers its + // drivers, and the driver core needs struct offsets to do that. + if (argc > 2) + early_layout(argv[2]); + g_names = getmem(sizeof(char *) * (unsigned long long)MAXMOD * KO_MAXUNDEF); + if (!g_names) { puts_("ldlsd: out of memory\n"); exit(); } + kpi.netfd = -1; + for (;;) { + name = p; + while (*p && *p != ',') p++; + int last = *p == 0; + *p = 0; + if (*name) + load_module(name); + if (last) break; + p++; } - if (p > 0) { puts_(" rehomed "); putn(p); puts_(" %gs per-CPU reference(s)\n"); } - } - - puts_(" loaded "); putn(span); puts_(" bytes at "); puthex((unsigned long long)mem); - puts_("\n"); - - entry = find_symbol(&k, "init_module"); - if (!entry) { puts_(" no init_module -- nothing to call\n"); exit(); } - - puts_("\n calling init_module\n\n"); - { - int (*init)(void) = (int (*)(void))(void *)entry; - int r = init(); - puts_("\n init_module returned "); putn(r); - puts_(" after "); putn(g_calls); puts_(" stub call(s)\n"); } if (argc > 2 && g_pci_driver) diff --git a/cmd/memfdtest/main.c b/cmd/memfdtest/main.c index 75b721d..ebdd60b 100644 --- a/cmd/memfdtest/main.c +++ b/cmd/memfdtest/main.c @@ -2,7 +2,7 @@ // // memfdtest -- two processes map the same memfd, one writes, the other sees it. // -// That sentence is docs/plan/foundations.md's own acceptance criterion for +// That sentence is docs/architecture/kernel.md's own acceptance criterion for // item 2, and it is the check a permissive implementation cannot pass: an // mmap that quietly returns private zeroed pages satisfies every return code // here and fails the one assertion that matters, because the reader is a diff --git a/cmd/passwd/main.c b/cmd/passwd/main.c new file mode 100644 index 0000000..9f4426f --- /dev/null +++ b/cmd/passwd/main.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// passwd [USER] +// +// Change a password. Setuid root, like su and for the same reason a program of +// its own: it has to write /etc/shadow, which no one else can. +// +// Anyone may change their own password after proving they know the old one; +// root may change anyone's without it. The new one is stored as a SHA-512 +// crypt hash with a fresh random salt, and the file is replaced by rename, so +// a crash leaves the old shadow or the new one and never half of each. + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int +read_password(const char *prompt, char *buf, size_t n) +{ + int fd = open("/dev/tty", O_RDWR | O_NOCTTY); + struct termios t, old; + size_t i = 0; + char c; + + if (fd < 0) + return -1; + if (tcgetattr(fd, &old) == 0) { + t = old; + t.c_lflag &= ~(ECHO | ECHONL); + tcsetattr(fd, TCSAFLUSH, &t); + } + if (write(fd, prompt, strlen(prompt)) < 0) {} + while (read(fd, &c, 1) == 1 && c != '\n' && c != '\r') + if (i + 1 < n) + buf[i++] = c; + buf[i] = 0; + tcsetattr(fd, TCSAFLUSH, &old); + if (write(fd, "\n", 1) < 0) {} + close(fd); + return 0; +} + +static void +make_salt(char *out) +{ + static const char a[] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + unsigned char r[16]; + int fd = open("/dev/urandom", O_RDONLY); + + if (fd < 0 || read(fd, r, sizeof r) != (ssize_t)sizeof r) { + fprintf(stderr, "passwd: no random numbers\n"); + exit(1); + } + close(fd); + strcpy(out, "$6$"); + for (int i = 0; i < 16; i++) + out[3 + i] = a[r[i] & 63]; + out[19] = '$'; + out[20] = 0; +} + +int +main(int argc, char **argv) +{ + uid_t uid = getuid(); + struct passwd *me = getpwuid(uid), *pw; + // getpwuid and getpwnam share one static buffer: keep the name we were + // given, not a pointer into it. + char *user = argc > 1 ? argv[1] : (me ? strdup(me->pw_name) : 0); + char oldp[256], p1[256], p2[256], salt[24], *hash; + FILE *in, *out; + char line[1024], tmp[] = "/etc/shadow.new"; + long days = (long)(time(0) / 86400); + int found = 0; + + if (!user || !(pw = getpwnam(user))) { + fprintf(stderr, "passwd: unknown user\n"); + return 1; + } + { + static struct passwd copy; + copy = *pw; + copy.pw_name = strdup(pw->pw_name); + pw = © + } + if (geteuid() != 0) { + fprintf(stderr, "passwd: must be setuid root\n"); + return 1; + } + if (uid != 0 && pw->pw_uid != uid) { + fprintf(stderr, "passwd: you may only change your own password\n"); + return 1; + } + + if (uid != 0) { + struct spwd *sp = getspnam(pw->pw_name); + const char *cur = sp ? sp->sp_pwdp : ""; + if (cur[0]) { + if (read_password("Current password: ", oldp, sizeof oldp) < 0) + return 1; + char *e = crypt(oldp, cur); + memset(oldp, 0, sizeof oldp); + if (!e || strcmp(e, cur) != 0) { + sleep(2); + fprintf(stderr, "passwd: incorrect password\n"); + return 1; + } + } + } + + if (read_password("New password: ", p1, sizeof p1) < 0 || + read_password("Retype new password: ", p2, sizeof p2) < 0) + return 1; + if (strcmp(p1, p2) != 0) { + fprintf(stderr, "passwd: the passwords do not match\n"); + return 1; + } + make_salt(salt); + // An empty password stays empty: crypt of "" is a hash, and the user asked + // for no password at all. + hash = p1[0] ? crypt(p1, salt) : ""; + if (!hash) { + fprintf(stderr, "passwd: cannot hash\n"); + return 1; + } + + if (!(in = fopen("/etc/shadow", "r"))) { + fprintf(stderr, "passwd: /etc/shadow: %s\n", strerror(errno)); + return 1; + } + umask(0077); + if (!(out = fopen(tmp, "w"))) { + fprintf(stderr, "passwd: %s: %s\n", tmp, strerror(errno)); + return 1; + } + while (fgets(line, sizeof line, in)) { + size_t n = strlen(pw->pw_name); + if (strncmp(line, pw->pw_name, n) == 0 && line[n] == ':') { + // name:hash:lastchg:min:max:warn:inactive:expire:reserved + char *f = strchr(line + n + 1, ':'); + fprintf(out, "%s:%s:%ld%s", pw->pw_name, hash, days, + f && (f = strchr(f + 1, ':')) ? f : ":0:::::\n"); + found = 1; + } else { + fputs(line, out); + } + } + fclose(in); + if (!found) + fprintf(out, "%s:%s:%ld:0:::::\n", pw->pw_name, hash, days); + memset(p1, 0, sizeof p1); + memset(p2, 0, sizeof p2); + + struct group *g = getgrnam("shadow"); + fchmod(fileno(out), 0640); + if (g) + fchown(fileno(out), 0, g->gr_gid); + if (fclose(out) != 0 || rename(tmp, "/etc/shadow") < 0) { + fprintf(stderr, "passwd: could not write /etc/shadow: %s\n", strerror(errno)); + unlink(tmp); + return 1; + } + printf("password updated\n"); + return 0; +} diff --git a/cmd/pcitest/main.c b/cmd/pcitest/main.c index 78a5387..aac0002 100644 --- a/cmd/pcitest/main.c +++ b/cmd/pcitest/main.c @@ -2,7 +2,7 @@ // // pcitest -- prove that a driver could live out here. // -// docs/plan/hybrid.md mechanisms 2 and 4, exercised end to end from a process: +// docs/architecture/devices.md mechanisms 2 and 4, exercised end to end from a process: // enumerate the bus, claim a device, map its registers, read them, and get // memory the device could be pointed at. // diff --git a/cmd/ping/main.c b/cmd/ping/main.c index 3d18ffa..4d74733 100644 --- a/cmd/ping/main.c +++ b/cmd/ping/main.c @@ -6,7 +6,7 @@ // worth recording because it is not "ours is better". toybox's ping reads its // replies with recvmsg(2) and asks for the TTL through an IP_RECVTTL control // message, and SchOS has neither: no recvmsg, no ancillary data. Both are owed -// -- recvmsg is item 1 of docs/plan/foundations.md, where it is wanted for +// -- recvmsg is item 1 of docs/architecture/kernel.md, where it is wanted for // SCM_RIGHTS -- and neither should be rushed to make one applet run. // // So this uses sendto/recvfrom, which the kernel does have, and proves the ICMP diff --git a/cmd/schmode/main.c b/cmd/schmode/main.c index b308ddc..508ae82 100644 --- a/cmd/schmode/main.c +++ b/cmd/schmode/main.c @@ -2,7 +2,7 @@ // // schmode -- change the display mode. // -// docs/plan/hybrid.md milestone H3. The interesting thing about this program +// docs/architecture/devices.md milestone H3. The interesting thing about this program // is how little it is: it opens a socket to `vgpud` and writes a line. No // syscall it makes has heard of a GPU, no kernel code runs that knows what a // scanout is, and if the driver on the other end gets the sequence wrong the diff --git a/cmd/socktest/main.c b/cmd/socktest/main.c index f026818..de5f777 100644 --- a/cmd/socktest/main.c +++ b/cmd/socktest/main.c @@ -447,7 +447,7 @@ strneq_(const char *a, const char *b, int n) // --------------------------------------------------------------------------- // 8. SCM_RIGHTS: a descriptor crossing between processes. // -// docs/plan/foundations.md item 1, docs/plan/hybrid.md mechanism 1. The thing +// docs/architecture/kernel.md item 1, docs/architecture/devices.md mechanism 1. The thing // being proved is not that recvmsg returns a number -- it is that the number // names the *same open file* the other process had, which is why every check // below moves data through the descriptor rather than inspecting it. @@ -763,7 +763,7 @@ test_fdpass_nodata(void) // --------------------------------------------------------------------------- // 15. A bound socket is a file. // -// docs/plan/foundations.md item 3, in its own words: bind() a socket, then +// docs/architecture/kernel.md item 3, in its own words: bind() a socket, then // stat() and `ls -l` the path and see an `s`, then unlink() it. // // The reason it matters is not tidiness. A Wayland client finds its compositor diff --git a/cmd/su/main.c b/cmd/su/main.c new file mode 100644 index 0000000..0e8ee26 --- /dev/null +++ b/cmd/su/main.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// su [-|-l] [-p] [-c COMMAND] [USER] +// +// Run a shell, or one command, as another user -- root by default. Setuid root, +// and a program of its own rather than a toybox applet: toybox is one binary +// under every name, and a setuid copy of it can be asked for any of its other +// applets by naming them, all as root. +// +// The rules are the ones su has always had, kept short: +// +// - root may become anyone, with no password; +// - anyone else is asked for the target's password, and gets three tries; +// - becoming root takes membership in `wheel` first, whatever the password +// is -- an account with no password is otherwise a door for every user. +// +// -, -l a login shell: a clean environment, the target's home, argv[0] "-sh" +// -p keep the environment (HOME, SHELL, USER and LOGNAME are still set +// unless the target is root) +// -c CMD run CMD with the shell's -c rather than an interactive shell + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int +in_wheel(uid_t uid, gid_t gid) +{ + struct group *g = getgrnam("wheel"); + struct passwd *pw = getpwuid(uid); + + if (!g) + return 0; + if (gid == g->gr_gid) + return 1; + for (char **m = g->gr_mem; pw && m && *m; m++) + if (strcmp(*m, pw->pw_name) == 0) + return 1; + return 0; +} + +// A line from the terminal with echo off. The password is read from the +// controlling terminal, not stdin, so a script cannot feed it. +static int +read_password(const char *prompt, char *buf, size_t n) +{ + int fd = open("/dev/tty", O_RDWR | O_NOCTTY); + struct termios t, old; + size_t i = 0; + char c; + + if (fd < 0) + return -1; + if (tcgetattr(fd, &old) == 0) { + t = old; + t.c_lflag &= ~(ECHO | ECHONL); + tcsetattr(fd, TCSAFLUSH, &t); + } + if (write(fd, prompt, strlen(prompt)) < 0) {} + while (read(fd, &c, 1) == 1 && c != '\n' && c != '\r') + if (i + 1 < n) + buf[i++] = c; + buf[i] = 0; + tcsetattr(fd, TCSAFLUSH, &old); + if (write(fd, "\n", 1) < 0) {} + close(fd); + return 0; +} + +// Does `pw` accept this password? An empty hash accepts anything, a locked one +// ("*", "!...") nothing. +static int +password_ok(const struct passwd *pw, const char *pass) +{ + const char *hash = pw->pw_passwd; + struct spwd *sp; + char *enc; + + if (hash[0] == 'x' && hash[1] == 0) { + sp = getspnam(pw->pw_name); + if (!sp) + return 0; + hash = sp->sp_pwdp; + } + if (hash[0] == 0) + return 1; + if (hash[0] == '*' || hash[0] == '!') + return 0; + enc = crypt(pass, hash); + return enc && strcmp(enc, hash) == 0; +} + +int +main(int argc, char **argv) +{ + const char *target = "root", *cmd = 0; + int login = 0, keep = 0, i; + struct passwd *pw, *me; + uid_t uid = getuid(); + char pass[256]; + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-") || !strcmp(argv[i], "-l")) + login = 1; + else if (!strcmp(argv[i], "-p")) + keep = 1; + else if (!strcmp(argv[i], "-c") && i + 1 < argc) + cmd = argv[++i]; + else if (argv[i][0] == '-' && argv[i][1]) { + fprintf(stderr, "usage: su [-|-l] [-p] [-c COMMAND] [USER]\n"); + return 2; + } else + target = argv[i]; + } + + if (geteuid() != 0) { + fprintf(stderr, "su: must be setuid root\n"); + return 1; + } + // getpwnam and getpwuid return the same static buffer, so the second call + // overwrites the first answer. Keep our own copy of the one that matters. + if (!(pw = getpwnam(target))) { + fprintf(stderr, "su: unknown user %s\n", target); + return 1; + } + { + static struct passwd copy; + copy = *pw; + copy.pw_name = strdup(pw->pw_name); + copy.pw_passwd = strdup(pw->pw_passwd); + copy.pw_dir = strdup(pw->pw_dir); + copy.pw_shell = strdup(pw->pw_shell); + pw = © + } + me = getpwuid(uid); + + if (uid != 0) { + int tries, ok = 0; + + if (pw->pw_uid == 0 && !in_wheel(uid, getgid())) { + fprintf(stderr, "su: %s is not in the wheel group\n", + me ? me->pw_name : "you"); + return 1; + } + for (tries = 0; tries < 3 && !ok; tries++) { + if (pw->pw_passwd[0] == 0 || + (pw->pw_passwd[0] == 'x' && getspnam(pw->pw_name) && + getspnam(pw->pw_name)->sp_pwdp[0] == 0)) { + ok = 1; + break; + } + if (read_password("Password: ", pass, sizeof pass) < 0) { + fprintf(stderr, "su: no terminal\n"); + return 1; + } + ok = password_ok(pw, pass); + memset(pass, 0, sizeof pass); + if (!ok) { + sleep(2); + fprintf(stderr, "su: incorrect password\n"); + } + } + if (!ok) + return 1; + } + + if (initgroups(pw->pw_name, pw->pw_gid) < 0 || + setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) { + fprintf(stderr, "su: cannot become %s: %s\n", pw->pw_name, strerror(errno)); + return 1; + } + + { + const char *shell = pw->pw_shell && *pw->pw_shell ? pw->pw_shell : "/bin/sh"; + char *sh_argv[5]; + int n = 0; + char arg0[256]; + + if (login) { + clearenv(); + setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1); + if (getenv("TERM") == 0) + setenv("TERM", "linux", 1); + } + if (login || !keep || pw->pw_uid != 0) { + setenv("HOME", pw->pw_dir, 1); + setenv("SHELL", shell, 1); + setenv("USER", pw->pw_name, 1); + setenv("LOGNAME", pw->pw_name, 1); + } + if (login && chdir(pw->pw_dir) < 0) + chdir("/"); + + if (login) { + const char *base = strrchr(shell, '/'); + snprintf(arg0, sizeof arg0, "-%s", base ? base + 1 : shell); + sh_argv[n++] = arg0; + } else { + const char *base = strrchr(shell, '/'); + sh_argv[n++] = (char *)(base ? base + 1 : shell); + } + if (cmd) { + sh_argv[n++] = "-c"; + sh_argv[n++] = (char *)cmd; + } + sh_argv[n] = 0; + execv(shell, sh_argv); + fprintf(stderr, "su: %s: %s\n", shell, strerror(errno)); + } + return 1; +} diff --git a/cmd/vgpud/main.c b/cmd/vgpud/main.c index 31a2f5e..99ee71a 100644 --- a/cmd/vgpud/main.c +++ b/cmd/vgpud/main.c @@ -2,7 +2,7 @@ // // vgpud -- the virtio-gpu driver, outside the kernel. // -// docs/plan/hybrid.md's first eviction. The same device the kernel drives in +// docs/architecture/devices.md's first eviction. The same device the kernel drives in // kernel/drivers/gpu/virtio_gpu/, driven instead by a process that asks the // kernel for four things and nothing else: // @@ -648,7 +648,7 @@ present(void) // // This used to be a test card: three colour bands, four gradients, a // checkerboard, and this instance's pid in binary along the bottom edge. That -// was scaffolding for hybrid.md's H2 and H3 -- a screenshot had to answer "did +// was scaffolding for the out-of-kernel driver work -- a screenshot had to answer "did // a *userspace process* put this here", and "did the driver come back" needed // a second run to look different from a first that never stopped. Both // milestones are closed, and what was left was a garish test pattern on a @@ -866,7 +866,7 @@ register_panic_fb(void) // --- the modeset -------------------------------------------------------------- // -// docs/plan/hybrid.md milestone H3: *"a modeset API, which is the thing an +// docs/architecture/devices.md milestone H3: *"a modeset API, which is the thing an // out-of-kernel GPU driver makes affordable and an in-kernel one never got."* // // The claim is worth testing rather than repeating, so here is the whole of @@ -944,7 +944,7 @@ set_mode(int w, int h) // // The name is a *name*, not a path. AF_UNIX sockets in this kernel live in a // table keyed by string and never touch the filesystem -- that is item 3 of -// docs/plan/foundations.md and it is not built. Nothing here depends on which +// docs/architecture/kernel.md and it is not built. Nothing here depends on which // it is, and the day it becomes a real path this keeps working. #define VGPUD_SOCKET "/run/vgpud" diff --git a/config/drivers.conf b/config/drivers.conf new file mode 100644 index 0000000..4446135 --- /dev/null +++ b/config/drivers.conf @@ -0,0 +1,45 @@ +# Which drivers are compiled into the kernel. +# +# y compiled in, and enabled: it is offered every device it can drive +# n not compiled in: the code is not linked and the kernel never knows it +# +# Everything is y unless it says otherwise. Change a line, or override one for +# a single build: +# +# make DRV_E1000=n DRV_NVME=n +# +# A changed value rebuilds only kernel/drivers/registry.o and the link. +# +# These are the drivers that can be left out cleanly: nothing else in the kernel +# calls them, they are only offered a device by the registry +# (kernel/drivers/registry.c). The others cannot be, because the rest of the +# kernel calls into them directly, and are not listed: +# +# vga_fb the boot framebuffer: the console and the crash screen draw on it +# xhci, usb core, usb hid / storage / hub +# the keyboard and the root disk can arrive over USB, bio.c and +# the hotplug thread call in, and so does the I2C-HID touchpad +# PS/2, IDE, the 16550 UART +# fixed-address legacy devices (kernel/dev/) +# +# Linux's own drivers, run in a process by ldlsd, are not here: they are files in +# the image (make ldls-r8169), not code in the kernel. + +# --- display -------------------------------------------------------------- +DRV_VGA_PCI = y # the stdvga/Bochs card (the framebuffer driver stays regardless) +DRV_VIRTIO_GPU = y # QEMU's virtio-gpu: mode setting and an explicit flush + +# --- network ---------------------------------------------------------------- +DRV_E1000 = y # Intel 8254x and 8257x +DRV_RTL8139 = y # Realtek 8139 (the one Realtek part QEMU emulates) +# The native RTL8168/8111 driver. Off by default because the laptop's NIC is +# driven by Linux's r8169 under LDLS (etc/schinit/ldls-r8169.job), and a driver +# here would claim the device first. Turn it on for a machine without that job. +DRV_R8169 = n + +# --- storage ---------------------------------------------------------------- +DRV_AHCI = y # SATA +DRV_NVME = y # NVMe SSDs + +# --- audio ------------------------------------------------------------------ +DRV_AC97 = y # Intel AC'97 (QEMU's and old boards') diff --git a/docs/README.md b/docs/README.md index 69dca39..411e591 100644 --- a/docs/README.md +++ b/docs/README.md @@ -72,29 +72,36 @@ good chance it is in there. | | | |---|---| | [README](architecture/README.md) | The system, layer by layer. Read first. | -| [boot.md](architecture/boot.md) | Firmware to the first userspace process. | -| [kernel.md](architecture/kernel.md) | SchIFT: memory, processes, scheduling, traps, signals, SMP. | +| [boot.md](architecture/boot.md) | Firmware to the first userspace process, and where the loader puts the kernel. | +| [kernel.md](architecture/kernel.md) | SchIFT: memory, processes and threads, traps, signals, SMP. | +| [scheduler.md](architecture/scheduler.md) | Fair share by virtual runtime, slices, and `nice`. | | [syscalls.md](architecture/syscalls.md) | The call table, and the Linux personality that lets real software run. | -| [filesystem.md](architecture/filesystem.md) | The VFS, the buffer cache, procfs, mounts. | +| [filesystem.md](architecture/filesystem.md) | The VFS, the buffer cache, procfs, sysfs, devtmpfs, mounts. | | [schfs.md](architecture/schfs.md) | SchFS: the copy-on-write root filesystem. | -| [devices.md](architecture/devices.md) | The device model, driver binding, the class vtables. | +| [scff.md](architecture/scff.md) | SCFF: the compressed image the live root ships as. | +| [fat.md](architecture/fat.md) | FAT12/16/32 with long names: USB sticks, SD cards, the ESP. | +| [devices.md](architecture/devices.md) | The device model, driver binding, the class vtables, and drivers that live in a process. | +| [ldls.md](architecture/ldls.md) | Running Linux's drivers: a `.ko` in a process, reaching hardware through `/dev/pci`. | | [usb.md](architecture/usb.md) | xHCI, the USB core, and the class drivers. | -| [graphics.md](architecture/graphics.md) | Framebuffer, text rendering, console, and the crash screen. | -| [terminals.md](architecture/terminals.md) | The six virtual terminals, and how one is switched to. | -| [networking.md](architecture/networking.md) | Drivers, lwIP, sockets — Unix and INET. | +| [graphics.md](architecture/graphics.md) | Framebuffer, `/dev/fb0`, text rendering, the console, PSOGM and GMMOS. | +| [terminals.md](architecture/terminals.md) | The twelve virtual terminals, ptys, and handing one to a display server. | +| [audio.md](architecture/audio.md) | `/dev/dsp`, AudioEngine, AC'97, and ALSA over OSS. | +| [networking.md](architecture/networking.md) | NIC drivers, lwIP, sockets — Unix, INET and netlink. | | [init.md](architecture/init.md) | `schinit`, PID 1 as a role, and the job manifests. | +| [packages.md](architecture/packages.md) | The apk root: Alpine's packages, and SchOS as one of them. | +| [desktop.md](architecture/desktop.md) | Xorg on the framebuffer, Window Maker, and what they stand on. | +| [debugging.md](architecture/debugging.md) | Symbols, a log that outlives the kernel, and asking the kernel a question. | ## plan/ — what SchOS will be | | | |---|---| -| [README](plan/README.md) | The three current decisions and the order they happen in. | -| [1.0.md](plan/1.0.md) | **The finish line, and the freeze.** What 1.0 contains, what is deferred, and the register of every half-built thing in the tree. | -| [foundations.md](plan/foundations.md) | The kernel work everything else is blocked on. | -| [wayland.md](plan/wayland.md) | The GUI stack, and what gets deleted to make room. | -| [hybrid.md](plan/hybrid.md) | Moving drivers out of the kernel. | +| [README](plan/README.md) | What is here, what used to be, and the order. | +| [1.0.md](plan/1.0.md) | **The finish line, and the freeze.** What 1.0 contains, where it stands, and the register of every half-built thing in the tree. | +| [roadmap.md](plan/roadmap.md) | Everything else scoped and still open. | +| [gpu.md](plan/gpu.md) | Linux's `amdgpu` through LDLS, and `/dev/dri` served by a process. | +| [finch.md](plan/finch.md) | The author's language for SchOS's own programs. | | [schapi.md](plan/schapi.md) | The long-term API layering, and the compatibility promise. | -| [roadmap.md](plan/roadmap.md) | Everything else still open. | ## guides/ — how to work on it diff --git a/docs/Website/README.md b/docs/Website/README.md index d9081c1..9c5bc89 100644 --- a/docs/Website/README.md +++ b/docs/Website/README.md @@ -30,7 +30,7 @@ is a token. A hex value in that file means the system is missing something, and the answer is to add it to Niveus rather than to work around it here. What that replaced was a 21 KB `style.css` that transcribed -[`include/gfx/theme.h`](../../include/theme.h) by hand — palette, type scale, +[`include/gfx/theme.h`](../../include/gfx/theme.h) by hand — palette, type scale, shadows, the lot. The intent was right and the mechanism was a copy: two files that had to be kept in step by remembering to. Now the whole identity is one declaration, in the `` of each page: @@ -70,22 +70,16 @@ rm -rf docs/Website/niveus && cp -r docs/niveus docs/Website/niveus ## The download page -`download.html` links to `releases/schos.iso`, a path relative to this -directory. **Nothing puts a file there** — the ISO is 62 MB and does not belong -in the repository — so the link is dead until a build is dropped next to the -site: +`download.html` links to the ISO attached to the release on the project's Forgejo +(`https://pandor.pl/git/nexoniarz/SchOS/releases/download/v0.1.0/schos-v0.1.0.iso`). The ISO is +hundreds of megabytes and does not belong in the repository, so nothing is stored under +`docs/Website/`; publishing a release is `tools/posix/release.sh vX.Y.Z`, and **when the tag +changes, change the link and the version beside it** (only the newest release keeps its ISO). -```sh -make iso -mkdir -p docs/Website/releases && cp schos.iso docs/Website/releases/ -``` - -The page is written so that this is the only thing that needs doing, and so -that nothing on it is a promise the tree cannot keep. In particular the support -table says **one real machine, under test** rather than listing hardware, -because one Chromebox is the entire bare-metal record and a compatibility list -would be fiction. When that changes, change the table; do not soften the -wording. +The page is written so that nothing on it is a promise the tree cannot keep. In +particular the support table lists the three machines SchOS has booted on and nothing else, +because that is the entire bare-metal record and a compatibility list would be fiction. When +that changes, change the table; do not soften the wording. The `dd` command is split across two lines on purpose. It was one long line first, and the card clipped it at `conv=` — a destructive command that can diff --git a/docs/Website/docs.js b/docs/Website/docs.js index dff0158..80b594f 100644 --- a/docs/Website/docs.js +++ b/docs/Website/docs.js @@ -30,14 +30,19 @@ ["architecture/syscalls.md", "Syscalls"], ["architecture/filesystem.md", "The VFS"], ["architecture/schfs.md", "SchFS"], + ["architecture/scff.md", "SCFF"], ["architecture/fat.md", "FAT"], ["architecture/terminals.md", "Virtual terminals"], ["architecture/devices.md", "Device model"], ["architecture/usb.md", "USB"], + ["architecture/udev.md", "udev"], + ["architecture/users.md", "Users and permissions"], ["architecture/graphics.md", "Graphics"], ["architecture/audio.md", "Audio"], ["architecture/networking.md", "Networking"], ["architecture/init.md", "init"], + ["architecture/packages.md", "Packages (apk)"], + ["architecture/desktop.md", "The desktop (Xorg)"], ["architecture/ldls.md", "Linux drivers (LDLS)"], ["architecture/debugging.md", "Debugging"] ]}, @@ -51,9 +56,8 @@ { group: "Plan", items: [ ["plan/README.md", "What plan/ is for"], ["plan/roadmap.md", "Roadmap"], - ["plan/foundations.md", "Foundations"], - ["plan/wayland.md", "Wayland"], - ["plan/hybrid.md", "Hybrid kernel"], + ["plan/gpu.md", "The GPU"], + ["plan/finch.md", "Finch"], ["plan/schapi.md", "SchAPI"] ]}, { group: "History", items: [ diff --git a/docs/Website/download.html b/docs/Website/download.html index 784f639..313db62 100644 --- a/docs/Website/download.html +++ b/docs/Website/download.html @@ -66,33 +66,34 @@

SchOS ships as one bootable ISO. It boots on UEFI, runs entirely from RAM, and touches nothing on the machine unless you tell it to — there is - no installer yet, and that is the point of trying it this way. + no installer yet, and that is the point of trying it this way. It comes up with + Xorg, Window Maker and Alpine's package manager.

- schos.iso · about 62 MB · UEFI x86-64 · + schos.iso · v0.1.0 · a few hundred MB · UEFI x86-64 · nothing is written to your disks

- +

Before you do

-

This has booted on exactly one real machine

+

This has booted on three real machines

Everything below QEMU is honest reporting rather than a compatibility list. SchOS drives real hardware, and it has been run on a very small - number of it. + number of it — from a USB stick, each time.
@@ -103,8 +104,16 @@ boots - Asus Chromebox Guado CN62 - under test + AMD B550 desktop Ryzen 5 4600G + boots + + + ASUS TUF A15 laptop Ryzen, RTL8168H + boots, network under test + + + HP all-in-one Ivy Bridge, 2012 + boots Anything else @@ -231,11 +240,10 @@ Step three

A shell

- schinit starts one on the console and a - second on tty2. - mksh is /bin/sh, - with zsh beside it and the real toybox - userland behind both. + schinit starts one on the console and one on + each of the next five terminals. zsh is the login shell, + /bin/sh is busybox's, and mksh is + the static one recovery uses. Type startx for the desktop.

@@ -243,9 +251,9 @@ 4
Step four -

Six terminals

+

Twelve terminals

- AltGr+F1…F6 + Ctrl+Alt+F1…F12 switches between them — the one chord a Linux host does not take for itself — or chvt N from a shell.

diff --git a/docs/Website/index.html b/docs/Website/index.html index 09feebf..4d5e6f9 100644 --- a/docs/Website/index.html +++ b/docs/Website/index.html @@ -347,7 +347,7 @@
Six of them, each with its own cell grid, scrollback and foreground - job — switched with AltGr+F1…F6, + job — switched with Ctrl+Alt+F1…F12, which is the one chord a Linux host does not take for itself. The console draws to whichever framebuffer is present, from the kernel's first line.
@@ -398,13 +398,13 @@
-

Schcom

- in progress +

The desktop

+ runs
- The compositor that will own the display. v0 runs, and descriptor - passing — the one thing a Wayland client cannot do without — now - exists. What a buffer still needs is a shareable memory object. + Xorg on the framebuffer with Window Maker, xterm and a few X clients; + NetSurf for the web. No acceleration yet — the GPU is a plan, and the plan is Linux's own + amdgpu through LDLS.
@@ -415,7 +415,7 @@
The native programming interface — specified, not built, and deliberately - so. It should be designed from the compositor's experience rather + so. It should be designed from the desktop's experience rather than before it.
@@ -549,19 +549,19 @@
lines of code in kernel/ and include/
-
45,207
+
51,893
lines of comments alongside them
-
16,111
+
17,213
first-party lines in all
-
61,367
+
72,766
syscalls, Linux numbering
-
184
+
193
drivers in the kernel
@@ -569,15 +569,15 @@
Alpine packages in the live root
-
88
+
179
commits
-
253
+
292
days since the first one
-
76
+
78
patches carried against fastfetch
diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 7e46a09..6356267 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -16,11 +16,10 @@ its distribution. | Kernel | **SchIFT** | Multi-core x86-64 kernel with a modular VFS | | Filesystem | **SchFS** | Transactional, copy-on-write, the root filesystem | | API | **SchAPI** | The native programming interface — *designed, not built* | -| Compositor | **Schcom** | Owns the display; v0 runs | +| Packages | **apk** | Alpine's package manager manages the root; SchOS itself is the package `schos-base` | -Six virtual terminals sit under all of it -- `/dev/tty1`..`tty6`, AltGr+F1..F6 -or `chvt N` -- -each with its own screen, line discipline and foreground process group. See +Twelve virtual terminals sit under all of it -- `/dev/tty1`..`tty12`, Ctrl+Alt+F1..F12 +or `chvt N` -- each with its own screen, line discipline and foreground process group. See [terminals.md](terminals.md). --- @@ -29,9 +28,11 @@ each with its own screen, line discipline and foreground process group. See ``` ┌─────────────────────────────────────────────────┐ - │ toybox kilo mksh zsh musl first-party cmd/ │ userland + │ Xorg + Window Maker · Alpine's packages (apk) │ desktop, userland + │ toybox mksh musl first-party cmd/ │ ├─────────────────────────────────────────────────┤ - │ vgpud — a driver that is an ordinary process │ + │ vgpud, ldlsd — drivers that are ordinary │ + │ processes (and Linux's r8169 inside ldlsd) │ ├─────────────────────────────────────────────────┤ │ schinit — PID 1, job manifests, supervision │ ├─────────────────────────────────────────────────┤ @@ -45,7 +46,8 @@ each with its own screen, line discipline and foreground process group. See ├─────────────────────────────────────────────────┤ │ /dev/pci — MMIO, DMA and IRQs to a process │ ← the eviction seam ├─────────────────────────────────────────────────┤ - │ drivers: xHCI e1000 virtio-gpu VBE IDE PS/2│ + │ drivers: xHCI AHCI NVMe e1000 8139 r8169 AC97 │ + │ virtio-gpu VBE IDE PS/2 I2C-HID │ ├─────────────────────────────────────────────────┤ │ scheduler │ paging │ traps │ IRQ │ SMP │ DMA │ SchIFT core └─────────────────────────────────────────────────┘ @@ -59,7 +61,8 @@ never names its consumer and a consumer never names a driver. And at **`/dev/pci`**, a driver stops having to be in the kernel at all: a process is handed a device's registers, DMA memory and interrupts, and the in-kernel driver that held it releases it cleanly. `vgpud` is the virtio-gpu driver living out -there today. See [../plan/hybrid.md](../plan/hybrid.md). +there today, and `ldlsd` runs Linux's own drivers the same way. See +[devices.md](devices.md) and [ldls.md](ldls.md). --- @@ -91,9 +94,14 @@ Read in this order if you are new: | [fat.md](fat.md) | FAT12/16/32 with long names: USB sticks, SD cards, the ESP. | | [devices.md](devices.md) | Buses, classes, binding, and the vtables consumers talk to. | | [usb.md](usb.md) | xHCI, the USB core, and the three class drivers. | -| [graphics.md](graphics.md) | Framebuffer, text rendering, the console, and PSOGM. | -| [terminals.md](terminals.md) | The six virtual terminals, and the chord that switches them. | -| [networking.md](networking.md) | NIC drivers, lwIP, and both socket families. | +| [users.md](users.md) | Accounts, login, and the permissions the kernel enforces. | +| [udev.md](udev.md) | eudev on SchOS: the device table, the `/sys` tree, uevents, datagram sockets. | +| [graphics.md](graphics.md) | Framebuffer, `/dev/fb0`, text rendering, the console, PSOGM and GMMOS. | +| [desktop.md](desktop.md) | Xorg on the framebuffer, Window Maker, and what they stand on. | +| [packages.md](packages.md) | The apk root: Alpine's packages, and SchOS as one of them. | +| [scff.md](scff.md) | The compressed image the live root ships as. | +| [terminals.md](terminals.md) | The twelve virtual terminals, ptys, and handing one to a display server. | +| [networking.md](networking.md) | NIC drivers, lwIP, and the socket families. | | [audio.md](audio.md) | `/dev/dsp`, AudioEngine, and the AC'97 driver under it. | | [init.md](init.md) | `schinit`, PID 1 as a role, and the job manifests. | | [debugging.md](debugging.md) | Symbols, a log that outlives the kernel, and asking the kernel a question. | diff --git a/docs/architecture/audio.md b/docs/architecture/audio.md index 5f49d12..27f11fc 100644 --- a/docs/architecture/audio.md +++ b/docs/architecture/audio.md @@ -40,7 +40,7 @@ until the speaker has caught up*. **It is one stream.** Mixing several programs into one device is a real feature with a real design behind it, and building it before anything had played a single sound would be exactly the mistake [../plan/1.0.md](../plan/1.0.md) -keeps a register for: work that exists and is connected to nothing. The second +keeps a register against: work that exists and is connected to nothing. The second program that wants to play at the same time is what should cause it to be written. @@ -84,6 +84,25 @@ Both register files are **I/O ports**, not MMIO — which is why nothing in the driver maps anything. `bar[n].is_io` is what says so, and a driver that `ioremap`ed these would be mapping a port number as though it were an address. +### Through ALSA, for programs that expect it + +SDL 3 has no OSS backend and SchOS has no ALSA *kernel* side, so sound from SDL games +and anything else that speaks ALSA goes **SDL → alsa-lib → alsa-plugins' OSS plugin → +`/dev/dsp`**. `alsa-lib` and `alsa-plugins` are in the Alpine `world`, and +`/etc/asound.conf` (from `schos-base`) makes the default device a `plug` onto the OSS +device at 16-bit stereo 48 kHz, which is what every SchOS audio driver plays. + +Running that path found two bugs underneath it: + +- **AC'97:** resetting the PCM-out box also clears the descriptor-list address and nothing + set it again, so every stream after the first stop played a "list" at physical address 0 + — silence — and ALSA resets before each stream. If the engine halts at the end of the + queue, the run bit is set again. +- **`/dev/dsp` was missing the ioctls the plugin's ring-and-pointer model uses:** + `SETFRAGMENT`, `GETFMTS`, `GETCAPS`, `SETTRIGGER`, `GETOPTR`, `GETODELAY`, and `poll()`, + writable once a whole fragment fits (ALSA spun at 76% CPU otherwise). `speaker-test` now + plays in real time at 0% CPU, verified in a recording of QEMU's AC'97. + ## How it is tested QEMU's `wav` audio backend writes what the guest plays to a file, so the test diff --git a/docs/architecture/boot.md b/docs/architecture/boot.md index 965e0e3..978a169 100644 --- a/docs/architecture/boot.md +++ b/docs/architecture/boot.md @@ -44,7 +44,7 @@ because there are none.** Everything the jump needs is computed before. ### The hand-off block One page of low memory at `0x30000`, defined once in -[`include/arch/schboot.h`](../../include/schboot.h) and included by both the loader +[`include/arch/schboot.h`](../../include/arch/schboot.h) and included by both the loader and `entry.S` so the two cannot disagree. | Offset | What | @@ -131,6 +131,42 @@ when the early console is set up. So a low framebuffer is attached exactly as before, and a high one is attached a few dozen instructions later, immediately after `kvmalloc()` — still thousands of lines before anything else happens. +### Where the kernel goes, and how it gets there + +The kernel is linked at physical **16 MB** (`KERNPHYS` in `memlayout.h`, repeated in +`kernel.ld`, which must agree) and is about 34 MB with the ramdisk inside it. From the +old 1 MB it ran across 8 MB, where OVMF keeps ACPI NVS memory that outlives the firmware, +so it was overwritten on every boot, after `ExitBootServices`. Firmware leaves its +leftovers low; above 16 MB is where a machine is most reliably empty. (The 15 MB below +the kernel is not given to the page allocator yet.) + +**Every free page of that range is claimed first.** A segment lying wholly on claimed +pages is read straight home; one that is not is read into a buffer elsewhere and +**copied home after `ExitBootServices`**, when boot-services memory is ours. An HP +all-in-one from 2012 used to freeze at the start of the loading bar because the loader +said "could not reserve" and read the kernel there anyway, over the firmware's own memory +with the firmware still running on it. Memory under the kernel that outlives the firmware +(ACPI NVS, runtime services) is named, and so is the loader or its stack sitting in the +range. Segments are read a megabyte at a time, so the bar moves through the ramdisk and +shows where a machine stopped. + +The copy and the jump themselves run from **one page below the kernel's lowest address**, +allocated as loader code before the firmware exits: a short position-independent routine, +the copy list and a stack, switching to the loader's page tables and its own stack before +it copies anything. Doing the copy from code the firmware had placed wherever it liked put +some of it inside the kernel's range on that machine: a triple fault and a reset. The +routine is `sysv_abi`, because UEFI code is the Microsoft convention and the first try loaded +the framebuffer address into `rsp`. + +**No 1 GB pages, no problem.** Intel's desktop CPUs before Haswell have none, and there the +PS bit in a PDPT entry is reserved: the first access after the switch is a page fault that +cannot be handled either. Both the loader and the kernel check CPUID `0x80000001` EDX bit 26; +without it the low 4 GB is four page directories of 2 MB pages (placed below the kernel, with +the staged segments kept below 4 GB), and the kernel gives each gigabyte of its direct map +and of `ioremap()` a page directory of its own. Neither KVM nor QEMU enforces the reserved +bit, so the fault could not be reproduced here; with the fallback forced on, the kernel +boots at 2 and 8 GB and both CPUs start. + ### The loader jumped out from under itself A machine with a lot of RAM froze at "Starting the kernel" — the splash still @@ -294,9 +330,64 @@ Dropping back to protected mode to re-enter through the 32-bit multiboot path is a mode switch whose failure mode is a triple fault with no console. The 32-bit entry is still present in `entry.S` and now has no caller. +### The boot manager + +F8 (or `o` on a serial line) opens the boot manager. It used to be a text list +on a dark plate; it is now drawn in the Niveus language the site is drawn in +(`docs/Website/niveus`): a window with a title bar, keys with a lit upper edge +and a shadow beneath, panels cut into the surface as wells, a graphite ground +that darkens towards the bottom, and the mark above the list. + +What makes that possible in a loader is that nothing in it is large: + +- **Shapes** are rounded rectangles with a vertical gradient. Corners are + anti-aliased by supersampling only the corner pixels, against a colour the + caller names. Nothing reads the framebuffer back, because that memory is + uncached on real hardware and a read per pixel is the difference between a + menu that appears and one that crawls. (The one exception is the pointer, + which saves sixteen by nineteen pixels.) +- **Type** is Instrument Sans, the face Niveus ships. A TrueType rasteriser + does not belong in a loader, so `tools/python/mknvfont.py` renders it once + into `include/gfx/nvfont.h` as 8-bit coverage bitmaps with fractional + advances, at the size for ordinary screens and at twice that for screens 1400 + pixels tall or more. Rasterising at the real size is what keeps it crisp; + doubling pixels does not. +- **Layout** is computed from the item list, not drawn from fixed positions: a + list taller than the screen drops the mark, then scrolls with a bar. + +An entry is a label, a newline and a line of detail. The entries are: + +| Entry | Does | +|---|---| +| Boot SchOS / (verbose) | the two ways of starting this system | +| other systems | one entry per loader found at a well-known path on any volume: Windows Boot Manager, Ubuntu, Debian, Fedora, Arch, openSUSE, systemd-boot, rEFInd, and any other `\EFI\BOOT\BOOTX64.EFI` that is not this one. Chosen, they are started with the firmware's own `LoadImage`/`StartImage`, and if the other loader returns, the menu is still here | +| Resolution | the list of modes the firmware offers, or a typed size, with the "keep this?" confirmation | +| System Information | firmware, UEFI revision, processor, memory, display, ACPI | +| Firmware Setup | sets `OsIndications` and restarts into the firmware's own settings; on firmware that does not support it, says so instead of restarting for nothing | +| Restart, Shut Down | `ResetSystem` | +| Remote debugging | `make DEBUG=1` builds only | + +Keyboard, mouse, touchpad and touchscreen all drive it: both UEFI pointer +protocols are polled (a mouse reports movement, a tablet reports position), +hover moves the highlight, a click activates, and the wheel scrolls. All input +goes through one function, `wait_event()`, so every screen behaves the same way. +The cursor stays hidden until the pointer has actually done something, because +the firmware's console layer installs a pointer protocol whether or not a mouse +is attached. USB devices appear some time after their controller is connected +and the menu is already up by then, so the loader connects every controller when +the menu opens and looks for pointers again twice a second for ten seconds. + +The "keep this resolution?" confirmation counts ten seconds down once, however +many times the pointer moves over the button: a person with a garbled screen +waving a mouse around must not be able to keep postponing the revert. + +**Not verified:** hover and click were exercised with synthetic events. Under +QEMU and OVMF a USB mouse or tablet never delivered an event to the loader, so +the pointer path has not been seen working on a device -- real hardware first. + ### The menu is on the serial line too -The three F8 screens paint into the framebuffer, which is the right place for +The screens paint into the framebuffer, which is the right place for them on a machine with a screen and nowhere at all on one driven over a serial cable — which is how this loader is usually driven. `f8_window()` already went out of its way to support that, accepting `o` as well as F8 because *"a serial @@ -315,7 +406,7 @@ its text across the menu that was just painted there. Two details that are not cosmetic: - **The item number is shown only where it is a key you can press.** The boot - menu takes 1–5. The resolution list takes only W/S and Enter and is longer + menu takes 1–9. The resolution list takes only W/S and Enter and is longer than nine entries, so numbering it printed `:`, `;`, `<` past the ninth — a character nobody can type, offering to select something. - **Q goes back as well as Esc.** On a serial line a lone `ESC` byte is @@ -610,7 +701,9 @@ in the one process that runs first. ## What it does not do -- **No BIOS path.** A machine without UEFI does not boot SchOS. +- **No BIOS path.** A machine without UEFI does not boot SchOS. The ISO carries a real EFI + system partition (MBR type `0xEF`, FAT16 by cluster count, the type old firmware reads best), + so a stick written with `dd` or `write.sh` boots on strict firmware as well as on lenient. - **The quiet boot hides a slow one.** With nothing printing between the loader and the greeting, a boot that stalls for ten seconds looks the same as one that does not. F8 is the answer and it has to be pressed *before* the @@ -621,9 +714,11 @@ in the one process that runs first. with its partitions, but nothing mounts it. See [usb.md](usb.md). - **NVMe and AHCI poll for completion**, one command at a time per disk. That is enough to boot and run from them, not to be fast. -- **`make ramroot` is the only way to boot with no data disk.** It links a - filesystem image into the kernel binary, which costs boot time and carries a - licensing constraint — see [../licensing.md](../licensing.md) §4. +- **The live ISO is a ramdisk root.** `make iso` builds the root as a SchFS image, + compresses it ([scff.md](scff.md)) and links it into the kernel; nothing is written to + the machine's disks, and everything written to the root is gone at reboot. There is + no installer, and no kernel that finds a root on a disk by SchFS's GPT type as its + *default*. `make ramroot` is the same without compression. - **The 32-bit multiboot entry is dead code.** It is left in place deliberately: deleting kernel entry code is its own piece of work with its own way of failing silently. diff --git a/docs/architecture/debugging.md b/docs/architecture/debugging.md index 809af56..135e2ff 100644 --- a/docs/architecture/debugging.md +++ b/docs/architecture/debugging.md @@ -29,10 +29,10 @@ asked for one. That is the right default and it has one bad consequence — to enable it.** A laptop with no serial port that stops before it can draw has nowhere to say why, and `schinitd enable` needs a prompt that does not exist. -So a build made with `make DEBUG=1` has a sixth entry in the F8 menu: +So a build made with `make DEBUG=1` has one more entry at the end of the F8 menu: ``` - > 6. Remote debugging: off + > Remote debugging: off ``` Picking it toggles the entry, booting carries the choice through @@ -179,6 +179,10 @@ part of the boot is blank for the part that is hardest to debug any other way. --- +A panic is not the end of the log. GMMOS ([graphics.md](graphics.md)) keeps running +under the report, can show this log on screen or as QR codes a phone can read, and +has a small terminal for looking around a machine whose kernel is dead. + ## Asking a question `/dev/dbg` is a conversation: write a line, read the answer. @@ -493,10 +497,16 @@ something that reports nonsense. - **One conversation at a time.** The answer buffer is global, so two programs asking at once get one answer each in some order — never half of the other's, but not two independent sessions either. -- **No userspace symbolization.** The kernel names kernel symbols. A fault in a - process still reports a bare address, and resolving it is a host-side job. -- **The stack walk stops where the frame pointer stops.** `getcallerpcs()` walks - `rbp` chains, so a trap frame in the middle of a chain ends the walk. +- **No userspace symbolization.** The kernel names kernel symbols. A process + killed by a fault is told which *file and offset* it died in (from + `/proc//maps`' records), which is enough to resolve with the file's own + symbols on the host; a bare address is all there is for anonymous memory. +- **The stack walk stops where the frame pointer stops, and it will not follow a + bad one.** `getstackpcs()` walks `rbp` chains, and each link must go up the same + stack by less than `KSTACKSIZE`: a chain can end at the syscall entry with a *user* + `rbp` that a program uses as an ordinary register (`-1024` in one game), and + dereferencing that under the syscall tracer once panicked the kernel inside + `acquire()`. A trap frame in the middle of a chain ends the walk. - **`sysrq g` costs you log history.** Its output goes through `cprintf`, so sixteen kilobytes of views push the same amount out of a 128 KB ring. - **No tracing.** No static tracepoints, no event ring, no counters. "What diff --git a/docs/architecture/desktop.md b/docs/architecture/desktop.md new file mode 100644 index 0000000..bba2848 --- /dev/null +++ b/docs/architecture/desktop.md @@ -0,0 +1,79 @@ +# The desktop: Xorg on the framebuffer + +The desktop is X11, on the framebuffer: Alpine's `xorg-server` with the `fbdev` video +driver and the `evdev` input driver, Window Maker as the window manager, and `xterm`. +`startx` runs `wmaker`. Nothing here is written by SchOS except the kernel interfaces it +stands on and four small files in `tools/posix/mkaroot.sh`. + +**Xorg is the desktop for now, not for ever.** SchOS will not write its own compositor (the one it had, Schcom, was deleted). +What comes next is whichever of XLibre (the Xorg fork), Xorg or a Wayland compositor from Alpine suits the machine; the groundwork +Wayland needs — udev, `/sys/devices`, libinput, a DRM device — is in [../plan/roadmap.md](../plan/roadmap.md) §5. + +## Why X and not Wayland + +Every Wayland compositor worth running — labwc, and everything built on wlroots — needs +DRM/KMS, libinput, udev and libseat. DRM/KMS is the largest of them by far: a kernel graphics +subsystem, and in practice a great deal taken from Linux. Xorg runs on a plain framebuffer +through `fbdev`, which SchOS already had. That is the shorter road to a desktop by a long way, +and it brings the window managers with it. udev, libinput and libseat are now in the image +([udev.md](udev.md)); DRM/KMS is the part still missing, and it is the GPU plan +([../plan/gpu.md](../plan/gpu.md)). + +## What it stands on + +| | | +|---|---| +| **fbdev** | `/dev/fb0` speaks Linux's fbdev ([graphics.md](graphics.md#devfb0-and-how-xorg-draws)) | +| **evdev** | `/dev/input/event0` (every keyboard) and `event1` (every mouse and touchpad) with Linux's queries, LED writes and key/button/relative-axis bits | +| **VTs** | `/dev/tty0`, `VT_SETMODE(VT_PROCESS)`, `KD_GRAPHICS`, the release/acquire handshake ([terminals.md](terminals.md)) | +| **dlopen** | Xorg loads its drivers as modules, so `exec` does dynamic linking ([kernel.md](kernel.md)) | +| **sysfs** | `/sys/bus/pci/devices/*/{boot_vga,enable,graphics/fb0}`, which libpciaccess reads | +| **ptys** | `xterm` runs its shell on a pseudo-terminal | +| **Unix sockets** | the X11 socket, abstract names, `SO_PEERCRED`, `flock` | +| **headers** | `` in the build sysroot, as `ports/linux-headers` | + +`cmd/xready` checks every one of the kernel calls Xorg makes before it draws anything — the +evdev queries, the fbdev calls, the console calls, and the `/sys` files — one at a time, on a +terminal of its own, and puts everything back. The calls were written against what the +programs are documented to do; each step past it found one that was not, which is what running +the real program is for. + +## The four files + +- **`/root/.xserverrc`** starts `X` on the VT `startx` was started from (startx works that + out only when `uname -s` says Linux, and SchOS's says SchIFT). It used to add **`-dumbSched`**, + Xorg's switch for reading input on the main loop, because Xorg normally reads input on a + thread of its own and, when SchOS had no threads, the server crashed the first time a client + disconnected, in `pthread_join` on a thread that was never made. Threads exist now and the switch + is gone: under QEMU, X starts with Window Maker, `xclock` and `xeyes` connect and disconnect, an + `xterm` runs and exits, and the server stays up. +- **`/root/.xinitrc`** is `exec wmaker`. Without it xinit falls back to twm and an xterm, which + are not in the image. +- **`/etc/X11/xorg.conf.d/10-schos.conf`** holds what the defaults do not give: Ctrl+Alt+Backspace + enabled, the keyboard options, and the framebuffer driver. Input devices are *found*, not + named: Xorg asks libudev, which reads what `udevd` wrote to `/run/udev` ([udev.md](udev.md)). +- **`/etc/asound.conf`** is for sound, not X ([audio.md](audio.md)). + +## Using it + +Twelve virtual terminals, switched with Ctrl+Alt+F1–F12 — on the console and under X +alike, because X owns the keyboard while it runs and switches only with that chord. `startx` on +a terminal runs the desktop there; Ctrl+Alt+Backspace ends the session and gives the terminal +back. Window Maker, `xterm`, `xeyes`, `xclock`, `xcalc`, DejaVu fonts; SDL 2 games +(`lbreakouthd`) and NetSurf, a GTK 3 browser, run in it. + +Touchpads that sit on an I2C bus (most laptops) are driven by `kernel/acpi/i2c_hid.c`, found +through ACPI, and reach X as a mouse: pointer, click and tap. + +## What it does not do + +- **No acceleration.** Software rendering to the framebuffer: no DRM, no glamor, no DRI, no + OpenGL beyond what software gives. GPU drivers are a plan ([../plan/gpu.md](../plan/gpu.md)). +- **udev covers the kernel's own devices only.** Input, terminals, memory devices, the + framebuffer and disks are announced; PCI and USB devices are in `/sys/bus/*/devices` but are + not yet in the `/sys/devices` tree or on the uevent socket ([udev.md](udev.md)). +- **Touchpads are mice**: no two-finger scroll, no gestures, no palm rejection. +- **No resolution change from inside X.** The mode is the console's; `schmode` changes the + `vgpud` head only, and the loader's menu is where a real panel's mode is chosen. +- **Runs as an ordinary user** where there is one: `startx` works by group permission alone + ([users.md](users.md)); the image has only root; X itself is still a single seat with no logind or seatd. diff --git a/docs/architecture/devices.md b/docs/architecture/devices.md index 63ad2fa..ae85ba7 100644 --- a/docs/architecture/devices.md +++ b/docs/architecture/devices.md @@ -4,7 +4,61 @@ How hardware gets found, claimed and used, and what the rest of the kernel sees of it. Writing a driver is [../guides/adding-a-driver.md](../guides/adding-a-driver.md). -Moving drivers out of the kernel entirely is [../plan/hybrid.md](../plan/hybrid.md). +Drivers that live in a process rather than in the kernel are described below +(*Drivers that are not in the kernel*), and the foreign ones in [ldls.md](ldls.md). + +--- + +## Which drivers are compiled in + +`config/drivers.conf` lists the drivers that can be left out of a kernel, `y` (compiled in and offered every device it can +drive) or `n` (not linked at all), all `y` unless a line says otherwise — the one exception is the native `r8169`, `n` +because Linux's runs in its place under LDLS. Override one for a build with `make DRV_E1000=n DRV_NVME=n`. A changed +choice rebuilds only `registry.o` and the link. The list is short on purpose: a driver is optional only if nothing else in +the kernel calls it (display: `vga_pci`, `virtio_gpu`; network: `e1000`, `rtl8139`, `r8169`; storage: `ahci`, `nvme`; +audio: `ac97`). `vga_fb`, the USB stack and the legacy PS/2, IDE and UART devices are always there, because the console, the +keyboard and the root disk can depend on them. A kernel with every optional driver off builds and links; whether such a +kernel is useful on any particular machine is the person who turned them off to say. + +## What stays in the kernel, and what may leave + +"Hybrid" is used to mean everything from "a monolith with a nice device model" to +"a microkernel that gave up", so the line is worth drawing exactly. + +**Stays in, permanently:** scheduling, address spaces, the trap and syscall path; +IPC and shared memory; the VFS and the **root** filesystem; IRQ demultiplexing; and +the console and PSOGM. That last one is not a size compromise but an invariant: the +crash screen must work when everything else is dead, so it cannot depend on anything +that could *be* dead. A crash screen served by a userspace process is absent in +exactly the case it exists for. + +**May leave, and should:** GPU drivers, non-root filesystems, NICs after the first, +and anything written by someone else. + +> **Anything needed to reach a shell stays in. Anything discovered at runtime, +> replaceable, or plausibly written by someone else goes out.** +> +> **The kernel keeps a minimal driver for everything on the path to a shell, and +> userspace owns the good one.** Break that and the compositor cannot start because +> the display driver is a process that needs a compositor. + +### USB stays in the kernel, and why + +It was on the list to leave, and the premise was wrong. USB is not a peripheral; it +is the bus peripherals arrive on, structurally the same kind of thing as PCI, and +nobody has proposed evicting PCI. The keyboard and the root disk that arrive over it +are on the path to a shell, so the stack that finds them is part of that path. Even +with a minimal in-kernel fallback written, the good driver and the minimal one would +be the same driver, and an eviction that leaves an identical copy behind has moved +nothing. A second, quieter difference: `/dev/pci` hands over one PCI *function*, one +owner and nothing shared, while a USB device sits behind a controller other devices +also sit behind — "the xHCI controller" includes the keyboard. What the eviction was +for, third-party drivers living outside the kernel, survives and belongs to +[ldls.md](ldls.md). + +The root filesystem stays in the kernel for the same reason (SchFS cannot be +restarted, and a bug in it is fatal), and so does the first NIC, because the network +thread and lwIP are in the kernel. --- @@ -148,7 +202,7 @@ bitmap, and loses them when it releases or exits. Granting it is not optional a driver's `outl` is an instruction compiled into it, not a call anything can intercept — and granting it *coarsely* is not acceptable, because `IOPL=3` is one flag that hands over the interrupt controller and the disk along with the -NIC. See [../../include/ioperm.h](../../include/ioperm.h). +NIC. See [../../include/arch/ioperm.h](../../include/arch/ioperm.h). ### Handing a device over @@ -184,8 +238,47 @@ client — and that is worth a sentence here because it is the thing the device model makes possible rather than the thing it does. A modeset is five commands to the device. In the kernel those five commands are also a reason to be nervous, because a wrong one takes the machine; in a process the worst case is -that `schinit` starts another one. See -[../plan/hybrid.md](../plan/hybrid.md), "A modeset, from a process". +that `schinit` starts another one. That is why the in-kernel driver never grew +one — not difficulty, exposure. + +``` +# schmode +1280x800 current +1280x800 maximum, framebuffer 4000 KB +# schmode 1024 768 +1024x768 ok +``` + +The scanout really resizes, and nothing in the kernel changed for it: `schmode` +writes a line to a socket and `vgpud` sends five commands to the device. The +framebuffer is allocated **once, at the largest mode**, and every smaller mode +reuses it, because `PCIIOC_DMA` has no free — a driver's blocks come back when it +exits and not before — so a modeset that allocated would spend the arena and the +failure would look like a GPU bug rather than like counting. A larger mode is +refused, and says why. The control socket speaks lines of text and has no buffer +submission, damage or vblank, which a compositor would need and nothing has asked +for. + +### A panic has to reach the screen + +Handing a device away costs something that was not on the list. The kernel panics, +paints the report into the console framebuffer, and halts — and the display it gave +to a process keeps showing whatever that process last put there. On a machine with +two heads, one says the kernel is dead and the other shows a shell prompt. There is +no scheduling after a panic, so the process holding the device is never going to run +again, and the kernel let go of the device deliberately. + +So the driver says in advance where it is scanning out, with `PCIIOC_PANICFB` +(`include/dev/pciuser.h`), and the kernel: (1) paints the report into that memory; +(2) asks the *kernel* driver the device was taken from to put it on screen — +`panic_present` in `struct pci_driver`; (3) which resets the device and rebuilds one +queue from **static** memory — no allocation, no locks, no sleeping, because a +corrupted heap is one of the ways a machine gets here. The registration is checked +like the promise it is: the range must lie inside a block that process got from +`PCIIOC_DMA` on that device, or it would be a way to nominate any physical address +to be overwritten at panic. It costs 20 KB of static memory in the virtio-gpu driver, +paid whether or not the machine ever panics; allocating it on demand would be +cheaper and would not work. ### Taking it back @@ -222,8 +315,9 @@ enforces it. Coming back is a job, not a kernel feature. `etc/schinit/vgpud.job` carries `KeepAlive = yes`, so killing the driver is the entire test: `schinitd` starts another one, it claims the device, and the pid of whichever process drew the -frame is legible in the frame itself. See -[../plan/hybrid.md](../plan/hybrid.md), "The driver came back". +frame is legible in the frame itself: read off the scanout across four generations of +the driver it said 4, 8, 10, 12, and the DMA arena handed back the same addresses +every time. ### What this is not @@ -231,8 +325,7 @@ frame is legible in the frame itself. See buffer can point the device at any physical address. This isolates a driver against its own *bugs* and not against its own *intent* — a crashed driver that restarts instead of taking the machine down is the entire benefit, and it is -worth having on those terms. [../plan/hybrid.md](../plan/hybrid.md) says so at -length. +worth having on those terms. VT-d/AMD-Vi is its own project. **One device per process.** A driver drives one thing; if you need two, run two. @@ -240,29 +333,22 @@ length. ## Linux drivers -**Decided 2026-08-08.** Linux drivers are not shipped with SchOS and never -will be. The user may install one on their own machine, through a device -manager or equivalent, and SchOS will load it. +[ldls.md](ldls.md) is how SchOS uses them: a Linux `.ko` runs in a process and +reaches the hardware through `/dev/pci`. The licensing position, stated exactly +because it is easy to get wrong in both directions: -The licensing reasoning matters and is worth stating exactly, because it is -easy to get wrong in both directions: +- **Distribution is what triggers the GPL.** SchOS is GPL-2.0-or-later, so shipping a + GPL-2.0-only driver in an image is *lawful* — see [../licensing.md](../licensing.md) §6 — + and the owner has decided it is wanted (code and firmware blobs). The ISO ships Linux's + r8169 set. The repository holds no binary: `.gitignore` refuses `*.ko`, and + `make ldls-r8169` stages the modules from a Linux build, where the matching BTF is. +- **Implementing a loader is not copying.** Writing support for a module format and + a driver ABI from published documentation produces our own work; the layouts come + from the kernel's BTF at load time on the user's machine, so the source holds none + of Linux's. -- **Distribution is what triggers the GPL.** SchOS is GPL-2.0-or-later, so - shipping a GPL-2.0-only driver in an image is now *possible* — see - [../licensing.md](../licensing.md) §6 — but LDLS still does not, by policy. A - user obtaining a driver themselves and loading it on their own machine is - their act, not ours, and is not a distribution by us. -- **Implementing a loader is not copying.** Writing support for a module format - and a driver ABI from published documentation produces our own work. It is a - clean-room implementation, and it stays one only if it is written from - documentation rather than transcribed from Linux source. Reading Linux to - understand *hardware* is fine; translating its code is not, whatever the - identifiers say. - -So the rule for this subsystem is the same as everywhere else in the tree: the -loader is ours and GPL-2.0-or-later, what it loads is the user's business, and -no third-party module enters an image we build. `RAMDISK_EXCLUDE` and `NOTPORTS` are the -existing mechanisms for keeping that true mechanically rather than by intention. +The loader is ours and GPL-2.0-or-later; what it loads is Linux's, unmodified, with its own +licence and source recorded in `NOTICE`. --- @@ -371,12 +457,13 @@ the console's minors to the console and the rest to their own functions. But - **No hot-plug.** Devices are registered at boot and never removed; the `in_use` flag is the hook a hot-unplug path would need, and nothing sets it for that purpose. -- **`NDEVICE` is 32**, compile-time, like every other table in the kernel. +- **`NDEVICE` is 128**, compile-time, like every other table in the kernel. - **In-kernel drivers are fixed at link time.** There is no module loader, and nothing can be added to the kernel after the build. A driver *outside* the - kernel is an ordinary program and can be started whenever — but it reaches its - device through `/dev/pci` and publishes nothing to the class vtables, so the - console cannot be handed to one yet. + kernel is an ordinary program and can be started whenever — it reaches its + device through `/dev/pci`, and a NIC driver can publish itself to the network + class through `/dev/netdev` (LDLS does); a display driver publishes nothing to + `display_ops`, so the console cannot be handed to one. - **The kernel does not rebind its own driver.** `PCIIOC_RELEASE`, or the process exiting, leaves the interrupt line masked and the in-kernel driver unbound — the console stays wherever the release put it. A *supervisor* can start a diff --git a/docs/architecture/filesystem.md b/docs/architecture/filesystem.md index 4ebf020..c5ee274 100644 --- a/docs/architecture/filesystem.md +++ b/docs/architecture/filesystem.md @@ -199,11 +199,14 @@ provides — reads and writes never reach it, because `fileread`/`filewrite` see It is read-only: `dirlink` and `ialloc` answer `EROFS` rather than pretending. A node exists because the kernel has the device, or it does not exist. -### What is there +### What is there in `/dev` -`console`, `null`, `keyboard`, `zero`, `full`, `random`, `urandom`, `tty`, -`stdin`, `stdout`, `stderr`, `fb0`, `mouse`, `hda`, `hdb`, and `input/event0` -and `input/event1` in one subdirectory. +`console`, `null`, `zero`, `full`, `random`, `urandom`, `tty`, `tty0` and +`tty1`..`tty12`, `stdin`, `stdout`, `stderr`, `keyboard`, `mouse`, `fb0`, `dsp`, +`ptmx` and a `pts/` directory of pseudo-terminal slaves, `hda`, `hdb`, +`input/event0`.., and the SchOS-specific `pci` (a device's registers, DMA and +interrupts, for a driver in a process), `netdev` (a network interface for a +process), `dbg` and `dbgro` (the kernel debugger's views). Every disk and partition the block layer registered is there too, under the name it was given (`sata0`, `sata0p2`, `nvme0p1`, `usb0p1`): major 8, minor its @@ -218,11 +221,18 @@ nothing behind it fails with `ENXIO`, which is a better answer than a missing path — and with no node at all, `echo x > /dev/hdb` silently creates an ordinary file of that name and appears to work. -### What is there +### What is there in `/proc` -Per-process directories, plus the global files: `meminfo`, `uptime`, `cpuinfo`, -`version`, `stat`, `loadavg`, `mounts`, `filesystems`, `schfs`, `pci`, -`interrupts`, `devices`, `netif`, and `sysrq`. +`/proc/self`, and one directory per process holding `stat`, `status`, `cmdline`, +`statm`, `comm`, `schedstat`, `maps`, `exe` (a symlink), `fd/` (a symlink per open +descriptor) and `task/` (one directory per thread). `maps` lists the file +mappings, and a process killed by a fault is told which file and offset it died +in. + +The global files: `meminfo`, `uptime`, `cpuinfo`, `version`, `stat`, `loadavg`, +`mounts`, `filesystems`, `partitions`, `schfs`, `pci`, `interrupts`, `devices`, +`netif`, `resolv.conf`, `slab`, `fsstat`, `kmsg`, `strace`, `bootmode`, +`bootdebug`, `quiet`, `verbose`, `schinit` and `sysrq`. **/proc is read-only except `sysrq`**, which is the one deliberate write target, and the code says so in one place. Marking every procfs inode read-only once @@ -242,13 +252,15 @@ mounted by init with `mount("sysfs", "/sys", "sysfs", 0, 0)`. | path | contents | |------|----------| -| `/sys/bus/pci/devices/0000:BB:DD.F/` | `vendor device class revision subsystem_vendor subsystem_device irq resource config uevent` | -| `/sys/bus/usb/devices/B-P[.P...]/` | `idVendor idProduct bcdDevice busnum devnum speed bDeviceClass bDeviceSubClass bDeviceProtocol bNumConfigurations uevent` | +| `/sys/devices/pci0000:00/0000:BB:DD.F/` (and `/sys/bus/pci/devices/` links to it) | `vendor device class revision subsystem_vendor subsystem_device irq resource config uevent subsystem driver` | +| `/sys/devices/pci0000:00//usbB/B-P[.P...]/` (and `/sys/bus/usb/devices/` links) | `idVendor idProduct bcdDevice busnum devnum speed bDeviceClass bDeviceSubClass bDeviceProtocol bNumConfigurations uevent subsystem driver` | | `/sys/firmware/acpi/tables/` | each ACPI table, raw; a repeated signature is numbered from 1 (`SSDT1`, `SSDT2`) | | `/sys/firmware/dmi/tables/` | `smbios_entry_point` and `DMI`, raw | | `/sys/firmware/memmap/N/` | `start end type`, the firmware's map as the loader handed it over | | `/sys/firmware/efi/` | present when UEFI booted the kernel; empty | | `/sys/class/dmi/id/` | `sys_vendor product_name product_version board_name bios_version`, and the rest Linux has, except serials and the UUID | +| `/sys/class/power_supply/` | `BAT0` (`capacity`, `status`, `energy_*`/`charge_*`, `voltage_now`, `cycle_count`, ...) and `AC` (`online`), from ACPI — the battery and the adapter are read through the laptop's embedded controller | +| `/sys/bus/pci/devices/…/` | also `boot_vga`, `enable` and `graphics/fb0`, which libpciaccess (and so Xorg) reads | **Linux's layout because everything that reads hardware reads it from there.** toybox's `lspci` and `lsusb` walk `/sys/bus/*/devices` and parse each `uevent` @@ -263,9 +275,18 @@ at the moment it is read, so a stick shows up in `/sys/bus/usb/devices` as soon as it is attached. Text files report size 0, as `/proc`'s do; binary ones (the tables, `config`) report their real length, since tools copy them by it. -What is missing, compared with Linux: `/sys/devices` and its symlinks (the -`bus/*/devices` entries are directories rather than links into it), -`/sys/class` beyond `dmi`, `/sys/block`, and anything writable. PCI `config` is +The kernel's own devices (input, tty, mem, graphics, block) have the Linux tree: +`/sys/devices/virtual//` with `dev`, `uevent` and a `subsystem` +link, `/sys/class//` and `/sys/dev/{char,block}/MAJ:MIN` as symlinks +into it, and input capabilities; `uevent` is writable, and a write is announced +on the uevent netlink socket ([udev.md](udev.md)). + +PCI and USB devices are in that tree too, under `/sys/devices/pci0000:00`, with +the `bus/*/devices` entries as links into it and `add`/`remove` events for USB +hot-plug ([udev.md](udev.md)). + +What is missing, compared with Linux: PCI bridges as parents, USB interfaces, +`/sys/class` entries for most devices, `/sys/block`, and most of what is writable. PCI `config` is 256 bytes, because config space is read through port `0xCF8`, which reaches no further. @@ -376,8 +397,6 @@ through. - **No page cache.** `mmap` of a file reads the whole thing in at map time, and writable `MAP_SHARED` of a file is refused rather than faked. This is the single largest gap in the layer. -- **No symlinks in procfs**, which is why `/proc//exe` is a regular file - and `ps -o comm` prints `[init]`. - **`rename()` does not move directories**, and will not replace one. `EISDIR` either way. - **No named pipes.** `mkfifo()` fails with `EPERM`, Linux's answer for a @@ -385,19 +404,13 @@ through. device, because `mknod()` read a Linux program's `(mode, dev)` as the native `(major, minor)`: `ls -l` showed `crw-rw-rw- 438, 0` where a `p` belongs. The zsh port was configured with `zsh_cv_sys_fifo=yes` on the strength of that. -- **No `/dev/fd`, and no `/proc/self`.** A process cannot reach its own open - descriptors by path. `<(cmd)` needs one or the other — zsh now knows it has - neither and says so. -- **No `AF_UNIX` socket inodes.** Socket names live in a table in - `kernel/net/unix.c`, so `bind()` creates nothing that can be `stat()`ed or - `unlink()`ed. +- **No `/sys/devices` and no uevents**, so nothing can be told that a device + appeared; Xorg is given its input devices by a config file instead. - **No unified inode cache across filesystems** beyond the single `itable`. -- **A known, un-root-caused hang:** heavy `ls -lR` use across many commands in - one long shell session can eventually make a *later*, unrelated directory - hang; the identical command succeeds on a fresh boot. It is not inode or file - table exhaustion — both fail cleanly and neither fired. It smells like a lock - or refcount leak in the `*at()` paths or in toybox's own fd handling. The next - step is instrumenting `itable`/`ftable` occupancy across a long session to see - what is climbing. - **`sys_delete`/`sys_show`/`sys_showstat`** are orphaned entries from the old v1 filesystem's recycle bin. Nothing reaches them and they die with `fs.c`. +- **One long-standing hang was never found.** Heavy `ls -lR` use across many + commands in one long shell session once made a *later*, unrelated directory + hang; the identical command succeeded on a fresh boot. It was instrumented and + hunted and could not be reproduced, which is recorded in + [../history/kernel.md](../history/kernel.md) rather than forgotten. diff --git a/docs/architecture/graphics.md b/docs/architecture/graphics.md index 58b0396..6676a1f 100644 --- a/docs/architecture/graphics.md +++ b/docs/architecture/graphics.md @@ -1,8 +1,8 @@ # Graphics, text and the console -Four things share this area and they are deliberately separate: the kernel's own -drawing surface, the compositor's display interface, the text-rendering layer, -and the crash screen. +Five things share this area and they are deliberately separate: the kernel's own +drawing surface, the `display` class a display server could use, the text-rendering +layer, the crash screen — and `/dev/fb0`, which is how Xorg reaches the screen. --- @@ -11,13 +11,13 @@ and the crash screen. This distinction causes confusion every time somebody meets it, so it is the first thing here. -| | [`gfx.h`](../../include/gfx.h) | [`display.h`](../../include/display.h) | +| | [`gfx.h`](../../include/gfx/gfx.h) | [`display.h`](../../include/gfx/display.h) | |---|---|---| -| **What** | The kernel's own drawing surface | The compositor's interface | +| **What** | The kernel's own drawing surface | A frame interface for a display server | | **About** | Pixels and text | *Frames* | | **Operations** | `draw_pixel`, `fill_rect`, `putc`, `text_at` | `acquire`, `present`, `wait_vblank` | | **Buffering** | None. Direct writes to the scanout. | Double-buffered | -| **Users** | The console and PSOGM | Schcom | +| **Users** | The console, PSOGM, GMMOS | None today — Xorg draws through `/dev/fb0` | | **Must work when** | **Nothing else in the system does** | Userspace is healthy | That last row is the reason they are not one interface. The console and the @@ -67,7 +67,7 @@ and `vgpud` mirroring it see what is on the screen. `struct display_ops`, found with `device_find_class(DEVCLASS_DISPLAY)`. `acquire()` allocates the back buffer **on first call, never at boot** — it is -around two megabytes at 800×600, and a machine whose compositor never starts +around two megabytes at 800×600, and a machine whose display server never starts should not pay that. A driver that cannot double-buffer returns 0, and the caller draws directly and accepts tearing rather than not drawing at all. `wait_vblank` is optional for the same reason. @@ -133,7 +133,7 @@ A machine with both a stdvga and a virtio-gpu has two displays, and `vgpud` — the out-of-kernel driver, `cmd/vgpud/main.c` — owns the second one. For a long time it drew a test card there: colour bands, gradients, a checkerboard, and its own pid in binary along the bottom. That was scaffolding for -[../plan/hybrid.md](../plan/hybrid.md)'s H2 and H3, where a screenshot had to +the out-of-kernel driver work ([devices.md](devices.md)), where a screenshot had to answer *did a userspace process put this here* and *did the driver come back*. Both of those are closed, so the test card became the Shift mark. Which was @@ -223,9 +223,8 @@ shows the top-left corner, which is where a terminal puts everything worth seeing. Scaling is the alternative and it is the wrong one: text resampled to a non-integer ratio is text you cannot read. -**This is not the endpoint.** A compositor (Gate 3) will want this head for -itself, and when there is one, the mirror goes away. Until then the honest -thing for a working display to show is the system running on it. +A display server that wants the head for itself can have it; until one does, the +honest thing for a working display to show is the system running on it. ### The F8 resolution is the head's resolution @@ -263,9 +262,26 @@ not apply: two `vgpud`s cannot coexist, because the first holds the PCI claim and the second is refused the device long before it reaches `bind()`. The claim is the mutual exclusion; the socket node never was. +## /dev/fb0, and how Xorg draws + +Xorg runs on the framebuffer through its `fbdev` driver, so `/dev/fb0` speaks Linux's +fbdev: `FBIOGET_FSCREENINFO`, `FBIOGET_VSCREENINFO`, `FBIOPUT_VSCREENINFO` (accepted +when it asks for the mode already in force), `FBIOPAN_DISPLAY`, `FBIOBLANK`, +`FBIOPUTCMAP`, and `mmap` of the physical framebuffer. `FBIOGET_VSCREENINFO` reports +nominal timings, as Linux's `efifb` does, instead of zeros — a zero pixel clock made +fbdev compute a 0 Hz RandR mode. Pixel layout comes from the same table the kernel +draws with, so a colour a program builds is the colour the kernel would. + +Once a program maps the framebuffer the kernel has no way to know when it draws, so +`fb_mapped()` marks the screen as no longer the text console's and `fbclose()` puts it +back. A terminal switched to `KD_GRAPHICS` is not repainted ([terminals.md](terminals.md)), +and the `VT_PROCESS` handshake tells the server before it loses the screen. `cmd/xready` +checks every one of these calls before Xorg is started; the picture itself is the proof +beyond that. + ## Text -[`include/gfx/text.h`](../../include/text.h) + `kernel/dev/text.c`. +[`include/gfx/text.h`](../../include/gfx/text.h) + `kernel/dev/text.c`. **The font was never the problem — the character-cell grid was.** The console draws text by indexing `font_data[256][16]` and stepping the cursor eight @@ -537,24 +553,63 @@ channel that works when the machine will not come back at all. machine whose network stack is dead**, and a phone camera is an out-of-band channel needing no driver on our side. -It encodes the **report itself**, not a URL — it reaches nothing. Deliberately -small in three ways: byte mode only, EC level L only (the screen is a clean, -evenly lit, high-contrast source; the damage QR's stronger levels protect -against does not apply), and versions 1–6 only, stopping short of version 7's -extra version-information blocks and version 10's wider count field. +It encodes the **report itself**, not a URL — it reaches nothing. Byte mode only +and EC level L only (the screen is a clean, evenly lit, high-contrast source; the +damage QR's stronger levels protect against does not apply), but **every version, +1 to 40**: version 40 at level L holds 2,953 bytes, which is what lets GMMOS put the +kernel log on screen as a series of codes. Checked against the `segno` library module +for module at every version. -134 bytes is a large crash report. If a payload ever needs more, the honest fix -is to shorten the report. +It draws on the report unconditionally rather than on a keypress, which is better: +it costs nothing, and requiring input would mean the one case where input is least +trustworthy is also the case where the report is hardest to retrieve. Verified by +`make crashshot && make crashqr`, which decodes the symbol out of the captured +framebuffer. -It draws unconditionally rather than on a keypress, which is better: it costs -nothing, and requiring input would mean the one case where input is least -trustworthy is also the case where the report is hardest to retrieve. Verified -by `make crashshot && make crashqr`, which decodes the symbol out of the -captured framebuffer. +### Every processor stops + +A panic used to stop only the processor it happened on; the rest carried on with a +dead kernel, drew over the report, and kept the fans loud until somebody held the power +button. The panicking processor now sends every other one an **NMI**, which arrives even +with interrupts off, and `trap()` halts a processor that takes one while `panic_stop` is +set — first thing, before the canary check, because that stack may be what broke. NMI has +its own per-CPU stack (IST 1): for a few instructions at the top of `syscall_entry` the +live stack is the user's, at ring 0, and a fault pushing the frame there would be a double +fault on the same stack and a reset instead of a report. A second processor that panics +while one is reporting halts. + +## GMMOS — the Guru Meditation Micro OS + +The report used to be the last thing the kernel did: a picture, blinking keyboard lights, +and a machine that needed its power button. GMMOS keeps the report and goes on living +under it (`kernel/gmmos/`, about 2,000 lines). The real kernel stays dead — nothing of it is +called again that takes a lock, sleeps, allocates or waits for an interrupt. GMMOS has its +own: the framebuffer, a clock read from the TSC, and keyboards it polls itself through +**micro drivers**, cut-down copies of the real ones that do only what a keyboard needs. + +| Key | | +|---|---| +| (nothing) | a status line counts down **three minutes** to power-off; any key cancels it | +| **Space** | the last 300 lines of this boot's log as **QR codes**, a page per symbol, numbered so a phone can take them in order; `+` / `-` trade module size for capacity | +| **L** | the log on screen, scrolled with the arrows | +| **T** | a terminal: `why`, `log`, `grep`, `ps`, `cpus`, `pci`, `mem` (page tables walked first, so a bad address cannot fault), `uptime`, `restart`, `poweroff` | +| **R R** / **P P** | restart / power off — twice, so a stray key does neither | + +The micro drivers are **PS/2**, **I2C-HID** (the laptop's own keyboard, over a DesignWare +controller — the live driver hands over what it found at boot, because AML cannot run on a +dead kernel) and **xHCI** (USB keyboards: every controller is halted, reset and enumerated +from nothing, root ports and USB 2 hubs up to two deep, a boot-protocol keyboard, no +interrupts, a static 256 KB pool). GMMOS probes all of them and polls every one that answers. + +Every buffer is static. A panic inside GMMOS, or a second one in the report, is reported +and then only blinks; GMMOS does not re-enter itself. Checked under QEMU: every screen, two +QR pages read off a screenshot with `zbar` and decoded to the log text exactly, restart +reboots, and left alone it powers off at three minutes. What has been seen on real hardware +is the report and the keyboards the laptops have; the xHCI path is the least exercised. ## One palette -[`include/gfx/theme.h`](../../include/theme.h). There were three unrelated designs +[`include/gfx/theme.h`](../../include/gfx/theme.h). There were three unrelated designs and the seams showed: the splash was deep purple with gold text deliberately matching the *panic* screen, the console was cream with near-black text, and so a healthy boot was dressed as a crash and then abruptly became an amber @@ -571,16 +626,20 @@ makes it mean something. ## What it does not do -- **The console still has its cell grid.** Cells are now a Unicode codepoint - drawn by the TrueType rasteriser rather than a CP437 byte drawn from a bitmap, - and the cell size follows the font's measured advance — but it is still a - fixed grid, and the text layer's proportional path is unused by it. -- **No mode setting.** Resolution is whatever the loader asked for. There is no - `/dev/dri`-style device and no way to change it at runtime. -- **No double buffering in practice.** `display_ops.acquire` is specified and - the compositor needs it; virtio-gpu binds but exposes no modeset API, and +- **The console still has its cell grid.** Cells are a Unicode codepoint drawn by the + TrueType rasteriser, and the cell size follows the font's measured advance (the font + scales to about 45 rows, 16 to 40 px per em, on whatever mode the firmware booted + in) — but it is a fixed grid, and the text layer's proportional path is unused by it. +- **Mode setting is the loader's and `vgpud`'s.** On real hardware resolution is whatever + the loader chose from its menu — which now asks "Keep this resolution?" and falls back + by itself, because AMD's GOP "sets" smaller modes without leaving the panel's native + timing and the screen showed three or four squashed copies side by side. There is no + `/dev/dri`, no KMS, and Xorg runs at the mode the console is in. +- **No double buffering in practice.** `display_ops.acquire` is specified and nothing + uses it; virtio-gpu exposes a modeset API on `vgpud`'s socket and nothing more, and `wait_vblank` is unimplemented everywhere. -- **No hardware acceleration of any kind**, and none is planned before 2D blit. +- **No hardware acceleration of any kind**: Xorg draws in software to the framebuffer + (no glamor, no DRI), and GPU drivers are a plan, not a feature ([../plan/gpu.md](../plan/gpu.md)). - **No CFF/Type 2 charstrings**, so OTF fonts do not render — that is a separate `font_ops` provider and a bytecode interpreter, not an extension of the `glyf` path. TrueType hinting is deliberately out of scope. @@ -593,6 +652,6 @@ makes it mean something. - **No composite point-matching.** Components positioned by matching a point in the base rather than by an offset are placed at the origin. Fonts that use it are rare and none in the tree do. -- **PSOGM renders and stops.** The interactive crash mode described in - [../plan/roadmap.md](../plan/roadmap.md) — walk the stack, read another - process's memory, dump page tables — is designed and not built. +- **GMMOS cannot look at a live process by name.** The "interactive crash mode" is built as far + as a dead machine allows — log, a terminal, `ps`, `cpus`, `pci`, memory by physical walk — but + the scheduler is gone, so there is no running a debugger against a process. diff --git a/docs/architecture/kernel.md b/docs/architecture/kernel.md index 28c722e..7b1ddef 100644 --- a/docs/architecture/kernel.md +++ b/docs/architecture/kernel.md @@ -11,18 +11,27 @@ driver or a protocol stack: memory, processes, scheduling, traps and signals. One page table per process; the kernel is mapped into all of them at `KERNBASE = 0xFFFF800000000000`, so a trap does not switch `CR3`. -`kvmalloc()` maps physical 0–4 GB as four 1 GB pages, with the top one marked -uncached — that is where PCI BARs and the framebuffer live. This is why 3 GB is -the most memory the kernel can treat as RAM, and it is a deliberate simplicity: -a direct map with a fixed offset makes `P2V`/`V2P` arithmetic rather than a -lookup. +`kvmalloc()` maps as many 1 GB pages of physical memory as the machine has RAM +(up to `PHYSTOP_MAX`, 256 GB), always at least the low four. The 3–4 GB slot stays +uncached because that is where the PCI hole is; everything above 4 GB is RAM, so +those are cached, and a device BAR up there still gets its own uncached window +from `ioremap()`, which takes the entries the direct map did not. On a CPU +without 1 GB pages (Intel's desktop parts before Haswell), each gigabyte is a +page directory of 2 MB pages instead — the same reach, one extra page of table +per gigabyte. A direct map with a fixed offset makes `P2V`/`V2P` arithmetic +rather than a lookup. -`kalloc()` hands out one 4 KB page at a time from a free list. **There is no -slab or pool allocator**, and the consequence is visible throughout the tree: -every subsystem that needs many small objects has invented its own fixed-size -table — `NPROC` processes, `NSOCK` sockets, `NDEVICE` devices, `NSHM` segments. -That is why the limits are compile-time constants rather than a design decision -about each one. +How much RAM there is comes from the firmware's E820/UEFI map, as a *list* of +usable regions, not one run ending at a top address (OVMF drops an ACPI NVS +region at 8 MB, which used to hide everything above it). The kernel itself is +linked at 16 MB, clear of what firmware keeps low. `kalloc` holds never-used +memory as ranges, so a large machine costs nothing until its memory is used. + +`kalloc()` hands out one 4 KB page at a time from a free list. Objects smaller +than a page come from the slab caches below; objects larger than one do not have +a home yet, so the biggest tables (`NPROC` processes, `NSOCK` sockets, `NDEVICE` +devices, `NSHM` segments) are still fixed-size arrays, which is why their limits +are compile-time constants rather than a design decision about each one. The free list carries a canary, checked at the top of both `kalloc()` and `kfree()`, so a wild write into it panics at the point it is touched rather than @@ -150,8 +159,8 @@ pages, reference counted, mapped through a VMA with `no_free` set so that `memfd_create(2)` is the same machinery reached by **descriptor** instead of by key. That difference is the whole point and the reason it exists on Linux at all: two processes share memory by passing the fd over a socket, so neither has -to agree on a path or race to invent a key. It is what a Wayland buffer crosses -on, and it is why descriptor passing (`SCM_RIGHTS`) had to be built first. +to agree on a path or race to invent a key. It is how a display server and its client +share a buffer, and it is why descriptor passing (`SCM_RIGHTS`) had to be built first. | | | |---|---| @@ -171,10 +180,46 @@ version of something this kernel cannot do: mapper. Here a freed page would simply stay mapped to memory somebody else now owns. -Writable `MAP_SHARED` of a **real file** is still `ENODEV`. That needs a page -cache, it is a separate project, and nothing in 1.0 asks for it — an anonymous -object has no such contract to break, which is exactly why `memfd` is the -narrow answer. +Writable `MAP_SHARED` of a **real file** is still refused. That needs a page +cache, it is a separate project, and nothing that runs today asks for it — an +anonymous object has no such contract to break, which is exactly why `memfd` is +the narrow answer. + +### Demand paging + +Anonymous memory can be recorded without being backed: the page appears, +zeroed, the first time it is touched — by the program, through `trap()`, or by +the kernel on its behalf, through `uva2ka()`. `mmap` does this for mappings of +1 MB or more, for `MAP_NORESERVE`, and for `PROT_NONE`. The reason is WebKit, +which reserves 4 GB of address space at start for its JIT and heaps and touches +a sliver of it; backing all of it at `mmap` time failed with `ENOMEM` and the +browser aborted. Go, Java and Firefox reserve the same way. + +`vm_fault()` takes a spinlock and nothing that sleeps, so `uva2ka()` may call it +from anywhere; the lock keeps two threads of one process from both mapping the +same page. `fork` and `munmap` skip the holes, so a page nobody touched is one +they never see, and a child that touches it reads zeroes — what it would have +read in the parent. + +File-backed mappings are **not** lazy: `mmap` of a file still reads the range in +at map time. + +### TLB shootdowns + +Unmapping from an address space that threads on other processors are running in +interrupts those processors and waits until each has reloaded `CR3` +(`tlb_shootdown()` in `vm.c`; the vector is one of the MSI lines, which `trap()` +already dispatches with an EOI). Without it, when one thread unmapped a range and +`mmap` handed the same addresses straight back, a thread on another CPU went on +reading and writing the *old* pages through its TLB: stores vanished and loads +were stale. musl's allocator noticed first — a group's metadata no longer +pointing back at it — and Epiphany, forty threads on four processors, died at a +different address on every run. + +Only one shootdown runs at a time, and it is taken without disabling interrupts: +two processors each waiting with interrupts off for the other to answer would +wait for ever. On a single processor, or if no vector could be allocated, it +does nothing (and says so at boot). ### What the fixed tables actually cost @@ -184,31 +229,69 @@ partly true: | | size | table | | |---|---|---|---| -| `struct proc` | 11744 | `NPROC` 128 | larger than a page; 1 KB of it is FPU state | -| `struct socket` | 4936 | `NSOCK` 128 | larger than a page | +| `struct proc` | 1984 | `NPROC` 128 | 254 KB in all; the address space, descriptors and signal handlers live in their own objects now, shared by threads | +| `struct mm` | 8232 | one per process, 128 | 1 MB in all, almost all of it the 256-entry VMA table | +| `struct socket` | about 4900 | `NSOCK` 128 | larger than a page | | `struct device` | 120 | `NDEVICE` 128 | 15 KB, never hit | | `struct buf` | 200 | `NBUF` 256 to start | grows from a slab, data from `kalloc`, up to RAM/32 | -| `struct inode` | 296 | none | grows from a slab; `NINODE` 200 is only how many are made up front | -| `struct file` | 64 | none | grows from a slab; `NFILE` 512 is only the static pool | +| `struct inode` | about 460 | none | grows from a slab; `NINODE` 200 is only how many are made up front | +| `struct file` | about 88 | none | grows from a slab; `NFILE` 512 is only the static pool | -The two largest cannot go in a page-backed slab at all. Freeing them needs a -contiguous multi-page allocator, which is a separate thing and is not written. -The buffer header shrank to 200 bytes when its 4 KB of data moved out to a page -of its own, which is what let the cache grow at all. +(Sizes from `nm -S` on a build of the tree, not from a header comment.) + +`struct proc` shrank by a factor of six when threads arrived, because the page +tables, VMAs, descriptor table and signal dispositions moved out of it into +objects a thread group shares. `struct socket` is still too large for a +page-backed slab; freeing it needs a contiguous multi-page allocator, which is a +separate thing and is not written. The buffer header shrank to 200 bytes when its +4 KB of data moved out to a page of its own, which is what let the cache grow at +all. ## Processes -`struct proc` in [`include/core/proc.h`](../../include/proc.h). A fixed table of -`NPROC` entries, allocated by linear scan. +`struct proc` in [`include/core/proc.h`](../../include/core/proc.h). A fixed table of +`NPROC` entries, allocated by linear scan. A thread is a `struct proc` too. **The field order at the top of the struct is fixed by assembly.** `trapasm.S` hard-codes `fs_base` at offset `0x18`, and there is a `_Static_assert` in `proc.c` that fails the build if it moves. Anything new goes at the end. -Each process carries: page directory and size, kernel stack, open file table, -cwd inode *and* cwd path string, signal handlers and masks, VMA table for -`mmap`, POSIX identity (pgid, sid, and all six of real/effective/saved uid and -gid), and the Linux-ABI syscall map. +Each process carries: a kernel stack, cwd inode *and* cwd path string, signal +masks, POSIX identity (pgid, sid, and all six of real/effective/saved uid and +gid), the Linux-ABI syscall map, and its x87/SSE registers (saved on a switch; +the kernel itself stays off them). Three things it *points to* rather than +owns, because a thread group shares them: + +| | | +|---|---| +| `struct mm` | the address space: page tables, heap top, the VMA table, which file each span came from, and a lock so one thread at a time changes the mappings | +| `struct files` | the descriptor table and its `FD_CLOEXEC` flags | +| `struct sighand` | signal dispositions — what `sigaction()` sets | + +Each is reference counted. `clone()` shares what its flags say and `fork()` +copies all three. + +### Threads + +`clone()` takes `pthread_create`'s flags (`CLONE_VM`, `FILES`, `SIGHAND`, +`THREAD`, `SETTLS`, `PARENT_SETTID`, `CHILD_CLEARTID`) and `posix_spawn`'s +(`VM`, `VFORK`, a stack). What follows from that: + +- **futex** — `WAIT`, `WAIT_BITSET` with timeouts, `WAKE`, `REQUEUE`, + `CMP_REQUEUE`, keyed by the word's *kernel* address so processes that map the + same page meet. `set_tid_address` keeps the word that exit zeroes and wakes, + which is what `pthread_join` waits on. +- **Exit.** `exit` ends the calling thread; `exit_group`, a fatal signal, or the + leader exiting ends them all. `reapd` frees exited threads, and the leader is + reaped by its parent once the rest are gone. `getpid` is the group, `gettid` + the thread. +- **Signals.** Stops and resumes reach every thread; other process-directed + signals go to the leader. +- **`exec`** kills the other threads and unshares the dispositions. +- **`/proc`** lists processes, `task/` lists threads; `comm`, `Threads:` and + `Tgid` are there. + +The first program to need all of this was GTK's, then WebKit's. ### Slot reuse @@ -232,12 +315,15 @@ exactly as a syscall would. The network thread is the current user. ## Scheduling -Round-robin over the process table, one scheduler per CPU, each with its own -context. `swtch()` saves callee-saved registers and switches stacks; `sched()` -returns to the scheduler and asserts four invariants on the way — holding -`ptable.lock`, `ncli == 1`, not `RUNNING`, interrupts off. +Fair share by virtual runtime, one scheduler per CPU, each with its own context +— the rule, the guards and `nice` are in [scheduler.md](scheduler.md). +`swtch()` saves callee-saved registers and switches stacks; `sched()` returns to +the scheduler and asserts four invariants on the way — holding `ptable.lock`, +`ncli == 1`, not `RUNNING`, interrupts off. -When a full pass finds nothing runnable the CPU halts rather than spinning. +When nothing is runnable the CPU goes idle rather than spinning: into ACPI's +`_CST` C2 state (CC6 on Zen), by port read or `MWAIT`, where the APIC timer keeps +running through it, and otherwise a plain `hlt`. **Locking is a single `ptable.lock` over the whole table.** Not per-process: a scheduling operation touches two processes (the one leaving and the one @@ -309,9 +395,23 @@ mksh's job control could not make progress until they existed. ## What userspace gets -`fork`, `exec`, `wait`/`waitpid`/`wait4` with `WNOHANG` and real pid selection, -pipes, signals, `mmap` (anonymous and file-backed), POSIX shared memory, Unix -domain sockets, `poll`/`select`/`epoll`, `eventfd`, `timerfd`, `signalfd`. +`fork`, `clone` (threads), `exec`, `wait`/`waitpid`/`wait4` with `WNOHANG` and +real pid selection, pipes, signals, `mmap` (anonymous and file-backed), POSIX +shared memory, `memfd`, Unix domain sockets, `poll`/`select`/`epoll`, `eventfd`, +`timerfd`, `signalfd`, `inotify`, `futex`. + +### Dynamic linking + +`exec` handles `ET_EXEC` and `ET_DYN`. A dynamically linked program names an +interpreter in `PT_INTERP` — on musl, `/lib/ld-musl-x86_64.so.1`, which is libc +under another name — and the kernel's whole part is to load that as well and +start it instead of the program. The interpreter finds the program through the +auxiliary vector (`AT_PHDR`, `AT_ENTRY`), maps libc and whatever else it needs +with `mmap()`, relocates, and jumps to `AT_ENTRY` itself. A position-independent +program goes where a fixed one would (`0x400000`); the interpreter goes at the +start of the `mmap` area (`0x40000000`) and `mmap` carries on above it, so the +two never meet. This is what lets Alpine's binaries run unmodified, and +`dlopen` works, so Xorg loads its drivers as modules. `mmap` uses a bump allocator (`proc->mmap_next`, starting at `0x40000000`) so every mapping gets its own disjoint, exactly-sized range. It previously assigned @@ -329,24 +429,29 @@ allocation over 8 MB mapped straight into a neighbouring live mapping. loop reports 97%, `sleep 2` reports 0% — but kernel time needs TSC deltas closed and reopened around every `sched()`, which is scheduler accounting rather than sampling, and is not written. - -- **No slab allocator.** One page at a time, and every subsystem's fixed-size - table is a consequence. This is the single change that would most improve the - rest of the kernel. -- **No demand paging and no page cache.** `mmap` of a file *reads the whole - thing in* at map time. Writable `MAP_SHARED` of a file is therefore refused - outright rather than faked — see [../plan/foundations.md](../plan/foundations.md). -- **No dynamic linking.** `exec` handles `ET_EXEC` only: no `PT_INTERP`, no - `ET_DYN`, no shared libraries, no `dlopen`. -- **No threads.** `clone` exists but does not share an address space; - `gettid` returns the pid. -- **Permissions are tracked but not enforced.** uid/gid are maintained honestly - so `getuid()`/`setuid()` stop lying, and nothing consults them on access. +- **`struct socket` is too big for a slab**, and every subsystem's fixed-size + table (`NPROC`, `NSOCK`, `NDEVICE`, `NSHM`) is still a compile-time constant. + Slabs exist for the objects that fit in a page. +- **No page cache.** `mmap` of a file *reads the range in* at map time, and + writable `MAP_SHARED` of a file is refused outright rather than faked. Demand + paging covers anonymous memory only. +- **Root is `euid == 0` and nothing finer.** No capabilities; file access, `kill`, `exec` + and the privileged calls are enforced as [users.md](users.md) says. +- **`mprotect` is accepted and not enforced** (`sys_mprotect` returns 0). RELRO is not read-only, + a `PROT_NONE` guard does not fault, and nothing has `NX` (`EFER.NXE` is never set), so every + mapping is executable. +- **`poll`, `select` and `epoll` wait a tick at a time** (10 ms at `HZ=100`) unless something + that can make a descriptor ready calls `poll_kick()` — which the pipe, socket and event paths + do. busybox's `wget` polls before every read and downloaded at 85 KB/s, a segment per tick, + until the network path started kicking. - **No swap**, and no memory pressure handling of any kind. `kalloc` failing is reported up as `-ENOMEM` and that is the whole policy. - **Nested signal delivery is not supported.** `in_signal` guards against re-entering a handler while one is running. -- **Scheduling is fair-share on one global run queue.** Priorities and - fairness accounting exist now ([scheduler.md](scheduler.md)); per-CPU run - queues, work stealing and CPU affinity do not, despite `sched_setaffinity` - existing. +- **One global run queue.** Fair share and `nice` exist; per-CPU run queues, work + stealing and CPU affinity do not, and `sched_setaffinity` is accepted and + ignored. +- **No IOMMU**, so DMA-capable devices (and the driver processes that program + them) are trusted. +- **256 GB is the most RAM** the kernel uses; the rest is ignored, with a line + saying so. diff --git a/docs/architecture/ldls.md b/docs/architecture/ldls.md index 6be30a0..cf7531c 100644 --- a/docs/architecture/ldls.md +++ b/docs/architecture/ldls.md @@ -6,7 +6,11 @@ drivers, and LDLS is what does that. It works, and on more than one driver. `8139too` and `e1000` — unmodified GPL kernel modules — load into a SchOS process, drive their hardware through -`/dev/pci`, and carry packets: +`/dev/pci`, and carry packets (both under QEMU). A third, Linux's `r8169`, is +the NIC driver on the ASUS TUF A15 laptop, and it is the first that needs a +*set* of modules and not one; it is described under *A set of modules*. It has +registered its interface and read its PHY on that laptop; whether it carries +traffic there has not been confirmed yet. ``` # ldlsd /lib/modules/8139too.ko /lib/modules/btf & @@ -46,18 +50,25 @@ Every one was a call that succeeded, returned zero, and did nothing. Five, and everything else is an implementation of them. -### 1. Nothing GPL ever enters an image we build +### 1. Linux's drivers ship in the image -The driver is **the user's**: obtained by them, loaded on their machine. SchOS -ships the layer and never the thing it loads. `.gitignore` refuses `*.ko`, -`make ldls-stage` is the one command that puts a module into a local image and -is deliberately not part of `all`, and `make ldls-unstage` takes it out again. +SchOS is GPL-2.0-or-later, and GPL-2.0-only code combines with it, so there is no +licensing reason to keep Linux's drivers out of an image. They are in: the **r8169 set** +(`mdio-bus`, `libphy`, `mdio_devres`, the Realtek PHY driver and `r8169`, unmodified) and +the RTL8168 PHY firmware are what the ISO carries for the laptop's network, with their +licence and corresponding source recorded in `NOTICE` +([../licensing.md](../licensing.md) §6). -[../licensing.md](../licensing.md) §6 is the whole argument. The short form is -that the combination happens in a user process on the user's machine, so no -combined work is distributed by anyone. Since SchOS moved to GPL-2.0-or-later -that is no longer the only way it *could* be done — it is the way it is done, -because a module we never ship is a module we never have to answer for. +(An earlier version of this rule said the opposite — that LDLS would ship the layer and never +the thing it loads. That was written when SchOS was Apache-2.0 and the combination could not +be distributed; it stopped being true with the move to the GPL, and the owner has said Linux +code and blobs may be shipped.) + +What is left is mechanical. The repository holds no `*.ko` — a binary does not belong in git, so +`.gitignore` refuses them — and `make ldls-r8169` (before `make iso`) stages the set from a +Linux build, together with its BTF, because a module, its dependencies and the BTF that +describes them have to come from the same build. `make ldls-stage KO=…` does the same for any +other module, which is then a distribution of *that* module's code, under its own licence. ### 2. It runs in a process, never in the kernel @@ -186,6 +197,55 @@ the object has to live where a signed 32-bit absolute address reaches. A process's mmap area starts at `0x40000000`, which is inside that window; on the host harness `MAP_32BIT` reaches it from the other end. +### A set of modules — what r8169 needed + +Linux's `r8169` is not one module. It needs `mdio-bus`, `libphy`, `mdio_devres`, +the Realtek PHY driver, the NIC itself, the RTL PHY firmware, and the BTF of the +kernel they came from. `make ldls-r8169` stages all of that from the running +kernel; `etc/schinit/ldls-r8169.job` starts `ldlsd` on it at boot, with its output +in `/var/log/ldls-r8169.log`. The job has `KeepAlive = no`: a driver that crashes +on real hardware would otherwise be restarted into the same crash every few seconds. + +What that added to `ldlsd`: + +- **A comma-separated module set**, loaded in dependency order, each linking + against the ones before it, with the BTF read before any `init_module` runs. + A library module (`libphy`) just exports symbols to the next; the PCI `probe` + runs once, at the end, for whichever module registered a `pci_driver`. +- **A driver core**: bus match, driver probe, so the PHY driver binds to the PHY + the way it does in Linux; **devres**; a kernel `printf`; real `udelay`/`msleep`/ + `ktime`; and **1000 Hz `jiffies`**, because the module's `HZ` is compiled in and + phylib re-arms its link poll `HZ` jiffies out. +- **Firmware** read from `/lib/firmware`. +- **phylib's data**, the `link_mode_params` table and its bitmaps. +- **The symbols whose stub-zero was a lie**: list validity, `try_module_get`, + `dev_err_probe`, `eth_platform_get_mac_address`, `napi_consume_skb` (as a stub it + leaked one buffer per transmitted frame until the DMA heap ran out), and the + rest of the table at the end of this document. +- `pdev->bus` is a real `pci_bus`; `net_device.priv_len` is set from BTF (the + driver's `memset` of its ring bookkeeping is bounds-checked against it, and + `__fortify_panic` fired on a length of zero); and `cmovcc` from per-CPU memory is + rewritten like the other `%gs` forms. +- **`__preempt_count` starts at `PREEMPT_ENABLED`.** mdio-bus keeps the register + it has just read in `r11` across `preempt_enable()`, which on x86 is + `decl %gs:__preempt_count; je`, to a call that preserves every register. With the + count at zero the call was made, into a logging stub whose syscall left `RFLAGS` + (0x246) in `r11` — so every MDIO read returned `0x02460246`. `preempt_schedule` is + now a bare `ret`. +- `__fortify_panic` names the module and function (`r8169: rtl_open.cold+0x…`) so + a photograph of the screen is enough, and exits instead of spinning on a core and + holding `/dev/netdev`. + +The kernel did two things for it. It turns **ASPM off on Realtek NIC links** at +boot, as `r8169` does through `pci_disable_link_state`, which a process cannot; +and a network interface that **appears after boot starts the stack** — with the +native driver out of the registry the laptop boots with no interface at all, and +`ldls0` arrives later. `ldlsd` says which refusal it got when attaching: `EBUSY` is +another process, anything else is the kernel. + +The native `drivers/net/r8169/` stays in the tree, with Linux's 8168H setup, as +the fallback; it is out of the registry. + ### The KPI — `ldls/kpi/` The Linux symbols, implemented. Register access to a mapped BAR; the PCI calls @@ -232,8 +292,8 @@ and setting both to zero makes the whole thing reversible with no lookup table. ### The back end — `/dev/pci` -Already built, for `vgpud`, before any of this existed. -[hybrid.md](../plan/hybrid.md) insisted the interface be about *PCI devices* +Already built, for `vgpud`, before any of this existed. The design insisted the +interface be about *PCI devices* rather than about a driver model, on the argument that an interface whose first consumers are native drivers is a general mechanism rather than a hole shaped like something foreign. A Linux driver's `pci_iomap`, `dma_alloc_coherent` and @@ -241,10 +301,9 @@ like something foreign. A Linux driver's `pci_iomap`, `dma_alloc_coherent` and ### Giving a device up — `release` -A driver in the kernel now has a `release` hook, and `/dev/pci` calls it before -handing its device to a process. `docs/plan/hybrid.md` had said the honest -answer to "may I have the one you are using" is *no* until a driver could be -told to let go; e1000 is the first that can. +A driver in the kernel has a `release` hook, and `/dev/pci` calls it before +handing its device to a process. The honest answer to "may I have the one you are +using" was *no* until a driver could be told to let go; e1000 is the first that can. Letting go is three things, and the third was missed at first: @@ -276,7 +335,7 @@ one flag and hands over all 65536 ports, including the interrupt controller, the timer and the disk; the TSS I/O permission bitmap is per port. A foreign driver already has DMA with no IOMMU, and the answer to "this is not a security boundary" is not to move it further — so it is the bitmap, at 8 KB per CPU of -resident memory. See [../../include/ioperm.h](../../include/ioperm.h). +resident memory. See [../../include/arch/ioperm.h](../../include/arch/ioperm.h). Writing it found a latent hole. The TSS's I/O map base is at offset 102 and the old code wrote offset 64; the field it meant to set stayed 0, and a base of 0 @@ -336,17 +395,17 @@ point of having built it against our own: ## What it does not do -- **Two drivers, one kernel version, one machine.** `8139too` and `e1000` on - 6.18.46 against QEMU. The second driver was worth more than the first: it - found the per-CPU problem, the retpoline problem, the port-I/O problem, the - interrupt storm and a seven-year-old hole in the TSS. Nothing here has been - tried on a second kernel or on real silicon, and "LDLS runs Linux drivers" is - still a claim about a category from a sample of two. +- **Three drivers, one kernel version.** `8139too` and `e1000` on 6.18.46 + against QEMU, and `r8169` on that version against QEMU (where it finds no + device) and on one laptop (where it gets as far as the PHY). The second driver + was worth more than the first: it found the per-CPU problem, the retpoline + problem, the port-I/O problem, the interrupt storm and a seven-year-old hole in + the TSS. Nothing here has been tried on a second kernel, and "LDLS runs Linux + drivers" is still a claim about a category from a sample of three. - **No interrupts**, per the polling note above. -- **`jiffies` is not `HZ`.** It comes from a 100 Hz `uptime()`, which is a - plausible `HZ` and not necessarily the one the module was built for. A driver - measuring a timeout against it gets a longer timeout, which is the safe - direction and is still wrong. +- **`jiffies` assumes `HZ` is 1000**, which is what the kernels LDLS loads from + are built with (`ldls_load.hz` can say otherwise). It comes from the + monotonic clock in milliseconds. - **One work item at a time, on the main loop.** A driver whose deferred work blocks stops everything, including the transmit path it is probably waiting for. @@ -363,14 +422,21 @@ point of having built it against our own: process, which is a leak in the strict sense and the right trade for a program that will be killed to restart it. - **BTF has to come from somewhere.** SchOS has no `/sys/kernel/btf/vmlinux`, - so the user brings it with the module, from the kernel the module was built - for. A kernel built without `CONFIG_DEBUG_INFO_BTF` has neither. + so `make ldls-stage` / `ldls-r8169` copy it from the Linux kernel the module + came from. A kernel built without `CONFIG_DEBUG_INFO_BTF` has neither. +- **There is address-space groundwork nothing uses.** `kernel/arch/x86_64/vm.c` + maps the kernel image a second time at `LDLS_KALIAS`, in the top 2 GB where a + `-mcmodel=kernel` module can reach, with a `vmemmap` and a module area beside it + — at every boot — and `include/ldls/ldls.h` describes a `/dev/ldls` control + channel to load modules into the kernel. That was the start of an in-kernel + loader, contradicting rule 2. Nothing calls either, and it should be finished + as a decision or deleted. - **No config-space writes.** `/dev/pci` has none, deliberately — a write is how a BAR gets moved out from under the kernel — so a driver that needs one is refused loudly rather than quietly succeeded. -- **No module-to-module dependencies, no firmware loading, no sysfs, no - unload.** `request_firmware` fails, `module_exit` is never called, and the - way to stop a driver is to kill the process. +- **No sysfs and no unload.** Modules depend on each other and firmware is read + from `/lib/firmware`, but `module_exit` is never called, and the way to stop a + driver is to kill the process. - **Some symbols are still logging stubs.** Deliberate, per rule 5 — and the single most expensive thing in this layer. **A stub that returns zero is not safe when zero is a meaningful answer**, and the list of ways that bit is diff --git a/docs/architecture/networking.md b/docs/architecture/networking.md index 84de7ba..a7b9fa5 100644 --- a/docs/architecture/networking.md +++ b/docs/architecture/networking.md @@ -1,7 +1,9 @@ # Networking ARP, IPv4, ICMP, UDP, TCP and DHCP, over a driver-agnostic interface class, -reached through the same socket syscalls as Unix domain sockets. +reached through the same socket syscalls as Unix domain sockets — plus DNS +(resolved in the process, by musl), HTTPS (`wget`, Alpine's TLS libraries) and a +small netlink responder for `getifaddrs`. Verified end to end rather than by inspection: `bootcheck -Net` starts a listener and a UDP responder on the host, tells the guest to talk to them, and @@ -25,7 +27,9 @@ that completes a handshake and drops every byte. └────────────────┬───────────────────┘ │ struct netdev_ops ┌────────────────┴───────────────────┐ - │ e1000, and any future NIC driver │ + │ e1000 rtl8139 r8169 — or a │ in the kernel + │ Linux driver in a process, which │ (LDLS, via /dev/netdev) + │ is a netdev like any other │ └────────────────────────────────────┘ ``` @@ -86,50 +90,55 @@ covers two decades of silicon — and why the driver prints the chip's XID on probe, since the PCI device id tells you almost nothing about which part arrived. -### Why it is native, when the plan said LDLS +### Native first, then Linux's own -The first plan was to run Linux's `r8169.ko` under [ldls.md](ldls.md). Measuring -it is what changed the answer: the module has **183 undefined symbols, 130 of -them not implemented**, and 35 of those 130 are **phylib** — -`phy_connect_direct`, `phy_start`, `mdiobus_read`, `genphy_soft_reset`. +The first plan was to run Linux's `r8169.ko` under [ldls.md](ldls.md), and +measuring it changed that: the module has **183 undefined symbols, 130 of them not +implemented**, and 35 of those 130 are **phylib** — `phy_connect_direct`, +`phy_start`, `mdiobus_read`, `genphy_soft_reset`. phylib is a subsystem with a link +state machine, not a set of leaf calls, and LDLS's usual answer for an +unimplemented symbol — a stub that reports itself and returns zero — is precisely +wrong for it: `phy_start` returning zero means the link never comes up and nothing +says why. So a **native driver** came first: it reaches the PHY through the +chip's own MII window (`PHYAR`, one register that is a whole MII bus) and skips +the subsystem, in the shape of the e1000 beside it. -phylib is a subsystem with a link state machine, not a set of leaf calls, and -LDLS's usual answer for an unimplemented symbol — a stub that reports itself -and returns zero — is precisely wrong for it. `phy_start` returning zero means -the link never comes up and there is nothing on screen to say why. The 8139too -that ldls.md uses as its model has no phylib dependency, which is why that one -was tractable. +It has since been done the other way as well. LDLS grew a driver core, devres, +firmware loading and phylib's tables ([ldls.md](ldls.md), *A set of modules*), +and the ASUS TUF A15's RTL8168H is now driven by **Linux's r8169, unmodified, in +a process**: `make ldls-r8169` stages it with libphy, mdio-bus and the PHY driver, +and `etc/schinit/ldls-r8169.job` starts it at boot. The native driver is **out of +the registry** — it would claim the device first — and stays in the tree, with +Linux's 8168H setup, as a fallback. -A native driver reaches the PHY through the chip's own MII window (`PHYAR`, -one register that is a whole MII bus) and skips the subsystem entirely. It is -less work than the port, it carries no GPL entanglement, it needs no firmware -blob, and it is not coupled to one Linux version. - -The register layout is a *fact about the hardware*, taken the way -[ldls.md](ldls.md) §3 says facts may be taken. The expression is this tree's: -a descriptor-ring driver in the same shape as the e1000 beside it. +On that laptop the interface is `ldls0`, and it appears *after* boot, which is why +`net_attach_late()` starts the stack when the first interface arrives instead of +turning it away. ### The Chromebox has one of these -The Asus Chromebox CN62 "Guado" turns out to carry an RTL8111 rather than the -Intel part its spec sheet suggests, so this driver covers it as well as the -laptop that prompted it — which is the argument for the family match in one -line rather than a device id per board. +The Asus Chromebox CN62 "Guado" carries an RTL8111 rather than the Intel part its +spec sheet suggests, so the family match (`10EC:8168`) covers it as well as the +laptop — which is the argument for matching a family in one line rather than a +device id per board. -### It has not run on hardware +### It has not been shown to work on hardware -**Stated plainly because it cannot be tested here.** QEMU emulates `rtl8139` -and nothing else from Realtek, and the 8139 is a different programming model — -a circular receive buffer rather than descriptors — so it is deliberately *not* -in the match table. Claiming it would break a NIC that works. +**Stated plainly because it cannot be tested here.** QEMU emulates `rtl8139` and +nothing else from Realtek, and the 8139 is a different programming model — a +circular receive buffer rather than descriptors — so it is deliberately *not* in +the match table. Claiming it would break a NIC that works. -What has been verified: the driver builds, its presence changes nothing on a -machine without the device, and with an `rtl8139` on the bus it correctly -leaves it unclaimed (`10ec:8139 network controller [-]`). +What has been verified: both drivers build, their presence changes nothing on a +machine without the device, and Linux's r8169 under LDLS loads, links and +initialises in QEMU, registering and finding no device. On the laptop it reached +probe and read the PHY (after a bug in LDLS that made every MDIO read return +`0x02460246` was fixed); the native 8168H bring-up — the PLL, the receive gate, the +PHY through the OCP window — has not run on a part. -What has not: every register write, on a real part. +What has not: traffic on real hardware, from either. -So the probe says everything a first bug report would need, with `cprintf` +The native probe says everything a first bug report would need, with `cprintf` rather than `kinfo` so it reaches the screen on a machine with no serial port: ``` @@ -141,6 +150,9 @@ register 1, where bit 2 is link and bit 5 is autonegotiation complete. Those four facts separate "the chip is not there", "the chip is there and the PHY sees nothing", and "everything is up and the rings are wrong". +The kernel turns **ASPM off** on Realtek NIC links at boot, as Linux's driver +does through `pci_disable_link_state` — which a process under LDLS cannot do. + ### Two constants that were wrong first Recorded because both would have worked, quietly: @@ -156,7 +168,7 @@ Recorded because both would have worked, quietly: ## The netdev class -A driver publishes a [`struct netdev_ops`](../../include/netdev.h) and is then +A driver publishes a [`struct netdev_ops`](../../include/dev/netdev.h) and is then finished with the network stack forever: it never mentions lwIP, and the stack never mentions it. @@ -262,6 +274,12 @@ to. Measured across three pings: `rx_frames` and `wake_rx` both went 2 → 6. table, and it returns immediately: everything happens in that thread, which cannot run until the scheduler is entered. +**The thread follows each device's link into lwIP.** The link used to be read once +at attach, 0.02 s into boot; real autonegotiation takes seconds, so on hardware +the interface stayed down for good. It also starts the stack when an interface +appears after boot (a driver in a process, a USB NIC), and attaches every device +there is as its first act; `net_eth_attach()` refuses one it already has. + DHCP obtains an address at boot. `/proc/netif` reports each interface's address, netmask, gateway, MAC and frame counts, taken at the lwIP seam. @@ -326,19 +344,20 @@ The creation lives in `sys_bind()` rather than here, because that is the layer with both: `kernel/net/unix.c` knows about sockets and has no business calling `create()`. -**`flock()` is still missing.** libwayland locks `wayland-0.lock` beside the -socket so that two compositors cannot claim one display. The lock file can be -created and opened now that paths are real; the advisory lock itself is a -separate mechanism with no other caller yet. +`flock()` and `fcntl` record locks exist (`kernel/fs/lock.c`), so the lock files +next to a socket work the way they do on Linux. Names in +the **abstract namespace** (a `sun_path` starting with a NUL) name no file; they +work, and `getsockname` reports them unnamed. `SO_PASSCRED` is honoured, and +`SO_PEERCRED` answers (tmux asks). ### Descriptor passing `sendmsg`/`recvmsg` with `SCM_RIGHTS`, which is the one thing a socket can carry that a pipe cannot: a reference to an object the kernel already knows, rather than a copy of its contents. Wayland's `wl_shm_create_pool` passes a descriptor -and has no integer-shaped alternative, and -[hybrid.md](../plan/hybrid.md)'s mechanism 1 is how a driver process is handed -the device it is allowed to touch. Both wanted the same primitive. +and has no integer-shaped alternative, and the same mechanism is how a driver +process is handed the device it is allowed to touch ([devices.md](devices.md)). +Both wanted the same primitive. **A batch of descriptors is pinned to the stream position of the first byte of the message that carried it.** That single rule is the whole delivery model, and @@ -374,9 +393,9 @@ descriptors to a position that never arrives. **There is no garbage collector.** An AF_UNIX socket descriptor, sent over that same socket and never received, holds a reference to the socket that holds the reference to it, and neither close can reach zero. Linux carries several hundred -lines to break these cycles. Nothing on either plan passes a socket over a -socket — Wayland passes memory, the driver work passes devices — so this is a -known and bounded leak (thirty-two sockets) rather than a silent one. +lines to break these cycles. Nothing passes a socket over a +socket — display servers pass memory, the driver work passes devices — so this is +a known and bounded leak (thirty-two sockets) rather than a silent one. ### AF_INET @@ -593,59 +612,51 @@ information was there and the code did not ask. ## What it does not do -- **`ping` is a first-party program**, `cmd/ping`, not toybox's. `recvmsg` now - exists, so half of what stopped toybox's is gone; what remains is - `IP_RECVTTL`, an `IPPROTO_IP` control message that would have to be generated - on the receive path rather than merely carried. Ours has no `-I`, no flood - mode and no TTL display. -- **`SCM_RIGHTS` is the only control message.** `SCM_CREDENTIALS` is refused - with `EINVAL` rather than ignored: a caller told its credentials were passed - would have the receiver read a uid out of uninitialised memory and act on it. +- **`ping` is a first-party program**, `cmd/ping`, not toybox's or busybox's. Ours + has no `-I`, no flood mode and no TTL display (`IP_RECVTTL` is an `IPPROTO_IP` + control message that would have to be generated on the receive path), and takes a + dotted quad or `localhost` — it does not link musl's resolver. Busybox's `ping`, + from the Alpine root, works over a `SOCK_RAW` ICMP socket. +- **Only `SCM_RIGHTS` and `SCM_CREDENTIALS` travel in a control message.** Anything + else is refused rather than ignored. - **A message may carry eight descriptors, and a socket eight such messages.** Past that `sendmsg` answers `ENOBUFS`. The cap exists because the kernel holds a reference to each one until it is received; a sender eight unreceived messages ahead of its receiver is not rescued by a ninth slot. -- **`ping` still takes a dotted quad**, even though names now work. It is a - first-party program that does not link musl's resolver, and `localhost` is - special-cased in it rather than resolved. Every *ported* program resolves - names properly; ours is the one that does not. -- **Nothing re-runs `resolvconf`.** The nameserver is written once at boot. A - lease that changes the DNS server, or a `/dev/pci` handover that brings up a - new interface, leaves a stale `/etc/resolv.conf` until somebody runs it again. -- **lwIP's own DNS client is still unused.** It is compiled in and only - `dns_getserver()` is ever called; resolution happens entirely in the process. - Nothing in the kernel can resolve a name, which matters the day something in - the kernel wants to. -- **`httpd` does not serve HTTP/1.1.** Every response is HTTP/1.0 with - `Connection: close`, because the directory listing is generated straight into - the socket and so has no `Content-Length` to give. One request per connection - is the cost. +- **Nothing re-runs `resolvconf`.** The nameserver is written once at boot + (`etc/schinit/resolvconf.job`). A lease that changes the DNS server, or a + `/dev/pci` handover that brings up a new interface, leaves a stale + `/etc/resolv.conf` until somebody runs it again. +- **lwIP's own DNS client is unused.** Resolution happens entirely in the process + (musl); nothing in the kernel can resolve a name. +- **`httpd` serves HTTP/1.0** with `Connection: close`, one request per connection, + and has no timeouts: a client that connects and says nothing ties up a child + process until it goes away, and `SO_RCVTIMEO` does not exist here. - **A queued-but-never-accepted connection is closed without clearing its callbacks.** `inet_close()` clears the argument and closes, which is enough - because the pcb is freed — but it is a narrower guarantee than the one the - main path makes, and it relies on `tcp_close()` succeeding. -- **`httpd` has no timeouts.** A client that connects and says nothing ties up - a child process until it goes away; `SO_RCVTIMEO` does not exist here (see - below), so there is nothing to set. -- **No routing table**, and no `/proc/net/route` to report one. With two - interfaces up, the first is the default route and the second is reachable only - by an address on its own subnet. That is a rule rather than an accident, and it - is not routing. The missing *file* is the more immediately visible half: it is - how a program asks which interface carries the default route, and without it - anything that filters on "the default one" sees nothing. -- **Netlink serves two message types.** No `RTM_GETROUTE`, no `RTM_NEWROUTE`, no - event stream, no multicast groups. A program that expects to *subscribe* to - link changes gets a socket that answers dumps and never speaks again. -- **No `getsockname`/`getpeername`**, and `accept()` does not report the peer's - address for an AF_INET socket. A server that wants to log who connected cannot. -- **`setsockopt` covers six options**, and refuses the rest with `ENOPROTOOPT` - rather than pretending: `SO_REUSEADDR`, `SO_KEEPALIVE`, `SO_BROADCAST` and - `TCP_NODELAY` reach lwIP's pcb; `SO_SNDBUF`/`SO_RCVBUF` are accepted and - report the real size on read. Notably absent: `SO_RCVTIMEO`/`SO_SNDTIMEO`. -- **`SOCK_DGRAM` over AF_UNIX does not exist** — stream only. -- **AF_UNIX names are not filesystem paths**, so nothing can `stat()` or - `unlink()` a socket. -- **One NIC driver exists.** The class is proven with two e1000 cards, not with - two different drivers. -- **No IPv6**, no DNS resolver in the kernel (lwIP's is compiled but unused by - anything), and no firewall or packet filter of any kind. + because the pcb is freed, but it is a narrower guarantee than the main path + makes and relies on `tcp_close()` succeeding. +- **No routing table**, and no `/proc/net/route`. With two interfaces up, the first + is the default route and the second is reachable only by an address on its own + subnet. That is a rule rather than an accident, and it is not routing. +- **Netlink is a responder**: `RTM_GETLINK` and `RTM_GETADDR` dumps (what + `getifaddrs` needs), `MSG_PEEK|MSG_TRUNC` sizing (GLib's network monitor measures + each datagram that way and spun at 100% when it answered 0), and nothing else. No + `RTM_GETROUTE`, no event stream, no multicast groups; a program that expects to + *subscribe* to link changes gets a socket that answers dumps and never speaks + again. +- **`setsockopt` applies what it understands** (`SO_REUSEADDR`, `SO_KEEPALIVE`, + `SO_BROADCAST`, `TCP_NODELAY` reach lwIP's pcb; `SO_SNDBUF`/`SO_RCVBUF` report the + real size; `SO_PASSCRED` on Unix sockets) and refuses the rest with `ENOPROTOOPT`. + Notably absent: `SO_RCVTIMEO`/`SO_SNDTIMEO`. +- **`SOCK_DGRAM` and `SOCK_SEQPACKET` over AF_UNIX** keep message boundaries (each message carries a + 16-byte header in the same ring) but have no unconnected form: `socketpair` and listeners work, + `sendto` a path does not. +- **Netlink `NETLINK_KOBJECT_UEVENT`** is the one multicast family: sockets bound to group 1 hear the + kernel's device events, group 2 what udevd re-announces, and a datagram can be addressed to a port id. +- **No IPv6**, no firewall or packet filter of any kind, and a TCP stack that has + been used for downloads at tens of megabits and no more than that. +- **Three drivers in the kernel, one Linux driver outside it**, and the class is + proven with two e1000 cards and an LDLS handover, not on a wide range of + hardware. The Realtek parts have never carried a packet on real silicon from this + tree. diff --git a/docs/architecture/packages.md b/docs/architecture/packages.md new file mode 100644 index 0000000..fc08924 --- /dev/null +++ b/docs/architecture/packages.md @@ -0,0 +1,102 @@ +# Packages: an apk root + +Alpine is musl with a different init, and SchOS is musl with a different kernel. +Alpine's binaries run on SchOS unmodified — its dynamic linker, its busybox, and +`apk` itself, fetching over TLS through SchOS's network stack. So instead of +porting programs one at a time, SchOS installs them. + +## The model + +The root filesystem is an apk-managed root: + +| Part | Where it comes from | +|---|---| +| musl, busybox, apk-tools, alpine-keys, CA certificates | Alpine's minirootfs (`ports/alpine`) | +| what `ports/alpine/world` lists (zsh, Xorg, Window Maker, htop, NetSurf …) | Alpine's packages, pinned in `world.lock` | +| schinit, its jobs, SchOS's tools, toybox, mksh, configuration | **schos-base**, an apk package the build makes from `sysroot/` | + +`apk add` installs anything from Alpine's main and community repositories. +`apk upgrade` updates Alpine's packages, and schos-base from the SchOS repository +at `/var/lib/schos/repo`. + +PID 1 is still schinit. Alpine's OpenRC is not installed, and packages that ship +OpenRC services install them where nothing reads them. + +## Who owns what + +Where Alpine and SchOS both have a program, Alpine's wins, and SchOS keeps only +what is its own (`tools/posix/mkaroot.sh` says so in its header): + +- `/bin/sh`, `cat`, `sed`, `find` and the rest are busybox. Alpine's package scripts + are written against busybox. +- zsh is Alpine's. SchOS keeps its `zshrc` and `zshenv` (`replaces=zsh`). +- `/etc/os-release` and `/etc/motd` are SchOS's (`replaces=`). +- `passwd`, `group` and `profile` are Alpine's. schos-base's post-install sets root's + shell to zsh, and SchOS's profile is `/etc/profile.d/schos.sh`. +- `pci.ids` and `usb.ids` are Alpine's (`hwdata-pci`, `hwdata-usb`): Xorg depends on + `hwdata-pci`, and two owners of one file is an error apk will not install past. +- mksh is `/bin/mksh`. schinit's recovery shell looks for it first because it is static: + a broken musl cannot take recovery down with it. + +## Building + + make qemu boots build/schfs.img, the root as a 1 GB SchFS disk + make iso the same root as the live system's compressed ramdisk + make alpine-lock re-resolve world against Alpine (needs the network) + +It is the only root; `sysroot/` is what schos-base is packaged from. Only +`alpine-lock` asks Alpine what is current. A normal build installs exactly what +`world.lock` names — files in `dist/alpine/`, fetched once and checked against the +lock's sha256, and against Alpine's signatures by apk — and never asks what is newer. +`world.lock` resolves the thirty-odd names in `world` into about 180 packages, every +dependency included, so every image after the first is reproducible. + +schos-base and its repository index are signed with a key made on the first build +(`build/apk-keys/`, never committed). The root trusts it beside Alpine's keys. The +install runs as an ordinary user with `--no-scripts`, because apk's scripts want a +chroot; `mkaroot.sh` does by hand the one change schos-base's post-install would +have made. + +## What it took from the kernel + +All of it found by running apk, and none of it specific to apk: + +- **TCP receive window:** it reopened before the reader drained, so lwIP refused data + and the sender waited on retransmission timers (5 KB/s). It reopens on read now, with a + 32-segment window; downloads run at tens of megabits. +- **`mmap` address reuse:** freed ranges were never reused, so musl's allocator used up the + 256 mapping records with 780 MB of RAM free. +- **`unlinkat` / `renameat`** returned a bare `-1`, read as `EPERM`, where apk expects + `ENOENT`; `renameat` could not replace its target. +- **`#!` scripts in `execve`**, and `ENOEXEC`: apk runs triggers directly. +- **`chroot`**, and mounting devtmpfs and proc more than once. +- Later, as bigger programs arrived: dynamic linking, threads and `futex`, demand paging, + pseudo-terminals, `inotify`, `flock`, Unix-socket credentials. Each is in + [kernel.md](kernel.md) or [syscalls.md](syscalls.md) because a package asked for it. + +## The live system + +The live ISO runs the apk root from RAM. The ramdisk is the compressed image +([scff.md](scff.md)); at boot the volume grows to half of the free memory (4 GB of root on +an 8 GB machine), backed by pages as they are written. Xorg and its dependencies are in the +image already; anything else installs into that space in about a minute, over the network, +and **is gone at reboot**. + +## What it does not do + +- **No installer.** Nothing is written to the machine's disks, so packages do not survive + a reboot. Keeping them needs an installer that writes the root to a SATA or NVMe disk, + and a kernel that finds its root there by the SchFS GPT type rather than as IDE disk 1. +- **Not every Alpine package runs.** The ones in `world` were chosen *because* they run: + terminal programs that draw and poll (htop, btop, tmux, mc), X clients, SDL 2 games, + NetSurf on GTK 3. Others hit whatever the kernel does not do yet; Epiphany (WebKit) is + one — its web-process child dies at start-up, and it has not been run to ground. + A package is one `apk add` away, and finding what it needs is how most of the kernel's + Linux surface got written. +- **Fortified libc is strict, and Alpine's packages are not always correct.** `lbreakout2` + hands `vsnprintf` a size larger than its buffer and traps on Alpine too; `world` carries + `lbreakouthd` instead. +- **apk's scripts and triggers do not run at build time** (`--no-scripts`), so a package + whose script matters has to have it done by hand in `mkaroot.sh`. +- **OpenRC services do nothing.** Anything that expects an init script to start it has to + be given a schinit job ([init.md](init.md)). diff --git a/docs/architecture/scheduler.md b/docs/architecture/scheduler.md index 5b1aecb..f48edb0 100644 --- a/docs/architecture/scheduler.md +++ b/docs/architecture/scheduler.md @@ -102,9 +102,8 @@ tracked. Two rules, with the two errnos POSIX gives them: alone. Without it nice is not a courtesy but a way for any process to outrank the rest of the system by asking. -Both are inert today, because everything runs as root. They are written now -because the alternative is growing a priority knob and a permission model in -separate commits, and the window between the two is exactly the bug. +Both bite now that there are users ([users.md](users.md)); root is exempt from +both. ## Deliberately not a red-black tree diff --git a/docs/architecture/syscalls.md b/docs/architecture/syscalls.md index 13fa504..427efd9 100644 --- a/docs/architecture/syscalls.md +++ b/docs/architecture/syscalls.md @@ -74,19 +74,19 @@ machine off, and Linux number 48 is the socket call. Two syscalls cannot share a ## What is implemented -Roughly 190 native numbers covering: +About 215 native numbers (193 of them with a Linux number) covering: | Area | | |---|---| -| Process | fork, exec, exit, wait/waitpid/wait4/waitid, clone, kill, sessions, process groups | +| Process | fork, exec, exit, exit_group, wait/waitpid/wait4/waitid, clone (processes and threads), futex, kill, tgkill, sessions, process groups | | Identity | the full real/effective/saved set for both uid and gid, groups, umask | -| Memory | mmap, munmap, mprotect, brk/sbrk, madvise, System V shared memory | +| Memory | mmap, munmap, mremap, mprotect, brk/sbrk, madvise, System V shared memory, memfd | | Filesystem | the ordinary set, the whole `*at()` family, statfs, xattr, mount/umount2 | | I/O | read/write, readv/writev, pread/pwrite, sendfile, poll/select/ppoll/pselect6, epoll (create, create1, ctl, wait, pwait), ioctl, fcntl | -| Sockets | socket, bind, listen, accept, connect, sendto, recvfrom, socketpair, setsockopt, getsockopt | +| Sockets | socket, bind, listen, accept/accept4, connect, sendto/recvfrom, sendmsg/recvmsg, socketpair, getsockname/getpeername, setsockopt/getsockopt, over AF_UNIX, AF_INET and AF_NETLINK | | Signals | rt_sigaction, rt_sigreturn, rt_sigsuspend, rt_sigpending, rt_sigtimedwait, sigaltstack | | Time | gettimeofday, clock_gettime, clock_getres, nanosleep, clock_nanosleep, alarm, timers | -| Notification | eventfd, eventfd2, timerfd_create/settime/gettime, signalfd, signalfd4 -- see `kernel/sys/sys_event.c` | +| Notification | eventfd, timerfd, signalfd, inotify -- see `kernel/sys/sys_event.c` | ### Argument fetching @@ -113,20 +113,19 @@ field by field, each through its own translation, or use `copyin`/`copyout`. ## What it does not do -- **`sendmsg`/`recvmsg` carry `SCM_RIGHTS` and nothing else.** - `SCM_CREDENTIALS` is refused rather than ignored. See - [networking.md](networking.md#descriptor-passing). -- **No `memfd_create`.** -- **No `getsockname` or `getpeername`**, and `accept()` does not report an - AF_INET peer's address. -- **`setsockopt` handles six options** and answers `ENOPROTOOPT` for everything - else. `SO_RCVTIMEO`/`SO_SNDTIMEO` are among the refused. -- **`clone` does not share an address space**, so it is `fork` with extra steps. +- **`sendmsg`/`recvmsg` carry `SCM_RIGHTS` and `SCM_CREDENTIALS`**, and nothing + else. Credentials are delivered only to a socket that asked with `SO_PASSCRED` + (D-Bus and WebKit do); other control messages are refused rather than ignored. + See [networking.md](networking.md#descriptor-passing). +- **`setsockopt` applies the options it understands** — they reach lwIP's pcb or + the Unix socket — and answers `ENOPROTOOPT` for everything else, never a + success that did nothing. The options that take a `struct` (`SO_RCVTIMEO`, + `SO_SNDTIMEO`) are among the refused. - **`sched_setaffinity` is accepted and ignored.** -- **`getrusage` does not distinguish `RUSAGE_CHILDREN`** from `RUSAGE_SELF`, and - fills in only the calling process's own tick count. -- **The ABI is chosen by link address.** It works, and it is a heuristic rather - than a fact about the binary. -- **`/proc//exe` is a regular file, not a symlink**, so `readlink()` on it - fails and `ps -o comm` prints `[init]` rather than `init`. Default `ps` and - `top` output is unaffected. +- **The ABI is chosen by link address.** Anything linked at `0x100000` or above + — musl's programs, and every dynamically linked one, whose interpreter is + placed at `0x40000000` — gets the Linux map; a first-party program linked at + `0x4000` gets the native one. It works, and it is a heuristic rather than a + fact about the binary. +- **`ptrace` and `seccomp`** are not implemented; a program that needs them + gets `ENOSYS`. diff --git a/docs/architecture/terminals.md b/docs/architecture/terminals.md index 1df3878..f3846bf 100644 --- a/docs/architecture/terminals.md +++ b/docs/architecture/terminals.md @@ -1,12 +1,13 @@ # Virtual terminals -Six of them, `/dev/tty1` through `/dev/tty6`. +Twelve of them, `/dev/tty1` through `/dev/tty12`, one per function key. | | | |---|---| -| **AltGr+F1 .. F6** | go to that terminal | -| **AltGr+-** / **AltGr+=** | previous / next terminal *in use* | +| **Ctrl+Alt+F1 .. F12** | go to that terminal — on the console and under X alike | +| **Alt+F1 .. F12** | the same, on the text console only (X keeps plain Alt) | +| **Ctrl+Alt+-** / **Ctrl+Alt+=** | previous / next terminal *in use* | | `chvt N` | the same, from a script or a serial console | The chord is settable — `chvt -c` — for the reasons below. Each one is an independent terminal: its own screen, its own @@ -14,8 +15,9 @@ cursor, its own line discipline, its own foreground process group and its own session. Two shells on two terminals are two unrelated processes that happen to share a keyboard and a screen. -A seventh is deliberately not bound. That is where a display server goes, -and it will claim switching with `VT_SETMODE` rather than a chord. +A display server takes one of them with `VT_SETMODE` and does its own +switching; X switches with Ctrl+Alt+F-key, which is why that is the default +here too. --- @@ -93,7 +95,9 @@ nothing to choose between. It records `proc->ctty` now, which is what |---|---|---| | `/dev/console` | 1 | the **active** terminal, so kernel messages follow a switch | | `/dev/tty` | 10 | the caller's **controlling** terminal, via `proc->ctty` | -| `/dev/tty1`..`tty6` | 11-16 | that terminal, whether or not it is on screen | +| `/dev/tty0` | | the active terminal, which is what Xorg and `openvt` ask for | +| `/dev/tty1`..`tty12` | 11-22 | that terminal, whether or not it is on screen | +| `/dev/ptmx`, `/dev/pts/N` | | pseudo-terminals, below | `/dev/tty` was an alias for `/dev/console` with the same major and minor. They are different questions and now have different answers. @@ -188,13 +192,13 @@ every candidate. |---|---| | **The host takes it, and the guest never sees it** | | | `Alt+F4`, `Alt+F2` | window manager | -| `Ctrl+Alt+F1..F6` | display manager — and it matches **loosely**, so adding Shift does not get past it | +| `Ctrl+Alt+F1..F12` | display manager — and it matches **loosely**, so adding Shift does not get past it | | `Ctrl+Alt+`digit | QEMU's own console tabs | | **Applications want it, and lose it permanently if the kernel eats it** | | | `Alt+`anything | Meta. zsh binds Meta-digit as `digit-argument`; so does every readline editor | | `Ctrl+`letter | control characters | | `Ctrl+Shift+`digit | types a character — `Ctrl+Shift+2` is `^@` | -| `F1..F6` alone | help, and whatever a full-screen program wants | +| `F1..F12` alone | help, and whatever a full-screen program wants | | **The keyboard layout wants it** | | | `AltGr+`letter | Polish diacritics — ą ć ę ł ń ó ś ź ż — and on some setups AltGr arrives as Ctrl+Alt | @@ -202,32 +206,33 @@ So the kernel ships a default and lets it be moved: ```sh chvt # report the active terminal and the current chord -chvt -c altgr+F # AltGr+F1..F6 (the default) -chvt -c ctrl+shift+F # Ctrl+Shift+F1..F6 -chvt -c alt+1 # Alt+1..6 +chvt -c ctrl+alt+F # Ctrl+Alt+F1..F12 (the default) +chvt -c altgr+F # AltGr+F1..F12 (the default until 2026-10) +chvt -c ctrl+shift+F # Ctrl+Shift+F1..F12 +chvt -c alt+1 # Alt+1..9 chvt -c off # no chord; chvt N is then the only way ``` `off` is a legitimate setting, not a failure mode. On a machine whose host grabs everything worth binding, `chvt N` from a shell is the honest answer. -The default is **AltGr+F1..F6**, and the `AltGr+`letter row above needs reading -carefully: what a Polish layout wants is AltGr with a **letter**. AltGr with a -**function key** produces nothing on any layout, no window manager binds it, -and no application asks for it. It is the one combination all three parties -leave alone. +The default is **Ctrl+Alt+F1..F12**. It was AltGr+F-key for a long time — +AltGr with a **function key** produces nothing on any layout, no window manager +binds it, and no application asks for it — but X owns the keyboard while it +runs and switches only with Ctrl+Alt+F-key, so the console and the desktop +answered different keys for the same thing. One chord everywhere wins. Inside +a VM whose host grabs Ctrl+Alt+F-key, `chvt -c altgr+F` brings the old one +back. That distinction is only expressible because Right Alt is tracked as its own modifier bit. It used to share `ALT`, which is harmless for typing — neither is used to look up a character — and made this impossible to say. -Verified rather than argued: with the default set, `AltGr+F2` switches, -`AltGr+F1` returns to a pixel-identical tty1, and `AltGr+a` still reaches the -shell, so ą is untouched. +AltGr+letter still reaches the shell untouched, so ą is unaffected either way. Whatever the modifiers are, they also bind **minus** and **equals** to the previous and next terminal. Those step only through terminals that are *in -use* — the same `opened` count `VT_GETSTATE` reports — because six terminals +use* — the same `opened` count `VT_GETSTATE` reports — because twelve terminals with two shells on them means four blank screens in between, and cycling past them is worse than not having the key. @@ -267,12 +272,10 @@ Three layers, in increasing order of how much a program is claiming: `VT_SETMODE` used to be accepted and ignored — a program could ask for the chord, be told yes, and still not get it. It works now. -What `VT_PROCESS` does **not** do yet is the release handshake: Linux signals -the owning process on every switch request and blocks until it answers -`VT_RELDISP`, which is how a display server releases the GPU before the screen -changes hands. Nothing here has anything to release, so the kernel simply -stops switching rather than negotiating. `struct vt_mode` already carries -`relsig`/`acqsig` for when Schcom needs it. +`VT_PROCESS` also does the release handshake Linux does: a switch away from a terminal in +that mode signals the owning process (`relsig`) and waits for `VT_RELDISP`, which is how a +display server stops drawing before the screen changes hands; switching back sends `acqsig`. +See *Handing a terminal to a program*, below. Input goes to whichever terminal is on screen. That is what makes a terminal a terminal: the keyboard follows the eyes. @@ -365,34 +368,56 @@ and erase characters — were added at the same time, because `TERM=linux` declares them and a terminal that drops them silently is the same class of bug waiting for a different editor. +## Handing a terminal to a program + +A display server takes a terminal with `VT_SETMODE(VT_PROCESS)` and `KDSETMODE(KD_GRAPHICS)`, +and from then on switching away from it is a **request**: the kernel sends the owner +`relsig` and waits for `VT_RELDISP`, so the server stops drawing and only then says +yes; switching back sends `acqsig` and the server redraws. A terminal in graphics mode +is not repainted by the kernel. If the owner dies, the terminal goes back to the kernel +whole. This is how Xorg runs on a terminal of its own, with Ctrl+Alt+F-key switching +between it and the text consoles and Ctrl+Alt+Backspace ending the session. + +## Pseudo-terminals + +`/dev/ptmx` makes a pair on every open and hands back the master; the slave is +`/dev/pts/N`, which the program on the other side gets as its stdin, stdout and +controlling terminal. Between them is a line discipline, the console's in miniature: +what the master writes is the slave's input (canonical editing, echo, `^C` as `SIGINT` +to the foreground group) and what the slave writes is read back by the master with NL +turned into CR NL. xterm, tmux, `script` and `sshd` all need exactly this. The pairs +share one lock; `kernel/dev/pty.c`. + +## The text cursor + +A steady inverted block — the cell with its colours swapped relative to what it would +show, so a cursor on reverse-video text is the plain text and every cursor on screen +is visible against its cell. `ESC[?25l` hides it, `ESC[?25h` shows it. It is drawn into +the grid's repaint and does not blink. + ## What it does not do - **No scrollback.** The grid is exactly the visible screen. Shift+PageUp does nothing because there is nothing above the top row to show. -- **No reflow on resize.** The geometry is read once at `vt_init()`. Nothing - resizes a framebuffer console today; a compositor will, and that is when this - has to grow. -- **No `VT_RELDISP` handshake.** `VT_PROCESS` stops the kernel switching, but - the kernel does not signal the owning process and wait for an answer. A - display server that needs to release the GPU *before* the screen changes - hands will need that, and `struct vt_mode` already has the fields. -- **No terminal past 6.** Linux allows 63. Six is what the chords reach and - what the grids are sized for. +- **No reflow on resize.** The geometry is read once at `vt_init()`; the console's + font is sized to the screen at that point, and a resolution change from `schmode` + repaints it, but text is not re-wrapped. +- **No terminal past 12.** Linux allows 63. Twelve is one per function key. - **Only 240x75 of any screen.** Larger framebuffers work; the grid is clamped and the remainder stays background. Raising `VT_MAXCOLS`/`VT_MAXROWS` is the fix, and costs BSS. - **No UTF-8 the font lacks.** The console draws through the TrueType - rasteriser now, so the limit is the font's `cmap` rather than a 256-entry - table — Latin, Latin Extended, Greek, Cyrillic and the common punctuation and - maths symbols all render. Anything genuinely absent still draws as `?`, which - is deliberate: drawing a *different* character would be worse. + rasteriser, so the limit is the font's `cmap` — Latin, Latin Extended, Greek, + Cyrillic and the common punctuation and maths symbols all render. Anything + genuinely absent still draws as `?`, which is deliberate: drawing a + *different* character would be worse. - **No shaping, and no wide characters.** One codepoint is one cell of one advance. Combining marks land on top of their own cell rather than the preceding one, and CJK draws at half the width it wants. Both need a width table and a cell that can span two columns. - **No dead keys**, so French and Spanish layouts are absent rather than - broken. See above. + broken. - **No per-terminal layout.** One keyboard, one layout. Switching to tty3 does not move anybody's fingers. -- **Only three layouts.** Adding one is a table in `keymap.c` and a line in the +- **Only three layouts** (US, Polish programmers', German). Adding one is a table in `keymap.c` and a line in the catalogue; the mechanism is not the limit, the typing is. diff --git a/docs/architecture/udev.md b/docs/architecture/udev.md new file mode 100644 index 0000000..de24757 --- /dev/null +++ b/docs/architecture/udev.md @@ -0,0 +1,138 @@ +# udev + +Alpine's `eudev` runs on SchOS as an ordinary process, started by `schinit` +(`etc/schinit/udevd.job`, with `udev-trigger.job` replaying the devices that +were there first). It is what lets Xorg *find* its keyboard and mouse instead of +being told, and the road to a Wayland compositor starts here: libinput and +libseat both ask udev what exists. + +The kernel's half is three pieces, all small, none of them specific to udev. + +## The device table: `kernel/dev/kdev.c` + +The devices the kernel implements itself — input event nodes, the memory +devices, terminals, the framebuffer, the disks — are listed once, with their +class, name, `/dev` name and parent. Everything below is generated from it. + +The device numbers are the ones `stat()` reports for the node, not SchOS's +internal ones: `/dev/tty1` is `4:1` to a Linux program (`linux_tty_rdev` in +`kernel/sys/sys_fs.c`), and udev compares what it reads in `/sys` with what +`stat()` says about the node. `dev_stat_numbers()` is the one mapping both use. + +## `/sys`: `kernel/fs/sysfs.c` + +``` +/sys/devices/pci0000:00/0000:00:1f.2/ vendor device class ... uevent subsystem -> ../../../bus/pci driver +/sys/devices/pci0000:00/0000:00:14.0/usb1/1-3 idVendor idProduct ... uevent subsystem -> ../../../../../bus/usb +/sys/bus/pci/devices/0000:00:1f.2 -> ../../../devices/pci0000:00/0000:00:1f.2 +/sys/bus/usb/devices/1-3 -> ../../../devices/pci0000:00/0000:00:14.0/usb1/1-3 +/sys/devices/virtual/input/input0/ name capabilities/{ev,key,rel,abs} properties +/sys/devices/virtual/input/input0/event0/ dev uevent subsystem -> ../../../../../class/input +/sys/class/input/event0 -> ../../devices/virtual/input/input0/event0 +/sys/dev/char/13:64 ... -> ../../devices/virtual/... +``` + +Symlinks are real (`T_SYMLINK` inodes whose target is generated). `uevent` is +readable (`DEVPATH=`, `SUBSYSTEM=`, `MAJOR=`, `MINOR=`, `DEVNAME=`) and +writable: `echo add > uevent` — which is what `udevadm trigger` does — makes the +kernel announce the device again. PCI and USB devices announce the same way: +`MODALIAS`, `PCI_ID`, `PCI_CLASS`, `PCI_SLOT_NAME` for a PCI device, `PRODUCT`, +`TYPE`, `BUSNUM`, `DEVNUM` for a USB one. + +**PCI devices** are nested under the PCI-to-PCI bridge they sit behind: a +device's parent is the bridge whose secondary..subordinate bus range holds its +bus (the innermost if several do), read from the bridge's config space, so an +xHCI behind a root port is `pci0000:00/0000:00:04.0/0000:01:00.0`. **USB +devices** sit under the xHCI controller that carries their bus, below that +bus's root hub `usbN`; the controller tells the USB core its own PCI address +when it comes up, so the link is recorded, not guessed. A hub's children are +its siblings, not its children. + +**USB interfaces** (`1-5:1.0`) are directories below their device, with +`bInterfaceClass`, `bInterfaceNumber`, a `driver` link when one is bound, and a +`uevent` carrying `DEVTYPE=usb_interface`, `INTERFACE=` and a `MODALIAS=usb:...` +that names the interface's class. They are announced after the drivers have +been offered the device, so the event says which interface was taken, and +removed before the device. Devices also carry what `lsusb -t` and libusb read: +`version`, `bMaxPacketSize0`, `bNumInterfaces`, `bmAttributes`, `bMaxPower`. + +**Hot-plug is real**: the USB core announces `add` when a device is attached +and `remove` as it detaches, and `usbN` is announced when its controller comes +up. With `udevadm monitor`, plugging in a mouse prints the kernel event and +then the same event after udev's rules have run. PCI has no hot-plug here. + +The input capability files are what `input_id` reads to decide +`ID_INPUT_KEYBOARD` or `ID_INPUT_MOUSE`. SchOS has two input devices whatever +the machine — every keyboard on `event0`, every mouse on `event1` +(`kernel/dev/input.c`) — so each has a fixed, plausible capability set. + +## `/dev`: `kernel/fs/devfs.c` + +devtmpfs is no longer read-only: udev creates `/dev/char/MAJ:MIN`, `/dev/fd`, +`/dev/input/by-*` and the like. A small in-memory overlay (192 entries) holds +directories and symlinks, listed after the kernel's own nodes in whichever +directory they are in. Device nodes themselves are still the kernel's: `mknod` +and regular files in `/dev` are refused. + +## The sockets: `kernel/net/unix.c` + +- **`AF_UNIX` `SOCK_DGRAM` and `SOCK_SEQPACKET`** keep message boundaries. + udevd's control socket is `SEQPACKET`, and its manager and workers talk over + a `DGRAM` socketpair. A message carries its sender's credentials with it + (several workers share one end), a zero-length datagram is a datagram, and a + datagram socket never reads end-of-stream. +- **`AF_NETLINK` / `NETLINK_KOBJECT_UEVENT`** sockets bind to multicast groups: + group 1 is the kernel's events, group 2 is what udevd re-announces once its + rules have run. A datagram can also be addressed to a port id — udevd hands + an event to an idle worker that way, and the worker believes it only from the + manager's port. The kernel's messages arrive as credentials with uid 0, pid 0. + A queue that is full drops the message, as Linux does, and `udevadm trigger` + is how a reader recovers. + +## libinput and libseat + +`libinput` (Alpine) reads the evdev nodes udev tagged and `xf86-input-libinput` +gives Xorg the same library; `libinput list-devices` as an ordinary user shows +`SchOS keyboard` and `SchOS pointer`. Xorg's `40-libinput.conf` now claims both, +and the log says `Using input driver 'libinput'`. + +`seatd` (job `etc/schinit/seatd.job`, socket `/run/seatd.sock`, group `video`) +and `libseat` let a program that is not root take devices from the system. The +client asks seatd for `seat0`; seatd takes the VT (`VT_SETMODE`, `KDSETMODE`, +`KDSKBMODE`, all there already), opens the device as root and passes the +descriptor back over the socket with `SCM_RIGHTS`. Checked as `schos` against +`/dev/input/event0` and `event1` with a small program linked to libseat, reading +the device's name through the descriptor it was handed. + +Getting there found two kernel bugs, both in descriptor passing: + +- **A socket returned by `accept()` was not an `AF_UNIX` socket as far as + `sendmsg` was concerned.** The end `connect()` queues for the listener was + allocated with its domain left at zero, and `sendmsg` refuses `SCM_RIGHTS` on + anything else. So a *server* could receive descriptors and never send one: + seatd, and equally a Wayland compositor handing a client a buffer. +- **A control buffer longer than its messages was an error.** seatd passes a + zeroed array with one descriptor at the front; Linux treats a following header + too short to be one as the end. `sendmsg` now does too. + +## What is not there + +- `/dev/bus/usb` nodes (usbfs). `lsusb -t` works because it reads sysfs; plain + `lsusb` goes through libusb, which also refuses to start while `uname -r` + says `0.1.0` (it wants Linux 2.6.32 or newer) and wants usbfs to open devices. +- A hub's port count (`maxchild` reads 0), and string descriptors + (`manufacturer`, `product`, `serial`). +- A USB keyboard works because every keyboard is `event0`; udev knows the + interface arrived but the input device does not hang below it. +- `hwdb` (`/etc/udev/hwdb.bin`): `udevadm hwdb --update` has not been run in the + image, so `60-input-id` logs "returned non-zero" for the hwdb import. The + properties that matter come from `input_id`. +- Block device partitions, `by-id` / `by-uuid` links, and a `/sys/block`. +- A compositor. libinput and libseat are in place, DRM is not; see [../plan/roadmap.md](../plan/roadmap.md). + +## Testing + +Boot the ISO and run `udevd --debug &`, then `udevadm trigger; udevadm settle`. +`/run/udev/data/c7:0` should contain +`ID_INPUT_KEYBOARD=1`, and `udevadm info /dev/input/event0` should print the +device. Under X, `/var/log/Xorg.0.log` says `config/udev: Adding input device`. diff --git a/docs/architecture/usb.md b/docs/architecture/usb.md index 4e2fb8a..29f55e6 100644 --- a/docs/architecture/usb.md +++ b/docs/architecture/usb.md @@ -21,13 +21,14 @@ The specification is public and the controller is not complicated. > BSD is much more valuable as the reference you check yourself against than as > the code you paste. -Licensing points the same way: the BSD stacks are BSD-2/3-clause and compatible, -Linux's drivers are GPL-2.0-only and are not — see -[../licensing.md](../licensing.md). +Licensing does not decide it: the BSD stacks are BSD-2/3-clause and compatible, and +since SchOS became GPL-2.0-or-later Linux's GPL-2.0-only drivers are compatible too +— see [../licensing.md](../licensing.md). The argument against porting is the +framework underneath, not the licence. ## What the spec gives you for free -Everything in [`include/dev/usb.h`](../../include/usb.h) comes from USB 2.0 +Everything in [`include/dev/usb.h`](../../include/dev/usb.h) comes from USB 2.0 chapter 9, which every device implements regardless of speed, class or vendor. **That is what makes enumeration possible at all:** before you know anything @@ -129,7 +130,8 @@ working one. filesystem's next read, by the rule in `blkdev_rw()`: a filesystem that went on with missing blocks would be guessing. - **HID is keyboards and pointers.** Consumer-control interfaces (volume keys), - game pads and the rest are declined. Keyboard LEDs are not driven. + game pads and the rest are declined. Caps, Num and Scroll Lock light their LEDs + on USB and PS/2 keyboards, one state shared between keyboards. - **USB 3 hubs** are not driven: the hub driver speaks the USB 2 hub protocol. - **No isochronous transfers**, so no audio and no cameras. - **No USB 3 SuperSpeed-specific features** beyond what enumeration needs — the diff --git a/docs/architecture/users.md b/docs/architecture/users.md new file mode 100644 index 0000000..c4c1c47 --- /dev/null +++ b/docs/architecture/users.md @@ -0,0 +1,79 @@ +# Users and permissions + +SchOS has accounts, a login prompt, and file permissions that the kernel +enforces. Until now every process was root and `uid` was bookkeeping; this is +the part that makes it a multi-user system. + +## What you meet + +- **`login` on all twelve terminals, tty1–tty12.** `etc/schos-getty` takes the + terminal back from whoever had it and runs `login`, which asks who is there + and starts their shell; when the shell exits `schinit` starts the prompt + again. Alt+F1…F12 (or Ctrl+Alt+F1…F12) switches between them. (There is no + longer a login-free root shell on tty6.) +- **One account in the image: `root`**, with an empty password, so `login` lets + it in without asking — it is a live image. `passwd` sets one. More users are + an `adduser` away; `wheel` is empty, so `su` and `doas` have no one to serve + until then. +- **`su` and `passwd`** (`cmd/su`, `cmd/passwd`) are setuid root and small + programs of their own, not toybox applets: toybox is one binary under every + name, and a setuid copy of it can be asked for any applet by naming it, as + root. `su` to root takes membership in `wheel`, whatever the password. +- **`doas`** (Alpine's, setuid) with `permit nopass :wheel` in `/etc/doas.conf`. + +## What the kernel enforces + +| | | +|---|---| +| open, create, unlink, rename, link | the usual owner/group/other bits, `w`+`x` on the directory, and the sticky bit (`/tmp`) | +| path walk | `x` on every directory searched | +| `exec` | `x` on the file; set-user-id and set-group-id make the new image run as its owner or group, and the saved ids follow | +| `chmod`, `chown`, `utimensat` | owner or root; a non-root `chown` drops the set-id bits | +| `access` / `faccessat` | by the *real* ids (`AT_EACCESS` for the effective ones) | +| `kill`, `tkill`, `tgkill` | root, or a target whose real or saved uid is one of yours. The kernel's own signals (`SIGPIPE`, the terminal's) are nobody's to refuse | +| `mount`, `umount`, `chroot`, `reboot`, `shutdown`, `setgroups`, `mknod` of a device | root | +| `setuid` family | root may set anything; anyone else only to an id they already hold | + +Root reads and writes everything and executes a file only if some execute bit +is set. A process's supplementary groups (`setgroups`) are consulted along with +its effective group. Ownership is stored by SchFS (it always had the fields); +FAT volumes belong to everyone, since they cannot say otherwise, and `/proc` +and `/sys` are root's. + +`inode_permission()` in `kernel/fs/fs.c` is the one check; the rest of this is +call sites. + +## Devices + +`/dev` is built by the kernel and each node has the owner, group and mode in the +`devnodes[]` table of `kernel/fs/devfs.c`: `null` and `zero` are 0666, +terminals `root:tty 0660`, `fb0` `root:video`, input `root:input`, raw disks +`root:disk`, `pci`, `dbg` and `netdev` root's alone. `chmod` and `chown` on a +node are kept (login claims its terminal this way, and udev sets modes) in an +override table applied whenever the inode is loaded. A new pseudo-terminal's +slave belongs to whoever opened `ptmx`. + +udev would set a node it has no rule for to 0600, which made `/dev/null` +root-only, so `etc/udev-schos.rules` (installed as `90-schos.rules`) names the +kernel's devices with the answers the kernel gave. + +## The image + +Everything in the image is owned by root: the build host's user means nothing +inside it. `mkschfs --owners FILE` takes the exceptions, one `path uid gid +[mode]` per line (`tools/posix/mkaroot.sh` writes it: the modes of `/tmp`, +`/var/tmp` and `/root`). The setuid bits are set +on `su`, `passwd` and `doas` in the root tree directly, because `apk` run as an +ordinary user may have dropped them. + +## Not done + +- **No PAM and no `/etc/securetty`.** `login` is busybox's and checks + `/etc/shadow` itself. +- **No capabilities.** Root is `euid == 0`, all of it. +- **No per-user `/run`, no logind.** X as a non-root user works (`startx` as `schos`, under + QEMU: Window Maker up) by group permission alone — video, input and tty — and + `/etc/X11/Xwrapper.config` lets anyone start it, because the wrapper's "console users" test + looks for a Linux VT on a standard descriptor that xinit's server child does not keep. +- **`/proc` and `/sys` hide nothing from other users.** +- **`mprotect` is still not enforced** ([kernel.md](kernel.md)). diff --git a/docs/branding/README.md b/docs/branding/README.md index 89b5de2..7e456e9 100644 --- a/docs/branding/README.md +++ b/docs/branding/README.md @@ -192,7 +192,7 @@ If either is edited, edit both. |---|---| | Loader splash | Shift, from `include/gfx/logo.h`, shaded per row by `metal()` in `boot/common/ui.c` — silver, not accent. | | Kernel console / early boot | Shift, same mask, composited flat in a theme colour by `gfx_draw_logo`. | -| A display with no compositor | Shift again, drawn by `vgpud` on `SCH_BG`. It replaced a test card — colour bands, gradients, a checkerboard and the driver's pid in binary — which was scaffolding for [../plan/hybrid.md](../plan/hybrid.md)'s H2 and H3 and outlived them. The mark answers the same question a black screen cannot, *something is driving this device*, and answers it as the system rather than as a test rig. | +| A display with no compositor | Shift again, drawn by `vgpud` on `SCH_BG`. It replaced a test card — colour bands, gradients, a checkerboard and the driver's pid in binary — which was scaffolding for [../architecture/devices.md](../architecture/devices.md)'s driver work and outlived them. The mark answers the same question a black screen cannot, *something is driving this device*, and answers it as the system rather than as a test rig. | | PSOGM | Shift, the same mask again, by `gfx_draw_logo()` from `kernel/core/panic.c`. At its native 128 px — there is no resampler in the crash path, and the mark was only ever drawn at one size there anyway. | | Serial crash report | `:(`, as text. The mark cannot be drawn in ASCII, and a mood marker in a log is not a mark. | | SchFS | **Nowhere yet.** `uberblock.svg` has no consumer; it is waiting for `projects/schfs-linux/` to have a README worth heading. | diff --git a/docs/guides/adding-a-driver.md b/docs/guides/adding-a-driver.md index a345c7a..8dd8c01 100644 --- a/docs/guides/adding-a-driver.md +++ b/docs/guides/adding-a-driver.md @@ -12,12 +12,20 @@ virtio, NVMe and the Intel datasheets are public documents with no licence on what you write from them. `kernel/drivers/bus/xhci/xhci.c` came from the spec, and so did `net/e1000/` and `gpu/virtio_gpu/`. -**BSD: yes. Linux: no.** This is a licence incompatibility rather than a -preference, and it decides the whole strategy — see -[../licensing.md](../licensing.md). BSD-2 and BSD-3 code can be used directly -with a `NOTICE` entry; GPL-2.0-only code can never be, at any distance. Reading -Linux to understand *hardware* is fine and normal; translating a Linux driver -function by function produces a derivative work whatever the identifiers say. +**Three sources, in the order to try them:** + +1. **A native driver, written from the specification.** The default, and what every driver in the + kernel is. +2. **Linux's own driver, unmodified, through LDLS** ([ldls.md](../architecture/ldls.md)), when the + device is one Linux supports and SchOS cannot afford to write a driver for — a NIC with a PHY + behind it, a GPU, a Wi-Fi card. It runs in a process, so a crash costs the interface and not the + machine. This is how the laptop's Realtek NIC is driven. +3. **BSD or Linux code taken into the tree.** Licensing no longer forbids either: SchOS is + GPL-2.0-or-later, so GPL-2.0-only code combines with it, and BSD-2/3 code can be used directly with + a `NOTICE` entry — see [../licensing.md](../licensing.md). The reason to prefer 1 and 2 is + maintenance, not law: a driver copied in is a driver that has to be tracked. + +Reading another kernel to understand *hardware* is fine and normal. One caveat about "port it from BSD" as a plan, learned by looking: FreeBSD's `sys/dev/usb` is roughly 100k lines welded to newbus, devclass, `bus_dma`, @@ -45,22 +53,29 @@ ours. kernel/drivers/ registry.c gpu/vga/ gpu/virtio_gpu/ - net/e1000/ + net/e1000/ net/rtl8139/ net/r8169/ + storage/ahci/ storage/nvme/ + audio/ac97.c bus/xhci/ usb/core/ usb/hid/ usb/storage/ usb/hub/ 2. Declare it in `kernel/drivers/registry.c` and add it to the array. 3. Add the object to `KERN_DRIVERS` in the `Makefile`. +3½. If nothing else in the kernel calls it, make it optional: a `DRV_` line in + `config/drivers.conf`, the name in `DRV_LIST` in the Makefile, the object behind + `$(filter y,$(DRV_))`, and an `#ifdef CONFIG_DRV_` around its line in `registry.c`. + A driver other code calls (`vga_fb`, the USB stack) cannot be optional. 4. Fill in the **class vtable** for what it is, and point `.ops` at it. That is the whole of what the rest of the kernel sees: | class | vtable | consumer | |---|---|---| - | `DEVCLASS_DISPLAY` | `struct display_ops` ([display.h](../../include/display.h)) | the compositor | - | `DEVCLASS_NET` | `struct netdev_ops` ([netdev.h](../../include/netdev.h)) | the TCP/IP stack | + | `DEVCLASS_DISPLAY` | `struct display_ops` ([display.h](../../include/gfx/display.h)) | none today — Xorg draws through `/dev/fb0` | + | `DEVCLASS_NET` | `struct netdev_ops` ([netdev.h](../../include/dev/netdev.h)) | the TCP/IP stack | + | `DEVCLASS_AUDIO` | `struct audio_ops` | `/dev/dsp` | A display driver that owns a linear framebuffer also publishes it with - `gfx_attach_scanout()` ([gfx.h](../../include/gfx.h)). That is what the text + `gfx_attach_scanout()` ([gfx.h](../../include/gfx/gfx.h)). That is what the text console and the crash screen draw on, and it is separate from the display class on purpose: the class is about *frames* — acquire, present, vblank — and the crash screen has to work when nothing else in the system does. @@ -102,7 +117,7 @@ That is data; the logic stays ours. - [ ] One directory under `kernel/drivers///` - [ ] Exports a `struct pci_driver` or `struct usb_driver` - [ ] Declared in `kernel/drivers/registry.c` **and** added to its array -- [ ] Object added to `KERN_DRIVERS` in the `Makefile` +- [ ] Object added to `KERN_DRIVERS` in the `Makefile`, and an option in `config/drivers.conf` if it can be left out - [ ] Class vtable filled in and `.ops` pointed at it - [ ] `.attached` implemented if it can drive more than one card - [ ] `devclass` set, if it is a USB driver diff --git a/docs/guides/building.md b/docs/guides/building.md index a4b3a5f..91ce833 100644 --- a/docs/guides/building.md +++ b/docs/guides/building.md @@ -8,8 +8,18 @@ change that only works on one of them is a bug. ```sh make # kernel + UEFI loader + root filesystem make qemu # build and boot it +make iso # a bootable ISO (the live system) for VMware or real hardware +./write.sh sdX # write that ISO to a USB stick (Linux; it checks what it is writing over) ``` +The first build downloads what it needs (musl, toybox, Alpine's packages -- a few +hundred megabytes, kept in `dist/`) and compiles Xorg's dependencies not at all: the +packages are Alpine's binaries, pinned in `ports/alpine/world.lock`. See +[packages](../architecture/packages.md). + +Which drivers go into the kernel is `config/drivers.conf` (all of the optional ones by default); +`make DRV_E1000=n` leaves one out for a build. See [devices](../architecture/devices.md). + ## Prerequisites | Need | Why | Windows | Linux | macOS | BSD | @@ -17,7 +27,7 @@ make qemu # build and boot it | `clang`, `ld.lld`, `llvm-objcopy`, `llvm-objdump` | cross-compiles the kernel, the 16/32-bit boot code, and musl userland | LLVM installer | `clang lld llvm` | `brew install llvm` | `pkg install llvm` | | `make` | — | Git Bash | `make` | Xcode CLT | `gmake` | | `perl` | generates the interrupt vector table | Git Bash | usually present | present | `pkg install perl5` | -| `python3` | boot logo, crash-screen QR | python.org (`python`) | `python3` | `brew install python` | `pkg install python3` | +| `python3` | boot logo, crash-screen QR, the numbers in the README | python.org (`python`) | `python3` | `brew install python` | `pkg install python3` | | `qemu-system-x86_64` | running and testing | `scoop install qemu` | `qemu-system-x86` | `brew install qemu` | `pkg install qemu` | | `curl`, `tar` | fetching ports | Git Bash | present | present | present | | `xorriso` **or** `mkisofs` | `make iso` only | `scoop install cdrtools` | `xorriso` | `brew install xorriso` | `pkg install xorriso` | @@ -81,10 +91,11 @@ produce. ## Ports -`musl`, `toybox` and `mksh` are built from source into `sysroot/`. **This does -not work on a Windows host** — `make` prints `ports: skipped` and uses whatever -is already staged in `sysroot/`. Building them needs a Linux host (or any other -Unix); do it once and the staged result serves the Windows builds. +`musl`, `toybox`, `mksh`, lwIP, uACPI, zstd, ncurses and the rest of `ports/` are built from source +into `sysroot/`; Alpine's packages are unpacked into the root from `dist/alpine/`. **This does not work +on a Windows host** -- `make` prints `ports: skipped` and uses whatever is already staged in +`sysroot/`. Building them needs a Linux host (or any other Unix); do it once and the staged result +serves the Windows builds. ## Where the tools live diff --git a/docs/guides/layout.md b/docs/guides/layout.md index d33f509..98758c0 100644 --- a/docs/guides/layout.md +++ b/docs/guides/layout.md @@ -14,47 +14,46 @@ deliberately kept out of `clean`). schos/ ├─ boot/ uefi/ the PE/COFF loader (splash, F8 menu, kernel load), │ common/ the UI it shares. UEFI only -- there is no BIOS path. -├─ kernel/ The kernel, by subsystem: arch/ core/ dev/ fs/ proc/ sys/ +├─ kernel/ The kernel, by subsystem: arch/ core/ dev/ drivers/ fs/ net/ +│ proc/ sys/ acpi/ (uACPI glue, the EC, I2C-HID) gmmos/ (the +│ micro OS under the crash screen) scff/ (the decompressor). +│ drivers/ is the in-kernel drivers, by class: audio bus gpu +│ net storage usb. +├─ ldls/ The Linux driver layer: the .ko loader, the BTF reader, the +│ Linux symbols reimplemented (kpi/), and host-side test tools. +│ The process that runs it is cmd/ldlsd. ├─ include/ Headers shared by kernel, userland, and tools/mkfs, by │ subsystem -- see "include/" below ├─ lib/ First-party userland runtime: _start glue, syscall stubs, │ the initcode blob embedded in the kernel, user.ld ├─ cmd/ First-party programs — one directory per program: │ cmd//main.c → sysroot/bin/ -│ schinit and schinitd are PID 1 and its supervisor; vgpud is -│ the virtio-gpu driver, which runs out here rather than in -│ the kernel; socktest/shmtest/nettest/pcitest are suites. -├─ ports/ Third-party software — one directory per port. -│ Contains ONLY a Makefile and optional patches/. -├─ projects/ Things that run ON SchOS rather than things that ARE it: -│ the compositor, window manager, desktop, and installer. +│ schinit and schinitd are PID 1 and its supervisor; vgpud and +│ ldlsd are drivers that run out here rather than in the +│ kernel; socktest/shmtest/nettest/pcitest/xready are suites. +├─ etc/ The configuration the image starts with: schinit's job +│ manifests (etc/schinit/*.job), motd, os-release, the zsh files. +├─ ports/ Third-party software -- one directory per port, containing +│ ONLY a Makefile and optional patches/. musl, toybox, mksh, +│ lwIP, uACPI, zstd, ncurses, the fonts, fastfetch, kilo; and +│ alpine/, which is the apk root (world, world.lock). +├─ projects/ Things that run ON SchOS rather than things that ARE it. │ Not part of the kernel build. See projects/README.md. -├─ tools/ Host-side helpers, SOURCE ONLY: mkfs.c, the SchFS test -│ suites, sign.pl, vectors.pl, and the PowerShell harnesses. -│ Their binaries build into build/tools/ -- this directory -│ used to hold both, which is the one rule this layout has. -│ tools/windows/loc.ps1 counts how much of the tree is first-party. -├─ sysroot/ Staging for the disk image. etc/ is hand-maintained; -│ bin/ lib/ sbin/ are populated by `make sysroot-install`. -├─ dist/ Upstream tarball cache. Survives `make clean`. -├─ Refrence/ A FreeBSD checkout (Refrence/freebsd-src), kept as reference -│ material and a parts bin. Never built, never in an image. -│ Read it when a specification is ambiguous; copy from it only -│ after the per-file licence check in docs/licensing.md. -│ A full clone is 2.1 GB and a previous NetBSD one was deleted -│ for that reason. Only sys/dev, sys/net and sys/netinet have -│ ever been read, so a sparse checkout is ~100 MB: -│ git clone --filter=blob:none --sparse Refrence/freebsd-src -│ git -C Refrence/freebsd-src sparse-checkout set │ sys/dev sys/net sys/netinet -├─ src-trunk/ A NetBSD checkout, when present -- reference material and a -│ parts bin. NOT built, NOT installed, must never reach an -│ image. Currently absent; it was deleted for disk space and -│ can be re-fetched sparsely. Copying from it is governed by -│ the per-file licence check in docs/licensing.md §3 -- -│ NetBSD is not uniformly licensed. +├─ tools/ Host-side helpers, SOURCE ONLY: mkfs.c, mkschfs.c, mkscff.c, +│ the SchFS test suites, vectors.pl, and the harnesses, grouped +│ by what runs them (windows/ .ps1, posix/ .sh, python/ .py). +│ Their binaries build into build/tools/. tools/windows/loc.ps1 +│ and tools/python/stats.py count how much of the tree is +│ first-party. +├─ sysroot/ Staging for the disk image, GENERATED by the build -- edit +│ tools/posix/mkaroot.sh and etc/, not this. +├─ dist/ Upstream tarball and package cache. Survives `make clean`. ├─ shell.nix NixOS development shell: the whole toolchain in one file +├─ write.sh Writes schos.iso to a USB stick, safely (Linux). +├─ run.ps1 The Windows-side QEMU launcher. ├─ docs/ architecture/ what it is plan/ what's next │ guides/ how to work history/ why it's like this +│ Website/ the site, and the docs reader that renders this └─ build/ ALL generated output (see below) ``` @@ -92,7 +91,10 @@ build/ ├─ kernel/ object files, the linked kernel, kernel.asm ├─ lib/ libc glue objects, initcode blob ├─ cmd/ linked first-party programs -├─ fs.img the filesystem image +├─ aroot/ the apk root the image is made from +├─ schfs.img the root as a disk image (make qemu) +├─ fs-ram.img the same root as the live ramdisk, fs-ram.scff compressed +├─ iso/ … what make iso assembles └─ ports// ├─ src/ extracted upstream tarball ├─ obj/ out-of-tree build directory @@ -130,57 +132,44 @@ Local modifications to upstream go in `ports/foo/patches/*.patch` and are applied after extraction — never by editing files under `build/`, which are wiped on every clean. -## Ramdisk root +## The live root ```sh -make # normal: root on the IDE disk (build/fs.img as hdb) -make ramroot # root from a SCHFS image linked into the kernel +make # normal: the root is a SchFS disk image, build/schfs.img +make iso # the same root as a compressed ramdisk linked into the kernel +make ramroot # the ramdisk, uncompressed ``` -`make ramroot` builds `build/fs-ram.img` (default 6000 blocks, override with -`RAMDISK_BLOCKS=`) and links it into the kernel, so the system boots to a shell -with **no data disk attached at all**. That is what makes destructive work on -the real disk survivable — reformatting it, or bringing up a new on-disk format -— and it is the first piece of the installer. +`make iso` builds the root as a SchFS image, compresses it +([SCFF](../architecture/scff.md)) and links it into the kernel, so the system boots +to a shell with **no data disk attached at all** -- which is what the USB stick is. +It is also what makes destructive work on a real disk survivable. Nothing written +to a live root survives a reboot. -Use `make ramroot`, not `make RAMROOT=1`: the flag changes `CFLAGS`, which make -cannot see, so objects compiled for one setting would be silently reused for -the other and `ROOTDEV` would point at the wrong device. The target drops the -compiled objects first. Switching back is the same — plain `make` after a -`ramroot` build reuses stale objects, so run `rm -rf build/kernel build/lib -build/cmd` first (or just `make clean`). - -The image costs boot time: the loader reads the kernel through the firmware's -file protocol, so a 6 MB ramroot kernel is visibly slower to start than the -350 KB default. +Use the targets, not `make RAMROOT=1`: the flag changes `CFLAGS`, which make cannot +see, so objects compiled for one setting would be silently reused for the other and +`ROOTDEV` would point at the wrong device. The targets drop the compiled objects +first. Going back to the disk root is the same -- run `make clean` or remove +`build/kernel build/lib build/cmd` first. **What may go in it is a licensing question, not only a size one.** The image is linked into the kernel binary, so anything in `sysroot/` when it is built becomes -part of a distributable artifact. A file staged there under a licence that -is not ours to redistribute does not merely ride along — it makes the kernel -itself undistributable. `RAMDISK_EXCLUDE` in the Makefile is the list of -paths kept out, and enforcing it in the build rather than relying on someone -remembering to unstage something first is deliberate. It has been needed once -already: a 33 MB test payload landed in `sysroot/` and both overflowed the image -and would have gone into the kernel binary. +part of a distributable artifact. A file staged there under a licence that is not +ours to redistribute does not merely ride along -- it makes the kernel itself +undistributable. `RAMDISK_EXCLUDE` in the Makefile is the list of paths kept out, +and enforcing it in the build rather than relying on someone remembering to unstage +something first is deliberate. (Linux modules for LDLS are staged by `make +ldls-stage`/`ldls-r8169` on request, and `.gitignore` refuses `*.ko`: binaries stay out of git. The +r8169 set is meant to be staged before `make iso`, since the ISO ships it.) -Three knobs, all in the Makefile, none of them `FSSIZE`: +The knobs: `RAMDISK_BLOCKS` (image size), `RAMDISK_NINODES` (the inode table is +*preallocated*: the default of 12000 once cost a quarter of a small image to +describe about 260 files), `RAMDISK_EXCLUDE`. If `mkfs` reports the image is full, +read its message: it prints the metadata breakdown and names the right knob. -| | | -|---|---| -| `RAMDISK_BLOCKS` | image size, 6000 (× 1 KiB `BSIZE`) | -| `RAMDISK_NINODES` | inode table size, 2000 | -| `RAMDISK_EXCLUDE` | sysroot-relative paths to omit, with their subtrees | - -`RAMDISK_NINODES` is worth knowing about because the inode table is -*preallocated*: mkfs's default of 12000 inodes cost 1501 of the 6000 blocks — a -quarter of the image — to describe about 260 files. At 2000 it costs 251, and -metadata overall fell from 1596 blocks to 345. - -If `mkfs` reports the image is full, read its message: it now prints the -metadata breakdown and names the right knob for whichever image overflowed. It -used to say only "increase FSSIZE in param.h", which is a constant that has -nothing to do with the ramdisk root and sends you to the wrong file. +The ISO build is fast on purpose (a few seconds when little changed): it copies with +`cp -p` and `install -C` so unchanged files keep their time stamps, and toggling the +ramdisk does not wipe the tree. ## Two things that will bite you diff --git a/docs/guides/testing.md b/docs/guides/testing.md index 8b0f6b8..dbd9579 100644 --- a/docs/guides/testing.md +++ b/docs/guides/testing.md @@ -12,9 +12,9 @@ reporting that it started. | | What it runs | What it proves | |---|---|---| -| `make schfs-check` | Host-native unit suites | The filesystem's logic, in about a second | +| `make schfs-check`, `make kmem-test`, `ldls-inspect`/`ldls-run` | Host-native unit suites and the Linux-module loader run on the build machine | The filesystem's logic, the slab allocator's bookkeeping, and a `.ko`'s layout, in about a second | | `tools/windows/bootcheck.ps1` | A real boot, driven over the serial console | That the system answers correctly | -| `make crashqr`, `make schcom-test` | A boot, then pixel assertions from the host | That the *screen* is right | +| `make crashqr` | A boot, then pixel assertions from the host | That the *screen* is right | The levels exist because each catches what the one below cannot. A unit test cannot tell you the kernel fails to boot; a boot test cannot tell you the screen @@ -77,21 +77,31 @@ dropped every byte would pass any check made from one side alone. Under QEMU's user-mode networking the host is `10.0.2.2` seen from inside, and SLIRP connects to the host's loopback — so the listener binds there. +## Test programs that run on SchOS + +Several first-party programs in `cmd/` exist to be run **on** the system and print which of their checks +failed, because only the system can say whether its own interfaces behave. `bootcheck` runs the ones it +can; the rest are run by hand or by a script: + +| | Checks | +|---|---| +| `socktest`, `nettest`, `netecho` | Unix and INET sockets, `SCM_RIGHTS` and credentials, `setsockopt`'s refusals | +| `shmtest`, `memfdtest` | System V shared memory; `memfd` mapped by two processes, and one sent over a socket | +| `pcitest` | `/dev/pci`: claim, map, DMA, interrupts, release | +| `evtest`, `locktest`, `sigtest` | the event descriptors; `flock` and record locks; signals | +| `dynhello` | dynamic linking: TLS, the heap, a forked child that still has its libraries | +| `xready` | every kernel call Xorg makes, before Xorg is started — the evdev queries, the fbdev ioctls, the console calls, the `/sys` files | +| `tools/python/ldls_check.py` | what LDLS needs from a kernel's BTF | + ## Pixel assertions ```sh make crashshot && make crashqr # crash the kernel, decode the QR out of the framebuffer -make schcom-test # boot, run the compositor and a client, check the pixels ``` `tools/windows/splashshot.ps1` counts colours. **That exists because a log-only harness once passed on a blank screen.** -`schcom-test` asserts **from the host** — the compositor's own report that it -composited something proves nothing. It runs two overlapping clients and checks -that the newer one wins in the overlap, because a compositor that cannot order -two surfaces cannot host a window manager. - Other capture tools, all in `tools/windows/`: `dispshot`, `fbshot`, `splashshot`, `textshot`, `ttyshot`. @@ -148,10 +158,12 @@ leak. - **No automated test runs on real hardware.** Everything above is QEMU. - **SMP is exercised but not stressed** — boots on 1, 2 and 4 processors are checked; nothing hunts for races. +- **Nothing asserts the desktop by pixel.** `xready` checks the calls Xorg makes; whether Window Maker + and an `xterm` appear on a real screen is looked at in screenshots, not asserted. - **No fuzzing anywhere**, and no `fsx`/`xfstests` against SchFS. Getting SchFS under those is the main argument for the FUSE implementation in [../plan/roadmap.md](../plan/roadmap.md). - **The SchFS round-trip test needs symlinks and hard links**, so it skips on a Git Bash host and only runs on Unix. -- **`make ports` does not work on Windows.** musl, toybox and mksh must be built +- **`make ports` does not work on Windows.** musl, toybox, mksh and the rest must be built once on a Unix host and staged. diff --git a/docs/history/README.md b/docs/history/README.md index e20176b..904dfbb 100644 --- a/docs/history/README.md +++ b/docs/history/README.md @@ -5,7 +5,7 @@ and the few write-ups that have nowhere better to live. | | | |---|---| -| [kernel.md](kernel.md) | The expensive ones. Boot, SMP, signals, ABI assumptions, `/proc`, input. | +| [kernel.md](kernel.md) | The expensive ones. Boot, SMP, signals, ABI assumptions, `/proc`, input, TLBs, a stub that returned a flag word. | | [schfs.md](schfs.md) | How the filesystem was built, and what each phase cost. | | [moving-to-linux.md](moving-to-linux.md) | The Windows-to-Fedora move. | diff --git a/docs/history/kernel.md b/docs/history/kernel.md index ff72a21..dc35a20 100644 --- a/docs/history/kernel.md +++ b/docs/history/kernel.md @@ -298,17 +298,8 @@ exported entry points, `nameifrom()`/`nameiparentfrom()`. `sys_fs.c` gained Every `*at()` syscall now uses these instead of its own inline absolute-path/`AT_FDCWD`-only check. -**Known follow-on issue, not yet root-caused**: heavy `ls`/`ls -lR` use -across many commands in one long-running shell session can eventually make -a *later*, unrelated directory hang (reproduced with `ls -lR /var` after -~10 prior commands in the same session; the identical command succeeds -immediately on a fresh boot). Not an inode-table or file-table exhaustion -— both `iget()` and `filealloc()` fail cleanly (panic / return 0 -respectively) when full, and neither fired. Smells like a lock or -reference-count leak somewhere in the *at() paths above, or in toybox's -own fd handling, that only manifests after enough accumulated opens/closes. -Next step: instrument `itable`/`ftable` occupancy counts and watch them -across a long `ls`-heavy session to see what's actually climbing. +**Known follow-on issue** (heavy `ls -lR` in one long shell session eventually hanging a +*later* directory): hunted, and not reproducible — see section 24. ## 6. Boot speed @@ -1255,3 +1246,86 @@ is at least as long as the width, and that the aperture holds that many rows. If not, it goes back to the previous mode and says so in the menu. This proves nothing about a mode that passes, but it stops a lying one from reaching the kernel. + +--- + +## 24. The `ls -lR` hang that was hunted and not found + +**Symptom.** After about ten `ls`-heavy commands in one long shell session, a later and +unrelated `ls -lR /var` would hang; the same command succeeded at once on a fresh boot. It +was not inode-table or file-table exhaustion (both fail cleanly), and the first hypothesis +was a lock or reference-count leak in the `*at()` paths. + +**What was done.** `/proc/fsstat` was given the occupancy counters the hypothesis asked +for (inode, file and buffer peaks, the SchFS volume lock and transaction owner), and the +workload was made heavier than the report: ten rounds of `ls -lR /bin` interleaved with +writes and `cd` — 33 execs and 13,378 tree descents. Nothing climbed: + + inode_peak 9 / 200 file_peak 9 / 512 + buf_pinned_peak 1 / 256 schfs0 locked 0 tx_owner_pid 0 tx_depth 0 + +**Conclusion.** The hypothesis is eliminated, and the bug is very likely gone — fixed in passing +by the `*at()` dirfd rewrite, the `getdents` cookie rewrite, or SchFS becoming the root, all of +which landed after it was reported. It was closed rather than chased. The instrumentation +stayed: if it happens again, `/proc/fsstat` names the table, or shows `locked 1` with +`tx_owner_pid 0`, which is an orphaned volume lock. + +Two real defects turned up on the way and were fixed, neither proven to be the cause: +`holdingsleep()` never compared the owning pid, which disarmed all seven `panic()` assertions +built on it; and SchFS's `tx_mine()` compared a bare `struct proc *`, which a recycled ptable +slot makes ambiguous. + +**The transferable part.** A hang you cannot reproduce still earns its instrumentation. The +absence of a result *is* the result, and the right place to leave it is where the next person +with the same symptom will look. + +## 25. Epiphany's heap was corrupted by a page nobody had unmapped yet + +**Symptom.** Epiphany (WebKit: forty threads on four processors) died at a different address +on every run; musl's allocator was the first to notice, a group's metadata no longer pointing +back at its group. + +**Diagnosis.** Nothing was freed twice. One thread unmapped a range, `mmap` handed the same +addresses straight back, and a thread on *another* processor went on reading and writing the +**old** pages through its TLB: stores vanished, loads were stale. Deferring the free of the +unmapped pages had already kept a stale translation from reaching a page someone else owned; +it did not keep one address space *coherent*. + +**Fix.** Unmapping from a shared address space interrupts the processors running it and waits +until each has reloaded `CR3` (`tlb_shootdown()`, `kernel/arch/x86_64/vm.c`). + +**The transferable part.** Threads turned an invisible assumption — one CPU's TLB is the only +one that matters — into the loudest bug in the tree, in the one program that used the most +threads. Memory corruption that moves every run is a coherence bug until proven otherwise. + +## 26. Every MDIO read returned `0x02460246` + +**Symptom.** Linux's `r8169` under LDLS, on the laptop, read its PHY's ID as `0x02460246`. + +**Diagnosis.** `0x246` is a value of `RFLAGS`, not a register. `mdio-bus` keeps the word it +has just read in `r11` across `preempt_enable()`, which on x86 is `decl %gs:__preempt_count; je` +to a call that preserves every register. LDLS had given `__preempt_count` zeroes, so the count +reached zero after the decrement and the call was made — into a logging stub whose `syscall` +instruction leaves `RFLAGS` in `r11`. + +**Fix.** `__preempt_count` starts at `PREEMPT_ENABLED` (x86 keeps `NEED_RESCHED` inverted), and +`preempt_schedule` and its notrace twin are a bare `ret`. + +**The transferable part.** A stub that returns zero is not safe when zero changes which path +runs — the same family as the five in [../architecture/ldls.md](../architecture/ldls.md). The +tell here was a value that was a plausible *flag word* rather than a plausible *register*. + +## 27. GLib's network monitor spun at 100% + +**Symptom.** Epiphany's main thread consumed a full core in `poll()` returning at once. + +**Diagnosis.** GLib sizes each netlink datagram with `recvmsg(MSG_PEEK|MSG_TRUNC)` and no buffer. +The kernel answered 0, GLib read 0 bytes, the datagram stayed queued, and `poll()` said readable +again, for ever. A drained netlink socket also reported hangup, because `sock_hup` took "no peer" +for "closed". + +**Fix.** `recvmsg` on netlink peeks without consuming and with `MSG_TRUNC` returns what is waiting. + +**The transferable part.** A kernel that accepts a flag and does nothing with it turns a +well-formed program into a busy loop. Do it, or refuse it. + diff --git a/docs/licensing.md b/docs/licensing.md index 0a3fff9..25a1dc0 100644 --- a/docs/licensing.md +++ b/docs/licensing.md @@ -267,42 +267,33 @@ copied file is a file to be understood, attributed and maintained forever. --- -## 6. LDLS, and the distribution boundary +## 6. LDLS, and shipping Linux's drivers -[architecture/ldls.md](architecture/ldls.md) runs Linux drivers. This section -was written when SchOS was Apache-2.0 and the boundary below was a legal -necessity. Under GPL-2.0-or-later it is a **project policy** instead: shipping -a GPL-2.0-only `.ko` with SchOS would now be lawful, provided the source goes -with it. It is still not done, because a module we never ship is a module we -never have to vouch for, track upstream, or provide source for. +[architecture/ldls.md](architecture/ldls.md) runs Linux drivers. This section used to be about a +distribution boundary: written when SchOS was Apache-2.0, it said a GPL-2.0-only `.ko` could not be +shipped and so SchOS would ship the layer and never the modules. **That no longer holds.** SchOS is +GPL-2.0-or-later, GPL-2.0-only code combines with it, and the owner has decided that Linux code and +firmware blobs are shipped. The ISO carries Linux's **r8169 set** — `mdio-bus`, `libphy`, +`mdio_devres`, the Realtek PHY driver and `r8169`, unmodified — and the RTL8168 PHY firmware. + +### What shipping them asks of us + +- **The licence stays with the code.** The modules are GPL-2.0-only, the firmware is Realtek's, redistributed + under linux-firmware's terms. `NOTICE` lists both, with where they come from. +- **The source goes with the binary** (GPLv2 §3). The modules are stock builds of the Linux kernel version + that `make ldls-r8169` took them from, so the corresponding source is that release of Linux, unmodified. +- **Nothing combines at link time.** The modules are loaded into an ordinary process by our loader; our code + is GPL-2.0-or-later, theirs GPL-2.0-only, and both are GPLv2-compatible whichever way one reads "combined". +- **No binary in git.** The repository stays source-only: `.gitignore` refuses `*.ko`, and `make ldls-r8169` + stages the set from a Linux build into the image. ### LDLS is ours -Every file under `ldls/` is ours, GPL-2.0-or-later. The loader was written from -the ELF-64 gABI and the x86-64 psABI; the shim is written under the rule in -[architecture/ldls.md](architecture/ldls.md). Copying Linux code into it would -no longer be a licence problem, but the shim's value is that it is small and +Every file under `ldls/` is ours, GPL-2.0-or-later. The loader was written from the ELF-64 gABI and the +x86-64 psABI; the shim is written under the rule in [architecture/ldls.md](architecture/ldls.md). Copying +Linux code into it would no longer be a licence problem, but the shim's value is that it is small and understood, so the existing rule stands. -### The combination is the user's - -| | licence | who distributes it | -|---|---|---| -| `ldls/` — loader, shim, host | **GPL-2.0-or-later** | us | -| a `.ko` | GPL-2.0-only, usually | the user's distribution | -| the two running together in a process | — | **nobody** | - -GPLv2 §0 is explicit about where its obligations attach: - -> Activities other than copying, distribution and modification are not covered -> by this License; they are outside its scope. **The act of running the Program -> is not restricted.** - -So: we ship LDLS and no modules. The user obtains a module from wherever they -get their software and runs it on their own machine. `RAMDISK_EXCLUDE` and the -`*.ko` rule in `.gitignore` keep that boundary mechanical rather than -intentional. - ### `EXPORT_SYMBOL_GPL` does not apply Linux marks much of its internal API `EXPORT_SYMBOL_GPL`, and modules carry @@ -322,7 +313,7 @@ show. ### BTF: the layouts are not in our source at all -§3 of the plan has one uncomfortable row — struct layouts. A compiled driver +Rule 3 of [architecture/ldls.md](architecture/ldls.md) has one uncomfortable row — struct layouts. A compiled driver indexes fields by offset, so the shim has to reproduce them exactly, and a field-for-field match *looks* like transcription even when it is not. diff --git a/docs/niveus/dist/niveus.css b/docs/niveus/dist/niveus.css new file mode 100644 index 0000000..c446fee --- /dev/null +++ b/docs/niveus/dist/niveus.css @@ -0,0 +1,3705 @@ +/* Niveus — generated bundle. Do not edit. + Source: niveus/niveus.css and the files it imports. + Rebuild: python3 tools/build.py +*/ + +/* ========================================================================== + Niveus — a calm, adaptive interface language. + Made by Nexoniarz. + -------------------------------------------------------------------------- + The single entry point. An application links this file and nothing else: + + + — optional, see below + — or omit data-theme to follow the system + + niveus.js is not required for anything to render. It exists only so state + changes can be *continuous*: it wraps an update in a view transition so the + browser can animate between the two states. Without it, every component + still works — changes simply arrive rather than travel. + + Everything ships inside the `niveus` cascade layer, so unlayered application + styles always win without needing !important or selector escalation. The + sub-layers are ordered below; that statement is what fixes their precedence, + independently of the order the files happen to load in. + ========================================================================== */ + +@layer niveus.tokens, niveus.theme, niveus.base, niveus.continuity, + niveus.components, niveus.utilities; + +/* --- Tokens: the values, appearance-agnostic ------------------------------- */ +@layer niveus.tokens { + /* ========================================================================== + Niveus — Color + -------------------------------------------------------------------------- + Two levels of tokens live here: + + 1. Palette Raw, appearance-agnostic values. Never referenced by + components. Prefixed --nv-palette-*. + + 2. Semantic The roles components actually consume (background, surface, + text-primary, accent, danger, …). Each role resolves with + light-dark(), pulling its value from the matching appearance + file: themes/light.css (--nv-light-*) or themes/dark.css + (--nv-dark-*). + + The appearance is chosen by `color-scheme`, which themes/*.css bind to the + [data-theme] attribute. A component therefore never knows — and must never + ask — which appearance is active. + ========================================================================== */ + + /* ========================================================================== + Brand + -------------------------------------------------------------------------- + The whole color system has one input. Set --nv-brand and the accent, its + hover and active states, the text that lands on it, the focus ring, the + selection highlight, the tint in the greys and the lean of the status colors + are all re-derived from it — in both appearances, with no second value to + keep in sync. + + :root { --nv-brand: #d92e3f; } + + Set it on any element with [data-nv-brand] to re-brand a subtree. + ========================================================================== */ + + :root { + --nv-brand: #4f6cff; + + /* How much of the brand bleeds into the greys. Enough that the neutrals + belong to the same family as the accent; not enough to read as colored. + 0% gives strictly neutral greys. */ + --nv-brand-tint: 4%; + + /* How far the status colors lean towards the brand. Red still reads as + danger and green still reads as success — they just stop looking borrowed + from another system. 0% keeps them literal. */ + --nv-brand-harmony: 12%; + + /* Where "too mid-toned to carry a label" begins, and how far either side a + color is pushed to escape it. Raising the pivot sends more brands dark + (white labels); lowering it sends more of them light (dark labels). */ + --nv-contrast-pivot: 0.7; + --nv-contrast-dark: 0.53; + --nv-contrast-light: 0.78; + } + + :root { + /* --- Neutral ---------------------------------------------------------- */ + --nv-palette-neutral-0: #ffffff; + --nv-palette-neutral-25: #fbfcfe; + --nv-palette-neutral-50: #f7f8fb; + --nv-palette-neutral-100: #f1f3f8; + --nv-palette-neutral-150: #e8ebf2; + --nv-palette-neutral-200: #dfe3ec; + --nv-palette-neutral-300: #ccd2de; + --nv-palette-neutral-400: #a7b0c0; + --nv-palette-neutral-500: #7c8797; + --nv-palette-neutral-600: #5b6574; + --nv-palette-neutral-700: #434b59; + --nv-palette-neutral-800: #2d3340; + --nv-palette-neutral-850: #232936; + --nv-palette-neutral-900: #1a1f29; + --nv-palette-neutral-925: #151a22; + --nv-palette-neutral-950: #10141b; + --nv-palette-neutral-975: #0b0e14; + --nv-palette-neutral-1000: #06080c; + + /* --- Accent (indigo) --------------------------------------------------- */ + --nv-palette-accent-50: #eef2ff; + --nv-palette-accent-100: #dfe6ff; + --nv-palette-accent-200: #c4d0ff; + --nv-palette-accent-300: #9fb3ff; + --nv-palette-accent-400: #7691ff; + --nv-palette-accent-500: #4f6cff; + --nv-palette-accent-600: #3f5cf0; + --nv-palette-accent-700: #3450d6; + --nv-palette-accent-800: #2a3fa8; + --nv-palette-accent-900: #22347f; + + /* --- Success (green) --------------------------------------------------- */ + --nv-palette-success-50: #e8f9ef; + --nv-palette-success-100: #ccf0dc; + --nv-palette-success-200: #9ee2bc; + --nv-palette-success-300: #62cd96; + --nv-palette-success-400: #33b478; + --nv-palette-success-500: #189a60; + --nv-palette-success-600: #0f7d4d; + --nv-palette-success-700: #0c6440; + --nv-palette-success-800: #0a5034; + --nv-palette-success-900: #08412b; + + /* --- Warning (amber) --------------------------------------------------- */ + --nv-palette-warning-50: #fff6e6; + --nv-palette-warning-100: #ffe9c2; + --nv-palette-warning-200: #ffd68d; + --nv-palette-warning-300: #fbbe52; + --nv-palette-warning-400: #eda520; + --nv-palette-warning-500: #cf8809; + --nv-palette-warning-600: #a96d05; + --nv-palette-warning-700: #855505; + --nv-palette-warning-800: #6a4406; + --nv-palette-warning-900: #563806; + + /* --- Danger (red) ------------------------------------------------------ */ + --nv-palette-danger-50: #ffeff1; + --nv-palette-danger-100: #ffdbde; + --nv-palette-danger-200: #ffb9bf; + --nv-palette-danger-300: #ff8b96; + --nv-palette-danger-400: #f95b6c; + --nv-palette-danger-500: #e2334a; + --nv-palette-danger-600: #c01f36; + --nv-palette-danger-700: #9c172b; + --nv-palette-danger-800: #7e1524; + --nv-palette-danger-900: #67141f; + } + + /* ========================================================================== + Semantic roles + ========================================================================== */ + + /* The subtree selector has to be repeated here, not just on the theme files: + var() inside a custom property is substituted where that property is + *declared*, so a --nv-color-* declared only on :root would bake in the root + brand and inherit the result unchanged. Re-declaring on [data-nv-brand] + re-runs the substitution against the subtree's own brand. */ + :root, + [data-nv-brand] { + /* --- Canvas and surfaces ----------------------------------------------- */ + --nv-color-background: light-dark(var(--nv-light-background), var(--nv-dark-background)); + --nv-color-background-subtle: light-dark(var(--nv-light-background-subtle), var(--nv-dark-background-subtle)); + --nv-color-surface: light-dark(var(--nv-light-surface), var(--nv-dark-surface)); + --nv-color-surface-elevated: light-dark(var(--nv-light-surface-elevated), var(--nv-dark-surface-elevated)); + --nv-color-surface-inset: light-dark(var(--nv-light-surface-inset), var(--nv-dark-surface-inset)); + --nv-color-surface-hover: light-dark(var(--nv-light-surface-hover), var(--nv-dark-surface-hover)); + --nv-color-surface-active: light-dark(var(--nv-light-surface-active), var(--nv-dark-surface-active)); + --nv-color-scrim: light-dark(var(--nv-light-scrim), var(--nv-dark-scrim)); + + /* --- Text --------------------------------------------------------------- */ + --nv-color-text-primary: light-dark(var(--nv-light-text-primary), var(--nv-dark-text-primary)); + --nv-color-text-secondary: light-dark(var(--nv-light-text-secondary), var(--nv-dark-text-secondary)); + --nv-color-text-tertiary: light-dark(var(--nv-light-text-tertiary), var(--nv-dark-text-tertiary)); + --nv-color-text-disabled: light-dark(var(--nv-light-text-disabled), var(--nv-dark-text-disabled)); + --nv-color-text-inverted: light-dark(var(--nv-light-text-inverted), var(--nv-dark-text-inverted)); + + /* --- Borders ------------------------------------------------------------ */ + --nv-color-border-subtle: light-dark(var(--nv-light-border-subtle), var(--nv-dark-border-subtle)); + --nv-color-border: light-dark(var(--nv-light-border), var(--nv-dark-border)); + --nv-color-border-strong: light-dark(var(--nv-light-border-strong), var(--nv-dark-border-strong)); + + /* --- Accent ------------------------------------------------------------- */ + --nv-color-accent: light-dark(var(--nv-light-accent), var(--nv-dark-accent)); + --nv-color-accent-hover: light-dark(var(--nv-light-accent-hover), var(--nv-dark-accent-hover)); + --nv-color-accent-active: light-dark(var(--nv-light-accent-active), var(--nv-dark-accent-active)); + --nv-color-accent-subtle: light-dark(var(--nv-light-accent-subtle), var(--nv-dark-accent-subtle)); + --nv-color-accent-subtle-hover: light-dark(var(--nv-light-accent-subtle-hover), var(--nv-dark-accent-subtle-hover)); + --nv-color-accent-border: light-dark(var(--nv-light-accent-border), var(--nv-dark-accent-border)); + --nv-color-accent-text: light-dark(var(--nv-light-accent-text), var(--nv-dark-accent-text)); + --nv-color-on-accent: light-dark(var(--nv-light-on-accent), var(--nv-dark-on-accent)); + + /* --- Success ------------------------------------------------------------ */ + --nv-color-success: light-dark(var(--nv-light-success), var(--nv-dark-success)); + --nv-color-success-hover: light-dark(var(--nv-light-success-hover), var(--nv-dark-success-hover)); + --nv-color-success-subtle: light-dark(var(--nv-light-success-subtle), var(--nv-dark-success-subtle)); + --nv-color-success-border: light-dark(var(--nv-light-success-border), var(--nv-dark-success-border)); + --nv-color-success-text: light-dark(var(--nv-light-success-text), var(--nv-dark-success-text)); + --nv-color-on-success: light-dark(var(--nv-light-on-success), var(--nv-dark-on-success)); + + /* --- Warning ------------------------------------------------------------ */ + --nv-color-warning: light-dark(var(--nv-light-warning), var(--nv-dark-warning)); + --nv-color-warning-hover: light-dark(var(--nv-light-warning-hover), var(--nv-dark-warning-hover)); + --nv-color-warning-subtle: light-dark(var(--nv-light-warning-subtle), var(--nv-dark-warning-subtle)); + --nv-color-warning-border: light-dark(var(--nv-light-warning-border), var(--nv-dark-warning-border)); + --nv-color-warning-text: light-dark(var(--nv-light-warning-text), var(--nv-dark-warning-text)); + --nv-color-on-warning: light-dark(var(--nv-light-on-warning), var(--nv-dark-on-warning)); + + /* --- Danger ------------------------------------------------------------- */ + --nv-color-danger: light-dark(var(--nv-light-danger), var(--nv-dark-danger)); + --nv-color-danger-hover: light-dark(var(--nv-light-danger-hover), var(--nv-dark-danger-hover)); + --nv-color-danger-active: light-dark(var(--nv-light-danger-active), var(--nv-dark-danger-active)); + --nv-color-danger-subtle: light-dark(var(--nv-light-danger-subtle), var(--nv-dark-danger-subtle)); + --nv-color-danger-border: light-dark(var(--nv-light-danger-border), var(--nv-dark-danger-border)); + --nv-color-danger-text: light-dark(var(--nv-light-danger-text), var(--nv-dark-danger-text)); + --nv-color-on-danger: light-dark(var(--nv-light-on-danger), var(--nv-dark-on-danger)); + + /* --- Depth and material ------------------------------------------------- */ + --nv-color-shadow-ambient: light-dark(var(--nv-light-shadow-ambient), var(--nv-dark-shadow-ambient)); + --nv-color-shadow-penumbra: light-dark(var(--nv-light-shadow-penumbra), var(--nv-dark-shadow-penumbra)); + --nv-color-shadow-umbra: light-dark(var(--nv-light-shadow-umbra), var(--nv-dark-shadow-umbra)); + --nv-color-shadow-inset: light-dark(var(--nv-light-shadow-inset), var(--nv-dark-shadow-inset)); + --nv-color-highlight: light-dark(var(--nv-light-highlight), var(--nv-dark-highlight)); + --nv-color-highlight-strong: light-dark(var(--nv-light-highlight-strong), var(--nv-dark-highlight-strong)); + + /* Elevation tint: on a dark canvas a shadow has nothing to darken, so a + raised surface also has to gain light. Transparent in light, where the + shadow does all the work. */ + --nv-color-elevation-tint-1: light-dark(var(--nv-light-elevation-tint-1), var(--nv-dark-elevation-tint-1)); + --nv-color-elevation-tint-2: light-dark(var(--nv-light-elevation-tint-2), var(--nv-dark-elevation-tint-2)); + --nv-color-elevation-tint-3: light-dark(var(--nv-light-elevation-tint-3), var(--nv-dark-elevation-tint-3)); + --nv-color-elevation-tint-4: light-dark(var(--nv-light-elevation-tint-4), var(--nv-dark-elevation-tint-4)); + --nv-color-elevation-tint-5: light-dark(var(--nv-light-elevation-tint-5), var(--nv-dark-elevation-tint-5)); + + --nv-color-material-frosted: light-dark(var(--nv-light-material-frosted), var(--nv-dark-material-frosted)); + --nv-color-material-glass: light-dark(var(--nv-light-material-glass), var(--nv-dark-material-glass)); + --nv-color-material-border: light-dark(var(--nv-light-material-border), var(--nv-dark-material-border)); + + /* --- Interaction --------------------------------------------------------- */ + --nv-color-focus-ring: light-dark(var(--nv-light-focus-ring), var(--nv-dark-focus-ring)); + --nv-color-focus-ring-contrast: light-dark(var(--nv-light-focus-ring-contrast), var(--nv-dark-focus-ring-contrast)); + --nv-color-selection: light-dark(var(--nv-light-selection), var(--nv-dark-selection)); + } +} +@layer niveus.tokens { + /* ========================================================================== + Niveus — Typography + -------------------------------------------------------------------------- + One type scale, used everywhere. Sizes are in rem so they respect the user's + browser font size; line heights are unitless so they inherit correctly. + ========================================================================== */ + + :root { + /* --- Families ----------------------------------------------------------- */ + --nv-font-family-sans: + "Inter var", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", + "Helvetica Neue", Arial, sans-serif; + --nv-font-family-mono: + "SF Mono", "JetBrains Mono", "IBM Plex Mono", ui-monospace, "Menlo", + "Consolas", monospace; + + /* --- Size scale ---------------------------------------------------------- */ + --nv-font-size-2xs: 0.6875rem; /* 11px — dense metadata, never body text */ + --nv-font-size-xs: 0.75rem; /* 12px */ + --nv-font-size-sm: 0.8125rem; /* 13px */ + --nv-font-size-md: 0.9375rem; /* 15px — default body size */ + --nv-font-size-lg: 1.0625rem; /* 17px */ + --nv-font-size-xl: 1.25rem; /* 20px */ + --nv-font-size-2xl: 1.5rem; /* 24px */ + --nv-font-size-3xl: 1.875rem; /* 30px */ + --nv-font-size-4xl: 2.375rem; /* 38px */ + --nv-font-size-5xl: 3rem; /* 48px */ + + /* --- Weight -------------------------------------------------------------- */ + --nv-font-weight-regular: 400; + --nv-font-weight-medium: 500; + --nv-font-weight-semibold: 600; + --nv-font-weight-bold: 700; + + /* --- Line height --------------------------------------------------------- */ + --nv-line-height-tight: 1.18; /* display and large headings */ + --nv-line-height-snug: 1.32; /* headings, single-line controls */ + --nv-line-height-normal: 1.5; /* body copy */ + --nv-line-height-relaxed: 1.68; /* long-form reading */ + + /* --- Letter spacing ------------------------------------------------------- + Large text needs negative tracking to stay optically even; small caps-ish + labels need positive tracking to stay readable. */ + --nv-letter-spacing-tighter: -0.022em; + --nv-letter-spacing-tight: -0.012em; + --nv-letter-spacing-normal: 0em; + --nv-letter-spacing-wide: 0.02em; + --nv-letter-spacing-wider: 0.06em; + + /* --- Composite roles ------------------------------------------------------ + Applied as classes in base.css (.nv-display, .nv-heading, …). Kept as + tokens so applications can compose their own elements from the same + values instead of guessing. */ + --nv-font-display: var(--nv-font-weight-semibold) var(--nv-font-size-4xl) / + var(--nv-line-height-tight) var(--nv-font-family-sans); + --nv-font-heading: var(--nv-font-weight-semibold) var(--nv-font-size-2xl) / + var(--nv-line-height-snug) var(--nv-font-family-sans); + --nv-font-title: var(--nv-font-weight-semibold) var(--nv-font-size-lg) / + var(--nv-line-height-snug) var(--nv-font-family-sans); + --nv-font-body: var(--nv-font-weight-regular) var(--nv-font-size-md) / + var(--nv-line-height-normal) var(--nv-font-family-sans); + --nv-font-label: var(--nv-font-weight-medium) var(--nv-font-size-sm) / + var(--nv-line-height-snug) var(--nv-font-family-sans); + --nv-font-caption: var(--nv-font-weight-regular) var(--nv-font-size-xs) / + var(--nv-line-height-normal) var(--nv-font-family-sans); + --nv-font-code: var(--nv-font-weight-regular) var(--nv-font-size-sm) / + var(--nv-line-height-normal) var(--nv-font-family-mono); + + /* --- Measure --------------------------------------------------------------- */ + --nv-measure-narrow: 44ch; + --nv-measure: 68ch; + --nv-measure-wide: 82ch; + } +} +@layer niveus.tokens { + /* ========================================================================== + Niveus — Spacing and size + -------------------------------------------------------------------------- + A 4px rhythm. The numeric suffix is the value in quarter-steps, so + --nv-space-4 is 16px and --nv-space-6 is 24px. Anything not on the scale is + application layout, not design-system spacing. + ========================================================================== */ + + :root { + --nv-space-0: 0; + --nv-space-px: 1px; + --nv-space-0-5: 0.125rem; /* 2px */ + --nv-space-1: 0.25rem; /* 4px */ + --nv-space-1-5: 0.375rem; /* 6px */ + --nv-space-2: 0.5rem; /* 8px */ + --nv-space-2-5: 0.625rem; /* 10px */ + --nv-space-3: 0.75rem; /* 12px */ + --nv-space-4: 1rem; /* 16px */ + --nv-space-5: 1.25rem; /* 20px */ + --nv-space-6: 1.5rem; /* 24px */ + --nv-space-7: 1.75rem; /* 28px */ + --nv-space-8: 2rem; /* 32px */ + --nv-space-10: 2.5rem; /* 40px */ + --nv-space-12: 3rem; /* 48px */ + --nv-space-14: 3.5rem; /* 56px */ + --nv-space-16: 4rem; /* 64px */ + --nv-space-20: 5rem; /* 80px */ + --nv-space-24: 6rem; /* 96px */ + --nv-space-32: 8rem; /* 128px */ + + /* --- Control sizing ------------------------------------------------------- + Shared by every interactive control so a button, an input and a select + line up on the same row without per-component tuning. */ + --nv-control-height-sm: 1.75rem; /* 28px */ + --nv-control-height-md: 2.25rem; /* 36px */ + --nv-control-height-lg: 2.75rem; /* 44px */ + + --nv-control-padding-sm: var(--nv-space-2-5); + --nv-control-padding-md: var(--nv-space-3); + --nv-control-padding-lg: var(--nv-space-4); + + --nv-control-gap-sm: var(--nv-space-1-5); + --nv-control-gap-md: var(--nv-space-2); + --nv-control-gap-lg: var(--nv-space-2-5); + + /* Minimum pointer target, independent of visual control height. */ + --nv-target-min: 2.75rem; /* 44px */ + + /* --- Layout ---------------------------------------------------------------- */ + --nv-layout-gutter: var(--nv-space-6); + --nv-layout-max-width: 72rem; + --nv-border-width: 1px; + --nv-border-width-strong: 2px; + } +} +@layer niveus.tokens { + /* ========================================================================== + Niveus — Radius + -------------------------------------------------------------------------- + Corner softness carries as much of the Niveus character as shadow does. + Radii grow with the surface: controls stay modest, containers get rounder, + and only pills and avatars go fully round. + ========================================================================== */ + + :root { + --nv-radius-none: 0; + --nv-radius-xs: 0.25rem; /* 4px — tags, swatches, nested chips */ + --nv-radius-sm: 0.5rem; /* 8px — small controls, menu items */ + --nv-radius-md: 0.75rem; /* 12px — buttons, inputs, default control radius */ + --nv-radius-lg: 1rem; /* 16px — cards, panels */ + --nv-radius-xl: 1.375rem; /* 22px — dialogs, sheets, large containers */ + --nv-radius-2xl: 1.75rem; /* 28px — hero surfaces */ + --nv-radius-full: 999rem; /* — pills, avatars, switches */ + + /* Nested radius: an inner surface inset by N should be N smaller than its + parent, or the corners read as off-center. */ + --nv-radius-nested-md: calc(var(--nv-radius-lg) - var(--nv-space-2)); + --nv-radius-nested-lg: calc(var(--nv-radius-xl) - var(--nv-space-3)); + } +} +@layer niveus.tokens { + /* ========================================================================== + Niveus — Elevation + -------------------------------------------------------------------------- + Depth is a scale, not a free choice. Each level describes one relationship + between a surface and what sits behind it: + + 0 flush painted on the canvas, no separation + 1 resting cards, list rows, static panels + 2 raised buttons, controls, anything that reacts to a pointer + 3 floating popovers, menus, dropdowns, tooltips + 4 lifted dialogs, sheets, detached panels + 5 overlay the topmost layer, used sparingly + + Every level is built from the same three shadow colors so depth stays + consistent between appearances: an ambient contact shadow that keeps the + edge crisp, a soft penumbra for the spread, and an umbra that only appears + once a surface is genuinely far from the canvas. + ========================================================================== */ + + :root { + --nv-elevation-0: none; + + --nv-elevation-1: + 0 1px 1px var(--nv-color-shadow-ambient), + 0 1px 3px -1px var(--nv-color-shadow-penumbra); + + --nv-elevation-2: + 0 1px 1px var(--nv-color-shadow-ambient), + 0 2px 4px -1px var(--nv-color-shadow-penumbra), + 0 4px 8px -4px var(--nv-color-shadow-umbra); + + --nv-elevation-3: + 0 1px 1px var(--nv-color-shadow-ambient), + 0 4px 8px -2px var(--nv-color-shadow-penumbra), + 0 10px 20px -6px var(--nv-color-shadow-umbra); + + --nv-elevation-4: + 0 1px 2px var(--nv-color-shadow-ambient), + 0 8px 16px -4px var(--nv-color-shadow-penumbra), + 0 20px 40px -10px var(--nv-color-shadow-umbra); + + --nv-elevation-5: + 0 1px 2px var(--nv-color-shadow-ambient), + 0 14px 28px -6px var(--nv-color-shadow-penumbra), + 0 32px 64px -14px var(--nv-color-shadow-umbra); + + /* --- Inset --------------------------------------------------------------- + Recessed surfaces: fields, wells, tracks, pressed controls. */ + --nv-elevation-inset-sm: inset 0 1px 2px var(--nv-color-shadow-inset); + --nv-elevation-inset-md: + inset 0 1px 1px var(--nv-color-shadow-inset), + inset 0 2px 5px -1px var(--nv-color-shadow-inset); + --nv-elevation-inset-lg: + inset 0 2px 3px var(--nv-color-shadow-inset), + inset 0 6px 12px -3px var(--nv-color-shadow-inset); + + /* --- Highlight ----------------------------------------------------------- + The thin catch of light along the top edge of a raised surface. This is + what makes Niveus read as material rather than as a rectangle with a + shadow. Always paired with an elevation, never used alone. */ + --nv-highlight-top: inset 0 1px 0 var(--nv-color-highlight); + --nv-highlight-top-strong: inset 0 1px 0 var(--nv-color-highlight-strong); + --nv-highlight-edge: + inset 0 1px 0 var(--nv-color-highlight), + inset 0 0 0 1px var(--nv-color-border-subtle); + + /* --- Focus --------------------------------------------------------------- + A contrast ring sits under the accent ring so focus stays visible on both + light and dark surfaces, and on accent-filled controls. */ + --nv-focus-ring-width: 3px; + --nv-focus-ring: + 0 0 0 1px var(--nv-color-focus-ring-contrast), + 0 0 0 calc(1px + var(--nv-focus-ring-width)) var(--nv-color-focus-ring); + } +} +@layer niveus.tokens { + /* ========================================================================== + Niveus — Motion + -------------------------------------------------------------------------- + Motion explains state and spatial relationship. It is short enough to feel + immediate and eased enough to feel physical. + + Rule of thumb: the smaller the element, the faster it moves. A button's + hover is 120ms; a dialog's entrance is 240ms. + ========================================================================== */ + + :root { + /* --- Duration ------------------------------------------------------------ */ + --nv-duration-instant: 0ms; + --nv-duration-fast: 120ms; /* hover, press, color change */ + --nv-duration-quick: 160ms; /* small elements entering */ + --nv-duration-normal: 220ms; /* popovers, menus, expansion */ + --nv-duration-slow: 320ms; /* dialogs, sheets, large surfaces */ + --nv-duration-slower: 480ms; /* full-screen transitions */ + + /* --- Easing --------------------------------------------------------------- + `standard` covers almost everything. Entrance decelerates into place, exit + accelerates away, and `spring` is reserved for confirmations — never for + anything a user triggers repeatedly. */ + --nv-ease-standard: cubic-bezier(0.2, 0.05, 0, 1); + --nv-ease-entrance: cubic-bezier(0.05, 0.7, 0.1, 1); + --nv-ease-exit: cubic-bezier(0.3, 0, 0.8, 0.15); + --nv-ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1); + --nv-ease-linear: linear; + + /* --- Continuity ------------------------------------------------------------- + Durations for changes the user is meant to *follow*, not merely notice. + They are longer than an interaction response because the eye has further + to travel: emphasis crossing a navigation bar, a panel growing open, a + whole surface changing appearance. */ + --nv-duration-travel: 260ms; /* emphasis moving from one element to another */ + --nv-duration-expand: 280ms; /* content growing out of, or back into, place */ + --nv-duration-theme: 400ms; /* the entire surface changing appearance */ + + /* Leaves quickly, lands softly. The right curve for something travelling a + distance the user is meant to follow with their eye. */ + --nv-ease-emphasized: cubic-bezier(0.32, 0.04, 0.06, 1); + + /* --- Composites ------------------------------------------------------------ + `translate` and `scale` are listed rather than `transform`, because the + components animate the individual properties — a transition on `transform` + does not cover them, and the movement snaps. */ + --nv-transition-colors: + color var(--nv-duration-fast) var(--nv-ease-standard), + background-color var(--nv-duration-fast) var(--nv-ease-standard), + border-color var(--nv-duration-fast) var(--nv-ease-standard); + --nv-transition-depth: + box-shadow var(--nv-duration-fast) var(--nv-ease-standard), + translate var(--nv-duration-fast) var(--nv-ease-standard), + scale var(--nv-duration-fast) var(--nv-ease-standard); + --nv-transition-control: + color var(--nv-duration-fast) var(--nv-ease-standard), + background-color var(--nv-duration-fast) var(--nv-ease-standard), + border-color var(--nv-duration-fast) var(--nv-ease-standard), + box-shadow var(--nv-duration-fast) var(--nv-ease-standard), + translate var(--nv-duration-fast) var(--nv-ease-standard), + scale var(--nv-duration-fast) var(--nv-ease-standard); + + /* Containers: the same as depth, plus the colors, so a surface glides into + its new appearance when the theme changes instead of snapping. */ + --nv-transition-surface: + color var(--nv-duration-fast) var(--nv-ease-standard), + background-color var(--nv-duration-fast) var(--nv-ease-standard), + border-color var(--nv-duration-fast) var(--nv-ease-standard), + box-shadow var(--nv-duration-fast) var(--nv-ease-standard), + translate var(--nv-duration-fast) var(--nv-ease-standard), + scale var(--nv-duration-fast) var(--nv-ease-standard); + + --nv-transition-expand: + block-size var(--nv-duration-expand) var(--nv-ease-standard), + grid-template-rows var(--nv-duration-expand) var(--nv-ease-standard), + opacity var(--nv-duration-expand) var(--nv-ease-standard); + + /* --- Displacement ---------------------------------------------------------- + How far things move. Presses sink by 1px; surfaces enter from 8px away. */ + --nv-lift-hover: -1px; + --nv-press-depth: 1px; + --nv-travel-sm: 4px; + --nv-travel-md: 8px; + --nv-travel-lg: 16px; + } + + /* Reduced motion is a system-wide contract, not a per-component opt-in: cut + duration to near-zero and neutralise displacement, but keep transitions + alive so state changes still resolve rather than snapping mid-flight. */ + @media (prefers-reduced-motion: reduce) { + :root { + --nv-duration-fast: 1ms; + --nv-duration-quick: 1ms; + --nv-duration-normal: 1ms; + --nv-duration-slow: 1ms; + --nv-duration-slower: 1ms; + --nv-duration-travel: 1ms; + --nv-duration-expand: 1ms; + --nv-duration-theme: 1ms; + --nv-ease-spring: var(--nv-ease-standard); + --nv-lift-hover: 0px; + --nv-press-depth: 0px; + --nv-travel-sm: 0px; + --nv-travel-md: 0px; + --nv-travel-lg: 0px; + } + } +} +@layer niveus.tokens { + /* ========================================================================== + Niveus — Materials + -------------------------------------------------------------------------- + A material is a surface treatment: what it is made of, not where it sits. + Elevation answers "how far from the canvas"; material answers "what does + light do when it hits this". + + solid opaque, stable. The default. Content lives here. + elevated solid with depth and a top highlight. Separated from context. + inset recessed into its parent. Fields, wells, tracks. + frosted translucent, heavily blurred. Chrome that sits over content: + navigation bars, toolbars, sticky headers. + glass more translucent, less blurred. Contextual layers where the + content behind should stay legible: overlays, hover cards. + + Translucency has a cost — it tints whatever is behind it and forces the GPU + to composite. Use frosted and glass for layers that move over content, not + for content itself. + ========================================================================== */ + + :root { + /* --- Blur ---------------------------------------------------------------- */ + --nv-blur-sm: 8px; + --nv-blur-md: 16px; + --nv-blur-lg: 28px; + --nv-blur-xl: 44px; + + /* Backdrops lose saturation when blurred; a touch of saturate() puts the + color back so frosted surfaces do not read as gray films. */ + --nv-backdrop-saturate: 180%; + --nv-backdrop-frosted: blur(var(--nv-blur-lg)) saturate(var(--nv-backdrop-saturate)); + --nv-backdrop-glass: blur(var(--nv-blur-md)) saturate(var(--nv-backdrop-saturate)); + --nv-backdrop-scrim: blur(var(--nv-blur-sm)) saturate(120%); + + /* --- Material recipes ----------------------------------------------------- + Each recipe is the full set of properties that defines the material. + Applied by the .nv-material-* classes in components/surface.css. */ + --nv-material-solid-bg: var(--nv-color-surface); + --nv-material-solid-border: var(--nv-color-border); + --nv-material-solid-shadow: var(--nv-elevation-0); + + --nv-material-elevated-bg: var(--nv-color-surface-elevated); + --nv-material-elevated-border: var(--nv-color-border-subtle); + --nv-material-elevated-shadow: var(--nv-highlight-top), var(--nv-elevation-2); + + --nv-material-inset-bg: var(--nv-color-surface-inset); + --nv-material-inset-border: var(--nv-color-border-subtle); + --nv-material-inset-shadow: var(--nv-elevation-inset-md); + + --nv-material-frosted-bg: var(--nv-color-material-frosted); + --nv-material-frosted-border: var(--nv-color-material-border); + --nv-material-frosted-shadow: var(--nv-highlight-top), var(--nv-elevation-3); + + --nv-material-glass-bg: var(--nv-color-material-glass); + --nv-material-glass-border: var(--nv-color-material-border); + --nv-material-glass-shadow: var(--nv-highlight-top-strong), var(--nv-elevation-4); + } + + /* Where translucency cannot be honoured — reduced transparency, or a browser + without backdrop-filter — materials fall back to their solid equivalents + rather than becoming unreadable washes. */ + @media (prefers-reduced-transparency: reduce) { + :root { + --nv-material-frosted-bg: var(--nv-color-surface-elevated); + --nv-material-glass-bg: var(--nv-color-surface-elevated); + --nv-backdrop-frosted: none; + --nv-backdrop-glass: none; + --nv-backdrop-scrim: none; + } + } + + @supports not (backdrop-filter: blur(1px)) { + :root { + --nv-material-frosted-bg: var(--nv-color-surface-elevated); + --nv-material-glass-bg: var(--nv-color-surface-elevated); + } + } +} + +/* --- Themes: what those values become in each appearance -------------------- */ +@layer niveus.theme { + /* ========================================================================== + Niveus — Light appearance + -------------------------------------------------------------------------- + Values for the light side of every semantic role. Nothing here is consumed + by components directly; tokens/color.css resolves these through light-dark(). + + Almost nothing below is a literal color. Each role is *derived* from + --nv-brand, so setting one value re-colours the entire system: the accent + and its states, what text goes on top of it, how the status colors lean, and + how much of the brand bleeds into the greys. See tokens/color.css for the + inputs. + + Light Niveus reads as paper on a cool, slightly recessed canvas: surfaces are + near-white, depth comes from soft shadow rather than from contrast, and a + crisp top highlight gives raised elements their edge. + ========================================================================== */ + + :root { + /* Follow the environment unless the application states an appearance. */ + color-scheme: light dark; + } + + :root[data-theme="light"] { + color-scheme: light; + } + + /* [data-nv-brand] re-derives the whole set for a subtree, so one region can + carry a different brand without a second theme. */ + :root, + [data-nv-brand] { + /* --- Canvas and surfaces ----------------------------------------------- + Paper stays paper: the two surfaces that hold content are left untinted so + text keeps its contrast. Everything the surfaces sit *in* picks up a trace + of the brand, which is what stops an accent from looking stuck on. */ + --nv-light-background: color-mix(in oklab, #e9ecf3, var(--nv-brand) var(--nv-brand-tint)); + --nv-light-background-subtle: color-mix(in oklab, #eff1f6, var(--nv-brand) var(--nv-brand-tint)); + --nv-light-surface: var(--nv-palette-neutral-0); + --nv-light-surface-elevated: var(--nv-palette-neutral-0); + --nv-light-surface-inset: color-mix(in oklab, #e4e8f0, var(--nv-brand) var(--nv-brand-tint)); + --nv-light-surface-hover: color-mix(in oklab, #f4f6fa, var(--nv-brand) var(--nv-brand-tint)); + --nv-light-surface-active: color-mix(in oklab, #e8ebf2, var(--nv-brand) var(--nv-brand-tint)); + --nv-light-scrim: rgb(16 21 30 / 0.32); + + /* --- Text --------------------------------------------------------------- + Primary text stays neutral — it carries the reading contrast. The quieter + two lean towards the brand, where a tint reads as warmth rather than as a + loss of legibility. */ + --nv-light-text-primary: var(--nv-palette-neutral-900); + --nv-light-text-secondary: color-mix(in oklab, var(--nv-palette-neutral-600), var(--nv-brand) 8%); + --nv-light-text-tertiary: color-mix(in oklab, var(--nv-palette-neutral-500), var(--nv-brand) 8%); + --nv-light-text-disabled: var(--nv-palette-neutral-400); + --nv-light-text-inverted: var(--nv-palette-neutral-0); + + /* --- Borders ------------------------------------------------------------ */ + --nv-light-border-subtle: rgb(16 21 30 / 0.06); + --nv-light-border: rgb(16 21 30 / 0.11); + --nv-light-border-strong: rgb(16 21 30 / 0.2); + + /* --- Accent -------------------------------------------------------------- + Every filled color is pushed away from the middle of the lightness range + before it is used. A mid-tone is the one place where neither white nor + black text reaches 4.5:1, so a brand that lands there is darkened until + white works — or, if it is already light, lightened until black does. The + brand's hue and chroma are untouched; only its lightness moves, and only + as far as it has to. */ + --nv-light-accent: oklch(from var(--nv-brand) calc( + clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1) * min(l, var(--nv-contrast-dark)) + + (1 - clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1)) * max(l, var(--nv-contrast-light)) + ) clamp(0.02, c, 0.25) h); + --nv-light-accent-hover: oklch(from var(--nv-light-accent) calc(l - 0.045) c h); + --nv-light-accent-active: oklch(from var(--nv-light-accent) calc(l - 0.09) c h); + --nv-light-accent-subtle: oklch(from var(--nv-light-accent) 0.965 calc(c * 0.16) h); + --nv-light-accent-subtle-hover: oklch(from var(--nv-light-accent) 0.93 calc(c * 0.22) h); + --nv-light-accent-border: oklch(from var(--nv-light-accent) l c h / 0.32); + --nv-light-accent-text: oklch(from var(--nv-light-accent) clamp(0.3, calc(l - 0.12), 0.5) c h); + /* Text that lands on the color itself. The clamp is a step function: below + the lightness threshold it resolves to 1 and the text goes near-white, + above it to 0 and the text goes near-black — so a yellow brand gets dark + labels and a navy one gets light labels, with no second token to set. */ + --nv-light-on-accent: oklch(from var(--nv-light-accent) + calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84) + calc(c * 0.2) h); + + /* --- Success ------------------------------------------------------------- */ + --nv-light-success: oklch(from + color-mix(in oklab, var(--nv-palette-success-500), var(--nv-brand) var(--nv-brand-harmony)) + calc( + clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1) * min(l, var(--nv-contrast-dark)) + + (1 - clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1)) * max(l, var(--nv-contrast-light)) + ) clamp(0.02, c, 0.25) h); + --nv-light-success-hover: oklch(from var(--nv-light-success) calc(l - 0.05) c h); + --nv-light-success-subtle: oklch(from var(--nv-light-success) 0.965 calc(c * 0.16) h); + --nv-light-success-border: oklch(from var(--nv-light-success) l c h / 0.3); + --nv-light-success-text: oklch(from var(--nv-light-success) clamp(0.3, calc(l - 0.1), 0.48) c h); + --nv-light-on-success: oklch(from var(--nv-light-success) + calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84) + calc(c * 0.2) h); + + /* --- Warning -------------------------------------------------------------- */ + /* Amber starts above the pivot on purpose. Pushed the other way it lands as + brown, which reads as neither a warning nor a color anyone chose. */ + --nv-light-warning: oklch(from + color-mix(in oklab, var(--nv-palette-warning-400), var(--nv-brand) var(--nv-brand-harmony)) + calc( + clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1) * min(l, var(--nv-contrast-dark)) + + (1 - clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1)) * max(l, var(--nv-contrast-light)) + ) clamp(0.02, c, 0.25) h); + --nv-light-warning-hover: oklch(from var(--nv-light-warning) calc(l - 0.05) c h); + --nv-light-warning-subtle: oklch(from var(--nv-light-warning) 0.965 calc(c * 0.16) h); + --nv-light-warning-border: oklch(from var(--nv-light-warning) l c h / 0.32); + --nv-light-warning-text: oklch(from var(--nv-light-warning) clamp(0.3, calc(l - 0.1), 0.48) c h); + --nv-light-on-warning: oklch(from var(--nv-light-warning) + calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84) + calc(c * 0.2) h); + + /* --- Danger ---------------------------------------------------------------- */ + --nv-light-danger: oklch(from + color-mix(in oklab, var(--nv-palette-danger-500), var(--nv-brand) var(--nv-brand-harmony)) + calc( + clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1) * min(l, var(--nv-contrast-dark)) + + (1 - clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1)) * max(l, var(--nv-contrast-light)) + ) clamp(0.02, c, 0.25) h); + --nv-light-danger-hover: oklch(from var(--nv-light-danger) calc(l - 0.05) c h); + --nv-light-danger-active: oklch(from var(--nv-light-danger) calc(l - 0.1) c h); + --nv-light-danger-subtle: oklch(from var(--nv-light-danger) 0.965 calc(c * 0.16) h); + --nv-light-danger-border: oklch(from var(--nv-light-danger) l c h / 0.3); + --nv-light-danger-text: oklch(from var(--nv-light-danger) clamp(0.3, calc(l - 0.1), 0.48) c h); + --nv-light-on-danger: oklch(from var(--nv-light-danger) + calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84) + calc(c * 0.2) h); + + /* --- Depth and material ------------------------------------------------- + Three shadow colors build every elevation level: an ambient contact + shadow, a soft penumbra, and a deeper umbra for high elevations. */ + --nv-light-shadow-ambient: rgb(16 21 30 / 0.05); + --nv-light-shadow-penumbra: rgb(16 21 30 / 0.08); + --nv-light-shadow-umbra: rgb(16 21 30 / 0.06); + --nv-light-shadow-inset: rgb(16 21 30 / 0.12); + --nv-light-highlight: rgb(255 255 255 / 0.85); + --nv-light-highlight-strong: rgb(255 255 255 / 1); + + /* Light surfaces are already lighter than their canvas; the shadow alone + carries elevation, so there is nothing to add. */ + --nv-light-elevation-tint-1: transparent; + --nv-light-elevation-tint-2: transparent; + --nv-light-elevation-tint-3: transparent; + --nv-light-elevation-tint-4: transparent; + --nv-light-elevation-tint-5: transparent; + + --nv-light-material-frosted: rgb(255 255 255 / 0.72); + --nv-light-material-glass: rgb(255 255 255 / 0.5); + --nv-light-material-border: rgb(255 255 255 / 0.55); + + /* --- Interaction ----------------------------------------------------------- */ + --nv-light-focus-ring: oklch(from var(--nv-light-accent) l c h / 0.4); + --nv-light-focus-ring-contrast: rgb(255 255 255 / 0.9); + --nv-light-selection: oklch(from var(--nv-light-accent) l c h / 0.2); + } +} +@layer niveus.theme { + /* ========================================================================== + Niveus — Dark appearance + -------------------------------------------------------------------------- + Values for the dark side of every semantic role, resolved through + light-dark() in tokens/color.css. Like the light appearance, almost nothing + here is a literal color — every role is derived from --nv-brand. + + Dark Niveus is not the light theme inverted. Surfaces get *lighter* as they + rise, shadows deepen rather than disappear, and the top highlight thins to a + faint rim so elevation still reads without glare. The accent moves up its + own ramp too: a mid-tone that reads as vivid on paper reads as muddy on a + dark canvas. + ========================================================================== */ + + :root[data-theme="dark"] { + color-scheme: dark; + } + + :root, + [data-nv-brand] { + /* --- Canvas and surfaces ----------------------------------------------- */ + --nv-dark-background: color-mix(in oklab, #0b0e14, var(--nv-brand) var(--nv-brand-tint)); + --nv-dark-background-subtle: color-mix(in oklab, #10141b, var(--nv-brand) var(--nv-brand-tint)); + --nv-dark-surface: color-mix(in oklab, #171c24, var(--nv-brand) var(--nv-brand-tint)); + --nv-dark-surface-elevated: color-mix(in oklab, #1e242e, var(--nv-brand) var(--nv-brand-tint)); + --nv-dark-surface-inset: color-mix(in oklab, #10141b, var(--nv-brand) var(--nv-brand-tint)); + --nv-dark-surface-hover: color-mix(in oklab, #222835, var(--nv-brand) var(--nv-brand-tint)); + --nv-dark-surface-active: color-mix(in oklab, #2a3140, var(--nv-brand) var(--nv-brand-tint)); + --nv-dark-scrim: rgb(3 5 9 / 0.62); + + /* --- Text --------------------------------------------------------------- */ + --nv-dark-text-primary: #e9edf4; + --nv-dark-text-secondary: color-mix(in oklab, #a3aebf, var(--nv-brand) 8%); + --nv-dark-text-tertiary: color-mix(in oklab, #7a8494, var(--nv-brand) 8%); + --nv-dark-text-disabled: #58616f; + --nv-dark-text-inverted: var(--nv-palette-neutral-975); + + /* --- Borders ------------------------------------------------------------ */ + --nv-dark-border-subtle: rgb(255 255 255 / 0.06); + --nv-dark-border: rgb(255 255 255 / 0.1); + --nv-dark-border-strong: rgb(255 255 255 / 0.2); + + /* --- Accent ------------------------------------------------------------- + Lifted up the ramp and slightly desaturated: saturated mid-tones read as + muddy on dark canvases, and fully saturated light ones read as neon. The + floor also puts every filled color above the pivot, so on a dark canvas + the derived label is always the dark one — which is what makes an accent + button read as lit rather than as a hole. */ + --nv-dark-accent: oklch(from var(--nv-brand) clamp(0.72, calc(l + 0.12), 0.86) clamp(0.02, calc(c * 0.92), 0.2) h); + --nv-dark-accent-hover: oklch(from var(--nv-dark-accent) calc(l + 0.05) c h); + --nv-dark-accent-active: oklch(from var(--nv-dark-accent) calc(l + 0.09) c h); + --nv-dark-accent-subtle: oklch(from var(--nv-dark-accent) l c h / 0.15); + --nv-dark-accent-subtle-hover: oklch(from var(--nv-dark-accent) l c h / 0.22); + --nv-dark-accent-border: oklch(from var(--nv-dark-accent) l c h / 0.38); + --nv-dark-accent-text: oklch(from var(--nv-dark-accent) clamp(0.72, calc(l + 0.08), 0.9) calc(c * 0.9) h); + --nv-dark-on-accent: oklch(from var(--nv-dark-accent) + calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84) + calc(c * 0.2) h); + + /* --- Success ------------------------------------------------------------ */ + --nv-dark-success: oklch(from + color-mix(in oklab, var(--nv-palette-success-400), var(--nv-brand) var(--nv-brand-harmony)) + clamp(0.72, calc(l + 0.1), 0.88) clamp(0.02, calc(c * 0.9), 0.18) h); + --nv-dark-success-hover: oklch(from var(--nv-dark-success) calc(l + 0.05) c h); + --nv-dark-success-subtle: oklch(from var(--nv-dark-success) l c h / 0.15); + --nv-dark-success-border: oklch(from var(--nv-dark-success) l c h / 0.35); + --nv-dark-success-text: oklch(from var(--nv-dark-success) clamp(0.74, calc(l + 0.08), 0.9) calc(c * 0.9) h); + --nv-dark-on-success: oklch(from var(--nv-dark-success) + calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84) + calc(c * 0.2) h); + + /* --- Warning ------------------------------------------------------------ */ + --nv-dark-warning: oklch(from + color-mix(in oklab, var(--nv-palette-warning-400), var(--nv-brand) var(--nv-brand-harmony)) + clamp(0.74, calc(l + 0.1), 0.88) clamp(0.02, calc(c * 0.9), 0.18) h); + --nv-dark-warning-hover: oklch(from var(--nv-dark-warning) calc(l + 0.05) c h); + --nv-dark-warning-subtle: oklch(from var(--nv-dark-warning) l c h / 0.15); + --nv-dark-warning-border: oklch(from var(--nv-dark-warning) l c h / 0.35); + --nv-dark-warning-text: oklch(from var(--nv-dark-warning) clamp(0.76, calc(l + 0.08), 0.9) calc(c * 0.9) h); + --nv-dark-on-warning: oklch(from var(--nv-dark-warning) + calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84) + calc(c * 0.2) h); + + /* --- Danger ------------------------------------------------------------- */ + --nv-dark-danger: oklch(from + color-mix(in oklab, var(--nv-palette-danger-400), var(--nv-brand) var(--nv-brand-harmony)) + clamp(0.72, calc(l + 0.1), 0.88) clamp(0.02, calc(c * 0.9), 0.18) h); + --nv-dark-danger-hover: oklch(from var(--nv-dark-danger) calc(l + 0.05) c h); + --nv-dark-danger-active: oklch(from var(--nv-dark-danger) calc(l + 0.09) c h); + --nv-dark-danger-subtle: oklch(from var(--nv-dark-danger) l c h / 0.15); + --nv-dark-danger-border: oklch(from var(--nv-dark-danger) l c h / 0.35); + --nv-dark-danger-text: oklch(from var(--nv-dark-danger) clamp(0.74, calc(l + 0.08), 0.9) calc(c * 0.9) h); + --nv-dark-on-danger: oklch(from var(--nv-dark-danger) + calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84) + calc(c * 0.2) h); + + /* --- Depth and material -------------------------------------------------- */ + --nv-dark-shadow-ambient: rgb(0 0 0 / 0.3); + --nv-dark-shadow-penumbra: rgb(0 0 0 / 0.4); + --nv-dark-shadow-umbra: rgb(0 0 0 / 0.35); + --nv-dark-shadow-inset: rgb(0 0 0 / 0.5); + --nv-dark-highlight: rgb(255 255 255 / 0.07); + --nv-dark-highlight-strong: rgb(255 255 255 / 0.12); + + /* The higher a surface sits, the more light reaches it. */ + --nv-dark-elevation-tint-1: rgb(255 255 255 / 0.025); + --nv-dark-elevation-tint-2: rgb(255 255 255 / 0.042); + --nv-dark-elevation-tint-3: rgb(255 255 255 / 0.06); + --nv-dark-elevation-tint-4: rgb(255 255 255 / 0.078); + --nv-dark-elevation-tint-5: rgb(255 255 255 / 0.095); + + --nv-dark-material-frosted: color-mix(in oklab, rgb(23 28 36 / 0.72), var(--nv-brand) var(--nv-brand-tint)); + --nv-dark-material-glass: color-mix(in oklab, rgb(30 36 46 / 0.55), var(--nv-brand) var(--nv-brand-tint)); + --nv-dark-material-border: rgb(255 255 255 / 0.1); + + /* --- Interaction ---------------------------------------------------------- */ + --nv-dark-focus-ring: oklch(from var(--nv-dark-accent) l c h / 0.5); + --nv-dark-focus-ring-contrast: rgb(6 8 12 / 0.9); + --nv-dark-selection: oklch(from var(--nv-dark-accent) l c h / 0.3); + } +} + +/* --- Base: elements, before any component ----------------------------------- */ +@layer niveus.base { + /* ========================================================================== + Niveus — Base + -------------------------------------------------------------------------- + The minimum an application needs before any component renders: a predictable + box model, the canvas, the default type, and the system-wide focus and + selection treatment. + + This layer styles elements, never classes with intent. Anything opinionated + enough to need a name belongs in components/. + ========================================================================== */ + + *, + *::before, + *::after { + box-sizing: border-box; + } + + html { + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; + } + + body { + margin: 0; + min-height: 100svh; + background-color: var(--nv-color-background); + color: var(--nv-color-text-primary); + font: var(--nv-font-body); + letter-spacing: var(--nv-letter-spacing-normal); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + font-synthesis-weight: none; + + /* The canvas glides into a new appearance with everything standing on it, + rather than cutting while the surfaces above it fade. */ + transition: var(--nv-transition-colors); + } + + /* --- Rhythm --------------------------------------------------------------- + Block elements carry no outer margin of their own; spacing between them is + the layout's decision, set with gap or the spacing tokens. */ + h1, h2, h3, h4, h5, h6, + p, figure, blockquote, dl, dd, pre { + margin: 0; + } + + h1, h2, h3, h4, h5, h6 { + font-weight: var(--nv-font-weight-semibold); + letter-spacing: var(--nv-letter-spacing-tight); + line-height: var(--nv-line-height-snug); + text-wrap: balance; + } + + h1 { font-size: var(--nv-font-size-3xl); letter-spacing: var(--nv-letter-spacing-tighter); } + h2 { font-size: var(--nv-font-size-2xl); } + h3 { font-size: var(--nv-font-size-xl); } + h4 { font-size: var(--nv-font-size-lg); } + h5 { font-size: var(--nv-font-size-md); } + h6 { font-size: var(--nv-font-size-sm); } + + p { + text-wrap: pretty; + } + + small { + font-size: var(--nv-font-size-xs); + } + + code, kbd, samp, pre { + font-family: var(--nv-font-family-mono); + font-size: var(--nv-font-size-sm); + } + + /* --- Links ----------------------------------------------------------------- */ + a { + color: var(--nv-color-accent-text); + text-decoration-color: color-mix(in srgb, currentColor 35%, transparent); + text-underline-offset: 0.18em; + transition: var(--nv-transition-colors); + } + + a:hover { + text-decoration-color: currentColor; + } + + /* --- Media ----------------------------------------------------------------- */ + img, svg, video, canvas, audio, iframe, embed, object { + display: block; + max-width: 100%; + } + + svg { + flex: none; + } + + hr { + border: 0; + border-top: var(--nv-border-width) solid var(--nv-color-border-subtle); + margin: 0; + } + + /* --- Forms ----------------------------------------------------------------- + Form controls do not inherit type by default; without this every field in + the system would silently fall back to the browser's own font. */ + button, input, optgroup, select, textarea { + font: inherit; + color: inherit; + margin: 0; + } + + textarea { + resize: vertical; + } + + :disabled { + cursor: not-allowed; + } + + /* --- Focus ----------------------------------------------------------------- + One focus treatment for the whole system. Components that need the ring to + follow their own shape override box-shadow, not outline-color. */ + :focus-visible { + outline: var(--nv-focus-ring-width) solid var(--nv-color-focus-ring); + outline-offset: 2px; + border-radius: var(--nv-radius-xs); + } + + ::selection { + background-color: var(--nv-color-selection); + color: var(--nv-color-text-primary); + } + + /* --- Scrollbars ------------------------------------------------------------ */ + * { + scrollbar-color: var(--nv-color-border-strong) transparent; + scrollbar-width: thin; + } + + /* ========================================================================== + Type roles + -------------------------------------------------------------------------- + Named roles from tokens/typography.css, available as classes so hierarchy + stays consistent even when the markup cannot use a heading element. + ========================================================================== */ + + .nv-display { + font: var(--nv-font-display); + letter-spacing: var(--nv-letter-spacing-tighter); + text-wrap: balance; + } + + .nv-heading { + font: var(--nv-font-heading); + letter-spacing: var(--nv-letter-spacing-tight); + text-wrap: balance; + } + + .nv-title { + font: var(--nv-font-title); + letter-spacing: var(--nv-letter-spacing-tight); + } + + .nv-body { + font: var(--nv-font-body); + text-wrap: pretty; + } + + .nv-label { + font: var(--nv-font-label); + } + + .nv-caption { + font: var(--nv-font-caption); + color: var(--nv-color-text-secondary); + } + + .nv-overline { + font: var(--nv-font-caption); + font-weight: var(--nv-font-weight-semibold); + letter-spacing: var(--nv-letter-spacing-wider); + text-transform: uppercase; + color: var(--nv-color-text-tertiary); + } + + .nv-code { + font: var(--nv-font-code); + } + + /* Text color roles, for when the role is about emphasis rather than size. */ + .nv-text-primary { color: var(--nv-color-text-primary); } + .nv-text-secondary { color: var(--nv-color-text-secondary); } + .nv-text-tertiary { color: var(--nv-color-text-tertiary); } + .nv-text-accent { color: var(--nv-color-accent-text); } + .nv-text-success { color: var(--nv-color-success-text); } + .nv-text-warning { color: var(--nv-color-warning-text); } + .nv-text-danger { color: var(--nv-color-danger-text); } +} + +/* --- Continuity: how one state becomes the next ----------------------------- */ +@layer niveus.continuity { + /* ========================================================================== + Niveus — Continuity + -------------------------------------------------------------------------- + Every interaction should read as a continuation of the previous state, not + as a replacement of it. When something moves, grows, collapses or changes + appearance, the change itself should be perceptible: where it came from, + where it went, and why. + + This layer holds the cross-cutting machinery that individual components + then opt into: + + · one duration and easing for everything the browser animates between + two states via a view transition + · emphasis that travels between elements instead of disappearing from one + and reappearing on another (--nv-selection-name) + · direction, so moving forward and moving back do not look identical + · appearance changes that cross-fade rather than cut + + The goal is not to animate everything. It is that nothing pops into a new + state the user could have watched it reach. + ========================================================================== */ + + :root { + /* Lets block-size: auto participate in transitions, which is what allows + content to expand from where it already is instead of appearing at full + height. Without it, every disclosure in the system would jump. */ + interpolate-size: allow-keywords; + + /* The default name for travelling emphasis. A page with more than one + selection group must give each group its own name — two visible elements + sharing a view-transition-name abort the transition entirely. */ + --nv-selection-name: nv-selection; + } + + /* --- View transitions ------------------------------------------------------- + Anything the application wraps in document.startViewTransition() inherits + the system's travel duration and easing, so a state change animates like + the rest of Niveus without the application choosing timings. */ + + ::view-transition-group(*) { + animation-duration: var(--nv-duration-travel); + animation-timing-function: var(--nv-ease-emphasized); + } + + /* The page behind a travelling element cross-fades quickly — it is context, + not the subject of the change, and a slow fade would make the whole + interface feel as though it were being replaced. */ + ::view-transition-old(root), + ::view-transition-new(root) { + animation-duration: var(--nv-duration-quick); + animation-timing-function: var(--nv-ease-standard); + } + + /* --- Travelling emphasis ------------------------------------------------------- + A selection marker moves between two elements, which no single element can + transition — so it is captured and interpolated. The problem that creates is + layering: transition layers paint above the page snapshot, so a marker that + sits *behind* its label at rest would slide over the top of every label it + passed, washing the text as it went. + + The fix is to capture the labels too and order the groups explicitly. The + marker travels below; the items ride above it, exactly as they do at rest. + niveus.js assigns each item its name — they have to be unique per document, + which no stylesheet can guarantee — and items it has not named simply stay in + the page snapshot. + + Because the layering is now correct, the marker needs no trick to stay out of + the way: it travels at full opacity, like the solid object it is. */ + + ::view-transition-group(.nv-travelling) { + z-index: 0; + } + + ::view-transition-group(.nv-carried) { + z-index: 1; + } + + /* A carried item does not move — only its color changes when selection leaves + or arrives. That resolves quickly so the label does not sit half-way between + two colors for the length of the travel. */ + ::view-transition-old(.nv-carried), + ::view-transition-new(.nv-carried) { + animation-duration: var(--nv-duration-fast); + } + + /* --- Appearance --------------------------------------------------------------- + A theme change is the one moment the entire surface is the subject, so the + root cross-fade slows down and every color arrives together. The attribute + is set for the duration of the change by nv.setTheme(). */ + + :root[data-nv-transition="theme"]::view-transition-old(root), + :root[data-nv-transition="theme"]::view-transition-new(root) { + animation-duration: var(--nv-duration-theme); + animation-timing-function: var(--nv-ease-standard); + } + + /* Without the view-transition path — no script, or a browser without the API — + surfaces still glide, because their own transitions cover color. */ + + /* --- Direction ------------------------------------------------------------------ + Moving forward and moving back should not look the same. The application + sets data-nv-direction on the root for the length of the transition; content + then leaves towards where the user came from and enters from where they are + going. Without a direction, content simply rises into place. */ + + ::view-transition-old(nv-content) { + animation: nv-fade-out var(--nv-duration-quick) var(--nv-ease-exit) both; + } + + ::view-transition-new(nv-content) { + animation: + nv-fade-in var(--nv-duration-normal) var(--nv-ease-entrance) both, + nv-rise var(--nv-duration-normal) var(--nv-ease-entrance) both; + } + + :root[data-nv-direction="forward"]::view-transition-old(nv-content) { + animation: + nv-fade-out var(--nv-duration-quick) var(--nv-ease-exit) both, + nv-exit-to-start var(--nv-duration-normal) var(--nv-ease-emphasized) both; + } + + :root[data-nv-direction="forward"]::view-transition-new(nv-content) { + animation: + nv-fade-in var(--nv-duration-normal) var(--nv-ease-entrance) both, + nv-enter-from-end var(--nv-duration-normal) var(--nv-ease-emphasized) both; + } + + :root[data-nv-direction="backward"]::view-transition-old(nv-content) { + animation: + nv-fade-out var(--nv-duration-quick) var(--nv-ease-exit) both, + nv-exit-to-end var(--nv-duration-normal) var(--nv-ease-emphasized) both; + } + + :root[data-nv-direction="backward"]::view-transition-new(nv-content) { + animation: + nv-fade-in var(--nv-duration-normal) var(--nv-ease-entrance) both, + nv-enter-from-start var(--nv-duration-normal) var(--nv-ease-emphasized) both; + } + + @keyframes nv-fade-in { + from { opacity: 0; } + } + + @keyframes nv-fade-out { + to { opacity: 0; } + } + + @keyframes nv-rise { + from { translate: 0 var(--nv-travel-sm); } + } + + @keyframes nv-enter-from-end { + from { translate: var(--nv-travel-lg) 0; } + } + + @keyframes nv-enter-from-start { + from { translate: calc(var(--nv-travel-lg) * -1) 0; } + } + + @keyframes nv-exit-to-start { + to { translate: calc(var(--nv-travel-md) * -1) 0; } + } + + @keyframes nv-exit-to-end { + to { translate: var(--nv-travel-md) 0; } + } + + /* --- Utilities --------------------------------------------------------------------- + .nv-continuous marks a region whose content should cross-fade in place when + it changes — the "same surface, different content" relationship. */ + + .nv-continuous { + view-transition-name: var(--nv-content-name, nv-content); + } + + /* Collapsible region for markup that is not a
. The 0fr -> 1fr grid + row is the one technique that animates to an unknown content height in every + browser, with no measurement and no script. */ + + .nv-collapse { + display: grid; + grid-template-rows: 0fr; + transition: + grid-template-rows var(--nv-duration-expand) var(--nv-ease-standard), + opacity var(--nv-duration-expand) var(--nv-ease-standard); + opacity: 0; + } + + .nv-collapse > * { + min-block-size: 0; + overflow: hidden; + } + + .nv-collapse[data-open], + .nv-collapse:not([hidden])[open] { + grid-template-rows: 1fr; + opacity: 1; + } + + /* Reduced motion does not mean "no continuity" — a state change still has to + resolve rather than be cut mid-way. The token layer already collapses every + duration to 1ms; this removes the displacement so nothing slides. */ + @media (prefers-reduced-motion: reduce) { + ::view-transition-group(*), + ::view-transition-old(*), + ::view-transition-new(*) { + animation-duration: 1ms; + } + } +} + +/* --- Components -------------------------------------------------------------- */ +@layer niveus.components { + /* ========================================================================== + Niveus — Surface + -------------------------------------------------------------------------- + The material vocabulary, expressed as classes. Every other component is + built from these; applications use them directly for anything the system + does not ship as a named component. + + .nv-surface a container using the solid material + .nv-material-{type} apply a material to any element + .nv-elevation-{0..5} apply a depth level to any element + + A material and an elevation are independent: .nv-material-solid + .nv-elevation-3 is a legitimate combination. What is not legitimate is a + raw box-shadow that belongs to neither scale. + ========================================================================== */ + + .nv-surface { + background-color: var(--nv-color-surface); + color: var(--nv-color-text-primary); + border: var(--nv-border-width) solid var(--nv-color-border); + border-radius: var(--nv-radius-lg); + transition: var(--nv-transition-surface); + } + + /* --- Materials ------------------------------------------------------------- */ + + .nv-material-solid { + background-color: var(--nv-material-solid-bg); + border: var(--nv-border-width) solid var(--nv-material-solid-border); + box-shadow: var(--nv-material-solid-shadow); + backdrop-filter: none; + } + + .nv-material-elevated { + background-color: var(--nv-material-elevated-bg); + border: var(--nv-border-width) solid var(--nv-material-elevated-border); + box-shadow: var(--nv-material-elevated-shadow); + backdrop-filter: none; + } + + .nv-material-inset { + background-color: var(--nv-material-inset-bg); + border: var(--nv-border-width) solid var(--nv-material-inset-border); + box-shadow: var(--nv-material-inset-shadow); + backdrop-filter: none; + } + + .nv-material-frosted { + background-color: var(--nv-material-frosted-bg); + border: var(--nv-border-width) solid var(--nv-material-frosted-border); + box-shadow: var(--nv-material-frosted-shadow); + backdrop-filter: var(--nv-backdrop-frosted); + -webkit-backdrop-filter: var(--nv-backdrop-frosted); + } + + .nv-material-glass { + background-color: var(--nv-material-glass-bg); + border: var(--nv-border-width) solid var(--nv-material-glass-border); + box-shadow: var(--nv-material-glass-shadow); + backdrop-filter: var(--nv-backdrop-glass); + -webkit-backdrop-filter: var(--nv-backdrop-glass); + } + + /* --- Elevation ------------------------------------------------------------- + Elevation utilities carry three things at once, because a lifted surface is + never only a shadow: the shadow itself, the highlight along its top edge, + and — on a dark canvas, where a shadow has nothing left to darken — a tint + that lets more light reach the surface the higher it sits. The tint is + transparent in light appearance, so the two read as one scale. */ + + .nv-elevation-0 { box-shadow: var(--nv-elevation-0); } + + .nv-elevation-1 { + box-shadow: var(--nv-highlight-top), var(--nv-elevation-1); + background-image: linear-gradient(var(--nv-color-elevation-tint-1) 0 0); + } + + .nv-elevation-2 { + box-shadow: var(--nv-highlight-top), var(--nv-elevation-2); + background-image: linear-gradient(var(--nv-color-elevation-tint-2) 0 0); + } + + .nv-elevation-3 { + box-shadow: var(--nv-highlight-top), var(--nv-elevation-3); + background-image: linear-gradient(var(--nv-color-elevation-tint-3) 0 0); + } + + .nv-elevation-4 { + box-shadow: var(--nv-highlight-top), var(--nv-elevation-4); + background-image: linear-gradient(var(--nv-color-elevation-tint-4) 0 0); + } + + .nv-elevation-5 { + box-shadow: var(--nv-highlight-top-strong), var(--nv-elevation-5); + background-image: linear-gradient(var(--nv-color-elevation-tint-5) 0 0); + } + + .nv-elevation-inset-sm { box-shadow: var(--nv-elevation-inset-sm); } + .nv-elevation-inset-md { box-shadow: var(--nv-elevation-inset-md); } + .nv-elevation-inset-lg { box-shadow: var(--nv-elevation-inset-lg); } + + /* --- Divider ---------------------------------------------------------------- */ + + .nv-divider { + border: 0; + border-top: var(--nv-border-width) solid var(--nv-color-border-subtle); + margin-block: var(--nv-space-4); + } + + .nv-divider--vertical { + border-top: 0; + border-left: var(--nv-border-width) solid var(--nv-color-border-subtle); + margin-block: 0; + margin-inline: var(--nv-space-4); + align-self: stretch; + } +} +@layer niveus.components { + /* ========================================================================== + Niveus — Button + -------------------------------------------------------------------------- + .nv-button secondary by default — a raised surface + .nv-button--primary the single affirmative action in a view + .nv-button--ghost low-emphasis, no material until hovered + .nv-button--danger destructive, and only when it is + .nv-button--frosted for use over imagery or frosted chrome + .nv-button--sm | --lg 36px by default + .nv-button--icon square, for a lone icon (needs a label) + .nv-button--block fills its container + + Every variant is defined purely by reassigning the private --_* properties + below. Interaction — the lift on hover, the sink on press, the focus ring — + lives once in the base rule, so a new variant can never drift from the way + the rest of the system responds to a pointer. + ========================================================================== */ + + .nv-button { + /* Variant surface, overridden per modifier. */ + --_bg: var(--nv-color-surface-elevated); + --_bg-hover: var(--nv-color-surface-hover); + --_bg-active: var(--nv-color-surface-active); + --_fg: var(--nv-color-text-primary); + --_border: var(--nv-color-border); + --_shadow: var(--nv-highlight-top), var(--nv-elevation-2); + --_shadow-hover: var(--nv-highlight-top), var(--nv-elevation-3); + --_shadow-active: var(--nv-elevation-inset-sm); + + /* Size, overridden per size modifier. */ + --_height: var(--nv-control-height-md); + --_padding: var(--nv-control-padding-md); + --_gap: var(--nv-control-gap-md); + --_font-size: var(--nv-font-size-sm); + --_radius: var(--nv-radius-md); + + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--_gap); + + block-size: var(--_height); + padding-inline: var(--_padding); + border: var(--nv-border-width) solid var(--_border); + border-radius: var(--_radius); + + background-color: var(--_bg); + color: var(--_fg); + box-shadow: var(--_shadow); + + font-family: var(--nv-font-family-sans); + font-size: var(--_font-size); + font-weight: var(--nv-font-weight-medium); + line-height: var(--nv-line-height-snug); + letter-spacing: var(--nv-letter-spacing-normal); + white-space: nowrap; + text-decoration: none; + + cursor: pointer; + user-select: none; + -webkit-tap-highlight-color: transparent; + transition: var(--nv-transition-control); + } + + /* A control smaller than the minimum target still gets a full-size hit area: + the pseudo-element grows outward without changing the visual size. */ + .nv-button::after { + content: ""; + position: absolute; + inset-block-start: 50%; + inset-inline-start: 50%; + translate: -50% -50%; + inline-size: max(100%, var(--nv-target-min)); + block-size: max(100%, var(--nv-target-min)); + } + + @media (hover: hover) { + .nv-button:hover:not(:disabled, [aria-disabled="true"]) { + background-color: var(--_bg-hover); + box-shadow: var(--_shadow-hover); + translate: 0 var(--nv-lift-hover); + } + } + + .nv-button:active:not(:disabled, [aria-disabled="true"]) { + background-color: var(--_bg-active); + box-shadow: var(--_shadow-active); + translate: 0 var(--nv-press-depth); + transition-duration: var(--nv-duration-instant); + } + + .nv-button:focus-visible { + outline: none; + box-shadow: var(--_shadow), var(--nv-focus-ring); + } + + .nv-button:disabled, + .nv-button[aria-disabled="true"] { + background-color: var(--nv-color-surface-inset); + color: var(--nv-color-text-disabled); + border-color: var(--nv-color-border-subtle); + box-shadow: none; + translate: none; + cursor: not-allowed; + } + + /* --- Variants --------------------------------------------------------------- */ + + .nv-button--primary { + --_bg: var(--nv-color-accent); + --_bg-hover: var(--nv-color-accent-hover); + --_bg-active: var(--nv-color-accent-active); + --_fg: var(--nv-color-on-accent); + --_border: transparent; + font-weight: var(--nv-font-weight-semibold); + } + + .nv-button--danger { + --_bg: var(--nv-color-danger); + --_bg-hover: var(--nv-color-danger-hover); + --_bg-active: var(--nv-color-danger-active); + --_fg: var(--nv-color-on-danger); + --_border: transparent; + --nv-color-focus-ring: var(--nv-color-danger-border); + font-weight: var(--nv-font-weight-semibold); + } + + .nv-button--ghost { + --_bg: transparent; + --_bg-hover: var(--nv-color-surface-hover); + --_bg-active: var(--nv-color-surface-active); + --_fg: var(--nv-color-text-secondary); + --_border: transparent; + --_shadow: none; + --_shadow-hover: none; + --_shadow-active: var(--nv-elevation-inset-sm); + } + + @media (hover: hover) { + .nv-button--ghost:hover:not(:disabled, [aria-disabled="true"]) { + color: var(--nv-color-text-primary); + translate: none; /* nothing to lift — a ghost has no material to raise */ + } + } + + .nv-button--subtle { + --_bg: var(--nv-color-accent-subtle); + --_bg-hover: var(--nv-color-accent-subtle-hover); + --_bg-active: var(--nv-color-accent-subtle-hover); + --_fg: var(--nv-color-accent-text); + --_border: var(--nv-color-accent-border); + --_shadow: none; + --_shadow-hover: var(--nv-elevation-1); + } + + .nv-button--frosted { + --_bg: var(--nv-material-glass-bg); + --_bg-hover: var(--nv-material-frosted-bg); + --_bg-active: var(--nv-material-frosted-bg); + --_border: var(--nv-material-glass-border); + backdrop-filter: var(--nv-backdrop-glass); + -webkit-backdrop-filter: var(--nv-backdrop-glass); + } + + /* --- Sizes ------------------------------------------------------------------- */ + + .nv-button--sm { + --_height: var(--nv-control-height-sm); + --_padding: var(--nv-control-padding-sm); + --_gap: var(--nv-control-gap-sm); + --_font-size: var(--nv-font-size-xs); + --_radius: var(--nv-radius-sm); + } + + .nv-button--lg { + --_height: var(--nv-control-height-lg); + --_padding: var(--nv-control-padding-lg); + --_gap: var(--nv-control-gap-lg); + --_font-size: var(--nv-font-size-md); + } + + .nv-button--icon { + --_padding: 0; + inline-size: var(--_height); + } + + .nv-button--pill { + --_radius: var(--nv-radius-full); + } + + .nv-button--block { + display: flex; + inline-size: 100%; + } + + /* --- Floating ------------------------------------------------------------------ + A control that sits *over* content rather than in the flow: a carousel arrow, + a floating action. Two things change. + + It is already off the surface, so there is nothing to lift it from — it grows + instead. And it is almost always positioned with `translate`, which the hover + lift would overwrite, throwing the button half its own height down the page + the moment a pointer touched it. --nv-button-offset carries that positioning + through every state. */ + + .nv-button--float { + --_shadow: var(--nv-highlight-top), var(--nv-elevation-3); + --_shadow-hover: var(--nv-highlight-top), var(--nv-elevation-4); + --_shadow-active: var(--nv-highlight-top), var(--nv-elevation-2); + + translate: var(--nv-button-offset, 0 0); + } + + @media (hover: hover) { + .nv-button--float:hover:not(:disabled, [aria-disabled="true"]) { + translate: var(--nv-button-offset, 0 0); + scale: 1.06; + } + } + + .nv-button--float:active:not(:disabled, [aria-disabled="true"]) { + translate: var(--nv-button-offset, 0 0); + scale: 0.97; + } + + /* --- Parts --------------------------------------------------------------------- */ + + .nv-button__icon { + inline-size: 1em; + block-size: 1em; + font-size: 1.15em; + opacity: 0.9; + } + + /* Busy: the label stays in place to keep the button from resizing mid-action, + and the spinner replaces it. */ + .nv-button[aria-busy="true"] { + color: transparent; + pointer-events: none; + } + + .nv-button[aria-busy="true"]::before { + content: ""; + position: absolute; + inline-size: 1em; + block-size: 1em; + border: 2px solid var(--_fg); + border-block-start-color: transparent; + border-radius: var(--nv-radius-full); + animation: nv-spin var(--nv-duration-slower) var(--nv-ease-linear) infinite; + } + + @keyframes nv-spin { + to { rotate: 1turn; } + } + + /* --- Group --------------------------------------------------------------------- + Segmented controls: buttons share an edge, so only the outer corners round + and the shared border is not drawn twice. */ + + .nv-button-group { + display: inline-flex; + align-items: center; + } + + .nv-button-group > .nv-button:not(:first-child) { + border-start-start-radius: 0; + border-end-start-radius: 0; + margin-inline-start: calc(var(--nv-border-width) * -1); + } + + .nv-button-group > .nv-button:not(:last-child) { + border-start-end-radius: 0; + border-end-end-radius: 0; + } + + .nv-button-group > .nv-button:hover, + .nv-button-group > .nv-button:focus-visible { + z-index: 1; + translate: none; + } +} +@layer niveus.components { + /* ========================================================================== + Niveus — Input + -------------------------------------------------------------------------- + .nv-field label + control + hint, correctly spaced + .nv-input text input, textarea and select + .nv-input-group control with a leading or trailing affix + .nv-select wrapper that supplies the chevron + .nv-checkbox / .nv-radio custom marks on the native controls + .nv-switch binary control with an inset track + + Inputs are the system's inset material: they read as recessed into the + surface that holds them, which is what distinguishes a place to type from a + thing to press. On focus the recess stays — only the ring and border change, + so the field never appears to jump forward. + ========================================================================== */ + + .nv-field { + display: flex; + flex-direction: column; + gap: var(--nv-space-1-5); + min-inline-size: 0; + } + + .nv-field__label { + font: var(--nv-font-label); + color: var(--nv-color-text-primary); + } + + .nv-field__label[data-optional]::after { + content: " " attr(data-optional); + color: var(--nv-color-text-tertiary); + font-weight: var(--nv-font-weight-regular); + } + + .nv-field__hint { + font: var(--nv-font-caption); + color: var(--nv-color-text-secondary); + } + + .nv-field__error { + display: flex; + align-items: center; + gap: var(--nv-space-1-5); + font: var(--nv-font-caption); + font-weight: var(--nv-font-weight-medium); + color: var(--nv-color-danger-text); + } + + /* --- Control ----------------------------------------------------------------- */ + + .nv-input { + --_height: var(--nv-control-height-md); + --_padding: var(--nv-control-padding-md); + --_font-size: var(--nv-font-size-md); + --_radius: var(--nv-radius-md); + --_border: var(--nv-color-border); + + inline-size: 100%; + min-inline-size: 0; + block-size: var(--_height); + padding-inline: var(--_padding); + + background-color: var(--nv-color-surface-inset); + color: var(--nv-color-text-primary); + border: var(--nv-border-width) solid var(--_border); + border-radius: var(--_radius); + box-shadow: var(--nv-elevation-inset-sm); + + font-family: var(--nv-font-family-sans); + font-size: var(--_font-size); + line-height: var(--nv-line-height-snug); + transition: var(--nv-transition-control); + appearance: none; + } + + .nv-input::placeholder { + color: var(--nv-color-text-tertiary); + } + + @media (hover: hover) { + .nv-input:hover:not(:disabled, [readonly]) { + --_border: var(--nv-color-border-strong); + } + } + + .nv-input:focus-visible, + .nv-input:focus { + outline: none; + --_border: var(--nv-color-accent); + box-shadow: var(--nv-elevation-inset-sm), var(--nv-focus-ring); + } + + .nv-input[aria-invalid="true"] { + --_border: var(--nv-color-danger); + --nv-color-focus-ring: var(--nv-color-danger-border); + } + + .nv-input:disabled { + background-color: var(--nv-color-background-subtle); + color: var(--nv-color-text-disabled); + box-shadow: none; + cursor: not-allowed; + } + + .nv-input[readonly] { + background-color: transparent; + box-shadow: none; + } + + /* Multi-line controls grow, so height becomes a minimum and the block padding + has to be stated explicitly. */ + textarea.nv-input { + block-size: auto; + min-block-size: calc(var(--nv-control-height-lg) * 2); + padding-block: var(--nv-space-2-5); + line-height: var(--nv-line-height-normal); + } + + .nv-input--sm { + --_height: var(--nv-control-height-sm); + --_padding: var(--nv-control-padding-sm); + --_font-size: var(--nv-font-size-sm); + --_radius: var(--nv-radius-sm); + } + + /* A color well: the swatch fills the control, so the value is the control. */ + .nv-input--color { + inline-size: var(--nv-control-height-md); + padding: var(--nv-space-1); + cursor: pointer; + } + + .nv-input--color::-webkit-color-swatch-wrapper { + padding: 0; + } + + .nv-input--color::-webkit-color-swatch, + .nv-input--color::-moz-color-swatch { + border: var(--nv-border-width) solid var(--nv-color-border); + border-radius: calc(var(--nv-radius-md) - var(--nv-space-1)); + } + + .nv-input--lg { + --_height: var(--nv-control-height-lg); + --_padding: var(--nv-control-padding-lg); + --_font-size: var(--nv-font-size-lg); + } + + /* --- Affixes ------------------------------------------------------------------- +
+ @ + +
+ The affix sits inside the field's recess rather than beside it, so the group + still reads as a single surface. */ + + .nv-input-group { + position: relative; + display: flex; + align-items: center; + inline-size: 100%; + } + + .nv-input-group__affix { + position: absolute; + z-index: 1; + display: flex; + align-items: center; + padding-inline: var(--nv-control-padding-md); + color: var(--nv-color-text-tertiary); + font-size: var(--nv-font-size-sm); + pointer-events: none; + } + + .nv-input-group__affix:first-child { + inset-inline-start: 0; + } + + .nv-input-group__affix:last-child { + inset-inline-end: 0; + } + + .nv-input-group__affix:first-child ~ .nv-input { + padding-inline-start: calc(var(--nv-control-padding-md) * 2 + 1em); + } + + .nv-input:has(~ .nv-input-group__affix:last-child) { + padding-inline-end: calc(var(--nv-control-padding-md) * 2 + 1em); + } + + /* Interactive affixes — a clear button, a reveal toggle — opt back in. */ + .nv-input-group__affix > * { + pointer-events: auto; + } + + /* --- Select ---------------------------------------------------------------------- +
*/ + + .nv-select { + position: relative; + display: flex; + inline-size: 100%; + } + + .nv-select > .nv-input { + padding-inline-end: calc(var(--nv-control-padding-md) * 2 + 0.5em); + cursor: pointer; + } + + .nv-select::after { + content: ""; + position: absolute; + inset-inline-end: var(--nv-control-padding-md); + inset-block-start: 50%; + inline-size: 0.45em; + block-size: 0.45em; + margin-block-start: -0.28em; + border-inline-end: 1.5px solid var(--nv-color-text-tertiary); + border-block-end: 1.5px solid var(--nv-color-text-tertiary); + rotate: 45deg; + pointer-events: none; + } + + /* --- Checkbox and radio ------------------------------------------------------------ */ + + .nv-checkbox, + .nv-radio { + position: relative; + flex: none; + inline-size: var(--nv-space-5); + block-size: var(--nv-space-5); + margin: 0; + background-color: var(--nv-color-surface-inset); + border: var(--nv-border-width) solid var(--nv-color-border-strong); + border-radius: var(--nv-radius-xs); + box-shadow: var(--nv-elevation-inset-sm); + cursor: pointer; + appearance: none; + transition: var(--nv-transition-control); + } + + .nv-radio { + border-radius: var(--nv-radius-full); + } + + .nv-checkbox:checked, + .nv-radio:checked, + .nv-checkbox:indeterminate { + background-color: var(--nv-color-accent); + border-color: transparent; + box-shadow: var(--nv-highlight-top), var(--nv-elevation-1); + } + + /* The marks are always present, at zero size and zero opacity, so checking a + box grows the tick out of its centre instead of stamping it there. A mark + that only exists in the :checked state has nothing to animate from. */ + + .nv-checkbox::after, + .nv-radio::after, + .nv-checkbox::before { + content: ""; + position: absolute; + opacity: 0; + scale: 0; + transition: + opacity var(--nv-duration-fast) var(--nv-ease-standard), + scale var(--nv-duration-quick) var(--nv-ease-spring); + } + + /* The tick is drawn from two borders so it scales with the control and needs + no asset. */ + .nv-checkbox::after { + inset-block-start: 45%; + inset-inline-start: 50%; + inline-size: 0.28em; + block-size: 0.55em; + translate: -50% -50%; + rotate: 45deg; + border-inline-end: 2px solid var(--nv-color-on-accent); + border-block-end: 2px solid var(--nv-color-on-accent); + } + + .nv-checkbox:checked:not(:indeterminate)::after { + opacity: 1; + scale: 1; + } + + /* Indeterminate is its own mark rather than a variation of the tick: the two + have to be able to cross over each other when the state changes. */ + .nv-checkbox::before { + inset: 50% 25% auto; + translate: 0 -50%; + block-size: 2px; + background-color: var(--nv-color-on-accent); + border-radius: var(--nv-radius-full); + } + + .nv-checkbox:indeterminate::before { + opacity: 1; + scale: 1; + } + + .nv-radio::after { + inset: 50% auto auto 50%; + translate: -50% -50%; + inline-size: 0.4em; + block-size: 0.4em; + background-color: var(--nv-color-on-accent); + border-radius: var(--nv-radius-full); + } + + .nv-radio:checked::after { + opacity: 1; + scale: 1; + } + + .nv-checkbox:disabled, + .nv-radio:disabled { + background-color: var(--nv-color-background-subtle); + border-color: var(--nv-color-border); + box-shadow: none; + cursor: not-allowed; + } + + /* --- Switch -------------------------------------------------------------------------- + The track is inset and the thumb is elevated: the physical relationship does + the explaining, so the control reads as on or off without a label. */ + + .nv-switch { + --_track-inline: calc(var(--nv-space-6) * 1.5); + --_track-block: var(--nv-space-6); + --_thumb: calc(var(--_track-block) - 6px); + --_inset: 2px; + --_travel: calc( + var(--_track-inline) - var(--_thumb) - var(--_inset) * 2 - + var(--nv-border-width) * 2 + ); + + position: relative; + flex: none; + inline-size: var(--_track-inline); + block-size: var(--_track-block); + margin: 0; + background-color: var(--nv-color-surface-inset); + border: var(--nv-border-width) solid var(--nv-color-border); + border-radius: var(--nv-radius-full); + box-shadow: var(--nv-elevation-inset-sm); + cursor: pointer; + appearance: none; + transition: var(--nv-transition-control); + } + + .nv-switch::after { + content: ""; + position: absolute; + inset-block-start: 50%; + inset-inline-start: var(--_inset); + inline-size: var(--_thumb); + block-size: var(--_thumb); + translate: 0 -50%; + background-color: var(--nv-color-surface-elevated); + border-radius: var(--nv-radius-full); + box-shadow: var(--nv-highlight-top), var(--nv-elevation-2); + transition: + translate var(--nv-duration-travel) var(--nv-ease-emphasized), + background-color var(--nv-duration-fast) var(--nv-ease-standard); + } + + .nv-switch:checked { + background-color: var(--nv-color-accent); + border-color: transparent; + } + + .nv-switch:checked::after { + translate: var(--_travel) -50%; + } + + .nv-switch:disabled { + cursor: not-allowed; + opacity: 0.55; + } + + /* --- Inline control + label --------------------------------------------------------- */ + + .nv-choice { + display: flex; + align-items: center; + gap: var(--nv-space-2-5); + font: var(--nv-font-body); + cursor: pointer; + } + + .nv-choice:has(:disabled) { + color: var(--nv-color-text-disabled); + cursor: not-allowed; + } + + /* translate has no logical equivalent, so the thumb's direction is flipped + explicitly rather than travelling the wrong way in RTL. */ + .nv-switch:checked:dir(rtl)::after { + translate: calc(var(--_travel) * -1) -50%; + } +} +@layer niveus.components { + /* ========================================================================== + Niveus — Card + -------------------------------------------------------------------------- + .nv-card resting surface, elevation 1 + .nv-card--elevated separated from its context, elevation 2 + .nv-card--inset recessed into its parent + .nv-card--frosted over imagery or scrolling content + .nv-card--interactive the whole card is one target + .nv-card__header / __body / __footer / __media + + A card is a container, not a look. If a card needs a shadow that is not on + the elevation scale, the answer is a different elevation — not a different + shadow. + ========================================================================== */ + + .nv-card { + --_bg: var(--nv-color-surface); + --_border: var(--nv-color-border-subtle); + --_shadow: var(--nv-highlight-top), var(--nv-elevation-1); + --_padding: var(--nv-space-5); + + position: relative; + display: flex; + flex-direction: column; + min-inline-size: 0; + + background-color: var(--_bg); + color: var(--nv-color-text-primary); + border: var(--nv-border-width) solid var(--_border); + border-radius: var(--nv-radius-lg); + box-shadow: var(--_shadow); + + /* Children are clipped to the corner radius so media and inset regions + cannot square off the card. */ + overflow: hidden; + transition: var(--nv-transition-surface); + } + + .nv-card--elevated { + --_bg: var(--nv-color-surface-elevated); + --_shadow: var(--nv-highlight-top), var(--nv-elevation-2); + } + + .nv-card--floating { + --_bg: var(--nv-color-surface-elevated); + --_shadow: var(--nv-highlight-top), var(--nv-elevation-3); + } + + .nv-card--inset { + --_bg: var(--nv-color-surface-inset); + --_shadow: var(--nv-elevation-inset-md); + --_border: var(--nv-color-border-subtle); + } + + .nv-card--frosted { + --_bg: var(--nv-material-frosted-bg); + --_border: var(--nv-material-frosted-border); + --_shadow: var(--nv-material-frosted-shadow); + backdrop-filter: var(--nv-backdrop-frosted); + -webkit-backdrop-filter: var(--nv-backdrop-frosted); + } + + .nv-card--flush { + --_padding: 0; + } + + /* --- Interactive ------------------------------------------------------------ + Rises on hover and sinks on press, exactly like a button — one physical + language for anything that responds to a pointer. */ + + .nv-card--interactive { + cursor: pointer; + text-align: start; + text-decoration: none; + } + + @media (hover: hover) { + .nv-card--interactive:hover { + box-shadow: var(--nv-highlight-top), var(--nv-elevation-3); + translate: 0 calc(var(--nv-lift-hover) * 2); + } + } + + .nv-card--interactive:active { + box-shadow: var(--nv-highlight-top), var(--nv-elevation-1); + translate: 0 var(--nv-press-depth); + transition-duration: var(--nv-duration-instant); + } + + .nv-card--interactive:focus-visible { + outline: none; + box-shadow: var(--_shadow), var(--nv-focus-ring); + } + + /* Cards whose whole area is clickable via a link inside: the link's target is + stretched over the card, so the markup stays semantic and only the link text + is announced. */ + .nv-card__link::after { + content: ""; + position: absolute; + inset: 0; + } + + /* --- Parts ------------------------------------------------------------------- */ + + .nv-card__header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--nv-space-4); + padding: var(--_padding); + padding-block-end: var(--nv-space-3); + } + + .nv-card__title { + font: var(--nv-font-title); + letter-spacing: var(--nv-letter-spacing-tight); + margin: 0; + } + + .nv-card__subtitle { + font: var(--nv-font-caption); + color: var(--nv-color-text-secondary); + margin-block-start: var(--nv-space-1); + } + + .nv-card__body { + flex: 1; + padding: var(--_padding); + color: var(--nv-color-text-secondary); + font: var(--nv-font-body); + } + + /* A header already opened the block, so the body should not open it again. */ + .nv-card__header + .nv-card__body { + padding-block-start: 0; + } + + .nv-card__footer { + display: flex; + align-items: center; + gap: var(--nv-space-2); + padding: var(--_padding); + padding-block-start: var(--nv-space-3); + border-block-start: var(--nv-border-width) solid var(--nv-color-border-subtle); + margin-block-start: auto; + } + + .nv-card__footer--plain { + border-block-start: 0; + padding-block-start: 0; + } + + .nv-card__media { + display: block; + inline-size: 100%; + aspect-ratio: 16 / 9; + object-fit: cover; + background-color: var(--nv-color-surface-inset); + } + + /* --- Group --------------------------------------------------------------------- + Equal-height cards on one rhythm, collapsing to a single column without a + media query. */ + + .nv-card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr)); + gap: var(--nv-space-4); + align-items: stretch; + } +} +@layer niveus.components { + /* ========================================================================== + Niveus — Dialog + -------------------------------------------------------------------------- + Built on the native element, so focus trapping, the top layer, + Escape-to-close and inertness come from the platform rather than from + script. + + … opened with .showModal() + .nv-dialog--sheet enters from the block end + .nv-dialog--wide | --compact width + .nv-dialog__header / __body / __footer / __close + + The dialog is the system's highest material: elevation 4 over a blurred + scrim. It enters by rising and settling, and leaves the way it came — the + motion is what tells the user the surface was always there, above the page. + ========================================================================== */ + + .nv-dialog { + --_width: 30rem; + --_padding: var(--nv-space-6); + + inline-size: min(var(--_width), calc(100vw - var(--nv-space-8))); + max-block-size: min(42rem, calc(100svh - var(--nv-space-12))); + padding: 0; + + background-color: var(--nv-color-surface-elevated); + color: var(--nv-color-text-primary); + border: var(--nv-border-width) solid var(--nv-color-border-subtle); + border-radius: var(--nv-radius-xl); + box-shadow: var(--nv-highlight-top), var(--nv-elevation-4); + + overflow: hidden; + + /* Closed state. [open] below is the resting state; @starting-style replays + these values for the entrance. */ + /* A dialog should look as though it came from the control that opened it. + nv.openDialog() sets --nv-origin to that control's position relative to + the dialog, so the entrance scale expands away from the trigger rather + than from the middle of the screen. Centred when nothing sets it. */ + transform-origin: var(--nv-origin, 50% 50%); + + opacity: 0; + scale: 0.97; + translate: 0 var(--nv-travel-md); + + transition: + opacity var(--nv-duration-normal) var(--nv-ease-standard), + scale var(--nv-duration-normal) var(--nv-ease-standard), + translate var(--nv-duration-normal) var(--nv-ease-standard), + display var(--nv-duration-normal) allow-discrete, + overlay var(--nv-duration-normal) allow-discrete; + } + + .nv-dialog[open] { + opacity: 1; + scale: 1; + translate: 0; + transition-timing-function: var(--nv-ease-entrance); + } + + @starting-style { + .nv-dialog[open] { + opacity: 0; + scale: 0.97; + translate: 0 var(--nv-travel-md); + } + } + + .nv-dialog::backdrop { + background-color: var(--nv-color-scrim); + backdrop-filter: var(--nv-backdrop-scrim); + -webkit-backdrop-filter: var(--nv-backdrop-scrim); + opacity: 0; + transition: + opacity var(--nv-duration-normal) var(--nv-ease-standard), + display var(--nv-duration-normal) allow-discrete, + overlay var(--nv-duration-normal) allow-discrete; + } + + .nv-dialog[open]::backdrop { + opacity: 1; + } + + @starting-style { + .nv-dialog[open]::backdrop { + opacity: 0; + } + } + + /* showModal() does not stop the page behind from scrolling. */ + html:has(.nv-dialog:modal) { + overflow: hidden; + } + + /* --- Widths -------------------------------------------------------------------- */ + + .nv-dialog--compact { --_width: 24rem; } + .nv-dialog--wide { --_width: 44rem; } + + /* --- Sheet ---------------------------------------------------------------------- + Anchored to the block end and full width. On small screens this is usually + the right form for anything with more than two actions. */ + + .nv-dialog--sheet { + --_width: 34rem; + + /* Flush to the viewport edges — a sheet with side gaps and square bottom + corners reads as a mistake rather than as a sheet. */ + inline-size: min(var(--_width), 100vw); + max-block-size: min(46rem, calc(100svh - var(--nv-space-10))); + margin-block-end: 0; + margin-block-start: auto; + border-start-start-radius: var(--nv-radius-xl); + border-start-end-radius: var(--nv-radius-xl); + border-end-start-radius: 0; + border-end-end-radius: 0; + scale: 1; + translate: 0 var(--nv-travel-lg); + } + + @starting-style { + .nv-dialog--sheet[open] { + scale: 1; + translate: 0 var(--nv-travel-lg); + } + } + + /* --- Parts ------------------------------------------------------------------------ */ + + .nv-dialog__header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--nv-space-4); + padding: var(--_padding); + padding-block-end: var(--nv-space-3); + } + + .nv-dialog__title { + font: var(--nv-font-heading); + font-size: var(--nv-font-size-xl); + letter-spacing: var(--nv-letter-spacing-tight); + margin: 0; + } + + .nv-dialog__description { + font: var(--nv-font-body); + color: var(--nv-color-text-secondary); + margin-block-start: var(--nv-space-2); + } + + .nv-dialog__body { + padding: var(--_padding); + padding-block: var(--nv-space-2); + overflow-y: auto; + overscroll-behavior: contain; + color: var(--nv-color-text-secondary); + } + + .nv-dialog__footer { + display: flex; + align-items: center; + justify-content: flex-end; + gap: var(--nv-space-2); + padding: var(--_padding); + padding-block-start: var(--nv-space-4); + } + + /* A footer over scrollable content needs its own ground: the inset tint and + hairline tell the user there is more above it. */ + .nv-dialog__footer--divided { + background-color: var(--nv-color-background-subtle); + border-block-start: var(--nv-border-width) solid var(--nv-color-border-subtle); + } + + .nv-dialog__close { + flex: none; + margin: calc(var(--nv-space-1) * -1); + } + + /* A dialog laid out as a column keeps its header and footer fixed while only + the body scrolls. */ + .nv-dialog:has(.nv-dialog__body) { + display: flex; + flex-direction: column; + } + + .nv-dialog:not([open]) { + display: none; + } +} +@layer niveus.components { + /* ========================================================================== + Niveus — Navigation + -------------------------------------------------------------------------- + .nv-nav horizontal bar, frosted, sticky by default + .nv-nav--vertical sidebar + .nv-nav--plain solid instead of frosted + .nv-nav__brand / __list / __item / __section / __spacer + .nv-nav-tabs in-page tab strip with a sliding indicator + + Navigation is chrome: it sits above content rather than in it. That is why + the bar is frosted — the blur is not decoration, it is the signal that + content is passing underneath. A sidebar that scrolls with the page is not + chrome, so .nv-nav--vertical drops the translucency by default. + ========================================================================== */ + + .nv-nav { + position: sticky; + inset-block-start: 0; + z-index: 10; + + display: flex; + align-items: center; + gap: var(--nv-space-4); + padding: var(--nv-space-2) var(--nv-space-4); + min-block-size: var(--nv-control-height-lg); + + background-color: var(--nv-material-frosted-bg); + border-block-end: var(--nv-border-width) solid var(--nv-color-border-subtle); + + /* The travelling selection marker sits behind its item at z-index -1; the + bar has to be its own stacking context or the marker would fall behind + the bar's background and disappear. */ + isolation: isolate; + box-shadow: var(--nv-highlight-top); + backdrop-filter: var(--nv-backdrop-frosted); + -webkit-backdrop-filter: var(--nv-backdrop-frosted); + transition: var(--nv-transition-surface); + } + + /* The bar only earns a shadow once something is behind it. Set data-scrolled + from the application on scroll. */ + .nv-nav[data-scrolled] { + box-shadow: var(--nv-highlight-top), var(--nv-elevation-2); + transition: box-shadow var(--nv-duration-normal) var(--nv-ease-standard); + } + + .nv-nav--plain { + background-color: var(--nv-color-surface); + backdrop-filter: none; + -webkit-backdrop-filter: none; + } + + .nv-nav--static { + position: static; + } + + /* A navigation group used inside content rather than as page chrome: no bar, + no border, no blur — just the items and their travelling selection. */ + .nv-nav--inline { + position: static; + display: inline-flex; + min-block-size: 0; + padding: var(--nv-space-1); + + background-color: transparent; + border-block-end: 0; + box-shadow: none; + backdrop-filter: none; + -webkit-backdrop-filter: none; + } + + .nv-nav__brand { + display: flex; + align-items: center; + gap: var(--nv-space-2); + font: var(--nv-font-title); + letter-spacing: var(--nv-letter-spacing-tight); + color: var(--nv-color-text-primary); + text-decoration: none; + padding-inline: var(--nv-space-1); + } + + .nv-nav__list { + display: flex; + align-items: center; + gap: var(--nv-space-1); + margin: 0; + padding: 0; + list-style: none; + min-inline-size: 0; + } + + .nv-nav__spacer { + flex: 1; + } + + /* --- Item -------------------------------------------------------------------- + Items are flat until they are touched. The active item is the only one with + material, which keeps the bar quiet while still answering "where am I". */ + + .nv-nav__item { + /* Rides above a travelling marker once niveus.js has given it a name. */ + view-transition-class: nv-carried; + + position: relative; + display: flex; + align-items: center; + gap: var(--nv-space-2); + padding: var(--nv-space-2) var(--nv-space-3); + min-block-size: var(--nv-control-height-md); + + border: var(--nv-border-width) solid transparent; + border-radius: var(--nv-radius-md); + background-color: transparent; + + color: var(--nv-color-text-secondary); + font: var(--nv-font-label); + text-decoration: none; + white-space: nowrap; + cursor: pointer; + transition: var(--nv-transition-control); + } + + @media (hover: hover) { + .nv-nav__item:hover { + background-color: var(--nv-color-surface-hover); + color: var(--nv-color-text-primary); + } + } + + .nv-nav__item:active { + background-color: var(--nv-color-surface-active); + box-shadow: var(--nv-elevation-inset-sm); + transition-duration: var(--nv-duration-instant); + } + + .nv-nav__item:focus-visible { + outline: none; + box-shadow: var(--nv-focus-ring); + } + + /* Selection is a material that *moves*. The emphasis lives in a pseudo-element + rather than on the item itself, and carries a view-transition-name: when the + selection changes inside a transition, the browser interpolates the marker's + old box into its new one, so the highlight slides from the previous item to + the next instead of blinking out and in somewhere else. + + Only one item in a group may be selected at a time — that is what keeps the + name unique. A page with several selection groups must give each its own + --nv-selection-name, or the duplicate name aborts the transition. */ + + /* Selection changes color and gains material — but not weight. Bolding the + selected item re-measures its text, which reflows every item beside it on + each change, and makes the old and new labels cross-fade against each other + at different widths. The pill is the cue; the weight was costing a flicker + for nothing. */ + .nv-nav__item[aria-current], + .nv-nav__item--active, + .nv-nav__item[aria-selected="true"] { + color: var(--nv-color-text-primary); + } + + .nv-nav__item[aria-current]::before, + .nv-nav__item--active::before, + .nv-nav__item[aria-selected="true"]::before { + content: ""; + position: absolute; + inset: 0; + z-index: -1; + + background-color: var(--nv-color-surface-elevated); + border: var(--nv-border-width) solid var(--nv-color-border-subtle); + border-radius: inherit; + box-shadow: var(--nv-highlight-top), var(--nv-elevation-1); + + view-transition-name: var(--nv-selection-name); + view-transition-class: nv-travelling; + } + + /* Hovering an already-selected item should not restate the emphasis. */ + @media (hover: hover) { + .nv-nav__item[aria-current]:hover, + .nv-nav__item--active:hover, + .nv-nav__item[aria-selected="true"]:hover { + background-color: transparent; + } + } + + .nv-nav__item[aria-disabled="true"] { + color: var(--nv-color-text-disabled); + pointer-events: none; + } + + .nv-nav__icon { + inline-size: 1.15em; + block-size: 1.15em; + flex: none; + opacity: 0.85; + } + + .nv-nav__badge { + margin-inline-start: auto; + padding-inline: var(--nv-space-1-5); + min-inline-size: var(--nv-space-5); + block-size: var(--nv-space-5); + display: inline-flex; + align-items: center; + justify-content: center; + + background-color: var(--nv-color-accent-subtle); + color: var(--nv-color-accent-text); + border-radius: var(--nv-radius-full); + font: var(--nv-font-caption); + font-size: var(--nv-font-size-2xs); + font-weight: var(--nv-font-weight-semibold); + } + + /* --- Vertical ------------------------------------------------------------------ */ + + .nv-nav--vertical { + position: sticky; + inset-block-start: 0; + + flex-direction: column; + align-items: stretch; + gap: var(--nv-space-1); + inline-size: 15rem; + block-size: 100svh; + padding: var(--nv-space-4) var(--nv-space-3); + + background-color: var(--nv-color-background-subtle); + backdrop-filter: none; + -webkit-backdrop-filter: none; + border-block-end: 0; + border-inline-end: var(--nv-border-width) solid var(--nv-color-border-subtle); + box-shadow: none; + overflow-y: auto; + } + + .nv-nav--vertical .nv-nav__list { + flex-direction: column; + align-items: stretch; + gap: var(--nv-space-0-5); + } + + .nv-nav--vertical .nv-nav__item { + justify-content: flex-start; + } + + .nv-nav--vertical .nv-nav__brand { + padding: var(--nv-space-2) var(--nv-space-3); + margin-block-end: var(--nv-space-3); + } + + .nv-nav__section { + padding: var(--nv-space-4) var(--nv-space-3) var(--nv-space-1-5); + font: var(--nv-font-caption); + font-weight: var(--nv-font-weight-semibold); + letter-spacing: var(--nv-letter-spacing-wider); + text-transform: uppercase; + color: var(--nv-color-text-tertiary); + } + + .nv-nav__section:first-child { + padding-block-start: 0; + } + + /* --- Tabs ------------------------------------------------------------------------ + For switching views inside a page, not for moving between pages. The strip + itself is inset, so the selected tab reads as lifted out of the track. */ + + .nv-nav-tabs { + display: inline-flex; + align-items: center; + gap: var(--nv-space-0-5); + padding: var(--nv-space-1); + + background-color: var(--nv-color-surface-inset); + border: var(--nv-border-width) solid var(--nv-color-border-subtle); + border-radius: var(--nv-radius-md); + box-shadow: var(--nv-elevation-inset-sm); + overflow-x: auto; + scrollbar-width: none; + isolation: isolate; + } + + .nv-nav-tabs::-webkit-scrollbar { + display: none; + } + + .nv-nav-tabs .nv-nav__item { + border-radius: var(--nv-radius-sm); + min-block-size: var(--nv-control-height-sm); + padding-block: var(--nv-space-1); + } + + /* Tabs are a selection group of their own, so they get their own marker name; + sharing the navigation bar's would mean two visible elements with the same + view-transition-name. */ + .nv-nav-tabs { + --nv-selection-name: nv-tabs-selection; + } + + .nv-nav-tabs .nv-nav__item[aria-selected="true"]::before { + border-radius: var(--nv-radius-sm); + } + + /* --- Panels ---------------------------------------------------------------------- + Switching tabs should feel like moving through one surface rather than + swapping the interface. The panel carries the shared content name, so the + outgoing and incoming panels cross-fade inside the same box — and slide + with the direction of travel when the application supplies one. */ + + .nv-tab-panel { + view-transition-name: var(--nv-content-name, nv-content); + } + + /* --- Breadcrumbs -------------------------------------------------------------------- */ + + .nv-breadcrumbs { + display: flex; + align-items: center; + gap: var(--nv-space-2); + margin: 0; + padding: 0; + list-style: none; + font: var(--nv-font-caption); + color: var(--nv-color-text-tertiary); + } + + .nv-breadcrumbs li + li::before { + content: "/"; + margin-inline-end: var(--nv-space-2); + color: var(--nv-color-border-strong); + } + + .nv-breadcrumbs a { + color: var(--nv-color-text-secondary); + text-decoration: none; + } + + .nv-breadcrumbs a:hover { + color: var(--nv-color-text-primary); + } + + .nv-breadcrumbs [aria-current] { + color: var(--nv-color-text-primary); + font-weight: var(--nv-font-weight-medium); + } +} +@layer niveus.components { + /* ========================================================================== + Niveus — Menu + -------------------------------------------------------------------------- + + +
+ +
+ +
+ + A menu has a spatial relationship with the control that opened it: anchor + positioning ties it to the trigger, and it scales out of the trigger's edge + rather than appearing somewhere unrelated. Both sides name the same anchor + through --nv-anchor, so the pair stays obvious in the markup. + + Light dismiss, Escape, and top-layer stacking come from the popover + attribute — none of it is scripted. + ========================================================================== */ + + .nv-menu-trigger { + anchor-name: var(--nv-anchor); + } + + .nv-menu { + position: fixed; + inset: auto; + margin: 0; + padding: var(--nv-space-1); + + min-inline-size: 12rem; + max-inline-size: min(20rem, calc(100vw - var(--nv-space-8))); + max-block-size: min(24rem, calc(100svh - var(--nv-space-12))); + overflow-y: auto; + overscroll-behavior: contain; + + /* Below the trigger, aligned to its inline-start edge, flipping to whichever + side has room. */ + position-anchor: var(--nv-anchor); + position-area: block-end span-inline-end; + position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline; + margin-block-start: var(--nv-space-2); + + background-color: var(--nv-material-frosted-bg); + border: var(--nv-border-width) solid var(--nv-material-frosted-border); + border-radius: var(--nv-radius-md); + box-shadow: var(--nv-highlight-top), var(--nv-elevation-3); + backdrop-filter: var(--nv-backdrop-frosted); + -webkit-backdrop-filter: var(--nv-backdrop-frosted); + color: var(--nv-color-text-primary); + + /* Grows from the edge nearest the trigger. */ + transform-origin: center block-start; + + opacity: 0; + scale: 0.96; + translate: 0 calc(var(--nv-travel-sm) * -1); + transition: + opacity var(--nv-duration-quick) var(--nv-ease-standard), + scale var(--nv-duration-quick) var(--nv-ease-entrance), + translate var(--nv-duration-quick) var(--nv-ease-entrance), + display var(--nv-duration-quick) allow-discrete, + overlay var(--nv-duration-quick) allow-discrete; + } + + .nv-menu:popover-open { + opacity: 1; + scale: 1; + translate: 0; + } + + @starting-style { + .nv-menu:popover-open { + opacity: 0; + scale: 0.96; + translate: 0 calc(var(--nv-travel-sm) * -1); + } + } + + /* Without anchor positioning the menu cannot know where its trigger is, so it + falls back to the popover's own centring rather than landing somewhere + arbitrary. The relationship is lost; the usability is not. */ + @supports not (anchor-name: --nv-test) { + .nv-menu { + inset: 0; + margin: auto; + transform-origin: center; + } + } + + /* --- Items -------------------------------------------------------------------- */ + + .nv-menu__item { + display: flex; + align-items: center; + gap: var(--nv-space-2-5); + inline-size: 100%; + padding: var(--nv-space-2) var(--nv-space-2-5); + + background-color: transparent; + border: 0; + border-radius: var(--nv-radius-sm); + color: var(--nv-color-text-secondary); + font: var(--nv-font-label); + text-align: start; + text-decoration: none; + white-space: nowrap; + cursor: pointer; + transition: var(--nv-transition-colors); + } + + @media (hover: hover) { + .nv-menu__item:hover:not(:disabled, [aria-disabled="true"]) { + background-color: var(--nv-color-surface-hover); + color: var(--nv-color-text-primary); + } + } + + .nv-menu__item:focus-visible { + outline: none; + background-color: var(--nv-color-surface-hover); + color: var(--nv-color-text-primary); + box-shadow: var(--nv-focus-ring); + } + + .nv-menu__item--danger { + color: var(--nv-color-danger-text); + } + + @media (hover: hover) { + .nv-menu__item--danger:hover:not(:disabled, [aria-disabled="true"]) { + background-color: var(--nv-color-danger-subtle); + color: var(--nv-color-danger-text); + } + } + + .nv-menu__item:disabled, + .nv-menu__item[aria-disabled="true"] { + color: var(--nv-color-text-disabled); + cursor: not-allowed; + } + + .nv-menu__item[aria-checked="true"]::before { + content: ""; + inline-size: 0.28em; + block-size: 0.55em; + margin-inline-end: var(--nv-space-1); + translate: 0 -0.12em; + rotate: 45deg; + border-inline-end: 2px solid var(--nv-color-accent); + border-block-end: 2px solid var(--nv-color-accent); + } + + .nv-menu__shortcut { + margin-inline-start: auto; + padding-inline-start: var(--nv-space-4); + color: var(--nv-color-text-tertiary); + font: var(--nv-font-caption); + } + + .nv-menu__separator { + border: 0; + border-block-start: var(--nv-border-width) solid var(--nv-color-border-subtle); + margin: var(--nv-space-1) 0; + } + + .nv-menu__label { + padding: var(--nv-space-2) var(--nv-space-2-5) var(--nv-space-1); + font: var(--nv-font-caption); + font-weight: var(--nv-font-weight-semibold); + letter-spacing: var(--nv-letter-spacing-wider); + text-transform: uppercase; + color: var(--nv-color-text-tertiary); + } +} +@layer niveus.components { + /* ========================================================================== + Niveus — Disclosure + -------------------------------------------------------------------------- +
+ … +
…
+
+ + Content expands out of the row that revealed it, at the position it already + occupies, rather than appearing at full height. The height animates because + the continuity layer turns on `interpolate-size`, which is what lets + block-size: auto take part in a transition — no measurement, no script, and + no wrapper whose height has to be kept in sync. + + .nv-collapse in continuity.css covers the same relationship for markup that + cannot be a
. + ========================================================================== */ + + .nv-disclosure { + --_padding: var(--nv-space-4); + + background-color: var(--nv-color-surface); + border: var(--nv-border-width) solid var(--nv-color-border-subtle); + border-radius: var(--nv-radius-lg); + box-shadow: var(--nv-highlight-top), var(--nv-elevation-1); + transition: var(--nv-transition-surface); + } + + .nv-disclosure--plain { + background-color: transparent; + border-color: transparent; + box-shadow: none; + } + + .nv-disclosure__summary { + display: flex; + align-items: center; + gap: var(--nv-space-3); + padding: var(--nv-space-3) var(--_padding); + + font: var(--nv-font-label); + color: var(--nv-color-text-primary); + border-radius: inherit; + cursor: pointer; + user-select: none; + list-style: none; + transition: var(--nv-transition-colors); + } + + /* Both spellings are needed to suppress the native marker. */ + .nv-disclosure__summary::marker, + .nv-disclosure__summary::-webkit-details-marker { + display: none; + content: ""; + } + + @media (hover: hover) { + .nv-disclosure__summary:hover { + background-color: var(--nv-color-surface-hover); + } + } + + .nv-disclosure__summary:focus-visible { + outline: none; + box-shadow: var(--nv-focus-ring); + } + + /* The chevron turns rather than swapping to a different glyph — the rotation + is what says "this is the same control in its other state". */ + .nv-disclosure__chevron { + inline-size: 0.5em; + block-size: 0.5em; + margin-inline-start: auto; + margin-block-start: -0.2em; + flex: none; + + border-inline-end: 1.5px solid var(--nv-color-text-tertiary); + border-block-end: 1.5px solid var(--nv-color-text-tertiary); + rotate: 45deg; + transition: rotate var(--nv-duration-normal) var(--nv-ease-standard); + } + + .nv-disclosure[open] .nv-disclosure__chevron { + rotate: 225deg; + margin-block-start: 0.15em; + } + + .nv-disclosure__body { + padding: 0 var(--_padding) var(--_padding); + color: var(--nv-color-text-secondary); + font: var(--nv-font-body); + } + + /* The element the browser creates for a
body. Transitioning + content-visibility as a discrete property is what keeps the content + rendered while it collapses, instead of vanishing on the first frame. */ + .nv-disclosure::details-content { + block-size: 0; + overflow: hidden; + opacity: 0; + transition: + block-size var(--nv-duration-expand) var(--nv-ease-standard), + opacity var(--nv-duration-expand) var(--nv-ease-standard), + content-visibility var(--nv-duration-expand) allow-discrete; + } + + .nv-disclosure[open]::details-content { + block-size: auto; + opacity: 1; + } + + /* Stacked disclosures read as one control surface, so only the outer corners + round and neighbours share a single hairline. */ + .nv-disclosure-group { + display: flex; + flex-direction: column; + } + + /* The negative margin collapses each pair of adjacent borders into a single + hairline, so neighbours are separated once rather than twice. */ + .nv-disclosure-group > .nv-disclosure:not(:first-child) { + border-start-start-radius: 0; + border-start-end-radius: 0; + margin-block-start: calc(var(--nv-border-width) * -1); + } + + .nv-disclosure-group > .nv-disclosure:not(:last-child) { + border-end-start-radius: 0; + border-end-end-radius: 0; + } +} +@layer niveus.components { + /* ========================================================================== + Niveus — Scroller + -------------------------------------------------------------------------- +
+
…cards…
+ + +
+ + A row of content that continues past its frame. Two things make that + readable rather than merely clipped: the edges fade, so the row visibly runs + under them instead of being cut, and snapping lands each item at the same + place every time — so moving left and right returns you to positions you + have already seen rather than to arbitrary offsets. + + Scrolling works with no script at all: wheel, trackpad, touch and keyboard + are the browser's. The buttons are an affordance for pointers that have no + horizontal axis, and niveus.js wires them. + ========================================================================== */ + + .nv-scroller-frame { + position: relative; + } + + .nv-scroller { + --nv-scroller-item: 18rem; + --nv-scroller-fade: var(--nv-space-8); + + display: flex; + gap: var(--nv-space-4); + overflow-x: auto; + overscroll-behavior-inline: contain; + + /* Room for items to lift on hover without being clipped by the scroll box. */ + padding-block: var(--nv-space-2); + + scroll-snap-type: inline mandatory; + scroll-padding-inline: var(--nv-space-1); + scroll-behavior: smooth; + scrollbar-width: none; + + /* The row runs under its own edges rather than stopping at them — but only + on the side where there is actually more to see. A fade at an end the row + has already reached is a lie about the content. niveus.js marks the ends; + with no script both sides fade, which is the safe default. */ + mask-image: linear-gradient( + to right, + transparent, + black var(--nv-scroller-fade), + black calc(100% - var(--nv-scroller-fade)), + transparent + ); + } + + .nv-scroller-frame[data-nv-at-start] .nv-scroller { + mask-image: linear-gradient( + to right, + black calc(100% - var(--nv-scroller-fade)), + transparent + ); + } + + .nv-scroller-frame[data-nv-at-end] .nv-scroller { + mask-image: linear-gradient(to right, transparent, black var(--nv-scroller-fade)); + } + + .nv-scroller-frame[data-nv-at-start][data-nv-at-end] .nv-scroller { + mask-image: none; + } + + .nv-scroller::-webkit-scrollbar { + display: none; + } + + .nv-scroller > * { + flex: 0 0 auto; + inline-size: min(var(--nv-scroller-item), 78%); + scroll-snap-align: start; + } + + /* Items that should size to their content rather than to the track. */ + .nv-scroller--auto > * { + inline-size: auto; + } + + @media (prefers-reduced-motion: reduce) { + .nv-scroller { + scroll-behavior: auto; + } + } + + /* --- Controls ------------------------------------------------------------ + Centred on the row's block axis and pulled half outside it, so they read as + belonging to the frame rather than floating over the content. */ + + .nv-scroller-frame [data-nv-scroll] { + position: absolute; + inset-block-start: 50%; + z-index: 1; + + /* Read by .nv-button--float, which keeps it through hover and press. */ + --nv-button-offset: 0 -50%; + } + + /* Belt and braces: the frame knows these controls float, so their centring + survives even if the markup forgets --float. */ + .nv-scroller-frame [data-nv-scroll], + .nv-scroller-frame [data-nv-scroll]:hover, + .nv-scroller-frame [data-nv-scroll]:active { + translate: var(--nv-button-offset); + } + + .nv-scroller-frame [data-nv-scroll="prev"] { + inset-inline-start: calc(var(--nv-space-4) * -1); + } + + .nv-scroller-frame [data-nv-scroll="next"] { + inset-inline-end: calc(var(--nv-space-4) * -1); + } + + /* At either end the control has nothing left to do, and says so. */ + .nv-scroller-frame [data-nv-scroll]:disabled { + opacity: 0; + pointer-events: none; + } + + @media (hover: none) { + .nv-scroller-frame [data-nv-scroll] { + display: none; + } + } +} +@layer niveus.components { + /* ========================================================================== + Niveus — Bed + -------------------------------------------------------------------------- +
…
+ + A bed is ground that is visibly *not* ordinary background. Where a card says + "this content belongs together", a bed says "what sits here is different in + kind" — a feature being shown off, a preview, a sandbox, an empty state, a + region under explanation. + + It reads that way because of what it does to the materials on top of it: the + hatch and the soft color blooms give translucent surfaces something to be + translucent *against*, and give shadows something to fall on. A frosted panel + over a flat grey proves nothing; over a bed it is obvious. + + That is also the rule for using one. A bed is for regions the user should + look *at*. Put content they need to work *through* on a plain surface — + patterning behind a form or a table is noise, and the calm is the point. + + Everything it is made of derives from the accent, so it follows the brand. + Set --nv-bed-tint to lead with a different color. + ========================================================================== */ + + .nv-bed { + --nv-bed-tint: var(--nv-color-accent); + --_bloom: 16%; + --_hatch: var(--nv-color-border-subtle); + + position: relative; + padding: var(--nv-space-6); + + background-color: var(--nv-color-background-subtle); + border: var(--nv-border-width) solid var(--nv-color-border-subtle); + border-radius: var(--nv-radius-xl); + + /* The hatch sits over the blooms, and both sit under the content: three + background layers rather than a pseudo-element, so nothing has to be + lifted out of the way. */ + background-image: + repeating-linear-gradient( + 45deg, + var(--_hatch) 0 2px, + transparent 2px 14px + ), + radial-gradient( + 60% 80% at 15% 20%, + color-mix(in oklab, var(--nv-bed-tint) var(--_bloom), transparent), + transparent 70% + ), + radial-gradient( + 50% 70% at 85% 25%, + color-mix(in oklab, var(--nv-color-danger) calc(var(--_bloom) * 0.7), transparent), + transparent 70% + ), + radial-gradient( + 70% 90% at 50% 100%, + color-mix(in oklab, var(--nv-color-success) calc(var(--_bloom) * 0.7), transparent), + transparent 70% + ); + + overflow: hidden; + isolation: isolate; + transition: var(--nv-transition-surface); + } + + /* Just the ground, no weather: for grouping a region without drawing the eye + to it. */ + .nv-bed--quiet { + --_bloom: 8%; + --_hatch: transparent; + } + + .nv-bed--vivid { + --_bloom: 26%; + } + + /* One color instead of three — a bed that belongs to a single state. */ + .nv-bed--single { + background-image: + repeating-linear-gradient(45deg, var(--_hatch) 0 2px, transparent 2px 14px), + radial-gradient( + 80% 100% at 30% 0%, + color-mix(in oklab, var(--nv-bed-tint) var(--_bloom), transparent), + transparent 75% + ); + } + + .nv-bed--flush { + border-radius: 0; + border-inline: 0; + } + + .nv-bed__label { + display: block; + margin-block-end: var(--nv-space-4); + font: var(--nv-font-caption); + font-weight: var(--nv-font-weight-semibold); + letter-spacing: var(--nv-letter-spacing-wider); + text-transform: uppercase; + color: var(--nv-color-text-tertiary); + } +} +@layer niveus.components { + /* ========================================================================== + Niveus — Badge + -------------------------------------------------------------------------- + In stock + 3 failing + Draft + + A small, non-interactive statement of state. Subtle by default, because a + badge is almost always beside something more important than itself; --solid + is for the one badge in a view that must be seen before anything else. + + If it can be clicked it is not a badge — that is a button, probably + .nv-button--pill --sm. + ========================================================================== */ + + .nv-badge { + --_bg: var(--nv-color-surface-inset); + --_fg: var(--nv-color-text-secondary); + --_border: var(--nv-color-border); + --_solid-bg: var(--nv-color-text-secondary); + --_solid-fg: var(--nv-color-surface); + + display: inline-flex; + align-items: center; + gap: var(--nv-space-1-5); + + block-size: var(--nv-space-5); + padding-inline: var(--nv-space-2); + + background-color: var(--_bg); + color: var(--_fg); + border: var(--nv-border-width) solid var(--_border); + border-radius: var(--nv-radius-full); + + font: var(--nv-font-caption); + font-weight: var(--nv-font-weight-semibold); + white-space: nowrap; + transition: var(--nv-transition-colors); + } + + .nv-badge--accent { + --_bg: var(--nv-color-accent-subtle); + --_fg: var(--nv-color-accent-text); + --_border: var(--nv-color-accent-border); + --_solid-bg: var(--nv-color-accent); + --_solid-fg: var(--nv-color-on-accent); + } + + .nv-badge--success { + --_bg: var(--nv-color-success-subtle); + --_fg: var(--nv-color-success-text); + --_border: var(--nv-color-success-border); + --_solid-bg: var(--nv-color-success); + --_solid-fg: var(--nv-color-on-success); + } + + .nv-badge--warning { + --_bg: var(--nv-color-warning-subtle); + --_fg: var(--nv-color-warning-text); + --_border: var(--nv-color-warning-border); + --_solid-bg: var(--nv-color-warning); + --_solid-fg: var(--nv-color-on-warning); + } + + .nv-badge--danger { + --_bg: var(--nv-color-danger-subtle); + --_fg: var(--nv-color-danger-text); + --_border: var(--nv-color-danger-border); + --_solid-bg: var(--nv-color-danger); + --_solid-fg: var(--nv-color-on-danger); + } + + /* Declared after the colors so it wins for --_bg; the color modifier is still + what decides which solid pair it reads. */ + .nv-badge--solid { + --_bg: var(--_solid-bg); + --_fg: var(--_solid-fg); + --_border: transparent; + } + + .nv-badge--square { + border-radius: var(--nv-radius-xs); + } + + /* Counts read better without a border, and with room for two digits. */ + .nv-badge--count { + min-inline-size: var(--nv-space-5); + padding-inline: var(--nv-space-1-5); + justify-content: center; + border-color: transparent; + font-size: var(--nv-font-size-2xs); + font-variant-numeric: tabular-nums; + } + + .nv-badge__dot { + inline-size: var(--nv-space-1-5); + block-size: var(--nv-space-1-5); + border-radius: var(--nv-radius-full); + background-color: currentColor; + flex: none; + } +} +@layer niveus.components { + /* ========================================================================== + Niveus — Table + -------------------------------------------------------------------------- +
+ …
+
+ + Rows of the same kind of thing. The frame handles the overflow, because a + table is the one component that legitimately refuses to get narrower — it + scrolls sideways inside its frame rather than forcing the page to. + + Numbers belong in .nv-table__number: aligned to the end and set in tabular + figures, so columns of digits line up and can be compared by eye. + ========================================================================== */ + + .nv-table-frame { + inline-size: 100%; + overflow-x: auto; + overscroll-behavior-inline: contain; + } + + .nv-table { + inline-size: 100%; + border-collapse: collapse; + font: var(--nv-font-body); + text-align: start; + } + + .nv-table th { + padding: var(--nv-space-2-5) var(--nv-space-4); + font: var(--nv-font-caption); + font-weight: var(--nv-font-weight-semibold); + letter-spacing: var(--nv-letter-spacing-wide); + color: var(--nv-color-text-tertiary); + text-align: start; + white-space: nowrap; + border-block-end: var(--nv-border-width) solid var(--nv-color-border); + } + + .nv-table td { + padding: var(--nv-space-3) var(--nv-space-4); + color: var(--nv-color-text-secondary); + border-block-end: var(--nv-border-width) solid var(--nv-color-border-subtle); + vertical-align: middle; + } + + /* The first cell in a row names it, so it carries the reading weight. */ + .nv-table td:first-child { + color: var(--nv-color-text-primary); + font-weight: var(--nv-font-weight-medium); + } + + .nv-table tbody tr:last-child td { + border-block-end: 0; + } + + @media (hover: hover) { + .nv-table tbody tr { + transition: var(--nv-transition-colors); + } + + .nv-table tbody tr:hover { + background-color: var(--nv-color-surface-hover); + } + } + + .nv-table__number { + text-align: end; + font-variant-numeric: tabular-nums; + } + + .nv-table__actions { + inline-size: 1%; + white-space: nowrap; + text-align: end; + } + + .nv-table--compact th { + padding-block: var(--nv-space-2); + } + + .nv-table--compact td { + padding-block: var(--nv-space-2); + } + + /* Rows that lead somewhere. Same physical language as an interactive card: + it responds to a pointer, so it must look like it can be pressed. */ + .nv-table--interactive tbody tr { + cursor: pointer; + } + + .nv-table--interactive tbody tr:focus-visible { + outline: none; + box-shadow: var(--nv-focus-ring); + } +} + +/* --- Utilities ----------------------------------------------------------------- */ +@layer niveus.utilities { + /* ========================================================================== + Niveus — Utilities + -------------------------------------------------------------------------- + A deliberately short list. Utilities exist for the handful of patterns that + appear in every application and have exactly one correct implementation — + not as a general-purpose styling language. Anything that needs a second + utility to look right is a component. + ========================================================================== */ + + /* Content for assistive technology only. Not display:none, which removes it + from the accessibility tree as well. */ + .nv-visually-hidden:not(:focus):not(:active) { + position: absolute; + inline-size: 1px; + block-size: 1px; + margin: -1px; + padding: 0; + border: 0; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + } + + .nv-container { + inline-size: 100%; + max-inline-size: var(--nv-layout-max-width); + margin-inline: auto; + padding-inline: var(--nv-layout-gutter); + } + + /* Vertical rhythm between siblings, on the spacing scale. */ + .nv-stack { + display: flex; + flex-direction: column; + gap: var(--nv-space-4); + } + + .nv-stack--tight { gap: var(--nv-space-2); } + .nv-stack--loose { gap: var(--nv-space-6); } + + /* A row that wraps instead of overflowing. */ + .nv-cluster { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--nv-space-3); + } + + .nv-cluster--tight { gap: var(--nv-space-2); } + .nv-cluster--between { justify-content: space-between; } + + .nv-truncate { + min-inline-size: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + /* Long-form text at a readable measure. */ + .nv-prose { + max-inline-size: var(--nv-measure); + line-height: var(--nv-line-height-relaxed); + color: var(--nv-color-text-secondary); + } +} diff --git a/docs/plan/1.0.md b/docs/plan/1.0.md index ccd09b7..9a44833 100644 --- a/docs/plan/1.0.md +++ b/docs/plan/1.0.md @@ -7,17 +7,16 @@ A finish line, so that the list of open work stops being infinite. ## Why this document exists [schapi.md](schapi.md) §3 says the stability freeze starts at 1.0 and covers -whatever is marked `stable` at that moment. **1.0 was never defined**, which -means there has been no finish line — and with no finish line every gap is -equally mandatory, the list only grows, and the project feels larger every week -regardless of how much gets done. +whatever is marked `stable` at that moment. 1.0 was never defined, which means +there was no finish line — and with no finish line every gap is equally mandatory, +the list only grows, and the project feels larger every week regardless of how much +gets done. -That feeling is structural, not a judgement about progress. This document fixes -the structure by drawing a line and putting most of the open work on the far -side of it. +That feeling is structural, not a judgement about progress. This document fixes the +structure by drawing a line and putting most of the open work on the far side of it. -> **Anything not named in this document is not part of 1.0.** It is not -> cancelled, not forgotten, and not up for reconsideration until 1.0 ships. +> **Anything not named in this document is not part of 1.0.** It is not cancelled, +> not forgotten, and not up for reconsideration until 1.0 ships. --- @@ -26,31 +25,42 @@ side of it. > **A machine one person can sit down at and use for an afternoon without > falling off an edge.** -Concretely — boots on real hardware, roots on SchFS, runs a shell and the real -toybox userland, is on the network, and puts a windowed terminal on the screen. +Concretely — boots on real hardware, roots on SchOS's own filesystem, runs a shell +and a real userland, is on the network, and puts a windowed desktop on the screen. -It is deliberately a **small complete system** rather than a rich incomplete -one. SchOS 1.0 is not Linux, does not run third-party applications, and has no -package manager. It is a system that works, end to end, with nothing in it that -lies about what it did. +It started as a deliberately small system: *no third-party applications and no package +manager*. That has changed on its own, and is worth recording rather than tidying away: +the root is an **apk root** ([packages](../architecture/packages.md)), Alpine's +programs run on it unmodified, and the desktop is Xorg with Window Maker +([desktop](../architecture/desktop.md)). What did not change is the bar: nothing in the +system lies about what it did. ### The acceptance test -One unattended run, asserted from the host, the way everything else here is -tested. When this passes, it is 1.0: +One unattended run, asserted from the host, the way everything else here is tested: ``` boot on real hardware from a USB stick → SchFS root mounts, generation counter advances - → schinit starts the compositor - → a terminal window appears and is asserted by pixel - → in it: ls, cat, a file written, a network fetch - → reboot - → the file is still there and the checksum matches + → schinit starts shells on the virtual terminals + → X starts; a window appears + → in it: ls, cat, a file written, a network fetch over HTTPS ``` -Nothing in that sentence is a feature list. Every clause is something that is -currently either broken, missing, or unproven. +**It passes. 1.0 is reached.** Where each clause stands: + +| Clause | State | +|---|---| +| boot from a USB stick on real hardware | **done** on an AMD B550 desktop, an ASUS TUF A15 laptop and an Ivy Bridge HP all-in-one | +| SchFS root, generation counter | **done** — the root is a SchFS image, in RAM when live | +| a terminal to type in | **done** — a shell on the first six of the twelve virtual terminals (`etc/schinit/*-shell.job`) | +| X starts, a window appears | **done** (`startx` → Window Maker + xterm); *asserted by pixel* has no automated test yet — `bootcheck` drives the serial console, and the screenshots are looked at | +| `ls`, `cat`, a file written | **done** | +| a network fetch over HTTPS | **done** (`wget`, `curl`, `apk`) | + +The test used to end with *reboot — the file is still there*. That clause is **dropped**, deliberately: 1.0 is a **live** +system. The root lives in RAM and nothing written to it survives a reboot, and that is what the project is calling finished. +Persistence (an installer, or a persistent partition) is after 1.0 — see [roadmap.md](roadmap.md) §5. --- @@ -59,11 +69,10 @@ currently either broken, missing, or unproven. **No new capability is started until this register is empty.** The failure this exists to stop is not laziness and not scope creep — it is -*abandonment by promotion*. A thing gets built to the point where it works, the -next thing becomes more interesting, and the first one is left in a corner: -present in the tree, not finished, not deleted, and not visible in any list. -Enough of those and the tree stops matching its own documentation, which is the -state this register was written to end. +*abandonment by promotion*. A thing gets built to the point where it works, the next +thing becomes more interesting, and the first one is left in a corner: present in the tree, +not finished, not deleted, and not visible in any list. Enough of those and the tree stops +matching its own documentation. Two rules, and they are the same rule seen from either end: @@ -71,73 +80,46 @@ Two rules, and they are the same rule seen from either end: > > **Adding a row requires closing one.** Two things in flight. Never three. -A row leaves this table in one of exactly two ways. Either it is **finished** — -and finished means a description exists in [../architecture/](../architecture/), -not that the code works — or it is **deleted**, and deleted means gone from the -tree, not marked for deletion. A directory that has said "to be deleted" for -months is the corner, not the cure; `projects/schwm/` and `projects/schde/` -proved that and are now actually gone. +A row leaves this table in one of exactly two ways. Either it is **finished** — and finished +means a description exists in [../architecture/](../architecture/), not that the code works — +or it is **deleted**, and deleted means gone from the tree, not marked for deletion. A +directory that has said "to be deleted" for months is the corner, not the cure. ### Built, and not connected to anything -The galling ones. The work exists, is tested, and nothing uses it. - | | Where it stopped | |---|---| -| ~~**The text layer**~~ | **Half closed, and the half that closed is the one that mattered.** The console draws real TrueType through `font_ops` now: cells hold a codepoint, the cell size comes from the font's measured advance, and `kernel/dev/ttf.c` has a caller that is not a self-test. What is still unused is `text.c`'s *proportional* path — the console remains a fixed grid. See Gate 0. | -| **A font from disk** | Still true, and now deliberate rather than pending: the console typeface is linked into the kernel image, because a panic during boot has to be readable before there is a filesystem. Loading a *second* font from disk is the open part. | -| **Socket activation** | `schinitd` parses `[Socket name]` and remembers the path. Nothing holds the socket. **This is the entire reason for copying launchd**, and it is the half that was not built. | -| **`display_ops.wait_vblank`** | In the vtable, implemented by the VBE driver, left null by virtio-gpu. A compositor that needs it cannot rely on it. | +| **The in-kernel LDLS window** | `vm.c` maps the kernel image a second time in the top 2 GB at every boot, and `include/ldls/ldls.h` describes a `/dev/ldls` loader — the start of loading Linux modules *into the kernel*, which [ldls.md](../architecture/ldls.md) rule 2 says is not how it is done. Nothing calls either. **Delete it**, or decide. | +| **`display_ops`** | `acquire` and `wait_vblank` are in the vtable, implemented by one driver at most, with no consumer: Xorg draws through `/dev/fb0`. Kept for the GPU work ([gpu.md](gpu.md)); that is the reason it stays. | +| **Socket activation** | `schinitd` parses `[Socket name]` and remembers the path. Nothing holds the socket. This is the entire reason for copying launchd, and it is the half that was not built. | +| **`sys_delete`, `sys_show`, `sys_showstat`** | Orphaned entries from the old v1 filesystem's recycle bin. They die with `fs.c`. | ### Started, and stopped part-way | | Where it stopped | |---|---| -| **`foundations.md`** | All but item 4 (`setsockopt`) built: descriptor passing, `memfd`, filesystem-named `AF_UNIX`, dynamic linking, and epoll/eventfd/timerfd/signalfd. | -| ~~**`hybrid.md`**~~ | **Closed.** All four mechanisms built; H1, H2 and H3 done. H4 — the USB eviction — is *withdrawn* rather than outstanding: USB is the bus peripherals arrive on, not a peripheral, and an eviction that has to leave an identical driver behind has moved nothing. See [hybrid.md](hybrid.md), "Decided: USB stays in the kernel". | -| ~~**`ldls.md`**~~ | **Closed.** L0 through L4 built: the loader handles 2,440 stock modules, and both `8139too` and Linux's `e1000` run in a SchOS process through `/dev/pci` with `ping` working over each. The e1000 run is a handover — the kernel was driving that NIC and gave it up. Described in [../architecture/ldls.md](../architecture/ldls.md). Two drivers, one kernel version, one machine. | -| **Schcom v0** | 715 lines that composite, over a wire format that is superseded. Frozen: it runs, nothing more is added, and it is kept only because it is the one thing in the tree that has ever put a client's pixels on screen. | -| ~~**`debugger.md`**~~ | **Closed.** D0 through D5 built, including the network transport: `schdbgd` bridges TCP to `/dev/dbgro`, ships disabled, binds loopback, and cannot reach a view that changes state. Described in [../architecture/debugging.md](../architecture/debugging.md). | - -**The row LDLS spent, and gave back.** `hybrid.md` closed — H3 built, H4 -withdrawn — and `ldls.md` took the slot it freed. It has since closed too, so -the slot is free again. Worth being honest that this is -the largest thing ever to enter this register, and that it was **explicitly not -part of 1.0**: nothing in the acceptance test needs a foreign driver. It was -taken on because the mechanisms it uses were finished and idle, and because -the alternative to it is writing a driver for every device that exists. - -**The row the debugger spent, and gave back.** Adding one requires closing one, -and *A released PCI device* closed when H2 landed. It is worth saying plainly -that the debugger is not a capability of the OS the way every other row here is: -nothing in it ships to a user or appears in the acceptance test. The case for it -is that **Gate 0 is a list of bugs to find in a kernel that could not be asked -anything** — so it is the instrument the rest of this register is surveyed with, -and it earned its slot by making every other row cheaper to close. - -It has closed. **Every plan in `docs/plan/` that was open is now closed**, which -means the next thing is Gate 0 itself: the survey, with the instrument built. - -**Closed since this register was written.** *A released PCI device* — release is -now symmetric with claim (bus mastering cleared before the DMA is freed, a -refusal when the device is the only display), a supervised job starts the -replacement, and it is described in -[../architecture/devices.md](../architecture/devices.md) under "Taking it back". +| **LDLS on real hardware** | `r8169` registers its interface on the laptop and reads its PHY; whether it carries traffic there is **not confirmed**. The native driver is out of the registry and stays as the fallback. | +| **The laptop's embedded controller** | The EC is found and served (battery and AC read through it); **SCI queries are not handled**, so events from it are not seen. | +| **Epiphany (WebKit)** | `apk add epiphany` installs it; its web-process child dies at start-up ("short read from the peer"). NetSurf runs. Not diagnosed. | ### Named in a "what it does not do" and still true -Not corners — these were written down honestly on the day and stayed written -down. They are here so the register is the whole picture rather than the -flattering part of it. +Not corners — these were written down honestly on the day and stayed written down. They are here +so the register is the whole picture rather than the flattering part of it. Each is described where +it lives. | | | |---|---| -| `/proc//exe` is a regular file | so `readlink()` fails and `ps -o comm` prints `[init]` | -| `getrusage` | does not distinguish `RUSAGE_CHILDREN` from `RUSAGE_SELF` | -| No slab allocator | every subsystem's fixed-size table is a consequence | -| No `getsockname`/`getpeername` | and `accept()` does not report an AF_INET peer | -| No routing table | first interface wins, which is a rule and not routing | +| No page cache | writable `MAP_SHARED` of a file is refused — [kernel.md](../architecture/kernel.md) | +| `mprotect` is a no-op, nothing has NX | [kernel.md](../architecture/kernel.md) | +| No routing table | first interface wins, which is a rule and not routing — [networking.md](../architecture/networking.md) | | No descriptor garbage collector | a socket passed over itself and never received leaks the pair | +| udev sees only the kernel's own devices | PCI and USB are not in the `/sys/devices` tree — [udev.md](../architecture/udev.md) | +| No EHCI driver | USB 2.0 ports wired only to EHCI do nothing — [usb.md](../architecture/usb.md) | +| Touchpads are mice | no two-finger scroll — [desktop.md](../architecture/desktop.md) | +| `ru_stime` is zero | [kernel.md](../architecture/kernel.md) | +| No IOMMU | a driver in a process is isolated against bugs, not malice — [devices.md](../architecture/devices.md) | +| One audio chip family | AC'97 only; laptops have HDA — [audio.md](../architecture/audio.md) | ### Frozen — scoped, unstarted, and not to be started @@ -147,431 +129,75 @@ Neither cancelled nor in progress. **Do not open these.** |---|---| | `projects/schinst/` | the installer, the installation environment, `.sip` | | `projects/schfs-linux/` | SchFS as a filesystem for Linux | -| [roadmap.md](roadmap.md) §4 | the interactive crash screen | -| [roadmap.md](roadmap.md) §6 | SchFS beyond SchOS | +| [gpu.md](gpu.md) | amdgpu and nouveau through LDLS — measured, with the work it needs written down | +| [finch.md](finch.md) | Finch inside SchOS (the language itself is finished and lives in its own repository) | | [schapi.md](schapi.md) | the API layering, designed and not built | -Each of those is well argued and none of them is on the path to the acceptance -test above. That is the whole test for whether something belongs in this section: -**not "is it worth doing" but "does 1.0 fail without it".** +Each of those is well argued and none of them is on the path to the acceptance test above. That is +the whole test for whether something belongs in this section: **not "is it worth doing" but "does 1.0 +fail without it".** --- -## The four gates +## The gates -In order. **Do not start a gate before the one before it closes**, and closing -means a description exists in [../architecture/](../architecture/) — see -[the rule](../README.md#the-rule-that-keeps-this-from-rotting). +In order. **Do not start a gate before the one before it closes**, and closing means a description exists in +[../architecture/](../architecture/). -### Gate 0 — Fix what is already wrong - -**This comes first, before any new capability.** The desktop work is the -temptation and it is the wrong thing to do next: a compositor built on a kernel -with an unexplained hang and a socket layer that reports success for things it -did not do will fail in ways indistinguishable from its own bugs. That is the -same argument that put `netdev_selftest()` before the TCP/IP stack, and it was -right then. - -**Things that are actively wrong:** - -- [x] ~~**The `ls -lR` hang.**~~ **Not reproducible.** Instrumented and hunted; - see below. Left here rather than deleted because the absence of a result - is the result. -- [x] ~~**`setsockopt` returns 0 without setting anything.**~~ Six options now - genuinely reach lwIP's pcb and everything else is `ENOPROTOOPT`. Round-trip - checks in `cmd/socktest`, which a permissive stub cannot pass: it has to - read back a value it was set *and* refuse an unknown option. -- [x] ~~**`accept4` silently drops its flags.**~~ It is its own table entry now - rather than an alias for `accept` — an alias would have had to read a - fourth argument that `accept` never passes, inventing a `SOCK_NONBLOCK` - out of whatever the caller last left in that register. -- [x] ~~**Timestamps on everything created after boot read 1970.**~~ Both - `know()` (SchFS) and `now_sec()` (v1) returned `uptime()` under comments - saying the kernel had no wall clock. It has had one since `time_init()` - started reading the RTC; the comments outlived the condition. Two errors, - not one — `uptime()` counts 10 ms ticks and they were used as seconds, so - the clocks also ran 100× fast. -- [x] ~~**User processes are corrupted on SMP.**~~ **Found and fixed.** - `syscall_entry` parked `%rax`, `%rcx` and `%rdx` in three *globals* - across the `FS_BASE` swap, under a comment arguing that `SYSCALL` - clearing `IF` made it safe. `IF` answers for an interrupt on *this* CPU - and says nothing about a second CPU running the same three stores — and - `%rcx` is the user return address `SYSRET` uses, so the CPU that lost the - race returned its process into another process's instruction stream. - - That is the whole of it: `rip 0x0` was a process handed a zero and - following it, and `0xffffffffffffffea` was `-EINVAL` in a swapped `%rax` - used as a pointer. Per-CPU words at `%gs:32..48` now. Measured at four - CPUs against the same pristine image: **10 faults in 10 boots before, 0 - in 18 after.** See [../architecture/kernel.md](../architecture/kernel.md), - "Per-CPU state". - -- [x] ~~**`$(command substitution)` hangs the shell.**~~ **Not a kernel bug.** - `ports/zsh/Makefile` answered configure's "does `kill(pid, 0)` return - ESRCH correctly?" with `no`, which makes zsh `#define ESRCH EINVAL`. - `waitforpid()` ends its wait on `errno != ESRCH`, so with the kernel - correctly returning ESRCH (3) and zsh comparing against EINVAL (22), the - shell waited for ever on a child it had already reaped. One word in a - cross-compile cache. Autoconf's own default for that check is `yes`; - omitting the line would have been right. See - [../history/kernel.md](../history/kernel.md), section 7. -- [x] ~~**`getrusage` does not distinguish `RUSAGE_CHILDREN`** from - `RUSAGE_SELF`.~~ It did not distinguish anything: it ignored `who` and - reported the machine's *uptime* as the caller's user time, so every - process of every age answered with the same number. Per-process CPU time - is sampled at the timer tick now, children's time is rolled up on reap, - and `wait4()`'s fourth argument — a `struct rusage *` that was read off - the stack and thrown away — is filled. That last one is why zsh's `time` - reported `16777216.01s user` for everything: 2^24 seconds is - uninitialised memory, and it never varied because it was never written. - `time` now says 97% cpu for a busy loop and 0% for `sleep 2`. - `ru_stime` is a known zero; see - [../architecture/kernel.md](../architecture/kernel.md). -- [x] ~~**`/proc//exe` is a regular file, not a symlink.**~~ - `lrwxrwxrwx ... exe -> /bin/schinit`, and `readlink` returns it. It also - needed a real `st_size`, which every other procfs file deliberately - reports as zero: `readlink(2)` bounds its copy by it, so a symlink of - size 0 resolves to the empty string — which looks like it worked and is - worse than the regular file was. -- [x] ~~**OVMF memory reporting.**~~ The region-list fix did close it. 1 GB - reports 1017 MB usable, 2 GB reports 2041 MB, which is the right shape: - the firmware keeps about 7 MB either way. -- [x] ~~**`ps` printed a header and nothing else.**~~ Found while checking the - `exe` fix. `ps` with no arguments shows only processes on *this* - terminal, and `/proc//stat` hardcoded `tty_nr` to 0, so nothing - could ever match — `ps -e` always worked, which is what hid it. Fields - 14-17 (utime/stime/cutime/cstime) were hardcoded zero too, so `TIME` was - `00:00:00` for everything however long it had run. - -**Work already done and not connected:** - -These are the galling ones — the replacement exists, is tested, and is unused. - -- [x] ~~**Point the console at a real typeface.**~~ It draws JetBrains Mono - through `font_ops` and `ttf.c`. The blocker was not the font: `vtcell.ch` - was **one byte** holding a CP437 index, so `Ą` (U+0104) could not be - *stored*, whatever was drawing it. The cell is a `uint32` codepoint now - and the cell size comes from the font's measured advance. -- [x] ~~**Delete the cell grid.**~~ Done as far as it should be done, and the - rest of it retired — which needs explaining, because the item as written - asked for something a terminal cannot be. - - `include/gfx/text.h` made the case against the grid in four charges: it - cannot do variable-width glyphs, cannot position text off a multiple of - eight, cannot anti-alias, and cannot hold a codepoint above 255. **All - four were about the drawing layer, and all four are now false** — glyphs - carry metrics, PSOGM positions text at arbitrary pixels, the TrueType - path blends 8-bit coverage, and `vtcell.ch` is a `uint32`. - - What is left is a grid in `vt.c`, and that one is not in the way: it is - what a terminal *is*. VT100 escapes address columns, `TIOCGWINSZ` - reports rows and columns, and every program that draws a box assumes - both. Deleting it would not modernise the console, it would stop it - being a terminal. - - So the real remainder was the duplicate: `gfx.c` walked `font_data` - itself and blitted glyphs its own way, while `text.c` did the same job - into a `struct surface` for nobody but a self-test. Two implementations - that had to agree about a cell size only one of them was told had - changed — and they did not agree, which is why every line of PSOGM once - drew the next character's bitmap underneath itself. There is one blitter - now: `gfx.c` hands `text_draw_glyph` the cell *as a surface of its own*, - so clipping a tall glyph to its line is the same bounds check that clips - to a screen. The bitmap is reached through `font_builtin()` like any - other provider, and `gfx.c` no longer includes `font.h`. -- [x] ~~**Load a font from disk**~~ and point the console at it. The built-in - one is linked in on purpose — an early panic has to be readable — so this - is about a *replaceable* font, not about having one. Drop a TrueType file - at `\schos\console.ttf` on the ESP and the console is drawn with it. - - **The loader reads it, not the kernel**, and that is the whole design - decision. The kernel could open the file once the root filesystem is - mounted — but the console has been drawing since long before that, so - switching then means re-measuring the cell and re-laying out every - terminal mid-boot, which wipes the kernel log off the screen at exactly - the moment a machine being brought up for the first time needs it. The - loader allocates the pages as `EfiReservedMemoryType`, so the E820 - handoff marks them reserved, the page allocator never offers them, and - the kernel uses the file *in place* — no copy, and no need for a - contiguous allocator the kernel does not have. -- [x] ~~**Interrupt-driven receive.**~~ The e1000's handler calls - `netdev_rx_signal()`, which ends the network thread's wait early; the frame - is still *pulled* by the thread, because an interrupt handler can neither - sleep on the buffer cache nor take the lwIP lock. The wait stays on the - tick so lwIP's timers keep their floor. Proven rather than assumed: - `/proc/netif` gained `wake_rx`/`wake_tick`, and across three pings - `rx_frames` and `wake_rx` both went 2 -> 6. - -**Structural, because everything after this compounds on it:** - -- [x] ~~**A slab or pool allocator.**~~ `kernel/core/kmem.c`. Slabs are pages - from `kalloc()` with a header at the front; free objects hold the free - list in their own first eight bytes, so an object needs no header and - freeing needs no search — the object's page base *is* its slab, and the - slab names its cache. Two interfaces: named `kmem_cache` for a subsystem - with one kind of thing, and `kmalloc`/`kmfree` over power-of-two classes - for everything else. Visible in `/proc/slab`, asserted by - `make kmem-test` on the host — the same arrangement as `schfs-objtest`, - so the bookkeeping is checked in a second rather than in a boot. - - **It does not do what this row said it would, and that is worth - recording.** The claim was that every fixed table — `NPROC`, `NSOCK`, - `NDEVICE`, `NFILE`, `NINODE`, `NBUF` — exists to work around `kalloc`. - Measured: `struct proc` is 10656 bytes, `struct socket` 4928, `struct - buf` 4272. **All three are larger than a page**, so a page-backed slab - cannot hold them whatever it does; `NINODE` is a cache and is supposed to - be bounded; and `NFILE`/`NDEVICE` are 48 and 120 bytes, where the table - costs 24 KB and 3.8 KB and the ceiling has never been reached. The tables - were not all working around `kalloc`. Freeing the big ones needs a - contiguous multi-page allocator, which is a different thing and is not - written. - - What it does fix is real and was measured rather than assumed. A pipe is - 640 bytes and came from a whole 4096-byte page, so every `|` in a shell - command burned 3.5 KB it never touched — **six pipes per page now**. And - `procread()` built its answer in a 2048-byte array on the kernel stack, - under a comment that had warned about exactly that since it was written; - its frame is 64 bytes now instead of ~2100. -- [x] ~~**SchFS on-disk format version policy.**~~ Done. `SCHFS_VERSION` for - frame changes, plus `compat`/`ro_compat`/`incompat` feature flags in what - was `reserved0` — so existing images keep every field offset and read - unchanged. `uber_check()` now distinguishes *damaged* (step over it; that - is what the ring is for) from *too new* (stop), because falling back to an - older uberblock mounts a stale filesystem and reports success. Proven on a - forged image: the old code mounted the stale root and exited 0. - Still owed: an actual upgrade step, which is only writable once there are - two formats. See [../architecture/schfs.md](../architecture/schfs.md). - -**Found while doing the above, and fixed:** - -- [x] ~~Every `AF_INET` socket failed with `ENFILE` until DHCP bound.~~ - `inet_new()` required an *address* before it would hand out a socket, and - reported the refusal as "file table overflow". Creating a socket is not - the same as having somewhere to send; a bind or connect with no route - fails on its own with an error that says so. `net_is_ready()` (lwIP up) is - now distinct from `net_is_up()` (DHCP bound). -- [x] ~~`socktest` asserted "AF_INET is refused, not faked".~~ True when written, - false since AF_INET landed — and **green the whole time**, because the - suite runs before DHCP binds and the `ENFILE` above made the refusal look - real. A test asserting a failure has to say *which* failure. - -**The suite itself:** - -Both of these were found by running `make schfs-check` rather than by reading -it, and both predate the current work — confirmed by stashing and re-running. -They are here rather than filed as nice-to-haves because a suite that is red for -known reasons stops being read, and then it is red for unknown reasons too. - -- [x] ~~**`make schfs-check` fails at baseline.**~~ Fixed, and it was not only a - test bug. The allocation map was taken through a `palloc` callback and - there was **no way to give it back** — no `schfs_space_release`, and a - comment in `schfs_space_init` reading *"caller frees what was taken"* for - an API the caller did not have. - - So the kernel leaked the whole map on **every unmount**, up to 2 MB a - time, and `schfs_space_init`'s own error path stranded a page it had - already taken because `npages` was only raised after *both* allocations - succeeded. The test's 518 leaked pages were the symptom that pointed at - it. - - The map now carries its own `pfree` so it can only be freed by the - allocator that filled it, `schfs_vol_open`/`schfs_vol_format` release any - map the volume already holds, and unmount releases. `schfs_objtest` - asserts the balance — 520 pages taken, 520 given back — so the next leak - fails the suite instead of being absorbed by it. - -- [x] ~~**31 UndefinedBehaviorSanitizer errors.**~~ Fixed, plus two the test - never reached. One root cause: a leaf's value area holds variable-length - records packed end to end, so a record begins wherever the last one ended - — a multiple of 8 only by luck — and the code read them by casting a - pointer into the block, which tells the compiler they are aligned when - they are not. - - The seven record types that live at arbitrary offsets now carry - `SCHFS_PACKED`. **No on-disk layout changed**, and that was measured - before it was applied rather than assumed: every one already had explicit - `pad` fields, so sizes and member offsets are identical with and without - the annotation. Seven `_Static_assert`s now pin those sizes, because a - record that silently grows misreads every existing volume from that point - on and looks like corruption rather than like a struct that changed. - - `schfs_node_hdr`, `schfs_item`, `schfs_kptr` and `schfs_uberblock` are - deliberately left alone — the node header is exactly 32 bytes, so - everything after it is already 8-aligned, and annotating them would cost - byte-wise access on the tree's hottest paths to fix a problem they do not - have. - - Verified by reading an image written *before* the change: full - `schfs_fsck --check` passes on structure, checksums, key ordering, data - blocks and derived free space. - - Also caught on the way: `mkschfs` passed a null pointer to `qsort` for an - empty directory. Every implementation ignores it when the count is zero; - the standard does not say they have to. - -**The identity item:** - -- [x] ~~**Boot from a USB disk.**~~ The root device is probed at boot instead of - compiled in: ramdisk (only in a `RAMDISK_ROOT` build), then USB, then the - IDE disks, first SchFS volume wins. Verified with **no IDE disk attached - at all** — boots, mounts the stick, writes a file, and the file is still - there on the next boot. `dmesg` says `root: USB disk (device 3)`, and a - machine with no root names every candidate it looked at instead of - panicking with "no root filesystem". See - [../architecture/boot.md](../architecture/boot.md). - -#### The `ls -lR` hang, and why it is closed - -Hunted with the instrumentation it asked for, under a workload heavier than the -one that originally triggered it — ten iterations of `ls -lR /bin`, interleaved -with writes and `cd`, 33 execs and 13378 tree descents. Measured across it: - -``` -inode_peak 9 / 200 unchanged -file_peak 9 / 512 unchanged -buf_pinned_peak 1 / 256 unchanged -schfs0 locked 0 tx_owner_pid 0 tx_depth 0 -``` - -Nothing climbed, nothing wedged, and the repaired `holdingsleep` assertion never -fired. **That eliminates this file's own stated hypothesis** — "a lock or -reference-count leak somewhere in the `*at()` paths" — and it is very likely the -bug was fixed in passing by the `*at()` dirfd rewrite, the getdents cookie -rewrite, or SchFS becoming the root, all of which landed after it was reported. - -It is closed rather than chased further, and that is a deliberate application of -the two-things-in-flight rule. The instrumentation is permanent: if it ever -happens again, `/proc/fsstat` names the table, or shows `locked 1` with -`tx_owner_pid 0` and the volume lock is orphaned. That is a much better position -than the one it was reported from. - -Two real defects were found on the way and fixed, neither proven to be the -cause: `holdingsleep()` never compared the owning pid, which disarmed all seven -`panic()` assertions built on it; and SchFS's `tx_mine()` compared a bare -`struct proc *`, which a recycled ptable slot makes ambiguous. - -### Gate 1 — The foundations - -[foundations.md](foundations.md), items 1–4. Descriptor passing, a shareable -memory object, `AF_UNIX` in the filesystem namespace. - -Nothing above needs these; everything below stops without them. - -- [x] ~~**Item 1 — `sendmsg`/`recvmsg` with `SCM_RIGHTS`.**~~ Built and tested; - fourteen checks in `cmd/socktest`, each moving data through the passed - descriptor rather than inspecting it. Described in - [../architecture/networking.md](../architecture/networking.md#descriptor-passing). - This was also mechanism 1 of [hybrid.md](hybrid.md) — one primitive for - both, which is the whole reason those are one plan. -- [x] ~~**Item 2 — a shareable memory object.**~~ `memfd_create`, `ftruncate`, - `mmap(MAP_SHARED)`. Built on the `shmget` segments exactly as - [foundations.md](foundations.md) proposed — the pages, refcounts, - `no_free` VMAs and the fork/exit accounting were all already right; what - memfd adds is a segment reached by *descriptor* rather than by name or - System V key, which is the whole reason it exists on Linux. - `cmd/memfdtest`: seventeen checks, ending with a memfd sent over a - socket to a receiver that closes every inherited copy first. -- [x] ~~**Item 3 — `AF_UNIX` sockets in the filesystem namespace.**~~ `bind()` - creates a `T_SOCK` node; `connect()` resolves the path and matches the - socket bound to *that inode*, not to a string. `ls -l` shows - `srwxr-xr-x`, `stat()` reports `S_IFSOCK`, `unlink()` removes it, and a - second bind to a live path is `EADDRINUSE` *from the directory* — which - is what makes a socket file left by a crashed server refuse a rebind, as - it must. `cmd/socktest` section 15. `flock()` is still missing and is - recorded as such. - -### Gate 2 — Init as a role — **done** - -Milestones I0–I3, all built. PID 1 forks a supervisor and spawns jobs on its -behalf; the job table lives in `/proc/schinit`; a heartbeat runs both ways; and -`proc_reincarnate()` re-execs PID 1 in place rather than panicking. Both failure -paths are exercised by `sysrq` keys rather than reasoned about. - -Before the desktop rather than after, because a compositor being brought up for -the first time crashes constantly, and something that restarts it and says why is -worth far more during that work than at the end of it. - -The description is now [../architecture/init.md](../architecture/init.md); the -plan document it replaced has been deleted, per -[the rule](../README.md#the-rule-that-keeps-this-from-rotting). - -### Gate 3 — The desktop - -[xorg.md](xorg.md): Xorg on the framebuffer, twm and xterm, then Window -Maker. Wayland ([wayland.md](wayland.md)) is not being worked on -- what it -needs, DRM/KMS above all, is far larger than what X needs. - -**W4 and W5 are not in 1.0.** Multi-window focus policy and a panel are -post-1.0. One terminal window that works is the bar. +| Gate | What | State | +|---|---|---| +| **0 — Fix what is already wrong** | the SMP register corruption, `setsockopt` that lied, timestamps that read 1970, the `ls -lR` hang (hunted, not reproducible — [history](../history/kernel.md) §24), … | **closed** | +| **1 — The foundations** | descriptor passing, a shareable memory object (`memfd`), `AF_UNIX` in the filesystem, dynamic linking, the event descriptors | **closed** — [kernel.md](../architecture/kernel.md), [networking.md](../architecture/networking.md) | +| **2 — Init as a role** | `schinit`, the supervisor, the heartbeat, PID 1 re-exec'd in place | **closed** — [init.md](../architecture/init.md) | +| **3 — The desktop** | Xorg on the framebuffer, Window Maker, a terminal | **closed** — [desktop.md](../architecture/desktop.md) | +| **4 — A root that survives a reboot** | dropped from 1.0 by decision; it is the installer, after 1.0 | **not in 1.0** | --- ## Not in 1.0 -The point of this list is that it is *written down*, so it can stop occupying -attention. Nothing here is cancelled. +The point of this list is that it is *written down*, so it can stop occupying attention. Nothing here is cancelled. | | Why it waits | |---|---| -| **The rest of the hybrid kernel** ([hybrid.md](hybrid.md)) | Still not a completeness requirement — but the mechanisms and the first eviction happened early, because the alternative to a driver compatibility layer is writing a driver for every device that exists, and that is not a plan. What is deferred is the foreign driver host, not the interface it needs. | -| **Mesa, SDL3** | The kernel and musl do dynamic linking now (`cmd/dynhello`), but every shipped program is still static. Mesa is the single biggest post-1.0 unlock and it is a whole project. | -| **A page cache** | `memfd` covers the desktop's needs. General writable `MAP_SHARED` of a file is a real project with no 1.0 consumer. | -| **`.sip` and the installer** | 1.0 installs by writing a USB stick. That is how most systems start and it is honest. | -| **SchFS snapshots, FUSE, Linux module, sparse files, online fsck** | The format version (Gate 0) is the prerequisite for all of them, and it is the only part on a clock. | -| **The interactive crash screen** | PSOGM renders, prints a full report and emits a QR code. That is enough. | +| **GPU acceleration, Mesa** | [gpu.md](gpu.md). A whole project; software rendering is enough to use the machine. | +| **A page cache** | General writable `MAP_SHARED` of a file is a real project with no 1.0 consumer. | +| **SchFS snapshots, FUSE, a Linux module, sparse files, online fsck** | The format version is the prerequisite and it exists; the rest is [roadmap.md](roadmap.md) §6. | | **CFF/OTF fonts, kerning, text layout** | TrueType and fixed advances are enough for a terminal. | -| **Routing, `getsockname`/`getpeername`, IPv6** | One interface, one default route, works. | -| **Multiple outputs, damage tracking, subsurfaces, xkbcommon** | One screen, full repaints, one keymap. | -| **Audio** | Does not exist in any form and is not required to use a machine. | -| **Panel, notifications, settings, file manager** | A terminal is a desktop. | -| **SchAPI itself** | Deliberately. It should be designed *from* the compositor's experience, not before it. | +| **Routing, IPv6, Wi-Fi** | One interface, one default route, works. | +| **HDA audio, recording, mixing** | AC'97 plays; that is sound. | +| **Threads' loose ends, `ptrace`, `seccomp`** | Whatever a package needs is found by running it. | +| **SchAPI itself** | Deliberately. It should be designed from the desktop's experience, not before it. | --- ## The rule for this list -**Adding to Gate 0–3 requires deleting something else from it.** +**Adding to the gates requires deleting something else from them.** -Not a productivity trick — a forcing function. The reason this project feels -overwhelming is that ~20 fronts have been open simultaneously, and no amount of -discipline makes that workable. The fix is structural. +Not a productivity trick — a forcing function. The reason this project felt overwhelming is that ~20 fronts +were open simultaneously, and no amount of discipline makes that workable. The fix is structural. And one working limit, worth holding to literally: > **Two things in flight. Never three.** -One in Gate 0 and one being written up. Everything else is closed — in the -document, and out of your head. - ---- - ## How you will know it is going well -Not by velocity. By this: - -**Every closed item leaves a description behind.** When the `ls -lR` hang is -fixed, `history/kernel.md` gains an entry saying what it actually was. When the -console moves onto the text layer, `architecture/graphics.md` loses a line from -its *What it does not do* section. - -Those "does not do" sections shrinking is the real progress bar, and it is the -only one that does not lie. - ---- +Not by velocity. By this: **every closed item leaves a description behind.** When the `ls -lR` hang was +closed, `history/kernel.md` gained an entry. When a gap closed, a "what it does not do" section shrank. +Those sections shrinking is the real progress bar, and it is the only one that does not lie. ## A note worth keeping -This document was written because the work started to feel overwhelming, and -that is worth recording rather than tidying away — because the feeling was -about the *shape* of the work and not its quality, and the two are easy to -confuse when you are inside it. +This document was written because the work started to feel overwhelming, and that is worth recording rather +than tidying away — because the feeling was about the *shape* of the work and not its quality, and the two are +easy to confuse when you are inside it. -The evidence at the time: SMP working on real hardware after five bugs stacked -behind each other; a copy-on-write filesystem whose crash test cuts the power one -write later, 119 times, and demands a clean mount after each; a TCP/IP stack -asserted from both ends; an xHCI driver written from the specification; and a -documentation tree that records why things are strange so they do not get -un-fixed by accident. +The evidence at the time: SMP working on real hardware after five bugs stacked behind each other; a copy-on-write +filesystem whose crash test cuts the power one write later, 119 times, and demands a clean mount after each; a +TCP/IP stack asserted from both ends; an xHCI driver written from the specification; and a documentation tree that +records why things are strange so they do not get un-fixed by accident. Since then: a desktop, real Linux +programs and Linux's own drivers, running. -None of that is the output of a project in trouble. It was a project with too -many tabs open. +None of that is the output of a project in trouble. It was a project with too many tabs open. diff --git a/docs/plan/README.md b/docs/plan/README.md index 6f631b1..81178ba 100644 --- a/docs/plan/README.md +++ b/docs/plan/README.md @@ -1,143 +1,65 @@ # What happens next -Everything in this directory is **unbuilt**. When a piece of it is finished it -leaves — deleted from here, rewritten as a description in -[../architecture/](../architecture/). See [the rule](../README.md#the-rule-that-keeps-this-from-rotting). +Everything in this directory is **unbuilt**. When a piece of it is finished it leaves — deleted from here, +rewritten as a description in [../architecture/](../architecture/). See +[the rule](../README.md#the-rule-that-keeps-this-from-rotting). > ## Read [1.0.md](1.0.md) first. > -> It draws a finish line and puts most of the work below on the far side of it. -> **Anything not named there is not being worked on**, and the documents in this -> directory describe more than 1.0 contains. -> -> It also holds **[the register of unfinished work](1.0.md#the-freeze-and-the-register-of-unfinished-work)**, -> which is the one place anything half-built is listed. No new capability starts -> until that register is empty, and a row leaves it only by being finished or by -> being deleted from the tree. +> It draws the finish line, says where the system stands against it, and holds +> **[the register of unfinished work](1.0.md#the-freeze-and-the-register-of-unfinished-work)** +> — the one place anything half-built is listed. No new capability starts until that register is +> empty, and a row leaves it only by being finished or by being deleted from the tree. --- -## The three current decisions - -Taken together, because they turn out to need the same foundation. +## What is here | | | |---|---| -| **[alpine.md](alpine.md)** | **Packages.** The root is managed by Alpine's apk: Alpine's binaries run unmodified, and SchOS itself is the apk package schos-base. | -| **[xorg.md](xorg.md)** | **The desktop.** Xorg on the framebuffer with the fbdev and evdev drivers; twm, then Window Maker. Replaces wayland.md for Gate 3. | -| **[wayland.md](wayland.md)** | *Not being worked on; see xorg.md.* The GUI stack becomes Wayland. SchWM and SchDE are **deleted** — actually gone from the tree, not marked; Schcom survives and changes its wire format. | -| ~~**hybrid.md**~~ | **Built.** All four mechanisms exist, the GPU driver runs outside the kernel, it survives being killed, and the display mode can be changed from a process. H4 (USB) was *withdrawn* rather than left open — USB is the bus peripherals arrive on, not a peripheral. | -| ~~init.md~~ | **Built.** PID 1 is now a role the kernel refills; the description moved to [../architecture/init.md](../architecture/init.md). | +| **[1.0.md](1.0.md)** | **The finish line.** What 1.0 is, the acceptance test and where each clause stands, the register, and everything explicitly deferred. | +| **[roadmap.md](roadmap.md)** | Everything else that is scoped and agreed: storage and the installer, graphics, networking, the hardware that makes a laptop a laptop, SchFS beyond SchOS, the kernel's own gaps. | +| **[gpu.md](gpu.md)** | Linux's own `amdgpu` through LDLS, and the `/dev/dri` a process has to serve. Measured, with the work it needs written down. | +| **[finch.md](finch.md)** | **Later.** The author's own LLVM language for SchOS's applications and tools in place of C. The language is finished and self-hosting in its own repository; the plan here is a target, bindings generated from `include/uapi/`, and the compiler in the image. | +| **[schapi.md](schapi.md)** | The long-term API layering and the compatibility promise. Direction agreed, nothing built. | -And the one they all wait on: +## What used to be here, and is built -| | | +Plans leave this directory when they are done. These did, and what replaced them is the description: + +| Was | Now | |---|---| -| **[foundations.md](foundations.md)** | Descriptor passing, shared memory, sockets in the filesystem. The critical path. **Item 1 is built** — `sendmsg`/`recvmsg` with `SCM_RIGHTS`. | +| Descriptor passing, shared memory, sockets in the filesystem, dynamic linking, the event descriptors (`foundations.md`) | [kernel.md](../architecture/kernel.md), [networking.md](../architecture/networking.md), [syscalls.md](../architecture/syscalls.md) | +| `init.md` | [init.md](../architecture/init.md) | +| `hybrid.md` — drivers in processes, `/dev/pci`, a modeset API, the panic path, why USB stays | [devices.md](../architecture/devices.md) | +| `ldls.md` — Linux drivers in a process | [ldls.md](../architecture/ldls.md) | +| `debugger.md` | [debugging.md](../architecture/debugging.md) | +| `alpine.md` — an apk-managed root | [packages.md](../architecture/packages.md) | +| `xorg.md` — the desktop | [desktop.md](../architecture/desktop.md) | +| The interactive crash screen (`roadmap.md` §4) | GMMOS, in [graphics.md](../architecture/graphics.md) | -And the one that says which parts of the above are actually being worked on: - -| | | -|---|---| -| **[1.0.md](1.0.md)** | The finish line. Four gates, and everything else explicitly deferred. | - -And the one the hybrid mechanisms were built for: - -| | | -|---|---| -| ~~ldls.md~~ | **Built.** The Linux Driver Layer for SchOS: someone else's drivers, in a process, reached through `/dev/pci`. `ping` works over `8139too` and over `e1000`. The description moved to [../architecture/ldls.md](../architecture/ldls.md). | -| ~~debugger.md~~ | **Built**, all of it, including the network transport. A debugger that observes rather than halts and answers in this kernel's own vocabulary: symbols, a log that outlives the kernel, `/dev/dbg`, `whose`, hardware watchpoints, and `schdbgd`. The description moved to [../architecture/debugging.md](../architecture/debugging.md). | - -Two longer-horizon documents sit behind these: [schapi.md](schapi.md), the API -layering and the compatibility promise, and [roadmap.md](roadmap.md), which holds -everything still open that is not one of the three above. +And one that was **decided against**: `wayland.md`. Wayland needs DRM/KMS, libinput, udev and libseat, and DRM/KMS +alone is a kernel graphics subsystem. The desktop is X. The reasoning moved to [desktop.md](../architecture/desktop.md). +`SchWM`, `SchDE` and the compositor `Schcom` are gone from the tree: SchOS uses other people's window manager and desktop — Window Maker +now; XLibre, Xorg or a Wayland compositor later — and puts its work into what they need underneath (udev, DRM). --- ## The order -Set by [1.0.md](1.0.md), which is the authority. In short: +Set by [1.0.md](1.0.md), which is the authority. **1.0 is reached** (a live system; persistence is not part of it). +What is left before anything new starts: -### 0. Fix what is already wrong — **first** - -Not a document of its own; it is Gate 0 of [1.0.md](1.0.md), assembled from the -*What it does not do* sections in [../architecture/](../architecture/). - -**Before any new capability**, and the reason is the one that put -`netdev_selftest()` before the TCP/IP stack: a compositor built on a kernel with -an unexplained hang and a socket layer that reports success for things it did not -do will fail in ways indistinguishable from its own bugs. - -### 1. `foundations.md` — the critical path - -Both the Wayland work and the hybrid work stop here. Descriptor passing and -shared memory are the substrate for handing a buffer to a compositor *and* for -handing a device to a driver process. **Doing it once for both is the entire -reason these are one plan rather than three.** - -### 2. `init.md` — before the desktop, not after — **done** - -A compositor being brought up for the first time crashes constantly, and -something that restarts it and says why is worth far more during that work than -at the end of it. Built; see [../architecture/init.md](../architecture/init.md). - -### 3. `hybrid.md` — **moved up, and the reason is worth recording** - -This document used to put the hybrid work *after 1.0*, deliberately last, on this -argument: - -> designing a userspace driver interface with no driver consuming it produces an -> interface that is wrong in ways nobody can see - -The argument was sound and the conclusion drawn from it was wrong. What forced -the change was the driver situation: SchOS cannot write a driver for every device -that exists, no project can, and the only route to the hardware support an -established OS has is a compatibility layer with the foreign drivers kept -*outside* the kernel. That layer is this plan's mechanisms. It is not optional and -it is not late work. - -The original argument still holds, and it is answered rather than ignored: -**SchOS's own drivers are the consumers.** `vgpud` is a real driver, with real -requirements, and building the interface against it is what keeps it a general -mechanism rather than a hole shaped like something foreign. The compositor is -still a demanding consumer — it is no longer the *only* one available. - -Four mechanisms, all built. One eviction done, the second withdrawn with its -reasons recorded, and the foreign driver host they were for is -[../architecture/ldls.md](../architecture/ldls.md) — built, and carrying -packets. - -### 4. `wayland.md` — with the GPU driver either side of the seam - -W1–W3 and a terminal. W4 and W5 are post-1.0. Item 1 of `foundations.md` — -descriptor passing — was the thing it could not start without, and it now -exists. - ---- - -## In one sentence - -> Fix what is broken, build the descriptor-passing and shared-memory substrate, -> make init survivable, move the drivers out through an interface SchOS's own -> drivers have to live with first — and put a terminal on the screen. - ---- +1. **Empty the register**: decide the unused in-kernel LDLS window (delete it, or use it), and build socket activation or + remove the key. +2. **The owner's list** ([roadmap.md](roadmap.md)): the Wayland groundwork (udev first, §5), making LDLS good enough that the GPU + driver can run ([gpu.md](gpu.md)), a boot manager worth looking at (§5b), then the installer, then Finch inside SchOS + ([finch.md](finch.md)). ## What this plan does not address Stated here so it is not mistaken for covered: -- **No IOMMU**, so the hybrid work isolates against bugs and not against a - hostile driver. -- **No page cache**, so `foundations.md` delivers a `memfd` and not a general - answer to shared file mappings. The general answer is still owed. -- **No garbage collector for descriptors in flight**, so an `AF_UNIX` socket - passed over itself and never received leaks. Bounded at thirty-two sockets, and - nothing on this plan does it. -- **No dynamic linking**, so no Mesa, no accelerated clients, and no third party - shipping a `.so` for anything. -- **A new compatibility surface** between PID 1 and PID 2, created by a plan - whose own [schapi.md](schapi.md) §3 spends a section warning about those. -- **The three open networking items** — `getsockname`, routing, and - interrupt-driven receive — are untouched by any of this and still open. They - are in [roadmap.md](roadmap.md). +- **No IOMMU**, so a driver in a process is isolated against its own bugs and not against a hostile driver. +- **No page cache**, so a general writable `MAP_SHARED` of a file is refused; `memfd` is the narrow answer. +- **No installer**, so nothing a live system writes survives a reboot — by decision, for 1.0. diff --git a/docs/plan/alpine.md b/docs/plan/alpine.md deleted file mode 100644 index 5e141ab..0000000 --- a/docs/plan/alpine.md +++ /dev/null @@ -1,80 +0,0 @@ -# apk: Alpine's packages on SchOS - -Alpine is musl with a different init, and SchOS is musl with a different -kernel. Alpine's binaries run on SchOS unmodified: its dynamic linker, its -busybox, and apk itself, fetching over TLS through SchOS's network stack. -So instead of porting programs one at a time, SchOS can install them. - -## The model - -The root filesystem is an apk-managed root: - -| Part | Where it comes from | -|---|---| -| musl, busybox, apk-tools, alpine-keys, CA certificates | Alpine's minirootfs (`ports/alpine`) | -| what `ports/alpine/world` lists (zsh, terminfo, ...) | Alpine's packages, pinned in `world.lock` | -| schinit, its jobs, SchOS's tools, toybox, mksh, configuration | **schos-base**, an apk package the build makes from `sysroot/` | - -`apk add` installs anything from Alpine's main and community repositories. -`apk upgrade` updates Alpine's packages, and schos-base from the SchOS -repository at `/var/lib/schos/repo`. - -PID 1 is still schinit. Alpine's OpenRC is not installed, and packages that -ship OpenRC services install them where nothing reads them. - -## Who owns what - -Where Alpine and SchOS both have a program, Alpine's wins, and SchOS keeps -only what is its own (see `tools/posix/mkaroot.sh`): - -- `/bin/sh`, `cat`, `sed`, `find` and the rest are busybox. Alpine's package - scripts are written against busybox. -- zsh is Alpine's. SchOS keeps its `zshrc` and `zshenv` (`replaces=zsh`). -- `/etc/os-release` and `/etc/motd` are SchOS's (`replaces=`). -- `passwd`, `group` and `profile` are Alpine's. schos-base's post-install - sets root's shell to zsh, and SchOS's profile is `/etc/profile.d/schos.sh`. -- mksh is `/bin/mksh`. schinit's recovery shell looks for it first because it - is static: a broken musl cannot take recovery down with it. - -## Building - - make qemu boots build/schfs.img, the root as a 1 GB SchFS disk - make iso the same root as the live system's ramdisk - make alpine-lock re-resolve world against Alpine (needs the network) - -It is the only root. sysroot/ is what schos-base is packaged from. - -Only `alpine-lock` asks Alpine what is current. A normal build installs -exactly what `world.lock` names: files in `dist/alpine/`, fetched once and -checked against the lock's sha256, and against Alpine's signatures by apk. - -schos-base and its repository index are signed with a key made on the first -build (`build/apk-keys/`, never committed). The root trusts it beside -Alpine's keys. - -## Kernel work this needed - -All of it found by running apk: - -- **TCP receive window:** it reopened before the reader drained, so lwIP - refused data and the sender waited on retransmission timers (5 KB/s). It - now reopens on read, with a 32-segment window. -- **mmap address reuse:** freed ranges were never reused, so musl's malloc - used up the 256 mapping records with 780 MB of RAM free. -- **unlinkat / renameat:** bare `-1`, read as EPERM, where apk expects ENOENT; - renameat could not replace its target, and wrote v1 dirents into SchFS. -- **`#!` scripts in execve**, and ENOEXEC: apk runs triggers directly. -- **chroot**, and mounting devtmpfs and proc more than once. - -## Next - -- The live ISO runs the apk root from RAM. The image in the kernel is only - what the root holds; at boot the volume grows into half of the free memory - (4 GB of root on an 8 GB machine), backed by pages as they are written. - Xorg installs into it in about a minute. All of it is gone at reboot. Keeping packages needs an installer that writes the root - to a SATA or NVMe disk, and a kernel that finds its root there by the - SchFS GPT type rather than as IDE disk 1. -- Xorg and Window Maker are in `world`, so the image starts with them: - `startx` runs `wmaker` from `/root/.xinitrc`. Mesa brings LLVM with it, - about 200 MB of the 358 MB root; the root ships compressed - ([SCFF](../architecture/scff.md)), so that costs about 60 MB of ISO. diff --git a/docs/plan/finch.md b/docs/plan/finch.md new file mode 100644 index 0000000..f6df53b --- /dev/null +++ b/docs/plan/finch.md @@ -0,0 +1,77 @@ +# Finch, for SchOS's own programs + +**Two things, and only one of them is later.** + +- *Bringing Finch into SchOS* (below) is **later** — behind [1.0.md](1.0.md)'s + freeze like everything else here that is not named there. +- *Finishing Finch itself*, and making it fully self-hosting, is work on the + Finch repository and does not touch this tree. It can proceed now; see + [the gap](#the-self-hosting-gap). + +Finch is the author's own language: (mirrored to +`pandor.pl/git/nexoniarz/Finch`, which is read-only). The plan is to write SchOS's own +applications and tools in it, in place of C, and so leave behind what C does +badly: silent narrowing, signed/unsigned mixing, undefined behaviour on an +out-of-range index, a forgotten `free`. + +## What it is today (version 2.4.0) + +Measured from the repository, not from its README alone: + +- **LLVM front end**, C-like syntax, no semicolons, no headers, no preprocessor. + It compiles through LLVM's `-O2` pipeline to native code; there is no + garbage collector. +- **Values, not references:** lists, text and structs copy on assignment and + are freed at the end of their block. Out-of-range indexes, division by zero + and null pointers stop with a runtime error rather than undefined behaviour. +- **Errors as values** (`int(s) or 0`, `try read_file(p)`), maps, methods, + modules. +- **C interop through libclang:** `import "foo.h"` plus `link "foo"` calls any C + library, structs by value and callbacks included. That is the way in to + everything SchOS already has — musl, the `/dev/*` interfaces, Alpine's + libraries. +- **Targets:** Linux, macOS, Windows, on x86-64 and ARM64 (`src/platform.fch`; + the System V, AAPCS64 and Microsoft x64 ABIs are in `src/gen.fch`). ARM64 and + Windows programs are run under qemu and Wine in the tests; macOS is IR-checked. +- **Language:** enums, `match`, generics, errors as values, maps, methods, + `defer`, and a standard library in Finch (`json`, `path`, `time`, `proc`). +- **Tooling:** a language server (`finch lsp`, written in Finch), VS Code and + Kate support, 107 tests. +- **Written in itself:** the whole compiler is Finch (about 8,500 lines in + `src/`, no C++) and builds itself from a committed seed to a byte-identical + result — `bootstrap/build.sh`. + +## What SchOS would need to do + +1. **A `schos` target.** `finch` picks a triple, a C compiler and a linker per + target. SchOS runs unmodified Alpine binaries and Alpine's musl + (see [packages](../architecture/packages.md)), so the first target is **the existing + `x86_64-unknown-linux-musl`**, and a Finch program built on the host should + run on SchOS before anything in Finch is made SchOS-specific. Test that + before writing a line of target code. +2. **The compiler on SchOS.** Needs LLVM 21 and libclang, both of which Alpine + packages. That is a question of apk and memory, not of SchOS. +3. **The Finch-written compiler on SchOS.** It emits LLVM IR as text and needs + only `clang` and libclang, no LLVM C++ libraries, so it is the compiler that + would ship in the image. It needs a seed made for the SchOS target. +4. **Bindings** for SchOS's own interfaces (`/dev/pci`, `/dev/netdev`, + schinit's job protocol, the framebuffer) — generated by the C importer from + the headers in `include/uapi/`, so they are not hand-written and cannot + drift from the kernel. + +## Where self-hosting stands + +The compiler is written in Finch and nothing else, and it is `main` of `github.com/Nexoniarz/Finch` +(`before-bootstrap` there is the all-C++ compiler, kept as the backup). `bootstrap/build.sh` builds it from a seed (its +own IR) and then from itself, and checks the two make identical IR. 110 tests pass, also under valgrind with no leaks. +Seeds: Linux x86-64 and ARM64; Windows x86-64 (runs under Wine, compiles itself and passes every test program); macOS +ARM64 and x86-64 (made with the 11.3 SDK, checked as IR only — nobody has run them on a Mac). The language has enums +(also with values inside), `match`, generics and a standard library; the compiler carries its runtime and std inside +itself. + +## Rules + +- Inference, not an instruction: the kernel stays in C. Finch is for + applications and tools; the user said "our apps or stuff like that". +- No new capability starts until [the register of unfinished work](1.0.md#the-freeze-and-the-register-of-unfinished-work) + is empty; the SchOS half of this document is a plan, not an exception to that. diff --git a/docs/plan/foundations.md b/docs/plan/foundations.md deleted file mode 100644 index 1e0252d..0000000 --- a/docs/plan/foundations.md +++ /dev/null @@ -1,199 +0,0 @@ -# Foundations - -The kernel work that [wayland.md](wayland.md) and [hybrid.md](hybrid.md) both -stop at. **This is the critical path**, and it is the reason those two are one -plan rather than two. - -The insight is small and it decides the schedule: the substrate for passing a -buffer to a compositor and the substrate for handing a device to a driver process -are the same substrate. Descriptor passing and shared memory, built once. - ---- - -## What is actually in the tree - -Measured against the source, not against a README. Each of these was checked -before anything below was planned around it: - -| | | -|---|---| -| ~~`sendmsg`/`recvmsg`, and therefore **`SCM_RIGHTS`**~~ | **Built.** See item 1 below. | -| Writable `MAP_SHARED` of a file | **Refused** — `kernel/sys/sys_mem.c:131` returns `-ENODEV`. | -| `memfd_create` | Does not exist. | -| `AF_UNIX` names | In a table in `kernel/net/unix.c`, not in the filesystem. | -| `setsockopt` | `return 0;` unconditionally — `kernel/sys/sys_net.c:436`. | -| `exec` | `ET_EXEC` only. No `PT_INTERP`, no `ET_DYN`. | - -> **`projects/schcom/README.md` said `SCM_RIGHTS` was done while nothing in the -> kernel implemented it** — the most load-bearing wrong line in the tree, with an -> entire GUI plan drawn on top of it. The line is now true, which is not the same -> as it having been right, and the README says so. - -The `MAP_SHARED` refusal is worth defending rather than fixing carelessly. The -code returns `-ENODEV` *honestly*: the contract of a writable shared mapping is -that stores land in the file and other mappers see them, and there is no page -cache that could make that true. Quietly handing back a private copy would be the -same lie in a new place. - ---- - -## The six items - -In dependency order. The first three are what Wayland cannot start without. -All but item 4 are built. - -### 1. `sendmsg` / `recvmsg` with `SCM_RIGHTS` — **done** - -Not optional and not substitutable. `wl_shm_create_pool` passes a **file -descriptor**; there is no path in the Wayland protocol that passes an integer -instead. - -The `handle_kind` field in `projects/schcom/proto.h` was good foresight and it -cannot help here — *our* protocol is the flexible one, and Wayland's is not. - -Built as described: `struct msghdr`/`cmsghdr` in Linux's layout, ancillary data -queued alongside the byte stream in `kernel/net/unix.c`, descriptor installation -into the receiver's file table, and the three ways a message can end — received, -received without room, never received — each releasing the kernel's reference. -Fourteen checks in `cmd/socktest`, every one of them moving data through the -descriptor rather than inspecting it. Documented in -[../architecture/networking.md](../architecture/networking.md#descriptor-passing). - -The delivery rule is the part worth knowing before building on it: **descriptors -are pinned to the stream position of the first byte of the message that carried -them**, and a read that has not reached that position is cut short so it stops -before it. That is what keeps "which message did these belong to" answerable. - -**This was also item 1 of the hybrid work**, where it is how a driver process is -handed the device it is allowed to touch. - -Two bugs fell out of building it, neither in the new code: - -- `sockpair()` never set the socket's domain, so **every `socketpair()` socket - claimed domain 0**. Invisible for as long as the data paths asked only whether - a socket was `AF_INET` or `AF_NETLINK` — zero answers "neither" correctly. - `SCM_RIGHTS` asks "is this `AF_UNIX`", and zero is wrong for that one. -- `pipe_poll()` reports `POLLHUP` but not `POLLIN` for a pipe at end of file, so - a **non-blocking read of a pipe whose writers have all gone returned `EAGAIN` - forever** instead of 0. A program that polls and reads would spin waiting for - an end that had already happened. `sockreadable()` always had the equivalent - condition; the pipe path did not. - -### 2. A shareable memory object — **done** - -`memfd_create`, plus `mmap(MAP_SHARED)` on it genuinely working between two -processes. Described in -[../architecture/kernel.md](../architecture/kernel.md#shared-memory-objects); -asserted by `cmd/memfdtest`, seventeen checks including the one that matters — -a descriptor sent over a socket, mapped by a receiver that has closed every -inherited copy, with stores visible in both directions. - -Narrower than a general page cache, and sufficient. An anonymous shared object -needs no file behind it, which is exactly why `memfd` exists on Linux at all — -and it means this can be built on the shared-memory machinery already in -`kernel/sys/sys_mem.c` (`shmget`/`shmat`/`shmdt` work, and `struct vma` already -carries `shmid` and a `no_free` flag for pages that are not ours to release). - -The general answer — a page cache, and writable `MAP_SHARED` of a real file — -stays owed. It is a larger project and nothing on this plan needs it. - -### 3. `AF_UNIX` sockets in the filesystem namespace — **done** - -Clients find a Wayland compositor by `bind()`ing `$XDG_RUNTIME_DIR/wayland-0` as -a real path, and libwayland `flock()`s the lock file beside it. A name table can -express neither. - -`bind()` creates a `T_SOCK` node and `connect()` resolves the path and matches -the socket bound to **that inode** — not to a string, which is what made two -spellings of one path miss each other. `ls -l` shows `srwxr-xr-x`, `stat()` -reports `S_IFSOCK`, and `unlink()` removes it. Asserted by `cmd/socktest`, -section 15. `flock()` is still not implemented; see below. - -`kernel/net/unix.c`'s own header comment already predicted this: - -> *"What is lost is `ls -l /tmp/schcom` showing an `s`, and `unlink()` of a socket -> path failing with ENOENT. Both are worth fixing when the VFS next gets opened -> up; neither blocks anything."* - -They block something now. This is that moment, and it is a VFS change rather than -a socket change: a socket inode type threaded through `iget`, `dirlookup` and -`stat`, plus `flock`. - -### 4. `setsockopt` that does not lie - -At minimum `SO_PASSCRED` and `SO_SNDBUF`. Returning 0 for an option you ignored -is worse than `ENOPROTOOPT`, because the caller adjusts its behaviour on the -strength of the lie. - -Small, and grouped here because it is in the same file as item 3 and will be -found by the same testing. - -### 5. Dynamic linking — **done** - -`PT_INTERP` and `ET_DYN` in `kernel/fs/exec.c`. A program that names an -interpreter gets it loaded at the bottom of the mmap area and started instead, -with `AT_PHDR`, `AT_PHENT`, `AT_PHNUM`, `AT_BASE` and `AT_ENTRY` to find the -program by; a PIE program is placed where a fixed one would be. The interpreter -is musl's own: `ports/musl` now builds `libc.so` as well, shipped as -`/lib/ld-musl-x86_64.so.1`, which is libc and loader in one file. - -The kernel half that was missing besides `exec` was `mmap(MAP_FIXED)` of a -file. It handed back zeroed pages whatever the descriptor said, and kept no -record of them for `fork` — and a dynamic linker maps every library segment -after the first exactly that way. - -Every port still links `-static`, on purpose: this makes the other kind -possible, it does not convert anything. `cmd/dynhello` is the test — a -program against `libc.so` and a library of its own, checking TLS, the heap and -a forked child that still has its libraries. - -`mprotect` is accepted and not enforced, so RELRO is not read-only yet. - -### 6. The event descriptors — **done** - -`epoll`, `eventfd`, `timerfd` and `signalfd`, in `kernel/sys/sys_event.c`. -libwayland's event loop is all four in one epoll set, which is why they arrive -together; not in the original list because they were stubs that looked like -syscalls, returning `ENOSYS` from `table.inc` entries that read as done. - -They wait the way `poll()` does, a tick at a time, so the latency is 10 ms. -`cmd/evtest` checks each one, then all four in one set, level- and -edge-triggered. - -Building it found a signal bug: a default-fatal signal killed on arrival even -when it was blocked. A blocked signal now stays pending, which is the contract -`signalfd` is built on. - ---- - -## Verification - -Each item gets a test before the thing that needs it is built, because a -substrate that is subtly wrong fails inside the consumer and looks like the -consumer's bug. - -- **Item 1:** two processes, one passes an open descriptor, the other reads - through it. Plus the leak case: a sender that exits with descriptors in flight. -- **Item 2:** two processes map the same `memfd`, one writes, the other sees it. -- **Item 3:** `bind()` a socket, then `stat()` and `ls -l` the path and see an - `s`, then `unlink()` it. -- **Items 1–3 together:** a third test that does all of it at once, because that - combination is what Wayland actually performs and the three passing separately - does not prove it. - -These belong in `bootcheck` as a new switch, following the `-Sock` model. - ---- - -## What this does not deliver - -- **No page cache**, so writable `MAP_SHARED` of a real file stays refused. -- **No `dlopen`** until item 5 is done, which means no Mesa and no third-party - shared libraries. -- **`SO_*` coverage is the two options named**, not the full set. -- **No credential passing** (`SCM_CREDENTIALS`) — only descriptors. Nothing on - this plan needs it, and a compositor that wants to know who connected will. -- **No `flock()`.** libwayland locks `wayland-0.lock` beside the socket to stop - two compositors claiming one display. The socket itself is now a real path, - so the lock file can be created and opened; what is missing is the advisory - lock, which is a separate mechanism and has no other caller yet. diff --git a/docs/plan/gpu.md b/docs/plan/gpu.md new file mode 100644 index 0000000..cd78da8 --- /dev/null +++ b/docs/plan/gpu.md @@ -0,0 +1,125 @@ +# The GPU, through Linux's own driver + +The laptop this is for (ASUS TUF A15 FA507NU) has a Radeon 680M in the CPU and +an RTX 4050 beside it. SchOS draws everything with the CPU on an EFI +framebuffer. Rendering a browser page that way keeps every core busy, and the +machine runs hot doing it. + +Writing a Radeon driver is not on the table: amdgpu is around four million lines +before its register headers. The plan is to **run Linux's amdgpu unmodified**, +with its firmware, in a process under LDLS ([../architecture/ldls.md](../architecture/ldls.md)), +and to give Mesa the `/dev/dri` it expects. Licensing is not an obstacle: +SchOS is GPL-2.0-or-later, and the user has said that Linux code and firmware +blobs may be shipped. That supersedes LDLS's rule 1. + +## Why LDLS and not an in-kernel linuxkpi + +FreeBSD's drm-kmod compiles Linux's DRM source into kernel modules against a +shim (linuxkpi). SchOS has already chosen the other route, and for good reasons +([devices.md](../architecture/devices.md)): GPU drivers live outside the kernel, a crashed driver +falls back to the framebuffer instead of taking the machine down, and LDLS +loads unmodified `.ko` files against the running kernel's BTF instead of +needing a source port per Linux release. vgpud already drives a GPU from a +process, and LDLS already runs 8139too and e1000 that way. + +## What it costs: the numbers + +Measured against Linux 6.18.54 (the host's kernel and modules), with +`modprobe --show-depends`, `nm -u` and the export tables: + +| Driver | Modules in the closure | Symbols needed from the core kernel | LDLS provides today | +|---|---|---|---| +| r8169 | 12 (libphy, mdio-bus, realtek PHY, ...) | 330 | 70 | +| amdgpu | 15 (ttm, gpu-sched, drm_buddy, drm_exec, drm_display_helper, ...) | 1,166 | 76 | + +Of amdgpu's 1,166 symbols, **314 are the DRM core** (`drm_*`). NixOS builds +`drm.ko` and `drm_kms_helper.ko` into the kernel, so a module set that has them +as modules is needed; see step 0. A further 45 are dma-fence, dma-buf and +dma-resv. The rest is ordinary kernel: memory, locks, workqueues, timers, PCI, +ACPI, firmware loading, i2c, backlight. + +## What LDLS lacks, and must grow + +LDLS today assumes one thread, no-op locks and a polled interrupt handler +(ldls.md, "What it costs"). amdgpu cannot work under that: + +1. **Threads.** `kthread_run`, workqueues with real worker threads, + `gpu-sched`'s scheduler threads. Map them to pthreads in the driver process. +2. **Locks that lock.** mutex, spinlock, rwsem, `ww_mutex` (TTM and dma-resv + depend on wound/wait semantics), completions and wait queues, on futexes. +3. **Interrupts as interrupts.** A thread blocked in `read()` on `/dev/pci` + calls the handler and any threaded handler, instead of the poll loop. +4. **Timers.** `timer_list`, `hrtimer`, `delayed_work` and `jiffies`, from a + timer thread. +5. **Memory with physical addresses.** TTM allocates system pages for GTT and + maps them for the GPU. Without an IOMMU, DMA address equals physical + address, so the `PCIIOC_DMA` arena has to grow to hundreds of megabytes, + with `struct page` (vmemmap) coverage for all of it. +6. **Firmware.** `request_firmware` reads `/lib/firmware/amdgpu/*.bin`, from + Alpine's `linux-firmware-amdgpu` package. +7. **ACPI.** On an APU the VBIOS comes from the VFCT table, and amdgpu calls + ATIF/ATCS. A small `/dev/acpi` (read a table, evaluate a method through + uACPI) covers both. + +Each of these is general LDLS work, not amdgpu work. Every later driver needs +the same pieces: e1000e, iwlwifi, nouveau. + +## The new kernel piece: `/dev/dri` served by a process + +Mesa and Xorg open `/dev/dri/card0` and `/dev/dri/renderD128`, then use +`ioctl`, `mmap` and `poll`, and pass buffers to each other as dma-buf file +descriptors. The kernel has to forward each of those to the driver process: + +- **open, ioctl, poll, close** go to the server process with the client's + identity attached, the same pattern `/dev/netdev` uses for NICs, extended to + ioctls with their argument buffers. +- **mmap** is the part the kernel must do itself. The server replies with + physical ranges (VRAM through the BAR, or GTT pages from the arena), and the + kernel maps those into the client's address space. +- **dma-buf** becomes a kernel file object naming a buffer the server owns. + `SCM_RIGHTS` passes it between processes (already built; see + [networking.md](../architecture/networking.md#descriptor-passing)), and X's DRI3 needs exactly that. +- **sysfs**: `/sys/class/drm/card0`, `/sys/dev/char/226:*`, and the + vendor/device files that libdrm and Mesa read to pick a driver. + +## Order + +Each step ends with something you can run. + +0. **One module set.** Build Linux 6.18 from source, once, with `DRM=m`, + `DRM_AMDGPU=m` and BTF on. That gives `drm.ko`, `drm_kms_helper.ko` and the + rest, plus a matching `vmlinux` BTF. `make ldls-modules` does the build, and + packaging it as an apk makes it installable. +1. **LDLS concurrency and memory** (lacks 1–5 above), proven on **r8169**. + The real Linux r8169 brings PHY tuning, the firmware patch, ASPM handling + and every RTL8168 variant. It is the laptop NIC, so this step also fixes the + network properly. Done when the laptop's ethernet runs at gigabit speed + through `ldls0`. +2. **amdgpu probes.** The driver process loads firmware, brings up the IP + blocks and takes the display from the EFI framebuffer. The kernel console + must hand over the screen and get it back if the process dies. Done when + `dmesg` shows amdgpu's "SMU is initialized" and the panel stays lit. +3. **KMS.** `/dev/dri/card0` with ioctl forwarding and the mmap path. Done + when `modetest` (Alpine `libdrm-tests`) sets a mode and draws a frame. +4. **Rendering.** `renderD128`, GEM mmap, dma-buf, syncobj. Done when + `eglinfo` names radeonsi and `glmark2-es2-drm` and `vkcube` (RADV) run. +5. **The desktop.** Xorg's modesetting driver with glamor, then Epiphany with + GL compositing. Done when the fans stay quiet while a page scrolls. + +## The RTX 4050 + +Later, and in two parts: + +- **Powered off, now.** Without a driver the dGPU sits in D0 and draws power. + Putting it in D3cold through its ACPI power resources (`_PR3` / `_OFF`) is + a few dozen lines in the kernel and helps the heat and the battery today. +- **Driven, after amdgpu.** nouveau with GSP firmware (Ada needs it), through + the same LDLS and `/dev/dri` machinery, for PRIME render offload. + +## Not GPU, but also why the laptop runs hot + +Until now, idle CPUs halted at C1 and never reached the deep states listed in +ACPI `_CST`. That kept every core powered at an idle shell prompt. It is fixed +alongside this plan: the scheduler's idle path now enters `_CST`'s C2 (CC6 on +Zen). P-state control (AMD CPPC) is still missing, so the cores run at the +firmware's default clock under load. diff --git a/docs/plan/hybrid.md b/docs/plan/hybrid.md deleted file mode 100644 index a26c4c9..0000000 --- a/docs/plan/hybrid.md +++ /dev/null @@ -1,558 +0,0 @@ -# The kernel becomes hybrid - -"Hybrid" is used to mean everything from "a monolith with a nice device model" to -"a microkernel that gave up". **Drawing the line precisely is most of the work**, -so that is what this document is. - -The device model it builds on is -[../architecture/devices.md](../architecture/devices.md). It is last on the -plan, and the reason is in [README.md](README.md#the-order-and-why-it-is-not-the-order-they-are-listed-in). - ---- - -## The line - -### Stays in the kernel, permanently - -- scheduling, address spaces, the trap and syscall path -- IPC and shared memory — the mechanism everything else is built out of -- the VFS and the **root** filesystem -- IRQ demultiplexing -- the console and PSOGM - -**That last one is not a size compromise, it is an invariant.** The crash screen -must work when everything else in the system is dead, which means it cannot -depend on anything that could *be* dead. A crash screen served by a userspace -process is a crash screen that is absent in exactly the case it exists for. - -### May leave, and should - -- GPU drivers -- USB, and every USB class driver under it -- non-root filesystems -- NICs after the first -- input transformation — layouts, gestures, remapping - -### The rule that separates the columns - -> **Anything needed to reach a shell stays in. Anything discovered at runtime, -> replaceable, or plausibly written by someone else goes out.** - -That is NT's compromise and macOS's, arrived at from the same pressure, and it is -the one that survives contact with a real machine. - ---- - -## Built so far - -**Mechanisms 2 and 4 exist.** `/dev/pci` (include/dev/pciuser.h, kernel/dev/pciuser.c) -enumerates the bus, hands a device to a process exclusively, maps a BAR into it -and allocates memory a device can be pointed at. `pcitest` proves it end to end: - -``` - # bdf vendor:device class driver - 6 00:04.0 8086:100e network e1000 - 7 00:07.0 8086:10d3 network - - -device 7: 8086:10d3 (network) - claimed - BAR0 at 0x0000000081060000, 128 KB - regs[0..3] = 0x00140261 0x00000000 0x00080283 0x00000000 - MMIO reaches the device - DMA 8 KB at virtual 0x40001000, physical 0x3f8eb000 -``` - -Those register values are the e1000e's CTRL and STATUS, with full-duplex and -link-up set -- which is how you tell a working mapping from one that reads -0xFFFFFFFF off an unmapped address. Claiming a device the kernel drives is -refused, and everything a process held is released when it exits, because a -driver that crashed must not keep its hardware away from its replacement. - -**Mechanism 3 exists too.** A process asks for a device's interrupts and then -reads them, uio-style: - -``` - interrupts routed here (line 10) - interrupt delivered, count = 1 - ICR = 0x80000004 -``` - -`0x80000004` is the e1000e's cause register with the bit the test asserted and -INT_ASSERTED set, read back by userspace as its acknowledgement. - -The line is **masked by the kernel on arrival and unmasked by userspace's -write()**, and that is the entire design rather than a detail. A PCI interrupt -line is level-triggered: it stays asserted until the cause is cleared *at the -device*, which is a different register on every chip and therefore something -only the driver can do. Signalling EOI on a line that is still asserted -re-enters the handler immediately and for ever -- 72,000 times a second, when -this kernel last did it by accident (history/kernel.md §13). - -So a driver that dies mid-interrupt leaves the line **masked, not unmasked**. -The instinct is the other way round -- do not leave the device dead -- and it -is wrong: unmasking an asserted line with no handler attached is a crashed -driver taking the machine down with it. Verified by killing one on purpose: - -``` - interrupt asserted; exiting WITHOUT acking (simulated crash) - timer: 3431 ticks taken, 3436 expected -``` - -Five ticks lost in seven seconds, which is the ordinary background. The next -driver to claim the device re-arms the line, so masked is safe without being -permanent -- also verified. - -## The first eviction happened - -`cmd/vgpud` is the virtio-gpu driver, outside the kernel. It finds the device -by vendor and device id, asks for it, and the kernel driver hands it over: - -``` -vgpud: virtio-gpu at 00:03.0, currently held by virtio-gpu -virtio-gpu: released to userspace; screen is the VBE framebuffer -vgpud: claimed -vgpud: display is 1280x800 -vgpud: scanout is ours, framebuffer at physical 0x3eceb000 -vgpud: drew a test pattern from userspace -``` - -Full virtio 1.0 bring-up from a process: reset, feature negotiation, virtqueue -in DMA memory, GET_DISPLAY_INFO, RESOURCE_CREATE_2D, ATTACH_BACKING, -SET_SCANOUT, and TRANSFER/FLUSH to present. The pattern it draws is on the -virtio-gpu head while the console carries on in VBE on the other one -- which -is the fallback rule working rather than being described. - -**No syscall knows this is a GPU.** The kernel handed over a PCI device; the -process decided what it was. That is the property that makes the interface -general, and it is why the evictions come before anything foreign. - -### What the first real driver asked for that a test program had not - -- **PCI configuration space** (`PCIIOC_CFG`). A virtio device says where its - registers are in *vendor capabilities*, which live in config space -- so a - driver that cannot read it cannot find its own registers. Anything modern is - discovered the same way. -- **A way for the incumbent to let go.** `struct pci_driver` gained a - `release` hook. A driver without one keeps its device, which is always safe - and, for anything on the path to a shell, correct. -- **The outgoing driver freeing its DMA.** Releasing the device while keeping - its 4 MB framebuffer is half a handover: the incoming driver asks for one and - is told there is no room. Found by doing it. -- **A bigger arena.** 4 MB was exactly one framebuffer at this resolution, and - for the moment either side of a handover there are two. 16 MB now. - -### And one bug it reproduced immediately - -The first working run raised this: - -``` -irq 11: 77780 interrupts/second -- a device on this line is not acknowledging it. -irq 11: handler 'e1000' is attached -``` - -`vgpud` polls the used ring, so it registered no handler -- and a virtio device -interrupts on every completion. A level-triggered line nobody acknowledges -re-fires for ever, drowning the e1000 that shares it. The fix is one line, -`VRING_AVAIL_F_NO_INTERRUPT` before the queue is enabled, and the value of it -is that the storm detector built for history/kernel.md section 13 named the -line, the rate and the neighbour without anyone having to look. - -**All four mechanisms now exist**, and with H3 done and H4 withdrawn, nothing -remains on this plan. What the mechanisms were built for next is the foreign -driver host -- [ldls.md](ldls.md). - -### One thing the interface is deliberately not - -It is about **PCI devices**, not about any driver model, and its first users -will be SchOS's own drivers. That ordering is the design: an interface whose -first consumers are native drivers is a general mechanism that something -foreign can use later, rather than a hole shaped like something foreign. The -difference matters technically -- a Linux-shaped interface would be wrong for -the evictions this plan actually calls for -- and it happens to be the only -version that survives a licensing conversation. - ---- - -## The driver came back - -H2 is the milestone this plan calls the one that cannot be skipped, and the -reason is that H1 proves only half of the idea. A driver that runs in a process -is interesting; a driver that runs in a process, *dies*, and is replaced by -another process that picks the device up is the thing the whole hybrid argument -rests on. - -`etc/schinit/vgpud.job` makes it a supervised job -- `KeepAlive = yes` -- so -killing it is the whole test: - -``` -# cat /proc/schinit -1 4 11 os.schos.vgpud -# kill 4 -schinitd: restarting os.schos.vgpud -vgpud: virtio-gpu at 00:03.0 -vgpud: claimed -vgpud: display is 1280x800 -vgpud: scanout is ours, framebuffer at physical 0x3eceb000 -vgpud: pid 8 drew a test pattern from userspace -``` - -The test pattern ends in a twelve-block tally of the drawing process's pid, -because a second run that redraws an identical picture is indistinguishable -from a first run that never stopped. Read off the virtio-gpu scanout across -four generations of the driver it says 4, 8, 10, 12. The same physical address -comes back every time, which is the DMA arena reclaiming cleanly rather than -drifting. - -### What it took that H1 had not needed - -- **A halt the kernel can apply without knowing what the device is.** A driver - that dies leaves its device bus-mastering into memory the kernel is about to - hand to somebody else. The kernel cannot quiesce a device it does not - understand, but it can clear `PCI_CMD_BUS_MASTER`, and that is enough to stop - the writes. `pciuser_stop()` does it before the DMA is freed, not after, and - claim/release became a symmetric pair: claiming sets the bit, releasing - clears it. `pcitest` grew a `bm` column so the state is visible rather than - assumed. -- **A refusal, when there is nothing to fall back to.** `vgpu_release()` now - asks `vga_fb_restore()` whether there is a framebuffer to put the console on, - and declines to hand the device over if there is not. The rule that the - kernel keeps a minimal driver for everything on the path to a shell is only - worth writing down if something enforces it. -- **The DMA arena not being handed to two allocators at once.** This is the bug - that stopped H2 for a day, and it is worth writing out: `freevm()` frees - every mapped page below `PHYSTOP`, and the DMA arena is the top 16 MB of - exactly that range. So a DMA block mapped into a driver and left for - `freevm()` went onto the page allocator's free list when the driver died, - while `pciuser_release_all()` handed the same pages back to the arena. Both - allocators then believed they owned it. The restarted `vgpud` asked for its - 4 MB framebuffer, got pages `kalloc()` had already given out -- including its - own page tables -- and `dma_alloc()`'s zeroing wiped them, so the driver died - inside the ioctl, on the return from a syscall whose stack had stopped - existing. `do_dma()` now records a VMA with `no_free` set, the same way - shared memory does, so `proc_release_shared()` unmaps the range before the - page tables are torn down. - -### The clause that is not true yet - -The milestone says *"the compositor reattaches"*. It does not, because there is -no compositor running: Schcom is v0 and is not started at boot. What has been -shown is the two parts that do not depend on it -- the screen falls back to -VBE, and the driver restarts and takes the device back. During the gap the VBE -head carries a live console and the virtio-gpu head still shows the dead -driver's last frame, which is exactly what a device nobody is driving does. - -(That last sentence is still true of a *crashed driver*, and no longer true of -a crashed *kernel* -- see "The clause nobody wrote down" below.) - -Reattachment is a Schcom milestone, not a hybrid one, and it is recorded that -way rather than counted here. - ---- - -## A modeset, from a process - -H3 is one sentence in the milestone table -- *"a modeset API, which is the -thing an out-of-kernel GPU driver makes affordable and an in-kernel one never -got"* -- and the claim in it is worth testing rather than repeating. - -``` -# schmode -1280x800 current -1280x800 maximum, framebuffer 4000 KB -# schmode modes -1280x800 * -1280x720 -1024x768 -800x600 -640x480 -# schmode 1024 768 -1024x768 ok -``` - -The scanout really resizes: captured from the virtio-gpu head, the frames come -back 1280x800, then 1024x768, then 640x480, then 1280x800 again, with the -checkerboard band showing no shear at any of them. - -**Nothing in the kernel changed for this.** No syscall on the path has heard of -a scanout; `schmode` writes a line to a socket and `vgpud` sends five commands -to the device. That is the whole of the milestone, and the reason it is -interesting is the comparison: the same five commands in the kernel would have -been the same five commands *and a reason to be nervous*, because getting the -sequence wrong there takes the machine instead of one process. **That is why -the in-kernel driver never grew one** -- not difficulty, exposure. - -### What it costs, and what it refuses - -The framebuffer is allocated **once, at the largest mode**, and every smaller -mode reuses it. `PCIIOC_DMA` has no free -- a driver's blocks come back when it -exits and not before -- so a modeset that allocated would spend sixteen slots -and then the arena, and the failure would look like a GPU bug rather than like -counting. A mode larger than the initial allocation is refused, and says why: - -``` -# schmode 1920 1080 -refused: larger than the framebuffer, which is fixed at startup -``` - -That bound is 4 MB, `PCIIOC_DMA`'s cap, which at 32 bits per pixel is about -1280x800. Raising it is a one-line change to a constant and a question about -how much of a 16 MB arena one driver should be able to take, which is a better -question than it looks and is not answered here. - -### The socket, and the argument it settles - -`vgpud` used to end in `for(;;) sleep(100);` under a comment arguing that -building a control socket before there is a compositor to judge it is exactly -the mistake this document spends a section warning about. - -That argument was right about a *compositor protocol* and wrong as a reason to -have no socket at all. A modeset API has a consumer that exists -- somebody -changing the mode and looking at the screen -- and `schmode` is small enough -that it can judge an interface this small. So the socket is there, it speaks -lines of text for the same reasons -[../architecture/debugging.md](../architecture/debugging.md) gives, and what is -deliberately **not** on it is everything a compositor would need: no buffer -submission, no damage, no vblank. Those still wait, for the original reason. - ---- - -## The mechanisms - -Four, and only the fourth is large. - -### 1. A control channel — **built** - -`AF_UNIX` plus `SCM_RIGHTS` — item 1 of [foundations.md](foundations.md), which -the Wayland work also needed first. A descriptor is how a driver process is -handed the device it is allowed to touch. - -Built as `sendmsg`/`recvmsg` in Linux's shape, because the programs that will -use it are not ours: libwayland and toybox both call `recvmsg` with Linux's -structures and would not notice a friendlier door. Descriptors are pinned to the -stream position of the first byte of their message, and the kernel holds a -reference to each until it is received — or closes it when the socket is dropped -with the message still queued, which is the case that leaks if nobody writes it -down. See -[../architecture/networking.md](../architecture/networking.md#descriptor-passing). - -**Do not build Mach ports.** The port model is worth its cost when there is a -naming and capability story to hang on it; there is not one yet, and inventing it -before the first userspace driver exists is designing against an imaginary user. - -### 2. MMIO to userspace - -Map a PCI BAR into a process. - -`sys_mmap` already maps the framebuffer's real physical range at its real -address — the path exists. This is that path generalised, behind a check that the -caller was granted that device. - -### 3. Interrupts to userspace - -**The handler stays in the kernel and does one thing: write to an `eventfd`.** -The driver process reads. - -`eventfd` exists already, and that is why it is the right primitive. A signal -would be delivered to a process that cannot safely be interrupted mid-transaction. - -### 4. DMA to userspace - -Physically contiguous memory whose physical address the process is told. - -`kernel/core/dma.c` already owns an arena reserved for exactly this, before -`kalloc` gets anything. It needs a door, a per-device budget, and a way to hand -the region back. - ---- - -## The clause nobody wrote down: a panic has to reach the screen - -Handing a device away costs something that was not on the list. The kernel -panics, paints the report into the console framebuffer, and halts — and the -display it gave to a process keeps showing whatever that process last put -there. On a machine with two heads, one says the kernel is dead and the other -shows a shell prompt. - -It is not a race that can be won by being careful. There is no scheduling after -a panic, so the process holding the device is never going to run again, and the -kernel let go of the device deliberately. - -**So the userspace driver says in advance where it is scanning out**, with -`PCIIOC_PANICFB` (see `include/dev/pciuser.h`), and the kernel does the rest: - -1. paints the report into that memory, -2. asks the *kernel* driver the device was taken from to put it on screen — - `panic_present` in `struct pci_driver`, -3. which resets the device and rebuilds one queue from **static** memory: no - allocation, no locks, no sleeping, because a corrupted heap is one of the - ways a machine gets here in the first place. - -The registration is checked like the promise it is: the range must lie inside a -block that process got from `PCIIOC_DMA` on that device. Without that it would -be a way to nominate any physical address in the machine to be overwritten at -panic, which is a worse bug than the one it fixes. - -What this buys is a property worth stating plainly: **a device can be evicted -to userspace without the kernel losing its ability to tell you the machine -died.** That is the difference between a driver boundary and a blind spot, and -it is one more thing the in-kernel arrangement got for free and the hybrid one -has to earn. - -It costs 20 KB of static memory in the virtio-gpu driver, paid whether or not -the machine ever panics. Allocating it on demand would be cheaper and would not -work. - ---- - -## The honest limitation - -> **There is no IOMMU, so a userspace driver is isolated against its own bugs and -> not against malice.** - -A process that can program a device's DMA engine can write to any physical -address, whatever its page tables say. - -That is not a reason to stop — isolation against bugs is most of the value, and a -crashed driver that restarts instead of panicking the machine is the entire -point. But it must be written down, because *"userspace driver"* reads as -*"sandboxed"* and here it does not mean that yet. VT-d/AMD-Vi is its own project -and comes after. - ---- - -## Order, and one thing not to do - -### Do not build loadable kernel modules first - -It is the obvious first step and it is a generation behind. It means designing a -**stable in-kernel driver ABI**, which is the most expensive promise in this -entire plan, for a mechanism Apple is actively retiring in favour of the one -above. - -`../architecture/devices.md` already separates the two ideas. They are separable, -and the second one is the one to build. - -### First eviction: the GPU driver - -Four reasons it is the right one and not just the interesting one: - -- **The boundary already exists and is already correct** — `struct display_ops`. -- **It has exactly one consumer**, the compositor, so the interface has a real - user to be judged by. -- **There is a working fallback underneath.** The VBE framebuffer stays - in-kernel, so a GPU driver that fails to start degrades to a working screen - rather than to no screen. -- It directly serves the work that will actually be happening. - -### Second: USB — and why it turned out not to be - -`xhci.c` is 1364 lines, self-contained, already spoken to through a bus -abstraction, and it is where third-party drivers will show up first. This is what -DriverKit was built for and the resemblance is not accidental. - -#### It is not the same shape as the GPU, and that was missed - -Written before the GPU eviction had been done, H4 says *"USB, same shape"*. It -is not, and the difference is one this document's own rule already decides. - -The GPU could leave because **there was a working fallback underneath it**: the -VBE framebuffer stays in the kernel, so a GPU driver that fails to start -degrades to a working screen. That is one of the four reasons the GPU was -chosen to go first, listed a few lines above. - -USB has no fallback, and it carries two things that are on the path to a shell: - -- **The root disk.** `USBDISK_DEV` dispatches through `bio.c` to - `usb_storage_rw`, and `usbstick.img` is a real boot medium built by `make`. -- **The keyboard.** `usb_hid` feeds `input_report(INPUT_KBD, ...)`, which is how - anything without PS/2 is typed on — including the Chromebox this is meant to - boot. - -So evicting `xhci.c` wholesale breaks the rule three sections down: - -> **The kernel keeps a minimal driver for everything on the path to a shell, and -> userspace owns the good one.** -> -> Break this and you get the circular boot failure where the compositor cannot -> start because the display driver is a process that needs a compositor. - -The USB version of that sentence is *the root filesystem cannot mount because -the storage driver is a process that lives on the root filesystem*, and it is -the same failure wearing different clothes. - -There is a second difference, subtler and probably the harder one. `/dev/pci` -hands over a **PCI function** — one device, one owner, nothing shared. A USB -device is not that: it sits behind a controller that other devices also sit -behind, so the unit of ownership and the unit of hardware are no longer the -same thing. Handing a process "the xHCI controller" gives it the keyboard too; -handing it "one USB device" is an interface `/dev/pci` does not have and that -this plan has not designed. - -#### Decided: USB stays in the kernel - -**H4 is withdrawn.** Not deferred, not blocked on the interface above — -withdrawn, because the premise was wrong. - -> **USB is not a peripheral.** It is the bus the peripherals arrive on. - -Nearly every machine has a USB port in some form, down to a bad micro-B on a -board that has nothing else, and the ones that do not have one are not made -worse by the stack being present. That makes USB structurally the same kind of -thing as PCI, not the same kind of thing as a GPU — and this document has never -proposed evicting PCI. - -Reading the eviction rule again with that in mind, it already said so: - -> **Anything needed to reach a shell stays in. Anything discovered at runtime, -> replaceable, or plausibly written by someone else goes out.** - -USB is how things are *discovered at runtime*, which is not the same as being -one of them. The keyboard and the root disk that arrive over it are on the path -to a shell, and the stack that finds them is part of that path. - -The two obstacles in the section above are real and they are why the premise -got questioned, but they are not the reason for this decision. Even with a -minimal in-kernel fallback written and a per-device ownership interface -designed, the answer would be the same: the good USB driver and the minimal one -would be the same driver, and an eviction that leaves an identical copy behind -has moved nothing. - -What the eviction was *for* — third-party drivers, written by someone else, -living outside the kernel — is a real goal and it survives this. It is simply -not a USB goal; it belongs to the foreign driver host, which is -[ldls.md](ldls.md). - -### The rule that keeps the boot path honest - -> **The kernel keeps a minimal driver for everything on the path to a shell, and -> userspace owns the good one.** - -Break this and you get the circular boot failure where the compositor cannot -start because the display driver is a process that needs a compositor. - ---- - -## Milestones - -| | | | -|---|---|---| -| **H0** | Mechanisms 2, 3 and 4, each with a test that exercises it from a userspace program against a device nothing else is using. | done | -| **H1** | A userspace virtio-gpu driver, publishing `display_ops` across the channel. Schcom talks to it and does not know the difference. | done, except that Schcom does not talk to it -- `cmd/vgpud` holds the device and draws its own frame | -| **H2** | Kill the driver process on purpose. The screen falls back to VBE, the driver restarts, the compositor reattaches. **This is the milestone that proves the whole idea**, and it is the one that cannot be skipped. | done, less the compositor -- see "The driver came back" | -| **H3** | A modeset API, which is the thing an out-of-kernel GPU driver makes affordable and an in-kernel one never got. | done -- see "A modeset, from a process" | -| ~~**H4**~~ | ~~USB, same shape.~~ **Withdrawn** — USB is the bus peripherals arrive on, not a peripheral. See "Decided: USB stays in the kernel". | | - ---- - -## What it will still not do - -- **No isolation against a hostile driver**, per the IOMMU note above. -- **No stable driver ABI**, deliberately. Out-of-tree drivers remain a - recompile-with-the-tree affair until there is a reason to promise otherwise. -- **The root filesystem stays in the kernel**, so SchFS cannot be restarted and a - bug in it is still fatal. Moving it out needs an initramfs story that does not - exist. -- **The first NIC stays in the kernel**, because the network thread and lwIP are - in the kernel and moving them is a separate project with no current benefit. -- **No driver sandboxing beyond address-space separation** — no seccomp-shaped - syscall restriction, no resource limits per driver. diff --git a/docs/plan/roadmap.md b/docs/plan/roadmap.md index a024ebb..15eae1e 100644 --- a/docs/plan/roadmap.md +++ b/docs/plan/roadmap.md @@ -1,183 +1,195 @@ # Roadmap — everything else still open -The three current decisions have their own documents; see -[README.md](README.md). This file holds what is left: work that is scoped, -agreed and unstarted, with nothing in it that is finished. +[1.0.md](1.0.md) draws the finish line and holds the register of what is half-built. +This file holds what is left beyond it: work that is scoped, agreed and unstarted, with +nothing in it that is finished. -**Finished work is not here.** It moved to -[../architecture/](../architecture/) and was rewritten as a description of what -it produced — see [the rule](../README.md#the-rule-that-keeps-this-from-rotting). -What used to be phases 1 through 4 of this file, several hundred lines of ticked -boxes, is now `usb`, `networking.md`, `graphics.md` and `devices.md` over there. +**Finished work is not here.** It moved to [../architecture/](../architecture/) and was +rewritten as a description of what it produced — see +[the rule](../README.md#the-rule-that-keeps-this-from-rotting). --- ## 1. Storage and the boot path -**Done: a USB disk, a SATA disk and their partitions can be booted from.** The -root is chosen at boot from what is present (`rootdev_mount()` in -`kernel/proc/proc.c`), and `make disk` builds a GPT image with an ESP and a SchFS -partition that a PC boots from a stick or a disk. +A USB disk, a SATA disk, an NVMe SSD and their partitions boot, and the root is chosen at boot from +what is present ([boot.md](../architecture/boot.md)). -NVMe (`kernel/drivers/storage/nvme/`) registers with the same block registry, -so an NVMe SSD boots the same way. - -**Next:** an installer that writes that layout to a disk, and interrupt-driven, -queued I/O for AHCI and NVMe. +- **An installer** that writes the layout `make disk` builds (a GPT disk with an ESP and a SchFS + partition) to a machine's disk, so a root survives a reboot. See §5. +- **Interrupt-driven, queued I/O for AHCI and NVMe.** Both poll for completion, one command at a time + per disk: enough to boot and run, not to be fast. +- **An EHCI driver.** USB 2.0 ports wired only to EHCI do nothing; xHCI ports work. ## 2. Graphics -- **A `/dev/dri`-style device with mode setting**, so resolution stops being - whatever the loader asked for. This was to be done *with* - [hybrid.md](hybrid.md)'s GPU eviction, and that eviction has now happened — - `cmd/vgpud` drives virtio-gpu from userspace. The modeset API is the next thing - it makes affordable, and is milestone H3 over there rather than here. -- **Double buffering and vblank.** `display_ops` specifies both and no driver - implements either. Schcom needs both to not tear. -- **Port the console onto the text layer and delete the cell grid.** The layer - exists, the rasteriser exists, and the console still indexes `font_data[256][16]`. -- **Load a font from disk** and point the console at it. -- **CFF/Type 2 charstrings**, so OTF fonts work. A separate `font_ops` provider - and a bytecode interpreter, *not* an extension of the `glyf` path. -- **Text layout** — advance widths, kerning — as a library in `lib/`, so the - terminal and the file manager are not each solving it. - -The console keeps its fixed grid until the first item above; proportional text -belongs to the GUI. +- **A `/dev/dri` device with mode setting**, so a real panel's resolution stops being whatever the + loader's menu chose and Mesa has something to open. This is the GPU plan, [gpu.md](gpu.md). +- **Double buffering and vblank**, which `display_ops` specifies and no driver implements; the same plan. +- **CFF/Type 2 charstrings**, so OTF fonts work. A separate `font_ops` provider and a bytecode + interpreter, *not* an extension of the `glyf` path. +- **Text layout** — advance widths, kerning — as a library, so the terminal and the file manager are not + each solving it. ## 3. Networking -Two things the stack owed on the day it landed, both still owed: +- **A routing table** and `/proc/net/route`. With two interfaces up the first is the default route and the + second is reachable only by an address on its own subnet; that is a rule and not routing. +- **Wi-Fi.** The laptop has no other way to be wireless. The route is the same as for the NIC: a Linux + driver (`iwlwifi`, `mt76`…) through LDLS, which needs `nl80211`, a firmware loader that exists, and a + supplicant (`wpa_supplicant` is an Alpine package). +- **IPv6.** +- **A netlink event stream**, for anything that wants to be told a link changed. -- **`accept()` does not report the peer's address**, and there is no - `getsockname`/`getpeername`. Nothing has needed them yet; a server that wants - to log who connected will. -- **No routing table.** With two interfaces up, the first is the default route - and the second is reachable only by an address on its own subnet. That is a - rule rather than an accident, and it is not routing. +## 4. The hardware that makes a laptop a laptop -Two that were on this list are now done and described in -[../architecture/networking.md](../architecture/networking.md): `setsockopt` -honours six options and refuses the rest, and `sendmsg`/`recvmsg` carry -`SCM_RIGHTS`. A third is closer than it was — toybox's `ping` needed `recvmsg` -*and* `IP_RECVTTL`, and only the second is still missing. +- **HDA audio.** AC'97 is what QEMU and old boards have; a laptop built this century has Intel HDA, + which is codec enumeration, CORB/RIRB and stream descriptors. It arrives as a second `struct audio_ops`. +- **Embedded-controller events** (the ACPI SCI), so a lid, a power button on the EC and a battery change are seen. +- **CPU frequency control** (AMD CPPC; Intel HWP/EIST). Idle already reaches `_CST`'s C2; under load the cores run at + the firmware's default clock. +- **Touchpad gestures** — two-finger scroll, palm rejection — on top of I2C-HID's mouse mode. +- **An IOMMU** (VT-d / AMD-Vi), which turns "a driver in a process is isolated against bugs" into "against malice" — + the largest honest caveat on [ldls.md](../architecture/ldls.md). +- **udev's job** — a uevent socket and `/sys/devices` — so X and others are told about devices instead of being + given a list. -## 4. The interactive crash screen +## 5. The desktop, and the road to Wayland -PSOGM renders and stops. The idea is *a small OS inside the crash*, and the -framing decides the design: +SchOS does not write its own compositor. (Schcom, a 715-line v0, was deleted: its wire format was a dead end, and a +compositor worth running is years of work that Alpine already packages.) The desktop is **Xorg with Window Maker** now +([desktop.md](../architecture/desktop.md)); **XLibre**, the Xorg fork, is a drop-in alternative if Alpine carries it, and a +Wayland compositor (labwc, sway, weston, a GNOME or KDE session) is the larger step. What Wayland — and a modern X — need +from the system, in the order they are useful: -> At panic time the kernel is the only thing running, which is exactly why it can -> offer what a live system cannot: walk the stack, read another process's memory, -> dump page tables, inspect the process table — with no scheduler and no locking -> to respect, because nothing else can race you. +1. **udev — done** ([udev.md](../architecture/udev.md)). eudev runs from `schinit`, the uevent netlink socket and the + `/sys/devices` tree exist, PCI and USB devices are in it with `add`/`remove` events from USB hot-plug, devtmpfs takes udev's + links, and Xorg finds its keyboard and mouse through it. **Left:** USB interfaces, PCI bridges as parents, `hwdb`. +2. **libinput and libseat — done.** `libinput list-devices` sees the keyboard and the pointer as an ordinary user, Xorg now + drives both through `xf86-input-libinput`, and `seatd` runs from `schinit`: a non-root program opens `seat0` through + `libseat`, is handed `/dev/input/event0` over the socket, and reads it. Getting there found a real kernel bug (sockets + returned by `accept()` could not carry file descriptors) and a second one in `sendmsg`'s control-buffer parsing; both would + have stopped a Wayland compositor and its clients too. **Not yet tried:** a compositor. +3. **A DRM device.** Wayland compositors want `/dev/dri/card0` with KMS, and Mesa wants `renderD128`. SchOS has neither: it has + `/dev/fb0`. This is the GPU plan ([gpu.md](gpu.md)) — Linux's own `amdgpu` run by LDLS and `/dev/dri` served by that + process — and, for machines without a real GPU driver, a *simple-framebuffer* DRM shim (Linux's `simpledrm`, which is a + `.ko` LDLS could also host). +4. **dma-buf, `epoll`-able sync files and the rest of the Linux graphics ABI**, as the compositors turn out to need them. -What makes it a *system* rather than a print statement is that it keeps the base -machine's drivers. Concretely, the ones that work with interrupts off: the -framebuffer (`gfx_draw_pixel` is a plain memory write), the PS/2 keyboard -(pollable via ports 0x60/0x64), and the serial port. **Those three are a complete -console.** +None of this is a kernel rewrite: it is the Linux interfaces that already exist half-built (`/sys`, netlink, evdev, `SCM_RIGHTS`, +`memfd`), finished until real programs stop asking for what is missing. The method is the one that has worked all along: `apk +add` the program, run it, and fix what it names. -Everything else — IDE, USB, the timer, anything that sleeps or takes a -sleeplock — is off limits, because the lock it needs may be precisely the one -held by the CPU that just died. +Nothing here starts before [1.0.md](1.0.md)'s register is empty, except where the owner says otherwise. -So the shape is a **polled event loop over preallocated state**: no `kalloc`, no -`sleep`/`wakeup`, no `acquire` on anything a live path might hold, one static -command buffer, and that driver set. **That restriction is not a limitation to -work around later; it is the invariant that makes the mode trustworthy when -everything else is not.** Anything wanting more should be answered by rebooting -into a recovery environment. +## 5b. The boot manager -A useful precedent to read but not copy: DDB, the BSD in-kernel debugger, is -exactly this, and its hard-won rule is the same one — *a debugger that can -deadlock is worse than no debugger.* +The loader ([boot.md](../architecture/boot.md#the-boot-manager)) is now a Niveus-styled window: bevelled keys, wells, Instrument Sans, +keyboard and pointer, a list of other systems it finds on the disks and can chain-load, the F8 options (resolution with its +"keep this?" confirmation, verbose boot), and real Firmware Setup, Restart and Shut Down. -## 5. The installer and `.sip` +Still to do: -Scoped in [`projects/schinst/README.md`](../../projects/schinst/README.md), -which holds the detail. In short: +- [ ] boot entries from a file on the ESP (several kernels, command lines, "drivers off", a default and a timeout), instead of + the two fixed ways of starting this system +- [ ] a SchFS partition as a boot source, not just the volume the loader came from +- [ ] an honest failure screen for the loader's own errors (today `die()` prints to the console) +- [ ] the pointer path verified on a real machine; QEMU with OVMF never delivered a USB mouse event to the loader +- [ ] light appearance, and a theme read from the ESP + +It is its own piece of work in `boot/uefi/`; the kernel's TrueType rasteriser is not available at that point, which is why the +loader carries a pre-rasterised face (`include/gfx/nvfont.h`). + +### The installer and `.sip` + +Scoped in [`projects/schinst/README.md`](../../projects/schinst/README.md), which holds the detail. In short: - [ ] `.sip` format specification, **written down before any code** - [ ] `tools/mksip` — capture a directory tree into a package - [ ] The apply path -- [ ] An installation environment that boots from the ISO, partitions, applies - the `.sip`, and installs the loader +- [ ] An installation environment that boots from the ISO, partitions, applies the `.sip`, and installs the loader -`.sip` is modelled on WIM, and the thing worth copying from WIM is that it is -**file-based, not block-based**: one file holds a whole filesystem, applies to a -partition of any size, and several images can share their common files. +`.sip` is modelled on WIM, and the thing worth copying from WIM is that it is **file-based, not block-based**: one +file holds a whole filesystem, applies to a partition of any size, and several images can share their common files. -**Most of this already exists.** `tools/mkschfs.c` and `tools/mkfs.c` both do -content dedup with an `(st_dev, st_ino)` fast path, and SchFS's B-tree is content -addressed. `mksip` should share that code rather than reimplement it — that is -the reason the dedup was factored the way it was. +**Most of this already exists.** `tools/mkschfs.c` and `tools/mkfs.c` both do content dedup with an `(st_dev, st_ino)` +fast path, and SchFS's B-tree is content addressed. `mksip` should share that code rather than reimplement it. And +the live image is already a compressed block image of a SchFS root ([scff.md](../architecture/scff.md)); an installer +may need nothing more than to write that, decompressed, to a partition. Open questions, all wanting a decision before code: -- **Compression.** Something with a small decompressor is worth more than - something with a good ratio, because the decompressor runs in the installation - environment. -- **Multiple images per `.sip`** — WIM's real trick, install/recovery/boot - sharing one blob store. Worth the complexity now, or not? -- **Can the kernel mount a `.sip` read-only directly?** It is close to a - filesystem already, and if it can, the installation environment's root can - simply *be* the `.sip`. +- **Compression.** Something with a small decompressor is worth more than something with a good ratio, because the + decompressor runs in the installation environment. (zstd is already in the kernel for SCFF.) +- **Multiple images per `.sip`** — WIM's real trick, install/recovery/boot sharing one blob store. Worth the complexity + now, or not? +- **Can the kernel mount a `.sip` read-only directly?** If it can, the installation environment's root can simply *be* the `.sip`. -The GUI for this is a Wayland client; see [wayland.md](wayland.md). +Its GUI is the boot manager's (5b) — the installation environment starts in it — or an X client. ## 6. SchFS beyond SchOS In the order they should happen. -**~~An on-disk format version~~, and an upgrade path.** The version policy is -done — see [../architecture/schfs.md](../architecture/schfs.md). What remains is -the **upgrade step**, and it cannot be written until there are two formats to -convert between; the first bump is when that becomes real work rather than -speculation. +**An upgrade step.** The format version policy is done ([schfs.md](../architecture/schfs.md)); the upgrade step cannot be +written until there are two formats to convert between, and the first bump is when it becomes real work. -The format is now pinned, which is what the other two items were waiting on. +**A Linux implementation, FUSE first.** Three reasons in order of weight: it can be tested with `fsx` and `xfstests`, which +are far harsher than anything written alongside the code; it carries no licence entanglement, since libfuse is LGPL and used +across a process boundary; and it can reuse the read/write logic already in `tools/mkschfs` and `tools/schfs_fsck` rather than +being written twice and having the two disagree. A kernel module comes only after FUSE passes `xfstests`; SchOS is GPL-2.0-or-later +([../licensing.md](../licensing.md)), so a Linux module is a technical project now and not a licensing one. -**A Linux implementation, FUSE first.** Three reasons in order of weight: it can -be tested with `fsx` and `xfstests`, which are far harsher than anything written -alongside the code; it carries no licence entanglement, since libfuse is LGPL and -used across a process boundary; and it can reuse the read/write logic already in -`tools/mkschfs` and `tools/schfs_fsck` rather than being written twice and having -the two disagree. - -A kernel module is a different project, comes only after FUSE passes `xfstests`, -and carries the licence problem [../licensing.md](../licensing.md) describes. - -**The missing features**, listed honestly before adding any: no journal replay -across an unclean shutdown beyond what copy-on-write gives for free, no -snapshots, no sparse-file support, and no online `fsck`. **Snapshots are the one -where the existing design already pays for most of the cost** — the B-tree is -copy-on-write, which is most of the machinery; nothing takes or names a snapshot. +**The missing features**, listed honestly before adding any: no journal replay across an unclean shutdown beyond what +copy-on-write gives for free, no snapshots, no sparse-file support, and no online `fsck`. **Snapshots are the one where the +existing design already pays for most of the cost** — the B-tree is copy-on-write, which is most of the machinery; nothing takes +or names a snapshot. ## 7. The kernel's own gaps Not phases, and each one improves everything above it: -- **A slab or pool allocator.** `kalloc` hands out one page at a time, and every - subsystem written since has invented its own fixed-size table to work around - that. This is the single change that would most improve the rest of the kernel. -- **A page cache**, which is what a general writable `MAP_SHARED` needs. - [foundations.md](foundations.md) delivers a `memfd` instead, which is narrower - and sufficient for the GUI. -- **Symlinks in procfs**, so `/proc//exe` can be one and `ps -o comm` stops - printing `[init]`. Belongs with the rest of the VFS work rather than on its own. -- **The `ls -lR` hang** — **not reproducible**, and kept here because the absence - of a result is a result. Instrumented and hunted at length; see Gate 0 of - [1.0.md](1.0.md). Two real defects were found on the way and fixed, neither - proven to be the cause. -- **No garbage collector for descriptors in flight.** An `AF_UNIX` socket passed - over itself and never received leaks the pair. Bounded at thirty-two sockets, - and nothing in the tree does it — but a third-party program could. +- **A page cache**, which is what a general writable `MAP_SHARED` of a file needs. `memfd` is the narrow answer and has been enough. +- **A working `mprotect`, and NX.** `mprotect` is accepted and ignored and every mapping is executable; RELRO, guard pages + and a W^X JIT all want the real thing. +- **Per-CPU run queues** and work stealing, with a load balancer that is not worse than the lock it replaces. +- **A garbage collector for descriptors in flight.** An `AF_UNIX` socket passed over itself and never received leaks the pair; + bounded at thirty-two, and nothing in the tree does it. +- **Kernel-time accounting**, so `ru_stime` is not zero. +- **A contiguous multi-page allocator**, so `struct proc` and `struct socket` can come from somewhere other than fixed tables. --- ## Kept out of scope on purpose -- Anything that needs pushing or publishing. This work stays local. - TrueType hinting. -- XWayland. +- DRM/KMS as a *kernel* subsystem; the GPU plan serves `/dev/dri` from a process. + +## Unfinished, as of 2026-10-26 + +Left where it stood; none of it is hidden by the code. + +- **`startx` as root fails.** The image now has only root, and in QEMU `startx` + ends with `xinit: unable to connect to X server: Bad file descriptor` and + writes `/var/log/Xorg.0.log` (not `~/.local/share/xorg/`). It worked as the + old `schos` user. The cause has not been looked at; start with that log. +- **Window Maker on the laptop hung** (no cursor movement, no exit, no VT + switch) while Xorg was using `xf86-input-libinput`. That package is out of the + image again; whether Xorg's own evdev driver is enough on that machine is not + confirmed. If it still hangs, the `Xorg.0.log` from the machine is what is + needed. +- **ldlsd's crash on real hardware** was a misaligned `movaps` in a variadic + function; LDLS guest code is now built with `-mstackrealign`. Not run on the + laptop since. Two `SchOS: permission denied (write)` lines appear just before + it; the message now names the process and inode so the next log says who. +- **Boot manager pointer**: the cursor is drawn whenever a pointer device + exists, but no click or movement has been seen from a real device (QEMU's USB + mouse never reached the loader). A laptop touchpad is usually not exposed to + UEFI at all. +- **Kernel-side splash**: the loader's splash is in the new style; what vgpud + draws once the kernel is up is still the old mark. +- **USB**: no `/dev/bus/usb` (usbfs), so libusb tools such as plain `lsusb` do + not work (`lsusb -t` does); `uname -r` says `0.1.0`, which libusb refuses; hub + port counts and string descriptors are missing. +- **Terminals**: all twelve ask for a login. There is no longer a login-free + root shell to fall back on if `login` itself breaks. diff --git a/docs/plan/schapi.md b/docs/plan/schapi.md index bbc386f..b2f5329 100644 --- a/docs/plan/schapi.md +++ b/docs/plan/schapi.md @@ -1,6 +1,11 @@ # SchAPI, and SchOS as a system -Status: **direction agreed, not yet implemented.** Nothing in this document +Status: **direction agreed, not yet implemented**, and frozen until 1.0 ships +([1.0.md](1.0.md)). Read it as a direction and not as a schedule: written when the plan was a +compositor of SchOS's own, it has not been revisited since the desktop became Xorg and the +packages became Alpine's, and §7 (what happens now) in particular predates both. It also assumed SchOS would write its +own compositor (Schcom) for the API to be designed *from*; that compositor was deleted, and SchOS's desktop +is other people's, so read every mention of Schcom below as "the desktop". Nothing in this document changes what is being built this week; it changes what everything is being built *towards*. See §7 for what actually happens now. diff --git a/docs/plan/wayland.md b/docs/plan/wayland.md deleted file mode 100644 index 1008c2b..0000000 --- a/docs/plan/wayland.md +++ /dev/null @@ -1,177 +0,0 @@ -# The GUI becomes Wayland - -Schcom survives and changes its wire format. SchWM and SchDE are deleted. What -follows is why that is not a retreat, and what it costs. - -Blocked on [foundations.md](foundations.md). Everything there is built except -item 4, `setsockopt` that does not lie: descriptor passing, shareable memory, -sockets in the filesystem, dynamic linking, and the epoll/eventfd/timerfd/ -signalfd set libwayland's event loop is made of. - ---- - -## What was deleted - -`projects/schwm/` and `projects/schde/`, both README-only; no code was lost. They -are **gone from the tree now** rather than marked for deletion — a directory that -has said "to be deleted" for months is a corner, not a decision. The arguments -each made are kept below, because they are the part worth keeping. - -### SchWM is not abandoned — Wayland dissolves it - -In X11 the window manager is a separate privileged client because X11 puts -compositing in the server and policy nowhere. **Wayland's central decision is -that the compositor *is* the window manager**: there is no protocol by which a -second process places another's windows, and every attempt to add one has been a -private extension. - -So SchWM's own argument — - -> *"Compositing is mechanical and must never stall: it runs every frame and a -> mistake there is a frozen screen. Window management is policy, full of special -> cases and personal taste, and it changes constantly."* - -— is a real and correct observation **about X11**, which Wayland answers a -different way. It stays worth honouring, but as a **module boundary inside -Schcom**, not a process boundary: policy in its own file behind its own -interface. Do not keep it in its own process, because the protocol to talk to it -does not exist. - -### SchDE is re-sited rather than abandoned - -A panel, a launcher and a wallpaper are ordinary Wayland clients using -`wlr-layer-shell` — MIT, an XML file, no dependency. They need no directory of -their own until one of them is written. SchDE's other observation stands on its -own and is recorded here so it does not go with the directory: **a GUI terminal -is not the kernel console with a window around it.** The console parses ANSI -because it draws straight to the framebuffer; a terminal client parses ANSI -itself and renders it with proportional-capable text. The two share no code. - -### `projects/schinst/` stays - -Its GUI was the part tied to SchWM, and that part becomes a Wayland client. -Everything else in it — the `.sip` format, `mksip`, the apply path, the -installation environment — is untouched by any of this, and remains the -best-specified unstarted work in the tree. - ---- - -## What survives - -**Schcom stays, and keeps its name.** It already owns the display, allocates and -owns client buffers, implements surface/attach/commit, routes input from -`/dev/input/eventN`, composites multiple clients bottom-to-top, and cleans up -after clients that die. - -Its own README says the protocol is deliberately Wayland-shaped — surfaces rather -than windows, buffers attached and made visible only on commit, the compositor -telling clients when to draw. That was the right call and this is where it is -cashed in: **what changes is the wire format and the buffer handle, not the -architecture.** - ---- - -## What has to exist first - -Items 1–4 of [foundations.md](foundations.md), and the first three are absolute — -libwayland does not start without them. - -The sharpest is descriptor passing. `wl_shm_create_pool` passes a **file -descriptor**, and there is no path in the protocol that passes an integer -instead. `struct schcom_surface`'s `handle_kind` field was written precisely so -that switching would be a new value rather than a protocol break, and it cannot -help: *our* protocol is the flexible one. - ---- - -## Port libwayland. Do not port Weston. - -**libwayland is the small piece**: a wire protocol, an object map, and an event -loop, needing `libffi` and — for the build-time scanner, on the host only — -`libexpat`. A real port, but a bounded one. - -**Weston is the trap.** It arrives welded to `libinput`, `udev`, `libdrm`, -`logind`-shaped seat management and a GL renderer, and porting it means porting -Linux's device management first and finding the compositor somewhere underneath. - -This is precisely the lesson already recorded twice in this tree — about -FreeBSD's `sys/dev/usb` (100k lines welded to newbus) and about FreeBSD's -`sys/netinet` (not a library; reaches into mbufs, epoch, netisr, VNET and uma). - -> The tree has learned this twice. It should not pay to learn it a third time. - -So **Schcom links `libwayland-server` and remains ours.** Input comes from -`/dev/input/eventN`, which already works, rather than from libinput. Output goes -through `display_ops`, which already exists. - -### Software rendering first, and probably for a long time - -`pixman` for composition, `wl_shm` for buffers. Mesa means EGL means `dlopen` -means a dynamic loader -- which now exists (see [foundations.md](foundations.md) -item 5), so it is no longer what stands in the way of Mesa; a GPU driver is. -Static binaries are still enough to reach a working desktop, and the desktop's -own programs can stay static until something concrete demands otherwise. - -### One sharp edge - -`wl_keyboard` hands the client **a keymap as an XKB text blob over a file -descriptor**. A compositor cannot avoid xkbcommon by having simpler opinions -about keyboards — the protocol requires the blob. - -The cheap route is one precompiled keymap string shipped in the binary, with -xkbcommon and `xkeyboard-config` deferred until more than one layout is wanted. - ---- - -## Licensing - -Checked before anything is fetched — the standing rule is that the licence goes -in `NOTICE` **before** the file is committed, not after. - -| | | -|---|---| -| wayland, wayland-protocols | MIT | -| `wlr-layer-shell` | MIT | -| pixman | MIT | -| libffi | MIT | -| libxkbcommon | MIT | -| xkeyboard-config | MIT | - -All GPL-2.0-or-later compatible, all needing only a `NOTICE` entry. The -Wayland ecosystem is permissively licensed almost throughout, so nothing here -raises a licensing question at all. - ---- - -## Milestones - -| | | -|---|---| -| **W0** | [foundations.md](foundations.md) items 1–4, with the combined test that passes a descriptor *and* shares a `memfd` *and* binds a socket at a real path. | -| **W1** | libwayland builds and installs into the sysroot. `wayland-scanner` runs on the host. | -| **W2** | Schcom serves `wl_compositor`, `wl_shm`, `wl_surface`, `xdg_shell`. A client draws one window. | -| **W3** | `wl_seat`: keyboard and pointer, from the evdev path that exists. | -| **W4** | Two clients, focus, stacking, move and resize. **This is where SchWM's policy lives, as a file.** | -| **W5** | `wlr-layer-shell`, and a panel. This is SchDE's first pixel. | - -Every milestone from W2 on is asserted **from the host, by pixel**, the way -`schcom-test` already does. The compositor's own report that it composited -something proves nothing. - ---- - -## What it will still not do - -- **No hardware acceleration.** Software rendering only, until Mesa, which is - not on this plan. Dynamic linking, which it also needs, is in. -- **No XWayland**, so no X11 clients, ever, unless someone ports it. -- **No mode setting.** Resolution stays whatever the loader asked for — see - [hybrid.md](hybrid.md), where the GPU driver's eviction is also where a modeset - API would arrive. -- **No damage tracking beyond "repaint everything"**, no multiple outputs, no - subsurfaces. Schcom's README already lists these; they are additions rather - than rewrites, which is the reason for committing to the surface/commit model - now. -- **One keyboard layout**, until xkbcommon is ported. -- **No clipboard, no drag and drop, no screen capture protocol**, none of which - W0–W5 touches. diff --git a/docs/plan/xorg.md b/docs/plan/xorg.md deleted file mode 100644 index db5d4e5..0000000 --- a/docs/plan/xorg.md +++ /dev/null @@ -1,44 +0,0 @@ -# Xorg - -The desktop is X11, on the framebuffer. This replaces [wayland.md](wayland.md) -as the plan for Gate 3 of [1.0.md](1.0.md); Wayland stays on file and is not -being worked on. - -## Why X and not Wayland - -Every Wayland compositor worth running -- labwc, and everything else built on -wlroots -- needs DRM/KMS, libinput, udev and libseat. DRM/KMS is the largest of -them by far: a kernel graphics subsystem, and in practice a great deal taken -from Linux. Xorg runs on a plain framebuffer through its `fbdev` driver and -reads input through `evdev`, both of which are interfaces SchOS already has. -It is the shorter road to a desktop by a long way, and it brings the window -managers with it: twm first, then Window Maker. - -DRM/KMS is not planned. libinput, udev and libseat are worth having for their -own sake and may come later; Xorg does not need them. - -## What is built - -- **The kernel calls** (`b1323e1`, checked by `cmd/xready`): evdev's queries, - fbdev's mode-set to the current mode, `KDSETMODE`, `KDSKBMODE`, the - `VT_PROCESS` switch handshake, and `/sys/bus/pci/devices/*/{boot_vga, - enable,graphics/fb0}` for libpciaccess. -- **Dynamic linking and `dlopen`** (`4330576`): Xorg loads its drivers as - modules. -- **``** in the build sysroot, as `ports/linux-headers`. - -## What is left, in order - -1. **The X client libraries.** xorgproto, xtrans, libXau, libXdmcp, - xcb-proto, libxcb, libX11, libXext, libXrender, libXfixes, libxkbfile, - libfontenc, libXfont2, freetype, zlib, pixman, libpciaccess. -2. **Keyboard and fonts.** xkbcomp and xkeyboard-config, the data X compiles - keymaps from; a misc-fixed font. -3. **The server.** xorg-server built with meson, GLX, glamor and DRI off; - xf86-video-fbdev; xf86-input-evdev with libevdev and mtdev. -4. **The first picture.** `twm` and `xterm`, started by hand from a VT. -5. **Window Maker**, and an `startx` that works. - -The kernel calls were written against what these programs are documented -to do. Each step past it is expected to find a call that was not -- that is -what running the real program is for. diff --git a/etc/schinit/console-shell.job b/etc/schinit/console-shell.job index 04f15bf..22ee943 100644 --- a/etc/schinit/console-shell.job +++ b/etc/schinit/console-shell.job @@ -1,11 +1,11 @@ # The interactive shell on /dev/tty1, the first virtual terminal. Label = os.schos.console-shell -# zsh, not /bin/sh. mksh stays as /bin/sh and is what schinit falls back to -# when no manifest loads -- so if zsh ever fails to start, the recovery shell -# is a different program rather than the same broken one. See ports/zsh. -Program = /bin/zsh -Args = -zsh +# A login prompt, not a shell: /etc/schos-getty hands the terminal to login, +# which asks who is there and starts their shell. tty6 keeps a root shell +# with no login, for when this part of the system is what is broken. +Program = /bin/sh +Args = sh /etc/schos-getty RunAtLoad = yes KeepAlive = yes @@ -15,8 +15,6 @@ KeepAlive = yes # most: a line editor with no TERM cannot ask terminfo how to move a cursor. Environment = TERM=linux Environment = PATH=/bin:/usr/bin -Environment = HOME=/root -Environment = SHELL=/bin/zsh # This job reads the console, so it has to own it: schinit puts every job in # its own session, and a session with no controlling terminal is not delivered diff --git a/etc/schinit/ldls-r8169.job b/etc/schinit/ldls-r8169.job new file mode 100644 index 0000000..1746390 --- /dev/null +++ b/etc/schinit/ldls-r8169.job @@ -0,0 +1,17 @@ +# The Realtek RTL8111/8168 ethernet, driven by Linux's own r8169. +# +# The modules (mdio-bus, libphy, mdio_devres, the Realtek PHY driver and r8169 +# itself), the kernel's BTF and the RTL PHY firmware are put in the image by +# `make ldls-r8169`. ldlsd loads them in that order, binds the PHY driver +# through its driver core, and probes the NIC; the interface is ldls0. +# +# On a machine with no RTL8168 ldlsd exits SCHINIT_EXIT_NORESTART and that is +# the end of it. KeepAlive is off while this is new: a driver that crashes on +# real hardware would otherwise be restarted into the same crash every few +# seconds. Its output, every line of it, is in /var/log/ldls-r8169.log. + +Label = os.schos.ldls-r8169 +Program = /bin/sh +Args = sh /lib/modules/ldls-r8169.sh +RunAtLoad = yes +KeepAlive = no diff --git a/etc/schinit/seatd.job b/etc/schinit/seatd.job new file mode 100644 index 0000000..7849386 --- /dev/null +++ b/etc/schinit/seatd.job @@ -0,0 +1,17 @@ +# The seat daemon: hands a Wayland compositor (through libseat) the input and +# display devices it needs without running the compositor as root. seatd opens +# them as itself and passes the file descriptors over its socket; the socket is +# group `video`, which the first user is in. +# +# Alpine's seatd package. A compositor that runs as root, or one started with +# seatd-launch, needs none of this. + +Label = os.schos.seatd +Program = /usr/bin/seatd +Args = seatd -g video +RunAtLoad = yes +KeepAlive = yes +ThrottleInterval = 3 + +StandardOut = /dev/null +StandardError = /dev/null diff --git a/etc/schinit/tty10-shell.job b/etc/schinit/tty10-shell.job new file mode 100644 index 0000000..c1e88b3 --- /dev/null +++ b/etc/schinit/tty10-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty10: every one of the twelve virtual terminals asks who is there. +# Alt+F10 (or Ctrl+Alt+F10) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty10-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty10 +StandardOut = /dev/tty10 +StandardError = /dev/tty10 + +ThrottleInterval = 1 diff --git a/etc/schinit/tty11-shell.job b/etc/schinit/tty11-shell.job new file mode 100644 index 0000000..5d25203 --- /dev/null +++ b/etc/schinit/tty11-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty11: every one of the twelve virtual terminals asks who is there. +# Alt+F11 (or Ctrl+Alt+F11) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty11-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty11 +StandardOut = /dev/tty11 +StandardError = /dev/tty11 + +ThrottleInterval = 1 diff --git a/etc/schinit/tty12-shell.job b/etc/schinit/tty12-shell.job new file mode 100644 index 0000000..212ac19 --- /dev/null +++ b/etc/schinit/tty12-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty12: every one of the twelve virtual terminals asks who is there. +# Alt+F12 (or Ctrl+Alt+F12) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty12-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty12 +StandardOut = /dev/tty12 +StandardError = /dev/tty12 + +ThrottleInterval = 1 diff --git a/etc/schinit/tty2-shell.job b/etc/schinit/tty2-shell.job index e7127a2..e437bfc 100644 --- a/etc/schinit/tty2-shell.job +++ b/etc/schinit/tty2-shell.job @@ -6,15 +6,13 @@ # what /dev/tty1 and /dev/tty2 being different terminals means. Label = os.schos.tty2-shell -Program = /bin/zsh -Args = -zsh +Program = /bin/sh +Args = sh /etc/schos-getty RunAtLoad = yes KeepAlive = yes Environment = TERM=linux Environment = PATH=/bin:/usr/bin -Environment = HOME=/root -Environment = SHELL=/bin/zsh ControllingTerminal = yes diff --git a/etc/schinit/tty3-shell.job b/etc/schinit/tty3-shell.job index 03449a5..6b6e8c5 100644 --- a/etc/schinit/tty3-shell.job +++ b/etc/schinit/tty3-shell.job @@ -1,24 +1,15 @@ -# An interactive shell on /dev/tty3. -# -# NVT is 6 (include/dev/vt.h) and the kernel creates all six terminals, but only -# two ever had anything running on them -- so Alt+F3 switched to a terminal -# that was working perfectly and had nobody to talk to, which looks exactly -# like a broken one. -# -# Identical to tty2-shell apart from the number. Six manifests rather than one -# with a range, because schinit has no notion of a template and inventing one -# for six lines each is the wrong trade. +# A login on /dev/tty3: every one of the twelve virtual terminals asks who is there. +# Alt+F3 (or Ctrl+Alt+F3) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. Label = os.schos.tty3-shell -Program = /bin/zsh -Args = -zsh +Program = /bin/sh +Args = sh /etc/schos-getty RunAtLoad = yes KeepAlive = yes Environment = TERM=linux Environment = PATH=/bin:/usr/bin -Environment = HOME=/root -Environment = SHELL=/bin/zsh ControllingTerminal = yes diff --git a/etc/schinit/tty4-shell.job b/etc/schinit/tty4-shell.job index fa22d4f..b9d021a 100644 --- a/etc/schinit/tty4-shell.job +++ b/etc/schinit/tty4-shell.job @@ -1,24 +1,15 @@ -# An interactive shell on /dev/tty4. -# -# NVT is 6 (include/dev/vt.h) and the kernel creates all six terminals, but only -# two ever had anything running on them -- so Alt+F4 switched to a terminal -# that was working perfectly and had nobody to talk to, which looks exactly -# like a broken one. -# -# Identical to tty2-shell apart from the number. Six manifests rather than one -# with a range, because schinit has no notion of a template and inventing one -# for six lines each is the wrong trade. +# A login on /dev/tty4: every one of the twelve virtual terminals asks who is there. +# Alt+F4 (or Ctrl+Alt+F4) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. Label = os.schos.tty4-shell -Program = /bin/zsh -Args = -zsh +Program = /bin/sh +Args = sh /etc/schos-getty RunAtLoad = yes KeepAlive = yes Environment = TERM=linux Environment = PATH=/bin:/usr/bin -Environment = HOME=/root -Environment = SHELL=/bin/zsh ControllingTerminal = yes diff --git a/etc/schinit/tty5-shell.job b/etc/schinit/tty5-shell.job index 70c49da..76ee275 100644 --- a/etc/schinit/tty5-shell.job +++ b/etc/schinit/tty5-shell.job @@ -1,24 +1,15 @@ -# An interactive shell on /dev/tty5. -# -# NVT is 6 (include/dev/vt.h) and the kernel creates all six terminals, but only -# two ever had anything running on them -- so Alt+F5 switched to a terminal -# that was working perfectly and had nobody to talk to, which looks exactly -# like a broken one. -# -# Identical to tty2-shell apart from the number. Six manifests rather than one -# with a range, because schinit has no notion of a template and inventing one -# for six lines each is the wrong trade. +# A login on /dev/tty5: every one of the twelve virtual terminals asks who is there. +# Alt+F5 (or Ctrl+Alt+F5) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. Label = os.schos.tty5-shell -Program = /bin/zsh -Args = -zsh +Program = /bin/sh +Args = sh /etc/schos-getty RunAtLoad = yes KeepAlive = yes Environment = TERM=linux Environment = PATH=/bin:/usr/bin -Environment = HOME=/root -Environment = SHELL=/bin/zsh ControllingTerminal = yes diff --git a/etc/schinit/tty6-shell.job b/etc/schinit/tty6-shell.job index 282f060..ff62eda 100644 --- a/etc/schinit/tty6-shell.job +++ b/etc/schinit/tty6-shell.job @@ -1,24 +1,15 @@ -# An interactive shell on /dev/tty6. -# -# NVT is 6 (include/dev/vt.h) and the kernel creates all six terminals, but only -# two ever had anything running on them -- so Alt+F6 switched to a terminal -# that was working perfectly and had nobody to talk to, which looks exactly -# like a broken one. -# -# Identical to tty2-shell apart from the number. Six manifests rather than one -# with a range, because schinit has no notion of a template and inventing one -# for six lines each is the wrong trade. +# A login on /dev/tty6: every one of the twelve virtual terminals asks who is there. +# Alt+F6 (or Ctrl+Alt+F6) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. Label = os.schos.tty6-shell -Program = /bin/zsh -Args = -zsh +Program = /bin/sh +Args = sh /etc/schos-getty RunAtLoad = yes KeepAlive = yes Environment = TERM=linux Environment = PATH=/bin:/usr/bin -Environment = HOME=/root -Environment = SHELL=/bin/zsh ControllingTerminal = yes diff --git a/etc/schinit/tty7-shell.job b/etc/schinit/tty7-shell.job new file mode 100644 index 0000000..2f1a9da --- /dev/null +++ b/etc/schinit/tty7-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty7: every one of the twelve virtual terminals asks who is there. +# Alt+F7 (or Ctrl+Alt+F7) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty7-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty7 +StandardOut = /dev/tty7 +StandardError = /dev/tty7 + +ThrottleInterval = 1 diff --git a/etc/schinit/tty8-shell.job b/etc/schinit/tty8-shell.job new file mode 100644 index 0000000..5ce6fd6 --- /dev/null +++ b/etc/schinit/tty8-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty8: every one of the twelve virtual terminals asks who is there. +# Alt+F8 (or Ctrl+Alt+F8) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty8-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty8 +StandardOut = /dev/tty8 +StandardError = /dev/tty8 + +ThrottleInterval = 1 diff --git a/etc/schinit/tty9-shell.job b/etc/schinit/tty9-shell.job new file mode 100644 index 0000000..511dd1a --- /dev/null +++ b/etc/schinit/tty9-shell.job @@ -0,0 +1,20 @@ +# A login on /dev/tty9: every one of the twelve virtual terminals asks who is there. +# Alt+F9 (or Ctrl+Alt+F9) shows it. Twelve manifests rather than one with a +# range, because schinit has no notion of a template. + +Label = os.schos.tty9-shell +Program = /bin/sh +Args = sh /etc/schos-getty +RunAtLoad = yes +KeepAlive = yes + +Environment = TERM=linux +Environment = PATH=/bin:/usr/bin + +ControllingTerminal = yes + +StandardIn = /dev/tty9 +StandardOut = /dev/tty9 +StandardError = /dev/tty9 + +ThrottleInterval = 1 diff --git a/etc/schinit/udev-trigger.job b/etc/schinit/udev-trigger.job new file mode 100644 index 0000000..aaf57d6 --- /dev/null +++ b/etc/schinit/udev-trigger.job @@ -0,0 +1,14 @@ +# Replay the devices that were already there when udevd started, so /run/udev +# knows about them. Runs once; see /etc/udev-trigger. +# +# udevd is started by its own job and schinit has no ordering, so the script +# waits for the daemon's control socket. + +Label = os.schos.udev-trigger +Program = /bin/sh +Args = sh /etc/udev-trigger +RunAtLoad = yes +KeepAlive = no + +StandardOut = /dev/tty1 +StandardError = /dev/tty1 diff --git a/etc/schinit/udevd.job b/etc/schinit/udevd.job new file mode 100644 index 0000000..4e8a55e --- /dev/null +++ b/etc/schinit/udevd.job @@ -0,0 +1,16 @@ +# eudev's daemon: the thing that turns the kernel's uevents into /run/udev +# data and /dev symlinks, which is how Xorg and every Wayland compositor learn +# what input and display devices exist. +# +# Alpine's eudev package; the kernel side is the uevent netlink socket and the +# /sys device tree (kernel/dev/kdev.c, kernel/fs/sysfs.c). + +Label = os.schos.udevd +Program = /sbin/udevd +Args = udevd +RunAtLoad = yes +KeepAlive = yes +ThrottleInterval = 3 + +StandardOut = /dev/tty1 +StandardError = /dev/tty1 diff --git a/etc/schos-getty b/etc/schos-getty new file mode 100644 index 0000000..23cb842 --- /dev/null +++ b/etc/schos-getty @@ -0,0 +1,10 @@ +#!/bin/sh +# What a virtual terminal runs: take the terminal back from whoever had it, and +# ask for a login. login hands the terminal to the user it lets in, and this +# runs again when their shell exits. +t=$(tty 2>/dev/null) +if [ -n "$t" ]; then + chown root:tty "$t" 2>/dev/null + chmod 0660 "$t" 2>/dev/null +fi +exec /bin/login diff --git a/etc/udev-schos.rules b/etc/udev-schos.rules new file mode 100644 index 0000000..a1366d3 --- /dev/null +++ b/etc/udev-schos.rules @@ -0,0 +1,14 @@ +# SchOS's own device permissions, applied after Alpine's (the name sorts last +# once installed as 90-schos.rules). +# +# The kernel makes these nodes with the permissions it means them to have +# (kernel/fs/devfs.c). udev, with no rule that names a node, would chmod it to +# 0600 -- which made /dev/null unusable to everyone but root -- so every kernel +# device is named here with the same answer the kernel gave. + +SUBSYSTEM=="mem", MODE="0666" +SUBSYSTEM=="tty", KERNEL=="console", GROUP="root", MODE="0600" +SUBSYSTEM=="tty", KERNEL=="tty0|tty[0-9]|tty1[0-2]", GROUP="tty", MODE="0660" +SUBSYSTEM=="input", GROUP="input", MODE="0660" +SUBSYSTEM=="graphics", GROUP="video", MODE="0660" +SUBSYSTEM=="block", GROUP="disk", MODE="0660" diff --git a/etc/udev-trigger b/etc/udev-trigger new file mode 100644 index 0000000..52f13ab --- /dev/null +++ b/etc/udev-trigger @@ -0,0 +1,9 @@ +#!/bin/sh +# Wait for udevd to listen, then ask the kernel to announce every device again. +i=0 +while [ ! -S /run/udev/control ] && [ $i -lt 100 ]; do + sleep 0.1 + i=$((i + 1)) +done +udevadm trigger --action=add +udevadm settle --timeout=60 diff --git a/include/arch/x86.h b/include/arch/x86.h index c695b9d..46933d9 100644 --- a/include/arch/x86.h +++ b/include/arch/x86.h @@ -198,6 +198,14 @@ lcr3(uint64 val) asm volatile("mov %0,%%cr3" : : "r" (val)); } +static inline uint64 +rcr3(void) +{ + uint64 val; + asm volatile("mov %%cr3,%0" : "=r" (val)); + return val; +} + // Invalidate the TLB entry for one page. Needed any time a PTE is changed // or cleared for an address space that may still be the active CR3 — // otherwise the CPU can keep translating through the stale entry until the diff --git a/include/core/defs.h b/include/core/defs.h index c7cbd0c..86e838b 100644 --- a/include/core/defs.h +++ b/include/core/defs.h @@ -61,6 +61,39 @@ int pciuser_mmio_ok(uint64 phys, uint64 len); void pciuser_release_all(struct proc*); int pciuser_owner_pid(int idx); int proc_reincarnate(int status); +int sig_default_stops(int); +struct mm* mm_alloc(void); +void mm_get(struct mm*); +void mm_put(struct mm*); +int mm_shared(struct mm*); +void mm_lock(struct mm*); +void mm_unlock(struct mm*); +struct files* files_alloc(void); +void files_get(struct files*); +void files_put(struct files*); +void files_lock(void); +void files_unlock(void); +struct sighand* sighand_alloc(void); +void sighand_get(struct sighand*); +void sighand_put(struct sighand*); +struct proc* proc_leader(struct proc*); +int proc_thread_count(int); +void thread_exit(void) __attribute__((noreturn)); +int futex_wake_key(void*, int); +int futex_op(addr_t, int, int, addr_t, addr_t, int); +int clone_proc(uint64, uint64, uint64, uint64, uint64); +void proc_exec_threads(void); +void proc_exec_done(void); +void thread_reaper_start(void); +void vm_defer_tick(void); +void mm_release_shared(struct mm*); +void proc_stop(int); +int sig_attention(struct proc*); +int sig_blocking_check(void); +void proc_cputime(uint64 *user, uint64 *sys); +// kernel/dev/procfs.c: /proc/strace +int strace_wanted(const char *name); +int proc_write_strace(int user_src, uint64 src, uint n); void proc_kill_init(void); extern uint init_generation; void cprintf(char*, ...); @@ -147,6 +180,10 @@ void mountinit(void); // 1 if the filesystem on this device refuses writes -- a mounted snapshot. // Ask before mutating, not after: the create path panics on a failed dirlink. int fs_readonly(uint dev); +int in_group(uint gid, int real); +uint inode_mode_bits(struct inode *ip); +int inode_permission(struct inode *ip, int mask, int real); +int capable(void); int mount_add(uint dev, uint64 inum, uint fsdev, uint64 root, const char *path, const char *source, const char *type); // Walk the mount table for /proc/mounts. `path` and `source` need MAXPATH @@ -457,6 +494,8 @@ extern uint64 boot_epoch; int cpunum(void); extern volatile uint* lapic; void lapiceoi(void); +extern volatile int panic_stop; // kernel/core/panic.c +void lapic_nmi_others(void); void lapicinit(void); void lapicstartap(uchar, uint); void microdelay(int); @@ -498,8 +537,22 @@ int sockname_inet(struct socket*, int peer, uint *addr, ushort *port #define SOCK_ABSTRACT_DEV 0xFFFFFFFFu // no filesystem has it; see unix.c int sockreadable(struct socket*); int sock_hup(struct socket*); +uint socknl_pending(struct socket*); +int sock_sendercred(struct socket*, int*, uint*, uint*); +int socknl_copy(struct socket*, uint, char*, int); +void socknl_take(struct socket*, uint); int sock_shutdown(struct socket*, int how); int sockpair(struct socket **a, struct socket **b); +int sockpair_type(struct socket **a, struct socket **b, int type); +int sock_is_packet(struct socket *s); +uint sock_nl_groups(struct socket *s); +int sock_netlink_bind(struct socket *s, uint pid, uint groups); +int sock_uevent_send(struct socket *s, const char *msg, int len, uint dpid, uint dgroups); +uint sock_nl_from(struct socket *s); +uint sock_nl_pid(struct socket *s); +uint sock_nl_bound_groups(struct socket *s); +void dev_stat_numbers(int major, int minor, int *maj, int *min); +void uevent_send(uint groups, const char *msg, int len, struct socket *from); const char* sockname(struct socket*); // AF_INET addressing. Separate from sockbind/sockconnect because the families // do not name the same kind of thing; `addr` and `port` are network order. @@ -510,6 +563,8 @@ int socksendto(struct socket*, char *buf, int n, int sockbind_in(struct socket*, uint addr, ushort port); int sockconnect_in(struct socket*, uint addr, ushort port); int sock_domain(struct socket*); +int sock_peercred(struct socket*, int*, uint*, uint*); +int sock_passcred(struct socket*); // Socket options. The syscall layer does the user-memory marshalling and these // do the work, because struct socket's fields live in kernel/net/unix.c and the // syscall layer deliberately cannot see them. @@ -550,6 +605,8 @@ int fork(void); int growproc(int64); int kill(int); int sigsend_pgrp(int pgid, int sig); +int sigsend_user(int pid, int sig); +int sigsend_pgrp_user(int pgid, int sig); int proc_pgid(int pid); int proc_setpgid(int pid, int pgid); int proc_setsid(void); @@ -564,6 +621,7 @@ int proc_exists(int); int proc_num(void); struct procinfo; int proc_snapshot(int idx, struct procinfo *out); +int proc_peek(int idx, struct procinfo *out); // no lock: GMMOS int proc_snapshot_pid(int pid, struct procinfo *out); void pinit(void); void procdump(void); @@ -696,6 +754,18 @@ int net_eth_detach(struct device *); int net_detach(struct device *); int net_attach_late(struct device *); int net_eth_poll(void); +void net_eth_link_check(void); +uint poll_generation(void); +// kernel/proc/mapname.c +struct mm; +void mapname_add(struct mm*, uint64 start, uint64 len, uint64 off, const char *path); +void mapname_unmap(struct mm*, uint64 start, uint64 len); +void mapname_free(struct mm*); +void mapname_copy(struct mm *dst, struct mm *src); +int mapname_find(struct mm*, uint64 addr, char *out, int cap, uint64 *off); +int mapname_gen(struct mm*, char *buf, int cap); +void mapname_report(struct proc*); +int proc_maps(int pid, char *buf, int cap); int net_eth_count(void); void net_eth_counts(int i, uint64 *rx, uint64 *tx); struct netif *net_eth_netif(int i); @@ -760,9 +830,27 @@ void shm_ref(int id); char* shm_page(int id, int pg); uint64 memfd_size(int id); int memfd_truncate(int id, uint64 len); +int memfd_seals(int id, int add); +int dsp_writable(void); +// inotify (kernel/sys/sys_event.c), and the lookup it needs (sys_fs.c) +void inotify_event(uint dev, uint64 inum, uint32 mask, const char *name, uint32 cookie); +void inotify_file(struct file *f, uint32 mask); +uint32 inotify_cookie(void); +int ev_fionread(struct evobj *o); +int pipe_avail(struct pipe *p); +int vm_fault(addr_t va); +void tlb_init(void); +// keyboard lock keys and their lights: kernel/dev/kbd.c +void kbd_set_leds(uint locks); +void kbd_lock_key(uint lock, int down); +uint kbd_lock_state(void); +void hid_leds_want(int bits); +void lapic_send_ipi(int apicid, int vector); +pte_t* walkpte(pml4e_t *pgdir, const void *va); +int path_identity(char *path, int follow, int onlydir, uint *dev, uint64 *inum); int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing); void shm_detach(int id); -void proc_release_shared(struct proc *); + int copy_vma_range(pml4e_t *dstpgdir, pml4e_t *srcpgdir, uint64 va, uint64 length, int is_fb); void clearpteu(pml4e_t *pgdir, char *uva); diff --git a/include/core/dma.h b/include/core/dma.h index a4b5e54..c4d751a 100644 --- a/include/core/dma.h +++ b/include/core/dma.h @@ -26,7 +26,7 @@ // // 4 MB was one framebuffer at 1280x800, which was fine while exactly one driver // wanted one. It stopped being fine the day a display driver could be handed to -// userspace (docs/plan/hybrid.md): either side of that handover **both drivers +// userspace (docs/architecture/devices.md): either side of that handover **both drivers // exist**, and the incoming one has to allocate its framebuffer before the // outgoing one lets go of its own. So the real requirement is two framebuffers // at once, not one. diff --git a/include/core/hwinfo.h b/include/core/hwinfo.h index 2024376..e90a052 100644 --- a/include/core/hwinfo.h +++ b/include/core/hwinfo.h @@ -57,7 +57,31 @@ struct usb_info { uint16 vid, pid, bcd; uint8 cls, subcls, proto; // the device descriptor's uint8 nconf; + uint16 bcdusb; // the version the device speaks + uint8 maxp0; // bMaxPacketSize0 + uint8 nif, confval, attrs; // the configuration descriptor's + uint16 maxpower; // in mA char path[24]; // Linux's name: "1-3", "2-1.4" char driver[16]; // the first interface's, or empty }; int usb_info_nth(int i, struct usb_info *out); +// How many USB buses have shown a device so far; bus numbers run 1..this. +int usb_nbuses(void); +// Tell /sys and udev a USB device appeared ("add") or is about to go ("remove"). +void sysfs_usb_uevent(const struct usb_info *u, const char *action); +void sysfs_usb_root_uevent(int bus, const char *action); +// A USB controller is up, at this PCI address; number its bus and announce its +// root hub. +void usb_bus_add(void *hc, int pci_bus, int pci_slot, int pci_func); +// The PCI address of the controller behind bus number `bus`; 0, or -1 if none. +int usb_bus_pci(int bus, int *pci_bus, int *pci_slot, int *pci_func); + +// One interface of a configured USB device, in the order the configuration +// descriptor lists them (alternate setting 0 only). +struct usb_if_info { + uint8 num, cls, subcls, proto, nendp; + char driver[16]; // what is bound to it, or empty +}; +int usb_if_nth(int bus, int devnum, int n, struct usb_if_info *out); +void sysfs_usb_if_uevent(const struct usb_info *u, const struct usb_if_info *i, + const char *action); diff --git a/include/core/klog.h b/include/core/klog.h index e03fa4d..3e9f4eb 100644 --- a/include/core/klog.h +++ b/include/core/klog.h @@ -41,4 +41,5 @@ void klog_putc(int c); int klog_read(uint64 off, char *dst, int n); uint64 klog_len(void); uint64 klog_prev_bytes(void); +uint64 klog_boot_bytes(void); int klog_recovered(void); diff --git a/include/core/param.h b/include/core/param.h index fc7abe2..9b57243 100644 --- a/include/core/param.h +++ b/include/core/param.h @@ -29,7 +29,7 @@ #define NOFILE 128 // open files per process #define NFILE 512 // open files per system #define NINODE 200 // maximum number of active i-nodes -#define NDEV 13 // maximum major device number +#define NDEV 16 // maximum major device number #define RAMDISK_DEV 2 // memory-backed block device (kernel/dev/ramdisk.c) #define USBDISK_DEV 3 // USB mass storage (kernel/drivers/usb_storage.c) diff --git a/include/core/proc.h b/include/core/proc.h index fbb692e..9eb0e97 100644 --- a/include/core/proc.h +++ b/include/core/proc.h @@ -31,6 +31,41 @@ struct vma { int shmid; // shared segment this came from, or -1 }; +// --- What threads share --------------------------------------------------- +// +// A thread is a struct proc like any other, scheduled like any other. What +// makes several of them one program is that they point at the same three +// objects below, each counted, each freed with its last user: clone() with +// CLONE_VM, CLONE_FILES and CLONE_SIGHAND shares them, fork() copies them. + +// The address space. pgdir is mirrored in every struct proc that uses it, +// because the scheduler, trapasm and half the kernel read it from there; it +// only changes at exec, which makes a new mm. +struct mm { + int ref; + int locked; // mm_lock(): one mmap/munmap/brk at a time + uint64 sz; // top of the heap; p->sz mirrors it + uint64 mmap_next; // next free mmap VA (bump allocator, see sys_mmap) + pml4e_t *pgdir; + struct vma vma[NVMA]; // VMA tracking for mmap + struct mapname *names; // which file each span came from; mapname.c +}; + +// The descriptor table, and FD_CLOEXEC beside it. +struct files { + int ref; + struct file *ofile[NOFILE]; + char flags[NOFILE]; +}; + +// Signal dispositions: what sigaction() sets. +struct sighand { + int ref; + uint64 handler[32]; // 0 SIG_DFL, 1 SIG_IGN, else an address + uint64 restorer[32]; + uint64 flags[32]; // sa_flags; SA_RESTART is the one read +}; + // Per-CPU state struct cpu { uchar id; @@ -174,8 +209,14 @@ struct procinfo { uint64 nvcsw, nivcsw; // voluntary / involuntary context switches char name[16]; char exe[64]; // full path, for /proc//exe + int kthread; // a kernel thread, not a program + int stopped; // stopped by a signal (ps shows T) + int tgid; // the process this thread belongs to + int nthreads; // threads in that process }; +#define NGROUPS_MAX 32 + struct proc { uint64 sz; // Size of process memory (bytes) [+0x00] pml4e_t* pgdir; // Page table (PML4 on x86_64) [+0x08] @@ -189,7 +230,7 @@ struct proc { void *chan; // [+0x40] int killed; // [+0x48] int _pad; // [+0x4c] - struct file *ofile[NOFILE]; // Open files [+0x50] + struct files *files; // Open files, shared by threads [+0x50] struct inode *cwd; // follows char name[16]; // // The path exec() was given. `name` is only the last component and only 15 @@ -236,7 +277,6 @@ struct proc { // alarmd, in kernel/proc/proc.c, is what posts it. uint alarm_at; uint alarm_every; - char ofile_flags[NOFILE]; // Real signal delivery (added for SIGCHLD — see kernel/sys/syscall.c's // deliver_signals() and kernel/proc/proc.c's exit()). sig_handler[sig]/ @@ -245,8 +285,7 @@ struct proc { // the pre-signal trapframe so sys_rt_sigreturn() can restore it; in_signal // prevents re-entering a handler while one is already running (we don't // support nested signal delivery). - uint64 sig_handler[32]; - uint64 sig_restorer[32]; + struct sighand *sighand; // dispositions, shared by threads struct trapframe saved_sigtf; int in_signal; @@ -257,8 +296,7 @@ struct proc { uint64 sigsusp_oldmask; int sigsusp_active; - struct vma vma[NVMA]; // VMA tracking for mmap - uint64 mmap_next; // next free mmap VA (bump allocator, see sys_mmap) + struct mm *mm; // address space, shared by threads // ABI translation (Linux compat syscall number map) int *syscall_map; @@ -299,6 +337,10 @@ struct proc { // are tracked honestly so getuid()/setuid() stop lying to userland. uint uid, euid, suid; uint gid, egid, sgid; + // Supplementary groups, as setgroups(2) set them: a file's group is + // consulted against these as well as against egid. + int ngroups; + uint groups[NGROUPS_MAX]; // --- Fair-share scheduling -------------------------------------------- // The whole of the scheduler's per-process state. What each field means, @@ -321,6 +363,34 @@ struct proc { // chroot(2): where an absolute path starts, and where ".." stops. 0 is the // system's root. Inherited across fork, held as a reference like cwd. struct inode *root; + + // Syscall tracing: every call and its result go to the log. Set by exec + // when the program's name is the one written to /proc/strace, and kept + // across fork so that what a traced program starts is traced too. + int strace; + + // umask(2). Inherited across fork; 022 for the first process. + uint umask; + + // Job control. stopsig is the signal that stopped the process, 0 while it + // runs; stop_report and cont_report are what wait(WUNTRACED/WCONTINUED) + // has yet to tell the parent. + int stopsig; + int stop_report; + int cont_report; + + // Threads. tgid is the pid of the thread group's leader -- what getpid() + // returns -- and equal to pid for every ordinary process. + int tgid; + uint64 clear_child_tid; // CLONE_CHILD_CLEARTID: zeroed and woken at exit + void *futex_key; // the futex word this thread waits on, or 0 + int futex_woken; + int autoreap; // a thread: reapd frees it, nobody waits for it + struct proc *vfork_parent; // CLONE_VFORK: released at exec or exit + int vfork_wait; // the vfork child this process is waiting on + int thread_dying; // killed with its group: exits as a thread + int group_status; // a thread's exit_group status, for the leader + int group_status_set; }; #define FD_CLOEXEC 1 diff --git a/include/dev/acpi.h b/include/dev/acpi.h index 0a28a56..5ff868a 100644 --- a/include/dev/acpi.h +++ b/include/dev/acpi.h @@ -30,4 +30,35 @@ void acpi_thread_start(void); void acpi_pci_route(void); int acpi_poweroff(void); // S5; returns only on failure +void acpi_poweroff_now(void); // S5 from a panic, no AML; returns only on failure int acpi_reboot(void); // the FADT reset register; ditto + +// Batteries and AC adapters, for /sys/class/power_supply. Units are the +// battery's own: unit 0 is mW/mWh, 1 is mA/mAh; ACPI_UNKNOWN where the +// firmware does not say. +#define ACPI_UNKNOWN 0xFFFFFFFFu +struct acpi_psu { + int battery; // else an AC adapter + int present; // battery: in the bay + int online; // adapter: plugged in + int unit; + uint32 state; // _BST: 1 discharging, 2 charging, 4 critical + uint32 rate, remaining, voltage; // _BST, voltage in mV + uint32 design, full, design_voltage, cycles; + int technology; // 0 primary, 1 rechargeable + char model[32], serial[32], kind[16], oem[32]; +}; +int acpi_psu_count(void); +int acpi_psu_get(int i, struct acpi_psu *out); + +// How an idle CPU sleeps: kernel/acpi/aml.c reads it from _CST, and the +// scheduler's idle path in kernel/proc/proc.c acts on it. +#define ACPI_IDLE_HLT 0 +#define ACPI_IDLE_IO 1 // read a port +#define ACPI_IDLE_MWAIT 2 // MWAIT with a hint +struct acpi_idle { + int kind; + uint64 addr; // the port, or the MWAIT hint + uint latency; // microseconds +}; +extern struct acpi_idle acpi_idle; diff --git a/include/dev/audio.h b/include/dev/audio.h index 9bc062f..caa0a49 100644 --- a/include/dev/audio.h +++ b/include/dev/audio.h @@ -79,6 +79,7 @@ int audio_configure(struct audio_format *f); // 0, or negative int audio_write(const void *pcm, int bytes); // bytes queued, or negative void audio_drain(void); void audio_stop(void); +int dsp_writable(void); // poll(): room for a write // --- what userspace uses --------------------------------------------------- // @@ -92,6 +93,23 @@ void audio_stop(void); #define SNDCTL_DSP_SYNC 0x00005001 // wait for the queue to drain #define SNDCTL_DSP_GETOSPACE 0x8010500C // struct audio_buf_info out #define SNDCTL_DSP_RESET 0x00005000 // stop, discard +// The rest of what a buffer-and-pointer client needs -- ALSA's OSS plugin, which +// is how SDL and everything else that speaks ALSA reaches this device. +#define SNDCTL_DSP_SETFRAGMENT 0xC004500A // int in/out: count<<16 | log2(size) +#define SNDCTL_DSP_GETFMTS 0x8004500B // int out: AFMT_* mask +#define SNDCTL_DSP_GETCAPS 0x8004500F // int out: DSP_CAP_* +#define SNDCTL_DSP_SETTRIGGER 0x40045010 // int in: PCM_ENABLE_OUTPUT or 0 +#define SNDCTL_DSP_GETOPTR 0x800C5012 // struct count_info out +#define SNDCTL_DSP_GETODELAY 0x80045017 // int out: bytes not yet heard + +#define DSP_CAP_TRIGGER 0x00001000 +#define PCM_ENABLE_OUTPUT 0x00000002 + +struct count_info { + int bytes; // played since the last reset + int blocks; // fragments played since the last GETOPTR + int ptr; // where playback is in a ring of fragsize * fragments +}; #define AFMT_S16_LE 0x00000010 diff --git a/include/dev/hid.h b/include/dev/hid.h index a390ddb..e6c21f4 100644 --- a/include/dev/hid.h +++ b/include/dev/hid.h @@ -12,6 +12,13 @@ void *hid_attach(const uchar *desc, int n, const char *what); // One input report, report ID included when the device uses them. void hid_attached_input(void *h, const uchar *rep, int len); +// For GMMOS, after a panic: whether a device is a keyboard, and one of its +// reports rewritten into the 8-byte boot layout (modifiers, reserved, six +// usages) with nothing else done -- no queue, no lock, no events. 0 when the +// report was not a keyboard's. +int hid_is_keyboard(void *h); +int hid_boot_report(void *h, const uchar *rep, int len, uchar *boot); + // I2C-HID: the touchpads (and some keyboards) of laptops. Started once ACPI // and kernel threads are up; see kernel/drivers/i2c/i2c_hid.c. void i2c_hid_start(void); diff --git a/include/dev/kbd.h b/include/dev/kbd.h index b352262..9d60e02 100644 --- a/include/dev/kbd.h +++ b/include/dev/kbd.h @@ -59,6 +59,9 @@ #define KBD_PGDN (KBD_SPECIAL + 0x07) #define KBD_INS (KBD_SPECIAL + 0x08) #define KBD_DEL (KBD_SPECIAL + 0x09) +// F1..F12, consecutive: KBD_F1 + n is F(n+1). +#define KBD_F1 (KBD_SPECIAL + 0x10) +#define KBD_F12 (KBD_SPECIAL + 0x1B) // Switch virtual terminal. // @@ -89,12 +92,10 @@ // leaves `chvt N` as the only way to switch, which is a legitimate choice on // a machine whose host takes everything. // -// The default is AltGr+F1..F6, and the "AltGr wants it" line above needs -// reading carefully: what a Polish layout wants is AltGr+*letter*. AltGr with -// a function key produces nothing on any layout, no window manager binds it, -// and no application asks for it. Right Alt being a separate modifier bit -// here is what makes the distinction expressible at all -- it used to share -// ALT, which is harmless for typing and useless for this. +// The default is Ctrl+Alt+F1..F12: the keys X switches with, so the console +// and the desktop answer the same chord. It used to be AltGr+F-key, which +// nothing else claims -- Right Alt being a separate modifier bit here is what +// makes that expressible, and `chvt -c altgr+F` still sets it. #define KBD_VT_SWITCH (KBD_SPECIAL + 0x20) // Previous / next terminal, on the same modifiers as the chord above. The diff --git a/include/dev/kdev.h b/include/dev/kdev.h new file mode 100644 index 0000000..99e052f --- /dev/null +++ b/include/dev/kdev.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// The devices the kernel implements itself, as /sys and udev see them. +// +// /dev holds them already (kernel/fs/devfs.c). This is the other half of what +// udev needs: for each node a place in /sys/devices, the class and dev/ links +// that lead back to it, the facts udev's rules read (MAJOR, MINOR, DEVNAME, +// input capabilities), and a way to announce it on the uevent netlink socket. +#pragma once + +#include "core/types.h" + +#define KDEV_NAME 24 +#define KDEV_DEVNAME 32 +#define KDEV_BLK0 256 // kdev indexes from here are block devices + +enum { KD_NONE, KD_KEYBOARD, KD_MOUSE }; + +struct kdev { + char subsys[16]; // class: input, mem, tty, graphics, block + char name[KDEV_NAME]; // directory name under the parent + char devname[KDEV_DEVNAME]; // path below /dev, "" for a device with no node + short major, minor; + int block; + int parent; // kdev index of the device it hangs off, or -1 + int cap; // KD_*: what an input device can do +}; + +int kdev_nsubsys(void); +const char *kdev_subsys(int s); + +// Index of the Nth device that exists, 0.. until -1. +int kdev_nth(int n); +int kdev_get(int i, struct kdev *k); +// The numbers stat() shows for its node, which are what /sys must show too. +void kdev_numbers(const struct kdev *k, int *maj, int *min); + +// "devices/virtual/input/input0/event0": where it lives below /sys. +int kdev_path(int i, char *buf, int cap); + +// Announce a device on the uevent socket: action is "add", "remove", "change". +void kdev_uevent(int i, const char *action); +// The next SEQNUM: events from every source (kdev, PCI, USB) share one count. +uint kdev_seqnum(void); +// What reading a device's `uevent` file says. +int kdev_uevent_text(int i, char *buf, int cap); diff --git a/include/dev/pci.h b/include/dev/pci.h index 4d5770a..93b2048 100644 --- a/include/dev/pci.h +++ b/include/dev/pci.h @@ -151,7 +151,7 @@ struct pci_driver { void (*attached)(struct pci_dev *, struct device *); // Give the device up, so a userspace driver can take it. // - // docs/plan/hybrid.md is built on the kernel *handing over* a device + // docs/architecture/devices.md is built on the kernel *handing over* a device // rather than a process wrestling it away: two drivers writing one set of // registers is not a subtle failure, it is hardware that appears to have // started lying. So the transfer is the incumbent's decision, and this is diff --git a/include/dev/pciuser.h b/include/dev/pciuser.h index 49f5da8..43f6e7e 100644 --- a/include/dev/pciuser.h +++ b/include/dev/pciuser.h @@ -2,7 +2,7 @@ // // /dev/pci -- device access from userspace. // -// docs/plan/hybrid.md, mechanisms 2 and 4: MMIO and DMA to a process. This is +// docs/architecture/devices.md, mechanisms 2 and 4: MMIO and DMA to a process. This is // the interface a driver that does not live in the kernel talks to, and it is // deliberately about *PCI devices*, not about any particular driver model. // @@ -23,7 +23,7 @@ // // There is no IOMMU. A process holding a DMA buffer can point the device at // any physical address, so this isolates a driver against its own *bugs* and -// not against its own *intent*. docs/plan/hybrid.md says so at length and it +// not against its own *intent*. docs/architecture/devices.md says so at length and it // remains true here: a crashed driver that restarts instead of taking the // machine down is the whole benefit, and it is not a security boundary. diff --git a/include/dev/pty.h b/include/dev/pty.h new file mode 100644 index 0000000..d278a1c --- /dev/null +++ b/include/dev/pty.h @@ -0,0 +1,42 @@ +#pragma once + +// Pseudo-terminals, Unix98 style: open /dev/ptmx and get the master side of a +// new pair; the slave is /dev/pts/N. What sits between the two is the same +// line discipline a console has. See kernel/dev/pty.c. + +#include "core/types.h" + +#define PTMX_MAJOR 13 // /dev/ptmx: opening it allocates a pair +#define PTM_MAJOR 14 // a master, minor = pair number; never in a listing +#define PTS_MAJOR 15 // /dev/pts/N +#define NPTY 32 + +// A controlling terminal that is a pty, in proc->ctty: PTY_CTTY + N. The +// console's are 1..NVT. +#define PTY_CTTY 64 + +struct inode; +struct file; + +int pty_alloc(void); // the new pair's number, or -1 +int pty_used(int n); +int pty_ioctl(struct inode *ip, uint request, addr_t arg); +int pty_poll(struct inode *ip, int events, int readable, int writable); +struct inode *ptmx_open(struct inode *ip); // ip locked; returns the master, locked +void pty_init(void); + +// Lines the devsw table needs. +int ptm_read(struct inode*, uint64, char*, int); +int ptm_write(struct inode*, uint64, char*, int); +int ptm_ready(struct inode*); +void ptm_open(struct inode*); +void ptm_close(struct inode*); +int pts_read(struct inode*, uint64, char*, int); +int pts_write(struct inode*, uint64, char*, int); +int pts_ready(struct inode*); +void pts_open(struct inode*); +void pts_close(struct inode*); + +// From kernel/fs/devfs.c: the inode of master N. +struct inode *devfs_pty_master(int n); +void devfs_pty_claim(int n, uint uid); diff --git a/include/dev/virtio.h b/include/dev/virtio.h index 3d1f61a..46fdbd1 100644 --- a/include/dev/virtio.h +++ b/include/dev/virtio.h @@ -4,7 +4,7 @@ // // It lived next to the driver while exactly one driver read it, with a note // saying it should move when a second reader appeared. One has: cmd/vgpud is -// the same device driven from userspace (docs/plan/hybrid.md's first +// the same device driven from userspace (docs/architecture/devices.md's first // eviction), and it needs the same register offsets and the same commands. // // Still one file rather than transport-and-protocol split in two. Both readers diff --git a/include/dev/vt.h b/include/dev/vt.h index b21700d..6e7d712 100644 --- a/include/dev/vt.h +++ b/include/dev/vt.h @@ -26,10 +26,10 @@ #include "core/param.h" #include "uapi/posix.h" -// Six, and Linux's numbering: /dev/tty1 through /dev/tty6, reached with -// Alt+1..Alt+6. A seventh is deliberately not bound -- that is where a -// display server goes, and Schcom will want it. -#define NVT 6 +// Twelve, one per function key, and Linux's numbering: /dev/tty1 through +// /dev/tty12, reached with Ctrl+Alt+F1..F12 (and plain Alt on the text +// console). The key codes leave room for sixteen; see KBD_VT_SWITCH. +#define NVT 12 // A cell. // @@ -82,7 +82,7 @@ struct vtcell { // Escape parser states. E_OSC and E_OSC_ESC exist for hyperlinks: a terminal // that does not follow OSC 8 must still consume it, or the URL is printed as // text. See the comment in kernel/core/console.c that this came from. -enum { VE_NORMAL, VE_ESC, VE_CSI, VE_OSC, VE_OSC_ESC }; +enum { VE_NORMAL, VE_ESC, VE_CSI, VE_OSC, VE_OSC_ESC, VE_CHARSET }; struct vt { int index; // 0-based; /dev/tty @@ -98,6 +98,10 @@ struct vt { // CR, LF, backspace, an escape sequence -- clears it. int wrap_pending; int saved_cx, saved_cy; // DECSC/DECRC + // The scrolling region, DECSTBM: rows scroll_top..scroll_bot inclusive. + // scroll_bot 0 means unset, the whole screen -- which keeps a grid resized + // underneath it from holding a stale bottom. + int scroll_top, scroll_bot; uint8 fg, bg; // current SGR colours int bold; @@ -108,6 +112,16 @@ struct vt { int esc_parm[VT_MAXPARM]; int esc_nparm; int esc_private; + int esc_qmark; // the marker was '?': a DEC private mode + + // The text cursor, a steady block. Drawn by painting its cell inverted over + // the grid, so the grid never holds it: curs_drawn says whether the screen + // currently does, and where, because erasing it means repainting that cell + // from the grid -- after the byte that moves the cursor has been consumed. + // DECTCEM (ESC [ ? 25 l) hides it; curses programs do before drawing. + int curs_hidden; + int curs_drawn; + int curs_x, curs_y; // UTF-8 decoder. uint32 u8_cp; diff --git a/include/fs/file.h b/include/fs/file.h index 7759a7b..95ccf0e 100644 --- a/include/fs/file.h +++ b/include/fs/file.h @@ -130,6 +130,13 @@ struct inode { uint64 size; uint rwmode; + // POSIX permission bits from a filesystem that keeps them, or 0 for one + // that does not, in which case stat makes some up from the type. Bit 18 set + // alone means a mode of 0000, which chmod can ask for. + uint mode; + // Owner. Whatever the filesystem keeps: SchFS has stored them all along, + // FAT and the synthetic filesystems report root. + uint uid, gid; uint supermode; uint showmode; uint atime; diff --git a/include/gfx/nvfont.h b/include/gfx/nvfont.h new file mode 100644 index 0000000..739bcaa --- /dev/null +++ b/include/gfx/nvfont.h @@ -0,0 +1,7867 @@ +#pragma once + +// GENERATED by tools/python/mknvfont.py -- do not edit. Run `make nvfont`. +// source: docs/Website/niveus/fonts/static (Instrument Sans, SIL OFL 1.1) +// +// 8-bit coverage bitmaps for printable ASCII, proportionally spaced, used by +// the boot manager (boot/common/ui.c). Not included by the kernel. + +struct nvglyph { unsigned short off; unsigned char w, h; signed char ox, oy; unsigned short adv; /* 1/64 px */ }; +struct nvfont { unsigned char ascent, descent; const struct nvglyph *g; const unsigned char *bits; }; +#define NVFONT_FIRST 32 +#define NVFONT_LAST 126 + +static const struct nvglyph nvg_body1[] = { + {0,3,0,0,0,189}, + {0,4,11,0,-11,273}, + {44,6,11,0,-11,400}, + {110,11,11,0,-11,692}, + {231,9,15,0,-13,598}, + {366,12,11,0,-11,755}, + {498,12,11,0,-11,731}, + {630,4,11,0,-11,234}, + {674,6,13,0,-11,406}, + {752,6,13,0,-11,406}, + {830,6,11,0,-11,402}, + {896,8,9,0,-9,516}, + {968,4,5,0,-2,254}, + {988,7,6,0,-6,479}, + {1030,4,2,0,-2,254}, + {1038,7,13,0,-12,426}, + {1129,10,11,0,-11,644}, + {1239,6,11,0,-11,373}, + {1305,8,11,0,-11,529}, + {1393,9,11,0,-11,554}, + {1492,9,11,0,-11,583}, + {1591,9,11,0,-11,555}, + {1690,9,11,0,-11,583}, + {1789,9,11,0,-11,524}, + {1888,9,11,0,-11,566}, + {1987,9,11,0,-11,590}, + {2086,4,8,0,-8,254}, + {2118,4,11,0,-8,254}, + {2162,8,9,0,-9,516}, + {2234,8,8,0,-8,516}, + {2298,8,9,0,-9,516}, + {2370,9,11,0,-11,551}, + {2469,13,11,0,-11,826}, + {2612,12,11,0,-11,702}, + {2744,10,11,0,-11,614}, + {2854,11,11,0,-11,714}, + {2975,11,11,0,-11,723}, + {3096,10,11,0,-11,609}, + {3206,9,11,0,-11,574}, + {3305,11,11,0,-11,733}, + {3426,11,11,0,-11,702}, + {3547,4,11,0,-11,244}, + {3591,7,11,0,-11,423}, + {3668,11,11,0,-11,674}, + {3789,9,11,0,-11,565}, + {3888,14,11,0,-11,866}, + {4042,11,11,0,-11,702}, + {4163,12,11,0,-11,758}, + {4295,10,11,0,-11,635}, + {4405,12,11,0,-11,764}, + {4537,10,11,0,-11,634}, + {4647,9,11,0,-11,598}, + {4746,10,11,0,-11,630}, + {4856,11,11,0,-11,680}, + {4977,11,11,0,-11,702}, + {5098,16,11,0,-11,1044}, + {5274,10,11,0,-11,669}, + {5384,11,11,-1,-11,660}, + {5505,9,11,0,-11,602}, + {5604,6,14,0,-11,406}, + {5688,7,13,0,-12,426}, + {5779,6,14,0,-11,406}, + {5863,8,12,0,-12,516}, + {5959,7,3,0,0,428}, + {5980,5,12,0,-12,340}, + {6040,8,8,0,-8,524}, + {6104,9,11,0,-11,588}, + {6203,9,8,0,-8,521}, + {6275,9,11,0,-11,588}, + {6374,9,8,0,-8,544}, + {6446,6,11,0,-11,348}, + {6512,9,11,0,-8,588}, + {6611,9,11,0,-11,582}, + {6710,4,11,0,-11,240}, + {6754,5,14,-1,-11,240}, + {6824,8,11,0,-11,524}, + {6912,4,11,0,-11,240}, + {6956,14,8,0,-8,894}, + {7068,9,8,0,-8,582}, + {7140,9,8,0,-8,567}, + {7212,9,11,0,-8,588}, + {7311,9,11,0,-8,588}, + {7410,6,9,0,-9,368}, + {7464,7,8,0,-8,464}, + {7520,6,11,0,-11,376}, + {7586,9,8,0,-8,572}, + {7658,8,8,0,-8,509}, + {7722,12,8,0,-8,752}, + {7818,9,8,0,-8,546}, + {7890,8,11,0,-8,509}, + {7978,8,8,0,-8,485}, + {8042,6,14,0,-11,406}, + {8126,4,14,0,-12,228}, + {8182,6,14,0,-11,406}, + {8266,8,8,0,-8,516}, +}; +static const unsigned char nvb_body1[] = { + 0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0, + 0,255,152,0,0,255,152,0,0,0,0,0,0,80,48,0,0,255,152,0,0,217,113,114, + 215,0,0,242,138,138,242,0,0,193,89,89,193,0,0,142,38,38,142,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,160,152,0,96,212,0, + 0,0,0,0,0,192,120,0,128,180,0,0,0,0,0,0,224,88,0,160,148,0,0,0, + 0,255,255,255,255,255,255,255,255,255,0,0,80,104,255,95,80,236,139,80,80,0,0,0, + 60,248,1,2,250,56,0,0,0,16,16,104,224,16,44,255,40,16,0,0,255,255,255,255, + 255,255,255,255,255,0,0,64,64,182,178,64,134,225,64,64,0,0,0,0,184,124,0,120, + 188,0,0,0,0,0,0,216,92,0,152,156,0,0,0,0,0,0,0,255,152,0,0,0, + 0,0,0,0,255,152,0,0,0,0,6,131,225,255,245,156,20,0,0,153,252,138,88,122, + 241,204,4,0,245,167,0,0,0,92,255,71,0,237,206,7,0,0,10,64,27,0,118,255, + 222,128,44,0,0,0,0,0,83,200,255,255,206,65,0,0,0,0,0,31,114,228,251,47, + 67,148,21,0,0,0,38,255,137,62,255,117,0,0,0,17,255,137,0,175,252,151,88,95, + 195,253,55,0,6,121,214,255,251,197,73,0,0,0,0,0,255,152,0,0,0,0,0,0, + 0,128,76,0,0,0,0,51,208,252,208,51,0,0,2,191,193,3,0,211,229,95,230,211, + 0,0,114,239,31,0,0,254,157,0,157,254,0,45,248,87,0,0,0,210,208,31,209,208, + 6,207,160,0,0,0,0,49,208,252,205,47,135,221,12,0,0,0,0,0,0,0,0,60, + 248,168,232,248,180,24,0,0,0,0,12,222,194,255,96,28,228,176,0,0,0,0,154,193, + 146,255,7,0,160,246,0,0,0,77,239,30,143,255,18,0,173,246,0,0,21,233,87,0, + 69,255,165,108,249,174,0,0,174,159,0,0,0,110,231,248,174,21,0,0,0,112,225,254, + 225,119,1,0,0,0,0,0,90,255,163,86,167,255,96,0,0,0,0,0,146,255,10,0, + 11,255,145,0,0,0,0,0,85,255,90,8,141,253,64,0,0,0,0,0,5,211,246,238, + 215,74,0,0,0,0,0,23,203,237,209,249,59,0,6,255,146,0,0,174,232,27,10,195, + 242,56,36,255,114,0,0,248,159,0,0,14,201,241,161,255,42,0,0,231,206,2,0,0, + 18,209,255,180,0,0,0,111,255,188,96,87,137,241,255,238,50,0,0,0,90,200,246,250, + 212,121,34,219,238,50,0,217,113,0,0,242,138,0,0,193,89,0,0,142,38,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,99,238,50,0,0,68,252,76,0,0,8,224,166,0,0,0,101,255,45, + 0,0,0,186,218,0,0,0,0,236,170,0,0,0,0,254,154,0,0,0,0,237,170,0, + 0,0,0,187,219,0,0,0,0,103,255,45,0,0,0,10,230,169,0,0,0,0,73,254, + 79,0,0,0,0,102,241,50,0,142,221,30,0,0,0,2,184,211,10,0,0,0,29,247, + 133,0,0,0,0,152,243,12,0,0,0,67,255,83,0,0,0,19,255,133,0,0,0,2, + 255,150,0,0,0,18,255,132,0,0,0,66,255,82,0,0,0,147,242,12,0,0,24,244, + 130,0,0,1,177,210,8,0,0,138,221,29,0,0,0,28,117,190,26,2,25,244,160,185, + 227,90,0,77,236,253,130,4,25,244,161,185,227,90,0,28,116,190,26,3,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,200,119,0,0,0,0,0,0,255,152,0,0,0, + 13,24,24,255,164,24,24,3,140,255,255,255,255,255,255,36,31,56,56,255,176,56,56,8, + 0,0,0,255,152,0,0,0,0,0,0,255,152,0,0,0,0,0,0,56,33,0,0,0, + 0,0,0,0,0,0,0,0,0,80,48,0,0,255,152,0,0,62,137,0,0,189,48,0, + 0,35,0,0,0,16,16,16,16,16,0,0,255,255,255,255,255,0,0,64,64,64,64,64, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,80, + 48,0,0,255,152,0,0,0,0,0,64,108,0,0,0,0,0,186,158,0,0,0,0,16, + 250,78,0,0,0,0,90,245,9,0,0,0,0,170,175,0,0,0,0,6,241,97,0,0, + 0,0,70,253,21,0,0,0,0,150,194,0,0,0,0,1,229,114,0,0,0,0,50,255, + 38,0,0,0,0,130,214,0,0,0,0,0,210,134,0,0,0,0,33,255,55,0,0,0, + 0,0,0,10,141,232,251,206,80,0,0,0,4,193,239,119,87,166,255,95,0,0,101,255, + 68,0,0,0,170,240,14,0,194,214,0,0,0,0,62,255,90,0,239,167,0,0,0,0, + 15,255,135,0,254,154,0,0,0,0,2,255,149,0,238,170,0,0,0,0,18,255,134,0, + 187,221,0,0,0,0,69,255,84,0,94,255,76,0,0,1,178,236,10,0,2,187,242,121, + 88,173,255,88,0,0,0,9,138,232,251,205,76,0,0,0,0,0,39,237,152,0,21,115, + 241,255,152,0,255,253,132,255,152,0,125,37,0,255,152,0,0,0,0,255,152,0,0,0, + 0,255,152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0, + 0,255,152,0,0,0,0,255,152,0,0,105,217,251,232,143,8,0,116,255,161,85,137,254, + 148,1,235,190,0,0,0,179,236,5,76,40,0,0,0,163,249,0,0,0,0,0,12,234, + 200,0,0,0,0,0,152,255,84,0,0,0,0,118,255,157,0,0,0,0,123,255,176,5, + 0,0,4,149,255,163,5,0,0,0,184,255,202,81,80,80,80,0,255,255,255,255,255,255, + 255,0,48,182,242,251,210,90,0,0,29,241,224,107,88,175,255,69,0,89,200,51,0,0, + 10,255,143,0,0,0,0,16,25,127,255,118,0,0,0,0,255,255,255,229,12,0,0,0, + 0,64,80,210,255,54,0,0,0,0,0,0,48,255,131,0,126,202,6,0,0,4,255,148, + 0,101,255,82,0,0,46,255,112,0,10,218,236,110,94,214,235,20,0,0,29,170,242,248, + 185,42,0,0,0,0,0,0,0,85,255,152,0,0,0,0,0,44,244,255,152,0,0,0, + 0,16,219,218,255,152,0,0,0,2,180,245,45,255,152,0,0,0,127,255,90,0,255,152, + 0,0,75,254,148,0,0,255,152,0,36,240,208,22,16,16,255,160,14,128,255,255,255,255, + 255,255,255,228,32,64,64,64,64,64,255,178,57,0,0,0,0,0,0,255,152,0,0,0, + 0,0,0,0,255,152,0,0,212,255,255,255,255,255,4,0,0,233,189,80,80,80,80,1, + 0,3,253,140,0,0,0,0,0,0,22,255,122,4,12,0,0,0,0,44,255,203,245,255, + 215,69,0,0,66,255,186,79,88,216,248,34,0,11,36,6,0,0,48,255,124,0,10,9, + 0,0,0,6,255,149,0,178,240,17,0,0,49,255,118,0,62,252,208,96,103,222,240,26, + 0,0,69,198,249,245,186,46,0,0,0,0,13,150,234,250,209,84,0,0,5,200,231,112, + 87,172,255,76,0,102,255,51,0,0,2,198,166,0,190,206,0,1,14,1,0,0,0,236, + 166,119,236,255,237,127,2,0,254,241,178,81,76,171,255,120,0,247,203,1,0,0,1,203, + 225,0,210,157,0,0,0,0,158,253,0,137,223,12,0,0,3,211,219,0,22,230,207,99, + 91,187,255,102,0,0,35,175,242,251,209,94,0,0,255,255,255,255,255,255,255,255,0,80, + 80,80,80,80,98,249,191,0,0,0,0,0,0,172,245,33,0,0,0,0,0,80,255,122, + 0,0,0,0,0,5,220,231,9,0,0,0,0,0,95,255,118,0,0,0,0,0,0,202, + 251,21,0,0,0,0,0,36,255,186,0,0,0,0,0,0,108,255,117,0,0,0,0,0, + 0,163,255,64,0,0,0,0,0,0,200,255,28,0,0,0,0,0,74,200,248,249,199,71, + 0,0,59,253,188,92,92,188,253,58,0,144,255,12,0,0,13,255,144,0,124,255,132,27, + 27,129,255,123,0,28,255,255,255,255,255,235,16,0,145,255,162,73,92,219,255,57,0,233, + 201,0,0,0,49,255,134,0,251,157,0,0,0,5,255,148,0,208,211,3,0,0,61,255, + 105,0,82,255,179,89,109,229,222,13,0,0,81,207,251,240,168,28,0,0,1,107,211,251, + 242,177,36,0,0,125,255,170,90,97,184,232,22,0,235,192,0,0,0,1,199,137,0,248, + 169,0,0,0,0,167,209,0,172,250,104,26,27,106,250,245,0,21,186,255,255,255,185,172, + 253,0,0,0,33,62,33,0,170,239,0,133,117,0,0,0,0,209,200,0,152,236,11,0, + 0,44,255,119,0,40,247,197,91,103,223,219,13,0,0,59,198,249,240,167,27,0,0,255, + 152,0,0,80,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,80, + 48,0,0,255,152,0,0,255,152,0,0,80,48,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,80,48,0,0,255,152,0,0,62,137,0,0,189,48,0,0,35, + 0,0,0,0,0,0,0,24,114,92,0,0,8,87,181,252,219,76,0,156,240,238,151,59, + 1,0,0,255,243,111,20,0,0,0,0,46,137,226,252,184,96,15,0,0,0,2,63,154, + 238,128,0,0,0,0,0,0,7,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,0,0,80,80,80,80,80,80,0,0,16,16,16,16,16, + 16,0,0,255,255,255,255,255,255,0,0,64,64,64,64,64,64,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,142,51,0,0,0, + 0,0,0,200,255,216,125,35,0,0,0,0,33,120,208,255,199,66,0,0,0,0,50,184, + 255,128,0,29,120,213,254,189,96,12,0,254,218,124,30,0,0,0,0,56,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,79,197,248,248, + 194,64,0,0,94,255,181,91,97,204,250,49,1,229,189,1,0,0,25,255,139,7,92,43, + 0,0,0,33,255,136,0,0,0,0,0,29,206,249,46,0,0,0,0,38,233,237,63,0, + 0,0,0,0,184,246,41,0,0,0,0,0,0,247,170,0,0,0,0,0,0,0,4,2, + 0,0,0,0,0,0,0,80,48,0,0,0,0,0,0,0,255,152,0,0,0,0,0,0, + 3,89,182,234,253,243,202,116,8,0,0,0,25,198,253,174,117,83,98,167,253,210,19,0, + 8,210,234,102,209,255,216,88,176,94,254,159,0,117,255,87,234,215,81,138,248,226,0,185, + 239,0,206,208,109,255,54,0,2,246,158,0,157,249,0,249,159,147,255,5,0,14,252,88, + 0,204,193,0,248,166,115,255,83,4,151,255,44,102,253,67,0,199,228,19,197,255,234,105, + 230,251,227,79,0,0,89,255,148,2,25,7,0,6,30,2,0,0,0,0,136,255,209,120, + 85,92,143,225,19,0,0,0,0,0,64,171,230,253,245,200,109,7,0,0,0,0,0,0, + 30,254,255,52,0,0,0,0,0,0,0,0,124,240,238,148,0,0,0,0,0,0,0,1, + 219,156,151,237,7,0,0,0,0,0,0,60,255,62,59,255,84,0,0,0,0,0,0,156, + 224,1,1,219,180,0,0,0,0,0,10,242,132,0,0,128,252,24,0,0,0,0,92,255, + 59,20,20,57,255,120,0,0,0,0,188,255,255,255,255,255,255,215,0,0,0,30,254,139, + 68,68,68,68,137,255,56,0,0,124,251,21,0,0,0,0,19,249,152,0,1,219,179,0, + 0,0,0,0,0,174,240,8,0,255,255,255,255,253,213,82,0,0,0,255,186,80,80,90, + 193,253,51,0,0,255,152,0,0,0,24,255,134,0,0,255,152,0,0,0,14,255,145,0, + 0,255,160,16,16,28,153,255,85,0,0,255,255,255,255,255,255,238,13,0,0,255,178,64, + 64,68,139,255,158,0,0,255,152,0,0,0,0,179,244,0,0,255,152,0,0,0,0,176, + 241,0,0,255,186,80,80,83,142,254,154,0,0,255,255,255,255,255,234,144,9,0,0,0, + 0,70,186,243,251,214,117,5,0,0,0,121,255,189,100,87,146,250,184,4,0,60,255,148, + 1,0,0,0,78,255,109,0,174,238,10,0,0,0,0,0,208,198,0,235,176,0,0,0, + 0,0,0,0,0,0,254,154,0,0,0,0,0,0,0,0,0,234,177,0,0,0,0,0, + 0,0,0,0,171,240,12,0,0,0,0,0,205,204,0,56,254,156,1,0,0,0,74,255, + 111,0,0,114,255,193,100,87,146,249,187,5,0,0,0,66,186,242,251,214,118,5,0,0, + 255,255,255,255,245,207,125,13,0,0,0,255,186,80,80,93,153,246,222,34,0,0,255,152, + 0,0,0,0,43,235,207,2,0,255,152,0,0,0,0,0,104,255,72,0,255,152,0,0, + 0,0,0,24,255,132,0,255,152,0,0,0,0,0,3,255,149,0,255,152,0,0,0,0, + 0,29,255,129,0,255,152,0,0,0,0,0,115,255,64,0,255,152,0,0,0,0,52,241, + 197,1,0,255,186,80,80,94,154,249,216,27,0,0,255,255,255,255,245,206,120,10,0,0, + 0,255,255,255,255,255,255,255,128,0,0,255,186,80,80,80,80,80,40,0,0,255,152,0, + 0,0,0,0,0,0,0,255,152,0,0,0,0,0,0,0,0,255,160,16,16,16,16,16, + 0,0,0,255,255,255,255,255,255,255,0,0,0,255,178,64,64,64,64,64,0,0,0,255, + 152,0,0,0,0,0,0,0,0,255,152,0,0,0,0,0,0,0,0,255,186,80,80,80, + 80,80,40,0,0,255,255,255,255,255,255,255,128,0,0,255,255,255,255,255,255,255,0,0, + 255,186,80,80,80,80,80,0,0,255,152,0,0,0,0,0,0,0,255,152,0,0,0,0, + 0,0,0,255,160,16,16,16,16,8,0,0,255,255,255,255,255,255,128,0,0,255,178,64, + 64,64,64,32,0,0,255,152,0,0,0,0,0,0,0,255,152,0,0,0,0,0,0,0, + 255,152,0,0,0,0,0,0,0,255,152,0,0,0,0,0,0,0,0,0,62,181,238,252, + 222,144,21,0,0,0,111,255,197,104,84,126,233,227,30,0,56,254,158,2,0,0,0,29, + 240,171,0,171,240,12,0,0,0,0,0,80,100,0,233,176,0,0,0,6,16,16,16,16, + 0,253,154,0,0,0,100,255,255,255,255,0,237,176,0,0,0,25,64,64,178,255,0,181, + 238,10,0,0,0,0,0,152,255,0,66,255,155,2,0,0,0,0,171,255,0,0,123,255, + 203,112,84,101,184,255,175,0,0,0,64,177,235,254,239,188,89,1,0,255,152,0,0,0, + 0,0,255,152,0,0,255,152,0,0,0,0,0,255,152,0,0,255,152,0,0,0,0,0, + 255,152,0,0,255,152,0,0,0,0,0,255,152,0,0,255,162,20,20,20,20,20,255,152, + 0,0,255,255,255,255,255,255,255,255,152,0,0,255,180,68,68,68,68,68,255,152,0,0, + 255,152,0,0,0,0,0,255,152,0,0,255,152,0,0,0,0,0,255,152,0,0,255,152, + 0,0,0,0,0,255,152,0,0,255,152,0,0,0,0,0,255,152,0,0,255,152,0,0, + 255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0, + 255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,0,0,0,255,152,0,0,0, + 0,0,255,152,0,0,0,0,0,255,152,0,0,0,0,0,255,152,0,0,0,0,0,255, + 152,0,0,0,0,0,255,152,0,0,0,0,0,255,152,0,0,0,0,0,255,152,0,0, + 0,0,22,255,139,0,95,89,91,192,255,66,0,157,247,250,211,95,0,0,0,255,152,0, + 0,0,25,212,245,73,0,0,255,152,0,0,33,221,236,54,0,0,0,255,152,0,43,229, + 224,38,0,0,0,0,255,152,53,237,209,25,0,0,0,0,0,255,205,242,191,15,0,0, + 0,0,0,0,255,216,252,182,7,0,0,0,0,0,0,255,152,94,254,177,5,0,0,0, + 0,0,255,152,0,108,255,171,4,0,0,0,0,255,152,0,0,126,255,166,3,0,0,0, + 255,152,0,0,0,140,255,160,2,0,0,255,152,0,0,0,1,155,255,154,1,0,255,152, + 0,0,0,0,0,0,0,255,152,0,0,0,0,0,0,0,255,152,0,0,0,0,0,0, + 0,255,152,0,0,0,0,0,0,0,255,152,0,0,0,0,0,0,0,255,152,0,0,0, + 0,0,0,0,255,152,0,0,0,0,0,0,0,255,152,0,0,0,0,0,0,0,255,152, + 0,0,0,0,0,0,0,255,190,88,88,88,88,88,0,0,255,255,255,255,255,255,255,0, + 0,255,255,196,0,0,0,0,0,29,255,255,152,0,0,255,252,255,29,0,0,0,0,115, + 252,255,152,0,0,255,185,255,116,0,0,0,0,203,183,255,152,0,0,255,152,197,204,0, + 0,0,35,255,93,255,152,0,0,255,152,106,255,37,0,0,122,246,12,255,152,0,0,255, + 152,20,251,126,0,0,210,168,0,255,152,0,0,255,152,0,181,214,0,40,255,77,0,255, + 152,0,0,255,152,0,90,255,46,128,237,5,0,255,152,0,0,255,152,0,11,244,134,216, + 151,0,0,255,152,0,0,255,152,0,0,165,241,255,61,0,0,255,152,0,0,255,152,0, + 0,73,255,224,1,0,0,255,152,0,0,255,253,48,0,0,0,0,255,152,0,0,255,255, + 194,1,0,0,0,255,152,0,0,255,211,255,87,0,0,0,255,152,0,0,255,152,168,226, + 10,0,0,255,152,0,0,255,152,30,246,130,0,0,255,152,0,0,255,152,0,126,246,30, + 0,255,152,0,0,255,152,0,9,224,169,0,255,152,0,0,255,152,0,0,84,255,62,255, + 152,0,0,255,152,0,0,0,192,208,255,152,0,0,255,152,0,0,0,47,253,255,152,0, + 0,255,152,0,0,0,0,151,255,152,0,0,0,0,65,182,238,252,221,142,21,0,0,0, + 0,117,255,194,104,85,132,238,229,39,0,0,60,255,155,1,0,0,0,31,230,209,2,0, + 177,239,12,0,0,0,0,0,100,255,73,0,236,176,0,0,0,0,0,0,24,255,132,0, + 254,155,0,0,0,0,0,0,3,255,150,0,233,180,0,0,0,0,0,0,29,255,129,0, + 168,244,16,0,0,0,0,0,110,255,64,0,51,253,165,3,0,0,0,39,238,200,1,0, + 0,106,254,199,105,85,135,242,226,33,0,0,0,0,61,180,239,253,222,140,19,0,0,0, + 255,255,255,255,254,227,135,6,0,0,255,185,80,80,84,145,255,146,0,0,255,152,0,0, + 0,0,181,239,0,0,255,152,0,0,0,0,165,248,0,0,255,160,16,16,20,85,246,176, + 0,0,255,255,255,255,255,255,193,25,0,0,255,178,64,64,63,34,0,0,0,0,255,152, + 0,0,0,0,0,0,0,0,255,152,0,0,0,0,0,0,0,0,255,152,0,0,0,0, + 0,0,0,0,255,152,0,0,0,0,0,0,0,0,0,0,65,182,238,252,221,142,21,0, + 0,0,0,117,255,194,104,85,132,238,229,39,0,0,60,255,155,1,0,0,0,31,230,209, + 2,0,177,237,10,0,0,0,0,0,96,255,73,0,236,173,0,0,0,0,0,0,22,255, + 132,0,253,155,0,0,0,0,0,0,4,255,150,0,233,180,0,0,0,0,0,0,30,255, + 128,0,172,244,17,0,0,0,0,0,110,255,58,0,56,254,166,3,0,0,0,37,237,181, + 0,0,0,110,254,201,105,97,140,240,230,93,80,0,0,0,59,176,236,255,255,255,255,255, + 255,0,255,255,255,255,253,219,119,3,0,0,255,185,80,80,87,154,255,135,0,0,255,152, + 0,0,0,0,184,238,0,0,255,152,0,0,0,0,167,246,0,0,255,160,16,16,23,90, + 248,170,0,0,255,255,255,255,255,255,181,18,0,0,255,186,227,247,91,27,0,0,0,0, + 255,152,40,234,222,32,0,0,0,0,255,152,0,48,240,224,32,0,0,0,255,152,0,0, + 57,245,226,34,0,0,255,152,0,0,0,69,249,226,36,0,4,122,217,253,242,186,59,0, + 0,147,252,141,84,101,209,249,56,0,244,169,0,0,0,19,244,174,0,236,208,11,0,0, + 0,51,53,0,110,255,231,142,62,3,0,0,0,0,68,186,252,255,232,119,3,0,0,0, + 0,16,90,189,255,141,67,148,21,0,0,0,1,193,241,62,255,124,0,0,0,0,172,244, + 0,171,255,163,94,87,145,253,152,0,4,109,205,246,250,217,126,6,0,255,255,255,255,255, + 255,255,255,255,0,80,80,80,185,255,80,80,80,80,0,0,0,0,152,255,0,0,0,0, + 0,0,0,0,152,255,0,0,0,0,0,0,0,0,152,255,0,0,0,0,0,0,0,0, + 152,255,0,0,0,0,0,0,0,0,152,255,0,0,0,0,0,0,0,0,152,255,0,0, + 0,0,0,0,0,0,152,255,0,0,0,0,0,0,0,0,152,255,0,0,0,0,0,0, + 0,0,152,255,0,0,0,0,0,255,152,0,0,0,0,0,255,152,0,0,255,152,0,0, + 0,0,0,255,152,0,0,255,152,0,0,0,0,0,255,152,0,0,255,152,0,0,0,0, + 0,255,152,0,0,255,152,0,0,0,0,0,255,152,0,0,255,152,0,0,0,0,0,255, + 152,0,0,254,153,0,0,0,0,1,255,150,0,0,230,177,0,0,0,0,26,255,125,0, + 0,163,245,26,0,0,0,119,255,58,0,0,38,241,221,109,87,145,253,173,0,0,0,0, + 44,178,241,253,222,129,7,0,0,84,255,86,0,0,0,0,0,74,255,76,7,239,177,0, + 0,0,0,0,165,233,4,0,152,250,19,0,0,0,11,244,144,0,0,56,255,105,0,0, + 0,91,255,48,0,0,1,219,195,0,0,0,182,208,0,0,0,0,124,255,32,0,22,252, + 114,0,0,0,0,30,254,123,0,108,252,24,0,0,0,0,0,192,214,0,199,180,0,0, + 0,0,0,0,96,255,84,255,84,0,0,0,0,0,0,12,245,238,240,7,0,0,0,0, + 0,0,0,164,255,151,0,0,0,0,48,255,111,0,0,0,0,220,255,69,0,0,0,12, + 250,128,1,230,181,0,0,0,32,255,251,135,0,0,0,75,255,55,0,158,244,6,0,0, + 96,241,163,203,0,0,0,145,236,2,0,84,255,63,0,0,164,177,91,253,15,0,0,214, + 167,0,0,16,252,133,0,0,228,109,23,255,79,0,28,255,93,0,0,0,194,203,0,39, + 255,40,0,209,144,0,97,254,22,0,0,0,121,253,18,105,226,0,0,141,212,0,165,204, + 0,0,0,0,48,255,86,172,157,0,0,71,255,24,234,130,0,0,0,0,1,230,157,235, + 88,0,0,10,249,134,255,58,0,0,0,0,0,158,247,254,21,0,0,0,191,247,237,3, + 0,0,0,0,0,84,255,207,0,0,0,0,122,255,168,0,0,0,14,226,221,11,0,0, + 0,35,244,154,0,68,255,146,0,0,3,195,219,12,0,0,150,254,62,0,117,252,57,0, + 0,0,11,219,219,54,249,130,0,0,0,0,0,61,253,248,203,5,0,0,0,0,0,0, + 206,255,91,0,0,0,0,0,0,96,254,230,221,11,0,0,0,0,32,242,146,56,253,150, + 0,0,0,3,195,217,10,0,144,255,68,0,0,120,253,56,0,0,10,220,226,14,49,250, + 134,0,0,0,0,62,254,157,1,187,252,48,0,0,0,0,104,255,84,0,32,244,203,3, + 0,0,24,240,172,0,0,0,109,255,111,0,0,171,237,23,0,0,0,2,195,243,29,75, + 255,94,0,0,0,0,0,38,248,182,225,181,0,0,0,0,0,0,0,117,255,242,29,0, + 0,0,0,0,0,0,3,255,155,0,0,0,0,0,0,0,0,0,255,152,0,0,0,0, + 0,0,0,0,0,255,152,0,0,0,0,0,0,0,0,0,255,152,0,0,0,0,0,0, + 0,0,0,255,152,0,0,0,0,0,255,255,255,255,255,255,255,255,0,80,80,80,80,80, + 135,255,191,0,0,0,0,0,11,219,241,30,0,0,0,0,0,150,255,93,0,0,0,0, + 0,73,255,173,0,0,0,0,0,19,231,232,19,0,0,0,0,0,171,255,74,0,0,0, + 0,0,93,255,153,0,0,0,0,0,30,241,219,11,0,0,0,0,0,191,255,137,80,80, + 80,80,80,0,255,255,255,255,255,255,255,255,0,255,255,255,255,84,0,255,185,80,80,26, + 0,255,152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0, + 0,255,152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0, + 0,255,152,0,0,0,0,255,159,16,16,5,0,255,255,255,255,84,0,64,64,64,64,21, + 26,128,17,0,0,0,0,7,243,95,0,0,0,0,0,170,174,0,0,0,0,0,90,245, + 9,0,0,0,0,16,252,76,0,0,0,0,0,190,154,0,0,0,0,0,110,232,2,0, + 0,0,0,31,255,58,0,0,0,0,0,208,136,0,0,0,0,0,130,214,0,0,0,0, + 0,50,255,38,0,0,0,0,1,225,118,0,0,0,0,0,147,198,0,0,184,255,255,255, + 152,0,58,80,80,255,152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0,0,255, + 152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0,0,255, + 152,0,0,0,0,255,152,0,0,0,0,255,152,0,12,16,16,255,152,0,184,255,255,255, + 152,0,46,64,64,64,38,0,0,12,230,236,17,0,0,0,0,132,243,238,145,0,0,0, + 33,247,90,77,250,40,0,0,170,157,0,0,143,182,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,16,16,16,16,16,16,16,255,255,255,255,255,255,255,64,64,64, + 64,64,64,64,1,107,128,36,0,0,50,238,194,2,0,0,55,242,105,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,126,225,252,230,128,1, + 0,121,254,137,84,164,255,86,0,79,90,0,0,11,255,145,0,0,54,132,175,216,255,152, + 0,126,251,154,96,56,255,152,0,244,164,0,0,29,255,152,0,225,228,94,99,202,255,155, + 0,64,214,252,217,75,248,177,0,255,152,0,0,0,0,0,0,0,255,152,0,0,0,0, + 0,0,0,255,152,0,0,0,0,0,0,0,255,152,118,231,250,193,42,0,0,255,231,193, + 92,115,238,231,14,0,255,227,9,0,0,82,255,100,0,255,164,0,0,0,10,255,145,0, + 255,162,0,0,0,12,255,144,0,255,227,8,0,0,85,255,96,0,255,238,193,91,115,239, + 223,10,0,255,154,137,238,248,185,35,0,0,0,64,199,249,236,148,11,0,0,61,251,193, + 90,115,241,175,0,0,192,228,9,0,0,88,200,18,0,248,162,0,0,0,0,0,0,0, + 247,162,0,0,0,0,0,0,0,189,230,10,0,0,83,196,18,0,58,250,193,91,114,239, + 172,0,0,0,62,197,249,236,146,10,0,0,0,0,0,0,0,0,255,152,0,0,0,0, + 0,0,0,255,152,0,0,0,0,0,0,0,255,152,0,0,99,221,252,211,63,255,152,0, + 85,255,191,91,115,221,255,152,0,202,227,8,0,0,85,255,152,0,248,164,0,0,0,12, + 255,152,0,249,162,0,0,0,11,255,152,0,203,224,6,0,0,82,255,152,0,89,255,189, + 89,114,223,255,152,0,0,103,225,252,207,60,255,152,0,0,64,194,248,243,179,37,0,0, + 62,251,197,93,102,219,233,19,0,192,233,26,16,16,63,255,112,0,248,255,255,255,255,255, + 255,149,0,247,190,64,64,64,64,64,37,0,189,230,10,0,0,34,120,35,0,56,249,198, + 94,101,219,220,10,0,0,58,192,248,242,171,28,0,0,0,56,208,253,124,0,0,215,235, + 95,46,0,0,255,156,0,0,120,255,255,255,255,92,38,80,255,186,80,29,0,0,255,152, + 0,0,0,0,255,152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0,0,255,152, + 0,0,0,0,255,152,0,0,0,0,97,220,252,213,68,255,152,0,85,255,192,92,115,225, + 255,152,0,200,227,8,0,0,82,255,152,0,248,164,0,0,0,10,255,152,0,249,162,0, + 0,0,13,255,152,0,206,227,9,0,0,85,255,152,0,92,255,193,92,115,219,255,152,0, + 0,105,226,252,205,58,255,149,0,43,47,0,0,0,45,255,116,0,115,252,138,84,103,219, + 236,23,0,1,110,215,253,240,176,39,0,0,255,152,0,0,0,0,0,0,0,255,152,0, + 0,0,0,0,0,0,255,152,0,0,0,0,0,0,0,255,152,110,226,251,200,41,0,0, + 255,234,196,91,113,248,201,0,0,255,224,8,0,0,166,253,0,0,255,165,0,0,0,152, + 255,0,0,255,152,0,0,0,152,255,0,0,255,152,0,0,0,152,255,0,0,255,152,0, + 0,0,152,255,0,0,255,152,0,0,0,152,255,0,0,255,152,0,0,80,48,0,0,0, + 0,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255, + 152,0,0,255,152,0,0,255,152,0,0,0,255,152,0,0,0,80,48,0,0,0,0,0, + 0,0,0,255,152,0,0,0,255,152,0,0,0,255,152,0,0,0,255,152,0,0,0,255, + 152,0,0,0,255,152,0,0,0,255,152,0,0,0,255,152,0,0,1,255,152,0,46,106, + 255,128,0,144,252,192,22,0,0,255,152,0,0,0,0,0,0,255,152,0,0,0,0,0, + 0,255,152,0,0,0,0,0,0,255,152,0,1,152,253,91,0,255,152,0,141,254,98,0, + 0,255,152,129,254,98,0,0,0,255,232,255,113,0,0,0,0,255,202,246,184,6,0,0, + 0,255,152,74,251,169,3,0,0,255,152,0,95,255,153,1,0,255,152,0,0,117,255,137, + 0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0, + 0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,111, + 231,247,161,10,135,235,246,157,5,0,0,255,230,191,89,157,255,217,188,88,157,255,104,0, + 0,255,227,8,0,11,255,222,6,0,11,255,149,0,0,255,164,0,0,0,255,167,0,0, + 0,255,152,0,0,255,152,0,0,0,255,152,0,0,0,255,152,0,0,255,152,0,0,0, + 255,152,0,0,0,255,152,0,0,255,152,0,0,0,255,152,0,0,0,255,152,0,0,255, + 152,0,0,0,255,152,0,0,0,255,152,0,0,255,152,103,224,252,201,41,0,0,255,228, + 201,93,113,248,202,0,0,255,230,12,0,0,166,253,0,0,255,165,0,0,0,152,255,0, + 0,255,152,0,0,0,152,255,0,0,255,152,0,0,0,152,255,0,0,255,152,0,0,0, + 152,255,0,0,255,152,0,0,0,152,255,0,0,0,58,190,247,247,191,58,0,0,58,249, + 208,98,98,207,250,59,0,191,232,14,0,0,13,232,192,0,248,164,0,0,0,0,164,248, + 0,246,166,0,0,0,0,167,245,0,186,235,17,0,0,17,236,186,0,54,248,209,97,98, + 208,248,54,0,0,55,189,247,247,188,55,0,0,255,157,140,236,249,190,41,0,0,255,248, + 193,92,115,238,228,13,0,255,227,9,0,0,82,255,100,0,255,164,0,0,0,10,255,145, + 0,255,162,0,0,0,12,255,144,0,255,227,8,0,0,85,255,95,0,255,245,193,91,115, + 239,222,10,0,255,156,145,239,248,182,33,0,0,255,152,0,0,0,0,0,0,0,255,152, + 0,0,0,0,0,0,0,255,152,0,0,0,0,0,0,0,0,96,220,252,214,71,255,152, + 0,83,255,190,90,115,227,255,152,0,200,227,7,0,0,82,255,152,0,248,164,0,0,0, + 12,255,152,0,249,162,0,0,0,10,255,152,0,202,224,6,0,0,82,255,152,0,88,255, + 189,89,112,226,255,152,0,0,101,222,252,211,70,255,152,0,0,0,0,0,0,0,255,152, + 0,0,0,0,0,0,0,255,152,0,0,0,0,0,0,0,255,152,0,0,0,0,0,4, + 0,255,154,141,240,128,0,255,240,183,91,41,0,255,219,3,0,0,0,255,167,0,0,0, + 0,255,152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0,0,255,152,0,0,0, + 0,52,200,249,240,162,15,0,220,225,92,115,249,158,0,244,182,2,0,76,84,0,120,254, + 220,151,68,0,0,0,40,116,185,255,136,47,160,49,0,0,172,245,17,239,214,95,98,231, + 220,0,51,193,248,248,194,51,0,0,12,45,0,0,0,0,244,152,0,0,0,0,255,152, + 0,0,136,255,255,255,255,196,43,80,255,186,80,61,0,0,255,152,0,0,0,0,255,152, + 0,0,0,0,255,152,0,0,0,0,253,156,0,0,0,0,214,231,95,73,0,0,61,213, + 252,181,0,255,152,0,0,0,152,255,0,0,255,152,0,0,0,152,255,0,0,255,152,0, + 0,0,152,255,0,0,255,152,0,0,0,152,255,0,0,255,152,0,0,0,165,255,0,0, + 253,165,0,0,11,229,255,0,0,194,247,113,93,200,230,255,0,0,32,193,251,221,101,152, + 255,0,163,245,12,0,0,12,245,147,63,255,90,0,0,90,255,48,1,218,180,0,0,178, + 204,0,0,120,251,19,18,250,105,0,0,24,252,104,102,246,16,0,0,0,177,194,190,164, + 0,0,0,0,77,254,254,64,0,0,0,0,4,230,220,1,0,0,135,254,18,0,2,232, + 186,0,0,60,255,71,60,255,80,0,57,255,250,13,0,124,244,7,3,237,144,0,134,196, + 243,85,0,185,176,0,0,166,208,0,210,110,170,162,5,244,102,0,0,90,254,47,255,31, + 92,236,58,255,27,0,0,19,253,186,206,0,18,252,180,208,0,0,0,0,197,255,126,0, + 0,193,255,132,0,0,0,0,121,255,46,0,0,114,255,57,0,0,8,212,216,9,0,32, + 243,159,0,0,45,248,144,2,193,218,12,0,0,0,108,254,163,250,51,0,0,0,0,1, + 186,255,121,0,0,0,0,0,19,228,255,156,0,0,0,0,1,182,222,99,255,83,0,0, + 0,116,253,59,0,171,237,27,0,54,251,129,0,0,22,235,189,2,159,249,21,0,0,16, + 248,143,52,255,115,0,0,104,255,40,0,201,216,1,0,200,190,0,0,95,255,61,43,255, + 86,0,0,8,236,162,140,232,5,0,0,0,138,246,234,132,0,0,0,0,34,254,253,30, + 0,0,0,0,1,226,178,0,0,0,0,0,67,255,74,0,0,0,0,0,177,223,2,0, + 0,0,0,33,253,121,0,0,0,0,0,255,255,255,255,255,255,0,0,80,80,80,122,255, + 190,0,0,0,0,8,210,234,26,0,0,0,0,148,255,76,0,0,0,0,77,255,146,0, + 0,0,0,26,237,206,7,0,0,0,0,190,255,118,80,80,80,0,0,255,255,255,255,255, + 255,0,0,0,42,205,253,255,0,0,171,237,92,80,0,0,176,199,0,0,0,0,127,246, + 5,0,0,0,75,255,48,0,0,17,114,255,42,0,0,255,255,144,0,0,0,65,153,249, + 30,0,0,0,66,255,55,0,0,0,115,253,11,0,0,0,167,209,0,0,0,0,181,220, + 28,16,0,0,78,250,255,255,0,0,0,20,61,64,0,255,152,0,0,255,152,0,0,255, + 152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255, + 152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255,152,0,0,255, + 253,205,42,0,0,80,92,236,170,0,0,0,0,199,173,0,0,0,5,246,124,0,0,0, + 49,255,74,0,0,0,44,255,113,17,0,0,0,145,255,255,0,0,30,249,151,65,0,0, + 56,255,66,0,0,0,11,253,113,0,0,0,0,209,163,0,0,16,29,220,181,0,0,255, + 255,250,78,0,0,64,61,20,0,0,0,2,9,0,0,0,2,14,43,226,255,211,111,29, + 109,201,171,165,76,153,244,255,253,85,51,15,0,0,13,57,31,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0, +}; +static const struct nvfont nvfont_body1 = { 15, 4, nvg_body1, nvb_body1 }; + +static const struct nvglyph nvg_small1[] = { + {0,2,0,0,0,154}, + {0,3,8,0,-8,210}, + {24,5,8,0,-8,295}, + {64,9,8,0,-8,550}, + {136,7,12,0,-10,467}, + {220,9,8,0,-8,604}, + {292,9,8,0,-8,580}, + {364,3,8,0,-8,178}, + {388,5,10,0,-9,312}, + {438,5,10,0,-9,312}, + {488,5,8,0,-8,313}, + {528,7,8,0,-8,408}, + {584,3,3,0,-1,196}, + {593,6,4,0,-4,389}, + {617,3,1,0,-1,196}, + {620,5,10,0,-9,340}, + {670,8,8,0,-8,511}, + {734,5,8,0,-8,300}, + {774,7,8,0,-8,419}, + {830,7,8,0,-8,441}, + {886,8,8,0,-8,461}, + {950,7,8,0,-8,441}, + {1006,7,8,0,-8,460}, + {1062,6,8,0,-8,409}, + {1110,7,8,0,-8,447}, + {1166,7,8,0,-8,468}, + {1222,3,5,0,-5,196}, + {1237,3,7,0,-5,196}, + {1258,6,7,0,-7,408}, + {1300,6,6,0,-6,408}, + {1336,6,7,0,-7,408}, + {1378,7,8,0,-8,435}, + {1434,10,8,0,-8,655}, + {1514,9,8,0,-8,559}, + {1586,8,8,0,-8,488}, + {1650,9,8,0,-8,569}, + {1722,9,8,0,-8,578}, + {1794,8,8,0,-8,490}, + {1858,7,8,0,-8,462}, + {1914,9,8,0,-8,588}, + {1986,9,8,0,-8,565}, + {2058,3,8,0,-8,195}, + {2082,6,8,-1,-8,349}, + {2130,8,8,0,-8,531}, + {2194,7,8,0,-8,452}, + {2250,11,8,0,-8,696}, + {2338,9,8,0,-8,565}, + {2410,9,8,0,-8,604}, + {2482,8,8,0,-8,504}, + {2546,10,8,0,-8,604}, + {2626,8,8,0,-8,504}, + {2690,7,8,0,-8,467}, + {2746,8,8,0,-8,498}, + {2810,9,8,0,-8,547}, + {2882,9,8,0,-8,559}, + {2954,13,8,0,-8,836}, + {3058,8,8,0,-8,528}, + {3122,9,8,0,-8,519}, + {3194,8,8,0,-8,478}, + {3258,5,10,0,-9,312}, + {3308,5,10,0,-9,340}, + {3358,5,10,0,-9,312}, + {3408,6,9,0,-9,408}, + {3462,6,1,0,0,327}, + {3468,4,9,0,-9,272}, + {3504,7,6,0,-6,409}, + {3546,7,8,0,-8,465}, + {3602,6,6,0,-6,409}, + {3638,7,8,0,-8,465}, + {3694,7,6,0,-6,433}, + {3736,5,8,-1,-8,272}, + {3776,7,8,0,-6,465}, + {3832,7,8,0,-8,460}, + {3888,3,8,0,-8,184}, + {3912,4,10,-1,-8,184}, + {3952,7,8,0,-8,411}, + {4008,3,8,0,-8,184}, + {4032,11,6,0,-6,708}, + {4098,7,6,0,-6,460}, + {4140,7,6,0,-6,449}, + {4182,7,8,0,-6,465}, + {4238,7,8,0,-6,465}, + {4294,5,7,0,-7,288}, + {4329,6,6,0,-6,363}, + {4365,6,8,-1,-8,290}, + {4413,7,6,0,-6,452}, + {4455,7,6,0,-6,402}, + {4497,9,6,0,-6,589}, + {4551,7,6,0,-6,423}, + {4593,7,8,0,-6,402}, + {4649,6,6,0,-6,381}, + {4685,5,10,0,-9,312}, + {4735,3,11,0,-10,186}, + {4768,5,10,0,-9,312}, + {4818,7,5,0,-5,408}, +}; +static const unsigned char nvb_small1[] = { + 0,252,0,0,252,0,0,252,0,0,252,0,0,252,0,0,252,0,0,0,0,0,197,0, + 36,227,80,187,0,23,222,68,178,0,0,141,18,123,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,212,0,52,161,0, + 0,0,0,38,178,0,90,123,0,0,41,200,220,228,200,232,216,200,19,0,0,114,101,0, + 167,47,0,0,0,0,151,62,0,205,10,0,0,100,200,245,209,201,254,200,156,0,0,0, + 213,0,26,190,0,0,0,0,11,202,0,65,151,0,0,0,0,0,0,34,0,0,0,0, + 0,0,248,0,0,0,0,69,210,218,191,27,0,0,228,41,0,101,164,0,0,235,38,0, + 11,83,0,0,92,230,142,39,0,0,0,0,20,112,220,110,0,47,95,0,0,23,236,0, + 25,226,25,0,33,231,0,0,74,203,204,207,75,0,0,0,0,248,0,0,0,0,0,0, + 38,0,0,0,93,211,211,96,0,0,32,200,19,238,23,21,237,0,13,195,43,0,231,38, + 36,231,2,175,74,0,0,74,199,200,73,137,112,0,0,0,0,0,0,94,155,62,168,169, + 62,0,0,59,186,5,226,25,27,228,0,31,197,19,0,237,22,26,238,12,194,43,0,0, + 95,211,213,95,0,0,76,203,199,203,72,0,0,0,0,238,25,0,35,236,0,0,0,0, + 196,76,0,76,201,0,0,0,0,75,245,191,154,21,0,0,0,106,190,85,214,70,7,241, + 0,0,239,17,0,30,213,138,190,0,0,211,78,0,0,58,255,128,0,0,42,187,206,203, + 190,87,208,96,36,227,0,23,222,0,0,141,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,32,186,23,0,5,200,38,0,0,103,142,0,0,0,196,50,0,0, + 0,244,7,0,0,0,244,8,0,0,0,197,51,0,0,0,107,144,0,0,0,7,202,41, + 0,0,0,34,188,23,23,189,36,0,0,0,38,206,9,0,0,0,138,112,0,0,0,48, + 202,0,0,0,5,247,0,0,0,4,247,0,0,0,47,202,0,0,0,137,109,0,0,37, + 202,7,0,22,185,34,0,0,24,65,168,77,12,24,163,216,149,12,24,65,168,77,12,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,28,0,0,0,0,0,0,252,0,0,0,0,0,0,252,0,0,0,6,200,200, + 255,200,200,10,0,0,0,252,0,0,0,0,0,0,224,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,197,0,0,115,0,0,79,0,10,200,200,200,200,22,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,197,0,0,0,0,43, + 65,0,0,0,146,70,0,0,1,209,6,0,0,54,163,0,0,0,134,82,0,0,0,206, + 10,0,0,42,174,0,0,0,124,92,0,0,0,200,16,0,0,31,185,0,0,0,0,0, + 89,201,202,203,89,0,0,77,191,13,0,13,191,77,0,196,54,0,0,0,53,196,0,245, + 7,0,0,0,4,244,0,243,9,0,0,0,6,243,0,192,62,0,0,0,59,192,0,71, + 198,16,0,15,196,74,0,0,86,202,201,204,87,0,0,0,59,242,0,43,180,176,254,0, + 30,45,0,252,0,0,0,0,252,0,0,0,0,252,0,0,0,0,252,0,0,0,0,252, + 0,0,0,0,252,0,0,110,209,202,202,58,0,72,208,11,0,56,221,0,53,51,0,0, + 9,245,0,0,0,0,0,104,175,0,0,0,0,67,223,30,0,0,0,88,218,41,0,0, + 3,135,195,23,0,0,0,115,255,205,200,200,200,28,1,123,210,200,198,65,0,87,200,10, + 0,36,230,0,10,11,0,0,54,235,0,0,0,110,212,255,114,0,0,0,0,0,94,205, + 0,89,74,0,0,3,247,0,77,210,14,0,66,199,0,0,116,214,202,188,36,0,0,0, + 0,0,97,255,0,0,0,0,0,69,218,254,0,0,0,0,46,225,43,252,0,0,0,28, + 223,68,0,252,0,0,13,209,101,0,0,252,0,0,107,232,208,208,208,255,208,22,0,0, + 0,0,0,252,0,0,0,0,0,0,0,252,0,0,4,250,200,200,200,113,0,29,219,0, + 0,0,0,0,55,223,183,206,156,14,0,59,156,12,2,134,160,0,0,0,0,0,13,242, + 0,93,44,0,0,7,246,0,114,183,6,0,102,178,0,3,138,214,204,181,24,0,0,1, + 123,208,205,177,18,0,99,173,4,0,110,158,0,202,50,0,0,9,49,0,244,85,184,207, + 168,25,0,246,114,3,1,108,189,0,213,8,0,0,4,246,0,122,110,0,0,82,200,0, + 4,142,203,206,186,35,169,200,200,200,210,245,0,0,0,0,160,105,0,0,0,73,200,1, + 0,0,2,215,65,0,0,0,75,206,0,0,0,0,166,117,0,0,0,0,233,51,0,0, + 0,24,255,10,0,0,0,44,183,201,201,185,44,0,219,51,0,0,51,219,0,231,71,0, + 0,71,231,0,108,255,224,224,255,106,0,201,113,2,1,112,200,0,247,7,0,0,3,247, + 0,189,93,0,0,89,189,0,27,178,205,206,178,27,0,32,182,205,204,147,5,0,199,87, + 0,0,105,122,0,248,6,0,0,4,213,0,182,112,1,2,105,245,0,20,164,207,184,80, + 245,0,87,14,0,0,37,210,0,156,117,0,1,149,117,0,17,174,206,207,142,3,0,196, + 0,0,0,0,0,0,0,0,0,0,0,197,0,0,196,0,0,0,0,0,0,0,0,0, + 0,0,197,0,0,115,0,0,79,0,0,0,0,0,15,100,0,4,73,168,195,110,0,232, + 166,41,0,0,0,88,175,193,114,29,0,0,0,15,96,183,0,0,0,0,0,0,0,0, + 0,0,0,0,56,200,200,200,200,131,0,0,0,0,0,0,56,200,200,200,200,132,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,5,0,0,0,0,136, + 202,154,65,2,0,0,1,53,165,230,0,11,92,181,184,92,0,200,120,27,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,90,205,201,203,57,0,53,210,14,0,50,222,0, + 51,58,0,0,19,239,0,0,0,0,11,188,120,0,0,0,0,158,118,0,0,0,0,0, + 220,1,0,0,0,0,0,3,0,0,0,0,0,0,197,0,0,0,0,0,6,112,199,200, + 204,199,95,0,0,9,194,151,179,204,115,181,186,120,0,135,144,163,115,0,153,185,25,234, + 0,226,30,240,8,0,115,114,10,235,0,248,5,228,38,12,210,51,118,140,0,203,66,72, + 192,152,112,187,134,5,0,66,218,55,0,0,1,59,0,0,0,0,51,175,205,200,204,153, + 9,0,0,0,0,113,248,45,0,0,0,0,0,1,208,103,152,0,0,0,0,0,71,156, + 6,222,15,0,0,0,0,177,52,0,135,109,0,0,0,31,201,0,0,34,213,1,0,0, + 134,229,208,208,208,248,65,0,7,219,11,0,0,0,80,172,0,90,147,0,0,0,0,4, + 224,27,0,252,200,200,201,205,72,0,0,252,0,0,0,33,230,0,0,252,0,0,0,64, + 225,0,0,252,200,200,218,255,99,0,0,252,0,0,0,104,196,0,0,252,0,0,0,3, + 247,0,0,252,0,0,0,69,205,0,0,252,200,200,202,199,46,0,0,0,44,178,204,203, + 189,59,0,0,44,216,53,0,0,36,215,49,0,179,82,0,0,0,0,87,132,0,242,10, + 0,0,0,0,0,0,0,243,10,0,0,0,0,0,0,0,181,82,0,0,0,0,83,141, + 0,45,218,53,0,0,36,216,53,0,0,44,180,205,203,193,63,0,0,252,200,200,204,178, + 49,0,0,0,252,0,0,0,50,217,51,0,0,252,0,0,0,0,75,186,0,0,252,0, + 0,0,0,6,242,0,0,252,0,0,0,0,9,241,0,0,252,0,0,0,0,81,182,0, + 0,252,0,0,0,54,220,49,0,0,252,200,200,206,179,47,0,0,0,252,200,200,200,200, + 141,0,0,252,0,0,0,0,0,0,0,252,0,0,0,0,0,0,0,252,200,200,200,200, + 76,0,0,252,0,0,0,0,0,0,0,252,0,0,0,0,0,0,0,252,0,0,0,0, + 0,0,0,252,200,200,200,200,159,0,0,252,200,200,200,200,103,0,252,0,0,0,0,0, + 0,252,0,0,0,0,0,0,252,0,0,0,0,0,0,252,200,200,200,200,34,0,252,0, + 0,0,0,0,0,252,0,0,0,0,0,0,252,0,0,0,0,0,0,0,39,174,207,201, + 201,133,9,0,43,222,64,0,0,3,116,173,0,177,89,0,0,0,0,2,37,0,240,14, + 0,0,90,200,200,224,0,244,10,0,0,0,0,0,248,0,187,82,0,0,0,0,0,248, + 0,50,221,66,0,0,1,89,237,0,0,44,171,207,201,210,161,33,0,252,0,0,0,0, + 0,248,0,0,252,0,0,0,0,0,248,0,0,252,0,0,0,0,0,248,0,0,252,208, + 208,208,208,208,252,0,0,252,0,0,0,0,0,248,0,0,252,0,0,0,0,0,248,0, + 0,252,0,0,0,0,0,248,0,0,252,0,0,0,0,0,248,0,0,252,0,0,252,0, + 0,252,0,0,252,0,0,252,0,0,252,0,0,252,0,0,252,0,0,0,0,0,252,0, + 0,0,0,0,252,0,0,0,0,0,252,0,0,0,0,0,252,0,0,0,0,0,252,0, + 0,0,0,0,252,0,0,0,0,50,227,0,56,205,203,204,67,0,0,252,0,0,0,64, + 224,66,0,252,0,0,86,217,43,0,0,252,0,111,202,24,0,0,0,252,139,180,12,0, + 0,0,0,252,136,183,16,0,0,0,0,252,0,98,208,36,0,0,0,252,0,0,66,221, + 67,0,0,252,0,0,0,39,214,106,0,252,0,0,0,0,0,0,252,0,0,0,0,0, + 0,252,0,0,0,0,0,0,252,0,0,0,0,0,0,252,0,0,0,0,0,0,252,0, + 0,0,0,0,0,252,0,0,0,0,0,0,252,200,200,200,200,88,0,252,193,0,0,0, + 0,0,177,250,0,0,252,147,61,0,0,0,45,147,248,0,0,252,48,166,0,0,0,147, + 48,248,0,0,252,0,201,22,0,11,193,0,248,0,0,252,0,109,121,0,100,112,0,248, + 0,0,252,0,19,215,2,198,21,0,248,0,0,252,0,0,172,126,172,0,0,248,0,0, + 252,0,0,72,255,76,0,0,248,0,0,252,121,0,0,0,0,248,0,0,252,224,62,0, + 0,0,248,0,0,252,59,224,22,0,0,248,0,0,252,0,118,185,2,0,248,0,0,252, + 0,1,180,124,0,248,0,0,252,0,0,19,223,63,248,0,0,252,0,0,0,58,227,250, + 0,0,252,0,0,0,0,116,252,0,0,0,50,184,203,202,185,52,0,0,50,214,43,0, + 0,43,215,51,0,183,76,0,0,0,0,74,184,0,243,9,0,0,0,0,7,242,0,242, + 13,0,0,0,0,10,242,0,177,84,0,0,0,0,83,181,0,43,219,48,0,0,48,221, + 47,0,0,51,186,204,205,186,53,0,0,252,200,200,204,196,47,0,0,252,0,0,0,67, + 208,0,0,252,0,0,0,3,247,0,0,252,0,0,0,95,194,0,0,252,200,200,204,172, + 30,0,0,252,0,0,0,0,0,0,0,252,0,0,0,0,0,0,0,252,0,0,0,0, + 0,0,0,0,50,184,203,202,185,52,0,0,0,50,213,42,0,0,42,213,51,0,0,183, + 73,0,0,0,0,72,184,0,0,243,9,0,0,0,0,6,242,0,0,242,14,0,0,0, + 0,10,242,0,0,183,85,0,0,0,0,84,177,0,0,51,222,50,0,0,48,207,37,0, + 0,0,50,180,199,212,255,220,218,71,0,252,200,200,202,199,65,0,0,252,0,0,0,39, + 231,0,0,252,0,0,0,66,219,0,0,252,228,208,203,175,43,0,0,252,130,201,16,0, + 0,0,0,252,0,132,206,18,0,0,0,252,0,0,132,210,21,0,0,252,0,0,0,136, + 213,23,0,56,194,200,207,164,15,0,224,47,0,0,129,156,0,232,52,0,0,14,72,0, + 68,220,181,102,19,0,0,0,3,62,142,225,88,46,100,0,0,0,31,234,19,225,50,0, + 0,42,225,0,48,187,203,201,196,59,188,200,200,255,200,200,194,0,0,0,0,252,0,0, + 0,0,0,0,0,252,0,0,0,0,0,0,0,252,0,0,0,0,0,0,0,252,0,0, + 0,0,0,0,0,252,0,0,0,0,0,0,0,252,0,0,0,0,0,0,0,252,0,0, + 0,0,0,252,0,0,0,0,0,248,0,0,252,0,0,0,0,0,248,0,0,252,0,0, + 0,0,0,248,0,0,252,0,0,0,0,0,248,0,0,251,1,0,0,0,0,248,0,0, + 229,27,0,0,0,21,229,0,0,134,158,6,0,5,154,135,0,0,4,129,207,200,205,129, + 4,0,90,176,0,0,0,0,3,221,27,7,230,28,0,0,0,76,172,0,0,134,128,0, + 0,0,180,65,0,0,31,225,4,0,32,212,1,0,0,0,177,82,0,134,109,0,0,0, + 0,71,186,5,220,15,0,0,0,0,1,217,122,152,0,0,0,0,0,0,113,255,45,0, + 0,0,63,194,0,0,0,46,255,78,0,0,0,164,78,3,232,21,0,0,126,184,158,0, + 0,4,227,9,0,154,98,0,0,201,35,225,2,0,66,170,0,0,74,178,0,27,197,0, + 178,58,0,146,89,0,0,7,232,11,102,118,0,98,138,0,216,13,0,0,0,166,82,181, + 38,0,21,211,46,182,0,0,0,0,83,172,205,0,0,0,194,164,99,0,0,0,0,11, + 247,133,0,0,0,113,253,21,0,0,22,222,47,0,0,1,173,102,0,63,215,13,0,115, + 159,0,0,0,127,167,61,204,9,0,0,0,3,192,229,36,0,0,0,0,11,206,230,47, + 0,0,0,0,162,113,67,215,13,0,0,102,172,1,0,128,167,0,51,211,14,0,0,3, + 189,103,12,215,80,0,0,0,57,213,13,0,45,229,30,0,17,215,47,0,0,0,100,197, + 4,173,103,0,0,0,0,0,164,201,167,0,0,0,0,0,0,12,253,13,0,0,0,0, + 0,0,0,252,0,0,0,0,0,0,0,0,252,0,0,0,0,0,0,0,0,252,0,0, + 0,0,0,175,200,200,200,217,245,0,0,0,0,0,2,190,102,0,0,0,0,0,124,171, + 0,0,0,0,0,58,219,16,0,0,0,0,16,219,57,0,0,0,0,0,171,124,0,0, + 0,0,0,102,190,2,0,0,0,0,0,246,217,200,200,200,200,25,0,224,200,200,38,0, + 252,0,0,0,0,252,0,0,0,0,252,0,0,0,0,252,0,0,0,0,252,0,0,0, + 0,252,0,0,0,0,252,0,0,0,0,252,0,0,0,0,224,200,200,38,25,83,0,0, + 0,6,209,1,0,0,0,166,50,0,0,0,82,134,0,0,0,11,205,0,0,0,0,175, + 41,0,0,0,94,122,0,0,0,18,199,0,0,0,0,185,30,0,0,0,106,110,34,200, + 200,228,0,0,0,0,252,0,0,0,0,252,0,0,0,0,252,0,0,0,0,252,0,0, + 0,0,252,0,0,0,0,252,0,0,0,0,252,0,0,0,0,252,0,34,200,200,228,0, + 0,0,146,226,13,0,0,49,183,92,142,0,2,180,26,0,163,46,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,200,200,200,200,200,22,10,194,139,0,0,14,198,56,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,45,195,201,213,92,0,0,140,65,0,28,233,0,0,1,61,115,159,251,0,0,170,152, + 71,30,249,0,0,243,16,0,61,251,0,0,112,215,196,137,247,10,0,252,0,0,0,0, + 0,0,252,0,0,0,0,0,0,252,110,197,212,179,21,0,252,127,0,0,121,167,0,252, + 15,0,0,11,241,0,252,14,0,0,11,240,0,251,126,0,0,121,164,0,236,121,202,210, + 171,17,12,162,206,204,127,1,158,123,0,2,173,80,240,15,0,0,3,4,240,16,0,0, + 2,2,156,128,0,2,169,82,11,160,207,204,124,0,0,0,0,0,0,248,0,0,0,0, + 0,0,248,0,16,171,211,202,120,248,0,162,124,0,0,122,252,0,240,15,0,0,11,252, + 0,242,14,0,0,11,252,0,166,122,0,0,123,251,0,19,175,212,199,119,232,0,12,157, + 207,205,187,35,0,159,133,1,0,85,200,0,241,207,200,200,202,222,0,238,17,0,0,0, + 0,0,151,136,1,0,105,136,0,9,151,207,204,164,14,0,0,0,125,217,123,0,0,247, + 17,0,50,200,255,200,97,0,0,252,0,0,0,0,252,0,0,0,0,252,0,0,0,0, + 252,0,0,0,0,252,0,0,16,169,210,202,121,232,0,162,127,0,0,122,249,0,240,15, + 0,0,11,251,0,242,15,0,0,13,252,0,167,129,0,0,127,252,0,19,175,213,197,111, + 245,0,92,44,0,0,73,190,0,43,187,201,199,176,28,0,0,252,0,0,0,0,0,0, + 252,0,0,0,0,0,0,252,107,192,220,97,0,0,252,111,0,32,238,0,0,252,10,0, + 0,248,0,0,252,0,0,0,248,0,0,252,0,0,0,248,0,0,252,0,0,0,248,0, + 0,197,0,0,0,0,0,252,0,0,252,0,0,252,0,0,252,0,0,252,0,0,252,0, + 0,0,197,0,0,0,0,0,0,0,252,0,0,0,252,0,0,0,252,0,0,0,252,0, + 0,0,252,0,0,0,252,0,0,6,252,0,70,213,156,0,0,252,0,0,0,0,0,0, + 252,0,0,0,0,0,0,252,0,3,157,168,6,0,252,8,176,142,2,0,0,252,192,123, + 0,0,0,0,252,145,180,8,0,0,0,252,0,135,189,11,0,0,252,0,0,125,199,15, + 0,252,0,0,252,0,0,252,0,0,252,0,0,252,0,0,252,0,0,252,0,0,252,0, + 0,252,98,192,221,103,127,197,217,99,0,0,252,123,0,26,254,117,0,26,239,0,0,252, + 16,0,0,251,8,0,0,248,0,0,252,0,0,0,248,0,0,0,248,0,0,252,0,0, + 0,248,0,0,0,248,0,0,252,0,0,0,248,0,0,0,248,0,0,252,102,191,219,97, + 0,0,252,118,0,30,238,0,0,252,15,0,0,248,0,0,252,0,0,0,248,0,0,252, + 0,0,0,248,0,0,252,0,0,0,248,0,12,160,209,208,160,12,0,158,130,0,0,126, + 158,0,240,14,0,0,11,239,0,240,19,0,0,15,237,0,156,138,0,0,135,154,0,11, + 160,208,208,159,11,0,0,236,119,199,211,178,21,0,252,128,0,0,121,167,0,252,15,0, + 0,11,241,0,252,14,0,0,11,240,0,252,126,0,0,121,164,0,252,121,202,210,171,17, + 0,252,0,0,0,0,0,0,252,0,0,0,0,0,16,171,211,202,120,232,0,162,124,0, + 0,122,250,0,240,15,0,0,11,252,0,242,14,0,0,11,252,0,166,122,0,0,123,252, + 0,19,175,212,199,119,248,0,0,0,0,0,0,248,0,0,0,0,0,0,248,0,0,0, + 0,0,2,0,252,115,199,67,0,252,109,0,0,0,252,15,0,0,0,252,0,0,0,0, + 252,0,0,0,0,252,0,0,0,0,100,209,203,203,57,0,242,23,0,53,149,0,145,199, + 116,48,0,0,0,34,98,178,150,30,206,15,0,20,242,0,106,210,202,207,99,0,0,138, + 0,0,0,0,0,252,0,0,0,50,200,255,200,166,0,0,0,252,0,0,0,0,0,252, + 0,0,0,0,0,252,0,0,0,0,0,245,15,0,0,0,0,124,218,165,0,0,252,0, + 0,0,248,0,0,252,0,0,0,248,0,0,252,0,0,0,248,0,0,252,0,0,10,251, + 0,0,236,35,0,113,252,0,0,94,219,192,109,248,0,146,114,0,0,32,214,1,40,214, + 0,0,131,110,0,0,186,60,3,215,15,0,0,78,160,77,150,0,0,0,3,213,191,43, + 0,0,0,0,118,190,0,0,0,130,116,0,4,235,47,0,52,183,50,188,0,68,216,132, + 0,122,103,1,217,10,153,62,208,0,192,24,0,146,79,197,0,165,63,192,0,0,66,210, + 130,0,76,211,119,0,0,4,239,40,0,6,238,39,0,21,220,45,0,78,190,3,0,59, + 208,37,211,24,0,0,0,123,243,62,0,0,0,0,163,220,87,0,0,0,108,158,22,218, + 38,0,61,204,9,0,68,208,9,139,127,0,0,36,211,1,25,227,9,0,143,101,0,0, + 153,107,11,219,9,0,0,34,217,107,130,0,0,0,0,163,239,24,0,0,0,0,103,160, + 0,0,0,0,1,208,46,0,0,0,0,72,190,0,0,0,0,0,178,200,200,244,118,0, + 0,0,59,214,12,0,0,17,219,50,0,0,0,172,114,0,0,0,102,182,1,0,0,0, + 246,216,200,200,125,0,52,200,200,32,0,131,103,0,0,0,80,148,0,0,0,38,196,0, + 0,104,232,99,0,0,0,40,193,0,0,0,62,176,0,0,0,110,125,0,0,0,132,105, + 0,0,0,36,196,200,32,0,126,0,0,252,0,0,252,0,0,252,0,0,252,0,0,252, + 0,0,252,0,0,252,0,0,252,0,0,252,0,0,252,0,56,201,195,32,0,0,0,135, + 99,0,0,0,180,48,0,0,0,226,8,0,0,0,136,225,78,0,0,224,10,0,0,0, + 207,30,0,0,0,157,78,0,0,0,136,100,0,56,201,187,21,0,51,198,194,108,37,164, + 8,141,16,32,159,222,123,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0, +}; +static const struct nvfont nvfont_small1 = { 12, 3, nvg_small1, nvb_small1 }; + +static const struct nvglyph nvg_title1[] = { + {0,5,0,0,0,296}, + {0,7,18,0,-18,453}, + {126,11,19,0,-19,690}, + {335,17,18,0,-18,1117}, + {641,16,24,0,-21,978}, + {1025,19,18,0,-18,1207}, + {1367,19,18,0,-18,1180}, + {1709,6,19,0,-19,393}, + {1823,11,22,0,-19,674}, + {2065,11,22,0,-19,674}, + {2307,10,18,0,-18,660}, + {2487,13,15,0,-15,836}, + {2682,7,8,0,-4,422}, + {2738,12,8,0,-8,757}, + {2834,7,4,0,-4,422}, + {2862,11,21,0,-19,682}, + {3093,16,18,0,-18,1040}, + {3381,9,18,0,-18,594}, + {3543,13,18,0,-18,856}, + {3777,14,18,0,-18,889}, + {4029,15,18,0,-18,945}, + {4299,14,18,0,-18,892}, + {4551,15,18,0,-18,945}, + {4821,13,18,0,-18,862}, + {5055,14,18,0,-18,919}, + {5307,15,18,0,-18,951}, + {5577,7,12,0,-12,422}, + {5661,7,16,0,-12,422}, + {5773,13,15,0,-15,836}, + {5968,13,13,0,-13,836}, + {6137,13,15,0,-15,836}, + {6332,14,19,0,-19,891}, + {6598,21,18,0,-18,1332}, + {6976,18,18,0,-18,1126}, + {7300,15,18,0,-18,991}, + {7570,18,18,0,-18,1147}, + {7894,18,18,0,-18,1160}, + {8218,15,18,0,-18,968}, + {8488,14,18,0,-18,912}, + {8740,18,18,0,-18,1174}, + {9064,17,18,0,-18,1114}, + {9370,6,18,0,-18,390}, + {9478,10,18,0,-18,656}, + {9658,17,18,0,-18,1094}, + {9964,14,18,0,-18,905}, + {10216,22,18,0,-18,1379}, + {10612,17,18,0,-18,1114}, + {10918,19,18,0,-18,1220}, + {11260,16,18,0,-18,1025}, + {11548,19,18,0,-18,1238}, + {11890,16,18,0,-18,1020}, + {12178,16,18,0,-18,978}, + {12466,16,18,0,-18,1020}, + {12754,17,18,0,-18,1081}, + {13060,18,18,0,-18,1126}, + {13384,26,18,0,-18,1670}, + {13852,17,18,0,-18,1086}, + {14158,17,18,0,-18,1075}, + {14464,15,18,0,-18,969}, + {14734,11,22,0,-19,674}, + {14976,11,21,0,-19,682}, + {15207,11,22,0,-19,674}, + {15449,13,18,0,-18,836}, + {15683,11,4,0,0,716}, + {15727,9,19,0,-19,544}, + {15898,13,13,0,-13,857}, + {16067,15,18,0,-18,954}, + {16337,14,13,0,-13,848}, + {16519,15,18,0,-18,954}, + {16789,14,13,0,-13,877}, + {16971,9,18,0,-18,568}, + {17133,15,18,0,-13,954}, + {17403,15,18,0,-18,940}, + {17673,6,18,0,-18,399}, + {17781,7,23,-1,-18,399}, + {17942,14,18,0,-18,857}, + {18194,6,18,0,-18,399}, + {18302,23,13,0,-13,1445}, + {18601,15,13,0,-13,940}, + {18796,14,13,0,-13,917}, + {18978,15,18,0,-13,954}, + {19248,15,18,0,-13,954}, + {19518,10,13,0,-13,599}, + {19648,12,13,0,-13,759}, + {19804,10,18,0,-18,624}, + {19984,15,13,0,-13,928}, + {20179,13,13,0,-13,823}, + {20348,19,13,0,-13,1229}, + {20595,14,13,0,-13,902}, + {20777,13,18,0,-13,823}, + {21011,12,13,0,-13,788}, + {21167,11,22,0,-19,674}, + {21409,6,23,0,-20,359}, + {21547,11,22,0,-19,674}, + {21789,14,11,0,-11,836}, +}; +static const unsigned char nvb_title1[] = { + 0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255, + 255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36, + 0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0, + 255,255,255,36,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0, + 0,255,255,255,36,0,0,112,249,145,0,0,145,249,112,0,0,0,238,255,255,19,19,255, + 255,238,0,0,0,249,255,255,29,29,255,255,249,0,0,0,213,255,246,2,2,247,255,213, + 0,0,0,161,255,196,0,0,196,255,165,0,0,0,105,255,142,0,0,142,255,109,0,0, + 0,51,255,87,0,0,87,255,52,0,0,0,5,247,32,0,0,32,247,5,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,12,255,255,128,0,0,76,255,255,64,0,0,0,0,0,0,0,44,255,255, + 96,0,0,108,255,255,32,0,0,0,0,0,0,0,80,255,255,61,0,0,141,255,250,3, + 0,0,0,0,0,0,0,112,255,255,28,0,0,176,255,220,0,0,0,0,0,152,152,152, + 208,255,255,154,152,152,234,255,232,152,152,152,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0, + 0,0,0,244,255,152,0,0,48,255,255,88,0,0,0,0,0,0,0,20,255,255,120,0, + 0,81,255,255,56,0,0,0,0,0,0,0,52,255,255,88,0,0,116,255,255,24,0,0, + 0,0,136,136,136,172,255,255,167,136,136,202,255,255,137,136,136,0,0,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,0,0,0,0,0,184,255,212,0,0,0,244,255,148,0,0,0,0,0,0,0, + 0,216,255,180,0,0,21,255,255,116,0,0,0,0,0,0,0,2,247,255,146,0,0,56, + 255,255,82,0,0,0,0,0,0,0,28,255,255,112,0,0,88,255,255,48,0,0,0,0, + 0,0,0,60,255,255,80,0,0,120,255,255,16,0,0,0,0,0,0,0,0,0,0,0, + 255,255,40,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,40,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,40,0,0,0,0,0,0,0,0,0,0,44,150,216, + 255,255,237,188,100,6,0,0,0,0,0,0,118,253,255,255,255,255,255,255,255,210,29,0, + 0,0,0,82,255,255,255,200,140,135,181,252,255,255,211,6,0,0,0,199,255,255,137,0, + 0,0,0,57,246,255,255,102,0,0,0,249,255,255,43,0,0,0,0,0,140,255,255,181, + 0,0,0,246,255,255,98,0,0,0,0,0,27,80,80,65,0,0,0,187,255,255,246,122, + 25,0,0,0,0,0,0,0,0,0,0,57,250,255,255,255,254,198,121,42,0,0,0,0, + 0,0,0,0,70,230,255,255,255,255,255,255,211,95,2,0,0,0,0,0,0,11,108,200, + 254,255,255,255,255,255,182,8,0,0,0,0,0,0,0,0,24,102,182,249,255,255,255,141, + 0,0,0,0,0,0,0,0,0,0,0,25,188,255,255,243,3,0,173,224,224,78,0,0, + 0,0,0,0,32,255,255,255,31,0,140,255,255,184,0,0,0,0,0,0,12,255,255,255, + 25,0,40,252,255,255,144,8,0,0,0,0,123,255,255,231,1,0,0,128,255,255,255,235, + 166,139,150,206,255,255,255,117,0,0,0,1,124,251,255,255,255,255,255,255,255,255,152,2, + 0,0,0,0,0,35,137,204,255,255,249,220,161,62,0,0,0,0,0,0,0,0,0,0, + 255,255,40,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,40,0,0,0,0,0, + 0,0,0,0,0,0,0,0,128,128,20,0,0,0,0,0,0,0,0,48,182,246,245,186, + 50,0,0,0,0,0,6,207,255,192,2,0,0,44,244,255,247,248,255,246,46,0,0,0, + 0,135,255,242,32,0,0,0,174,255,165,8,9,168,255,175,0,0,0,59,253,255,96,0, + 0,0,0,237,255,33,0,0,31,255,237,0,0,12,222,255,174,0,0,0,0,0,254,255, + 8,0,0,4,255,253,0,0,154,255,233,21,0,0,0,0,0,233,255,43,0,0,41,255, + 233,0,77,255,255,76,0,0,0,0,0,0,163,255,186,23,26,189,255,159,21,233,255,154, + 0,0,0,0,0,0,0,33,235,255,255,255,255,233,29,174,255,220,12,0,0,0,0,0, + 0,0,0,33,159,222,222,157,30,98,255,253,58,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,32,242,255,134,48,183,246,245,186,51,0,0,0,0,0,0,0,0,2,194,255, + 205,49,244,255,244,245,255,247,49,0,0,0,0,0,0,0,119,255,248,42,173,255,164,6, + 8,166,255,178,0,0,0,0,0,0,46,249,255,112,0,237,255,34,0,0,33,255,241,0, + 0,0,0,0,7,211,255,189,2,0,254,255,7,0,0,4,255,255,2,0,0,0,0,139, + 255,240,30,0,0,235,255,37,0,0,35,255,239,0,0,0,0,63,254,255,91,0,0,0, + 169,255,167,8,11,175,255,173,0,0,0,14,224,255,170,0,0,0,0,40,242,255,247,248, + 255,242,43,0,0,0,159,255,230,19,0,0,0,0,0,48,182,246,246,182,45,0,0,0, + 0,0,0,11,121,209,249,250,217,138,23,0,0,0,0,0,0,0,0,0,0,21,212,255, + 255,255,255,255,255,234,45,0,0,0,0,0,0,0,0,0,161,255,255,248,157,153,238,255, + 255,206,0,0,0,0,0,0,0,0,0,241,255,255,97,0,0,53,255,255,255,23,0,0, + 0,0,0,0,0,0,247,255,255,44,0,0,13,255,255,255,22,0,0,0,0,0,0,0, + 0,189,255,255,124,0,0,137,255,255,196,0,0,0,0,0,0,0,0,0,63,254,255,249, + 105,186,255,255,232,38,0,0,0,0,0,0,0,0,0,0,178,255,255,255,255,255,173,28, + 0,0,0,0,0,0,0,0,0,21,186,255,255,255,255,255,82,0,0,0,4,255,255,255, + 32,0,0,12,211,255,255,202,181,255,255,233,43,0,0,22,255,255,255,12,0,0,132,255, + 255,196,7,5,179,255,255,233,43,0,63,255,255,225,0,0,0,219,255,255,73,0,0,8, + 187,255,255,233,43,137,255,255,159,0,0,0,252,255,255,41,0,0,0,10,195,255,255,233, + 236,255,255,63,0,0,0,237,255,255,89,0,0,0,0,14,202,255,255,255,255,190,0,0, + 0,0,171,255,255,226,40,0,0,0,0,46,252,255,255,255,78,0,0,0,0,41,244,255, + 255,251,183,142,144,188,251,255,255,255,255,233,43,0,0,0,0,61,231,255,255,255,255,255, + 255,255,255,196,221,255,255,233,43,0,0,0,0,14,112,192,236,253,247,217,157,65,0,32, + 227,255,255,233,43,0,112,249,145,0,0,0,238,255,255,19,0,0,249,255,255,29,0,0, + 213,255,246,2,0,0,161,255,196,0,0,0,105,255,142,0,0,0,51,255,87,0,0,0, + 5,247,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,123,255,255,130,0,0,0,0,0,0,100,255,255,162,0,0,0,0,0, + 0,51,250,255,215,9,0,0,0,0,0,4,211,255,255,58,0,0,0,0,0,0,102,255, + 255,174,0,0,0,0,0,0,1,219,255,255,57,0,0,0,0,0,0,61,255,255,218,0, + 0,0,0,0,0,0,139,255,255,144,0,0,0,0,0,0,0,194,255,255,91,0,0,0, + 0,0,0,0,233,255,255,57,0,0,0,0,0,0,0,252,255,255,40,0,0,0,0,0, + 0,0,252,255,255,40,0,0,0,0,0,0,0,235,255,255,57,0,0,0,0,0,0,0, + 198,255,255,91,0,0,0,0,0,0,0,142,255,255,144,0,0,0,0,0,0,0,67,255, + 255,219,0,0,0,0,0,0,0,3,226,255,255,59,0,0,0,0,0,0,0,112,255,255, + 177,0,0,0,0,0,0,0,7,219,255,255,65,0,0,0,0,0,0,0,60,252,255,219, + 12,0,0,0,0,0,0,0,110,255,255,166,1,0,0,0,0,0,0,0,127,255,255,131, + 0,0,96,255,255,164,2,0,0,0,0,0,0,0,130,255,255,147,0,0,0,0,0,0, + 0,2,189,255,255,92,0,0,0,0,0,0,0,34,247,255,240,23,0,0,0,0,0,0, + 0,141,255,255,147,0,0,0,0,0,0,0,27,252,255,247,17,0,0,0,0,0,0,0, + 183,255,255,103,0,0,0,0,0,0,0,108,255,255,177,0,0,0,0,0,0,0,55,255, + 255,234,0,0,0,0,0,0,0,21,255,255,255,15,0,0,0,0,0,0,4,255,255,255, + 32,0,0,0,0,0,0,4,255,255,255,32,0,0,0,0,0,0,21,255,255,255,13,0, + 0,0,0,0,0,55,255,255,230,0,0,0,0,0,0,0,108,255,255,175,0,0,0,0, + 0,0,0,183,255,255,98,0,0,0,0,0,0,24,251,255,243,14,0,0,0,0,0,0, + 137,255,255,139,0,0,0,0,0,0,31,246,255,236,18,0,0,0,0,0,2,188,255,255, + 84,0,0,0,0,0,0,127,255,255,139,0,0,0,0,0,0,96,255,255,162,2,0,0, + 0,0,0,0,0,5,12,253,255,95,4,1,0,0,21,213,45,208,255,47,179,90,0,0, + 153,255,239,205,248,198,255,226,9,0,71,153,229,255,255,247,178,98,12,0,71,157,235,255, + 255,250,184,102,12,0,153,255,239,205,248,198,255,226,9,0,21,214,45,208,255,47,179,90, + 0,0,0,5,12,253,255,95,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,40,0, + 0,0,0,0,0,0,0,0,0,255,255,40,0,0,0,0,0,0,0,0,0,0,255,255, + 40,0,0,0,0,0,0,0,0,0,0,255,255,40,0,0,0,0,0,0,136,136,136,136, + 255,255,156,136,136,136,68,0,0,255,255,255,255,255,255,255,255,255,255,128,0,0,255,255, + 255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,255,255,40,0,0,0,0,0,0, + 0,0,0,0,255,255,40,0,0,0,0,0,0,0,0,0,0,255,255,40,0,0,0,0, + 0,0,0,0,0,0,255,255,40,0,0,0,0,0,0,0,0,0,0,128,128,20,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36, + 0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,25,0,0,0, + 0,142,243,2,0,0,0,5,213,168,0,0,0,0,205,245,44,0,0,0,0,202,53,0, + 0,0,0,0,136,136,136,136,136,136,136,136,136,0,0,0,255,255,255,255,255,255,255,255, + 255,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0, + 0,255,255,255,36,0,0,0,0,0,0,0,9,245,255,166,0,0,0,0,0,0,0,76, + 255,255,90,0,0,0,0,0,0,0,150,255,252,17,0,0,0,0,0,0,1,226,255,193, + 0,0,0,0,0,0,0,48,255,255,116,0,0,0,0,0,0,0,125,255,255,40,0,0, + 0,0,0,0,0,200,255,218,0,0,0,0,0,0,0,23,254,255,143,0,0,0,0,0, + 0,0,97,255,255,67,0,0,0,0,0,0,0,174,255,240,6,0,0,0,0,0,0,7, + 243,255,170,0,0,0,0,0,0,0,70,255,255,94,0,0,0,0,0,0,0,147,255,253, + 20,0,0,0,0,0,0,0,223,255,197,0,0,0,0,0,0,0,44,255,255,121,0,0, + 0,0,0,0,0,121,255,255,44,0,0,0,0,0,0,0,197,255,224,0,0,0,0,0, + 0,0,20,253,255,147,0,0,0,0,0,0,0,93,255,255,71,0,0,0,0,0,0,0, + 170,255,243,7,0,0,0,0,0,0,6,240,255,174,0,0,0,0,0,0,0,0,0,0, + 0,13,122,207,249,250,217,138,23,0,0,0,0,0,0,0,43,223,255,255,255,255,255,255, + 237,65,0,0,0,0,0,29,234,255,255,228,152,147,217,255,255,246,50,0,0,0,0,180, + 255,255,188,11,0,0,3,155,255,255,210,2,0,0,47,255,255,244,22,0,0,0,0,6, + 221,255,255,83,0,0,138,255,255,160,0,0,0,0,0,0,121,255,255,173,0,0,199,255, + 255,94,0,0,0,0,0,0,57,255,255,235,0,0,235,255,255,55,0,0,0,0,0,0, + 20,255,255,255,15,0,252,255,255,39,0,0,0,0,0,0,4,255,255,255,31,0,249,255, + 255,40,0,0,0,0,0,0,6,255,255,255,29,0,232,255,255,60,0,0,0,0,0,0, + 25,255,255,255,12,0,191,255,255,100,0,0,0,0,0,0,67,255,255,228,0,0,128,255, + 255,167,0,0,0,0,0,0,138,255,255,164,0,0,38,254,255,248,29,0,0,0,0,14, + 234,255,255,74,0,0,0,166,255,255,195,14,0,0,6,171,255,255,202,1,0,0,0,23, + 226,255,255,231,153,148,220,255,255,242,43,0,0,0,0,0,37,219,255,255,255,255,255,255, + 234,58,0,0,0,0,0,0,0,11,120,207,249,250,215,133,21,0,0,0,0,0,0,0, + 0,5,199,255,255,36,0,0,0,15,177,255,255,255,36,0,7,98,227,255,255,255,255,36, + 98,241,255,255,255,255,255,255,36,128,255,255,254,115,255,255,255,36,128,255,200,61,0,255, + 255,255,36,45,41,0,0,0,255,255,255,36,0,0,0,0,0,255,255,255,36,0,0,0, + 0,0,255,255,255,36,0,0,0,0,0,255,255,255,36,0,0,0,0,0,255,255,255,36, + 0,0,0,0,0,255,255,255,36,0,0,0,0,0,255,255,255,36,0,0,0,0,0,255, + 255,255,36,0,0,0,0,0,255,255,255,36,0,0,0,0,0,255,255,255,36,0,0,0, + 0,0,255,255,255,36,0,0,0,0,0,255,255,255,36,0,0,0,25,138,211,247,253,231, + 172,64,0,0,0,0,73,242,255,255,255,255,255,255,255,130,0,0,44,247,255,255,238,159, + 144,204,255,255,255,77,0,176,255,255,219,23,0,0,2,166,255,255,188,9,250,255,255,87, + 0,0,0,0,56,255,255,242,14,104,104,104,16,0,0,0,0,44,255,255,253,0,0,0, + 0,0,0,0,0,0,96,255,255,230,0,0,0,0,0,0,0,0,2,206,255,255,170,0, + 0,0,0,0,0,0,0,121,255,255,255,67,0,0,0,0,0,0,0,83,253,255,255,175, + 0,0,0,0,0,0,0,79,250,255,255,225,21,0,0,0,0,0,0,92,251,255,255,238, + 44,0,0,0,0,0,0,119,255,255,255,237,50,0,0,0,0,0,5,155,255,255,255,226, + 42,0,0,0,0,0,19,189,255,255,255,205,26,0,0,0,0,0,0,222,255,255,255,249, + 146,136,136,136,136,136,136,0,255,255,255,255,255,255,255,255,255,255,255,255,0,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,4,97,190,238,254,239,191,97,3,0,0,0,0, + 19,198,255,255,255,255,255,255,255,191,11,0,0,0,186,255,255,252,178,141,172,252,255,255, + 148,0,0,64,255,255,250,61,0,0,0,91,255,255,245,5,0,137,255,255,170,0,0,0, + 0,9,255,255,255,30,0,8,12,12,6,0,0,0,0,39,255,255,255,20,0,0,0,0, + 0,0,0,6,52,205,255,255,209,0,0,0,0,0,0,128,255,255,255,255,255,252,67,0, + 0,0,0,0,0,128,255,255,255,255,255,193,14,0,0,0,0,0,0,68,136,139,165,239, + 255,255,207,12,0,0,0,0,0,0,0,0,0,21,214,255,255,129,0,0,0,0,0,0, + 0,0,0,0,84,255,255,218,0,181,232,232,87,0,0,0,0,0,41,255,255,252,0,151, + 255,255,172,0,0,0,0,0,69,255,255,240,0,59,255,255,255,89,0,0,0,12,197,255, + 255,179,0,0,164,255,255,255,195,143,153,229,255,255,252,55,0,0,8,172,255,255,255,255, + 255,255,255,246,88,0,0,0,0,0,73,171,229,253,247,211,139,31,0,0,0,0,0,0, + 0,0,0,0,0,67,254,255,255,36,0,0,0,0,0,0,0,0,0,19,230,255,255,255, + 36,0,0,0,0,0,0,0,0,0,174,255,255,255,255,36,0,0,0,0,0,0,0,0, + 101,255,255,255,255,255,36,0,0,0,0,0,0,0,38,245,255,255,255,255,255,36,0,0, + 0,0,0,0,5,204,255,255,159,255,255,255,36,0,0,0,0,0,0,135,255,255,222,14, + 255,255,255,36,0,0,0,0,0,64,253,255,253,59,0,255,255,255,36,0,0,0,0,16, + 225,255,255,129,0,0,255,255,255,36,0,0,0,0,167,255,255,202,4,0,0,255,255,255, + 36,0,0,0,94,255,255,245,37,0,0,0,255,255,255,36,0,0,34,242,255,255,101,0, + 0,0,0,255,255,255,36,0,0,127,255,255,255,255,255,255,255,255,255,255,255,255,255,0, + 128,255,255,255,255,255,255,255,255,255,255,255,255,255,0,72,144,144,144,144,144,144,144,144, + 255,255,255,162,144,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 36,0,0,0,0,107,255,255,255,255,255,255,255,255,255,44,0,0,0,129,255,255,255,255, + 255,255,255,255,255,44,0,0,0,151,255,255,183,136,136,136,136,136,136,23,0,0,0,172, + 255,255,84,0,0,0,0,0,0,0,0,0,0,195,255,255,64,0,0,0,0,0,0,0, + 0,0,0,216,255,255,44,0,0,0,0,0,0,0,0,0,0,238,255,255,132,217,252,238, + 183,67,0,0,0,0,5,255,255,255,255,255,255,255,255,255,122,0,0,0,25,255,255,255, + 235,156,146,220,255,255,255,67,0,0,42,232,232,204,19,0,0,7,187,255,255,189,0,0, + 0,0,0,0,0,0,0,0,58,255,255,251,6,0,0,0,0,0,0,0,0,0,10,255, + 255,255,30,5,52,52,52,4,0,0,0,0,7,255,255,255,30,5,244,255,255,65,0,0, + 0,0,55,255,255,248,4,0,164,255,255,208,19,0,0,7,186,255,255,179,0,0,36,244, + 255,255,236,156,146,220,255,255,252,51,0,0,0,69,241,255,255,255,255,255,255,250,94,0, + 0,0,0,0,28,142,216,249,251,223,156,42,0,0,0,0,0,0,0,21,133,212,249,251, + 220,146,30,0,0,0,0,0,0,60,234,255,255,255,255,255,255,242,66,0,0,0,0,38, + 240,255,255,222,150,146,215,255,255,239,23,0,0,0,188,255,255,163,5,0,0,4,171,255, + 255,134,0,0,50,255,255,232,10,0,0,0,0,44,224,224,180,0,0,135,255,255,143,0, + 0,0,0,0,0,0,0,0,0,0,192,255,255,84,0,43,111,133,116,57,0,0,0,0, + 0,228,255,255,63,167,255,255,255,255,255,197,30,0,0,0,248,255,255,209,255,255,255,255, + 255,255,255,224,18,0,0,255,255,255,255,149,38,4,26,121,250,255,255,143,0,0,245,255, + 255,147,0,0,0,0,0,114,255,255,235,1,0,219,255,255,55,0,0,0,0,0,21,255, + 255,255,24,0,174,255,255,44,0,0,0,0,0,7,255,255,255,30,0,103,255,255,107,0, + 0,0,0,0,58,255,255,250,7,0,14,239,255,238,53,0,0,0,19,203,255,255,181,0, + 0,0,100,255,255,253,181,140,162,239,255,255,250,50,0,0,0,0,125,254,255,255,255,255, + 255,255,245,80,0,0,0,0,0,0,54,164,226,252,247,211,140,29,0,0,0,0,255,255, + 255,255,255,255,255,255,255,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,0, + 136,136,136,136,136,136,136,136,180,255,255,215,0,0,0,0,0,0,0,0,6,212,255,255, + 64,0,0,0,0,0,0,0,0,123,255,255,168,0,0,0,0,0,0,0,0,28,246,255, + 250,34,0,0,0,0,0,0,0,0,158,255,255,151,0,0,0,0,0,0,0,0,34,252, + 255,252,31,0,0,0,0,0,0,0,0,147,255,255,168,0,0,0,0,0,0,0,0,12, + 242,255,255,63,0,0,0,0,0,0,0,0,95,255,255,225,1,0,0,0,0,0,0,0, + 0,181,255,255,141,0,0,0,0,0,0,0,0,10,247,255,255,65,0,0,0,0,0,0, + 0,0,68,255,255,248,8,0,0,0,0,0,0,0,0,125,255,255,202,0,0,0,0,0, + 0,0,0,0,172,255,255,157,0,0,0,0,0,0,0,0,0,211,255,255,120,0,0,0, + 0,0,0,0,0,0,240,255,255,96,0,0,0,0,0,0,0,0,1,90,187,238,254,238, + 188,90,1,0,0,0,0,6,176,255,255,255,255,255,255,255,177,6,0,0,0,131,255,255, + 255,179,142,182,255,255,255,131,0,0,2,239,255,255,105,0,0,0,106,255,255,239,2,0, + 29,255,255,255,10,0,0,0,10,255,255,255,30,0,24,255,255,255,35,0,0,0,35,255, + 255,255,23,0,0,222,255,255,199,46,6,44,197,255,255,223,0,0,0,89,255,255,255,255, + 255,255,255,255,255,90,0,0,0,9,194,255,255,255,255,255,255,255,195,8,0,0,8,199, + 255,255,241,166,142,166,242,255,255,196,8,0,126,255,255,212,23,0,0,0,23,214,255,255, + 123,0,217,255,255,79,0,0,0,0,0,79,255,255,216,0,252,255,255,42,0,0,0,0, + 0,42,255,255,252,0,237,255,255,79,0,0,0,0,0,79,255,255,237,0,172,255,255,211, + 22,0,0,0,22,212,255,255,172,0,43,247,255,255,241,165,142,165,241,255,255,248,46,0, + 0,68,237,255,255,255,255,255,255,255,239,74,0,0,0,0,18,123,200,240,254,242,203,128, + 21,0,0,0,0,0,22,132,208,245,253,229,168,61,0,0,0,0,0,0,67,239,255,255, + 255,255,255,255,255,135,0,0,0,0,37,245,255,255,243,166,140,170,245,255,255,107,0,0, + 0,160,255,255,218,27,0,0,0,32,224,255,241,15,0,0,229,255,255,83,0,0,0,0, + 0,89,255,255,103,0,0,252,255,255,42,0,0,0,0,0,40,255,255,173,0,0,236,255, + 255,82,0,0,0,0,0,73,255,255,218,0,0,172,255,255,217,26,0,0,0,19,208,255, + 255,243,0,0,53,253,255,255,243,166,140,164,239,255,255,255,254,0,0,0,101,253,255,255, + 255,255,255,253,138,255,255,250,0,0,0,0,51,169,233,253,235,174,54,51,255,255,234,0, + 0,0,0,0,0,0,0,0,0,0,76,255,255,201,0,0,92,124,124,21,0,0,0,0, + 0,120,255,255,149,0,0,150,255,255,88,0,0,0,0,0,201,255,255,73,0,0,70,255, + 255,213,17,0,0,0,107,255,255,218,2,0,0,1,196,255,255,234,157,140,192,255,255,253, + 66,0,0,0,0,28,218,255,255,255,255,255,255,250,93,0,0,0,0,0,0,11,124,207, + 246,253,224,156,41,0,0,0,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0, + 0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0, + 0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255, + 255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255, + 36,0,0,0,255,255,255,25,0,0,0,0,142,243,2,0,0,0,5,213,168,0,0,0, + 0,205,245,44,0,0,0,0,202,53,0,0,0,0,0,0,0,0,0,0,0,0,0,19, + 42,0,0,0,0,0,0,0,0,7,83,176,251,128,0,0,0,0,0,1,58,151,237,255, + 255,255,128,0,0,0,33,126,219,255,255,255,255,209,118,25,0,0,195,255,255,255,255,221, + 133,42,0,0,0,0,0,255,255,255,186,57,1,0,0,0,0,0,0,0,255,255,255,255, + 206,118,29,0,0,0,0,0,0,54,146,233,255,255,255,254,195,106,22,0,0,0,0,0, + 4,72,164,244,255,255,255,252,103,0,0,0,0,0,0,0,11,90,182,252,255,128,0,0, + 0,0,0,0,0,0,0,0,22,109,88,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255, + 255,255,255,255,0,0,255,255,255,255,255,255,255,255,255,255,255,0,0,136,136,136,136,136, + 136,136,136,136,136,136,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,0,0,255, + 255,255,255,255,255,255,255,255,255,255,0,0,136,136,136,136,136,136,136,136,136,136,136,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,35,0,0,0,0,0, + 0,0,0,0,0,0,0,255,199,109,22,0,0,0,0,0,0,0,0,0,255,255,255,252, + 182,91,12,0,0,0,0,0,0,88,178,249,255,255,255,244,165,74,4,0,0,0,0,0, + 17,101,189,253,255,255,255,234,86,0,0,0,0,0,0,0,25,115,244,255,255,128,0,0, + 0,0,0,1,59,150,236,255,255,255,128,0,0,0,43,134,224,255,255,255,255,196,102,16, + 0,0,210,255,255,255,255,219,128,34,0,0,0,0,0,255,255,239,152,59,1,0,0,0, + 0,0,0,0,177,84,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 37,99,129,131,107,48,0,0,0,0,0,0,38,188,255,255,255,255,255,255,202,46,0,0, + 0,52,240,255,255,255,255,255,255,255,255,242,42,0,7,222,255,255,192,62,11,8,67,219, + 255,255,178,0,92,255,255,206,5,0,0,0,0,68,255,255,242,0,117,196,196,83,0,0, + 0,0,0,49,255,255,249,0,0,0,0,0,0,0,0,0,3,170,255,255,199,0,0,0, + 0,0,0,0,0,27,189,255,255,252,63,0,0,0,0,0,0,0,63,235,255,255,245,83, + 0,0,0,0,0,0,0,44,246,255,255,213,40,0,0,0,0,0,0,0,0,174,255,255, + 211,15,0,0,0,0,0,0,0,0,0,239,255,255,74,0,0,0,0,0,0,0,0,0, + 0,99,100,100,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0, + 0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255, + 255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0, + 0,0,0,0,10,93,166,215,242,253,246,219,169,86,5,0,0,0,0,0,0,0,0,3, + 112,237,255,255,255,255,255,255,255,255,255,217,59,0,0,0,0,0,0,15,188,255,255,255, + 234,179,150,139,147,180,238,255,255,249,73,0,0,0,0,6,195,255,255,192,67,54,122,128, + 68,0,0,6,99,239,255,240,26,0,0,0,134,255,254,113,8,164,255,255,255,255,137,113, + 188,186,54,244,255,139,0,0,25,248,255,124,0,163,255,255,255,255,255,252,221,255,208,0, + 131,255,215,0,0,120,255,216,3,62,255,255,249,92,8,71,249,255,255,144,0,54,255,250, + 0,0,191,255,116,0,167,255,255,134,0,0,0,174,255,255,80,0,33,255,250,0,0,233, + 255,55,0,229,255,255,56,0,0,0,154,255,254,18,0,52,255,219,0,0,252,255,32,0, + 252,255,255,43,0,0,0,201,255,208,0,0,111,255,153,0,0,249,255,44,0,240,255,255, + 109,0,0,77,255,255,150,0,5,217,255,54,0,0,217,255,99,0,183,255,255,250,158,162, + 249,248,255,159,20,164,255,163,0,0,0,153,255,208,4,60,252,255,255,255,255,140,185,255, + 255,255,255,195,12,0,0,0,50,255,255,155,2,76,210,252,221,109,1,40,202,252,222,123, + 6,0,0,0,0,0,147,255,255,197,62,1,0,0,0,0,0,0,22,24,0,0,0,0, + 0,0,0,6,176,255,255,255,228,173,145,139,150,176,220,255,160,0,0,0,0,0,0,0, + 0,2,113,241,255,255,255,255,255,255,255,255,255,183,0,0,0,0,0,0,0,0,0,0, + 14,101,173,219,245,254,243,206,145,57,0,0,0,0,0,0,0,0,0,0,0,0,64,255, + 255,255,120,0,0,0,0,0,0,0,0,0,0,0,0,0,152,255,255,255,208,0,0,0, + 0,0,0,0,0,0,0,0,0,4,236,255,255,255,255,40,0,0,0,0,0,0,0,0, + 0,0,0,73,255,255,172,255,255,128,0,0,0,0,0,0,0,0,0,0,0,162,255,252, + 27,243,255,216,0,0,0,0,0,0,0,0,0,0,8,242,255,190,0,170,255,255,50,0, + 0,0,0,0,0,0,0,0,82,255,255,110,0,87,255,255,137,0,0,0,0,0,0,0, + 0,0,170,255,255,31,0,13,249,255,225,1,0,0,0,0,0,0,0,12,246,255,205,0, + 0,0,182,255,255,58,0,0,0,0,0,0,0,90,255,255,123,0,0,0,102,255,255,146, + 0,0,0,0,0,0,0,178,255,255,42,0,0,0,24,254,255,231,3,0,0,0,0,0, + 17,249,255,255,255,255,255,255,255,255,255,255,66,0,0,0,0,0,100,255,255,255,255,255, + 255,255,255,255,255,255,154,0,0,0,0,0,188,255,255,171,156,156,156,156,156,163,255,255, + 237,5,0,0,0,23,253,255,229,1,0,0,0,0,0,0,210,255,255,76,0,0,0,108, + 255,255,150,0,0,0,0,0,0,0,130,255,255,164,0,0,0,196,255,255,70,0,0,0, + 0,0,0,0,50,255,255,243,9,0,29,255,255,240,5,0,0,0,0,0,0,0,0,225, + 255,255,84,0,0,255,255,255,255,255,255,255,255,245,202,109,5,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,194,9,0,0,255,255,255,154,136,136,136,143,198,255,255,255,133, + 0,0,255,255,255,36,0,0,0,0,0,146,255,255,224,0,0,255,255,255,36,0,0,0, + 0,0,50,255,255,253,0,0,255,255,255,36,0,0,0,0,0,52,255,255,233,0,0,255, + 255,255,36,0,0,0,0,1,157,255,255,156,0,0,255,255,255,154,136,136,136,144,205,255, + 255,224,23,0,0,255,255,255,255,255,255,255,255,255,255,244,41,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,216,18,0,0,255,255,255,36,0,0,0,8,73,228,255,255,149, + 0,0,255,255,255,36,0,0,0,0,0,70,255,255,240,2,0,255,255,255,36,0,0,0, + 0,0,7,255,255,255,29,0,255,255,255,36,0,0,0,0,0,23,255,255,255,29,0,255, + 255,255,36,0,0,0,0,0,138,255,255,238,2,0,255,255,255,154,136,136,136,141,196,255, + 255,255,140,0,0,255,255,255,255,255,255,255,255,255,255,255,197,10,0,0,255,255,255,255, + 255,255,255,255,245,205,112,6,0,0,0,0,0,0,0,25,127,200,240,254,239,195,118,17, + 0,0,0,0,0,0,0,0,110,245,255,255,255,255,255,255,255,237,83,0,0,0,0,0, + 0,144,255,255,255,229,161,139,163,232,255,255,254,94,0,0,0,0,100,255,255,254,119,4, + 0,0,0,8,143,255,255,249,39,0,0,15,238,255,255,115,0,0,0,0,0,0,0,166, + 255,255,164,0,0,107,255,255,214,2,0,0,0,0,0,0,0,40,255,255,245,7,0,182, + 255,255,119,0,0,0,0,0,0,0,0,0,156,168,168,27,0,231,255,255,65,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,252,255,255,41,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,251,255,255,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,228, + 255,255,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,179,255,255,126,0,0,0, + 0,0,0,0,0,0,145,160,160,33,0,101,255,255,220,4,0,0,0,0,0,0,0,39, + 255,255,247,11,0,11,233,255,255,126,0,0,0,0,0,0,0,170,255,255,169,0,0,0, + 90,255,255,255,129,6,0,0,0,9,148,255,255,250,41,0,0,0,0,134,255,255,255,232, + 163,140,162,234,255,255,255,100,0,0,0,0,0,0,104,243,255,255,255,255,255,255,255,239, + 88,0,0,0,0,0,0,0,0,20,124,198,239,254,239,196,121,19,0,0,0,0,0,255, + 255,255,255,255,255,255,244,213,160,81,4,0,0,0,0,0,0,255,255,255,255,255,255,255, + 255,255,255,255,218,67,0,0,0,0,0,255,255,255,154,136,136,138,154,200,254,255,255,254, + 110,0,0,0,0,255,255,255,36,0,0,0,0,0,36,183,255,255,255,81,0,0,0,255, + 255,255,36,0,0,0,0,0,0,2,165,255,255,233,12,0,0,255,255,255,36,0,0,0, + 0,0,0,0,13,231,255,255,105,0,0,255,255,255,36,0,0,0,0,0,0,0,0,133, + 255,255,183,0,0,255,255,255,36,0,0,0,0,0,0,0,0,68,255,255,231,0,0,255, + 255,255,36,0,0,0,0,0,0,0,0,42,255,255,252,0,0,255,255,255,36,0,0,0, + 0,0,0,0,0,45,255,255,251,0,0,255,255,255,36,0,0,0,0,0,0,0,0,77, + 255,255,227,0,0,255,255,255,36,0,0,0,0,0,0,0,0,146,255,255,176,0,0,255, + 255,255,36,0,0,0,0,0,0,0,20,240,255,255,95,0,0,255,255,255,36,0,0,0, + 0,0,0,5,181,255,255,224,8,0,0,255,255,255,36,0,0,0,0,0,42,194,255,255, + 252,67,0,0,0,255,255,255,154,136,136,137,154,204,255,255,255,251,94,0,0,0,0,255, + 255,255,255,255,255,255,255,255,255,255,213,58,0,0,0,0,0,255,255,255,255,255,255,255, + 245,215,160,78,3,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,154,136,136,136, + 136,136,136,136,136,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255,154, + 136,136,136,136,136,136,136,68,0,0,0,255,255,255,255,255,255,255,255,255,255,255,128,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,255,255,255,36,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255,154, + 136,136,136,136,136,136,136,136,68,0,0,255,255,255,255,255,255,255,255,255,255,255,255,128, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,255,255,255,255,255,255,255, + 255,255,255,255,255,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,255,255,255, + 154,136,136,136,136,136,136,136,136,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0, + 0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0, + 0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255, + 255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,154,136,136,136,136,136,136,136, + 0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0, + 0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255, + 36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0, + 0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,30,131,204,243,254,239,200,127,26,0,0,0,0,0,0, + 0,1,118,249,255,255,255,255,255,255,255,247,113,0,0,0,0,0,0,151,255,255,255,228, + 161,142,161,226,255,255,255,134,0,0,0,0,102,255,255,253,116,4,0,0,0,3,117,255, + 255,255,64,0,0,16,239,255,255,113,0,0,0,0,0,0,0,148,255,255,181,0,0,108, + 255,255,214,2,0,0,0,0,0,0,0,42,192,192,180,0,0,183,255,255,120,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,229,255,255,65,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,252,255,255,41,0,0,0,0,24,255,255,255,255,255,255,255,36,0,253, + 255,255,41,0,0,0,0,24,255,255,255,255,255,255,255,36,0,235,255,255,65,0,0,0, + 0,13,136,136,136,136,255,255,255,36,0,191,255,255,118,0,0,0,0,0,0,0,0,0, + 255,255,255,36,0,121,255,255,211,2,0,0,0,0,0,0,0,0,255,255,255,36,0,25, + 247,255,255,112,0,0,0,0,0,0,0,0,255,255,255,36,0,0,121,255,255,254,126,9, + 0,0,0,0,11,141,255,255,255,36,0,0,2,164,255,255,255,238,173,142,142,174,240,255, + 255,255,180,8,0,0,0,2,123,249,255,255,255,255,255,255,255,255,245,122,3,0,0,0, + 0,0,0,28,126,196,236,254,249,228,183,114,20,0,0,0,0,255,255,255,36,0,0,0, + 0,0,0,0,255,255,255,36,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255, + 36,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,255,255,255,36, + 0,0,0,0,0,0,0,255,255,255,36,0,0,255,255,255,36,0,0,0,0,0,0,0, + 255,255,255,36,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,255, + 255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,255,255,255,158,140,140,140,140, + 140,140,140,255,255,255,36,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,36, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,36,0,0,255,255,255,36,0, + 0,0,0,0,0,0,255,255,255,36,0,0,255,255,255,36,0,0,0,0,0,0,0,255, + 255,255,36,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,255,255, + 255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,255,255,255,36,0,0,0,0,0, + 0,0,255,255,255,36,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0, + 0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,255,255,255,36,0,0, + 0,0,0,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255, + 255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255, + 255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255, + 255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255, + 255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,0, + 0,0,0,0,255,255,255,36,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0, + 255,255,255,36,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,255,255,255,36, + 0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,255,255,255,36,0,0,0,0, + 0,0,255,255,255,36,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,255,255, + 255,36,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,255,255,255,36,0,0, + 0,0,0,0,255,255,255,36,0,0,0,0,0,15,255,255,255,31,1,0,0,0,0,119, + 255,255,248,6,124,198,160,159,203,255,255,255,167,0,128,255,255,255,255,255,255,220,27,0, + 80,208,242,254,243,203,124,14,0,0,0,255,255,255,36,0,0,0,0,0,29,227,255,255, + 245,58,0,0,255,255,255,36,0,0,0,0,19,215,255,255,248,69,0,0,0,255,255,255, + 36,0,0,0,10,199,255,255,252,78,0,0,0,0,255,255,255,36,0,0,5,183,255,255, + 254,91,0,0,0,0,0,255,255,255,36,0,1,163,255,255,255,103,0,0,0,0,0,0, + 255,255,255,36,0,141,255,255,255,116,0,0,0,0,0,0,0,255,255,255,36,118,255,255, + 255,130,0,0,0,0,0,0,0,0,255,255,255,132,255,255,255,142,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,155,1,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,158,2,0,0,0,0,0,0,0,0,0,255,255,255,126,254,255,255,158,2,0,0, + 0,0,0,0,0,0,255,255,255,36,100,255,255,255,158,2,0,0,0,0,0,0,0,255, + 255,255,36,0,109,255,255,255,158,2,0,0,0,0,0,0,255,255,255,36,0,0,120,255, + 255,255,158,2,0,0,0,0,0,255,255,255,36,0,0,0,131,255,255,255,158,2,0,0, + 0,0,255,255,255,36,0,0,0,0,141,255,255,255,158,2,0,0,0,255,255,255,36,0, + 0,0,0,1,151,255,255,255,158,2,0,0,255,255,255,36,0,0,0,0,0,2,161,255, + 255,255,158,3,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0, + 0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255, + 255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0, + 0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0, + 0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255, + 36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0, + 0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0, + 0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0, + 0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255, + 255,255,166,148,148,148,148,148,148,148,74,0,0,255,255,255,255,255,255,255,255,255,255,255, + 128,0,0,255,255,255,255,255,255,255,255,255,255,255,128,0,0,255,255,255,255,255,58,0, + 0,0,0,0,0,0,31,255,255,255,255,255,0,0,0,255,255,255,255,255,138,0,0,0, + 0,0,0,0,110,255,255,255,255,255,0,0,0,255,255,255,253,255,218,0,0,0,0,0, + 0,0,190,255,252,255,255,255,0,0,0,255,255,255,194,255,255,41,0,0,0,0,0,16, + 251,255,191,255,255,255,0,0,0,255,255,255,116,255,255,118,0,0,0,0,0,90,255,255, + 114,255,255,255,0,0,0,255,255,255,47,247,255,198,0,0,0,0,0,170,255,245,45,255, + 255,255,0,0,0,255,255,255,36,180,255,254,24,0,0,0,6,241,255,178,36,255,255,255, + 0,0,0,255,255,255,36,102,255,255,100,0,0,0,70,255,255,98,36,255,255,255,0,0, + 0,255,255,255,36,25,255,255,178,0,0,0,150,255,254,23,36,255,255,255,0,0,0,255, + 255,255,36,0,202,255,247,11,0,1,226,255,198,0,36,255,255,255,0,0,0,255,255,255, + 36,0,125,255,255,80,0,50,255,255,120,0,36,255,255,255,0,0,0,255,255,255,36,0, + 46,255,255,159,0,130,255,255,42,0,36,255,255,255,0,0,0,255,255,255,36,0,0,225, + 255,235,3,206,255,218,0,0,36,255,255,255,0,0,0,255,255,255,36,0,0,146,255,255, + 93,255,255,141,0,0,36,255,255,255,0,0,0,255,255,255,36,0,0,70,255,255,234,255, + 255,62,0,0,36,255,255,255,0,0,0,255,255,255,36,0,0,6,240,255,255,255,237,3, + 0,0,36,255,255,255,0,0,0,255,255,255,36,0,0,0,170,255,255,255,161,0,0,0, + 36,255,255,255,0,0,0,255,255,255,36,0,0,0,90,255,255,255,82,0,0,0,36,255, + 255,255,0,0,0,255,255,255,254,47,0,0,0,0,0,0,255,255,255,36,0,0,255,255, + 255,255,176,0,0,0,0,0,0,255,255,255,36,0,0,255,255,255,255,255,50,0,0,0, + 0,0,255,255,255,36,0,0,255,255,255,255,255,179,0,0,0,0,0,255,255,255,36,0, + 0,255,255,255,204,255,255,56,0,0,0,0,255,255,255,36,0,0,255,255,255,77,253,255, + 184,0,0,0,0,255,255,255,36,0,0,255,255,255,36,164,255,255,60,0,0,0,255,255, + 255,36,0,0,255,255,255,36,37,252,255,189,0,0,0,255,255,255,36,0,0,255,255,255, + 36,0,160,255,255,64,0,0,255,255,255,36,0,0,255,255,255,36,0,33,251,255,194,0, + 0,255,255,255,36,0,0,255,255,255,36,0,0,155,255,255,68,0,255,255,255,36,0,0, + 255,255,255,36,0,0,30,250,255,198,0,255,255,255,36,0,0,255,255,255,36,0,0,0, + 150,255,255,72,255,255,255,36,0,0,255,255,255,36,0,0,0,27,248,255,202,255,255,255, + 36,0,0,255,255,255,36,0,0,0,0,144,255,255,255,255,255,36,0,0,255,255,255,36, + 0,0,0,0,25,246,255,255,255,255,36,0,0,255,255,255,36,0,0,0,0,0,141,255, + 255,255,255,36,0,0,255,255,255,36,0,0,0,0,0,20,244,255,255,255,36,0,0,0, + 0,0,0,25,127,200,240,254,243,205,137,35,0,0,0,0,0,0,0,0,0,111,246,255, + 255,255,255,255,255,255,252,134,4,0,0,0,0,0,0,149,255,255,255,232,163,140,159,225, + 255,255,255,178,4,0,0,0,0,105,255,255,254,126,6,0,0,0,2,105,250,255,255,135, + 0,0,0,18,241,255,255,117,0,0,0,0,0,0,0,88,255,255,252,39,0,0,114,255, + 255,215,3,0,0,0,0,0,0,0,0,186,255,255,147,0,0,187,255,255,118,0,0,0, + 0,0,0,0,0,0,86,255,255,221,0,0,232,255,255,63,0,0,0,0,0,0,0,0, + 0,29,255,255,255,12,0,253,255,255,40,0,0,0,0,0,0,0,0,0,5,255,255,255, + 32,0,251,255,255,43,0,0,0,0,0,0,0,0,0,7,255,255,255,31,0,228,255,255, + 71,0,0,0,0,0,0,0,0,0,36,255,255,255,9,0,179,255,255,130,0,0,0,0, + 0,0,0,0,0,99,255,255,215,0,0,102,255,255,224,6,0,0,0,0,0,0,0,1, + 201,255,255,137,0,0,13,235,255,255,135,0,0,0,0,0,0,0,107,255,255,250,32,0, + 0,0,95,255,255,255,137,9,0,0,0,4,116,253,255,255,128,0,0,0,0,0,139,255, + 255,255,235,165,140,160,229,255,255,255,170,2,0,0,0,0,0,0,106,243,255,255,255,255, + 255,255,255,250,128,3,0,0,0,0,0,0,0,0,21,122,195,239,253,242,204,133,32,0, + 0,0,0,0,0,255,255,255,255,255,255,255,255,247,216,147,36,0,0,0,0,255,255,255, + 255,255,255,255,255,255,255,255,248,89,0,0,0,255,255,255,153,136,136,136,137,157,232,255, + 255,252,50,0,0,255,255,255,36,0,0,0,0,0,14,200,255,255,171,0,0,255,255,255, + 36,0,0,0,0,0,0,73,255,255,233,0,0,255,255,255,36,0,0,0,0,0,0,42, + 255,255,252,0,0,255,255,255,36,0,0,0,0,0,0,78,255,255,233,0,0,255,255,255, + 36,0,0,0,0,0,16,204,255,255,166,0,0,255,255,255,154,136,136,136,137,157,234,255, + 255,249,45,0,0,255,255,255,255,255,255,255,255,255,255,255,246,83,0,0,0,255,255,255, + 255,255,255,255,255,247,215,145,33,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 36,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 36,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,25,127,200,240,254,243,205,137,35,0,0,0,0,0,0, + 0,0,0,111,246,255,255,255,255,255,255,255,252,134,4,0,0,0,0,0,0,149,255,255, + 255,232,163,140,159,224,255,255,255,178,4,0,0,0,0,105,255,255,254,122,6,0,0,0, + 2,104,249,255,255,135,0,0,0,18,241,255,255,113,0,0,0,0,0,0,0,86,255,255, + 252,39,0,0,114,255,255,210,2,0,0,0,0,0,0,0,0,183,255,255,147,0,0,187, + 255,255,117,0,0,0,0,0,0,0,0,0,84,255,255,221,0,0,232,255,255,62,0,0, + 0,0,0,0,0,0,0,27,255,255,255,12,0,253,255,255,40,0,0,0,0,0,0,0, + 0,0,4,255,255,255,32,0,251,255,255,43,0,0,0,0,0,0,0,0,0,8,255,255, + 255,30,0,227,255,255,72,0,0,0,0,0,0,0,0,0,38,255,255,254,9,0,177,255, + 255,130,0,0,0,0,0,0,0,0,0,98,255,255,210,0,0,97,255,255,224,5,0,0, + 0,0,0,0,0,0,200,255,255,129,0,0,9,227,255,255,131,0,0,0,0,0,0,0, + 103,255,255,240,22,0,0,0,76,254,255,255,133,7,0,0,0,3,112,252,255,253,81,0, + 0,0,0,0,110,254,255,255,234,164,140,167,227,255,255,255,203,136,136,136,0,0,0,0, + 76,227,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,9,99,178,227, + 251,255,255,255,255,255,255,255,255,255,0,255,255,255,255,255,255,255,252,228,168,60,0,0, + 0,0,0,255,255,255,255,255,255,255,255,255,255,255,140,0,0,0,0,255,255,255,153,136, + 136,136,144,205,255,255,255,98,0,0,0,255,255,255,36,0,0,0,0,0,144,255,255,218, + 0,0,0,255,255,255,36,0,0,0,0,0,24,255,255,255,18,0,0,255,255,255,36,0, + 0,0,0,0,8,255,255,255,32,0,0,255,255,255,36,0,0,0,0,0,71,255,255,250, + 7,0,0,255,255,255,36,0,0,0,11,76,229,255,255,175,0,0,0,255,255,255,255,255, + 255,255,255,255,255,255,238,36,0,0,0,255,255,255,255,255,255,255,255,255,255,201,43,0, + 0,0,0,255,255,255,185,255,255,255,177,110,51,0,0,0,0,0,0,255,255,255,37,168, + 255,255,231,33,0,0,0,0,0,0,0,255,255,255,36,10,205,255,255,221,23,0,0,0, + 0,0,0,255,255,255,36,0,31,235,255,255,208,15,0,0,0,0,0,255,255,255,36,0, + 0,63,251,255,255,193,8,0,0,0,0,255,255,255,36,0,0,0,110,255,255,255,177,4, + 0,0,0,255,255,255,36,0,0,0,0,158,255,255,255,158,1,0,0,255,255,255,36,0, + 0,0,0,7,201,255,255,255,139,0,0,0,0,0,44,150,216,247,254,234,188,100,5,0, + 0,0,0,0,0,118,253,255,255,255,255,255,255,255,209,28,0,0,0,0,82,255,255,255, + 209,148,142,185,253,255,255,210,6,0,0,0,199,255,255,143,1,0,0,0,57,245,255,255, + 99,0,0,0,249,255,255,43,0,0,0,0,0,133,255,255,181,0,0,0,246,255,255,94, + 0,0,0,0,0,26,84,84,69,0,0,0,187,255,255,244,117,19,0,0,0,0,0,0, + 0,0,0,0,57,250,255,255,255,252,190,112,34,0,0,0,0,0,0,0,0,70,230,255, + 255,255,255,255,255,204,91,1,0,0,0,0,0,0,11,108,200,254,255,255,255,255,255,178, + 8,0,0,0,0,0,0,0,0,24,102,182,249,255,255,255,139,0,0,0,0,0,0,0, + 0,0,0,0,25,188,255,255,240,3,0,170,220,220,76,0,0,0,0,0,0,32,255,255, + 255,30,0,140,255,255,183,0,0,0,0,0,0,12,255,255,255,26,0,41,253,255,255,141, + 7,0,0,0,0,123,255,255,231,1,0,0,130,255,255,255,235,166,139,150,206,255,255,255, + 120,0,0,0,1,125,251,255,255,255,255,255,255,255,255,154,2,0,0,0,0,0,36,137, + 204,241,254,248,220,161,62,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,136,136,136,136,136, + 153,255,255,255,136,136,136,136,136,136,0,0,0,0,0,0,36,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 36,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 36,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 36,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 36,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 36,255,255,255,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255, + 255,36,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,255,255,255, + 36,0,0,0,0,0,0,0,255,255,255,36,0,0,255,255,255,36,0,0,0,0,0,0, + 0,255,255,255,36,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0, + 255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,255,255,255,36,0,0,0, + 0,0,0,0,255,255,255,36,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255, + 36,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,255,255,255,36, + 0,0,0,0,0,0,0,255,255,255,36,0,0,254,255,255,36,0,0,0,0,0,0,0, + 255,255,255,32,0,0,238,255,255,47,0,0,0,0,0,0,11,255,255,255,17,0,0,200, + 255,255,89,0,0,0,0,0,0,52,255,255,236,0,0,0,136,255,255,179,0,0,0,0, + 0,0,142,255,255,172,0,0,0,39,253,255,255,113,0,0,0,0,80,251,255,255,71,0, + 0,0,0,141,255,255,255,210,147,143,195,255,255,255,175,0,0,0,0,0,4,156,255,255, + 255,255,255,255,255,255,181,11,0,0,0,0,0,0,0,65,166,223,250,252,227,174,79,1, + 0,0,0,0,39,255,255,254,27,0,0,0,0,0,0,0,0,87,255,255,191,0,0,205, + 255,255,111,0,0,0,0,0,0,0,0,172,255,255,101,0,0,114,255,255,196,0,0,0, + 0,0,0,0,11,246,255,249,17,0,0,26,253,255,254,27,0,0,0,0,0,0,85,255, + 255,177,0,0,0,0,189,255,255,109,0,0,0,0,0,0,171,255,255,86,0,0,0,0, + 99,255,255,195,0,0,0,0,0,11,245,255,242,9,0,0,0,0,16,249,255,254,25,0, + 0,0,0,85,255,255,162,0,0,0,0,0,0,174,255,255,108,0,0,0,0,171,255,255, + 71,0,0,0,0,0,0,83,255,255,193,0,0,0,10,245,255,233,4,0,0,0,0,0, + 0,8,241,255,253,24,0,0,84,255,255,147,0,0,0,0,0,0,0,0,158,255,255,107, + 0,0,169,255,255,57,0,0,0,0,0,0,0,0,69,255,255,191,0,10,245,255,221,1, + 0,0,0,0,0,0,0,0,3,231,255,253,23,84,255,255,133,0,0,0,0,0,0,0, + 0,0,0,143,255,255,105,169,255,255,42,0,0,0,0,0,0,0,0,0,0,53,255,255, + 200,244,255,207,0,0,0,0,0,0,0,0,0,0,0,0,218,255,255,255,255,118,0,0, + 0,0,0,0,0,0,0,0,0,0,127,255,255,255,254,29,0,0,0,0,0,0,0,0, + 0,0,0,0,38,255,255,255,193,0,0,0,0,0,0,0,2,235,255,255,60,0,0,0, + 0,0,0,202,255,255,253,13,0,0,0,0,0,8,248,255,250,11,0,167,255,255,126,0, + 0,0,0,0,11,251,255,255,255,69,0,0,0,0,0,66,255,255,190,0,0,95,255,255, + 192,0,0,0,0,0,68,255,255,255,255,130,0,0,0,0,0,132,255,255,119,0,0,25, + 255,255,248,8,0,0,0,0,128,255,252,235,255,190,0,0,0,0,0,196,255,255,49,0, + 0,0,210,255,255,66,0,0,0,0,188,255,201,167,255,246,5,0,0,0,12,250,255,233, + 1,0,0,0,140,255,255,132,0,0,0,5,245,255,138,106,255,255,54,0,0,0,71,255, + 255,163,0,0,0,0,69,255,255,196,0,0,0,53,255,255,76,44,255,255,114,0,0,0, + 137,255,255,93,0,0,0,0,8,246,255,250,12,0,0,114,255,253,15,1,236,255,174,0, + 0,0,203,255,255,23,0,0,0,0,0,184,255,255,71,0,0,174,255,204,0,0,176,255, + 234,1,0,15,253,255,207,0,0,0,0,0,0,113,255,255,137,0,1,235,255,140,0,0, + 112,255,255,38,0,76,255,255,137,0,0,0,0,0,0,42,255,255,203,0,39,255,255,78, + 0,0,52,255,255,98,0,142,255,255,66,0,0,0,0,0,0,0,227,255,253,15,100,255, + 254,18,0,0,3,241,255,158,0,208,255,245,6,0,0,0,0,0,0,0,157,255,255,76, + 160,255,208,0,0,0,0,182,255,218,18,254,255,181,0,0,0,0,0,0,0,0,86,255, + 255,142,221,255,144,0,0,0,0,119,255,255,104,255,255,110,0,0,0,0,0,0,0,0, + 19,253,255,227,255,255,80,0,0,0,0,59,255,255,224,255,255,40,0,0,0,0,0,0, + 0,0,0,201,255,255,255,255,18,0,0,0,0,6,246,255,255,255,225,0,0,0,0,0, + 0,0,0,0,0,130,255,255,255,212,0,0,0,0,0,0,189,255,255,255,153,0,0,0, + 0,0,0,0,0,0,0,60,255,255,255,148,0,0,0,0,0,0,128,255,255,255,83,0, + 0,0,0,0,0,155,255,255,238,22,0,0,0,0,0,0,80,255,255,229,19,0,16,229, + 255,255,163,0,0,0,0,0,16,229,255,255,72,0,0,0,75,255,255,255,68,0,0,0, + 0,155,255,255,151,0,0,0,0,0,162,255,255,219,8,0,0,67,255,255,219,11,0,0, + 0,0,0,19,232,255,255,131,0,11,222,255,253,58,0,0,0,0,0,0,0,82,255,255, + 250,41,143,255,255,134,0,0,0,0,0,0,0,0,0,169,255,255,213,253,255,208,6,0, + 0,0,0,0,0,0,0,0,22,236,255,255,255,249,45,0,0,0,0,0,0,0,0,0, + 0,0,94,255,255,255,123,0,0,0,0,0,0,0,0,0,0,0,0,134,255,255,255,181, + 0,0,0,0,0,0,0,0,0,0,0,57,253,255,255,255,255,91,0,0,0,0,0,0, + 0,0,0,11,219,255,250,150,255,255,237,24,0,0,0,0,0,0,0,0,150,255,255,130, + 2,200,255,255,174,0,0,0,0,0,0,0,72,255,255,212,6,0,45,251,255,255,88,0, + 0,0,0,0,19,229,255,253,56,0,0,0,136,255,255,235,21,0,0,0,0,166,255,255, + 144,0,0,0,0,10,222,255,255,169,0,0,0,87,255,255,222,11,0,0,0,0,0,70, + 255,255,255,82,0,27,237,255,255,68,0,0,0,0,0,0,0,165,255,255,233,19,48,252, + 255,255,146,0,0,0,0,0,0,0,36,250,255,255,80,0,142,255,255,251,40,0,0,0, + 0,0,0,175,255,255,176,0,0,13,227,255,255,184,0,0,0,0,0,66,255,255,245,31, + 0,0,0,80,255,255,255,74,0,0,0,3,209,255,255,115,0,0,0,0,0,176,255,255, + 216,5,0,0,102,255,255,208,4,0,0,0,0,0,29,243,255,255,111,0,15,234,255,254, + 56,0,0,0,0,0,0,0,113,255,255,239,19,139,255,255,150,0,0,0,0,0,0,0, + 0,4,208,255,255,178,250,255,232,16,0,0,0,0,0,0,0,0,0,54,254,255,255,255, + 255,88,0,0,0,0,0,0,0,0,0,0,0,148,255,255,255,184,0,0,0,0,0,0, + 0,0,0,0,0,0,15,255,255,255,51,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,255,255,255,255, + 255,255,255,255,255,255,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,136, + 136,136,136,136,136,136,136,165,255,255,255,202,0,0,0,0,0,0,0,0,0,1,187,255, + 255,246,39,0,0,0,0,0,0,0,0,0,104,255,255,255,108,0,0,0,0,0,0,0, + 0,0,33,244,255,255,185,1,0,0,0,0,0,0,0,0,2,192,255,255,238,27,0,0, + 0,0,0,0,0,0,0,109,255,255,255,90,0,0,0,0,0,0,0,0,0,37,245,255, + 255,166,0,0,0,0,0,0,0,0,0,2,195,255,255,229,18,0,0,0,0,0,0,0, + 0,0,112,255,255,255,70,0,0,0,0,0,0,0,0,0,40,247,255,255,147,0,0,0, + 0,0,0,0,0,0,3,200,255,255,217,10,0,0,0,0,0,0,0,0,0,117,255,255, + 252,54,0,0,0,0,0,0,0,0,0,44,249,255,255,129,0,0,0,0,0,0,0,0, + 0,0,204,255,255,251,140,136,136,136,136,136,136,136,136,0,0,255,255,255,255,255,255,255, + 255,255,255,255,255,255,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0, + 136,136,136,136,136,136,136,68,0,0,0,255,255,255,255,255,255,255,128,0,0,0,255,255, + 255,255,255,255,255,128,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36, + 0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0, + 0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0, + 0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0, + 0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255, + 255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255, + 36,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0, + 0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,153,136,136,136, + 68,0,0,0,255,255,255,255,255,255,255,128,0,0,0,255,255,255,255,255,255,255,128,0, + 6,240,255,174,0,0,0,0,0,0,0,0,170,255,243,7,0,0,0,0,0,0,0,93, + 255,255,71,0,0,0,0,0,0,0,20,253,255,147,0,0,0,0,0,0,0,0,197,255, + 224,0,0,0,0,0,0,0,0,121,255,255,44,0,0,0,0,0,0,0,44,255,255,121, + 0,0,0,0,0,0,0,0,223,255,197,0,0,0,0,0,0,0,0,147,255,253,20,0, + 0,0,0,0,0,0,70,255,255,94,0,0,0,0,0,0,0,7,243,255,170,0,0,0, + 0,0,0,0,0,174,255,240,6,0,0,0,0,0,0,0,97,255,255,67,0,0,0,0, + 0,0,0,23,254,255,143,0,0,0,0,0,0,0,0,200,255,218,0,0,0,0,0,0, + 0,0,125,255,255,40,0,0,0,0,0,0,0,48,255,255,116,0,0,0,0,0,0,0, + 1,226,255,193,0,0,0,0,0,0,0,0,151,255,252,17,0,0,0,0,0,0,0,76, + 255,255,90,0,0,0,0,0,0,0,9,245,255,166,0,0,68,136,136,136,136,136,136,136, + 19,0,0,128,255,255,255,255,255,255,255,36,0,0,128,255,255,255,255,255,255,255,36,0, + 0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,0, + 0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0, + 0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0, + 255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255, + 255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36, + 0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0, + 0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0, + 0,0,0,255,255,255,36,0,0,68,136,136,136,136,255,255,255,36,0,0,128,255,255,255, + 255,255,255,255,36,0,0,128,255,255,255,255,255,255,255,36,0,0,0,0,0,29,243,255, + 250,40,0,0,0,0,0,0,0,0,171,255,255,255,191,1,0,0,0,0,0,0,72,255, + 255,154,255,255,92,0,0,0,0,0,8,220,255,167,0,148,255,232,16,0,0,0,0,128, + 255,227,17,0,9,214,255,149,0,0,0,37,249,254,65,0,0,0,50,250,253,51,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,136,136, + 136,136,136,136,136,136,136,136,136,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,0,43,233,255,255,222,10,0,0,0,0,45,235,255,255,136,0, + 0,0,0,0,48,238,255,251,45,0,0,0,0,0,50,238,255,198,2,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,52,165,227,252,249,217,141,21,0,0,0, + 0,102,253,255,255,255,255,255,255,225,26,0,0,41,252,255,255,196,140,163,248,255,255,154, + 0,0,132,255,255,161,0,0,0,97,255,255,228,0,0,7,12,12,5,0,0,0,39,255, + 255,254,0,0,0,0,2,56,113,154,194,235,255,255,255,0,0,0,83,222,255,255,255,255, + 252,255,255,255,0,0,92,255,255,236,128,72,32,38,255,255,255,0,0,216,255,255,70,0, + 0,0,52,255,255,255,0,0,253,255,255,61,0,0,4,165,255,255,255,0,0,224,255,255, + 233,148,150,218,255,255,255,255,1,0,105,255,255,255,255,255,255,145,255,255,255,18,0,0, + 88,203,248,249,201,90,2,243,255,255,60,0,0,255,255,255,36,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,67,191,244,249, + 200,86,0,0,0,0,0,255,255,255,115,253,255,255,255,255,255,128,0,0,0,0,255,255, + 255,243,240,155,147,227,255,255,255,58,0,0,0,255,255,255,236,33,0,0,16,213,255,255, + 171,0,0,0,255,255,255,122,0,0,0,0,81,255,255,241,1,0,0,255,255,255,56,0, + 0,0,0,20,255,255,255,22,0,0,255,255,255,38,0,0,0,0,2,255,255,255,34,0, + 0,255,255,255,56,0,0,0,0,20,255,255,255,20,0,0,255,255,255,120,0,0,0,0, + 83,255,255,238,0,0,0,255,255,255,236,33,0,0,16,213,255,255,164,0,0,0,255,255, + 255,251,240,156,147,227,255,255,252,47,0,0,0,255,255,255,137,255,255,255,255,255,255,110, + 0,0,0,0,255,255,255,36,88,204,250,244,190,70,0,0,0,0,0,0,4,103,196,243, + 252,225,156,39,0,0,0,0,0,21,199,255,255,255,255,255,255,249,93,0,0,0,3,195, + 255,255,246,165,141,198,255,255,252,54,0,0,94,255,255,241,45,0,0,0,130,255,255,180, + 0,0,188,255,255,127,0,0,0,0,5,160,164,155,0,0,238,255,255,58,0,0,0,0, + 0,0,0,0,0,0,254,255,255,38,0,0,0,0,0,0,0,0,0,0,237,255,255,58, + 0,0,0,0,0,0,0,0,0,0,185,255,255,129,0,0,0,0,5,160,164,155,0,0, + 91,255,255,242,46,0,0,0,129,255,255,179,0,0,2,193,255,255,247,167,141,198,255,255, + 251,51,0,0,0,21,199,255,255,255,255,255,255,248,89,0,0,0,0,0,4,103,196,244, + 253,225,155,39,0,0,0,0,0,0,0,0,0,0,0,0,0,36,255,255,255,0,0,0, + 0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0,0,0,0,0,0,0,0,36, + 255,255,255,0,0,0,0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0,0,0, + 0,0,0,0,0,36,255,255,255,0,0,0,0,26,146,222,252,240,186,65,36,255,255,255, + 0,0,0,51,236,255,255,255,255,255,253,120,255,255,255,0,0,13,228,255,255,251,174,138, + 174,251,249,255,255,255,0,0,115,255,255,247,57,0,0,0,54,245,255,255,255,0,0,195, + 255,255,133,0,0,0,0,0,131,255,255,255,0,0,239,255,255,62,0,0,0,0,0,59, + 255,255,255,0,0,254,255,255,40,0,0,0,0,0,41,255,255,255,0,0,242,255,255,59, + 0,0,0,0,0,62,255,255,255,0,0,202,255,255,131,0,0,0,0,0,132,255,255,255, + 0,0,127,255,255,245,54,0,0,0,54,245,255,255,255,0,0,19,237,255,255,251,175,138, + 173,251,249,255,255,255,0,0,0,65,244,255,255,255,255,255,251,117,255,255,255,0,0,0, + 0,35,156,229,252,237,176,54,36,255,255,255,0,0,0,0,7,110,198,243,252,226,155,37, + 0,0,0,0,0,26,209,255,255,255,255,255,255,246,79,0,0,0,4,203,255,255,243,160, + 143,205,255,255,247,37,0,0,101,255,255,237,36,0,0,1,160,255,255,161,0,0,191,255, + 255,122,0,0,0,0,38,255,255,239,1,0,238,255,255,255,255,255,255,255,255,255,255,255, + 25,0,254,255,255,255,255,255,255,255,255,255,255,255,33,0,238,255,255,168,136,136,136,136, + 136,136,136,136,10,0,188,255,255,118,0,0,0,0,0,0,0,0,0,0,96,255,255,236, + 35,0,0,0,101,228,228,142,0,0,3,200,255,255,243,162,140,185,255,255,249,42,0,0, + 0,23,203,255,255,255,255,255,255,250,87,0,0,0,0,0,5,104,196,242,253,230,165,47, + 0,0,0,0,0,0,0,54,179,238,254,231,0,0,0,57,249,255,255,255,255,0,0,0, + 185,255,255,242,154,146,0,0,0,242,255,255,85,0,0,0,0,0,255,255,255,37,0,0, + 0,255,255,255,255,255,255,255,128,0,255,255,255,255,255,255,255,128,0,136,136,255,255,255, + 154,136,68,0,0,0,255,255,255,36,0,0,0,0,0,255,255,255,36,0,0,0,0,0, + 255,255,255,36,0,0,0,0,0,255,255,255,36,0,0,0,0,0,255,255,255,36,0,0, + 0,0,0,255,255,255,36,0,0,0,0,0,255,255,255,36,0,0,0,0,0,255,255,255, + 36,0,0,0,0,0,255,255,255,36,0,0,0,0,0,255,255,255,36,0,0,0,0,0, + 22,140,221,250,242,187,67,36,255,255,255,0,0,0,46,232,255,255,255,255,255,254,122,255, + 255,255,0,0,11,224,255,255,251,174,138,174,251,249,255,255,255,0,0,113,255,255,247,57, + 0,0,0,54,245,255,255,255,0,0,195,255,255,133,0,0,0,0,0,131,255,255,255,0, + 0,239,255,255,62,0,0,0,0,0,59,255,255,255,0,0,254,255,255,40,0,0,0,0, + 0,41,255,255,255,0,0,242,255,255,59,0,0,0,0,0,62,255,255,255,0,0,200,255, + 255,131,0,0,0,0,0,132,255,255,255,0,0,123,255,255,245,54,0,0,0,54,245,255, + 255,255,0,0,17,234,255,255,251,175,138,173,251,244,255,255,255,0,0,0,61,242,255,255, + 255,255,255,249,106,255,255,255,0,0,0,0,33,157,229,252,236,174,49,39,255,255,251,0, + 0,0,0,0,0,0,0,0,0,0,76,255,255,220,0,0,86,192,192,118,0,0,0,0, + 16,203,255,255,146,0,0,30,245,255,255,207,149,139,165,238,255,255,238,27,0,0,0,75, + 242,255,255,255,255,255,255,255,231,54,0,0,0,0,0,28,135,206,244,254,242,200,121,16, + 0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,36,24,148,225,252,239,174,43,0,0,0,0,255, + 255,255,79,233,255,255,255,255,255,242,42,0,0,0,255,255,255,237,252,177,141,178,255,255, + 255,174,0,0,0,255,255,255,242,55,0,0,0,128,255,255,239,0,0,0,255,255,255,119, + 0,0,0,0,46,255,255,255,0,0,0,255,255,255,57,0,0,0,0,36,255,255,255,0, + 0,0,255,255,255,36,0,0,0,0,36,255,255,255,0,0,0,255,255,255,36,0,0,0, + 0,36,255,255,255,0,0,0,255,255,255,36,0,0,0,0,36,255,255,255,0,0,0,255, + 255,255,36,0,0,0,0,36,255,255,255,0,0,0,255,255,255,36,0,0,0,0,36,255, + 255,255,0,0,0,255,255,255,36,0,0,0,0,36,255,255,255,0,0,0,255,255,255,36, + 0,0,0,0,36,255,255,255,0,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255, + 255,255,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,255, + 255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255, + 255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255, + 255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,0, + 255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0, + 0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255, + 255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0, + 0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,255, + 255,255,36,0,0,0,255,255,255,36,0,0,17,255,255,255,30,76,133,200,255,255,241,4, + 128,255,255,255,255,127,0,109,248,250,213,114,2,0,0,0,255,255,255,36,0,0,0,0, + 0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36, + 0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0, + 255,255,255,36,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,148,255,255, + 232,35,0,0,255,255,255,36,0,0,116,255,255,241,48,0,0,0,255,255,255,36,0,83, + 254,255,248,64,0,0,0,0,255,255,255,36,56,247,255,253,81,0,0,0,0,0,255,255, + 255,70,235,255,255,101,0,0,0,0,0,0,255,255,255,237,255,255,123,0,0,0,0,0, + 0,0,255,255,255,255,255,255,52,0,0,0,0,0,0,0,255,255,255,175,255,255,230,30, + 0,0,0,0,0,0,255,255,255,39,181,255,255,211,13,0,0,0,0,0,255,255,255,36, + 13,213,255,255,184,4,0,0,0,0,255,255,255,36,0,34,237,255,255,150,0,0,0,0, + 255,255,255,36,0,0,63,251,255,255,115,0,0,0,255,255,255,36,0,0,0,102,255,255, + 254,79,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255, + 255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255, + 255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255, + 255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255, + 255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,36,168,237,251, + 210,92,0,0,63,186,242,250,206,84,0,0,0,0,0,255,255,255,87,240,255,255,255,255, + 255,98,103,254,255,255,255,255,255,87,0,0,0,0,255,255,255,240,239,157,151,239,255,255, + 237,250,239,156,151,239,255,255,221,0,0,0,0,255,255,255,229,29,0,0,68,255,255,255, + 227,28,0,0,68,255,255,255,23,0,0,0,255,255,255,104,0,0,0,5,255,255,255,103, + 0,0,0,5,255,255,255,36,0,0,0,255,255,255,54,0,0,0,0,255,255,255,56,0, + 0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,0,255,255,255,36,0,0, + 0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,0,255,255,255,36,0,0,0, + 0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,0,255,255,255,36,0,0,0,0, + 255,255,255,36,0,0,0,255,255,255,36,0,0,0,0,255,255,255,36,0,0,0,0,255, + 255,255,36,0,0,0,255,255,255,36,0,0,0,0,255,255,255,36,0,0,0,0,255,255, + 255,36,0,0,0,255,255,255,36,0,0,0,0,255,255,255,36,0,0,0,0,255,255,255, + 36,0,0,0,255,255,255,36,0,0,0,0,255,255,255,36,0,0,0,0,255,255,255,36, + 0,0,0,255,255,255,36,24,148,225,252,239,174,43,0,0,0,0,255,255,255,79,233,255, + 255,255,255,255,242,42,0,0,0,255,255,255,237,252,177,141,178,255,255,255,174,0,0,0, + 255,255,255,242,55,0,0,0,128,255,255,239,0,0,0,255,255,255,119,0,0,0,0,46, + 255,255,255,0,0,0,255,255,255,57,0,0,0,0,36,255,255,255,0,0,0,255,255,255, + 36,0,0,0,0,36,255,255,255,0,0,0,255,255,255,36,0,0,0,0,36,255,255,255, + 0,0,0,255,255,255,36,0,0,0,0,36,255,255,255,0,0,0,255,255,255,36,0,0, + 0,0,36,255,255,255,0,0,0,255,255,255,36,0,0,0,0,36,255,255,255,0,0,0, + 255,255,255,36,0,0,0,0,36,255,255,255,0,0,0,255,255,255,36,0,0,0,0,36, + 255,255,255,0,0,0,0,3,93,188,238,254,238,188,95,3,0,0,0,0,21,196,255,255, + 255,255,255,255,255,197,21,0,0,3,199,255,255,253,178,138,177,252,255,255,199,3,0,97, + 255,255,248,62,0,0,0,58,247,255,255,97,0,191,255,255,133,0,0,0,0,0,132,255, + 255,188,0,238,255,255,58,0,0,0,0,0,58,255,255,238,0,253,255,255,39,0,0,0, + 0,0,39,255,255,252,0,236,255,255,64,0,0,0,0,0,64,255,255,236,0,185,255,255, + 145,0,0,0,0,0,142,255,255,184,0,90,255,255,251,70,0,0,0,67,249,255,255,88, + 0,2,191,255,255,254,179,141,178,253,255,255,187,1,0,0,18,191,255,255,255,255,255,255, + 255,189,16,0,0,0,0,2,91,185,238,254,238,186,91,2,0,0,0,0,255,255,255,36, + 80,196,245,247,198,83,0,0,0,0,0,255,255,255,143,255,255,255,255,255,255,126,0,0, + 0,0,255,255,255,255,240,155,147,227,255,255,255,57,0,0,0,255,255,255,236,33,0,0, + 16,213,255,255,171,0,0,0,255,255,255,122,0,0,0,0,81,255,255,241,1,0,0,255, + 255,255,56,0,0,0,0,20,255,255,255,22,0,0,255,255,255,38,0,0,0,0,2,255, + 255,255,34,0,0,255,255,255,56,0,0,0,0,20,255,255,255,19,0,0,255,255,255,120, + 0,0,0,0,83,255,255,236,0,0,0,255,255,255,236,33,0,0,16,213,255,255,164,0, + 0,0,255,255,255,252,240,156,147,227,255,255,252,47,0,0,0,255,255,255,142,255,255,255, + 255,255,255,108,0,0,0,0,255,255,255,36,93,207,250,244,188,67,0,0,0,0,0,255, + 255,255,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255,36, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,0,0,0, + 0,0,0,23,142,221,250,244,191,75,36,255,255,255,0,0,0,49,234,255,255,255,255,255, + 255,139,255,255,255,0,0,12,225,255,255,250,173,138,174,251,254,255,255,255,0,0,114,255, + 255,245,53,0,0,0,53,244,255,255,255,0,0,195,255,255,131,0,0,0,0,0,130,255, + 255,255,0,0,239,255,255,60,0,0,0,0,0,59,255,255,255,0,0,254,255,255,40,0, + 0,0,0,0,41,255,255,255,0,0,242,255,255,58,0,0,0,0,0,60,255,255,255,0, + 0,202,255,255,128,0,0,0,0,0,131,255,255,255,0,0,125,255,255,244,51,0,0,0, + 52,245,255,255,255,0,0,18,234,255,255,250,173,138,171,250,255,255,255,255,0,0,0,62, + 242,255,255,255,255,255,254,136,255,255,255,0,0,0,0,32,153,226,252,238,182,64,36,255, + 255,255,0,0,0,0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0,0,0,0, + 0,0,0,0,36,255,255,255,0,0,0,0,0,0,0,0,0,0,0,36,255,255,255,0, + 0,0,0,0,0,0,0,0,0,0,36,255,255,255,0,0,0,0,0,0,0,0,0,0, + 0,36,255,255,255,0,0,0,255,255,255,36,79,204,250,128,0,0,255,255,255,126,255,255, + 255,128,0,0,255,255,255,249,243,168,139,72,0,0,255,255,255,218,24,0,0,0,0,0, + 255,255,255,94,0,0,0,0,0,0,255,255,255,57,0,0,0,0,0,0,255,255,255,36, + 0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,255,255,255,36,0,0,0,0, + 0,0,255,255,255,36,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,255,255, + 255,36,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,49,168,229,252,245,202, + 110,5,0,0,0,73,251,255,255,255,255,255,255,187,5,0,0,211,255,255,223,144,163,250, + 255,255,107,0,0,252,255,255,53,0,0,110,240,240,170,0,0,224,255,255,133,5,0,0, + 0,0,0,0,0,105,255,255,255,237,172,107,29,0,0,0,0,0,96,227,255,255,255,255, + 252,141,2,0,0,0,0,2,53,121,189,253,255,255,121,0,44,84,84,48,0,0,0,83, + 255,255,226,0,102,255,255,210,7,0,0,63,255,255,252,0,22,243,255,255,217,146,151,234, + 255,255,208,0,0,83,249,255,255,255,255,255,255,250,69,0,0,0,41,156,222,251,252,226, + 164,47,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,24,116,212,36,0,0,0, + 0,0,0,252,255,255,36,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,255, + 255,255,36,0,0,0,128,255,255,255,255,255,255,255,255,128,128,255,255,255,255,255,255,255, + 255,128,68,136,136,255,255,255,154,136,136,68,0,0,0,255,255,255,36,0,0,0,0,0, + 0,255,255,255,36,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,255,255,255, + 36,0,0,0,0,0,0,255,255,255,36,0,0,0,0,0,0,255,255,255,37,0,0,0, + 0,0,0,239,255,255,82,0,0,0,0,0,0,183,255,255,242,155,142,88,0,0,0,58, + 251,255,255,255,255,128,0,0,0,0,60,184,239,254,238,98,0,255,255,255,36,0,0,0, + 0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,0,255,255,255,36,0,0,0,255, + 255,255,36,0,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,0,255,255, + 255,36,0,0,0,255,255,255,36,0,0,0,0,255,255,255,36,0,0,0,255,255,255,36, + 0,0,0,0,255,255,255,36,0,0,0,255,255,255,36,0,0,0,0,255,255,255,36,0, + 0,0,255,255,255,36,0,0,0,14,255,255,255,36,0,0,0,255,255,255,46,0,0,0, + 79,255,255,255,36,0,0,0,239,255,255,122,0,0,20,215,255,255,255,36,0,0,0,175, + 255,255,253,175,159,235,240,255,255,255,36,0,0,0,48,247,255,255,255,255,251,80,255,255, + 255,36,0,0,0,0,54,192,248,244,185,56,0,255,255,255,36,0,0,176,255,255,142,0, + 0,0,0,0,146,255,255,148,80,255,255,225,1,0,0,0,1,229,255,255,52,5,235,255, + 255,53,0,0,0,57,255,255,209,0,0,144,255,255,136,0,0,0,141,255,255,112,0,0, + 47,255,255,220,0,0,1,224,255,251,21,0,0,0,208,255,255,47,0,51,255,255,175,0, + 0,0,0,111,255,255,132,0,136,255,255,77,0,0,0,0,20,249,255,215,0,220,255,232, + 4,0,0,0,0,0,172,255,255,90,255,255,140,0,0,0,0,0,0,76,255,255,236,255, + 255,44,0,0,0,0,0,0,4,232,255,255,255,203,0,0,0,0,0,0,0,0,140,255, + 255,255,105,0,0,0,0,0,0,0,0,44,255,255,248,16,0,0,0,0,120,255,255,168, + 0,0,0,0,227,255,255,40,0,0,0,101,255,255,171,46,255,255,227,0,0,0,42,255, + 255,255,112,0,0,0,158,255,255,98,1,227,255,255,30,0,0,113,255,255,255,185,0,0, + 0,216,255,255,25,0,154,255,255,88,0,0,185,255,252,255,248,10,0,18,255,255,207,0, + 0,81,255,255,146,0,8,247,255,146,255,255,75,0,74,255,255,133,0,0,13,250,255,204, + 0,70,255,255,27,230,255,146,0,132,255,255,60,0,0,0,190,255,251,10,141,255,208,0, + 158,255,220,0,189,255,239,3,0,0,0,116,255,255,65,212,255,135,0,85,255,255,38,243, + 255,168,0,0,0,0,43,255,255,150,255,255,62,0,16,252,255,156,255,255,95,0,0,0, + 0,0,226,255,249,255,240,4,0,0,195,255,251,255,254,23,0,0,0,0,0,152,255,255, + 255,172,0,0,0,122,255,255,255,204,0,0,0,0,0,0,79,255,255,255,98,0,0,0, + 50,255,255,255,130,0,0,0,0,0,0,12,249,255,255,25,0,0,0,1,231,255,255,57, + 0,0,0,0,127,255,255,221,10,0,0,0,109,255,255,219,12,0,3,197,255,255,140,0, + 0,33,245,255,251,52,0,0,0,34,242,255,253,52,1,191,255,255,120,0,0,0,0,0, + 95,255,255,210,109,255,255,190,2,0,0,0,0,0,0,171,255,255,255,255,237,28,0,0, + 0,0,0,0,0,18,228,255,255,255,82,0,0,0,0,0,0,0,0,0,174,255,255,230, + 8,0,0,0,0,0,0,0,0,93,255,255,255,255,144,0,0,0,0,0,0,0,36,244, + 255,240,225,255,255,68,0,0,0,0,0,6,204,255,255,94,67,255,255,229,19,0,0,0, + 0,139,255,255,176,0,0,156,255,255,173,0,0,0,70,254,255,235,23,0,0,16,229,255, + 255,97,0,23,233,255,255,82,0,0,0,0,80,255,255,243,34,173,255,255,150,0,0,0, + 0,0,162,255,255,144,74,255,255,237,5,0,0,0,10,244,255,255,41,3,227,255,255,79, + 0,0,0,91,255,255,192,0,0,130,255,255,171,0,0,0,183,255,255,88,0,0,32,254, + 255,248,15,0,23,252,255,234,6,0,0,0,186,255,255,100,0,110,255,255,135,0,0,0, + 0,84,255,255,192,0,203,255,254,34,0,0,0,0,7,235,255,254,69,255,255,185,0,0, + 0,0,0,0,141,255,255,230,255,255,80,0,0,0,0,0,0,41,255,255,255,255,228,4, + 0,0,0,0,0,0,0,197,255,255,255,128,0,0,0,0,0,0,0,0,98,255,255,252, + 28,0,0,0,0,0,0,0,0,82,255,255,176,0,0,0,0,0,0,0,0,0,182,255, + 255,71,0,0,0,0,0,0,0,0,36,254,255,222,2,0,0,0,0,0,0,0,0,143, + 255,255,121,0,0,0,0,0,0,0,0,12,241,255,250,22,0,0,0,0,0,0,0,0, + 106,255,255,168,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,0,0, + 255,255,255,255,255,255,255,255,255,255,0,0,136,136,136,136,136,147,255,255,255,206,0,0, + 0,0,0,0,0,160,255,255,243,38,0,0,0,0,0,0,95,255,255,255,90,0,0,0, + 0,0,0,40,244,255,255,156,0,0,0,0,0,0,9,211,255,255,212,9,0,0,0,0, + 0,0,155,255,255,245,43,0,0,0,0,0,0,89,255,255,255,99,0,0,0,0,0,0, + 37,243,255,255,164,0,0,0,0,0,0,0,206,255,255,255,148,136,136,136,136,136,0,0, + 255,255,255,255,255,255,255,255,255,255,0,0,255,255,255,255,255,255,255,255,255,255,0,0, + 0,0,0,5,79,124,136,136,68,0,0,0,0,28,215,255,255,255,255,128,0,0,0,0, + 167,255,255,255,255,255,128,0,0,0,0,230,255,255,73,2,0,0,0,0,0,0,226,255, + 238,0,0,0,0,0,0,0,0,183,255,255,24,0,0,0,0,0,0,0,129,255,255,82, + 0,0,0,0,0,0,0,78,255,255,137,0,0,0,0,0,0,0,58,255,255,166,0,0, + 0,0,0,0,18,164,255,255,125,0,0,0,0,0,255,255,255,255,201,14,0,0,0,0, + 0,255,255,255,243,76,0,0,0,0,0,0,136,154,241,255,253,62,0,0,0,0,0,0, + 0,86,255,255,156,0,0,0,0,0,0,0,60,255,255,158,0,0,0,0,0,0,0,103, + 255,255,110,0,0,0,0,0,0,0,156,255,255,51,0,0,0,0,0,0,0,208,255,248, + 4,0,0,0,0,0,0,0,234,255,246,9,0,0,0,0,0,0,0,206,255,255,200,137, + 136,68,0,0,0,0,93,255,255,255,255,255,128,0,0,0,0,0,84,198,245,255,255,128, + 0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36, + 0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36, + 0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36, + 0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36, + 0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36, + 0,0,255,255,255,36,0,0,255,255,255,36,0,0,255,255,255,36,0,0,136,136,133,109, + 37,0,0,0,0,0,0,255,255,255,255,253,118,0,0,0,0,0,255,255,255,255,255,254, + 42,0,0,0,0,0,0,15,190,255,255,104,0,0,0,0,0,0,0,112,255,255,98,0, + 0,0,0,0,0,0,153,255,255,55,0,0,0,0,0,0,0,212,255,251,8,0,0,0, + 0,0,0,15,254,255,211,0,0,0,0,0,0,0,42,255,255,189,0,0,0,0,0,0, + 0,11,245,255,246,63,3,0,0,0,0,0,0,93,250,255,255,255,128,0,0,0,0,0, + 14,177,255,255,255,128,0,0,0,0,0,188,255,255,196,139,68,0,0,0,0,32,255,255, + 215,2,0,0,0,0,0,0,34,255,255,192,0,0,0,0,0,0,0,2,240,255,234,0, + 0,0,0,0,0,0,0,181,255,255,30,0,0,0,0,0,0,0,124,255,255,80,0,0, + 0,0,0,0,0,130,255,255,108,0,0,0,0,136,136,151,247,255,255,81,0,0,0,0, + 255,255,255,255,255,216,8,0,0,0,0,255,255,253,229,155,22,0,0,0,0,0,73,207, + 251,240,192,108,14,0,0,27,196,192,1,58,252,255,255,255,255,255,244,169,142,220,255,201, + 0,176,255,229,146,162,236,255,255,255,255,255,255,79,0,177,196,45,0,0,8,97,188,238, + 252,211,87,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0, +}; +static const struct nvfont nvfont_title1 = { 24, 6, nvg_title1, nvb_title1 }; + +static const struct nvglyph nvg_body2[] = { + {0,6,0,0,0,378}, + {0,9,22,0,-22,545}, + {198,13,23,0,-23,801}, + {497,22,22,0,-22,1384}, + {981,19,29,0,-26,1196}, + {1532,24,22,0,-22,1509}, + {2060,23,22,0,-22,1461}, + {2566,7,23,0,-23,468}, + {2727,13,26,0,-23,812}, + {3065,13,26,0,-23,812}, + {3403,13,22,0,-22,804}, + {3689,16,18,0,-18,1033}, + {3977,8,9,0,-4,509}, + {4049,15,10,0,-10,958}, + {4199,8,4,0,-4,509}, + {4231,13,26,0,-24,852}, + {4569,20,22,0,-22,1288}, + {5009,12,22,0,-22,747}, + {5273,17,22,0,-22,1058}, + {5647,17,22,0,-22,1108}, + {6021,18,22,0,-22,1165}, + {6417,17,22,0,-22,1110}, + {6791,18,22,0,-22,1165}, + {7187,16,22,0,-22,1048}, + {7539,18,22,0,-22,1133}, + {7935,18,22,0,-22,1181}, + {8331,8,15,0,-15,509}, + {8451,8,20,0,-15,509}, + {8611,16,18,0,-18,1033}, + {8899,16,16,0,-16,1033}, + {9155,16,18,0,-18,1033}, + {9443,17,23,0,-23,1102}, + {9834,26,22,0,-22,1651}, + {10406,22,22,0,-22,1404}, + {10890,19,22,0,-22,1229}, + {11308,22,22,0,-22,1428}, + {11792,23,22,0,-22,1446}, + {12298,19,22,0,-22,1217}, + {12716,18,22,0,-22,1148}, + {13112,23,22,0,-22,1467}, + {13618,22,22,0,-22,1404}, + {14102,8,22,0,-22,488}, + {14278,13,22,0,-22,847}, + {14564,21,22,0,-22,1348}, + {15026,18,22,0,-22,1131}, + {15422,27,22,0,-22,1732}, + {16016,22,22,0,-22,1404}, + {16500,24,22,0,-22,1517}, + {17028,20,22,0,-22,1269}, + {17468,24,22,0,-22,1528}, + {17996,20,22,0,-22,1267}, + {18436,19,22,0,-22,1196}, + {18854,20,22,0,-22,1260}, + {19294,21,22,0,-22,1359}, + {19756,22,22,0,-22,1404}, + {20240,33,22,0,-22,2089}, + {20966,21,22,0,-22,1338}, + {21428,21,22,0,-22,1321}, + {21890,19,22,0,-22,1204}, + {22308,13,27,0,-24,812}, + {22659,13,26,0,-24,852}, + {22997,13,27,0,-24,812}, + {23348,16,23,0,-23,1033}, + {23716,13,4,0,0,856}, + {23768,11,23,0,-23,680}, + {24021,16,16,0,-16,1048}, + {24277,18,22,0,-22,1177}, + {24673,16,16,0,-16,1043}, + {24929,18,22,0,-22,1177}, + {25325,17,16,0,-16,1089}, + {25597,11,22,0,-22,695}, + {25839,18,22,0,-16,1177}, + {26235,18,22,0,-22,1164}, + {26631,8,22,0,-22,480}, + {26807,9,28,-1,-22,480}, + {27059,16,22,0,-22,1048}, + {27411,8,22,0,-22,480}, + {27587,28,16,0,-16,1788}, + {28035,18,16,0,-16,1164}, + {28323,18,16,0,-16,1135}, + {28611,18,22,0,-16,1177}, + {29007,18,22,0,-16,1177}, + {29403,11,16,0,-16,735}, + {29579,15,16,0,-16,927}, + {29819,12,21,0,-21,753}, + {30071,18,16,0,-16,1144}, + {30359,16,16,0,-16,1018}, + {30615,23,16,0,-16,1503}, + {30983,17,16,0,-16,1092}, + {31255,16,22,0,-16,1018}, + {31607,15,16,0,-16,970}, + {31847,13,27,0,-24,812}, + {32198,7,29,0,-25,457}, + {32401,13,27,0,-24,812}, + {32752,16,14,0,-14,1033}, +}; +static const unsigned char nvb_body2[] = { + 0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,107,248,155,0,0,0,153,248,110,0,0,0,0,235,255,255, + 28,0,27,255,255,236,0,0,0,0,252,255,255,43,0,44,255,255,251,0,0,0,0,227, + 255,255,19,0,19,255,255,227,0,0,0,0,182,255,230,0,0,0,230,255,182,0,0,0, + 0,136,255,184,0,0,0,184,255,136,0,0,0,0,90,255,138,0,0,0,138,255,90,0, + 0,0,0,45,255,93,0,0,0,93,255,45,0,0,0,0,5,249,46,0,0,0,46,249, + 5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 150,255,255,38,0,0,0,74,255,255,114,0,0,0,0,0,0,0,0,0,0,0,185,255, + 253,6,0,0,0,109,255,255,80,0,0,0,0,0,0,0,0,0,0,0,220,255,224,0, + 0,0,0,144,255,255,44,0,0,0,0,0,0,0,0,0,0,4,252,255,188,0,0,0, + 0,180,255,255,9,0,0,0,0,0,0,0,0,0,0,35,255,255,153,0,0,0,0,215, + 255,229,0,0,0,0,0,0,0,0,0,0,0,71,255,255,118,0,0,0,2,248,255,194, + 0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 0,0,0,156,156,156,156,230,255,255,158,156,156,156,202,255,255,189,156,156,156,156,0,0, + 0,0,0,0,0,212,255,233,0,0,0,0,136,255,255,53,0,0,0,0,0,0,0,0, + 0,0,1,246,255,197,0,0,0,0,171,255,255,17,0,0,0,0,0,0,0,0,0,0, + 26,255,255,162,0,0,0,0,206,255,238,0,0,0,0,0,0,0,0,0,0,0,61,255, + 255,127,0,0,0,0,240,255,203,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,0,156,156,156,156,228,255,255,162,156,156,156,199, + 255,255,193,156,156,156,156,0,0,0,0,0,0,0,202,255,242,0,0,0,0,126,255,255, + 62,0,0,0,0,0,0,0,0,0,0,0,237,255,207,0,0,0,0,161,255,255,27,0, + 0,0,0,0,0,0,0,0,0,16,255,255,172,0,0,0,0,196,255,247,1,0,0,0, + 0,0,0,0,0,0,0,52,255,255,136,0,0,0,0,232,255,212,0,0,0,0,0,0, + 0,0,0,0,0,88,255,255,101,0,0,0,12,255,255,176,0,0,0,0,0,0,0,0, + 0,0,0,123,255,255,65,0,0,0,47,255,255,141,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,216,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,216,0,0,0,0,0,0,0, + 0,0,0,0,0,1,76,166,219,255,255,254,193,119,18,0,0,0,0,0,0,0,0,27, + 195,255,255,255,255,255,255,255,255,239,83,0,0,0,0,0,0,13,216,255,255,255,219,175, + 170,203,254,255,255,254,83,0,0,0,0,0,127,255,255,237,68,0,0,0,0,36,199,255, + 255,239,16,0,0,0,0,214,255,255,103,0,0,0,0,0,0,19,236,255,255,112,0,0, + 0,0,250,255,255,53,0,0,0,0,0,0,0,138,255,255,183,0,0,0,0,246,255,255, + 90,0,0,0,0,0,0,0,43,128,128,108,0,0,0,0,197,255,255,222,32,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,89,255,255,255,243,140,48,0,0,0,0,0,0, + 0,0,0,0,0,0,0,148,255,255,255,255,255,219,134,49,0,0,0,0,0,0,0,0, + 0,0,0,97,232,255,255,255,255,255,255,217,111,7,0,0,0,0,0,0,0,0,0,9, + 100,193,253,255,255,255,255,255,219,45,0,0,0,0,0,0,0,0,0,0,0,24,106,190, + 253,255,255,255,237,32,0,0,0,0,0,0,0,0,0,0,0,0,0,28,153,255,255,255, + 169,0,0,0,28,28,28,5,0,0,0,0,0,0,0,0,135,255,255,249,9,0,0,236, + 255,255,67,0,0,0,0,0,0,0,0,26,255,255,255,41,0,0,177,255,255,145,0,0, + 0,0,0,0,0,0,5,255,255,255,42,0,0,84,255,255,247,46,0,0,0,0,0,0, + 0,61,255,255,253,12,0,0,2,201,255,255,236,89,3,0,0,0,0,44,219,255,255,184, + 0,0,0,0,32,227,255,255,255,233,181,159,166,205,255,255,255,246,45,0,0,0,0,0, + 26,183,255,255,255,255,255,255,255,255,255,229,62,0,0,0,0,0,0,0,0,57,146,206, + 255,255,255,227,182,104,12,0,0,0,0,0,0,0,0,0,0,0,0,255,255,216,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,216,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,216,0,0,0,0,0,0,0,0,0,0,1,95, + 200,248,248,203,101,2,0,0,0,0,0,0,0,33,241,255,161,0,0,0,0,0,158,255, + 255,255,255,255,255,168,1,0,0,0,0,0,4,199,255,221,14,0,0,0,0,88,255,255, + 234,169,171,237,255,255,93,0,0,0,0,0,132,255,252,56,0,0,0,0,0,191,255,200, + 14,0,0,17,204,255,192,0,0,0,0,66,254,255,125,0,0,0,0,0,0,239,255,75, + 0,0,0,0,76,255,239,0,0,0,19,230,255,196,3,0,0,0,0,0,0,254,255,45, + 0,0,0,0,45,255,254,0,0,1,179,255,240,31,0,0,0,0,0,0,0,238,255,78, + 0,0,0,0,81,255,238,0,0,108,255,255,89,0,0,0,0,0,0,0,0,189,255,204, + 16,0,0,19,208,255,186,0,46,248,255,161,0,0,0,0,0,0,0,0,0,84,255,255, + 237,169,171,239,255,255,80,11,215,255,222,14,0,0,0,0,0,0,0,0,0,0,154,255, + 255,255,255,255,255,150,0,154,255,252,57,0,0,0,0,0,0,0,0,0,0,0,1,91, + 198,248,247,197,91,0,85,255,255,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,32,239,255,196,3,15,142,227,252,225,141,16,0,0,0,0,0,0,0, + 0,0,0,0,4,197,255,241,32,14,213,255,255,255,255,255,213,15,0,0,0,0,0,0, + 0,0,0,0,131,255,255,89,0,141,255,223,54,7,65,230,255,141,0,0,0,0,0,0, + 0,0,0,65,253,255,163,0,1,235,255,74,0,0,0,82,255,235,1,0,0,0,0,0, + 0,0,19,228,255,222,14,0,24,255,255,12,0,0,0,14,255,255,23,0,0,0,0,0, + 0,1,177,255,252,58,0,0,38,255,255,6,0,0,0,6,255,255,38,0,0,0,0,0, + 0,107,255,255,127,0,0,0,23,255,255,47,0,0,0,51,255,255,23,0,0,0,0,0, + 46,248,255,197,3,0,0,0,0,231,255,170,2,0,5,181,255,231,0,0,0,0,0,9, + 213,255,241,32,0,0,0,0,0,133,255,255,211,163,219,255,255,133,0,0,0,0,0,154, + 255,255,90,0,0,0,0,0,0,11,205,255,255,255,255,255,204,11,0,0,0,0,84,255, + 255,164,0,0,0,0,0,0,0,0,12,136,225,252,224,134,12,0,0,0,0,0,0,0, + 0,0,57,163,225,251,248,216,150,42,0,0,0,0,0,0,0,0,0,0,0,0,0,1, + 139,255,255,255,255,255,255,255,252,116,0,0,0,0,0,0,0,0,0,0,0,0,118,255, + 255,255,220,163,172,240,255,255,255,86,0,0,0,0,0,0,0,0,0,0,6,240,255,255, + 156,3,0,0,17,198,255,255,205,0,0,0,0,0,0,0,0,0,0,41,255,255,255,22, + 0,0,0,0,70,255,255,250,0,0,0,0,0,0,0,0,0,0,34,255,255,255,15,0, + 0,0,0,65,255,255,238,0,0,0,0,0,0,0,0,0,0,1,229,255,255,102,0,0, + 0,3,180,255,255,159,0,0,0,0,0,0,0,0,0,0,0,111,255,255,238,33,0,39, + 184,255,255,229,25,0,0,0,0,0,0,0,0,0,0,0,3,192,255,255,220,191,254,255, + 255,207,34,0,0,0,0,0,0,0,0,0,0,0,0,16,143,255,255,255,255,255,216,101, + 4,0,0,0,0,0,0,0,0,0,0,0,0,81,236,255,255,255,255,255,224,18,0,0, + 0,0,4,255,255,255,44,0,0,0,0,100,255,255,255,171,78,228,255,255,201,15,0,0, + 0,18,255,255,255,29,0,0,0,44,250,255,254,97,0,0,40,233,255,255,201,15,0,0, + 51,255,255,250,4,0,0,0,163,255,255,156,0,0,0,0,45,238,255,255,199,14,0,101, + 255,255,205,0,0,0,0,230,255,255,68,0,0,0,0,0,52,241,255,255,198,14,180,255, + 255,134,0,0,0,0,253,255,255,52,0,0,0,0,0,0,58,245,255,255,204,253,255,255, + 42,0,0,0,0,238,255,255,94,0,0,0,0,0,0,0,66,247,255,255,255,255,176,0, + 0,0,0,0,177,255,255,206,6,0,0,0,0,0,0,0,76,255,255,255,255,44,0,0, + 0,0,0,62,255,255,255,188,37,0,0,0,0,1,64,198,255,255,255,255,189,9,0,0, + 0,0,0,127,255,255,255,254,205,167,159,180,228,255,255,255,247,253,255,255,187,9,0,0, + 0,0,0,100,240,255,255,255,255,255,255,255,255,255,185,40,99,255,255,255,184,8,0,0, + 0,0,0,16,108,181,227,249,253,236,198,137,47,0,0,0,107,255,255,255,182,7,0,0, + 107,248,155,0,0,0,0,235,255,255,28,0,0,0,252,255,255,43,0,0,0,227,255,255, + 19,0,0,0,182,255,230,0,0,0,0,136,255,184,0,0,0,0,90,255,138,0,0,0, + 0,45,255,93,0,0,0,0,5,249,46,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77,248, + 255,183,8,0,0,0,0,0,0,0,72,250,255,195,9,0,0,0,0,0,0,0,45,244, + 255,225,20,0,0,0,0,0,0,0,10,215,255,253,58,0,0,0,0,0,0,0,0,136, + 255,255,145,0,0,0,0,0,0,0,0,33,250,255,240,17,0,0,0,0,0,0,0,0, + 148,255,255,135,0,0,0,0,0,0,0,0,10,242,255,255,35,0,0,0,0,0,0,0, + 0,81,255,255,207,0,0,0,0,0,0,0,0,0,150,255,255,143,0,0,0,0,0,0, + 0,0,0,201,255,255,97,0,0,0,0,0,0,0,0,0,235,255,255,67,0,0,0,0, + 0,0,0,0,0,252,255,255,51,0,0,0,0,0,0,0,0,0,252,255,255,52,0,0, + 0,0,0,0,0,0,0,236,255,255,67,0,0,0,0,0,0,0,0,0,203,255,255,97, + 0,0,0,0,0,0,0,0,0,154,255,255,145,0,0,0,0,0,0,0,0,0,86,255, + 255,211,0,0,0,0,0,0,0,0,0,13,246,255,255,40,0,0,0,0,0,0,0,0, + 0,158,255,255,142,0,0,0,0,0,0,0,0,0,39,252,255,244,23,0,0,0,0,0, + 0,0,0,0,148,255,255,157,0,0,0,0,0,0,0,0,0,14,222,255,255,67,0,0, + 0,0,0,0,0,0,0,51,247,255,229,24,0,0,0,0,0,0,0,0,0,77,251,255, + 201,10,0,0,0,0,0,0,0,0,0,80,249,255,185,8,0,1,142,255,255,123,0,0, + 0,0,0,0,0,0,0,1,161,255,255,123,0,0,0,0,0,0,0,0,0,6,198,255, + 255,93,0,0,0,0,0,0,0,0,0,31,241,255,245,40,0,0,0,0,0,0,0,0, + 0,105,255,255,198,1,0,0,0,0,0,0,0,0,3,214,255,255,87,0,0,0,0,0, + 0,0,0,0,94,255,255,206,0,0,0,0,0,0,0,0,0,7,238,255,255,52,0,0, + 0,0,0,0,0,0,0,163,255,255,135,0,0,0,0,0,0,0,0,0,97,255,255,203, + 0,0,0,0,0,0,0,0,0,49,255,255,249,2,0,0,0,0,0,0,0,0,19,255, + 255,255,28,0,0,0,0,0,0,0,0,4,255,255,255,44,0,0,0,0,0,0,0,0, + 4,255,255,255,44,0,0,0,0,0,0,0,0,19,255,255,255,27,0,0,0,0,0,0, + 0,0,49,255,255,247,2,0,0,0,0,0,0,0,0,95,255,255,198,0,0,0,0,0, + 0,0,0,0,159,255,255,129,0,0,0,0,0,0,0,0,5,236,255,255,44,0,0,0, + 0,0,0,0,0,86,255,255,197,0,0,0,0,0,0,0,0,2,207,255,255,74,0,0, + 0,0,0,0,0,0,96,255,255,187,0,0,0,0,0,0,0,0,25,236,255,242,33,0, + 0,0,0,0,0,0,4,191,255,255,83,0,0,0,0,0,0,0,1,155,255,255,115,0, + 0,0,0,0,0,0,0,140,255,255,121,0,0,0,0,0,0,0,0,0,0,0,0,5, + 247,255,144,0,0,0,0,0,0,0,74,39,0,202,255,98,0,101,10,0,0,0,9,225, + 237,60,154,255,52,144,255,126,0,0,0,87,254,255,248,187,255,180,255,255,224,6,0,0, + 0,30,124,219,255,255,252,180,84,7,0,0,0,0,28,120,212,255,255,249,172,80,5,0, + 0,0,87,254,255,248,187,255,180,255,255,223,6,0,0,9,226,237,60,154,255,52,144,255, + 126,0,0,0,0,74,39,0,202,255,98,0,101,10,0,0,0,0,0,0,5,247,255,145, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,216,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,216,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,216,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,216,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,216,0,0,0,0,0, + 0,0,0,156,156,156,156,156,255,255,243,156,156,156,156,78,0,0,0,255,255,255,255,255, + 255,255,255,255,255,255,255,128,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,128, + 0,0,0,0,0,0,0,0,255,255,216,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,216,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,216,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,216,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,216,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,108,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0, + 0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,41,0, + 0,0,0,0,106,255,17,0,0,0,0,0,150,218,0,0,0,0,0,58,243,119,0,0, + 0,0,0,254,194,7,0,0,0,0,0,113,6,0,0,0,0,0,0,156,156,156,156,156, + 156,156,156,156,156,156,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,0,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0, + 0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,117,128,99,0,0,0,0,0, + 0,0,0,0,38,255,255,139,0,0,0,0,0,0,0,0,0,115,255,255,62,0,0,0, + 0,0,0,0,0,0,193,255,237,3,0,0,0,0,0,0,0,0,18,252,255,163,0,0, + 0,0,0,0,0,0,0,90,255,255,86,0,0,0,0,0,0,0,0,0,170,255,250,13, + 0,0,0,0,0,0,0,0,6,240,255,186,0,0,0,0,0,0,0,0,0,67,255,255, + 110,0,0,0,0,0,0,0,0,0,146,255,255,32,0,0,0,0,0,0,0,0,0,223, + 255,210,0,0,0,0,0,0,0,0,0,45,255,255,134,0,0,0,0,0,0,0,0,0, + 122,255,255,57,0,0,0,0,0,0,0,0,0,199,255,232,2,0,0,0,0,0,0,0, + 0,24,254,255,157,0,0,0,0,0,0,0,0,0,98,255,255,80,0,0,0,0,0,0, + 0,0,0,176,255,247,11,0,0,0,0,0,0,0,0,9,245,255,181,0,0,0,0,0, + 0,0,0,0,75,255,255,104,0,0,0,0,0,0,0,0,0,153,255,255,27,0,0,0, + 0,0,0,0,0,1,229,255,206,0,0,0,0,0,0,0,0,0,51,255,255,128,0,0, + 0,0,0,0,0,0,0,130,255,255,50,0,0,0,0,0,0,0,0,0,206,255,229,1, + 0,0,0,0,0,0,0,0,28,255,255,152,0,0,0,0,0,0,0,0,0,106,255,255, + 75,0,0,0,0,0,0,0,0,0,0,0,0,0,6,100,187,236,253,243,200,120,17,0, + 0,0,0,0,0,0,0,0,0,45,216,255,255,255,255,255,255,255,235,75,0,0,0,0, + 0,0,0,0,51,241,255,255,249,189,159,181,242,255,255,253,88,0,0,0,0,0,0,16, + 228,255,255,190,26,0,0,0,13,154,255,255,248,44,0,0,0,0,0,143,255,255,210,9, + 0,0,0,0,0,0,167,255,255,190,0,0,0,0,16,246,255,255,62,0,0,0,0,0, + 0,0,21,246,255,255,55,0,0,0,97,255,255,212,0,0,0,0,0,0,0,0,0,163, + 255,255,146,0,0,0,166,255,255,140,0,0,0,0,0,0,0,0,0,92,255,255,214,0, + 0,0,211,255,255,92,0,0,0,0,0,0,0,0,0,44,255,255,253,6,0,0,241,255, + 255,63,0,0,0,0,0,0,0,0,0,15,255,255,255,33,0,0,254,255,255,52,0,0, + 0,0,0,0,0,0,0,4,255,255,255,46,0,0,252,255,255,53,0,0,0,0,0,0, + 0,0,0,6,255,255,255,44,0,0,237,255,255,69,0,0,0,0,0,0,0,0,0,20, + 255,255,255,29,0,0,205,255,255,99,0,0,0,0,0,0,0,0,0,51,255,255,250,4, + 0,0,158,255,255,151,0,0,0,0,0,0,0,0,0,101,255,255,206,0,0,0,87,255, + 255,225,2,0,0,0,0,0,0,0,0,176,255,255,136,0,0,0,10,239,255,255,79,0, + 0,0,0,0,0,0,33,251,255,255,44,0,0,0,0,129,255,255,221,14,0,0,0,0, + 0,2,182,255,255,178,0,0,0,0,0,10,218,255,255,200,32,0,0,0,16,166,255,255, + 244,35,0,0,0,0,0,0,42,237,255,255,251,189,159,179,242,255,255,250,76,0,0,0, + 0,0,0,0,0,39,210,255,255,255,255,255,255,255,230,66,0,0,0,0,0,0,0,0, + 0,0,5,96,187,236,254,243,197,115,13,0,0,0,0,0,0,0,0,0,0,0,0,163, + 255,255,48,0,0,0,0,0,0,0,116,255,255,255,48,0,0,0,0,0,14,154,255,255, + 255,255,48,0,0,0,36,127,234,255,255,255,255,255,48,0,0,123,255,255,255,255,183,255, + 255,255,48,0,0,128,255,255,253,143,6,255,255,255,48,0,0,128,244,162,46,0,0,255, + 255,255,48,0,0,21,6,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,39,143,209, + 245,253,239,195,119,14,0,0,0,0,0,0,1,126,252,255,255,255,255,255,255,255,231,56, + 0,0,0,0,0,136,255,255,255,239,175,161,194,254,255,255,240,33,0,0,0,59,255,255, + 255,159,10,0,0,0,50,233,255,255,164,0,0,0,177,255,255,198,2,0,0,0,0,0, + 91,255,255,245,5,0,6,248,255,255,83,0,0,0,0,0,0,19,255,255,255,35,0,20, + 152,152,152,19,0,0,0,0,0,0,6,255,255,255,44,0,0,0,0,0,0,0,0,0, + 0,0,0,42,255,255,255,23,0,0,0,0,0,0,0,0,0,0,0,0,123,255,255,227, + 0,0,0,0,0,0,0,0,0,0,0,0,16,233,255,255,143,0,0,0,0,0,0,0, + 0,0,0,0,0,162,255,255,250,33,0,0,0,0,0,0,0,0,0,0,0,107,255,255, + 255,131,0,0,0,0,0,0,0,0,0,0,0,82,253,255,255,197,5,0,0,0,0,0, + 0,0,0,0,0,80,250,255,255,224,26,0,0,0,0,0,0,0,0,0,0,92,251,255, + 255,231,37,0,0,0,0,0,0,0,0,0,0,115,254,255,255,229,40,0,0,0,0,0, + 0,0,0,0,3,146,255,255,255,218,33,0,0,0,0,0,0,0,0,0,12,179,255,255, + 255,199,21,0,0,0,0,0,0,0,0,0,30,207,255,255,255,169,9,0,0,0,0,0, + 0,0,0,0,0,233,255,255,255,243,157,156,156,156,156,156,156,156,156,156,0,0,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,0,0,0,0,0,36,138,207,243,253,239,197,123,19,0,0,0,0, + 0,0,1,126,251,255,255,255,255,255,255,255,237,73,0,0,0,0,0,138,255,255,255,244, + 181,160,187,249,255,255,249,52,0,0,0,56,255,255,255,169,16,0,0,0,30,212,255,255, + 189,0,0,0,167,255,255,205,3,0,0,0,0,0,63,255,255,254,15,0,0,235,255,255, + 100,0,0,0,0,0,0,8,255,255,255,42,0,1,48,48,48,12,0,0,0,0,0,0, + 24,255,255,255,34,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,235,3,0,0, + 0,0,0,0,0,0,0,3,35,135,253,255,255,129,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,255,191,7,0,0,0,0,0,0,0,0,255,255,255,255,255,255,237,41,0, + 0,0,0,0,0,0,0,0,156,156,157,176,237,255,255,245,75,0,0,0,0,0,0,0, + 0,0,0,0,0,9,146,255,255,245,33,0,0,0,0,0,0,0,0,0,0,0,0,1, + 188,255,255,150,0,0,0,0,0,0,0,0,0,0,0,0,0,86,255,255,222,0,65,248, + 248,246,10,0,0,0,0,0,0,0,52,255,255,251,0,18,253,255,255,80,0,0,0,0, + 0,0,0,72,255,255,244,0,0,186,255,255,205,5,0,0,0,0,0,0,161,255,255,194, + 0,0,58,255,255,255,176,21,0,0,0,5,124,255,255,255,92,0,0,0,128,255,255,255, + 248,187,160,174,232,255,255,255,178,2,0,0,0,0,110,246,255,255,255,255,255,255,255,255, + 155,7,0,0,0,0,0,0,26,127,199,239,253,245,212,148,49,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,68,254,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0, + 22,231,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,1,182,255,255,255,255,48, + 0,0,0,0,0,0,0,0,0,0,0,116,255,255,255,255,255,48,0,0,0,0,0,0, + 0,0,0,0,52,250,255,255,255,255,255,48,0,0,0,0,0,0,0,0,0,14,221,255, + 255,163,255,255,255,48,0,0,0,0,0,0,0,0,0,166,255,255,221,14,255,255,255,48, + 0,0,0,0,0,0,0,0,97,255,255,251,52,0,255,255,255,48,0,0,0,0,0,0, + 0,40,244,255,255,119,0,0,255,255,255,48,0,0,0,0,0,0,7,208,255,255,187,2, + 0,0,255,255,255,48,0,0,0,0,0,0,147,255,255,234,25,0,0,0,255,255,255,48, + 0,0,0,0,0,79,255,255,255,74,0,0,0,0,255,255,255,48,0,0,0,0,28,237, + 255,255,144,0,0,0,0,0,255,255,255,48,0,0,0,3,193,255,255,206,7,0,0,0, + 0,0,255,255,255,48,0,0,0,105,255,255,255,205,176,176,176,176,176,176,255,255,255,192, + 176,176,0,128,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,128,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,160,255,255,255,255,255,255,255,255,255,255,255, + 8,0,0,0,0,183,255,255,255,255,255,255,255,255,255,255,255,8,0,0,0,0,206,255, + 255,180,156,156,156,156,156,156,156,156,5,0,0,0,0,229,255,255,47,0,0,0,0,0, + 0,0,0,0,0,0,0,1,251,255,255,25,0,0,0,0,0,0,0,0,0,0,0,0, + 20,255,255,255,6,0,0,0,0,0,0,0,0,0,0,0,0,43,255,255,240,0,0,0, + 0,0,0,0,0,0,0,0,0,0,66,255,255,219,62,181,237,252,234,180,80,1,0,0, + 0,0,0,89,255,255,247,255,255,255,255,255,255,255,179,11,0,0,0,0,112,255,255,255, + 255,207,162,171,234,255,255,255,179,0,0,0,0,136,255,255,236,67,0,0,0,8,148,255, + 255,255,82,0,0,0,87,144,144,60,0,0,0,0,0,1,185,255,255,188,0,0,0,0, + 0,0,0,0,0,0,0,0,0,72,255,255,250,5,0,0,0,0,0,0,0,0,0,0, + 0,0,18,255,255,255,33,0,0,0,0,0,0,0,0,0,0,0,0,2,255,255,255,45, + 0,71,108,108,67,0,0,0,0,0,0,0,13,255,255,255,33,0,129,255,255,214,1,0, + 0,0,0,0,0,61,255,255,248,4,0,47,255,255,255,90,0,0,0,0,0,0,165,255, + 255,183,0,0,0,180,255,255,246,86,0,0,0,5,132,255,255,255,71,0,0,0,27,229, + 255,255,255,216,164,170,230,255,255,255,158,0,0,0,0,0,36,211,255,255,255,255,255,255, + 255,255,148,4,0,0,0,0,0,0,5,95,181,231,252,248,218,154,51,0,0,0,0,0, + 0,0,0,0,12,112,194,239,254,243,203,128,21,0,0,0,0,0,0,0,0,67,232,255, + 255,255,255,255,255,255,240,80,0,0,0,0,0,0,73,251,255,255,245,184,159,180,243,255, + 255,252,72,0,0,0,0,27,240,255,255,165,18,0,0,0,15,172,255,255,227,8,0,0, + 0,158,255,255,181,2,0,0,0,0,0,7,220,255,255,86,0,0,21,250,255,254,37,0, + 0,0,0,0,0,0,117,228,228,135,0,0,99,255,255,195,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,161,255,255,131,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 207,255,255,88,0,49,154,220,250,249,220,156,50,0,0,0,0,0,236,255,255,63,135,254, + 255,255,255,255,255,255,254,140,2,0,0,0,252,255,255,183,255,255,219,169,159,191,249,255, + 255,255,140,0,0,0,255,255,255,255,228,71,0,0,0,0,25,184,255,255,254,50,0,0, + 247,255,255,247,39,0,0,0,0,0,0,7,212,255,255,157,0,0,227,255,255,162,0,0, + 0,0,0,0,0,0,104,255,255,220,0,0,193,255,255,117,0,0,0,0,0,0,0,0, + 55,255,255,250,0,0,142,255,255,126,0,0,0,0,0,0,0,0,55,255,255,250,0,0, + 69,255,255,190,0,0,0,0,0,0,0,0,101,255,255,218,0,0,3,222,255,255,75,0, + 0,0,0,0,0,7,212,255,255,150,0,0,0,89,255,255,245,97,3,0,0,0,26,184, + 255,255,251,39,0,0,0,0,158,255,255,255,229,173,160,190,249,255,255,255,112,0,0,0, + 0,0,3,139,254,255,255,255,255,255,255,255,244,104,0,0,0,0,0,0,0,0,44,146, + 213,245,252,238,195,124,24,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,156,156,156,156, + 156,156,156,156,156,156,156,212,255,255,216,0,0,0,0,0,0,0,0,0,0,0,25,237, + 255,254,61,0,0,0,0,0,0,0,0,0,0,0,179,255,255,153,0,0,0,0,0,0, + 0,0,0,0,0,87,255,255,237,18,0,0,0,0,0,0,0,0,0,0,12,228,255,255, + 110,0,0,0,0,0,0,0,0,0,0,0,129,255,255,221,5,0,0,0,0,0,0,0, + 0,0,0,20,244,255,255,96,0,0,0,0,0,0,0,0,0,0,0,136,255,255,225,5, + 0,0,0,0,0,0,0,0,0,0,13,241,255,255,115,0,0,0,0,0,0,0,0,0, + 0,0,105,255,255,248,18,0,0,0,0,0,0,0,0,0,0,0,208,255,255,168,0,0, + 0,0,0,0,0,0,0,0,0,42,255,255,255,77,0,0,0,0,0,0,0,0,0,0, + 0,126,255,255,245,8,0,0,0,0,0,0,0,0,0,0,0,201,255,255,177,0,0,0, + 0,0,0,0,0,0,0,0,15,253,255,255,112,0,0,0,0,0,0,0,0,0,0,0, + 73,255,255,255,55,0,0,0,0,0,0,0,0,0,0,0,123,255,255,253,9,0,0,0, + 0,0,0,0,0,0,0,0,167,255,255,222,0,0,0,0,0,0,0,0,0,0,0,0, + 203,255,255,187,0,0,0,0,0,0,0,0,0,0,0,0,231,255,255,161,0,0,0,0, + 0,0,0,0,0,0,0,5,96,180,229,251,249,224,170,76,1,0,0,0,0,0,0,0, + 43,214,255,255,255,255,255,255,255,255,191,23,0,0,0,0,0,30,234,255,255,255,204,162, + 167,217,255,255,255,209,10,0,0,0,0,168,255,255,229,54,0,0,0,0,86,247,255,255, + 122,0,0,0,9,250,255,255,76,0,0,0,0,0,0,124,255,255,212,0,0,0,42,255, + 255,255,8,0,0,0,0,0,0,56,255,255,250,0,0,0,36,255,255,255,21,0,0,0, + 0,0,0,68,255,255,244,0,0,0,5,240,255,255,128,0,0,0,0,0,1,173,255,255, + 197,0,0,0,0,139,255,255,255,152,46,6,11,58,176,255,255,255,91,0,0,0,0,11, + 200,255,255,255,255,255,255,255,255,255,255,160,1,0,0,0,0,0,43,240,255,255,255,255, + 255,255,255,255,188,2,0,0,0,0,0,72,245,255,255,241,180,158,173,231,255,255,255,160, + 0,0,0,0,31,244,255,255,142,10,0,0,0,3,107,252,255,255,93,0,0,0,149,255, + 255,173,0,0,0,0,0,0,0,126,255,255,208,0,0,0,224,255,255,76,0,0,0,0, + 0,0,0,28,255,255,255,20,0,0,252,255,255,50,0,0,0,0,0,0,0,3,255,255, + 255,44,0,0,240,255,255,82,0,0,0,0,0,0,0,34,255,255,255,32,0,0,185,255, + 255,185,1,0,0,0,0,0,0,138,255,255,234,1,0,0,78,255,255,255,153,12,0,0, + 0,4,116,254,255,255,129,0,0,0,0,158,255,255,255,242,180,158,173,232,255,255,255,201, + 8,0,0,0,0,3,137,252,255,255,255,255,255,255,255,255,171,15,0,0,0,0,0,0, + 0,38,137,204,241,253,245,213,152,56,0,0,0,0,0,0,0,0,0,29,127,197,238,252, + 245,213,148,47,0,0,0,0,0,0,0,1,122,248,255,255,255,255,255,255,255,254,143,3, + 0,0,0,0,0,143,255,255,255,246,186,160,170,222,255,255,255,161,0,0,0,0,65,255, + 255,255,165,18,0,0,0,1,76,232,255,255,89,0,0,0,175,255,255,190,2,0,0,0, + 0,0,0,45,250,255,222,2,0,0,233,255,255,82,0,0,0,0,0,0,0,0,166,255, + 255,66,0,0,252,255,255,50,0,0,0,0,0,0,0,0,120,255,255,138,0,0,236,255, + 255,79,0,0,0,0,0,0,0,0,141,255,255,190,0,0,182,255,255,184,1,0,0,0, + 0,0,0,14,229,255,255,225,0,0,79,255,255,255,157,15,0,0,0,0,43,200,255,255, + 255,245,0,0,0,168,255,255,255,245,186,159,167,208,255,255,203,255,255,254,0,0,0,7, + 160,255,255,255,255,255,255,255,255,156,58,255,255,252,0,0,0,0,0,60,161,221,250,250, + 222,160,59,0,66,255,255,238,0,0,0,0,0,0,0,0,0,0,0,0,0,0,89,255, + 255,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,126,255,255,172,0,0,158,252, + 252,93,0,0,0,0,0,0,0,0,181,255,255,115,0,0,111,255,255,160,0,0,0,0, + 0,0,0,17,247,255,255,36,0,0,33,255,255,247,30,0,0,0,0,0,0,138,255,255, + 185,0,0,0,0,171,255,255,210,37,0,0,0,4,116,255,255,252,50,0,0,0,0,25, + 229,255,255,252,193,159,171,229,255,255,255,114,0,0,0,0,0,0,39,214,255,255,255,255, + 255,255,255,247,105,0,0,0,0,0,0,0,0,6,101,188,237,253,245,206,136,30,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 41,0,0,0,0,0,106,255,17,0,0,0,0,0,150,218,0,0,0,0,0,58,243,119, + 0,0,0,0,0,254,194,7,0,0,0,0,0,113,6,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,1,65,70,0,0,0,0,0,0,0,0,0,0,0,29,124,222, + 255,128,0,0,0,0,0,0,0,0,7,84,184,253,255,255,255,128,0,0,0,0,0,0, + 44,144,235,255,255,255,255,197,100,14,0,0,0,15,104,203,255,255,255,255,229,136,40,0, + 0,0,0,0,0,246,255,255,255,249,172,72,3,0,0,0,0,0,0,0,0,255,255,255, + 183,23,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,234,147,55,0,0,0,0, + 0,0,0,0,0,48,141,232,255,255,255,255,219,127,37,0,0,0,0,0,0,0,0,4, + 76,169,248,255,255,255,255,200,108,20,0,0,0,0,0,0,0,0,16,102,196,255,255,255, + 255,128,0,0,0,0,0,0,0,0,0,0,0,37,128,223,255,128,0,0,0,0,0,0, + 0,0,0,0,0,0,0,1,62,66,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255, + 255,255,0,0,0,156,156,156,156,156,156,156,156,156,156,156,156,156,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255, + 255,255,0,0,0,156,156,156,156,156,156,156,156,156,156,156,156,156,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,68,2,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,227,136,40,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,201,108,19,0,0,0,0,0,0,0,0,0,102,194,255,255,255,255,249,173,80,5,0, + 0,0,0,0,0,0,0,31,123,214,255,255,255,255,235,145,52,0,0,0,0,0,0,0, + 0,0,50,141,230,255,255,255,255,118,0,0,0,0,0,0,0,0,0,0,3,93,242,255, + 255,128,0,0,0,0,0,0,0,0,5,80,176,252,255,255,255,128,0,0,0,0,0,0, + 44,140,234,255,255,255,255,196,99,12,0,0,0,16,106,204,255,255,255,255,232,137,40,0, + 0,0,0,0,0,248,255,255,255,251,176,77,4,0,0,0,0,0,0,0,0,255,255,215, + 116,24,0,0,0,0,0,0,0,0,0,0,0,156,57,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,74,164,220,248,252,232,187,105, + 9,0,0,0,0,0,0,32,196,255,255,255,255,255,255,255,255,225,53,0,0,0,0,31, + 229,255,255,255,208,164,162,203,255,255,255,241,38,0,0,1,196,255,255,221,53,0,0,0, + 0,53,227,255,255,177,0,0,69,255,255,242,32,0,0,0,0,0,0,74,255,255,252,12, + 0,152,255,255,142,0,0,0,0,0,0,0,7,255,255,255,42,0,122,160,160,51,0,0, + 0,0,0,0,0,21,255,255,255,38,0,0,0,0,0,0,0,0,0,0,0,0,132,255, + 255,243,5,0,0,0,0,0,0,0,0,0,0,0,106,254,255,255,143,0,0,0,0,0, + 0,0,0,0,0,8,157,255,255,255,204,12,0,0,0,0,0,0,0,0,0,15,196,255, + 255,255,177,13,0,0,0,0,0,0,0,0,0,2,191,255,255,254,122,1,0,0,0,0, + 0,0,0,0,0,0,91,255,255,255,104,0,0,0,0,0,0,0,0,0,0,0,0,187, + 255,255,181,0,0,0,0,0,0,0,0,0,0,0,0,0,236,255,255,84,0,0,0,0, + 0,0,0,0,0,0,0,0,0,190,192,192,39,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,18,97,162,208,238,252,252,235,199,142,59,0,0,0,0,0,0,0,0,0,0,0, + 0,0,26,154,247,255,255,255,255,255,255,255,255,255,255,203,58,0,0,0,0,0,0,0, + 0,0,0,93,242,255,255,255,248,203,173,160,163,184,227,255,255,255,253,113,0,0,0,0, + 0,0,0,0,128,255,255,254,174,73,7,0,0,0,0,0,0,41,145,249,255,255,100,0, + 0,0,0,0,0,107,255,255,212,52,0,45,169,236,252,219,118,2,21,64,64,82,227,255, + 246,31,0,0,0,0,44,250,255,190,12,0,97,250,255,255,255,255,255,142,124,255,255,103, + 42,246,255,147,0,0,0,0,184,255,218,14,0,77,254,255,255,225,163,189,255,252,208,255, + 255,39,0,136,255,228,0,0,0,44,255,255,67,0,11,230,255,255,160,6,0,0,93,255, + 255,255,231,0,0,49,255,255,20,0,0,130,255,201,0,0,104,255,255,217,6,0,0,0, + 0,214,255,255,167,0,0,10,255,255,37,0,0,192,255,117,0,0,186,255,255,113,0,0, + 0,0,0,171,255,255,103,0,0,5,255,255,29,0,0,232,255,66,0,0,235,255,255,61, + 0,0,0,0,0,185,255,255,39,0,0,35,255,247,4,0,0,251,255,44,0,0,254,255, + 255,54,0,0,0,0,7,238,255,231,0,0,0,102,255,186,0,0,0,252,255,47,0,0, + 243,255,255,97,0,0,0,0,103,255,255,168,0,0,1,207,255,93,0,0,0,230,255,81, + 0,0,197,255,255,217,19,0,0,58,243,255,255,124,0,0,108,255,216,5,0,0,0,182, + 255,153,0,0,102,255,255,255,236,166,183,253,230,255,255,183,47,128,253,248,55,0,0,0, + 0,108,255,244,25,0,3,185,255,255,255,255,255,226,39,232,255,255,255,255,243,75,0,0, + 0,0,0,16,239,255,191,7,0,7,129,223,253,230,147,20,0,58,206,252,232,156,31,0, + 0,0,0,0,0,0,103,255,255,190,25,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,155,255,255,242,136,41,0,0,0,0,0,0,0,18,86, + 164,11,0,0,0,0,0,0,0,0,0,2,133,254,255,255,255,232,191,167,158,165,186,223, + 255,255,255,106,0,0,0,0,0,0,0,0,0,0,0,57,191,255,255,255,255,255,255,255, + 255,255,255,255,193,54,0,0,0,0,0,0,0,0,0,0,0,0,0,43,123,182,223,246, + 255,249,225,184,123,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,246, + 255,255,255,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,255,255,255, + 255,148,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,196,255,255,253,255,237, + 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,255,255,193,187,255,255,84,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,132,255,255,100,92,255,255,184,0,0,0, + 0,0,0,0,0,0,0,0,0,0,3,228,255,248,15,12,244,255,253,27,0,0,0,0, + 0,0,0,0,0,0,0,0,71,255,255,169,0,0,162,255,255,120,0,0,0,0,0,0, + 0,0,0,0,0,0,168,255,255,76,0,0,68,255,255,216,0,0,0,0,0,0,0,0, + 0,0,0,16,248,255,234,5,0,0,3,229,255,255,56,0,0,0,0,0,0,0,0,0, + 0,104,255,255,146,0,0,0,0,138,255,255,152,0,0,0,0,0,0,0,0,0,0,200, + 255,255,52,0,0,0,0,44,255,255,240,9,0,0,0,0,0,0,0,0,40,255,255,215, + 0,0,0,0,0,0,208,255,255,91,0,0,0,0,0,0,0,0,136,255,255,123,0,0, + 0,0,0,0,114,255,255,188,0,0,0,0,0,0,0,3,229,255,255,199,180,180,180,180, + 180,180,197,255,255,254,30,0,0,0,0,0,0,72,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,124,0,0,0,0,0,0,169,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,219,1,0,0,0,0,19,249,255,246,14,0,0,0,0,0,0,0,0,10,243,255, + 255,60,0,0,0,0,108,255,255,168,0,0,0,0,0,0,0,0,0,0,160,255,255,156, + 0,0,0,0,204,255,255,75,0,0,0,0,0,0,0,0,0,0,67,255,255,242,12,0, + 0,44,255,255,233,4,0,0,0,0,0,0,0,0,0,0,2,227,255,255,95,0,0,140, + 255,255,144,0,0,0,0,0,0,0,0,0,0,0,0,136,255,255,192,0,4,232,255,255, + 51,0,0,0,0,0,0,0,0,0,0,0,0,43,255,255,255,33,0,0,255,255,255,255, + 255,255,255,255,255,248,218,153,44,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255, + 255,255,255,252,115,0,0,0,0,0,255,255,255,177,156,156,156,156,159,186,248,255,255,255, + 84,0,0,0,0,255,255,255,48,0,0,0,0,0,0,24,204,255,255,214,0,0,0,0, + 255,255,255,48,0,0,0,0,0,0,0,55,255,255,255,25,0,0,0,255,255,255,48,0, + 0,0,0,0,0,0,6,255,255,255,45,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,25,255,255,255,28,0,0,0,255,255,255,48,0,0,0,0,0,0,0,126,255,255,224, + 0,0,0,0,255,255,255,48,0,0,0,0,6,39,138,253,255,255,106,0,0,0,0,255, + 255,255,255,255,255,255,255,255,255,255,255,255,160,2,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,198,11,0,0,0,0,0,255,255,255,177,156,156,156,156,157,173,234, + 255,255,219,36,0,0,0,0,255,255,255,48,0,0,0,0,0,0,7,142,255,255,219,11, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,184,255,255,121,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,84,255,255,208,0,0,0,255,255,255,48,0,0,0, + 0,0,0,0,0,52,255,255,246,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0, + 69,255,255,250,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,155,255,255,212,0, + 0,0,255,255,255,48,0,0,0,0,0,0,2,109,254,255,255,128,0,0,0,255,255,255, + 177,156,156,156,156,156,168,224,255,255,255,222,14,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,255,213,35,0,0,0,0,255,255,255,255,255,255,255,255,255,253,235,187,101, + 6,0,0,0,0,0,0,0,0,0,3,77,160,215,245,252,240,202,140,44,0,0,0,0, + 0,0,0,0,0,0,0,69,216,255,255,255,255,255,255,255,255,255,169,21,0,0,0,0, + 0,0,0,0,124,255,255,255,255,217,171,159,178,233,255,255,255,226,36,0,0,0,0,0, + 0,117,255,255,255,189,48,0,0,0,0,2,83,228,255,255,220,17,0,0,0,0,55,252, + 255,255,141,2,0,0,0,0,0,0,0,27,223,255,255,152,0,0,0,0,197,255,255,181, + 1,0,0,0,0,0,0,0,0,0,60,255,255,251,26,0,0,54,255,255,253,37,0,0, + 0,0,0,0,0,0,0,0,0,196,255,255,110,0,0,138,255,255,184,0,0,0,0,0, + 0,0,0,0,0,0,0,123,255,255,168,0,0,196,255,255,114,0,0,0,0,0,0,0, + 0,0,0,0,0,2,4,4,3,0,0,235,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,252,255,255,53,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,251,255,255,54,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,232,255,255,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,193,255,255,119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,131,255,255,193,0,0,0,0,0,0,0,0,0,0,0,0,112,255,255,182,0,0,43, + 255,255,255,46,0,0,0,0,0,0,0,0,0,0,0,184,255,255,122,0,0,0,184,255, + 255,192,3,0,0,0,0,0,0,0,0,0,48,254,255,253,34,0,0,0,43,249,255,255, + 153,3,0,0,0,0,0,0,0,22,216,255,255,159,0,0,0,0,0,100,255,255,255,193, + 51,0,0,0,0,2,81,226,255,255,223,19,0,0,0,0,0,0,104,252,255,255,255,218, + 170,157,177,231,255,255,255,227,38,0,0,0,0,0,0,0,0,56,206,255,255,255,255,255, + 255,255,255,255,169,22,0,0,0,0,0,0,0,0,0,0,1,68,155,212,244,253,241,203, + 141,47,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,248,224,184,121,37,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,177,39, + 0,0,0,0,0,0,0,0,255,255,255,177,156,156,156,157,167,199,248,255,255,255,247,94, + 0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,11,99,229,255,255,255,99, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,17,188,255,255,250,50, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,10,207,255,255,196,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,51,255,255,255,56,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,194,255,255,142,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,117,255,255,200,0,0,0, + 0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,73,255,255,235,0,0,0,0, + 255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,53,255,255,252,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,0,0,0,56,255,255,252,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,81,255,255,233,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,0,0,0,0,132,255,255,194,0,0,0,0,255,255,255,48, + 0,0,0,0,0,0,0,0,0,0,1,213,255,255,133,0,0,0,0,255,255,255,48,0, + 0,0,0,0,0,0,0,0,0,77,255,255,255,44,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,21,226,255,255,181,0,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,0,0,28,208,255,255,244,36,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,15,112,238,255,255,252,81,0,0,0,0,0,0,255,255,255,177,156,156,156,156,167, + 202,250,255,255,255,242,77,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,254,168,31,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,248,224,183, + 121,33,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,128,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0, + 0,0,255,255,255,177,156,156,156,156,156,156,156,156,156,156,78,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,177,156,156,156,156,156, + 156,156,156,156,156,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0, + 255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,177,156,156,156,156,156,156,156, + 156,156,156,156,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0, + 0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,128,0,0,0,255,255,255,177,156,156,156,156,156,156,156,156,156,156, + 78,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,128, + 0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,255,255, + 255,177,156,156,156,156,156,156,156,156,156,78,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,70,153,211,243,254,245,215,164,84, + 5,0,0,0,0,0,0,0,0,0,0,0,58,208,255,255,255,255,255,255,255,255,255,223, + 80,0,0,0,0,0,0,0,0,0,111,253,255,255,255,227,176,159,168,208,254,255,255,255, + 137,0,0,0,0,0,0,0,105,255,255,255,205,64,1,0,0,0,0,32,162,255,255,255, + 120,0,0,0,0,0,47,250,255,255,165,6,0,0,0,0,0,0,0,0,120,255,255,251, + 36,0,0,0,0,190,255,255,198,5,0,0,0,0,0,0,0,0,0,1,198,255,255,141, + 0,0,0,49,255,255,255,47,0,0,0,0,0,0,0,0,0,0,0,102,244,244,195,0, + 0,0,133,255,255,193,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,194,255,255,119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 231,255,255,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,252, + 255,255,55,0,0,0,0,0,0,0,200,255,255,255,255,255,255,255,255,0,0,0,254,255, + 255,53,0,0,0,0,0,0,0,200,255,255,255,255,255,255,255,255,0,0,0,238,255,255, + 72,0,0,0,0,0,0,0,122,156,156,156,156,177,255,255,255,0,0,0,205,255,255,114, + 0,0,0,0,0,0,0,0,0,0,0,0,48,255,255,255,0,0,0,150,255,255,185,0, + 0,0,0,0,0,0,0,0,0,0,0,48,255,255,255,0,0,0,68,255,255,254,38,0, + 0,0,0,0,0,0,0,0,0,0,48,255,255,255,0,0,0,2,213,255,255,191,3,0, + 0,0,0,0,0,0,0,0,0,48,255,255,255,0,0,0,0,68,255,255,255,164,7,0, + 0,0,0,0,0,0,0,0,107,255,255,255,0,0,0,0,0,130,255,255,255,213,84,6, + 0,0,0,0,0,42,163,255,255,255,224,0,0,0,0,0,0,128,254,255,255,255,243,193, + 166,158,176,220,255,255,255,255,197,26,0,0,0,0,0,0,0,64,207,255,255,255,255,255, + 255,255,255,255,255,231,108,3,0,0,0,0,0,0,0,0,0,1,63,144,201,235,252,252, + 238,206,155,82,7,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,48,0,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,48,0,0,0,0,255,255,255,195,180,180,180,180,180,180, + 180,180,180,180,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0, + 255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0, + 255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0, + 255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0, + 255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0, + 255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0, + 255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0, + 255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0, + 0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0, + 0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,11,255,255, + 255,42,0,0,0,0,0,0,0,0,71,255,255,254,14,0,20,2,0,0,0,0,41,220, + 255,255,199,0,0,128,235,187,162,162,200,254,255,255,255,72,0,0,128,255,255,255,255,255, + 255,255,249,104,0,0,0,69,189,228,249,254,240,204,137,34,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,72,249,255,255,233,43,0,0,0,255,255,255,48,0, + 0,0,0,0,0,0,67,247,255,255,233,43,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,62,246,255,255,231,43,0,0,0,0,0,255,255,255,48,0,0,0,0,0,57,242, + 255,255,230,38,0,0,0,0,0,0,255,255,255,48,0,0,0,0,52,241,255,255,230,38, + 0,0,0,0,0,0,0,255,255,255,48,0,0,0,48,238,255,255,230,38,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,44,235,255,255,228,37,0,0,0,0,0,0,0,0, + 0,255,255,255,48,0,39,233,255,255,228,36,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,36,229,255,255,228,36,0,0,0,0,0,0,0,0,0,0,0,255,255,255,80,226, + 255,255,226,36,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,65, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,96,240,255,255,222,28,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,58,246,255,255,218,25,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,48,0,72,250,255,255,213,21,0,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,83,253,255,255,210,18,0,0,0,0,0,0,0,0, + 0,255,255,255,48,0,0,0,97,255,255,255,206,16,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,112,255,255,255,201,14,0,0,0,0,0,0,0,255,255,255,48,0, + 0,0,0,0,130,255,255,255,196,11,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,144,255,255,255,191,9,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,1, + 159,255,255,255,186,8,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,3,173,255, + 255,255,180,6,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,7,186,255,255,255, + 176,5,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,189,172,172,172,172,172,172,172,172,172,172, + 0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,255,255,84,0,0, + 0,0,0,0,0,0,0,0,0,51,255,255,255,255,255,0,0,0,0,255,255,255,255,255, + 172,0,0,0,0,0,0,0,0,0,0,0,138,255,255,255,255,255,0,0,0,0,255,255, + 255,247,255,246,12,0,0,0,0,0,0,0,0,0,1,224,255,243,255,255,255,0,0,0, + 0,255,255,255,173,255,255,90,0,0,0,0,0,0,0,0,0,56,255,255,167,255,255,255, + 0,0,0,0,255,255,255,88,255,255,178,0,0,0,0,0,0,0,0,0,143,255,255,81, + 255,255,255,0,0,0,0,255,255,255,48,209,255,249,16,0,0,0,0,0,0,0,2,228, + 255,203,48,255,255,255,0,0,0,0,255,255,255,48,124,255,255,97,0,0,0,0,0,0, + 0,60,255,255,117,48,255,255,255,0,0,0,0,255,255,255,48,39,255,255,184,0,0,0, + 0,0,0,0,147,255,255,33,48,255,255,255,0,0,0,0,255,255,255,48,0,208,255,252, + 20,0,0,0,0,0,3,231,255,203,0,48,255,255,255,0,0,0,0,255,255,255,48,0, + 122,255,255,104,0,0,0,0,0,64,255,255,117,0,48,255,255,255,0,0,0,0,255,255, + 255,48,0,36,255,255,191,0,0,0,0,0,152,255,255,33,0,48,255,255,255,0,0,0, + 0,255,255,255,48,0,0,207,255,253,25,0,0,0,4,234,255,202,0,0,48,255,255,255, + 0,0,0,0,255,255,255,48,0,0,120,255,255,110,0,0,0,68,255,255,116,0,0,48, + 255,255,255,0,0,0,0,255,255,255,48,0,0,35,255,255,197,0,0,0,156,255,255,32, + 0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0,205,255,255,30,0,5,237, + 255,201,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0,119,255,255,116, + 0,72,255,255,116,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0,33, + 255,255,204,0,160,255,255,30,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0, + 0,0,0,203,255,255,43,240,255,200,0,0,0,0,48,255,255,255,0,0,0,0,255,255, + 255,48,0,0,0,0,117,255,255,199,255,255,115,0,0,0,0,48,255,255,255,0,0,0, + 0,255,255,255,48,0,0,0,0,32,255,255,255,255,255,30,0,0,0,0,48,255,255,255, + 0,0,0,0,255,255,255,48,0,0,0,0,0,200,255,255,255,200,0,0,0,0,0,48, + 255,255,255,0,0,0,0,255,255,255,48,0,0,0,0,0,115,255,255,255,115,0,0,0, + 0,0,48,255,255,255,0,0,0,0,255,255,255,246,29,0,0,0,0,0,0,0,0,0, + 255,255,255,48,0,0,0,0,255,255,255,255,168,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,255,255,255,255,255,61,0,0,0,0,0,0,0,0,255,255,255,48, + 0,0,0,0,255,255,255,255,255,207,3,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,255,255,255,231,255,255,101,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 255,255,255,96,253,255,235,16,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255, + 255,48,153,255,255,143,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48, + 21,239,255,251,40,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,111, + 255,255,185,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,5,214,255, + 255,78,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,71,255,255,220, + 7,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,178,255,255,120,0, + 0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,36,249,255,243,26,0,0, + 255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,136,255,255,161,0,0,255,255, + 255,48,0,0,0,0,255,255,255,48,0,0,0,0,13,230,255,255,56,0,255,255,255,48, + 0,0,0,0,255,255,255,48,0,0,0,0,0,95,255,255,201,2,255,255,255,48,0,0, + 0,0,255,255,255,48,0,0,0,0,0,1,201,255,255,96,255,255,255,48,0,0,0,0, + 255,255,255,48,0,0,0,0,0,0,55,255,255,231,255,255,255,48,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,161,255,255,255,255,255,48,0,0,0,0,255,255,255,48, + 0,0,0,0,0,0,0,25,243,255,255,255,255,48,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,120,255,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,7,220,255,255,255,48,0,0,0,0,0,0,0,0,2,76,159,213,245,254, + 244,212,157,75,2,0,0,0,0,0,0,0,0,0,0,0,0,67,216,255,255,255,255,255, + 255,255,255,255,213,65,0,0,0,0,0,0,0,0,0,0,125,255,255,255,255,219,172,158, + 174,223,255,255,255,254,118,0,0,0,0,0,0,0,0,120,255,255,255,191,52,0,0,0, + 0,0,57,199,255,255,255,115,0,0,0,0,0,0,59,253,255,255,148,2,0,0,0,0, + 0,0,0,4,158,255,255,252,55,0,0,0,0,0,202,255,255,186,2,0,0,0,0,0, + 0,0,0,0,3,191,255,255,199,0,0,0,0,60,255,255,253,37,0,0,0,0,0,0, + 0,0,0,0,0,42,254,255,255,57,0,0,0,143,255,255,184,0,0,0,0,0,0,0, + 0,0,0,0,0,0,189,255,255,142,0,0,0,201,255,255,113,0,0,0,0,0,0,0, + 0,0,0,0,0,0,115,255,255,200,0,0,0,237,255,255,71,0,0,0,0,0,0,0, + 0,0,0,0,0,0,72,255,255,235,0,0,0,254,255,255,53,0,0,0,0,0,0,0, + 0,0,0,0,0,0,53,255,255,252,0,0,0,250,255,255,55,0,0,0,0,0,0,0, + 0,0,0,0,0,0,56,255,255,249,0,0,0,232,255,255,78,0,0,0,0,0,0,0, + 0,0,0,0,0,0,80,255,255,231,0,0,0,190,255,255,126,0,0,0,0,0,0,0, + 0,0,0,0,0,0,129,255,255,190,0,0,0,128,255,255,202,0,0,0,0,0,0,0, + 0,0,0,0,0,0,207,255,255,127,0,0,0,40,255,255,255,58,0,0,0,0,0,0, + 0,0,0,0,0,66,255,255,255,39,0,0,0,0,176,255,255,206,7,0,0,0,0,0, + 0,0,0,0,11,214,255,255,176,0,0,0,0,0,36,245,255,255,170,7,0,0,0,0, + 0,0,0,11,182,255,255,245,36,0,0,0,0,0,0,87,254,255,255,205,61,0,0,0, + 0,1,69,213,255,255,255,88,0,0,0,0,0,0,0,0,93,249,255,255,255,223,172,158, + 175,226,255,255,255,249,94,0,0,0,0,0,0,0,0,0,0,49,199,255,255,255,255,255, + 255,255,255,255,198,49,0,0,0,0,0,0,0,0,0,0,0,0,0,63,151,211,244,254, + 241,208,150,63,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,249, + 226,176,91,4,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,209, + 36,0,0,0,0,0,255,255,255,175,156,156,156,156,156,169,219,255,255,255,226,19,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,80,244,255,255,145,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,111,255,255,238,2,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,24,255,255,255,30,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,0,2,255,255,255,44,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,28,255, + 255,255,28,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,118,255,255,232,1,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,86,248,255,255,135,0,0,0,0,255,255, + 255,177,156,156,156,156,156,168,217,255,255,255,218,12,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,197,27,0,0,0,0,0,255,255,255,255,255,255,255,255,255,250, + 226,174,83,2,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,2,76,159,213,245,254,244,212,157,75,2,0,0,0,0,0,0,0,0,0,0,0, + 0,67,216,255,255,255,255,255,255,255,255,255,213,65,0,0,0,0,0,0,0,0,0,0, + 125,255,255,255,255,218,172,158,174,222,255,255,255,254,118,0,0,0,0,0,0,0,0,120, + 255,255,255,190,51,0,0,0,0,0,57,197,255,255,255,115,0,0,0,0,0,0,59,253, + 255,255,146,2,0,0,0,0,0,0,0,3,153,255,255,252,55,0,0,0,0,0,202,255, + 255,182,1,0,0,0,0,0,0,0,0,0,2,188,255,255,199,0,0,0,0,60,255,255, + 252,34,0,0,0,0,0,0,0,0,0,0,0,40,254,255,255,57,0,0,0,143,255,255, + 181,0,0,0,0,0,0,0,0,0,0,0,0,0,184,255,255,142,0,0,0,201,255,255, + 110,0,0,0,0,0,0,0,0,0,0,0,0,0,113,255,255,200,0,0,0,237,255,255, + 69,0,0,0,0,0,0,0,0,0,0,0,0,0,70,255,255,235,0,0,0,254,255,255, + 52,0,0,0,0,0,0,0,0,0,0,0,0,0,52,255,255,252,0,0,0,252,255,255, + 56,0,0,0,0,0,0,0,0,0,0,0,0,0,57,255,255,249,0,0,0,233,255,255, + 81,0,0,0,0,0,0,0,0,0,0,0,0,0,82,255,255,228,0,0,0,194,255,255, + 128,0,0,0,0,0,0,0,0,0,0,0,0,0,130,255,255,187,0,0,0,134,255,255, + 207,0,0,0,0,0,0,0,0,0,0,0,0,0,208,255,255,121,0,0,0,51,255,255, + 255,64,0,0,0,0,0,0,0,0,0,0,0,66,255,255,252,30,0,0,0,0,194,255, + 255,212,9,0,0,0,0,0,0,0,0,0,9,213,255,255,152,0,0,0,0,0,50,251, + 255,255,180,9,0,0,0,0,0,0,0,9,180,255,255,217,14,0,0,0,0,0,0,108, + 255,255,255,211,65,0,0,0,0,0,65,211,255,255,212,29,0,0,0,0,0,0,0,0, + 113,252,255,255,255,225,173,172,192,225,255,255,255,251,167,156,156,156,156,0,0,0,0,0, + 0,56,204,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0, + 0,0,0,63,146,204,238,254,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255, + 255,255,255,255,255,255,255,244,212,152,55,0,0,0,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,164,10,0,0,0,0,0,255,255,255,175,156,156,156,156,157,176, + 232,255,255,255,184,3,0,0,0,0,255,255,255,48,0,0,0,0,0,0,4,114,254,255, + 255,97,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,154,255,255,193,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,68,255,255,241,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,52,255,255,252,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,94,255,255,226,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,11,211,255,255,157,0,0,0,0,255,255,255,48,0,0,0,0,1,21,83,210,255,255, + 249,41,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,253,95,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,206,61,0,0,0,0,0,0,255,255, + 255,215,255,255,255,221,155,144,110,50,0,0,0,0,0,0,0,0,255,255,255,51,169,255, + 255,253,96,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,5,179,255,255,254,101, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,8,187,255,255,255,107,0,0,0, + 0,0,0,0,0,0,255,255,255,48,0,0,10,195,255,255,255,113,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,14,203,255,255,255,119,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,18,210,255,255,255,125,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,0,23,217,255,255,255,131,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 28,223,255,255,255,137,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,34,228,255, + 255,255,144,1,0,0,0,0,1,76,166,219,248,254,236,193,119,18,0,0,0,0,0,0, + 0,0,27,195,255,255,255,255,255,255,255,255,239,83,0,0,0,0,0,0,13,216,255,255, + 255,210,167,162,195,252,255,255,254,83,0,0,0,0,0,127,255,255,234,59,0,0,0,0, + 33,198,255,255,239,17,0,0,0,0,214,255,255,100,0,0,0,0,0,0,19,238,255,255, + 112,0,0,0,0,250,255,255,53,0,0,0,0,0,0,0,143,255,255,183,0,0,0,0, + 246,255,255,92,0,0,0,0,0,0,0,45,124,124,105,0,0,0,0,197,255,255,225,37, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,89,255,255,255,246,148,52,1,0,0, + 0,0,0,0,0,0,0,0,0,0,148,255,255,255,255,255,222,138,53,1,0,0,0,0, + 0,0,0,0,0,0,97,232,255,255,255,255,255,255,220,114,9,0,0,0,0,0,0,0, + 0,0,9,100,193,253,255,255,255,255,255,221,49,0,0,0,0,0,0,0,0,0,0,0, + 24,106,190,253,255,255,255,238,34,0,0,0,0,0,0,0,0,0,0,0,0,0,28,153, + 255,255,255,172,0,0,0,32,32,32,5,0,0,0,0,0,0,0,0,135,255,255,250,9, + 0,0,235,255,255,67,0,0,0,0,0,0,0,0,26,255,255,255,41,0,0,176,255,255, + 147,0,0,0,0,0,0,0,0,5,255,255,255,42,0,0,84,255,255,248,48,0,0,0, + 0,0,0,0,61,255,255,253,12,0,0,2,200,255,255,237,90,3,0,0,0,0,44,219, + 255,255,184,0,0,0,0,31,227,255,255,255,236,181,159,166,205,255,255,255,246,45,0,0, + 0,0,0,26,183,255,255,255,255,255,255,255,255,255,229,62,0,0,0,0,0,0,0,0, + 57,146,206,240,254,249,227,182,104,12,0,0,0,0,0,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,0,0,156,156,156,156,156,156,156,255,255,255,175,156,156,156,156,156,156, + 156,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0, + 255,255,255,48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,249,255,255,52,0,0,0,0,0,0,0,0,0,4,255, + 255,255,41,0,0,0,227,255,255,73,0,0,0,0,0,0,0,0,0,24,255,255,255,19, + 0,0,0,185,255,255,122,0,0,0,0,0,0,0,0,0,71,255,255,233,0,0,0,0, + 120,255,255,207,1,0,0,0,0,0,0,0,0,158,255,255,168,0,0,0,0,29,251,255, + 255,103,0,0,0,0,0,0,0,52,251,255,255,71,0,0,0,0,0,146,255,255,251,115, + 6,0,0,0,1,83,237,255,255,191,0,0,0,0,0,0,9,200,255,255,255,238,178,159, + 172,226,255,255,255,226,27,0,0,0,0,0,0,0,13,167,255,255,255,255,255,255,255,255, + 255,193,29,0,0,0,0,0,0,0,0,0,0,54,149,208,242,254,245,215,160,70,1,0, + 0,0,0,0,1,219,255,255,126,0,0,0,0,0,0,0,0,0,0,0,0,98,255,255, + 200,0,0,126,255,255,217,0,0,0,0,0,0,0,0,0,0,0,0,189,255,255,105,0, + 0,33,255,255,255,52,0,0,0,0,0,0,0,0,0,0,26,253,255,249,19,0,0,0, + 192,255,255,143,0,0,0,0,0,0,0,0,0,0,115,255,255,172,0,0,0,0,99,255, + 255,231,3,0,0,0,0,0,0,0,0,0,206,255,255,76,0,0,0,0,14,246,255,255, + 69,0,0,0,0,0,0,0,0,42,255,255,234,4,0,0,0,0,0,164,255,255,161,0, + 0,0,0,0,0,0,0,133,255,255,144,0,0,0,0,0,0,69,255,255,242,9,0,0, + 0,0,0,0,1,223,255,255,48,0,0,0,0,0,0,3,229,255,255,87,0,0,0,0, + 0,0,59,255,255,209,0,0,0,0,0,0,0,0,136,255,255,178,0,0,0,0,0,0, + 150,255,255,115,0,0,0,0,0,0,0,0,41,255,255,251,19,0,0,0,0,5,237,255, + 252,24,0,0,0,0,0,0,0,0,0,204,255,255,105,0,0,0,0,77,255,255,180,0, + 0,0,0,0,0,0,0,0,0,108,255,255,195,0,0,0,0,169,255,255,84,0,0,0, + 0,0,0,0,0,0,0,19,249,255,255,32,0,0,13,246,255,240,8,0,0,0,0,0, + 0,0,0,0,0,0,175,255,255,123,0,0,94,255,255,152,0,0,0,0,0,0,0,0, + 0,0,0,0,80,255,255,213,0,0,186,255,255,56,0,0,0,0,0,0,0,0,0,0, + 0,0,5,235,255,255,49,24,253,255,218,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,147,255,255,139,112,255,255,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52, + 255,255,229,206,255,254,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,212,255, + 255,255,255,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,118,255,255,255, + 255,94,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,253,255,255,244,12, + 0,0,0,0,0,0,0,0,0,132,255,255,186,0,0,0,0,0,0,0,0,0,156,255, + 255,255,100,0,0,0,0,0,0,0,0,1,231,255,255,40,0,0,58,255,255,247,8,0, + 0,0,0,0,0,0,0,224,255,255,255,169,0,0,0,0,0,0,0,0,45,255,255,222, + 0,0,0,3,239,255,255,69,0,0,0,0,0,0,0,32,255,255,255,255,233,1,0,0, + 0,0,0,0,0,114,255,255,149,0,0,0,0,168,255,255,139,0,0,0,0,0,0,0, + 100,255,255,237,255,255,44,0,0,0,0,0,0,0,183,255,255,76,0,0,0,0,96,255, + 255,207,0,0,0,0,0,0,0,164,255,253,118,255,255,111,0,0,0,0,0,0,7,246, + 255,248,11,0,0,0,0,23,255,255,255,22,0,0,0,0,0,0,231,255,203,31,255,255, + 176,0,0,0,0,0,0,66,255,255,186,0,0,0,0,0,0,205,255,255,90,0,0,0, + 0,0,41,255,255,133,0,220,255,241,3,0,0,0,0,0,134,255,255,112,0,0,0,0, + 0,0,132,255,255,159,0,0,0,0,0,107,255,255,64,0,150,255,255,53,0,0,0,0, + 0,205,255,255,40,0,0,0,0,0,0,59,255,255,228,0,0,0,0,0,172,255,245,6, + 0,82,255,255,120,0,0,0,0,21,254,255,222,0,0,0,0,0,0,0,3,239,255,255, + 42,0,0,0,2,237,255,181,0,0,16,253,255,186,0,0,0,0,88,255,255,149,0,0, + 0,0,0,0,0,0,168,255,255,111,0,0,0,48,255,255,113,0,0,0,201,255,246,6, + 0,0,0,158,255,255,76,0,0,0,0,0,0,0,0,96,255,255,181,0,0,0,115,255, + 255,45,0,0,0,132,255,255,63,0,0,0,227,255,248,11,0,0,0,0,0,0,0,0, + 23,255,255,244,6,0,0,180,255,231,1,0,0,0,63,255,255,128,0,0,41,255,255,186, + 0,0,0,0,0,0,0,0,0,0,206,255,255,63,0,4,241,255,162,0,0,0,0,6, + 245,255,196,0,0,110,255,255,113,0,0,0,0,0,0,0,0,0,0,132,255,255,133,0, + 57,255,255,93,0,0,0,0,0,181,255,250,11,0,179,255,255,40,0,0,0,0,0,0, + 0,0,0,0,60,255,255,203,0,122,255,255,25,0,0,0,0,0,114,255,255,71,6,244, + 255,223,0,0,0,0,0,0,0,0,0,0,0,3,239,255,253,18,188,255,211,0,0,0, + 0,0,0,45,255,255,139,62,255,255,150,0,0,0,0,0,0,0,0,0,0,0,0,169, + 255,255,93,246,255,142,0,0,0,0,0,0,1,232,255,204,132,255,255,76,0,0,0,0, + 0,0,0,0,0,0,0,0,96,255,255,214,255,255,74,0,0,0,0,0,0,0,164,255, + 253,216,255,249,11,0,0,0,0,0,0,0,0,0,0,0,0,24,255,255,255,255,250,11, + 0,0,0,0,0,0,0,96,255,255,255,255,187,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,206,255,255,255,191,0,0,0,0,0,0,0,0,28,255,255,255,255,114,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,132,255,255,255,122,0,0,0,0,0,0,0, + 0,0,214,255,255,255,40,0,0,0,0,0,0,0,0,61,253,255,255,124,0,0,0,0, + 0,0,0,0,0,6,209,255,255,146,0,0,0,139,255,255,250,44,0,0,0,0,0,0, + 0,0,135,255,255,214,9,0,0,0,8,212,255,255,204,4,0,0,0,0,0,0,58,253, + 255,250,50,0,0,0,0,0,51,251,255,255,120,0,0,0,0,0,11,219,255,255,121,0, + 0,0,0,0,0,0,130,255,255,248,40,0,0,0,0,150,255,255,196,3,0,0,0,0, + 0,0,0,5,205,255,255,200,3,0,0,70,255,255,242,33,0,0,0,0,0,0,0,0, + 0,44,249,255,255,116,0,16,229,255,255,96,0,0,0,0,0,0,0,0,0,0,0,117, + 255,255,246,37,166,255,255,173,0,0,0,0,0,0,0,0,0,0,0,0,3,196,255,255, + 225,255,255,230,19,0,0,0,0,0,0,0,0,0,0,0,0,0,37,245,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,116,255,255,255,156,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,1,180,255,255,255,200,3,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,103,255,255,255,255,255,121,0,0,0,0,0,0,0,0, + 0,0,0,0,0,37,245,255,250,147,255,255,249,45,0,0,0,0,0,0,0,0,0,0, + 0,4,202,255,255,124,1,191,255,255,206,5,0,0,0,0,0,0,0,0,0,0,129,255, + 255,202,4,0,35,245,255,255,130,0,0,0,0,0,0,0,0,0,55,252,255,247,40,0, + 0,0,110,255,255,252,51,0,0,0,0,0,0,0,11,219,255,255,112,0,0,0,0,2, + 195,255,255,214,8,0,0,0,0,0,0,154,255,255,192,2,0,0,0,0,0,37,246,255, + 255,139,0,0,0,0,0,77,255,255,243,32,0,0,0,0,0,0,0,113,255,255,253,61, + 0,0,0,22,233,255,255,102,0,0,0,0,0,0,0,0,3,200,255,255,219,11,0,0, + 178,255,255,182,1,0,0,0,0,0,0,0,0,0,40,248,255,255,150,0,4,204,255,255, + 238,22,0,0,0,0,0,0,0,0,0,0,128,255,255,235,21,0,46,250,255,255,166,0, + 0,0,0,0,0,0,0,0,39,249,255,255,88,0,0,0,128,255,255,255,72,0,0,0, + 0,0,0,0,1,194,255,255,176,0,0,0,0,6,210,255,255,222,10,0,0,0,0,0, + 0,99,255,255,240,24,0,0,0,0,0,52,252,255,255,136,0,0,0,0,0,22,238,255, + 255,96,0,0,0,0,0,0,0,136,255,255,252,48,0,0,0,0,165,255,255,184,0,0, + 0,0,0,0,0,0,8,216,255,255,202,3,0,0,71,255,255,242,30,0,0,0,0,0, + 0,0,0,0,58,254,255,255,108,0,10,222,255,255,104,0,0,0,0,0,0,0,0,0, + 0,0,144,255,255,243,29,136,255,255,191,1,0,0,0,0,0,0,0,0,0,0,0,11, + 221,255,255,201,251,255,245,35,0,0,0,0,0,0,0,0,0,0,0,0,0,65,255,255, + 255,255,255,112,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,152,255,255,255,198, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,255,255,255,62,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,156,156,156,156,156,156, + 156,156,156,156,163,255,255,255,212,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131, + 255,255,252,52,0,0,0,0,0,0,0,0,0,0,0,0,0,46,251,255,255,133,0,0, + 0,0,0,0,0,0,0,0,0,0,0,3,204,255,255,212,7,0,0,0,0,0,0,0, + 0,0,0,0,0,0,116,255,255,252,52,0,0,0,0,0,0,0,0,0,0,0,0,0, + 35,246,255,255,133,0,0,0,0,0,0,0,0,0,0,0,0,0,1,190,255,255,213,7, + 0,0,0,0,0,0,0,0,0,0,0,0,0,102,255,255,253,55,0,0,0,0,0,0, + 0,0,0,0,0,0,0,26,240,255,255,139,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,175,255,255,216,8,0,0,0,0,0,0,0,0,0,0,0,0,0,85,255,255,253, + 56,0,0,0,0,0,0,0,0,0,0,0,0,0,19,233,255,255,139,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,161,255,255,216,8,0,0,0,0,0,0,0,0,0,0, + 0,0,0,70,255,255,253,56,0,0,0,0,0,0,0,0,0,0,0,0,0,11,224,255, + 255,139,0,0,0,0,0,0,0,0,0,0,0,0,0,0,145,255,255,216,8,0,0,0, + 0,0,0,0,0,0,0,0,0,0,57,254,255,253,56,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,212,255,255,234,156,156,156,156,156,156,156,156,156,156,156,156,0,0,0, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,156,156,156,156,156,156,156,156,78, + 0,0,0,0,255,255,255,255,255,255,255,255,128,0,0,0,0,255,255,255,255,255,255,255, + 255,128,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0, + 0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0, + 0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0, + 255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,175,156,156,156,156,78, + 0,0,0,0,255,255,255,255,255,255,255,255,128,0,0,0,0,255,255,255,255,255,255,255, + 255,128,0,0,63,128,128,28,0,0,0,0,0,0,0,0,0,66,255,255,114,0,0,0, + 0,0,0,0,0,0,5,240,255,190,0,0,0,0,0,0,0,0,0,0,168,255,252,16, + 0,0,0,0,0,0,0,0,0,90,255,255,90,0,0,0,0,0,0,0,0,0,17,252, + 255,166,0,0,0,0,0,0,0,0,0,0,191,255,240,4,0,0,0,0,0,0,0,0, + 0,114,255,255,66,0,0,0,0,0,0,0,0,0,37,255,255,142,0,0,0,0,0,0, + 0,0,0,0,214,255,219,0,0,0,0,0,0,0,0,0,0,138,255,255,42,0,0,0, + 0,0,0,0,0,0,60,255,255,118,0,0,0,0,0,0,0,0,0,3,235,255,195,0, + 0,0,0,0,0,0,0,0,0,161,255,253,21,0,0,0,0,0,0,0,0,0,83,255, + 255,95,0,0,0,0,0,0,0,0,0,13,249,255,172,0,0,0,0,0,0,0,0,0, + 0,185,255,243,7,0,0,0,0,0,0,0,0,0,106,255,255,70,0,0,0,0,0,0, + 0,0,0,30,255,255,148,0,0,0,0,0,0,0,0,0,0,208,255,225,0,0,0,0, + 0,0,0,0,0,0,130,255,255,46,0,0,0,0,0,0,0,0,0,52,255,255,125,0, + 0,0,0,0,0,0,0,0,1,230,255,202,0,0,0,0,0,0,0,0,0,0,154,255, + 254,24,0,0,0,0,0,0,0,0,0,77,255,255,101,0,0,0,0,0,0,0,0,0, + 9,245,255,178,0,0,78,156,156,156,156,156,156,156,156,29,0,0,0,128,255,255,255,255, + 255,255,255,255,48,0,0,0,128,255,255,255,255,255,255,255,255,48,0,0,0,0,0,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0, + 255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255,48,0, + 0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,78,156,156,156,156,156,255,255,255,48,0,0,0,128,255,255,255,255, + 255,255,255,255,48,0,0,0,128,255,255,255,255,255,255,255,255,48,0,0,0,0,0,0, + 0,0,60,128,128,74,0,0,0,0,0,0,0,0,0,0,0,11,225,255,255,241,24,0, + 0,0,0,0,0,0,0,0,0,137,255,254,248,255,166,0,0,0,0,0,0,0,0,0, + 44,251,255,141,112,255,255,66,0,0,0,0,0,0,0,1,196,255,216,8,2,193,255,217, + 6,0,0,0,0,0,0,96,255,252,52,0,0,34,244,255,123,0,0,0,0,0,18,234, + 255,132,0,0,0,0,104,255,246,34,0,0,0,0,153,255,208,6,0,0,0,0,1,186, + 255,182,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,156,156,156,156,156,156,156, + 156,156,156,156,156,156,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,0,5,179,255,255,255,144,0,0,0,0,0,0,7,184,255, + 255,253,56,0,0,0,0,0,0,9,189,255,255,212,7,0,0,0,0,0,0,10,194,255, + 255,130,0,0,0,0,0,0,0,14,199,255,251,46,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,74,169,223,249,253,232,183,89,1,0,0,0,0,0,11,177,255,255,255,255,255,255,255, + 255,177,5,0,0,0,0,172,255,255,255,208,163,167,222,255,255,255,123,0,0,0,51,255, + 255,250,75,0,0,0,3,164,255,255,229,0,0,0,104,224,224,147,0,0,0,0,0,36, + 255,255,255,27,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,46,0,0,0,0, + 0,0,0,23,65,107,148,190,255,255,255,48,0,0,0,0,48,155,228,255,255,255,255,255, + 255,255,255,48,0,0,1,138,254,255,255,255,227,184,144,102,255,255,255,48,0,0,106,255, + 255,240,104,20,0,0,0,0,255,255,255,48,0,0,215,255,255,94,0,0,0,0,0,4, + 255,255,255,48,0,0,253,255,255,55,0,0,0,0,0,72,255,255,255,48,0,0,238,255, + 255,147,1,0,0,0,67,233,255,255,255,48,0,0,161,255,255,255,213,163,167,214,255,236, + 255,255,255,52,0,0,25,220,255,255,255,255,255,255,243,67,255,255,255,74,0,0,0,16, + 132,212,249,250,222,150,33,0,226,255,255,114,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,48,0,78,181,234,252,235,181,76,0,0,0,0,0,0,255,255,255, + 50,158,255,255,255,255,255,255,255,164,5,0,0,0,0,255,255,255,162,255,252,191,160,180, + 242,255,255,255,151,0,0,0,0,255,255,255,254,214,39,0,0,0,17,176,255,255,255,55, + 0,0,0,255,255,255,244,31,0,0,0,0,0,6,212,255,255,163,0,0,0,255,255,255, + 149,0,0,0,0,0,0,0,96,255,255,235,0,0,0,255,255,255,83,0,0,0,0,0, + 0,0,31,255,255,255,24,0,0,255,255,255,53,0,0,0,0,0,0,0,5,255,255,255, + 43,0,0,255,255,255,53,0,0,0,0,0,0,0,6,255,255,255,41,0,0,255,255,255, + 81,0,0,0,0,0,0,0,35,255,255,255,19,0,0,255,255,255,149,0,0,0,0,0, + 0,0,102,255,255,228,0,0,0,255,255,255,244,31,0,0,0,0,0,9,218,255,255,151, + 0,0,0,255,255,255,255,214,39,0,0,0,19,181,255,255,252,40,0,0,0,255,255,255, + 186,255,252,191,160,180,244,255,255,255,127,0,0,0,0,255,255,255,55,186,255,255,255,255, + 255,255,255,140,1,0,0,0,0,255,255,255,48,3,102,198,244,253,229,167,58,0,0,0, + 0,0,0,0,0,6,100,190,238,254,239,193,107,7,0,0,0,0,0,0,42,214,255,255, + 255,255,255,255,255,218,42,0,0,0,0,38,237,255,255,253,192,160,183,247,255,255,233,27, + 0,0,2,205,255,255,216,41,0,0,0,24,194,255,255,169,0,0,84,255,255,246,33,0, + 0,0,0,0,19,240,255,253,22,0,173,255,255,149,0,0,0,0,0,0,0,101,148,148, + 41,0,225,255,255,83,0,0,0,0,0,0,0,0,0,0,0,0,250,255,255,54,0,0, + 0,0,0,0,0,0,0,0,0,0,250,255,255,54,0,0,0,0,0,0,0,0,0,0, + 0,0,223,255,255,87,0,0,0,0,0,0,0,0,0,0,0,0,166,255,255,156,0,0, + 0,0,0,0,0,90,140,140,43,0,76,255,255,248,37,0,0,0,0,0,15,235,255,254, + 27,0,1,198,255,255,218,43,0,0,0,22,189,255,255,171,0,0,0,33,233,255,255,253, + 193,160,181,246,255,255,230,27,0,0,0,0,38,211,255,255,255,255,255,255,255,215,40,0, + 0,0,0,0,0,5,100,188,238,254,239,191,103,6,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,48,0,0,0,0,0,37,151,220,250,249,211,128,14,0,255,255,255,48,0,0, + 0,0,99,249,255,255,255,255,255,255,218,27,255,255,255,48,0,0,0,84,255,255,255,251, + 188,159,181,246,255,188,255,255,255,48,0,0,14,234,255,255,207,33,0,0,0,21,185,255, + 255,255,255,48,0,0,105,255,255,242,26,0,0,0,0,0,9,218,255,255,255,48,0,0, + 181,255,255,148,0,0,0,0,0,0,0,102,255,255,255,48,0,0,228,255,255,83,0,0, + 0,0,0,0,0,34,255,255,255,48,0,0,250,255,255,54,0,0,0,0,0,0,0,5, + 255,255,255,48,0,0,251,255,255,52,0,0,0,0,0,0,0,5,255,255,255,48,0,0, + 231,255,255,79,0,0,0,0,0,0,0,34,255,255,255,48,0,0,186,255,255,142,0,0, + 0,0,0,0,0,101,255,255,255,48,0,0,110,255,255,239,23,0,0,0,0,0,8,217, + 255,255,255,48,0,0,16,237,255,255,203,32,0,0,0,20,181,255,255,255,255,48,0,0, + 0,89,255,255,255,250,189,159,180,244,255,196,255,255,255,48,0,0,0,0,107,251,255,255, + 255,255,255,255,213,27,255,255,255,48,0,0,0,0,0,42,155,223,250,245,202,115,9,0, + 255,255,255,48,0,0,0,0,0,5,92,179,231,252,246,209,138,30,0,0,0,0,0,0, + 0,44,212,255,255,255,255,255,255,255,246,99,0,0,0,0,0,43,239,255,255,253,194,159, + 173,237,255,255,255,93,0,0,0,4,211,255,255,215,42,0,0,0,10,162,255,255,242,21, + 0,0,88,255,255,244,31,0,0,0,0,0,4,210,255,255,124,0,0,175,255,255,149,0, + 0,0,0,0,0,0,110,255,255,196,0,0,226,255,255,195,156,156,156,156,156,156,156,184, + 255,255,238,0,0,250,255,255,255,255,255,255,255,255,255,255,255,255,255,253,0,0,250,255, + 255,255,255,255,255,255,255,255,255,255,255,255,251,0,0,223,255,255,83,0,0,0,0,0, + 0,0,0,0,0,0,0,0,167,255,255,149,0,0,0,0,0,0,0,0,0,0,0,0, + 0,77,255,255,245,33,0,0,0,0,0,0,122,180,180,83,0,0,1,197,255,255,216,42, + 0,0,0,3,113,255,255,245,25,0,0,0,32,231,255,255,253,198,160,170,228,255,255,255, + 97,0,0,0,0,0,34,204,255,255,255,255,255,255,255,248,102,0,0,0,0,0,0,0, + 3,89,179,231,253,247,212,139,32,0,0,0,0,0,0,0,0,0,12,124,208,245,254,227, + 0,0,0,0,15,210,255,255,255,255,255,0,0,0,0,139,255,255,255,211,160,172,0,0, + 0,0,221,255,255,163,1,0,0,0,0,0,0,253,255,255,64,0,0,0,0,0,0,0, + 255,255,255,48,0,0,0,0,255,255,255,255,255,255,255,255,255,128,0,255,255,255,255,255, + 255,255,255,255,128,0,156,156,156,255,255,255,177,156,156,78,0,0,0,0,255,255,255,48, + 0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,255,255,255,48,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0, + 0,255,255,255,48,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,34,146,217,250,250, + 215,134,18,0,255,255,255,48,0,0,0,0,97,248,255,255,255,255,255,255,228,36,255,255, + 255,48,0,0,0,82,255,255,255,251,189,159,180,244,255,203,255,255,255,48,0,0,12,233, + 255,255,210,36,0,0,0,21,184,255,255,255,255,48,0,0,103,255,255,243,28,0,0,0, + 0,0,9,216,255,255,255,48,0,0,180,255,255,148,0,0,0,0,0,0,0,99,255,255, + 255,48,0,0,227,255,255,82,0,0,0,0,0,0,0,32,255,255,255,48,0,0,250,255, + 255,53,0,0,0,0,0,0,0,5,255,255,255,48,0,0,251,255,255,53,0,0,0,0, + 0,0,0,6,255,255,255,48,0,0,231,255,255,81,0,0,0,0,0,0,0,37,255,255, + 255,48,0,0,187,255,255,149,0,0,0,0,0,0,0,106,255,255,255,48,0,0,113,255, + 255,244,31,0,0,0,0,0,12,221,255,255,255,48,0,0,18,239,255,255,214,39,0,0, + 0,21,187,255,255,255,255,48,0,0,0,96,255,255,255,252,191,160,180,246,255,181,255,255, + 255,48,0,0,0,0,113,252,255,255,255,255,255,255,205,22,255,255,255,48,0,0,0,0, + 0,45,160,226,253,245,201,110,6,3,255,255,255,40,0,0,0,0,0,0,0,0,0,0, + 0,0,0,36,255,255,253,11,0,0,54,120,120,64,0,0,0,0,0,0,0,129,255,255, + 200,0,0,0,49,255,255,239,70,0,0,0,0,1,95,250,255,255,90,0,0,0,0,153, + 255,255,255,219,173,158,172,227,255,255,255,174,1,0,0,0,0,4,145,254,255,255,255,255, + 255,255,255,255,156,7,0,0,0,0,0,0,0,47,145,208,244,254,245,213,150,52,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,54,163, + 227,253,243,196,94,1,0,0,0,0,0,255,255,255,48,130,255,255,255,255,255,255,255,164, + 1,0,0,0,0,255,255,255,166,255,254,198,160,177,243,255,255,255,95,0,0,0,0,255, + 255,255,255,216,47,0,0,0,24,219,255,255,199,0,0,0,0,255,255,255,241,29,0,0, + 0,0,0,99,255,255,246,0,0,0,0,255,255,255,136,0,0,0,0,0,0,54,255,255, + 255,0,0,0,0,255,255,255,72,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255, + 255,255,53,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48,255,255, + 255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48,255,255, + 255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0, + 0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0, + 0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0, + 0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0, + 0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0, + 0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0, + 0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,255,255,255,48,0,0,0,0,1,255,255,255,48,0,0,0, + 0,42,255,255,255,34,0,0,157,144,224,255,255,231,2,0,0,255,255,255,255,255,97,0, + 0,0,227,254,243,193,80,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,5,181,255, + 255,245,63,0,0,255,255,255,48,0,0,0,3,169,255,255,246,65,0,0,0,255,255,255, + 48,0,0,1,158,255,255,247,67,0,0,0,0,255,255,255,48,0,0,146,255,255,248,69, + 0,0,0,0,0,255,255,255,48,0,133,255,255,249,72,0,0,0,0,0,0,255,255,255, + 48,121,255,255,250,75,0,0,0,0,0,0,0,255,255,255,155,255,255,250,78,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,114,0,0,0,0,0,0,0,0,0,255,255,255, + 226,255,255,227,31,0,0,0,0,0,0,0,0,255,255,255,66,213,255,255,219,22,0,0, + 0,0,0,0,0,255,255,255,48,29,226,255,255,208,16,0,0,0,0,0,0,255,255,255, + 48,0,41,237,255,255,196,10,0,0,0,0,0,255,255,255,48,0,0,55,246,255,255,182, + 5,0,0,0,0,255,255,255,48,0,0,0,74,251,255,255,167,2,0,0,0,255,255,255, + 48,0,0,0,0,94,255,255,255,151,0,0,0,255,255,255,48,0,0,0,0,0,117,255, + 255,255,134,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,69,181,238,252,225,147, + 21,0,0,2,92,190,240,252,222,140,17,0,0,0,0,0,0,255,255,255,49,147,255,255, + 255,255,255,255,225,26,15,189,255,255,255,255,255,255,219,22,0,0,0,0,0,255,255,255, + 173,255,248,182,161,213,255,255,255,170,187,255,248,184,161,213,255,255,255,162,0,0,0,0, + 0,255,255,255,255,197,27,0,0,1,156,255,255,255,255,202,30,0,0,1,159,255,255,249, + 8,0,0,0,0,255,255,255,232,18,0,0,0,0,37,255,255,255,234,20,0,0,0,0, + 37,255,255,255,41,0,0,0,0,255,255,255,126,0,0,0,0,0,3,255,255,255,128,0, + 0,0,0,0,3,255,255,255,48,0,0,0,0,255,255,255,69,0,0,0,0,0,0,255, + 255,255,70,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,51,0,0,0, + 0,0,0,255,255,255,53,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,255,255,255,48,0,0,0, + 0,255,255,255,48,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0, + 0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,255,255,255,48,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,255,255,255,48,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,255,255,255,48,0,0,0, + 0,255,255,255,48,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,255,255,255,48,0,54,163,227,253,243,196,94,1,0,0,0,0,0,255, + 255,255,48,130,255,255,255,255,255,255,255,164,1,0,0,0,0,255,255,255,166,255,254,198, + 160,177,243,255,255,255,95,0,0,0,0,255,255,255,255,216,47,0,0,0,24,219,255,255, + 199,0,0,0,0,255,255,255,241,29,0,0,0,0,0,99,255,255,246,0,0,0,0,255, + 255,255,136,0,0,0,0,0,0,54,255,255,255,0,0,0,0,255,255,255,72,0,0,0, + 0,0,0,48,255,255,255,0,0,0,0,255,255,255,53,0,0,0,0,0,0,48,255,255, + 255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48,255,255, + 255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48,255,255, + 255,0,0,0,0,0,0,1,77,169,224,250,250,225,172,84,3,0,0,0,0,0,0,0, + 30,195,255,255,255,255,255,255,255,255,205,37,0,0,0,0,0,30,228,255,255,255,212,163, + 165,213,255,255,255,236,39,0,0,0,2,195,255,255,234,71,0,0,0,0,70,234,255,255, + 206,3,0,0,82,255,255,251,52,0,0,0,0,0,0,48,250,255,255,86,0,0,173,255, + 255,161,0,0,0,0,0,0,0,0,158,255,255,173,0,0,227,255,255,84,0,0,0,0, + 0,0,0,0,84,255,255,226,0,0,252,255,255,53,0,0,0,0,0,0,0,0,53,255, + 255,250,0,0,249,255,255,56,0,0,0,0,0,0,0,0,56,255,255,248,0,0,221,255, + 255,92,0,0,0,0,0,0,0,0,94,255,255,221,0,0,161,255,255,173,0,0,0,0, + 0,0,0,0,176,255,255,165,0,0,68,255,255,254,62,0,0,0,0,0,0,67,255,255, + 255,72,0,0,0,186,255,255,240,80,0,0,0,0,82,241,255,255,191,1,0,0,0,23, + 223,255,255,255,217,164,167,216,255,255,255,228,29,0,0,0,0,0,26,190,255,255,255,255, + 255,255,255,255,195,30,0,0,0,0,0,0,0,1,76,169,225,251,252,227,171,81,2,0, + 0,0,0,0,0,255,255,255,48,4,97,189,238,252,233,176,72,0,0,0,0,0,0,255, + 255,255,68,201,255,255,255,255,255,255,255,159,4,0,0,0,0,255,255,255,224,255,252,191, + 160,180,242,255,255,255,147,0,0,0,0,255,255,255,255,214,39,0,0,0,17,176,255,255, + 255,53,0,0,0,255,255,255,244,31,0,0,0,0,0,6,212,255,255,163,0,0,0,255, + 255,255,149,0,0,0,0,0,0,0,96,255,255,235,0,0,0,255,255,255,83,0,0,0, + 0,0,0,0,31,255,255,255,23,0,0,255,255,255,53,0,0,0,0,0,0,0,5,255, + 255,255,43,0,0,255,255,255,53,0,0,0,0,0,0,0,6,255,255,255,41,0,0,255, + 255,255,81,0,0,0,0,0,0,0,35,255,255,255,19,0,0,255,255,255,149,0,0,0, + 0,0,0,0,102,255,255,228,0,0,0,255,255,255,244,31,0,0,0,0,0,9,218,255, + 255,149,0,0,0,255,255,255,255,214,39,0,0,0,19,181,255,255,251,38,0,0,0,255, + 255,255,208,255,252,191,160,180,244,255,255,255,123,0,0,0,0,255,255,255,63,202,255,255, + 255,255,255,255,255,134,1,0,0,0,0,255,255,255,48,7,113,203,246,250,225,163,54,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,147,218,250,250, + 215,138,21,0,255,255,255,48,0,0,0,0,94,248,255,255,255,255,255,255,230,42,255,255, + 255,48,0,0,0,80,254,255,255,249,185,159,180,244,255,212,255,255,255,48,0,0,12,233, + 255,255,204,31,0,0,0,20,182,255,255,255,255,48,0,0,103,255,255,241,25,0,0,0, + 0,0,8,217,255,255,255,48,0,0,180,255,255,147,0,0,0,0,0,0,0,100,255,255, + 255,48,0,0,227,255,255,82,0,0,0,0,0,0,0,33,255,255,255,48,0,0,250,255, + 255,54,0,0,0,0,0,0,0,5,255,255,255,48,0,0,251,255,255,52,0,0,0,0, + 0,0,0,5,255,255,255,48,0,0,231,255,255,79,0,0,0,0,0,0,0,33,255,255, + 255,48,0,0,185,255,255,142,0,0,0,0,0,0,0,100,255,255,255,48,0,0,109,255, + 255,239,23,0,0,0,0,0,7,215,255,255,255,48,0,0,16,237,255,255,203,32,0,0, + 0,17,178,255,255,255,255,48,0,0,0,89,255,255,255,250,189,159,180,244,255,219,255,255, + 255,48,0,0,0,0,108,251,255,255,255,255,255,255,226,42,255,255,255,48,0,0,0,0, + 0,40,154,222,250,246,207,124,14,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,48,0,0,0,255,255,255,48,14,138,223,253,128,0,0,255,255,255,65,212,255,255,255, + 128,0,0,255,255,255,206,255,239,178,157,83,0,0,255,255,255,255,159,11,0,0,0,0, + 0,255,255,255,210,4,0,0,0,0,0,0,255,255,255,113,0,0,0,0,0,0,0,255, + 255,255,68,0,0,0,0,0,0,0,255,255,255,54,0,0,0,0,0,0,0,255,255,255, + 48,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,255,255,255,48,0, + 0,0,0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,255,255,255,48,0,0,0, + 0,0,0,0,255,255,255,48,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,255,255,255,48,0,0,0,0,0,0,0,0,4,99,187,235,254,248,215,147,36,0, + 0,0,0,0,13,195,255,255,255,255,255,255,255,249,92,0,0,0,0,146,255,255,255,204, + 160,173,238,255,255,252,51,0,0,0,232,255,255,132,0,0,0,17,202,255,255,167,0,0, + 0,252,255,255,55,0,0,0,0,64,196,196,170,0,0,0,214,255,255,163,6,0,0,0, + 0,0,0,0,0,0,0,98,255,255,255,230,150,83,18,0,0,0,0,0,0,0,0,117, + 249,255,255,255,255,254,201,99,4,0,0,0,0,0,0,30,129,204,253,255,255,255,255,194, + 13,0,0,0,0,0,0,0,0,13,69,145,245,255,255,159,0,0,38,56,56,27,0,0, + 0,0,0,65,255,255,251,14,0,146,255,255,170,0,0,0,0,0,8,255,255,255,44,0, + 69,255,255,254,99,0,0,0,0,111,255,255,255,24,0,1,187,255,255,255,217,167,162,208, + 255,255,255,192,0,0,0,17,190,255,255,255,255,255,255,255,255,217,30,0,0,0,0,1, + 82,174,227,251,253,235,190,107,10,0,0,0,0,0,0,0,40,136,37,0,0,0,0,0, + 0,0,0,200,255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0, + 255,255,255,255,255,255,255,255,255,255,128,0,255,255,255,255,255,255,255,255,255,255,128,0, + 156,156,156,255,255,255,177,156,156,156,78,0,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,255,255,255,48,0,0,0,0,0, + 0,0,0,255,255,255,48,0,0,0,0,0,0,0,0,250,255,255,61,0,0,0,0,0, + 0,0,0,217,255,255,156,1,0,0,0,0,0,0,0,137,255,255,255,211,162,172,107,0, + 0,0,0,16,217,255,255,255,255,255,128,0,0,0,0,0,16,137,214,248,254,231,95,0, + 0,255,255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0, + 0,0,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48, + 255,255,255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0, + 0,255,255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0, + 0,0,0,0,0,48,255,255,255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48, + 255,255,255,0,0,0,0,255,255,255,48,0,0,0,0,0,0,48,255,255,255,0,0,0, + 0,255,255,255,48,0,0,0,0,0,0,53,255,255,255,0,0,0,0,255,255,255,48,0, + 0,0,0,0,0,73,255,255,255,0,0,0,0,255,255,255,54,0,0,0,0,0,0,139, + 255,255,255,0,0,0,0,244,255,255,100,0,0,0,0,0,32,243,255,255,255,0,0,0, + 0,193,255,255,220,25,0,0,0,51,220,255,255,255,255,0,0,0,0,83,255,255,255,243, + 177,161,200,255,255,158,255,255,255,0,0,0,0,0,143,255,255,255,255,255,255,253,122,48, + 255,255,255,0,0,0,0,0,0,79,189,242,253,228,163,49,0,48,255,255,255,0,0,122, + 255,255,213,0,0,0,0,0,0,0,0,216,255,255,90,27,252,255,255,46,0,0,0,0, + 0,0,50,255,255,239,8,0,179,255,255,137,0,0,0,0,0,0,138,255,255,147,0,0, + 78,255,255,226,1,0,0,0,0,1,225,255,255,48,0,0,4,232,255,255,62,0,0,0, + 0,60,255,255,204,0,0,0,0,135,255,255,151,0,0,0,0,148,255,255,104,0,0,0, + 0,37,255,255,237,5,0,0,4,233,255,246,15,0,0,0,0,0,192,255,255,76,0,0, + 69,255,255,162,0,0,0,0,0,0,92,255,255,166,0,0,158,255,255,62,0,0,0,0, + 0,0,10,240,255,245,11,7,240,255,218,1,0,0,0,0,0,0,0,148,255,255,90,80, + 255,255,119,0,0,0,0,0,0,0,0,49,255,255,181,168,255,252,24,0,0,0,0,0, + 0,0,0,0,205,255,251,247,255,176,0,0,0,0,0,0,0,0,0,0,105,255,255,255, + 255,76,0,0,0,0,0,0,0,0,0,0,16,246,255,255,229,4,0,0,0,0,0,0, + 0,0,0,0,0,162,255,255,134,0,0,0,0,0,0,51,255,255,248,8,0,0,0,0, + 0,174,255,255,82,0,0,0,0,0,92,255,255,175,1,231,255,255,64,0,0,0,0,7, + 245,255,255,159,0,0,0,0,0,154,255,255,100,0,157,255,255,125,0,0,0,0,73,255, + 255,255,235,2,0,0,0,0,217,255,255,26,0,81,255,255,188,0,0,0,0,150,255,255, + 255,255,58,0,0,0,24,255,255,207,0,0,13,250,255,246,6,0,0,1,226,255,193,255, + 255,135,0,0,0,84,255,255,131,0,0,0,188,255,255,60,0,0,48,255,255,86,205,255, + 212,0,0,0,148,255,255,57,0,0,0,112,255,255,124,0,0,126,255,249,13,126,255,255, + 33,0,0,212,255,235,3,0,0,0,38,255,255,185,0,0,203,255,182,0,46,255,255,110, + 0,18,254,255,162,0,0,0,0,0,218,255,244,5,25,255,255,102,0,0,225,255,187,0, + 80,255,255,88,0,0,0,0,0,144,255,255,55,102,255,254,24,0,0,146,255,250,14,142, + 255,252,17,0,0,0,0,0,69,255,255,119,178,255,198,0,0,0,67,255,255,86,204,255, + 195,0,0,0,0,0,0,6,243,255,191,246,255,118,0,0,0,6,240,255,177,253,255,121, + 0,0,0,0,0,0,0,175,255,255,255,255,38,0,0,0,0,166,255,255,255,255,45,0, + 0,0,0,0,0,0,100,255,255,255,214,0,0,0,0,0,89,255,255,255,226,0,0,0, + 0,0,0,0,0,26,255,255,255,134,0,0,0,0,0,16,250,255,255,152,0,0,0,0, + 0,0,0,0,0,205,255,255,55,0,0,0,0,0,0,186,255,255,77,0,0,0,0,0, + 34,242,255,255,109,0,0,0,0,0,3,200,255,255,153,0,0,0,93,255,255,245,35,0, + 0,0,0,123,255,255,214,10,0,0,0,0,167,255,255,193,2,0,0,47,250,255,249,46, + 0,0,0,0,0,15,224,255,255,111,0,6,209,255,255,111,0,0,0,0,0,0,0,60, + 253,255,245,36,133,255,255,182,1,0,0,0,0,0,0,0,0,129,255,255,213,253,255,233, + 23,0,0,0,0,0,0,0,0,0,4,199,255,255,255,255,73,0,0,0,0,0,0,0, + 0,0,0,0,42,255,255,255,154,0,0,0,0,0,0,0,0,0,0,0,0,149,255,255, + 255,211,8,0,0,0,0,0,0,0,0,0,0,82,255,255,255,255,255,145,0,0,0,0, + 0,0,0,0,0,33,239,255,251,77,237,255,255,73,0,0,0,0,0,0,0,5,199,255, + 255,124,0,91,255,255,233,22,0,0,0,0,0,0,137,255,255,196,3,0,0,175,255,255, + 181,1,0,0,0,0,73,255,255,242,33,0,0,0,23,237,255,255,108,0,0,0,26,234, + 255,255,95,0,0,0,0,0,89,255,255,247,44,0,3,190,255,255,171,0,0,0,0,0, + 0,0,173,255,255,210,7,119,255,255,230,4,0,0,0,0,0,0,2,226,255,255,88,20, + 248,255,255,78,0,0,0,0,0,0,68,255,255,233,6,0,161,255,255,179,0,0,0,0, + 0,0,164,255,255,134,0,0,55,255,255,253,28,0,0,0,0,14,246,255,253,33,0,0, + 0,204,255,255,125,0,0,0,0,100,255,255,180,0,0,0,0,96,255,255,222,2,0,0, + 0,199,255,255,76,0,0,0,0,8,236,255,255,69,0,0,40,255,255,225,3,0,0,0, + 0,0,140,255,255,170,0,0,135,255,255,122,0,0,0,0,0,0,34,254,255,249,21,3, + 229,255,251,24,0,0,0,0,0,0,0,180,255,255,115,72,255,255,168,0,0,0,0,0, + 0,0,0,75,255,255,215,168,255,255,64,0,0,0,0,0,0,0,0,2,222,255,255,255, + 255,215,1,0,0,0,0,0,0,0,0,0,116,255,255,255,255,111,0,0,0,0,0,0, + 0,0,0,0,18,247,255,255,246,16,0,0,0,0,0,0,0,0,0,0,0,173,255,255, + 158,0,0,0,0,0,0,0,0,0,0,0,4,227,255,255,52,0,0,0,0,0,0,0, + 0,0,0,0,85,255,255,204,0,0,0,0,0,0,0,0,0,0,0,0,194,255,255,100, + 0,0,0,0,0,0,0,0,0,0,0,47,255,255,240,10,0,0,0,0,0,0,0,0, + 0,0,0,155,255,255,147,0,0,0,0,0,0,0,0,0,0,0,18,246,255,255,41,0, + 0,0,0,0,0,0,0,0,0,0,117,255,255,192,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,0,0,0,156,156,156,156,156,156,156,174,255,255,255,210,0,0,0,0,0, + 0,0,0,0,0,172,255,255,244,40,0,0,0,0,0,0,0,0,0,100,255,255,255,93, + 0,0,0,0,0,0,0,0,0,38,245,255,255,160,0,0,0,0,0,0,0,0,0,6, + 206,255,255,216,11,0,0,0,0,0,0,0,0,0,139,255,255,247,45,0,0,0,0,0, + 0,0,0,0,69,254,255,255,102,0,0,0,0,0,0,0,0,0,21,230,255,255,168,0, + 0,0,0,0,0,0,0,0,1,178,255,255,221,14,0,0,0,0,0,0,0,0,0,108, + 255,255,249,51,0,0,0,0,0,0,0,0,0,44,248,255,255,110,0,0,0,0,0,0, + 0,0,0,0,210,255,255,247,157,156,156,156,156,156,156,156,0,0,0,255,255,255,255,255, + 255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0, + 0,0,0,0,20,102,145,156,156,156,0,0,0,0,0,0,75,242,255,255,255,255,255,0, + 0,0,0,0,20,242,255,255,255,255,255,255,0,0,0,0,0,92,255,255,223,46,2,0, + 0,0,0,0,0,0,117,255,255,124,0,0,0,0,0,0,0,0,0,93,255,255,137,0, + 0,0,0,0,0,0,0,0,44,255,255,190,0,0,0,0,0,0,0,0,0,3,244,255, + 244,3,0,0,0,0,0,0,0,0,0,196,255,255,49,0,0,0,0,0,0,0,0,0, + 145,255,255,102,0,0,0,0,0,0,0,0,0,118,255,255,127,0,0,0,0,0,0,0, + 0,1,182,255,255,95,0,0,0,0,0,0,156,160,212,255,255,209,8,0,0,0,0,0, + 0,255,255,255,255,184,16,0,0,0,0,0,0,0,255,255,255,255,255,131,0,0,0,0, + 0,0,0,0,5,57,227,255,255,62,0,0,0,0,0,0,0,0,0,127,255,255,125,0, + 0,0,0,0,0,0,0,0,129,255,255,118,0,0,0,0,0,0,0,0,0,175,255,255, + 69,0,0,0,0,0,0,0,0,0,227,255,254,16,0,0,0,0,0,0,0,0,23,255, + 255,211,0,0,0,0,0,0,0,0,0,76,255,255,156,0,0,0,0,0,0,0,0,0, + 112,255,255,118,0,0,0,0,0,0,0,0,0,107,255,255,174,0,0,0,0,0,0,0, + 0,0,49,255,255,255,201,157,156,156,0,0,0,0,0,0,159,255,255,255,255,255,255,0, + 0,0,0,0,0,2,107,202,245,255,255,255,0,0,0,0,128,128,128,24,0,0,0,255, + 255,255,48,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,255,255,255,48, + 0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0, + 255,255,255,48,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,255,255,255, + 48,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0, + 0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,255,255, + 255,48,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0, + 0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,255, + 255,255,48,0,0,0,255,255,255,48,0,0,0,255,255,255,48,0,0,0,255,255,255,48, + 0,0,0,156,156,156,145,102,20,0,0,0,0,0,0,0,255,255,255,255,255,242,75,0, + 0,0,0,0,0,255,255,255,255,255,255,243,22,0,0,0,0,0,0,0,2,45,220,255, + 255,97,0,0,0,0,0,0,0,0,0,123,255,255,120,0,0,0,0,0,0,0,0,0, + 137,255,255,93,0,0,0,0,0,0,0,0,0,191,255,255,44,0,0,0,0,0,0,0, + 0,3,244,255,244,3,0,0,0,0,0,0,0,0,49,255,255,195,0,0,0,0,0,0, + 0,0,0,103,255,255,145,0,0,0,0,0,0,0,0,0,130,255,255,118,0,0,0,0, + 0,0,0,0,0,96,255,255,182,1,0,0,0,0,0,0,0,0,10,210,255,255,212,160, + 156,0,0,0,0,0,0,0,16,186,255,255,255,255,0,0,0,0,0,0,0,133,255,255, + 255,255,255,0,0,0,0,0,0,62,255,255,227,57,5,0,0,0,0,0,0,0,125,255, + 255,126,0,0,0,0,0,0,0,0,0,118,255,255,129,0,0,0,0,0,0,0,0,0, + 69,255,255,175,0,0,0,0,0,0,0,0,0,16,254,255,227,0,0,0,0,0,0,0, + 0,0,0,212,255,255,23,0,0,0,0,0,0,0,0,0,156,255,255,76,0,0,0,0, + 0,0,0,0,0,118,255,255,114,0,0,0,0,0,0,0,0,0,172,255,255,113,0,0, + 0,0,0,156,156,158,200,255,255,255,53,0,0,0,0,0,255,255,255,255,255,255,161,0, + 0,0,0,0,0,255,255,255,245,202,107,2,0,0,0,0,0,0,36,124,153,138,85,11, + 0,0,0,0,0,23,72,55,0,86,249,255,255,255,255,243,144,36,0,0,0,153,255,165, + 22,245,255,255,255,255,255,255,255,255,200,160,197,255,255,88,107,255,233,56,3,33,117,223, + 255,255,255,255,255,255,195,3,102,172,92,0,0,0,0,4,83,176,235,254,232,147,13,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; +static const struct nvfont nvfont_body2 = { 30, 8, nvg_body2, nvb_body2 }; + +static const struct nvglyph nvg_small2[] = { + {0,5,0,0,0,307}, + {0,7,18,0,-18,419}, + {126,9,18,0,-18,590}, + {288,17,18,0,-18,1100}, + {594,15,24,0,-21,934}, + {954,19,18,0,-18,1207}, + {1296,18,18,0,-18,1160}, + {1620,6,18,0,-18,356}, + {1728,10,22,0,-19,624}, + {1948,10,22,0,-19,624}, + {2168,10,19,0,-19,627}, + {2358,13,15,0,-15,816}, + {2553,6,7,0,-3,392}, + {2595,12,8,0,-8,777}, + {2691,6,3,0,-3,392}, + {2709,11,21,0,-20,680}, + {2940,16,18,0,-18,1023}, + {3228,9,18,0,-18,601}, + {3390,13,18,0,-18,837}, + {3624,14,18,0,-18,882}, + {3876,14,18,0,-18,922}, + {4128,14,18,0,-18,882}, + {4380,14,18,0,-18,920}, + {4632,13,18,0,-18,817}, + {4866,14,18,0,-18,894}, + {5118,15,18,0,-18,937}, + {5388,6,12,0,-12,392}, + {5460,6,16,0,-12,392}, + {5556,13,15,0,-15,816}, + {5751,13,13,0,-13,816}, + {5920,13,14,0,-14,816}, + {6102,14,19,0,-19,871}, + {6368,20,18,0,-18,1310}, + {6728,17,18,0,-18,1118}, + {7034,15,18,0,-18,977}, + {7304,18,18,0,-18,1138}, + {7628,18,18,0,-18,1155}, + {7952,15,18,0,-18,980}, + {8222,14,18,0,-18,925}, + {8474,18,18,0,-18,1175}, + {8798,18,18,0,-18,1130}, + {9122,6,18,0,-18,390}, + {9230,11,18,0,-18,699}, + {9428,17,18,0,-18,1063}, + {9734,14,18,0,-18,903}, + {9986,22,18,0,-18,1392}, + {10382,18,18,0,-18,1130}, + {10706,19,18,0,-18,1207}, + {11048,16,18,0,-18,1008}, + {11336,19,18,0,-18,1209}, + {11678,16,18,0,-18,1008}, + {11966,15,18,0,-18,934}, + {12236,16,18,0,-18,995}, + {12524,17,18,0,-18,1094}, + {12830,17,18,0,-18,1118}, + {13136,26,18,0,-18,1673}, + {13604,17,18,0,-18,1057}, + {13910,16,18,0,-18,1038}, + {14198,15,18,0,-18,957}, + {14468,10,22,0,-19,624}, + {14688,11,21,0,-20,680}, + {14919,10,22,0,-19,624}, + {15139,13,18,0,-18,816}, + {15373,10,3,0,0,654}, + {15403,9,19,0,-19,544}, + {15574,13,13,0,-13,819}, + {15743,15,18,0,-18,931}, + {16013,13,13,0,-13,819}, + {16182,15,18,0,-18,931}, + {16452,14,13,0,-13,866}, + {16634,9,18,0,-18,544}, + {16796,15,18,0,-13,931}, + {17066,14,18,0,-18,920}, + {17318,6,18,0,-18,369}, + {17426,7,23,-1,-18,369}, + {17587,13,19,0,-19,822}, + {17834,6,18,0,-18,369}, + {17942,22,13,0,-13,1416}, + {18228,14,13,0,-13,920}, + {18410,14,13,0,-13,897}, + {18592,15,18,0,-13,931}, + {18862,15,18,0,-13,931}, + {19132,9,13,0,-13,576}, + {19249,11,13,0,-13,727}, + {19392,9,17,0,-17,579}, + {19545,14,13,0,-13,905}, + {19727,13,13,0,-13,803}, + {19896,18,13,0,-13,1178}, + {20130,13,13,0,-13,846}, + {20299,13,18,0,-13,803}, + {20533,12,13,0,-13,762}, + {20689,10,22,0,-19,624}, + {20909,6,23,0,-20,372}, + {21047,10,22,0,-19,624}, + {21267,13,10,0,-10,816}, +}; +static const unsigned char nvb_small2[] = { + 0,0,248,255,0,0,0,0,0,248,255,0,0,0,0,0,248,255,0,0,0,0,0,248, + 255,0,0,0,0,0,248,255,0,0,0,0,0,248,255,0,0,0,0,0,248,255,0,0, + 0,0,0,248,255,0,0,0,0,0,248,255,0,0,0,0,0,248,255,0,0,0,0,0, + 248,255,0,0,0,0,0,248,255,0,0,0,0,0,248,255,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,216,216,0,0,0,0,0,255,255,0,0,0,0, + 0,255,255,0,0,0,0,0,188,182,0,0,188,182,0,0,0,252,244,0,0,252,244,0, + 0,0,226,221,0,0,229,221,0,0,0,188,183,0,0,191,183,0,0,0,148,141,0,0, + 150,141,0,0,0,108,100,0,0,108,100,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,46,255,162,0,0,0,50,255,158,0,0,0,0,0,0,0,0,83,255, + 126,0,0,0,87,255,121,0,0,0,0,0,0,0,0,120,255,88,0,0,0,124,255,84, + 0,0,0,0,0,0,0,0,157,255,51,0,0,0,161,255,47,0,0,0,0,0,0,0, + 0,193,255,15,0,0,0,197,255,12,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,0,0,180,180,180,189,255,235,180,180,180,190,255,234,180,180,180,0,0, + 0,0,0,47,255,161,0,0,0,51,255,157,0,0,0,0,0,0,0,0,84,255,124,0, + 0,0,88,255,120,0,0,0,0,0,0,0,0,121,255,87,0,0,0,125,255,83,0,0, + 0,0,0,0,0,0,157,255,51,0,0,0,161,255,47,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,0,0,180,180,180,253,247,180,180,180,180,254,246, + 180,180,180,180,0,0,0,0,12,255,197,0,0,0,15,255,193,0,0,0,0,0,0,0, + 0,48,255,160,0,0,0,53,255,156,0,0,0,0,0,0,0,0,85,255,123,0,0,0, + 89,255,119,0,0,0,0,0,0,0,0,121,255,87,0,0,0,126,255,83,0,0,0,0, + 0,0,0,0,158,255,50,0,0,0,162,255,46,0,0,0,0,0,0,0,0,0,0,0, + 0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,78,178,232,255,255, + 203,120,11,0,0,0,0,0,3,163,255,255,231,208,222,254,255,220,33,0,0,0,0,116, + 255,213,53,0,0,0,29,178,255,207,3,0,0,0,218,255,45,0,0,0,0,0,5,214, + 255,80,0,0,0,252,254,0,0,0,0,0,0,0,110,255,151,0,0,0,235,255,47,0, + 0,0,0,0,0,22,84,58,0,0,0,152,255,216,46,0,0,0,0,0,0,0,0,0, + 0,0,17,208,255,254,184,93,13,0,0,0,0,0,0,0,0,0,11,140,247,255,255,247, + 170,73,1,0,0,0,0,0,0,0,0,18,107,196,255,255,255,200,37,0,0,0,0,0, + 0,0,0,0,0,29,120,226,255,233,29,0,0,0,0,0,0,0,0,0,0,0,11,185, + 255,156,0,0,139,196,50,0,0,0,0,0,0,0,39,255,226,0,0,135,255,128,0,0, + 0,0,0,0,0,5,255,245,0,0,44,254,235,25,0,0,0,0,0,0,49,255,218,0, + 0,0,150,255,218,63,0,0,0,0,36,207,255,130,0,0,0,5,157,255,255,229,189,184, + 210,254,255,189,9,0,0,0,0,0,60,160,220,255,255,236,187,97,3,0,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,0,0,0,0,0,0,0,0,128,124,0,0,0,0,0,0,0,0,48,185,246,245, + 182,48,0,0,0,0,0,0,148,255,100,0,0,0,45,244,255,204,207,255,244,43,0,0, + 0,0,68,255,179,0,0,0,0,174,255,130,0,0,141,255,167,0,0,0,16,227,236,23, + 0,0,0,0,237,255,19,0,0,28,255,229,0,0,0,164,255,82,0,0,0,0,0,254, + 251,0,0,0,4,255,245,0,0,82,255,163,0,0,0,0,0,0,233,255,21,0,0,32, + 255,225,0,23,235,227,16,0,0,0,0,0,0,163,255,136,0,0,146,255,152,0,178,255, + 67,0,0,0,0,0,0,0,33,236,255,204,208,255,229,24,99,255,146,0,0,0,0,0, + 0,0,0,0,33,161,222,221,153,27,32,242,216,10,0,14,13,0,0,0,0,0,0,0, + 0,0,0,0,2,192,252,54,61,204,255,255,208,66,0,0,0,0,0,0,0,0,0,113, + 255,130,49,248,232,99,99,234,251,56,0,0,0,0,0,0,0,42,248,205,5,172,255,82, + 0,0,80,255,181,0,0,0,0,0,0,5,205,248,42,0,230,255,14,0,0,9,255,238, + 0,0,0,0,0,0,130,255,113,0,0,246,255,3,0,0,0,251,253,0,0,0,0,0, + 52,252,191,2,0,0,227,255,36,0,0,30,255,234,0,0,0,0,9,216,242,32,0,0, + 0,161,255,148,0,0,150,255,166,0,0,0,0,146,255,97,0,0,0,0,35,240,255,207, + 208,255,241,38,0,0,0,67,254,175,0,0,0,0,0,0,42,180,246,246,180,44,0,0, + 0,0,0,0,29,157,231,254,233,163,38,0,0,0,0,0,0,0,0,0,0,35,234,255, + 222,185,227,255,243,54,0,0,0,0,0,0,0,0,0,173,255,141,1,0,3,137,255,200, + 0,0,0,0,0,0,0,0,0,240,255,16,0,0,0,9,253,251,0,0,0,0,0,0, + 0,0,0,231,255,23,0,0,0,26,255,226,0,0,0,0,0,0,0,0,0,153,255,135, + 0,0,12,184,255,113,0,0,0,0,0,0,0,0,0,24,235,254,106,123,230,252,128,1, + 0,0,0,0,0,0,0,0,0,13,165,255,255,247,160,42,0,0,0,0,0,0,0,0, + 0,0,45,224,255,199,250,248,72,0,0,0,0,4,255,244,0,0,0,24,233,249,84,0, + 78,250,248,72,0,0,0,20,255,229,0,0,0,147,255,116,0,0,0,78,250,248,72,0, + 0,55,255,196,0,0,0,226,255,20,0,0,0,0,78,250,248,72,0,115,255,141,0,0, + 0,254,252,0,0,0,0,0,0,78,250,248,72,204,255,63,0,0,0,235,255,30,0,0, + 0,0,0,0,78,250,254,255,209,1,0,0,0,167,255,141,0,0,0,0,0,0,0,114, + 255,255,119,0,0,0,0,43,247,255,134,13,0,0,0,8,104,239,255,255,249,75,0,0, + 0,0,71,238,255,247,200,182,199,246,255,240,92,78,250,249,75,0,0,0,0,20,127,202, + 242,254,239,195,118,18,0,0,78,250,249,75,0,0,188,182,0,0,0,0,252,244,0,0, + 0,0,226,221,0,0,0,0,188,183,0,0,0,0,148,141,0,0,0,0,108,100,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,36,128,73,0,0,0,0,0,0,32,229,207,14,0,0,0,0,0, + 12,212,235,29,0,0,0,0,0,0,153,255,81,0,0,0,0,0,0,55,255,182,0,0, + 0,0,0,0,0,183,255,53,0,0,0,0,0,0,33,254,204,0,0,0,0,0,0,0, + 116,255,122,0,0,0,0,0,0,0,181,255,62,0,0,0,0,0,0,0,224,255,20,0, + 0,0,0,0,0,0,249,254,1,0,0,0,0,0,0,0,254,250,0,0,0,0,0,0, + 0,0,240,255,7,0,0,0,0,0,0,0,205,255,39,0,0,0,0,0,0,0,154,255, + 89,0,0,0,0,0,0,0,81,255,163,0,0,0,0,0,0,0,7,236,244,14,0,0, + 0,0,0,0,0,128,255,120,0,0,0,0,0,0,0,13,230,239,22,0,0,0,0,0, + 0,0,73,254,175,0,0,0,0,0,0,0,0,124,255,113,0,0,0,0,0,0,0,0, + 139,254,88,0,0,76,128,33,0,0,0,0,0,0,0,18,216,226,30,0,0,0,0,0, + 0,0,37,242,210,11,0,0,0,0,0,0,0,98,255,153,0,0,0,0,0,0,0,1, + 197,255,54,0,0,0,0,0,0,0,67,255,181,0,0,0,0,0,0,0,0,216,254,30, + 0,0,0,0,0,0,0,133,255,111,0,0,0,0,0,0,0,71,255,174,0,0,0,0, + 0,0,0,28,255,216,0,0,0,0,0,0,0,7,255,241,0,0,0,0,0,0,0,2, + 255,246,0,0,0,0,0,0,0,15,255,231,0,0,0,0,0,0,0,47,255,196,0,0, + 0,0,0,0,0,97,255,141,0,0,0,0,0,0,0,170,255,66,0,0,0,0,0,0, + 16,246,224,3,0,0,0,0,0,0,124,255,110,0,0,0,0,0,0,24,241,217,6,0, + 0,0,0,0,1,175,252,59,0,0,0,0,0,0,119,255,109,0,0,0,0,0,0,92, + 254,129,0,0,0,0,0,0,0,0,0,0,229,176,0,0,0,0,0,34,110,0,190,136, + 5,132,6,0,0,149,255,149,155,113,190,255,96,0,0,5,87,193,246,240,170,64,1,0, + 0,4,83,186,245,237,165,62,0,0,0,148,255,149,155,113,190,255,96,0,0,34,110,0, + 190,136,5,132,6,0,0,0,0,0,229,176,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,128,124,0,0,0,0,0,0,0,0,0,0,0, + 255,248,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0, + 0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0, + 180,180,180,180,255,254,180,180,180,180,0,0,0,255,255,255,255,255,255,255,255,255,255,0, + 0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,255,248,0, + 0,0,0,0,0,0,0,0,0,0,128,124,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,216,209,0,0,0,0,255,248,0,0,0,0,255, + 246,0,0,0,0,0,214,0,0,0,0,30,175,0,0,0,0,196,65,0,0,0,0,75, + 0,0,0,0,0,180,180,180,180,180,180,180,180,0,0,0,0,255,255,255,255,255,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,216,209,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,0, + 0,0,0,0,71,128,15,0,0,0,0,0,0,0,0,199,227,1,0,0,0,0,0,0, + 0,22,254,153,0,0,0,0,0,0,0,0,96,255,77,0,0,0,0,0,0,0,0,172, + 247,10,0,0,0,0,0,0,0,6,243,180,0,0,0,0,0,0,0,0,67,255,106,0, + 0,0,0,0,0,0,0,144,255,30,0,0,0,0,0,0,0,0,218,210,0,0,0,0, + 0,0,0,0,39,255,135,0,0,0,0,0,0,0,0,115,255,59,0,0,0,0,0,0, + 0,0,191,236,3,0,0,0,0,0,0,0,16,252,164,0,0,0,0,0,0,0,0,86, + 255,88,0,0,0,0,0,0,0,0,162,252,16,0,0,0,0,0,0,0,3,236,192,0, + 0,0,0,0,0,0,0,59,255,116,0,0,0,0,0,0,0,0,135,255,41,0,0,0, + 0,0,0,0,0,211,222,0,0,0,0,0,0,0,0,31,255,145,0,0,0,0,0,0, + 0,0,106,255,70,0,0,0,0,0,0,0,0,0,0,0,19,133,212,249,249,212,131,18, + 0,0,0,0,0,0,0,56,232,255,236,191,191,236,255,231,52,0,0,0,0,0,37,240, + 250,106,5,0,0,4,105,249,237,34,0,0,0,0,190,255,96,0,0,0,0,0,0,92, + 255,186,0,0,0,55,255,200,0,0,0,0,0,0,0,0,199,255,51,0,0,143,255,106, + 0,0,0,0,0,0,0,0,104,255,139,0,0,200,255,45,0,0,0,0,0,0,0,0, + 45,255,200,0,0,235,255,10,0,0,0,0,0,0,0,0,10,255,235,0,0,252,252,0, + 0,0,0,0,0,0,0,0,0,252,251,0,0,249,253,0,0,0,0,0,0,0,0,0, + 0,254,249,0,0,232,255,15,0,0,0,0,0,0,0,0,16,255,232,0,0,193,255,54, + 0,0,0,0,0,0,0,0,54,255,193,0,0,132,255,117,0,0,0,0,0,0,0,0, + 118,255,132,0,0,43,255,214,2,0,0,0,0,0,0,3,217,255,43,0,0,0,173,255, + 112,0,0,0,0,0,0,118,255,174,0,0,0,0,28,234,253,117,6,0,0,8,121,254, + 234,28,0,0,0,0,0,48,227,255,239,191,191,239,255,227,48,0,0,0,0,0,0,0, + 16,129,212,250,249,211,129,16,0,0,0,0,0,0,0,0,3,196,248,0,0,0,0,0, + 5,156,255,248,0,0,0,0,58,201,255,255,248,0,0,77,207,255,252,116,255,248,0,0, + 128,255,192,54,0,255,248,0,0,41,34,0,0,0,255,248,0,0,0,0,0,0,0,255, + 248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0, + 0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0, + 0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255, + 248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0, + 0,0,255,248,0,0,0,0,0,44,163,228,253,245,199,102,3,0,0,0,0,91,250,255, + 225,186,203,252,255,178,4,0,0,49,250,251,93,1,0,0,40,221,255,112,0,0,176,255, + 128,0,0,0,0,0,69,255,210,0,7,248,255,27,0,0,0,0,0,5,255,249,0,11, + 92,88,0,0,0,0,0,0,5,254,249,0,0,0,0,0,0,0,0,0,0,54,255,211, + 0,0,0,0,0,0,0,0,0,0,154,255,138,0,0,0,0,0,0,0,0,0,50,252, + 249,30,0,0,0,0,0,0,0,0,16,219,255,125,0,0,0,0,0,0,0,0,7,190, + 255,189,4,0,0,0,0,0,0,0,6,179,255,214,17,0,0,0,0,0,0,0,8,179, + 255,220,27,0,0,0,0,0,0,0,14,190,255,216,28,0,0,0,0,0,0,0,24,207, + 255,204,22,0,0,0,0,0,0,0,40,224,255,184,12,0,0,0,0,0,0,0,0,239, + 255,254,184,180,180,180,180,180,180,180,0,0,255,255,255,255,255,255,255,255,255,255,255,0, + 0,0,0,39,154,223,252,244,198,101,3,0,0,0,0,0,89,249,255,233,187,195,248,255, + 180,4,0,0,0,45,250,254,116,4,0,0,22,195,255,116,0,0,0,159,255,155,0,0, + 0,0,0,45,255,210,0,0,0,175,208,55,0,0,0,0,0,6,255,244,0,0,0,0, + 0,0,0,0,0,0,0,45,255,228,0,0,0,0,0,0,0,0,0,0,18,193,255,159, + 0,0,0,0,0,0,0,180,180,191,244,255,234,30,0,0,0,0,0,0,0,255,255,255, + 255,253,58,0,0,0,0,0,0,0,0,0,0,17,116,254,218,14,0,0,0,0,0,0, + 0,0,0,0,0,144,255,127,0,0,0,0,0,0,0,0,0,0,0,43,255,208,0,0, + 20,192,187,2,0,0,0,0,0,6,255,242,0,0,2,238,255,50,0,0,0,0,0,16, + 255,237,0,0,0,158,255,160,0,0,0,0,0,88,255,189,0,0,0,37,248,255,118,3, + 0,0,46,229,255,78,0,0,0,0,83,249,255,230,185,203,253,255,138,0,0,0,0,0, + 0,44,163,229,252,238,185,76,0,0,0,0,0,0,0,0,0,0,0,0,67,254,248,0, + 0,0,0,0,0,0,0,0,0,18,228,255,248,0,0,0,0,0,0,0,0,0,0,169, + 255,255,248,0,0,0,0,0,0,0,0,0,92,255,226,255,248,0,0,0,0,0,0,0, + 0,32,241,254,65,255,248,0,0,0,0,0,0,0,2,193,255,142,0,255,248,0,0,0, + 0,0,0,0,120,255,211,8,0,255,248,0,0,0,0,0,0,50,250,250,48,0,0,255, + 248,0,0,0,0,0,9,214,255,121,0,0,0,255,248,0,0,0,0,0,146,255,196,3, + 0,0,0,255,248,0,0,0,0,72,255,244,34,0,0,0,0,255,248,0,0,0,19,230, + 255,101,0,0,0,0,0,255,248,0,0,0,122,255,255,255,255,255,255,255,255,255,255,255, + 255,128,102,204,204,204,204,204,204,204,204,255,255,204,204,102,0,0,0,0,0,0,0,0, + 0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0, + 0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,1,250,255,255,255,255,255,255,255,255,24,0,0,0,17,255,246,180,180,180,180,180, + 180,180,17,0,0,0,39,255,202,0,0,0,0,0,0,0,0,0,0,0,60,255,182,0, + 0,0,0,0,0,0,0,0,0,0,81,255,163,0,0,0,0,0,0,0,0,0,0,0, + 104,255,143,0,0,0,0,0,0,0,0,0,0,0,125,255,135,140,226,252,236,171,49,0, + 0,0,0,0,147,255,250,250,197,184,230,255,251,88,0,0,0,0,168,255,188,26,0,0, + 3,113,254,248,33,0,0,0,75,104,17,0,0,0,0,0,147,255,144,0,0,0,0,0, + 0,0,0,0,0,0,48,255,212,0,0,0,0,0,0,0,0,0,0,0,8,255,242,0, + 0,16,28,15,0,0,0,0,0,0,5,255,242,0,0,119,255,175,0,0,0,0,0,0, + 38,255,213,0,0,40,255,250,37,0,0,0,0,0,128,255,145,0,0,0,161,255,218,44, + 0,0,2,99,250,248,35,0,0,0,12,193,255,254,206,186,226,255,250,87,0,0,0,0, + 0,4,105,199,243,253,229,164,45,0,0,0,0,0,0,0,64,180,239,253,230,163,41,0, + 0,0,0,0,0,123,255,254,205,184,222,255,247,73,0,0,0,0,85,255,217,42,0,0, + 0,90,251,240,21,0,0,5,228,248,36,0,0,0,0,0,143,255,119,0,0,80,255,158, + 0,0,0,0,0,0,43,144,96,0,0,153,255,81,0,0,0,0,0,0,0,0,0,0, + 0,203,255,33,2,79,152,176,160,99,9,0,0,0,0,235,255,32,202,255,255,255,255,255, + 221,42,0,0,0,250,252,205,226,90,18,4,45,169,255,226,15,0,0,254,255,234,27,0, + 0,0,0,1,179,255,125,0,0,246,255,125,0,0,0,0,0,0,58,255,207,0,0,221, + 255,71,0,0,0,0,0,0,7,255,245,0,0,180,255,74,0,0,0,0,0,0,1,251, + 252,0,0,116,255,127,0,0,0,0,0,0,33,255,223,0,0,26,250,232,18,0,0,0, + 0,0,133,255,151,0,0,0,139,255,200,35,0,0,1,102,252,246,35,0,0,0,6,178, + 255,253,204,184,226,255,246,77,0,0,0,0,0,2,98,198,244,253,225,154,35,0,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,255,0,180,180,180,180,180,180,180,180,180,206, + 255,228,0,0,0,0,0,0,0,0,0,3,199,255,77,0,0,0,0,0,0,0,0,0, + 115,255,174,0,0,0,0,0,0,0,0,0,30,245,247,32,0,0,0,0,0,0,0,0, + 0,167,255,139,0,0,0,0,0,0,0,0,0,51,255,243,19,0,0,0,0,0,0,0, + 0,0,177,255,139,0,0,0,0,0,0,0,0,0,39,254,253,29,0,0,0,0,0,0, + 0,0,0,143,255,178,0,0,0,0,0,0,0,0,0,4,234,255,86,0,0,0,0,0, + 0,0,0,0,67,255,247,10,0,0,0,0,0,0,0,0,0,144,255,181,0,0,0,0, + 0,0,0,0,0,0,210,255,119,0,0,0,0,0,0,0,0,0,14,254,255,66,0,0, + 0,0,0,0,0,0,0,59,255,255,21,0,0,0,0,0,0,0,0,0,98,255,241,0, + 0,0,0,0,0,0,0,0,0,127,255,216,0,0,0,0,0,0,0,0,0,75,179,235, + 253,237,184,78,0,0,0,0,0,1,154,255,254,208,186,208,254,255,154,1,0,0,0,103, + 255,211,38,0,0,0,40,211,255,103,0,0,0,210,255,48,0,0,0,0,0,49,255,210, + 0,0,0,250,254,1,0,0,0,0,0,1,254,251,0,0,0,239,255,44,0,0,0,0, + 0,44,255,239,0,0,0,169,255,208,36,0,0,0,35,207,255,169,0,0,0,39,242,255, + 253,205,182,205,253,255,241,38,0,0,0,0,80,255,255,255,255,255,255,255,147,2,0,0, + 0,18,226,255,165,52,10,10,55,170,255,165,1,0,0,137,255,151,0,0,0,0,0,0, + 159,255,95,0,0,218,255,30,0,0,0,0,0,0,39,255,200,0,0,251,252,0,0,0, + 0,0,0,0,4,255,242,0,0,242,255,18,0,0,0,0,0,0,26,255,234,0,0,183, + 255,117,0,0,0,0,0,0,125,255,175,0,0,62,254,250,100,3,0,0,3,106,251,253, + 55,0,0,0,103,251,255,232,187,187,233,255,250,96,0,0,0,0,0,43,155,221,250,250, + 219,152,39,0,0,0,0,0,0,41,156,225,252,244,199,102,3,0,0,0,0,0,0,93, + 250,255,221,184,202,252,255,185,7,0,0,0,0,50,252,247,86,0,0,0,29,188,255,143, + 0,0,0,0,171,255,113,0,0,0,0,0,9,216,251,27,0,0,0,233,255,21,0,0, + 0,0,0,0,111,255,114,0,0,0,254,252,0,0,0,0,0,0,0,72,255,178,0,0, + 0,234,255,22,0,0,0,0,0,0,92,255,219,0,0,0,172,255,115,0,0,0,0,0, + 0,181,255,242,0,0,0,56,254,247,84,0,0,0,15,150,243,253,254,0,0,0,0,112, + 255,255,223,183,197,246,247,75,252,251,0,0,0,0,0,62,181,238,252,227,154,37,7,255, + 238,0,0,0,0,0,0,0,0,0,0,0,0,33,255,211,0,0,0,78,104,21,0,0, + 0,0,0,0,76,255,165,0,0,0,158,255,89,0,0,0,0,0,0,146,255,97,0,0, + 0,85,255,181,0,0,0,0,0,23,240,242,13,0,0,0,5,216,255,130,5,0,0,26, + 200,255,116,0,0,0,0,0,46,235,255,234,187,196,249,255,160,1,0,0,0,0,0,0, + 25,146,221,253,244,194,90,1,0,0,0,0,0,0,255,248,0,0,0,0,255,248,0,0, + 0,0,216,209,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,216,209,0,0, + 0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0, + 0,0,216,209,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,216,209,0,0, + 0,0,255,248,0,0,0,0,255,246,0,0,0,0,0,214,0,0,0,0,30,175,0,0, + 0,0,196,65,0,0,0,0,75,0,0,0,0,0,0,0,0,0,0,0,0,0,20,0, + 0,0,0,0,0,0,0,0,1,64,166,249,0,0,0,0,0,0,0,20,115,217,255,255, + 191,0,0,0,0,1,64,166,249,255,232,137,37,0,0,0,0,115,217,255,254,183,83,5, + 0,0,0,0,0,0,255,255,159,31,0,0,0,0,0,0,0,0,0,226,255,254,188,94, + 12,0,0,0,0,0,0,0,2,68,164,246,255,244,163,68,2,0,0,0,0,0,0,0, + 14,100,196,255,255,226,136,0,0,0,0,0,0,0,0,0,37,132,226,255,0,0,0,0, + 0,0,0,0,0,0,0,2,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255, + 255,255,0,0,0,180,180,180,180,180,180,180,180,180,180,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,0,0,0,180,180, + 180,180,180,180,180,180,180,180,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,208,112,21,0,0,0,0, + 0,0,0,0,0,0,242,255,251,176,80,5,0,0,0,0,0,0,0,10,88,184,253,255, + 235,144,48,0,0,0,0,0,0,0,0,25,116,211,255,255,208,112,0,0,0,0,0,0, + 0,0,0,48,165,255,255,0,0,0,0,0,0,0,1,67,169,249,255,228,0,0,0,0, + 0,24,122,221,255,253,180,78,3,0,0,0,75,175,252,255,228,129,29,0,0,0,0,0, + 0,255,253,180,78,3,0,0,0,0,0,0,0,0,129,29,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,21,108,159,177,163,115,25,0,0,0,0,0,0,109, + 245,255,255,255,255,255,246,100,0,0,0,0,115,255,237,103,26,5,29,117,247,255,73,0, + 0,28,249,245,40,0,0,0,0,0,93,255,195,0,0,117,255,141,0,0,0,0,0,0, + 6,254,245,0,0,100,160,49,0,0,0,0,0,0,11,255,249,0,0,0,0,0,0,0, + 0,0,0,0,117,255,200,0,0,0,0,0,0,0,0,0,0,104,253,254,73,0,0,0, + 0,0,0,0,0,4,150,255,251,99,0,0,0,0,0,0,0,0,0,163,255,230,59,0, + 0,0,0,0,0,0,0,0,82,255,234,34,0,0,0,0,0,0,0,0,0,0,184,255, + 103,0,0,0,0,0,0,0,0,0,0,0,232,255,23,0,0,0,0,0,0,0,0,0, + 0,0,127,132,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,216,216,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,0,0,0,0,0,0,0,0,0,0,0,0,20,112,186,230,251,249,226,175,93,6, + 0,0,0,0,0,0,0,0,5,129,245,255,251,209,185,189,225,255,255,219,55,0,0,0, + 0,0,0,18,195,255,229,105,14,0,0,0,0,43,170,255,248,64,0,0,0,0,6,197, + 255,183,15,11,114,173,158,61,0,46,60,126,255,233,17,0,0,0,132,255,195,6,27,216, + 255,255,255,253,74,231,235,1,182,255,126,0,0,25,248,247,32,2,198,255,158,21,23,173, + 224,255,172,0,69,255,205,0,0,119,255,154,0,84,255,198,2,0,0,15,246,255,111,0, + 12,255,245,0,0,191,255,68,0,175,255,83,0,0,0,0,204,255,48,0,0,252,253,0, + 0,233,255,16,0,227,255,24,0,0,0,0,207,241,2,0,16,255,231,0,0,253,251,0, + 0,245,255,4,0,0,0,7,247,180,0,0,70,255,178,0,0,248,255,4,0,229,255,28, + 0,0,0,91,255,118,0,0,165,255,88,0,0,218,255,44,0,169,255,133,0,0,24,227, + 255,81,0,70,254,206,3,0,0,156,255,125,0,47,249,255,169,141,231,164,255,198,149,247, + 229,35,0,0,0,58,255,237,23,0,66,207,252,219,98,0,149,246,237,158,24,0,0,0, + 0,0,166,255,202,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,198, + 255,231,102,13,0,0,0,0,9,79,158,3,0,0,0,0,0,0,0,8,146,253,255,250, + 210,186,185,209,249,255,230,32,0,0,0,0,0,0,0,0,0,36,130,197,235,254,249,223, + 172,94,9,0,0,0,0,0,0,0,0,0,0,0,0,215,255,235,5,0,0,0,0,0, + 0,0,0,0,0,0,0,54,255,245,255,78,0,0,0,0,0,0,0,0,0,0,0,0, + 148,255,95,255,172,0,0,0,0,0,0,0,0,0,0,0,7,237,210,0,213,249,19,0, + 0,0,0,0,0,0,0,0,0,83,255,117,0,121,255,106,0,0,0,0,0,0,0,0, + 0,0,176,254,27,0,30,254,200,0,0,0,0,0,0,0,0,0,21,251,188,0,0,0, + 193,255,40,0,0,0,0,0,0,0,0,110,255,96,0,0,0,100,255,133,0,0,0,0, + 0,0,0,0,204,246,14,0,0,0,16,248,226,2,0,0,0,0,0,0,44,255,169,0, + 0,0,0,0,172,255,68,0,0,0,0,0,0,139,255,76,0,0,0,0,0,80,255,163, + 0,0,0,0,0,3,230,255,255,255,255,255,255,255,255,255,244,12,0,0,0,0,72,255, + 229,204,204,204,204,204,204,204,230,255,96,0,0,0,0,167,255,55,0,0,0,0,0,0, + 0,59,255,190,0,0,0,14,246,219,0,0,0,0,0,0,0,0,1,222,254,30,0,0, + 100,255,127,0,0,0,0,0,0,0,0,0,131,255,124,0,0,195,255,35,0,0,0,0, + 0,0,0,0,0,39,255,218,0,33,255,197,0,0,0,0,0,0,0,0,0,0,0,202, + 255,56,0,0,255,255,255,255,255,255,255,245,203,116,7,0,0,0,0,255,254,180,180,180, + 180,181,202,251,255,201,12,0,0,0,255,248,0,0,0,0,0,0,31,208,255,138,0,0, + 0,255,248,0,0,0,0,0,0,0,57,255,221,0,0,0,255,248,0,0,0,0,0,0, + 0,7,255,246,0,0,0,255,248,0,0,0,0,0,0,0,22,255,220,0,0,0,255,248, + 0,0,0,0,0,0,0,115,255,140,0,0,0,255,248,0,0,0,0,3,29,121,250,218, + 16,0,0,0,255,255,255,255,255,255,255,255,255,242,34,0,0,0,0,255,254,180,180,180, + 180,183,212,255,255,198,6,0,0,0,255,248,0,0,0,0,0,0,60,235,255,111,0,0, + 0,255,248,0,0,0,0,0,0,0,85,255,205,0,0,0,255,248,0,0,0,0,0,0, + 0,9,255,247,0,0,0,255,248,0,0,0,0,0,0,0,4,253,249,0,0,0,255,248, + 0,0,0,0,0,0,0,66,255,206,0,0,0,255,248,0,0,0,0,0,0,42,220,255, + 105,0,0,0,255,254,180,180,180,180,181,204,253,255,171,2,0,0,0,255,255,255,255,255, + 255,255,244,198,97,2,0,0,0,0,0,0,0,39,146,214,247,250,224,164,60,0,0,0, + 0,0,0,0,0,3,135,253,255,236,191,185,220,255,255,160,8,0,0,0,0,0,1,166, + 255,221,77,2,0,0,0,50,199,255,178,2,0,0,0,0,112,255,209,18,0,0,0,0, + 0,0,8,198,255,105,0,0,0,17,241,250,38,0,0,0,0,0,0,0,0,41,255,226, + 2,0,0,109,255,156,0,0,0,0,0,0,0,0,0,0,202,255,50,0,0,182,255,71, + 0,0,0,0,0,0,0,0,0,0,70,112,37,0,0,228,255,21,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,252,253,1,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,252,253,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,228,255,21, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,182,255,71,0,0,0,0,0,0, + 0,0,0,0,63,112,44,0,0,109,255,157,0,0,0,0,0,0,0,0,0,0,185,255, + 63,0,0,17,242,250,38,0,0,0,0,0,0,0,0,31,251,234,6,0,0,0,112,255, + 209,18,0,0,0,0,0,0,6,188,255,116,0,0,0,0,1,164,255,221,77,2,0,0, + 0,47,196,255,187,4,0,0,0,0,0,3,130,252,255,234,191,185,219,255,255,169,11,0, + 0,0,0,0,0,0,0,36,143,211,247,251,227,168,66,0,0,0,0,0,0,0,255,255, + 255,255,255,255,250,227,179,101,11,0,0,0,0,0,0,0,255,254,180,180,180,180,188,217, + 254,255,233,85,0,0,0,0,0,0,255,248,0,0,0,0,0,0,24,127,246,255,124,0, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,45,233,255,86,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,61,254,235,12,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,171,255,102,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,81,255, + 178,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,28,255,224,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,0,6,255,244,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,8,255,243,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,36,255, + 220,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,94,255,172,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,0,191,255,94,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,78,255,227,8,0,0,0,255,248,0,0,0,0,0,0,0,0,59,240,254,74, + 0,0,0,0,255,248,0,0,0,0,0,0,28,138,251,254,110,0,0,0,0,0,255,254, + 180,180,180,180,188,218,255,255,228,76,0,0,0,0,0,0,255,255,255,255,255,255,251,227, + 178,98,9,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,128,0,0, + 0,255,254,180,180,180,180,180,180,180,180,180,90,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,128,0,0,0, + 0,255,254,180,180,180,180,180,180,180,180,90,0,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,254,180,180,180,180,180,180,180,180,180,90,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,0,0,0,255,254,180,180,180,180,180,180,180,180,180,0,0,0,255,248,0,0, + 0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0, + 255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0, + 0,0,0,0,0,0,0,0,255,254,180,180,180,180,180,180,180,180,90,0,0,0,255,255, + 255,255,255,255,255,255,255,255,128,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0, + 0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0, + 0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0, + 255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,53,157,221,251,250,219,153,48,0,0,0,0,0,0,0,0,6, + 149,255,255,230,190,187,225,255,254,143,3,0,0,0,0,0,2,176,255,215,67,1,0,0, + 0,58,207,255,158,0,0,0,0,0,117,255,204,14,0,0,0,0,0,0,12,211,255,76, + 0,0,0,18,244,249,35,0,0,0,0,0,0,0,0,76,255,182,0,0,0,109,255,155, + 0,0,0,0,0,0,0,0,0,9,148,134,0,0,0,180,255,71,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,226,255,22,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,248,254,1,0,0,0,0,0,93,180,180,180,180,180,180,0,0,0,254,252,0, + 0,0,0,0,0,132,255,255,255,255,255,255,0,0,0,237,255,17,0,0,0,0,0,0, + 0,0,0,0,248,255,0,0,0,197,255,64,0,0,0,0,0,0,0,0,0,0,248,255, + 0,0,0,131,255,144,0,0,0,0,0,0,0,0,0,0,248,255,0,0,0,36,253,244, + 28,0,0,0,0,0,0,0,0,0,248,255,0,0,0,0,145,255,199,14,0,0,0,0, + 0,0,0,38,254,255,0,0,0,0,8,194,255,219,81,4,0,0,0,7,99,238,255,173, + 0,0,0,0,0,10,157,255,255,242,198,182,198,244,255,249,125,2,0,0,0,0,0,0, + 0,49,150,213,245,254,239,197,127,27,0,0,0,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,255,248,0,0,0,0,255,255,204,204,204,204,204,204,204,204,204,204,255,248, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,255,248,0, + 0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0, + 0,0,255,248,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0, + 255,248,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,8,255,243,0,0, + 0,0,0,0,0,0,64,255,210,0,0,22,3,0,0,0,34,213,255,127,0,0,128,241, + 198,182,204,252,255,199,10,0,0,82,213,245,254,242,197,111,7,0,0,0,0,0,255,248, + 0,0,0,0,0,0,0,9,187,255,220,30,0,0,0,255,248,0,0,0,0,0,0,9, + 187,255,218,28,0,0,0,0,255,248,0,0,0,0,0,9,187,255,216,26,0,0,0,0, + 0,255,248,0,0,0,0,9,187,255,213,23,0,0,0,0,0,0,255,248,0,0,0,8, + 186,255,211,21,0,0,0,0,0,0,0,255,248,0,0,8,184,255,206,21,0,0,0,0, + 0,0,0,0,255,248,0,8,184,255,206,18,0,0,0,0,0,0,0,0,0,255,248,8, + 184,255,201,16,0,0,0,0,0,0,0,0,0,0,255,250,184,255,199,15,0,0,0,0, + 0,0,0,0,0,0,0,255,250,187,255,202,19,0,0,0,0,0,0,0,0,0,0,0, + 255,248,8,179,255,212,27,0,0,0,0,0,0,0,0,0,0,255,248,0,5,170,255,223, + 35,0,0,0,0,0,0,0,0,0,255,248,0,0,3,161,255,231,45,0,0,0,0,0, + 0,0,0,255,248,0,0,0,2,150,255,238,56,0,0,0,0,0,0,0,255,248,0,0, + 0,0,1,139,255,244,69,0,0,0,0,0,0,255,248,0,0,0,0,0,0,129,255,249, + 82,0,0,0,0,0,255,248,0,0,0,0,0,0,0,119,255,252,96,0,0,0,0,255, + 248,0,0,0,0,0,0,0,0,107,255,255,112,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0, + 0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0, + 255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0, + 0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0, + 0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0, + 0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0, + 255,254,180,180,180,180,180,180,180,180,180,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,0,0,0,255,255,255,100,0,0,0,0,0,0,0,0,0,0,82,255,255,248,0,0, + 0,0,255,255,255,192,0,0,0,0,0,0,0,0,0,0,174,253,255,248,0,0,0,0, + 255,248,197,254,30,0,0,0,0,0,0,0,0,19,249,187,255,248,0,0,0,0,255,248, + 106,255,123,0,0,0,0,0,0,0,0,104,255,95,255,248,0,0,0,0,255,248,19,251, + 215,0,0,0,0,0,0,0,0,196,246,13,255,248,0,0,0,0,255,248,0,178,255,52, + 0,0,0,0,0,0,33,255,167,0,255,248,0,0,0,0,255,248,0,86,255,146,0,0, + 0,0,0,0,125,255,75,0,255,248,0,0,0,0,255,248,0,9,242,234,4,0,0,0, + 0,0,218,235,4,0,255,248,0,0,0,0,255,248,0,0,158,255,76,0,0,0,0,54, + 255,148,0,0,255,248,0,0,0,0,255,248,0,0,66,255,169,0,0,0,0,147,255,56, + 0,0,255,248,0,0,0,0,255,248,0,0,2,228,247,14,0,0,4,234,219,1,0,0, + 255,248,0,0,0,0,255,248,0,0,0,138,255,100,0,0,76,255,129,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,46,255,192,0,0,169,255,37,0,0,0,255,248,0,0, + 0,0,255,248,0,0,0,0,210,254,31,14,247,201,0,0,0,0,255,248,0,0,0,0, + 255,248,0,0,0,0,118,255,123,97,255,109,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,28,254,216,190,252,22,0,0,0,0,255,248,0,0,0,0,255,248,0,0, + 0,0,0,190,255,255,182,0,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0, + 0,98,255,255,90,0,0,0,0,0,255,248,0,0,0,0,255,255,123,0,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,255,255,249,44,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,255,255,255,204,4,0,0,0,0,0,0,0,255,248,0,0,0,0,255,251, + 219,255,123,0,0,0,0,0,0,0,255,248,0,0,0,0,255,248,60,253,250,45,0,0, + 0,0,0,0,255,248,0,0,0,0,255,248,0,139,255,205,5,0,0,0,0,0,255,248, + 0,0,0,0,255,248,0,8,216,255,125,0,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,56,253,250,46,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,139,255,206, + 5,0,0,0,255,248,0,0,0,0,255,248,0,0,0,8,216,255,127,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,54,252,251,48,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,0,133,255,208,5,0,255,248,0,0,0,0,255,248,0,0,0,0,0,7, + 212,255,129,0,255,248,0,0,0,0,255,248,0,0,0,0,0,0,52,252,251,50,255,248, + 0,0,0,0,255,248,0,0,0,0,0,0,0,133,255,210,255,248,0,0,0,0,255,248, + 0,0,0,0,0,0,0,6,211,255,255,248,0,0,0,0,255,248,0,0,0,0,0,0, + 0,0,50,251,255,248,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,131,255,248, + 0,0,0,0,0,0,0,53,157,221,250,250,220,157,53,0,0,0,0,0,0,0,0,0, + 6,149,255,255,228,189,190,229,255,255,149,6,0,0,0,0,0,0,3,179,255,209,63,0, + 0,0,1,67,212,255,179,3,0,0,0,0,0,125,255,196,10,0,0,0,0,0,0,12, + 199,255,125,0,0,0,0,24,246,245,28,0,0,0,0,0,0,0,0,30,246,246,24,0, + 0,0,119,255,147,0,0,0,0,0,0,0,0,0,0,148,255,119,0,0,0,191,255,64, + 0,0,0,0,0,0,0,0,0,0,67,255,189,0,0,0,234,255,17,0,0,0,0,0, + 0,0,0,0,0,18,255,232,0,0,0,253,252,0,0,0,0,0,0,0,0,0,0,0, + 0,253,252,0,0,0,251,254,1,0,0,0,0,0,0,0,0,0,0,1,254,251,0,0, + 0,228,255,23,0,0,0,0,0,0,0,0,0,0,25,255,228,0,0,0,180,255,77,0, + 0,0,0,0,0,0,0,0,0,80,255,182,0,0,0,106,255,165,0,0,0,0,0,0, + 0,0,0,0,168,255,108,0,0,0,15,240,252,43,0,0,0,0,0,0,0,0,46,253, + 242,17,0,0,0,0,110,255,214,19,0,0,0,0,0,0,22,216,255,114,0,0,0,0, + 0,1,167,255,221,73,1,0,0,2,77,224,255,172,2,0,0,0,0,0,0,4,141,254, + 255,230,189,190,233,255,254,142,4,0,0,0,0,0,0,0,0,0,47,152,219,250,250,218, + 150,45,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,251,224,165,58,0,0,0, + 0,0,255,254,180,180,180,180,180,190,228,255,255,124,0,0,0,0,255,248,0,0,0,0, + 0,0,1,77,239,255,69,0,0,0,255,248,0,0,0,0,0,0,0,0,98,255,181,0, + 0,0,255,248,0,0,0,0,0,0,0,0,18,255,233,0,0,0,255,248,0,0,0,0, + 0,0,0,0,3,255,244,0,0,0,255,248,0,0,0,0,0,0,0,0,39,255,219,0, + 0,0,255,248,0,0,0,0,0,0,0,0,153,255,149,0,0,0,255,248,0,0,0,0, + 0,10,52,163,255,240,29,0,0,0,255,255,255,255,255,255,255,255,255,255,222,52,0,0, + 0,0,255,254,180,180,180,180,180,174,147,84,6,0,0,0,0,0,255,248,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,53,157,221,250,250,220,157,53,0,0,0, + 0,0,0,0,0,0,6,149,255,255,228,189,190,229,255,255,149,6,0,0,0,0,0,0, + 3,179,255,209,63,0,0,0,1,67,212,255,179,3,0,0,0,0,0,125,255,195,10,0, + 0,0,0,0,0,12,198,255,125,0,0,0,0,24,246,244,26,0,0,0,0,0,0,0, + 0,29,246,246,24,0,0,0,119,255,144,0,0,0,0,0,0,0,0,0,0,147,255,119, + 0,0,0,191,255,64,0,0,0,0,0,0,0,0,0,0,64,255,189,0,0,0,234,255, + 16,0,0,0,0,0,0,0,0,0,0,17,255,232,0,0,0,253,252,0,0,0,0,0, + 0,0,0,0,0,0,0,252,252,0,0,0,249,254,1,0,0,0,0,0,0,0,0,0, + 0,1,255,250,0,0,0,227,255,26,0,0,0,0,0,0,0,0,0,0,26,255,227,0, + 0,0,183,255,81,0,0,0,0,0,0,0,0,0,0,81,255,177,0,0,0,111,255,169, + 0,0,0,0,0,0,0,0,0,0,169,255,99,0,0,0,20,244,253,46,0,0,0,0, + 0,0,0,0,46,253,230,10,0,0,0,0,121,255,215,20,0,0,0,0,0,0,19,214, + 254,77,0,0,0,0,0,2,174,255,222,73,1,0,0,1,73,221,247,93,0,0,0,0, + 0,0,0,4,139,253,255,231,189,190,231,255,255,201,187,207,226,246,0,0,0,0,0,0, + 41,144,209,245,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,253,234, + 180,78,0,0,0,0,0,0,255,254,180,180,180,180,186,215,255,255,156,1,0,0,0,0, + 255,248,0,0,0,0,0,0,53,226,255,101,0,0,0,0,255,248,0,0,0,0,0,0, + 0,71,255,205,0,0,0,0,255,248,0,0,0,0,0,0,0,5,254,249,0,0,0,0, + 255,248,0,0,0,0,0,0,0,7,255,246,0,0,0,0,255,248,0,0,0,0,0,0, + 0,79,255,201,0,0,0,0,255,248,0,0,0,0,0,0,58,230,255,97,0,0,0,0, + 255,254,180,180,180,180,186,217,255,255,152,1,0,0,0,0,255,255,255,255,255,255,253,234, + 178,75,0,0,0,0,0,0,255,250,162,255,230,43,0,0,0,0,0,0,0,0,0,0, + 255,248,2,158,255,235,50,0,0,0,0,0,0,0,0,0,255,248,0,2,158,255,240,57, + 0,0,0,0,0,0,0,0,255,248,0,0,2,155,255,244,66,0,0,0,0,0,0,0, + 255,248,0,0,0,1,151,255,247,74,0,0,0,0,0,0,255,248,0,0,0,0,1,151, + 255,250,84,0,0,0,0,0,255,248,0,0,0,0,0,1,147,255,252,94,0,0,0,0, + 255,248,0,0,0,0,0,0,1,147,255,254,104,0,0,0,0,0,78,178,232,253,245,204, + 122,13,0,0,0,0,0,3,163,255,253,207,184,198,246,255,222,35,0,0,0,0,116,255, + 200,32,0,0,0,15,161,255,212,4,0,0,0,218,255,39,0,0,0,0,0,3,210,255, + 84,0,0,0,252,253,1,0,0,0,0,0,0,107,255,155,0,0,0,235,255,55,0,0, + 0,0,0,0,17,64,45,0,0,0,152,255,225,61,0,0,0,0,0,0,0,0,0,0, + 0,17,208,255,255,200,109,22,0,0,0,0,0,0,0,0,0,11,140,247,255,255,252,183, + 84,3,0,0,0,0,0,0,0,0,18,107,196,255,255,255,209,42,0,0,0,0,0,0, + 0,0,0,0,29,120,226,255,236,32,0,0,0,0,0,0,0,0,0,0,0,11,185,255, + 160,0,0,144,204,53,0,0,0,0,0,0,0,39,255,228,0,0,132,255,130,0,0,0, + 0,0,0,0,5,255,245,0,0,43,254,237,26,0,0,0,0,0,0,49,255,217,0,0, + 0,146,255,218,64,0,0,0,0,36,207,255,128,0,0,0,4,154,255,255,229,189,184,210, + 254,255,187,8,0,0,0,0,0,59,160,220,248,254,236,187,96,3,0,0,0,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,0,0,180,180,180,180,180,180,255,254,180,180,180, + 180,180,180,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,248,255,0,0,0,0,255,248,0,0,0,0,0,0,0, + 0,0,248,255,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,248,255,0,0,0, + 0,255,248,0,0,0,0,0,0,0,0,0,248,255,0,0,0,0,255,248,0,0,0,0, + 0,0,0,0,0,248,255,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,248,255, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,248,255,0,0,0,0,255,248,0, + 0,0,0,0,0,0,0,0,248,255,0,0,0,0,255,248,0,0,0,0,0,0,0,0, + 0,248,255,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,248,255,0,0,0,0, + 255,248,0,0,0,0,0,0,0,0,0,248,255,0,0,0,0,245,254,3,0,0,0,0, + 0,0,0,3,254,245,0,0,0,0,217,255,33,0,0,0,0,0,0,0,33,255,217,0, + 0,0,0,158,255,102,0,0,0,0,0,0,0,102,255,158,0,0,0,0,65,255,220,11, + 0,0,0,0,0,11,218,255,63,0,0,0,0,0,177,255,190,31,0,0,0,32,190,255, + 172,0,0,0,0,0,0,14,193,255,252,204,182,205,253,255,188,13,0,0,0,0,0,0, + 0,3,98,191,239,254,239,188,93,2,0,0,0,0,7,237,255,38,0,0,0,0,0,0, + 0,0,0,0,146,255,108,0,149,255,132,0,0,0,0,0,0,0,0,0,4,234,249,19, + 0,56,255,223,1,0,0,0,0,0,0,0,0,75,255,175,0,0,0,216,255,61,0,0, + 0,0,0,0,0,0,167,255,80,0,0,0,120,255,154,0,0,0,0,0,0,0,13,246, + 235,5,0,0,0,29,254,240,7,0,0,0,0,0,0,95,255,146,0,0,0,0,0,188, + 255,84,0,0,0,0,0,0,188,255,52,0,0,0,0,0,92,255,176,0,0,0,0,0, + 26,253,212,0,0,0,0,0,0,10,243,250,19,0,0,0,0,116,255,116,0,0,0,0, + 0,0,0,160,255,107,0,0,0,0,208,253,26,0,0,0,0,0,0,0,64,255,200,0, + 0,0,44,255,184,0,0,0,0,0,0,0,0,1,223,255,37,0,0,136,255,88,0,0, + 0,0,0,0,0,0,0,130,255,129,0,2,227,241,9,0,0,0,0,0,0,0,0,0, + 37,255,221,1,65,255,156,0,0,0,0,0,0,0,0,0,0,0,196,255,59,156,255,60, + 0,0,0,0,0,0,0,0,0,0,0,100,255,159,241,219,1,0,0,0,0,0,0,0, + 0,0,0,0,16,247,255,255,127,0,0,0,0,0,0,0,0,0,0,0,0,0,168,255, + 255,33,0,0,0,0,0,0,0,168,255,92,0,0,0,0,0,0,0,54,255,255,118,0, + 0,0,0,0,0,0,24,255,204,0,0,95,255,162,0,0,0,0,0,0,0,123,255,255, + 189,0,0,0,0,0,0,0,94,255,131,0,0,23,255,231,1,0,0,0,0,0,0,193, + 252,234,248,10,0,0,0,0,0,0,163,255,58,0,0,0,206,255,46,0,0,0,0,0, + 12,250,196,157,255,70,0,0,0,0,0,1,233,239,3,0,0,0,133,255,117,0,0,0, + 0,0,75,255,124,86,255,141,0,0,0,0,0,49,255,168,0,0,0,0,60,255,188,0, + 0,0,0,0,144,255,53,17,253,210,0,0,0,0,0,117,255,96,0,0,0,0,4,240, + 248,10,0,0,0,0,213,236,2,0,198,255,24,0,0,0,0,189,255,25,0,0,0,0, + 0,172,255,73,0,0,0,27,255,167,0,0,128,255,94,0,0,0,10,248,208,0,0,0, + 0,0,0,98,255,143,0,0,0,95,255,94,0,0,57,255,163,0,0,0,75,255,134,0, + 0,0,0,0,0,26,255,214,0,0,0,164,255,24,0,0,3,238,232,1,0,0,143,255, + 62,0,0,0,0,0,0,0,209,255,29,0,1,232,208,0,0,0,0,170,255,46,0,0, + 214,240,4,0,0,0,0,0,0,0,135,255,98,0,46,255,135,0,0,0,0,99,255,115, + 0,29,255,172,0,0,0,0,0,0,0,0,64,255,169,0,115,255,65,0,0,0,0,29, + 255,186,0,98,255,99,0,0,0,0,0,0,0,0,6,242,237,3,186,244,6,0,0,0, + 0,0,212,246,8,169,255,28,0,0,0,0,0,0,0,0,0,174,255,61,246,178,0,0, + 0,0,0,0,142,255,71,237,210,0,0,0,0,0,0,0,0,0,0,103,255,191,255,106, + 0,0,0,0,0,0,70,255,192,255,138,0,0,0,0,0,0,0,0,0,0,30,255,255, + 255,35,0,0,0,0,0,0,8,247,255,255,64,0,0,0,0,0,0,0,0,0,0,0, + 212,255,221,0,0,0,0,0,0,0,0,185,255,243,6,0,0,0,0,0,0,104,255,221, + 11,0,0,0,0,0,0,0,13,223,253,56,0,0,1,188,255,147,0,0,0,0,0,0, + 0,156,255,133,0,0,0,0,32,243,254,62,0,0,0,0,0,77,255,210,6,0,0,0, + 0,0,104,255,220,11,0,0,0,20,233,250,47,0,0,0,0,0,0,1,191,255,144,0, + 0,0,172,255,123,0,0,0,0,0,0,0,0,33,244,254,61,0,92,255,200,4,0,0, + 0,0,0,0,0,0,0,108,255,219,39,240,246,40,0,0,0,0,0,0,0,0,0,0, + 1,193,255,242,255,110,0,0,0,0,0,0,0,0,0,0,0,0,35,253,255,194,2,0, + 0,0,0,0,0,0,0,0,0,0,0,79,255,255,217,9,0,0,0,0,0,0,0,0, + 0,0,0,20,233,250,209,255,140,0,0,0,0,0,0,0,0,0,0,0,170,255,123,35, + 245,253,57,0,0,0,0,0,0,0,0,0,90,255,201,4,0,109,255,217,8,0,0,0, + 0,0,0,0,26,237,246,40,0,0,2,193,255,139,0,0,0,0,0,0,0,181,255,110, + 0,0,0,0,34,245,253,56,0,0,0,0,0,101,255,192,2,0,0,0,0,0,108,255, + 216,8,0,0,0,32,242,242,32,0,0,0,0,0,0,1,191,255,139,0,0,2,192,255, + 100,0,0,0,0,0,0,0,0,33,244,253,56,0,57,253,251,49,0,0,0,0,0,0, + 0,0,23,237,252,51,0,142,255,207,5,0,0,0,0,0,0,0,172,255,133,0,0,10, + 219,255,124,0,0,0,0,0,0,84,255,212,7,0,0,0,61,254,249,44,0,0,0,0, + 19,233,252,52,0,0,0,0,0,144,255,201,3,0,0,0,166,255,133,0,0,0,0,0, + 0,10,219,255,117,0,0,79,255,212,7,0,0,0,0,0,0,0,62,254,248,38,16,229, + 253,55,0,0,0,0,0,0,0,0,0,145,255,195,160,255,139,0,0,0,0,0,0,0, + 0,0,0,11,221,255,255,216,8,0,0,0,0,0,0,0,0,0,0,0,63,255,255,56, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255, + 255,255,255,0,0,0,180,180,180,180,180,180,180,180,180,224,255,232,0,0,0,0,0,0, + 0,0,0,0,0,20,234,255,82,0,0,0,0,0,0,0,0,0,0,0,162,255,170,0, + 0,0,0,0,0,0,0,0,0,0,71,255,236,22,0,0,0,0,0,0,0,0,0,0, + 11,224,255,90,0,0,0,0,0,0,0,0,0,0,0,143,255,177,0,0,0,0,0,0, + 0,0,0,0,0,55,253,240,27,0,0,0,0,0,0,0,0,0,0,6,211,255,97,0, + 0,0,0,0,0,0,0,0,0,0,125,255,186,1,0,0,0,0,0,0,0,0,0,0, + 40,249,243,31,0,0,0,0,0,0,0,0,0,0,2,194,255,105,0,0,0,0,0,0, + 0,0,0,0,0,105,255,193,1,0,0,0,0,0,0,0,0,0,0,29,242,246,36,0, + 0,0,0,0,0,0,0,0,0,0,177,255,112,0,0,0,0,0,0,0,0,0,0,0, + 87,255,198,2,0,0,0,0,0,0,0,0,0,0,0,233,255,207,180,180,180,180,180,180, + 180,180,180,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,180,180, + 180,180,180,180,0,0,0,0,255,255,255,255,255,255,0,0,0,0,255,248,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0, + 255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0, + 0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0, + 0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0, + 255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0, + 0,0,0,0,0,0,255,254,180,180,180,180,0,0,0,0,255,255,255,255,255,255,0,0, + 0,63,128,25,0,0,0,0,0,0,0,0,68,255,108,0,0,0,0,0,0,0,0,6, + 243,184,0,0,0,0,0,0,0,0,0,173,248,11,0,0,0,0,0,0,0,0,97,255, + 79,0,0,0,0,0,0,0,0,23,254,154,0,0,0,0,0,0,0,0,0,200,229,1, + 0,0,0,0,0,0,0,0,125,255,50,0,0,0,0,0,0,0,0,48,255,126,0,0, + 0,0,0,0,0,0,1,229,201,0,0,0,0,0,0,0,0,0,153,254,23,0,0,0, + 0,0,0,0,0,77,255,97,0,0,0,0,0,0,0,0,10,247,172,0,0,0,0,0, + 0,0,0,0,180,243,6,0,0,0,0,0,0,0,0,106,255,67,0,0,0,0,0,0, + 0,0,30,255,144,0,0,0,0,0,0,0,0,0,210,218,0,0,0,0,0,0,0,0, + 0,134,255,39,0,0,0,0,0,0,0,0,58,255,115,0,0,0,0,0,0,0,0,3, + 235,191,0,0,0,0,0,0,0,0,0,161,250,16,0,0,90,180,180,180,180,180,174,0, + 0,0,128,255,255,255,255,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0, + 0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255, + 248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0, + 0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0, + 0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0, + 0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0, + 0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255, + 248,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,255,248,0,0,0, + 90,180,180,180,180,255,248,0,0,0,128,255,255,255,255,255,248,0,0,0,0,0,0,4, + 211,255,146,0,0,0,0,0,0,0,0,0,114,255,240,253,50,0,0,0,0,0,0,0, + 29,243,202,36,242,201,2,0,0,0,0,0,0,172,245,37,0,91,255,104,0,0,0,0, + 0,73,255,102,0,0,0,167,239,22,0,0,0,9,222,178,0,0,0,0,16,226,162,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,180, + 180,180,180,180,180,180,180,180,180,255,255,255,255,255,255,255,255,255,255,0,43,235,255,186, + 1,0,0,0,0,0,48,238,255,105,0,0,0,0,0,0,55,242,244,34,0,0,0,0, + 0,0,62,246,195,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,94,192,241,254,237,177,52,0,0,0,0,1,166,255,252,203,184,222,255,248,52,0,0, + 0,76,255,222,36,0,0,1,144,255,177,0,0,0,120,228,91,0,0,0,0,23,255,238, + 0,0,0,0,0,0,0,0,0,0,0,252,255,0,0,0,0,0,1,46,102,146,190,234, + 255,255,0,0,0,0,88,218,255,255,252,216,175,254,255,0,0,0,105,255,235,118,47,6, + 0,0,248,255,0,0,0,224,255,50,0,0,0,0,0,251,255,0,0,0,254,252,2,0, + 0,0,0,43,255,255,0,0,0,220,255,95,0,0,0,40,209,255,255,1,0,0,96,255, + 255,209,184,208,254,184,249,255,13,0,0,0,79,195,245,250,214,122,6,229,255,46,0,0, + 0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,248,0,46,169,233,252,229,158,32,0,0,0,0,0,255,248,72,248,241, + 189,189,239,255,240,56,0,0,0,0,255,251,231,149,10,0,0,9,145,255,230,13,0,0, + 0,255,255,183,1,0,0,0,0,0,182,255,115,0,0,0,255,255,68,0,0,0,0,0, + 0,71,255,191,0,0,0,255,255,10,0,0,0,0,0,0,17,255,233,0,0,0,255,250, + 0,0,0,0,0,0,0,2,255,246,0,0,0,255,255,10,0,0,0,0,0,0,18,255, + 231,0,0,0,255,255,64,0,0,0,0,0,0,73,255,188,0,0,0,255,255,180,0,0, + 0,0,0,1,187,255,109,0,0,0,255,252,240,145,9,0,0,9,148,255,224,10,0,0, + 0,255,248,90,253,241,189,189,240,255,236,49,0,0,0,0,255,248,0,60,182,239,252,225, + 146,26,0,0,0,0,0,0,14,129,216,250,242,188,76,0,0,0,0,0,34,220,255,237, + 187,197,250,255,136,0,0,0,6,211,255,141,7,0,0,31,208,255,77,0,0,106,255,181, + 0,0,0,0,0,41,254,188,0,0,193,255,66,0,0,0,0,0,0,92,95,0,0,239, + 255,10,0,0,0,0,0,0,0,0,0,0,253,250,0,0,0,0,0,0,0,0,0,0, + 0,237,255,13,0,0,0,0,0,0,0,0,0,0,188,255,72,0,0,0,0,0,0,85, + 92,0,0,97,255,190,1,0,0,0,0,35,253,190,0,0,4,208,255,151,9,0,0,31, + 207,255,75,0,0,0,31,219,255,238,188,197,250,255,131,0,0,0,0,0,13,127,215,250, + 243,188,75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,248,0,0,0,0,0,29,150,226,252,238,178,55,0,255,248,0,0, + 0,0,54,238,255,238,188,191,243,251,82,255,248,0,0,0,13,230,255,139,8,0,0,11, + 152,235,255,248,0,0,0,116,255,176,0,0,0,0,0,1,188,255,248,0,0,0,196,255, + 64,0,0,0,0,0,0,72,255,248,0,0,0,239,255,10,0,0,0,0,0,0,18,255, + 248,0,0,0,254,250,0,0,0,0,0,0,0,2,255,248,0,0,0,242,255,9,0,0, + 0,0,0,0,18,255,248,0,0,0,200,255,63,0,0,0,0,0,0,73,255,248,0,0, + 0,122,255,175,0,0,0,0,0,1,187,255,248,0,0,0,16,233,255,139,8,0,0,10, + 149,241,255,248,0,0,0,0,58,240,255,238,188,190,241,251,84,255,248,0,0,0,0,0, + 32,155,228,252,236,175,53,0,255,248,0,0,0,0,0,8,111,202,245,252,223,151,32,0, + 0,0,0,0,30,213,255,246,194,187,230,255,244,67,0,0,0,6,209,255,164,16,0,0, + 3,111,255,239,23,0,0,106,255,190,2,0,0,0,0,0,154,255,134,0,0,192,255,68, + 0,0,0,0,0,0,54,255,208,0,0,239,255,189,180,180,180,180,180,180,188,255,241,0, + 0,253,255,255,255,255,255,255,255,255,255,255,245,0,0,237,255,13,0,0,0,0,0,0, + 0,0,0,0,0,185,255,75,0,0,0,0,0,0,0,0,0,0,0,92,255,198,3,0, + 0,0,0,0,109,200,89,0,0,2,194,255,168,18,0,0,1,96,250,228,15,0,0,0, + 21,202,255,247,195,186,228,255,232,49,0,0,0,0,0,5,108,200,245,252,220,139,22,0, + 0,0,0,0,0,1,110,215,250,238,0,0,0,0,112,255,252,194,192,0,0,0,0,219, + 255,84,0,0,0,0,0,0,253,253,3,0,0,0,0,0,0,255,248,0,0,0,0,128, + 255,255,255,255,255,255,255,0,90,180,180,255,254,180,180,180,0,0,0,0,255,248,0,0, + 0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255, + 248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0, + 0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0, + 0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,23, + 144,221,251,240,187,69,0,255,248,0,0,0,0,49,234,255,239,189,189,241,255,100,255,248, + 0,0,0,11,225,255,142,9,0,0,11,151,244,255,248,0,0,0,113,255,177,0,0,0, + 0,0,1,185,255,248,0,0,0,194,255,64,0,0,0,0,0,0,72,255,248,0,0,0, + 239,255,10,0,0,0,0,0,0,17,255,248,0,0,0,254,250,0,0,0,0,0,0,0, + 2,255,248,0,0,0,241,255,10,0,0,0,0,0,0,21,255,248,0,0,0,200,255,67, + 0,0,0,0,0,0,79,255,248,0,0,0,122,255,184,1,0,0,0,0,2,194,255,248, + 0,0,0,15,233,255,151,10,0,0,12,157,238,255,248,0,0,0,0,57,240,255,241,189, + 190,241,250,80,255,248,0,0,0,0,0,32,155,229,252,236,171,49,3,255,244,0,0,0, + 0,0,0,0,0,0,0,0,0,32,255,217,0,0,0,89,168,63,0,0,0,0,0,0, + 119,255,149,0,0,0,48,253,227,58,0,0,0,1,90,248,245,33,0,0,0,0,102,252, + 255,222,186,188,228,255,245,76,0,0,0,0,0,0,44,155,222,250,250,219,147,33,0,0, + 0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0, + 0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0, + 0,0,255,248,0,34,155,227,252,236,168,36,0,0,0,0,255,248,68,244,246,194,187,237, + 255,238,38,0,0,0,255,252,239,157,16,0,0,9,177,255,170,0,0,0,255,255,178,1, + 0,0,0,0,40,255,234,0,0,0,255,255,54,0,0,0,0,0,3,255,248,0,0,0, + 255,255,5,0,0,0,0,0,0,255,248,0,0,0,255,248,0,0,0,0,0,0,0,255, + 248,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,255,248,0,0,0,0, + 0,0,0,255,248,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,255,248, + 0,0,0,0,0,0,0,255,248,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0, + 0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,40,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,0,255,248,0,0,0,0,0,255,248,0,0,0,0,0,40,39,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,255, + 248,0,0,0,0,0,255,248,0,0,0,0,0,255,248,0,0,0,0,0,255,248,0,0, + 0,0,0,255,248,0,0,0,0,0,255,248,0,0,0,0,0,255,248,0,0,0,0,0, + 255,248,0,0,0,0,0,255,248,0,0,0,0,0,255,248,0,0,0,0,0,255,248,0, + 0,0,0,0,255,248,0,0,0,0,0,255,248,0,0,0,0,1,255,248,0,0,0,0, + 50,255,229,0,0,194,187,243,255,148,0,0,236,254,228,144,9,0,0,0,0,128,124,0, + 0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,255, + 248,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0, + 0,255,248,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0, + 0,0,0,255,248,0,0,0,0,5,175,255,212,23,0,0,255,248,0,0,0,6,175,255, + 208,21,0,0,0,255,248,0,0,6,178,255,203,18,0,0,0,0,255,248,0,6,178,255, + 199,15,0,0,0,0,0,255,248,6,178,255,192,13,0,0,0,0,0,0,255,250,178,255, + 188,10,0,0,0,0,0,0,0,255,254,248,255,104,0,0,0,0,0,0,0,0,255,248, + 74,250,253,89,0,0,0,0,0,0,0,255,248,0,81,252,252,82,0,0,0,0,0,0, + 255,248,0,0,90,253,250,76,0,0,0,0,0,255,248,0,0,0,98,255,248,70,0,0, + 0,0,255,248,0,0,0,0,107,255,247,65,0,0,0,255,248,0,0,0,0,0,116,255, + 245,59,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248, + 0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,49,177,241,249,199, + 70,0,0,64,184,242,249,198,67,0,0,0,0,0,255,248,80,250,234,187,214,255,253,67, + 114,255,235,188,211,255,253,70,0,0,0,0,255,253,242,130,5,0,0,137,255,226,254,138, + 6,0,0,128,255,202,0,0,0,0,255,255,164,0,0,0,0,20,255,255,173,0,0,0, + 0,14,254,252,3,0,0,0,255,255,50,0,0,0,0,0,250,255,59,0,0,0,0,0, + 242,255,8,0,0,0,255,255,4,0,0,0,0,0,248,255,10,0,0,0,0,0,240,255, + 8,0,0,0,255,248,0,0,0,0,0,0,248,255,0,0,0,0,0,0,240,255,8,0, + 0,0,255,248,0,0,0,0,0,0,248,255,0,0,0,0,0,0,240,255,8,0,0,0, + 255,248,0,0,0,0,0,0,248,255,0,0,0,0,0,0,240,255,8,0,0,0,255,248, + 0,0,0,0,0,0,248,255,0,0,0,0,0,0,240,255,8,0,0,0,255,248,0,0, + 0,0,0,0,248,255,0,0,0,0,0,0,240,255,8,0,0,0,255,248,0,0,0,0, + 0,0,248,255,0,0,0,0,0,0,240,255,8,0,0,0,255,248,0,0,0,0,0,0, + 248,255,0,0,0,0,0,0,240,255,8,0,0,0,255,248,0,34,155,227,252,236,168,36, + 0,0,0,0,255,248,68,244,246,194,187,237,255,238,38,0,0,0,255,252,239,157,16,0, + 0,9,177,255,170,0,0,0,255,255,178,1,0,0,0,0,40,255,234,0,0,0,255,255, + 54,0,0,0,0,0,3,255,248,0,0,0,255,255,5,0,0,0,0,0,0,255,248,0, + 0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,255,248,0,0,0,0,0,0, + 0,255,248,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,255,248,0,0, + 0,0,0,0,0,255,248,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0, + 255,248,0,0,0,0,0,0,0,255,248,0,0,0,255,248,0,0,0,0,0,0,0,255, + 248,0,0,0,0,11,122,208,248,249,208,124,12,0,0,0,0,0,30,216,255,243,190,190, + 241,255,220,34,0,0,0,6,209,255,156,12,0,0,11,152,255,213,7,0,0,102,255,187, + 1,0,0,0,0,1,186,255,106,0,0,192,255,67,0,0,0,0,0,0,67,255,193,0, + 0,239,255,10,0,0,0,0,0,0,10,255,239,0,0,253,251,0,0,0,0,0,0,0, + 0,251,253,0,0,237,255,14,0,0,0,0,0,0,14,255,237,0,0,187,255,77,0,0, + 0,0,0,0,77,255,187,0,0,97,255,196,2,0,0,0,0,2,196,255,97,0,0,4, + 204,255,164,15,0,0,14,163,255,204,4,0,0,0,28,214,255,244,191,192,244,255,215,28, + 0,0,0,0,0,10,119,208,249,249,208,121,11,0,0,0,0,0,255,248,0,64,179,236, + 252,228,154,30,0,0,0,0,0,255,248,117,255,241,189,189,239,255,239,54,0,0,0,0, + 255,254,253,149,10,0,0,9,145,255,227,12,0,0,0,255,255,183,1,0,0,0,0,0, + 182,255,113,0,0,0,255,255,68,0,0,0,0,0,0,71,255,191,0,0,0,255,255,10, + 0,0,0,0,0,0,17,255,232,0,0,0,255,250,0,0,0,0,0,0,0,2,255,246, + 0,0,0,255,255,10,0,0,0,0,0,0,18,255,231,0,0,0,255,255,64,0,0,0, + 0,0,0,73,255,187,0,0,0,255,255,180,0,0,0,0,0,1,187,255,106,0,0,0, + 255,254,250,145,9,0,0,9,148,255,222,9,0,0,0,255,248,112,255,241,189,189,240,255, + 233,46,0,0,0,0,255,248,0,72,188,242,253,221,142,22,0,0,0,0,0,255,248,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,26,146,222,252,240,185,67,0,255,248,0,0,0,0,51,236,255,238,188,191,243,255,103, + 255,248,0,0,0,12,227,255,139,8,0,0,11,152,247,255,248,0,0,0,115,255,176,0, + 0,0,0,0,1,188,255,248,0,0,0,195,255,64,0,0,0,0,0,0,72,255,248,0, + 0,0,239,255,10,0,0,0,0,0,0,18,255,248,0,0,0,254,250,0,0,0,0,0, + 0,0,2,255,248,0,0,0,240,255,9,0,0,0,0,0,0,18,255,248,0,0,0,200, + 255,63,0,0,0,0,0,0,73,255,248,0,0,0,122,255,175,0,0,0,0,0,1,187, + 255,248,0,0,0,16,233,255,139,8,0,0,10,149,251,255,248,0,0,0,0,60,240,255, + 238,188,190,241,254,108,255,248,0,0,0,0,0,31,153,226,252,238,182,65,0,255,248,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,255,248,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,248,0,0,0,0,255,248,0,94,210,251,128,0,0,255,248,122,255,220,183,94,0,0, + 255,254,243,87,0,0,0,0,0,255,255,136,0,0,0,0,0,0,255,255,44,0,0,0, + 0,0,0,255,255,4,0,0,0,0,0,0,255,249,0,0,0,0,0,0,0,255,248,0, + 0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0, + 255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0, + 0,0,0,73,191,241,249,221,139,16,0,0,0,94,255,254,201,187,236,255,214,14,0,0, + 221,255,74,0,0,10,183,255,125,0,0,252,253,2,0,0,0,46,192,141,0,0,213,255, + 118,1,0,0,0,0,0,0,0,80,253,255,214,132,57,2,0,0,0,0,0,66,196,255, + 255,255,224,86,0,0,0,0,0,0,25,95,172,251,255,87,0,17,32,14,0,0,0,0, + 73,255,213,0,110,255,150,0,0,0,0,2,253,252,0,32,251,249,72,0,0,0,97,255, + 221,0,0,115,255,255,213,184,211,255,255,93,0,0,0,72,184,238,254,238,184,69,0,0, + 0,0,0,40,133,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0, + 0,0,0,0,0,0,255,248,0,0,0,0,128,255,255,255,255,255,255,255,128,90,180,180, + 255,254,180,180,180,90,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0, + 0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0, + 0,0,0,0,0,0,255,248,0,0,0,0,0,0,0,255,248,0,0,0,0,0,0,0, + 251,253,2,0,0,0,0,0,0,219,255,79,0,0,0,0,0,0,115,255,253,197,184,107, + 0,0,0,2,120,219,253,247,108,0,0,255,248,0,0,0,0,0,0,248,255,0,0,0, + 0,255,248,0,0,0,0,0,0,248,255,0,0,0,0,255,248,0,0,0,0,0,0,248, + 255,0,0,0,0,255,248,0,0,0,0,0,0,248,255,0,0,0,0,255,248,0,0,0, + 0,0,0,248,255,0,0,0,0,255,248,0,0,0,0,0,0,248,255,0,0,0,0,255, + 248,0,0,0,0,0,0,248,255,0,0,0,0,255,248,0,0,0,0,0,4,255,255,0, + 0,0,0,255,251,0,0,0,0,0,50,255,255,0,0,0,0,243,255,26,0,0,0,0, + 161,255,255,0,0,0,0,185,255,150,1,0,2,119,244,253,255,0,0,0,0,55,249,255, + 220,184,228,251,86,248,255,0,0,0,0,0,60,193,248,242,180,53,0,248,255,0,0,94, + 255,174,0,0,0,0,0,0,15,247,232,5,10,241,249,19,0,0,0,0,0,100,255,139, + 0,0,151,255,105,0,0,0,0,0,192,255,40,0,0,51,255,197,0,0,0,0,30,254, + 195,0,0,0,0,208,255,35,0,0,0,120,255,95,0,0,0,0,108,255,127,0,0,0, + 213,242,10,0,0,0,0,16,248,219,1,0,50,255,152,0,0,0,0,0,0,164,255,56, + 0,142,255,52,0,0,0,0,0,0,65,255,149,3,232,208,0,0,0,0,0,0,0,1, + 219,237,78,255,109,0,0,0,0,0,0,0,0,122,255,226,248,17,0,0,0,0,0,0, + 0,0,25,252,255,165,0,0,0,0,0,0,0,0,0,0,178,255,66,0,0,0,0,0, + 48,255,189,0,0,0,0,0,175,255,48,0,0,0,0,60,255,151,1,229,248,8,0,0, + 0,9,245,255,130,0,0,0,0,128,255,77,0,156,255,66,0,0,0,78,255,255,210,0, + 0,0,0,192,248,11,0,82,255,132,0,0,0,158,250,168,255,33,0,0,10,248,185,0, + 0,13,250,198,0,0,3,235,184,70,255,113,0,0,68,255,112,0,0,0,190,252,14,0, + 62,255,102,5,240,194,0,0,133,255,39,0,0,0,116,255,76,0,140,253,21,0,163,253, + 21,0,199,220,0,0,0,0,43,255,142,0,218,191,0,0,82,255,97,13,252,146,0,0, + 0,0,0,224,208,42,255,108,0,0,10,245,178,76,255,71,0,0,0,0,0,151,254,143, + 255,27,0,0,0,174,247,151,246,9,0,0,0,0,0,77,255,250,198,0,0,0,0,93, + 255,250,180,0,0,0,0,0,0,11,248,255,115,0,0,0,0,16,250,255,107,0,0,0, + 0,0,0,0,185,255,34,0,0,0,0,0,186,255,33,0,0,0,0,86,255,216,9,0, + 0,0,0,56,253,223,14,0,0,165,255,142,0,0,0,10,219,253,60,0,0,0,16,226, + 254,61,0,0,145,255,132,0,0,0,0,0,65,254,219,10,63,254,204,5,0,0,0,0, + 0,0,141,255,156,222,245,38,0,0,0,0,0,0,0,7,211,255,255,102,0,0,0,0, + 0,0,0,0,0,135,255,244,14,0,0,0,0,0,0,0,0,55,251,243,255,160,0,0, + 0,0,0,0,0,15,223,246,45,201,255,82,0,0,0,0,0,0,171,255,107,0,40,248, + 236,24,0,0,0,0,102,255,181,1,0,0,117,255,182,1,0,0,44,247,234,23,0,0, + 0,3,200,255,104,0,9,212,255,78,0,0,0,0,0,40,247,245,37,89,255,192,0,0, + 0,0,0,0,16,248,232,5,6,232,255,41,0,0,0,0,0,104,255,137,0,0,129,255, + 144,0,0,0,0,0,203,255,36,0,0,27,251,239,10,0,0,0,44,255,190,0,0,0, + 0,168,255,96,0,0,0,140,255,89,0,0,0,0,60,255,199,0,0,4,232,236,7,0, + 0,0,0,0,207,255,49,0,76,255,142,0,0,0,0,0,0,98,255,152,0,173,255,42, + 0,0,0,0,0,0,9,238,243,34,251,197,0,0,0,0,0,0,0,0,139,255,208,255, + 96,0,0,0,0,0,0,0,0,33,253,255,240,10,0,0,0,0,0,0,0,0,0,179, + 255,148,0,0,0,0,0,0,0,0,0,0,196,255,48,0,0,0,0,0,0,0,0,0, + 45,255,203,0,0,0,0,0,0,0,0,0,0,151,255,102,0,0,0,0,0,0,0,0, + 0,13,243,244,13,0,0,0,0,0,0,0,0,0,105,255,155,0,0,0,0,0,0,0, + 0,0,0,211,255,54,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0, + 0,0,180,180,180,180,180,180,221,255,229,0,0,0,0,0,0,0,0,16,229,255,72,0, + 0,0,0,0,0,0,0,160,255,150,0,0,0,0,0,0,0,0,75,255,219,11,0,0, + 0,0,0,0,0,16,229,253,57,0,0,0,0,0,0,0,0,160,255,133,0,0,0,0, + 0,0,0,0,75,255,207,6,0,0,0,0,0,0,0,16,230,249,45,0,0,0,0,0, + 0,0,0,162,255,115,0,0,0,0,0,0,0,0,77,255,193,2,0,0,0,0,0,0, + 0,0,231,255,204,180,180,180,180,180,180,180,0,0,255,255,255,255,255,255,255,255,255,255, + 0,0,0,0,0,70,157,179,180,90,0,0,0,0,105,255,255,255,255,128,0,0,0,0, + 229,250,67,3,0,0,0,0,0,3,255,211,0,0,0,0,0,0,0,0,226,241,2,0, + 0,0,0,0,0,0,176,255,39,0,0,0,0,0,0,0,126,255,92,0,0,0,0,0, + 0,0,74,255,143,0,0,0,0,0,0,0,32,255,180,0,0,0,0,0,0,0,65,255, + 159,0,0,0,0,0,180,188,245,225,35,0,0,0,0,0,255,255,255,171,9,0,0,0, + 0,0,0,8,121,255,134,0,0,0,0,0,0,0,28,255,182,0,0,0,0,0,0,0, + 59,255,158,0,0,0,0,0,0,0,110,255,106,0,0,0,0,0,0,0,161,255,55,0, + 0,0,0,0,0,0,211,250,7,0,0,0,0,0,0,2,252,214,0,0,0,0,0,0, + 0,1,245,237,15,0,0,0,0,0,0,0,156,255,232,183,180,90,0,0,0,0,9,147, + 233,255,255,128,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0, + 0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0, + 0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0, + 0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0, + 0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0, + 0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0,0,255,248,0,0,0, + 180,180,173,128,20,0,0,0,0,0,255,255,255,255,223,16,0,0,0,0,0,0,18,175, + 255,107,0,0,0,0,0,0,0,84,255,134,0,0,0,0,0,0,0,115,255,98,0,0, + 0,0,0,0,0,169,255,50,0,0,0,0,0,0,0,223,249,5,0,0,0,0,0,0, + 19,255,206,0,0,0,0,0,0,0,55,255,163,0,0,0,0,0,0,0,32,255,193,1, + 0,0,0,0,0,0,0,133,255,217,181,90,0,0,0,0,0,71,237,255,255,128,0,0, + 0,0,18,245,222,38,1,0,0,0,0,0,57,255,159,0,0,0,0,0,0,0,33,255, + 190,0,0,0,0,0,0,0,1,238,239,1,0,0,0,0,0,0,0,183,255,34,0,0, + 0,0,0,0,0,131,255,85,0,0,0,0,0,0,0,86,255,127,0,0,0,0,0,0, + 0,127,255,123,0,0,0,0,180,180,198,254,249,40,0,0,0,0,255,255,249,204,69,0, + 0,0,0,0,76,216,253,236,184,104,20,0,0,52,200,28,27,248,228,185,219,255,255,252, + 205,187,243,210,1,78,191,12,0,0,41,128,202,245,249,191,37,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0, +}; +static const struct nvfont nvfont_small2 = { 24, 6, nvg_small2, nvb_small2 }; + +static const struct nvglyph nvg_title2[] = { + {0,9,0,0,0,593}, + {0,14,35,0,-35,906}, + {490,22,36,0,-36,1379}, + {1282,35,35,0,-35,2233}, + {2507,31,46,0,-41,1957}, + {3933,38,35,0,-35,2415}, + {5263,37,35,0,-35,2359}, + {6558,12,36,0,-36,786}, + {6990,21,41,0,-36,1349}, + {7851,21,41,0,-36,1349}, + {8712,21,36,0,-36,1321}, + {9468,26,30,0,-30,1671}, + {10248,13,15,0,-7,845}, + {10443,24,16,0,-16,1514}, + {10827,13,7,0,-7,845}, + {10918,21,40,0,-36,1364}, + {11758,33,35,0,-35,2080}, + {12913,19,35,0,-35,1189}, + {13578,27,35,0,-35,1711}, + {14523,28,35,0,-35,1779}, + {15503,30,35,0,-35,1889}, + {16553,28,35,0,-35,1785}, + {17533,30,35,0,-35,1889}, + {18583,27,35,0,-35,1723}, + {19528,29,35,0,-35,1837}, + {20543,30,35,0,-35,1902}, + {21593,13,23,0,-23,845}, + {21892,13,31,0,-23,845}, + {22295,26,28,0,-28,1671}, + {23023,26,25,0,-25,1671}, + {23673,26,28,0,-28,1671}, + {24401,28,36,0,-36,1782}, + {25409,42,35,0,-35,2663}, + {26879,35,35,0,-35,2252}, + {28104,31,35,0,-35,1981}, + {29189,36,35,0,-35,2295}, + {30449,36,35,0,-35,2319}, + {31709,30,35,0,-35,1935}, + {32759,29,35,0,-35,1825}, + {33774,37,35,0,-35,2347}, + {35069,35,35,0,-35,2227}, + {36294,12,35,0,-35,780}, + {36714,21,35,0,-35,1312}, + {37449,34,35,0,-35,2187}, + {38639,28,35,0,-35,1809}, + {39619,43,35,0,-35,2759}, + {41124,35,35,0,-35,2227}, + {42349,38,35,0,-35,2439}, + {43679,32,35,0,-35,2049}, + {44799,39,35,0,-35,2476}, + {46164,32,35,0,-35,2040}, + {47284,31,35,0,-35,1957}, + {48369,32,35,0,-35,2040}, + {49489,34,35,0,-35,2163}, + {50679,35,35,0,-35,2252}, + {51904,52,35,0,-35,3339}, + {53724,34,35,0,-35,2172}, + {54914,34,35,0,-35,2150}, + {56104,30,35,0,-35,1938}, + {57154,21,41,0,-36,1349}, + {58015,21,40,0,-36,1364}, + {58855,21,41,0,-36,1349}, + {59716,26,36,0,-36,1671}, + {60652,22,7,0,0,1432}, + {60806,17,36,0,-36,1087}, + {61418,27,25,0,-25,1714}, + {62093,30,35,0,-35,1908}, + {63143,27,25,0,-25,1696}, + {63818,30,35,0,-35,1908}, + {64868,27,25,0,-25,1754}, + {65543,18,35,0,-35,1137}, + {66173,30,35,0,-25,1908}, + {67223,29,35,0,-35,1880}, + {68238,12,35,0,-35,799}, + {68658,14,45,-2,-35,799}, + {69288,27,35,0,-35,1714}, + {70233,12,35,0,-35,799}, + {70653,45,25,0,-25,2891}, + {71778,29,25,0,-25,1880}, + {72503,29,25,0,-25,1834}, + {73228,30,35,0,-25,1908}, + {74278,30,35,0,-25,1908}, + {75328,19,25,0,-25,1198}, + {75803,24,25,0,-25,1518}, + {76403,19,33,0,-33,1247}, + {77030,29,25,0,-25,1855}, + {77755,26,25,0,-25,1647}, + {78405,38,25,0,-25,2458}, + {79355,28,25,0,-25,1803}, + {80055,26,35,0,-25,1647}, + {80965,25,25,0,-25,1576}, + {81590,21,41,0,-36,1349}, + {82451,11,44,0,-38,719}, + {82935,21,41,0,-36,1349}, + {83796,27,21,0,-21,1671}, +}; +static const unsigned char nvb_title2[] = { + 0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0, + 0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,0,255,255,255,255,255,255,255,0, + 0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,0,255,255,255,255, + 255,255,255,0,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,0, + 255,255,255,255,255,255,255,0,0,0,0,0,0,0,99,221,253,221,99,0,0,0,0,99, + 221,253,221,99,0,0,0,0,0,0,0,85,255,255,255,255,255,85,0,0,85,255,255,255, + 255,255,85,0,0,0,0,0,0,204,255,255,255,255,255,204,0,0,204,255,255,255,255,255, + 204,0,0,0,0,0,0,251,255,255,255,255,255,251,0,0,251,255,255,255,255,255,251,0, + 0,0,0,0,0,245,255,255,255,255,255,245,0,0,245,255,255,255,255,255,245,0,0,0, + 0,0,0,214,255,255,255,255,255,215,0,0,214,255,255,255,255,255,215,0,0,0,0,0, + 0,165,255,255,255,255,255,168,0,0,165,255,255,255,255,255,168,0,0,0,0,0,0,108, + 255,255,255,255,255,108,0,0,108,255,255,255,255,255,108,0,0,0,0,0,0,44,255,255, + 255,255,255,44,0,0,44,255,255,255,255,255,44,0,0,0,0,0,0,1,232,255,255,255, + 232,1,0,0,1,232,255,255,255,232,1,0,0,0,0,0,0,0,169,255,255,255,169,0, + 0,0,0,169,255,255,255,169,0,0,0,0,0,0,0,0,102,255,255,255,101,0,0,0, + 0,102,255,255,255,101,0,0,0,0,0,0,0,0,36,255,255,255,36,0,0,0,0,36, + 255,255,255,36,0,0,0,0,0,0,0,0,0,228,255,227,0,0,0,0,0,0,228,255, + 227,0,0,0,0,0,0,0,0,0,0,160,255,160,0,0,0,0,0,0,160,255,160,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,190,255,255, + 255,255,135,0,0,0,0,0,0,210,255,255,255,255,111,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,224,255,255,255,255,100,0,0,0,0,0,1,244,255,255,255, + 255,75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,254,255,255,255,255, + 64,0,0,0,0,0,24,255,255,255,255,255,40,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,39,255,255,255,255,255,29,0,0,0,0,0,59,255,255,255,255,254,7, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,73,255,255,255,255,249,2,0, + 0,0,0,0,94,255,255,255,255,227,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,108,255,255,255,255,216,0,0,0,0,0,0,129,255,255,255,255,192,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,144,255,255,255,255,181,0,0,0,0, + 0,0,164,255,255,255,255,156,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,179,255,255,255,255,145,0,0,0,0,0,0,200,255,255,255,255,121,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,213,255,255,255,255,111,0,0,0,0,0,0, + 234,255,255,255,255,87,0,0,0,0,0,0,0,0,0,0,220,220,220,220,220,220,220,255, + 255,255,255,255,235,220,220,220,220,220,220,255,255,255,255,255,229,220,220,220,220,220,0,0, + 0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0, + 0,0,0,0,0,168,255,255,255,255,156,0,0,0,0,0,0,189,255,255,255,255,132,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,202,255,255,255,255,121,0,0, + 0,0,0,0,224,255,255,255,255,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,237,255,255,255,255,87,0,0,0,0,0,6,253,255,255,255,255,63,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,16,255,255,255,255,255,52,0,0,0,0, + 0,38,255,255,255,255,255,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 51,255,255,255,255,255,17,0,0,0,0,0,73,255,255,255,255,247,2,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,86,255,255,255,255,238,0,0,0,0,0,0,108, + 255,255,255,255,214,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,121,255, + 255,255,255,203,0,0,0,0,0,0,144,255,255,255,255,179,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,220, + 220,220,220,220,229,255,255,255,255,255,224,220,220,220,220,220,232,255,255,255,255,255,220,220, + 220,220,220,220,220,0,0,0,0,0,0,0,0,0,0,75,255,255,255,255,247,2,0,0, + 0,0,0,98,255,255,255,255,225,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,110,255,255,255,255,214,0,0,0,0,0,0,133,255,255,255,255,190,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,144,255,255,255,255,180,0,0,0,0,0, + 0,168,255,255,255,255,156,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 180,255,255,255,255,144,0,0,0,0,0,0,203,255,255,255,255,120,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,215,255,255,255,255,109,0,0,0,0,0,0,238, + 255,255,255,255,85,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,247,255, + 255,255,255,74,0,0,0,0,0,17,255,255,255,255,255,50,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,28,255,255,255,255,255,40,0,0,0,0,0,52,255,255,255, + 255,255,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,255,255,255,255, + 254,6,0,0,0,0,0,88,255,255,255,255,236,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,99,255,255,255,255,225,0,0,0,0,0,0,123,255,255,255,255,201, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 128,128,128,128,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,94,156,202,255,255,255,255,255,201, + 154,91,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,162,249,255,255, + 255,255,255,255,255,255,255,255,255,248,156,29,0,0,0,0,0,0,0,0,0,0,0,0, + 1,118,249,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,245,101,0,0,0,0, + 0,0,0,0,0,0,0,150,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,136,0,0,0,0,0,0,0,0,0,109,255,255,255,255,255,255,255,255,250,227, + 222,238,255,255,255,255,255,255,255,255,255,111,0,0,0,0,0,0,0,23,244,255,255,255, + 255,255,253,154,54,3,0,0,0,23,93,205,255,255,255,255,255,255,250,40,0,0,0,0, + 0,0,123,255,255,255,255,255,250,70,0,0,0,0,0,0,0,0,2,131,255,255,255,255, + 255,255,170,0,0,0,0,0,0,196,255,255,255,255,255,146,0,0,0,0,0,0,0,0, + 0,0,0,163,255,255,255,255,255,252,21,0,0,0,0,0,239,255,255,255,255,255,80,0, + 0,0,0,0,0,0,0,0,0,0,26,251,255,255,255,255,255,92,0,0,0,0,0,254, + 255,255,255,255,255,87,0,0,0,0,0,0,0,0,0,0,0,0,187,255,255,255,255,255, + 145,0,0,0,0,0,244,255,255,255,255,255,159,0,0,0,0,0,0,0,0,0,0,0, + 0,48,84,84,84,84,84,56,0,0,0,0,0,207,255,255,255,255,255,253,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,138,255,255,255, + 255,255,255,251,140,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,35,251,255,255,255,255,255,255,255,251,183,104,27,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,130,255,255,255,255,255,255,255,255,255,255,255,206, + 130,52,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,167,255,255,255,255, + 255,255,255,255,255,255,255,255,255,231,154,72,4,0,0,0,0,0,0,0,0,0,0,0, + 0,0,2,127,251,255,255,255,255,255,255,255,255,255,255,255,255,255,255,229,123,14,0,0, + 0,0,0,0,0,0,0,0,0,0,0,44,177,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,234,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,144,229,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,144,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,2,57,131,206,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,102,176,244,255,255,255,255, + 255,255,255,255,253,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,7,83,197,255,255,255,255,255,255,255,167,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,105,254,255,255,255,255,255,245,5,0,0,63, + 96,96,96,96,96,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,255,255,255, + 255,255,255,43,0,0,139,255,255,255,255,255,170,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,42,255,255,255,255,255,255,67,0,0,84,255,255,255,255,255,242,11,0,0,0, + 0,0,0,0,0,0,0,0,0,0,4,255,255,255,255,255,255,66,0,0,16,249,255,255, + 255,255,255,127,0,0,0,0,0,0,0,0,0,0,0,0,0,19,255,255,255,255,255,255, + 46,0,0,0,166,255,255,255,255,255,251,70,0,0,0,0,0,0,0,0,0,0,0,0, + 101,255,255,255,255,255,249,9,0,0,0,45,252,255,255,255,255,255,249,111,3,0,0,0, + 0,0,0,0,0,0,50,238,255,255,255,255,255,184,0,0,0,0,0,138,255,255,255,255, + 255,255,255,218,115,45,4,0,0,0,5,55,146,249,255,255,255,255,255,255,76,0,0,0, + 0,0,5,186,255,255,255,255,255,255,255,255,255,250,230,222,230,252,255,255,255,255,255,255, + 255,255,179,0,0,0,0,0,0,0,10,178,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,207,16,0,0,0,0,0,0,0,0,2,116,246,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,170,13,0,0,0,0,0,0,0,0, + 0,0,0,26,141,237,255,255,255,255,255,255,255,255,255,255,255,255,255,192,70,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,5,67,131,179,255,255,255,255,255,231,203,163, + 108,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,70,164,222,250,249,222,166,76,1,0,0,0,0,0,0,0,0,0,0,0,0, + 0,19,229,255,255,255,206,6,0,0,0,0,0,0,0,27,191,255,255,255,255,255,255,255, + 255,198,31,0,0,0,0,0,0,0,0,0,0,0,1,177,255,255,255,245,40,0,0,0, + 0,0,0,0,26,225,255,255,255,255,255,255,255,255,255,255,229,31,0,0,0,0,0,0, + 0,0,0,0,108,255,255,255,255,103,0,0,0,0,0,0,0,1,188,255,255,255,255,255, + 255,255,255,255,255,255,255,195,1,0,0,0,0,0,0,0,0,46,248,255,255,255,177,0, + 0,0,0,0,0,0,0,71,255,255,255,255,194,62,7,11,72,206,255,255,255,255,74,0, + 0,0,0,0,0,0,9,212,255,255,255,230,19,0,0,0,0,0,0,0,0,162,255,255, + 255,199,6,0,0,0,0,10,209,255,255,255,163,0,0,0,0,0,0,0,153,255,255,255, + 255,69,0,0,0,0,0,0,0,0,0,217,255,255,255,74,0,0,0,0,0,0,80,255, + 255,255,217,0,0,0,0,0,0,82,255,255,255,255,142,0,0,0,0,0,0,0,0,0, + 0,246,255,255,255,17,0,0,0,0,0,0,18,255,255,255,246,0,0,0,0,0,28,237, + 255,255,255,208,7,0,0,0,0,0,0,0,0,0,0,255,255,255,255,2,0,0,0,0, + 0,0,2,255,255,255,254,0,0,0,0,3,192,255,255,255,247,42,0,0,0,0,0,0, + 0,0,0,0,0,245,255,255,255,21,0,0,0,0,0,0,21,255,255,255,245,0,0,0, + 0,126,255,255,255,255,107,0,0,0,0,0,0,0,0,0,0,0,0,214,255,255,255,80, + 0,0,0,0,0,0,86,255,255,255,213,0,0,0,59,252,255,255,255,179,1,0,0,0, + 0,0,0,0,0,0,0,0,0,159,255,255,255,206,8,0,0,0,0,14,214,255,255,255, + 156,0,0,15,224,255,255,255,233,22,0,0,0,0,0,0,0,0,0,0,0,0,0,68, + 255,255,255,255,198,63,7,10,75,212,255,255,255,255,64,0,0,170,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,186,255,255,255,255,255,255,255,255,255, + 255,255,255,179,0,0,99,255,255,255,255,145,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,26,224,255,255,255,255,255,255,255,255,255,255,218,21,0,38,245,255,255,255, + 210,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,191,255,255,255, + 255,255,255,255,255,183,21,0,7,206,255,255,255,248,44,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,1,70,164,222,250,250,220,160,66,0,0,0,142,255, + 255,255,255,108,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,74,255,255,255,255,181,1,0,5,96,186,238,252, + 234,185,96,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 23,234,255,255,255,233,23,0,35,210,255,255,255,255,255,255,255,212,41,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,1,186,255,255,255,255,74,0,27,230,255, + 255,255,255,255,255,255,255,255,234,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,117,255,255,255,255,147,0,0,183,255,255,255,255,255,255,255,255,255,255,255,189, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,51,250,255,255,255,211,8,0, + 58,255,255,255,255,179,40,4,47,189,255,255,255,255,62,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,12,218,255,255,255,248,45,0,0,147,255,255,255,203,5,0,0,0,7, + 210,255,255,255,149,0,0,0,0,0,0,0,0,0,0,0,0,0,0,160,255,255,255,255, + 111,0,0,0,205,255,255,255,87,0,0,0,0,0,92,255,255,255,205,0,0,0,0,0, + 0,0,0,0,0,0,0,0,90,255,255,255,255,183,1,0,0,0,238,255,255,255,27,0, + 0,0,0,0,29,255,255,255,237,0,0,0,0,0,0,0,0,0,0,0,0,34,242,255, + 255,255,234,23,0,0,0,0,254,255,255,255,3,0,0,0,0,0,4,255,255,255,254,0, + 0,0,0,0,0,0,0,0,0,0,5,199,255,255,255,255,76,0,0,0,0,0,253,255, + 255,255,5,0,0,0,0,0,6,255,255,255,252,0,0,0,0,0,0,0,0,0,0,0, + 134,255,255,255,255,148,0,0,0,0,0,0,236,255,255,255,31,0,0,0,0,0,33,255, + 255,255,235,0,0,0,0,0,0,0,0,0,0,66,254,255,255,255,213,9,0,0,0,0, + 0,0,201,255,255,255,92,0,0,0,0,0,99,255,255,255,201,0,0,0,0,0,0,0, + 0,0,19,228,255,255,255,249,46,0,0,0,0,0,0,0,141,255,255,255,207,6,0,0, + 0,10,217,255,255,255,141,0,0,0,0,0,0,0,0,1,177,255,255,255,255,113,0,0, + 0,0,0,0,0,0,49,255,255,255,255,181,41,4,50,195,255,255,255,255,49,0,0,0, + 0,0,0,0,0,107,255,255,255,255,184,1,0,0,0,0,0,0,0,0,0,174,255,255, + 255,255,255,255,255,255,255,255,255,173,0,0,0,0,0,0,0,0,46,248,255,255,255,235, + 25,0,0,0,0,0,0,0,0,0,0,22,225,255,255,255,255,255,255,255,255,255,224,22, + 0,0,0,0,0,0,0,9,212,255,255,255,255,79,0,0,0,0,0,0,0,0,0,0, + 0,0,32,205,255,255,255,255,255,255,255,203,30,0,0,0,0,0,0,0,0,152,255,255, + 255,255,151,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,90,184,237,252,234,179, + 86,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,105,179,224,249,253,241, + 209,156,76,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,4,124,244,255,255,255,255,255,255,255,255,255,221,83,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,193,255,255,255,255,255,255, + 255,255,255,255,255,255,255,146,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,3,191,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,133,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,108,255,255,255,255,255,255, + 255,253,226,231,255,255,255,255,255,255,254,47,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,221,255,255,255,255,255,255,134,17,0,0,26,148,255,255,255,255,255, + 154,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,255,255,255,255,255, + 255,142,0,0,0,0,0,0,141,255,255,255,255,220,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,67,255,255,255,255,255,255,30,0,0,0,0,0,0,27,255,255, + 255,255,250,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,255,255,255, + 255,255,255,4,0,0,0,0,0,0,5,255,255,255,255,250,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,34,255,255,255,255,255,255,43,0,0,0,0,0,0,56, + 255,255,255,255,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224, + 255,255,255,255,255,140,0,0,0,0,0,2,188,255,255,255,255,144,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,119,255,255,255,255,255,250,50,0,0,0,7, + 163,255,255,255,255,250,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,10,224,255,255,255,255,255,222,20,2,79,215,255,255,255,255,255,127,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,253,255,255,255,255,255,214,221, + 255,255,255,255,255,255,167,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,118,255,255,255,255,255,255,255,255,255,255,255,255,141,3,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,179,255,255,255,255,255, + 255,255,255,255,255,211,66,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,31,178,255,255,255,255,255,255,255,255,255,255,248,84,2,0,0,0,0,0, + 0,2,255,255,255,255,255,255,68,0,0,0,0,0,0,0,81,242,255,255,255,255,255,255, + 255,255,255,255,255,255,168,4,0,0,0,0,0,0,12,255,255,255,255,255,255,57,0,0, + 0,0,0,0,100,254,255,255,255,255,255,255,196,198,255,255,255,255,255,255,168,4,0,0, + 0,0,0,33,255,255,255,255,255,255,34,0,0,0,0,0,65,253,255,255,255,255,255,230, + 79,0,14,200,255,255,255,255,255,255,168,4,0,0,0,0,65,255,255,255,255,255,251,6, + 0,0,0,0,5,221,255,255,255,255,255,215,26,0,0,0,18,209,255,255,255,255,255,255, + 168,4,0,0,0,111,255,255,255,255,255,209,0,0,0,0,0,91,255,255,255,255,255,248, + 37,0,0,0,0,0,22,216,255,255,255,255,255,255,168,4,0,0,174,255,255,255,255,255, + 149,0,0,0,0,0,176,255,255,255,255,255,159,0,0,0,0,0,0,0,28,223,255,255, + 255,255,255,255,168,4,13,245,255,255,255,255,255,74,0,0,0,0,0,227,255,255,255,255, + 255,92,0,0,0,0,0,0,0,0,33,228,255,255,255,255,255,255,165,110,255,255,255,255, + 255,235,5,0,0,0,0,0,251,255,255,255,255,255,76,0,0,0,0,0,0,0,0,0, + 40,234,255,255,255,255,255,255,255,255,255,255,255,255,134,0,0,0,0,0,0,250,255,255, + 255,255,255,100,0,0,0,0,0,0,0,0,0,0,47,238,255,255,255,255,255,255,255,255, + 255,255,244,23,0,0,0,0,0,0,222,255,255,255,255,255,174,0,0,0,0,0,0,0, + 0,0,0,0,56,242,255,255,255,255,255,255,255,255,255,122,0,0,0,0,0,0,0,166, + 255,255,255,255,255,254,61,0,0,0,0,0,0,0,0,0,0,0,64,247,255,255,255,255, + 255,255,255,205,5,0,0,0,0,0,0,0,71,255,255,255,255,255,255,238,66,0,0,0, + 0,0,0,0,0,0,0,35,203,255,255,255,255,255,255,255,173,3,0,0,0,0,0,0, + 0,1,193,255,255,255,255,255,255,255,175,79,17,0,0,0,1,30,92,174,252,255,255,255, + 255,255,255,255,255,255,165,3,0,0,0,0,0,0,0,34,237,255,255,255,255,255,255,255, + 255,255,238,222,227,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,165,3,0,0, + 0,0,0,0,0,50,233,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,165,3,0,0,0,0,0,0,0,28,186,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,228,97,116,255,255,255,255,255,255,255, + 165,3,0,0,0,0,0,0,0,0,75,193,255,255,255,255,255,255,255,255,255,255,255,255, + 255,201,98,8,0,0,126,255,255,255,255,255,255,255,165,3,0,0,0,0,0,0,0,0, + 0,34,108,165,207,233,249,255,247,230,201,158,101,33,0,0,0,0,0,0,137,255,255,255, + 255,255,255,255,165,3,0,0,0,0,99,221,253,221,99,0,0,0,0,0,0,85,255,255, + 255,255,255,85,0,0,0,0,0,204,255,255,255,255,255,204,0,0,0,0,0,251,255,255, + 255,255,255,251,0,0,0,0,0,245,255,255,255,255,255,245,0,0,0,0,0,214,255,255, + 255,255,255,215,0,0,0,0,0,165,255,255,255,255,255,168,0,0,0,0,0,108,255,255, + 255,255,255,108,0,0,0,0,0,44,255,255,255,255,255,44,0,0,0,0,0,1,232,255, + 255,255,232,1,0,0,0,0,0,0,169,255,255,255,169,0,0,0,0,0,0,0,102,255, + 255,255,101,0,0,0,0,0,0,0,36,255,255,255,36,0,0,0,0,0,0,0,0,228, + 255,227,0,0,0,0,0,0,0,0,0,160,255,160,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131,255,255,255,255,255, + 120,0,0,0,0,0,0,0,0,0,0,0,0,1,147,255,255,255,255,255,117,0,0,0, + 0,0,0,0,0,0,0,0,0,0,138,255,255,255,255,255,132,0,0,0,0,0,0,0, + 0,0,0,0,0,0,110,255,255,255,255,255,165,1,0,0,0,0,0,0,0,0,0,0, + 0,0,68,253,255,255,255,255,207,9,0,0,0,0,0,0,0,0,0,0,0,0,23,233, + 255,255,255,255,245,39,0,0,0,0,0,0,0,0,0,0,0,0,0,176,255,255,255,255, + 255,112,0,0,0,0,0,0,0,0,0,0,0,0,0,81,255,255,255,255,255,208,3,0, + 0,0,0,0,0,0,0,0,0,0,0,5,220,255,255,255,255,255,68,0,0,0,0,0, + 0,0,0,0,0,0,0,0,99,255,255,255,255,255,193,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,213,255,255,255,255,255,78,0,0,0,0,0,0,0,0,0,0,0,0, + 0,64,255,255,255,255,255,229,3,0,0,0,0,0,0,0,0,0,0,0,0,0,159,255, + 255,255,255,255,142,0,0,0,0,0,0,0,0,0,0,0,0,0,4,238,255,255,255,255, + 255,63,0,0,0,0,0,0,0,0,0,0,0,0,0,57,255,255,255,255,255,246,6,0, + 0,0,0,0,0,0,0,0,0,0,0,0,117,255,255,255,255,255,197,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,168,255,255,255,255,255,151,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,206,255,255,255,255,255,116,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,232,255,255,255,255,255,93,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 248,255,255,255,255,255,78,0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,255,255, + 255,255,255,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,255,255,255,255,255, + 78,0,0,0,0,0,0,0,0,0,0,0,0,0,0,232,255,255,255,255,255,93,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,207,255,255,255,255,255,118,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,169,255,255,255,255,255,153,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,121,255,255,255,255,255,200,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,61,255,255,255,255,255,248,8,0,0,0,0,0,0,0,0,0,0,0,0,0, + 6,243,255,255,255,255,255,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,168,255, + 255,255,255,255,148,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,255,255,255,255, + 255,235,6,0,0,0,0,0,0,0,0,0,0,0,0,0,3,223,255,255,255,255,255,91, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,111,255,255,255,255,255,208,1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,9,229,255,255,255,255,255,84,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,93,255,255,255,255,255,222,7,0,0,0,0,0,0,0, + 0,0,0,0,0,0,1,188,255,255,255,255,255,132,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,30,238,255,255,255,255,251,52,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,77,254,255,255,255,255,221,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 120,255,255,255,255,255,180,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,146,255, + 255,255,255,255,145,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,150,255,255,255, + 255,255,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,133,255,255,255,255,255, + 122,0,0,0,0,63,244,255,255,255,255,190,15,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,63,247,255,255,255,255,206,18,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,77,253,255,255,255,255,205,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,111, + 255,255,255,255,255,189,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,165,255,255, + 255,255,255,149,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,221,255,255,255,255, + 255,86,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,237,24, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,159,255,255,255,255,255,166,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,25,246,255,255,255,255,255,55,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,137,255,255,255,255,255,183,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,26,250,255,255,255,255,255,43,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,171,255,255,255,255,255,148,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,77,255,255,255,255,255,235,4,0,0,0,0,0,0,0,0,0,0,0,0,0,7,244, + 255,255,255,255,255,65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,185,255,255,255, + 255,255,133,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,193, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,81,255,255,255,255,255,241,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,46,255,255,255,255,255,255,23,0,0,0,0,0, + 0,0,0,0,0,0,0,0,21,255,255,255,255,255,255,48,0,0,0,0,0,0,0,0, + 0,0,0,0,0,7,255,255,255,255,255,255,64,0,0,0,0,0,0,0,0,0,0,0, + 0,0,2,255,255,255,255,255,255,70,0,0,0,0,0,0,0,0,0,0,0,0,0,7, + 255,255,255,255,255,255,64,0,0,0,0,0,0,0,0,0,0,0,0,0,21,255,255,255, + 255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0,45,255,255,255,255,255,255, + 20,0,0,0,0,0,0,0,0,0,0,0,0,0,81,255,255,255,255,255,238,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,126,255,255,255,255,255,189,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,180,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0, + 0,0,0,0,6,241,255,255,255,255,255,58,0,0,0,0,0,0,0,0,0,0,0,0, + 0,71,255,255,255,255,255,229,2,0,0,0,0,0,0,0,0,0,0,0,0,0,164,255, + 255,255,255,255,136,0,0,0,0,0,0,0,0,0,0,0,0,0,19,246,255,255,255,255, + 253,33,0,0,0,0,0,0,0,0,0,0,0,0,0,125,255,255,255,255,255,169,0,0, + 0,0,0,0,0,0,0,0,0,0,0,15,238,255,255,255,255,253,43,0,0,0,0,0, + 0,0,0,0,0,0,0,0,142,255,255,255,255,255,153,0,0,0,0,0,0,0,0,0, + 0,0,0,0,48,251,255,255,255,255,232,16,0,0,0,0,0,0,0,0,0,0,0,0, + 7,208,255,255,255,255,255,74,0,0,0,0,0,0,0,0,0,0,0,0,0,148,255,255, + 255,255,255,137,0,0,0,0,0,0,0,0,0,0,0,0,0,95,255,255,255,255,255,180, + 3,0,0,0,0,0,0,0,0,0,0,0,0,67,250,255,255,255,255,200,10,0,0,0, + 0,0,0,0,0,0,0,0,0,57,244,255,255,255,255,201,15,0,0,0,0,0,0,0, + 0,0,0,0,0,61,242,255,255,255,255,190,14,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,40,255,255,255,255,216,0,0,0,0,0,0,0,0,0,0,0, + 0,21,0,0,2,240,255,255,255,158,0,0,15,6,0,0,0,0,0,0,0,1,194,150, + 2,0,188,255,255,255,102,0,25,209,114,0,0,0,0,0,0,0,86,255,255,169,6,132, + 255,255,255,45,38,223,255,239,18,0,0,0,0,0,9,224,255,255,255,187,90,255,255,242, + 56,234,255,255,255,146,0,0,0,0,0,122,255,255,255,255,255,222,255,255,235,244,255,255, + 255,255,251,39,0,0,0,0,99,190,251,255,255,255,255,255,255,255,255,255,255,255,237,161, + 51,0,0,0,0,0,0,16,90,167,241,255,255,255,255,255,222,142,62,3,0,0,0,0, + 0,0,0,0,21,99,180,249,255,255,255,255,255,233,154,72,6,0,0,0,0,0,0,99, + 194,253,255,255,255,255,255,255,255,255,255,255,255,240,166,51,0,0,0,0,123,255,255,255, + 255,255,222,255,255,235,244,255,255,255,255,251,39,0,0,0,0,9,224,255,255,255,187,90, + 255,255,242,56,234,255,255,255,146,0,0,0,0,0,0,86,255,255,169,6,132,255,255,255, + 45,38,223,255,239,18,0,0,0,0,0,0,1,194,150,2,0,188,255,255,255,102,0,25, + 209,114,0,0,0,0,0,0,0,0,21,0,0,2,240,255,255,255,158,0,0,15,6,0, + 0,0,0,0,0,0,0,0,0,0,40,255,255,255,255,216,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128, + 128,128,128,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,220,220,220,220,220,220,220,220,255,255,255,255,255,220,220, + 220,220,220,220,220,220,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,255,255,255,255, + 255,255,69,0,0,0,0,0,0,255,255,255,255,255,255,55,0,0,0,0,0,0,0,0, + 10,255,255,255,23,0,0,0,0,0,0,0,0,46,255,255,227,0,0,0,0,0,0,0, + 0,0,117,255,255,151,0,0,0,0,0,0,0,0,36,235,255,255,48,0,0,0,0,0, + 0,0,124,240,255,255,159,0,0,0,0,0,0,0,0,255,255,255,200,12,0,0,0,0, + 0,0,0,0,255,252,147,9,0,0,0,0,0,0,0,0,0,133,34,0,0,0,0,0, + 0,0,0,0,0,0,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,0, + 0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0, + 0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0, + 0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0, + 0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255, + 255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255, + 255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,255, + 255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,220,255,255,255,255,106,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,46,255,255,255,255,255,27,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,126,255,255,255,255,203,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,206,255,255,255,255,123,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,255, + 255,255,255,255,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,110,255,255,255,255, + 218,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,190,255,255,255,255,139,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,18,252,255,255,255,255,58,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,93,255,255,255,255,232,2,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,174,255,255,255,255,154,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,9,245,255,255,255,255,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78, + 255,255,255,255,243,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,158,255,255,255, + 255,170,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,235,255,255,255,255,90,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,62,255,255,255,255,250,16,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,142,255,255,255,255,186,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,222,255,255,255,255,106,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,46,255,255,255,255,255,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 126,255,255,255,255,202,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,206,255,255, + 255,255,122,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,42, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,110,255,255,255,255,218,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,190,255,255,255,255,138,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,18,252,255,255,255,255,58,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,94,255,255,255,255,232,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,174,255,255,255,255,154,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,245,255, + 255,255,255,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,255,255,255,255,243, + 7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,158,255,255,255,255,170,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,3,235,255,255,255,255,90,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,62,255,255,255,255,250,15,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,142,255,255,255,255,186,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,223,255,255,255,255,106,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,255, + 255,255,255,255,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0,126,255,255,255,255, + 201,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,207,255,255,255,255,122,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,42,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,111,255,255,255,255,217,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,193,255,255,255,255,137,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,21,253,255,255,255,255,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,31,114,177,222,246,254,246,220,177,113,30,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,175,254,255,255,255,255,255, + 255,255,255,255,253,174,40,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, + 125,251,255,255,255,255,255,255,255,255,255,255,255,255,255,250,119,1,0,0,0,0,0,0, + 0,0,0,0,0,0,0,7,171,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,169,5,0,0,0,0,0,0,0,0,0,0,0,2,175,255,255,255,255,255,255,255, + 255,236,222,238,255,255,255,255,255,255,255,255,169,2,0,0,0,0,0,0,0,0,0,0, + 128,255,255,255,255,255,255,239,120,29,0,0,0,31,120,238,255,255,255,255,255,255,122,0, + 0,0,0,0,0,0,0,0,51,252,255,255,255,255,255,208,27,0,0,0,0,0,0,0, + 27,206,255,255,255,255,255,251,47,0,0,0,0,0,0,0,0,195,255,255,255,255,255,226, + 22,0,0,0,0,0,0,0,0,0,19,221,255,255,255,255,255,191,0,0,0,0,0,0, + 0,65,255,255,255,255,255,255,68,0,0,0,0,0,0,0,0,0,0,0,64,255,255,255, + 255,255,255,61,0,0,0,0,0,0,172,255,255,255,255,255,188,0,0,0,0,0,0,0, + 0,0,0,0,0,0,184,255,255,255,255,255,168,0,0,0,0,0,12,249,255,255,255,255, + 255,84,0,0,0,0,0,0,0,0,0,0,0,0,0,81,255,255,255,255,255,247,11,0, + 0,0,0,78,255,255,255,255,255,250,11,0,0,0,0,0,0,0,0,0,0,0,0,0, + 10,248,255,255,255,255,255,76,0,0,0,0,137,255,255,255,255,255,195,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,195,255,255,255,255,255,136,0,0,0,0,182,255,255, + 255,255,255,150,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,147,255,255,255,255, + 255,181,0,0,0,0,216,255,255,255,255,255,114,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,114,255,255,255,255,255,216,0,0,0,0,238,255,255,255,255,255,89,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,89,255,255,255,255,255,238,0,0,0,0, + 251,255,255,255,255,255,78,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,255, + 255,255,255,255,251,0,0,0,0,255,255,255,255,255,255,74,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,74,255,255,255,255,255,255,0,0,0,0,249,255,255,255,255,255, + 82,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,82,255,255,255,255,255,249,0, + 0,0,0,233,255,255,255,255,255,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,98,255,255,255,255,255,234,0,0,0,0,208,255,255,255,255,255,122,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,125,255,255,255,255,255,208,0,0,0,0,170,255,255, + 255,255,255,161,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,166,255,255,255,255, + 255,171,0,0,0,0,122,255,255,255,255,255,209,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,218,255,255,255,255,255,123,0,0,0,0,59,255,255,255,255,255,254,21,0, + 0,0,0,0,0,0,0,0,0,0,0,0,30,255,255,255,255,255,255,62,0,0,0,0, + 3,234,255,255,255,255,255,100,0,0,0,0,0,0,0,0,0,0,0,0,0,114,255,255, + 255,255,255,237,4,0,0,0,0,0,147,255,255,255,255,255,205,0,0,0,0,0,0,0, + 0,0,0,0,0,3,218,255,255,255,255,255,150,0,0,0,0,0,0,39,254,255,255,255, + 255,255,86,0,0,0,0,0,0,0,0,0,0,0,105,255,255,255,255,255,254,43,0,0, + 0,0,0,0,0,165,255,255,255,255,255,235,32,0,0,0,0,0,0,0,0,0,39,242, + 255,255,255,255,255,169,0,0,0,0,0,0,0,0,29,242,255,255,255,255,255,220,37,0, + 0,0,0,0,0,0,40,226,255,255,255,255,255,244,31,0,0,0,0,0,0,0,0,0, + 96,255,255,255,255,255,255,245,129,33,0,0,0,34,131,245,255,255,255,255,255,255,100,0, + 0,0,0,0,0,0,0,0,0,0,143,255,255,255,255,255,255,255,255,238,222,238,255,255, + 255,255,255,255,255,255,148,0,0,0,0,0,0,0,0,0,0,0,0,1,146,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,146,2,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,102,245,255,255,255,255,255,255,255,255,255,255,255,255,255,246,105,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,33,165,252,255,255,255,255,255, + 255,255,255,255,252,165,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,27,111,177,221,246,254,246,219,175,110,26,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,62,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,19,220,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,21,203, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,54,223,255,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,15,138,252,255,255,255,255,255,255,255,255,72,0,0,0, + 0,0,32,130,237,255,255,255,255,255,255,255,255,255,255,72,0,0,0,87,213,255,255,255, + 255,255,255,255,255,255,255,255,255,255,72,0,0,0,128,255,255,255,255,255,255,255,246,255, + 255,255,255,255,255,72,0,0,0,128,255,255,255,255,255,255,249,77,255,255,255,255,255,255, + 72,0,0,0,128,255,255,255,255,255,230,62,0,255,255,255,255,255,255,72,0,0,0,128, + 255,255,255,253,157,20,0,0,255,255,255,255,255,255,72,0,0,0,128,255,237,149,39,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,49,55,3,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,12,91,159,206,236,251,252,239,211,164,95,14,0,0,0,0,0,0,0,0,0,0,0, + 0,0,13,135,243,255,255,255,255,255,255,255,255,255,255,244,132,10,0,0,0,0,0,0, + 0,0,0,0,56,225,255,255,255,255,255,255,255,255,255,255,255,255,255,255,213,34,0,0, + 0,0,0,0,0,0,68,246,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 226,25,0,0,0,0,0,0,38,243,255,255,255,255,255,255,255,251,225,227,253,255,255,255, + 255,255,255,255,186,0,0,0,0,0,1,196,255,255,255,255,255,255,212,80,8,0,0,14, + 102,238,255,255,255,255,255,255,72,0,0,0,0,73,255,255,255,255,255,255,182,8,0,0, + 0,0,0,0,42,242,255,255,255,255,255,174,0,0,0,0,175,255,255,255,255,255,231,14, + 0,0,0,0,0,0,0,0,125,255,255,255,255,255,243,3,0,0,5,246,255,255,255,255, + 255,118,0,0,0,0,0,0,0,0,0,40,255,255,255,255,255,255,36,0,0,48,255,255, + 255,255,255,255,37,0,0,0,0,0,0,0,0,0,6,255,255,255,255,255,255,62,0,0, + 34,116,116,116,116,116,115,1,0,0,0,0,0,0,0,0,0,9,255,255,255,255,255,255, + 69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,40,255,255,255, + 255,255,255,59,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103, + 255,255,255,255,255,255,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,193,255,255,255,255,255,237,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,59,255,255,255,255,255,255,170,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4,205,255,255,255,255,255,255,83,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,133,255,255,255,255,255,255,222,4,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,79,254,255,255,255,255,255,255,89,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,55,246,255,255,255,255,255,255,187,1,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,238,255,255,255,255,255,255,237, + 28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,235,255,255,255,255,255, + 255,253,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,51,236,255,255,255, + 255,255,255,255,108,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,241,255, + 255,255,255,255,255,255,133,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,81, + 248,255,255,255,255,255,255,255,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,108,253,255,255,255,255,255,255,255,140,1,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,2,137,255,255,255,255,255,255,255,255,129,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,9,170,255,255,255,255,255,255,255,253,106,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,22,200,255,255,255,255,255,255,255,246,79,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,43,224,255,255,255,255,255,255,255,230,53,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,74,243,255,255,255,255,255,255,255,205,28,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,253,255,255,255,255,255,255,255,255,229,220, + 220,220,220,220,220,220,220,220,220,220,220,220,220,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,0,0,0,0,0,0,0,0,0,0,15,93,160,207,237,252,254,241,213,169,105,24,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,20,144,245,255,255,255,255,255,255,255,255, + 255,255,251,162,29,0,0,0,0,0,0,0,0,0,0,0,72,235,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,242,78,0,0,0,0,0,0,0,0,0,89,252,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,253,81,0,0,0,0,0,0,0,55,250, + 255,255,255,255,255,255,255,252,227,225,249,255,255,255,255,255,255,255,242,29,0,0,0,0, + 0,4,214,255,255,255,255,255,255,210,86,10,0,0,6,72,210,255,255,255,255,255,255,151, + 0,0,0,0,0,93,255,255,255,255,255,255,168,6,0,0,0,0,0,0,14,215,255,255, + 255,255,255,239,3,0,0,0,0,190,255,255,255,255,255,217,7,0,0,0,0,0,0,0, + 0,85,255,255,255,255,255,255,41,0,0,0,11,251,255,255,255,255,255,102,0,0,0,0, + 0,0,0,0,0,18,255,255,255,255,255,255,67,0,0,0,56,255,255,255,255,255,255,31, + 0,0,0,0,0,0,0,0,0,4,255,255,255,255,255,255,67,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,34,255,255,255,255,255,255,41,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,126,255,255,255,255,255,238, + 3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,62,248,255,255, + 255,255,255,148,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,61,147, + 250,255,255,255,255,255,237,24,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,255,255,255,255,255,255,251,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,233,69,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,246,80,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,172,16,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,220,220,220,221,231,254,255,255,255,255,255, + 255,255,206,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,74, + 191,255,255,255,255,255,255,172,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,138,255,255,255,255,255,255,67,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,2,199,255,255,255,255,255,176,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,87,255,255,255,255,255,246,6,0,0, + 48,72,72,72,72,72,41,0,0,0,0,0,0,0,0,0,0,0,26,255,255,255,255,255, + 255,44,0,0,154,255,255,255,255,255,175,0,0,0,0,0,0,0,0,0,0,0,3,255, + 255,255,255,255,255,67,0,0,113,255,255,255,255,255,236,4,0,0,0,0,0,0,0,0, + 0,0,13,255,255,255,255,255,255,68,0,0,51,255,255,255,255,255,255,80,0,0,0,0, + 0,0,0,0,0,0,62,255,255,255,255,255,255,46,0,0,1,222,255,255,255,255,255,211, + 8,0,0,0,0,0,0,0,0,0,165,255,255,255,255,255,246,6,0,0,0,113,255,255, + 255,255,255,255,172,7,0,0,0,0,0,0,0,101,255,255,255,255,255,255,172,0,0,0, + 0,7,218,255,255,255,255,255,255,213,89,14,0,0,2,51,164,255,255,255,255,255,255,255, + 55,0,0,0,0,0,52,248,255,255,255,255,255,255,255,254,230,222,244,255,255,255,255,255, + 255,255,255,148,0,0,0,0,0,0,0,77,249,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,185,6,0,0,0,0,0,0,0,0,60,226,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,152,6,0,0,0,0,0,0,0,0,0,0,13,128,237, + 255,255,255,255,255,255,255,255,255,255,255,193,64,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,8,82,152,201,234,252,254,245,220,178,120,41,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,117,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,250,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,12,219,255,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,162,255,255,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,90,255,255,255,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,35,242,255,255,255,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5, + 201,255,255,255,255,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,137,255,255,255,255,255,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,66,254,255,255,255,255,255,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,230,255,255,255,255,255,214,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,180,255,255, + 255,255,255,248,46,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,109,255,255,255,255,255,255,108,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,46,248,255,255,255,255,255,177,1,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,11,214,255,255,255,255,255,228,19,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,154,255,255,255,255,255, + 253,65,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,84, + 255,255,255,255,255,255,131,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,30,239,255,255,255,255,255,197,4,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,3,197,255,255,255,255,255,239,32,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,128,255,255,255,255,255,255,85,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,60,253,255,255, + 255,255,255,154,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,18,226,255,255,255,255,255,214,11,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,172,255,255,255,255,255,248,46,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,102,255,255,255,255,255,255,108,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,246,255,255,255,255,255, + 255,221,220,220,220,220,220,220,220,220,220,255,255,255,255,255,255,231,220,220,220,0,0,0, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,76,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,76,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,76,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,204,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,84,0,0,0,0,0,0, + 0,0,226,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,84,0,0, + 0,0,0,0,0,0,249,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,84,0,0,0,0,0,0,0,16,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,84,0,0,0,0,0,0,0,39,255,255,255,255,255,250,220,220,220,220, + 220,220,220,220,220,220,220,220,220,72,0,0,0,0,0,0,0,61,255,255,255,255,255,192, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84,255,255, + 255,255,255,171,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,107,255,255,255,255,255,149,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,130,255,255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,152,255,255,255,255,255,107,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,175,255,255,255,255,255,85,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,198,255,255,255,255,255,64, + 24,115,177,209,218,208,176,120,38,0,0,0,0,0,0,0,0,0,0,0,0,220,255,255, + 255,255,255,157,247,255,255,255,255,255,255,255,255,180,36,0,0,0,0,0,0,0,0,0, + 0,244,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,245,85,0,0,0,0, + 0,0,0,0,10,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254, + 89,0,0,0,0,0,0,0,32,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,247,44,0,0,0,0,0,0,56,255,255,255,255,255,255,254,162,64,15,4, + 32,106,229,255,255,255,255,255,255,195,0,0,0,0,0,0,78,255,255,255,255,255,248,72, + 0,0,0,0,0,0,19,204,255,255,255,255,255,255,65,0,0,0,0,0,81,208,208,208, + 208,208,100,0,0,0,0,0,0,0,0,30,244,255,255,255,255,255,160,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,148,255,255,255,255,255,231,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69,255,255,255, + 255,255,255,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 26,255,255,255,255,255,255,53,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,4,255,255,255,255,255,255,68,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,69,0,0,19,84,84,84,84,84, + 84,11,0,0,0,0,0,0,0,0,0,0,20,255,255,255,255,255,255,55,0,0,22,255, + 255,255,255,255,255,71,0,0,0,0,0,0,0,0,0,0,60,255,255,255,255,255,255,23, + 0,0,0,216,255,255,255,255,255,159,0,0,0,0,0,0,0,0,0,0,129,255,255,255, + 255,255,231,0,0,0,0,132,255,255,255,255,255,251,45,0,0,0,0,0,0,0,0,14, + 231,255,255,255,255,255,158,0,0,0,0,31,251,255,255,255,255,255,223,36,0,0,0,0, + 0,0,7,178,255,255,255,255,255,255,62,0,0,0,0,0,145,255,255,255,255,255,255,242, + 121,24,0,0,5,74,206,255,255,255,255,255,255,188,0,0,0,0,0,0,11,215,255,255, + 255,255,255,255,255,255,233,223,248,255,255,255,255,255,255,255,243,37,0,0,0,0,0,0, + 0,34,227,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,250,74,0,0,0, + 0,0,0,0,0,0,29,201,255,255,255,255,255,255,255,255,255,255,255,255,255,255,234,67, + 0,0,0,0,0,0,0,0,0,0,0,4,108,231,255,255,255,255,255,255,255,255,255,255, + 249,151,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,77,150,201,234,250,254, + 242,215,169,104,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5, + 79,153,205,238,252,251,235,201,147,69,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,4,110,230,255,255,255,255,255,255,255,255,255,255,217,83,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,33,204,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 159,6,0,0,0,0,0,0,0,0,0,0,0,45,233,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,177,3,0,0,0,0,0,0,0,0,0,27,230,255,255,255,255, + 255,255,255,251,226,227,253,255,255,255,255,255,255,255,129,0,0,0,0,0,0,0,0,1, + 191,255,255,255,255,255,255,191,72,7,0,0,11,92,224,255,255,255,255,255,250,38,0,0, + 0,0,0,0,0,97,255,255,255,255,255,255,127,1,0,0,0,0,0,0,18,208,255,255, + 255,255,255,155,0,0,0,0,0,0,5,226,255,255,255,255,255,153,0,0,0,0,0,0, + 0,0,0,43,254,255,255,255,255,242,8,0,0,0,0,0,89,255,255,255,255,255,236,14, + 0,0,0,0,0,0,0,0,0,0,191,255,255,255,255,255,65,0,0,0,0,0,185,255, + 255,255,255,255,131,0,0,0,0,0,0,0,0,0,0,0,98,180,180,180,180,180,78,0, + 0,0,0,15,250,255,255,255,255,255,42,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,75,255,255,255,255,255,230,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130,255,255,255,255,255,178,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,172,255,255, + 255,255,255,138,0,0,0,40,130,193,233,251,252,233,195,134,47,0,0,0,0,0,0,0, + 0,0,0,206,255,255,255,255,255,109,0,30,174,255,255,255,255,255,255,255,255,255,255,186, + 42,0,0,0,0,0,0,0,0,229,255,255,255,255,255,90,64,238,255,255,255,255,255,255, + 255,255,255,255,255,255,248,93,0,0,0,0,0,0,0,246,255,255,255,255,255,127,244,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,94,0,0,0,0,0,0,253,255,255, + 255,255,255,250,255,255,255,255,250,227,224,246,255,255,255,255,255,255,255,255,249,45,0,0, + 0,0,0,254,255,255,255,255,255,255,255,253,161,60,5,0,0,2,47,144,249,255,255,255, + 255,255,255,194,0,0,0,0,0,246,255,255,255,255,255,255,235,60,0,0,0,0,0,0, + 0,0,46,230,255,255,255,255,255,255,55,0,0,0,0,233,255,255,255,255,255,254,59,0, + 0,0,0,0,0,0,0,0,0,55,254,255,255,255,255,255,140,0,0,0,0,211,255,255, + 255,255,255,177,0,0,0,0,0,0,0,0,0,0,0,0,181,255,255,255,255,255,200,0, + 0,0,0,179,255,255,255,255,255,104,0,0,0,0,0,0,0,0,0,0,0,0,110,255, + 255,255,255,255,235,0,0,0,0,137,255,255,255,255,255,75,0,0,0,0,0,0,0,0, + 0,0,0,0,78,255,255,255,255,255,252,0,0,0,0,80,255,255,255,255,255,83,0,0, + 0,0,0,0,0,0,0,0,0,0,78,255,255,255,255,255,252,0,0,0,0,18,252,255, + 255,255,255,129,0,0,0,0,0,0,0,0,0,0,0,0,111,255,255,255,255,255,230,0, + 0,0,0,0,187,255,255,255,255,217,3,0,0,0,0,0,0,0,0,0,0,0,184,255, + 255,255,255,255,188,0,0,0,0,0,84,255,255,255,255,255,118,0,0,0,0,0,0,0, + 0,0,0,62,255,255,255,255,255,255,121,0,0,0,0,0,3,214,255,255,255,255,254,108, + 0,0,0,0,0,0,0,0,52,234,255,255,255,255,255,251,29,0,0,0,0,0,0,69, + 255,255,255,255,255,255,192,81,13,0,0,1,47,145,250,255,255,255,255,255,255,149,0,0, + 0,0,0,0,0,0,142,255,255,255,255,255,255,255,254,231,223,244,255,255,255,255,255,255, + 255,255,220,14,0,0,0,0,0,0,0,0,4,172,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,229,37,0,0,0,0,0,0,0,0,0,0,3,142,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,198,30,0,0,0,0,0,0,0,0,0,0, + 0,0,0,60,196,255,255,255,255,255,255,255,255,255,255,255,223,101,3,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,47,127,186,224,246,255,249,228,193,140,65,2,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,220,220,220,220, + 220,220,220,220,220,220,220,220,220,220,220,220,220,220,247,255,255,255,255,255,224,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,38,244,255,255,255,255,255,68, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,204,255,255,255,255, + 255,153,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130,255,255, + 255,255,255,232,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,49, + 251,255,255,255,255,255,88,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,3,205,255,255,255,255,255,187,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,110,255,255,255,255,255,252,42,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,21,241,255,255,255,255,255,148,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,151,255,255,255,255,255,242,20,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,40,252,255,255,255,255,255,129,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,169,255,255,255,255,255,238,14,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,254,255,255,255,255,255,125, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,166,255,255,255,255, + 255,243,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,252,255, + 255,255,255,255,146,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 139,255,255,255,255,255,255,38,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,8,237,255,255,255,255,255,189,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,90,255,255,255,255,255,255,91,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,185,255,255,255,255,255,244,10,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,23,252,255,255,255,255,255,167,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,102,255,255,255,255,255,255,84,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,182,255,255,255,255,255,250,13, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,246,255,255,255,255, + 255,193,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,255,255, + 255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 129,255,255,255,255,255,255,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,187,255,255,255,255,255,255,15,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,238,255,255,255,255,255,219,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,30,255,255,255,255,255,255,177,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,71,255,255,255,255,255,255,139,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,107,255,255,255,255,255,255,105,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,139,255,255,255,255,255,255, + 77,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,166,255,255,255, + 255,255,255,55,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,57,132,187,224,246,255,249,232,200,152,82,8,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,86,212,255,255,255,255,255,255,255,255,255,255,255,238,127,10,0,0,0,0, + 0,0,0,0,0,0,0,15,178,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 220,45,0,0,0,0,0,0,0,0,0,12,204,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,241,49,0,0,0,0,0,0,0,0,161,255,255,255,255,255,255,255, + 255,241,222,235,255,255,255,255,255,255,255,255,222,11,0,0,0,0,0,0,50,255,255,255, + 255,255,255,255,153,43,0,0,0,26,118,241,255,255,255,255,255,255,123,0,0,0,0,0, + 0,151,255,255,255,255,255,255,94,0,0,0,0,0,0,0,38,236,255,255,255,255,255,224, + 0,0,0,0,0,0,215,255,255,255,255,255,177,0,0,0,0,0,0,0,0,0,102,255, + 255,255,255,255,255,32,0,0,0,0,0,247,255,255,255,255,255,96,0,0,0,0,0,0, + 0,0,0,24,255,255,255,255,255,255,64,0,0,0,0,0,253,255,255,255,255,255,75,0, + 0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,67,0,0,0,0,0,232,255,255, + 255,255,255,103,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,47,0,0,0, + 0,0,183,255,255,255,255,255,194,1,0,0,0,0,0,0,0,0,121,255,255,255,255,255, + 247,7,0,0,0,0,0,99,255,255,255,255,255,255,130,1,0,0,0,0,0,0,63,247, + 255,255,255,255,255,170,0,0,0,0,0,0,8,220,255,255,255,255,255,255,192,82,24,2, + 13,59,152,252,255,255,255,255,255,252,48,0,0,0,0,0,0,0,52,243,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,111,0,0,0,0,0,0,0,0,0, + 46,211,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,239,93,0,0,0,0,0, + 0,0,0,0,0,0,22,198,255,255,255,255,255,255,255,255,255,255,255,255,255,233,58,0, + 0,0,0,0,0,0,0,0,0,0,92,240,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,253,145,7,0,0,0,0,0,0,0,0,123,255,255,255,255,255,255,255,255,238, + 223,231,254,255,255,255,255,255,255,255,188,7,0,0,0,0,0,0,80,255,255,255,255,255, + 255,230,110,26,0,0,0,13,85,204,255,255,255,255,255,255,152,0,0,0,0,0,10,230, + 255,255,255,255,255,199,18,0,0,0,0,0,0,0,3,149,255,255,255,255,255,255,58,0, + 0,0,0,100,255,255,255,255,255,242,24,0,0,0,0,0,0,0,0,0,2,194,255,255, + 255,255,255,172,0,0,0,0,180,255,255,255,255,255,149,0,0,0,0,0,0,0,0,0, + 0,0,79,255,255,255,255,255,246,6,0,0,0,229,255,255,255,255,255,91,0,0,0,0, + 0,0,0,0,0,0,0,21,255,255,255,255,255,255,45,0,0,0,252,255,255,255,255,255, + 74,0,0,0,0,0,0,0,0,0,0,0,2,255,255,255,255,255,255,68,0,0,0,251, + 255,255,255,255,255,93,0,0,0,0,0,0,0,0,0,0,0,21,255,255,255,255,255,255, + 67,0,0,0,225,255,255,255,255,255,150,0,0,0,0,0,0,0,0,0,0,0,80,255, + 255,255,255,255,255,41,0,0,0,171,255,255,255,255,255,242,24,0,0,0,0,0,0,0, + 0,0,2,193,255,255,255,255,255,240,3,0,0,0,82,255,255,255,255,255,255,199,18,0, + 0,0,0,0,0,0,2,144,255,255,255,255,255,255,157,0,0,0,0,3,210,255,255,255, + 255,255,255,230,110,26,0,0,0,12,81,199,255,255,255,255,255,255,251,40,0,0,0,0, + 0,49,247,255,255,255,255,255,255,255,255,237,222,231,254,255,255,255,255,255,255,255,255,123, + 0,0,0,0,0,0,0,76,248,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,157,1,0,0,0,0,0,0,0,0,54,220,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,250,119,1,0,0,0,0,0,0,0,0,0,0,9,116,228,255,255, + 255,255,255,255,255,255,255,255,255,251,165,39,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,4,67,138,189,225,246,255,251,236,206,161,99,22,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,4,71,144,195,230,249,255,246,224,186,128,48,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,6,112,229,255,255,255,255,255,255,255,255,255,255,255, + 200,65,0,0,0,0,0,0,0,0,0,0,0,0,0,42,211,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,147,5,0,0,0,0,0,0,0,0,0,0,56,240,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,179,5,0,0,0,0,0,0,0, + 0,29,235,255,255,255,255,255,255,255,255,244,223,230,252,255,255,255,255,255,255,255,150,0, + 0,0,0,0,0,0,0,181,255,255,255,255,255,255,242,128,37,1,0,0,8,71,182,255, + 255,255,255,255,255,74,0,0,0,0,0,0,52,255,255,255,255,255,255,221,34,0,0,0, + 0,0,0,0,0,102,254,255,255,255,255,217,4,0,0,0,0,0,145,255,255,255,255,255, + 250,41,0,0,0,0,0,0,0,0,0,0,132,255,255,255,255,255,85,0,0,0,0,0, + 206,255,255,255,255,255,163,0,0,0,0,0,0,0,0,0,0,0,12,243,255,255,255,255, + 185,0,0,0,0,0,242,255,255,255,255,255,95,0,0,0,0,0,0,0,0,0,0,0, + 0,179,255,255,255,255,251,17,0,0,0,0,255,255,255,255,255,255,74,0,0,0,0,0, + 0,0,0,0,0,0,0,149,255,255,255,255,255,77,0,0,0,0,246,255,255,255,255,255, + 94,0,0,0,0,0,0,0,0,0,0,0,0,158,255,255,255,255,255,132,0,0,0,0, + 216,255,255,255,255,255,161,0,0,0,0,0,0,0,0,0,0,0,0,215,255,255,255,255, + 255,174,0,0,0,0,161,255,255,255,255,255,249,39,0,0,0,0,0,0,0,0,0,0, + 80,255,255,255,255,255,255,206,0,0,0,0,77,255,255,255,255,255,255,218,31,0,0,0, + 0,0,0,0,0,56,239,255,255,255,255,255,255,229,0,0,0,0,2,214,255,255,255,255, + 255,255,240,124,36,1,0,0,3,54,150,251,255,255,255,255,255,255,255,245,0,0,0,0, + 0,63,253,255,255,255,255,255,255,255,255,243,223,227,250,255,255,255,255,255,255,255,255,255, + 255,252,0,0,0,0,0,0,113,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,163,255,255,255,255,255,254,0,0,0,0,0,0,0,106,250,255,255,255,255,255,255,255, + 255,255,255,255,255,250,96,78,255,255,255,255,255,248,0,0,0,0,0,0,0,0,47,191, + 255,255,255,255,255,255,255,255,255,255,192,46,0,88,255,255,255,255,255,234,0,0,0,0, + 0,0,0,0,0,0,49,134,194,232,252,252,235,199,139,52,0,0,0,103,255,255,255,255, + 255,214,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,126,255,255,255,255,255,184,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,156,255,255,255,255,255,145,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,198,255,255,255,255,255,96,0,0,0,0, + 113,228,228,228,228,228,67,0,0,0,0,0,0,0,0,0,0,0,6,246,255,255,255,255, + 255,35,0,0,0,0,82,255,255,255,255,255,120,0,0,0,0,0,0,0,0,0,0,0, + 69,255,255,255,255,255,216,0,0,0,0,0,22,254,255,255,255,255,196,0,0,0,0,0, + 0,0,0,0,0,0,169,255,255,255,255,255,127,0,0,0,0,0,0,196,255,255,255,255, + 255,60,0,0,0,0,0,0,0,0,0,57,254,255,255,255,255,250,27,0,0,0,0,0, + 0,94,255,255,255,255,255,224,31,0,0,0,0,0,0,0,37,227,255,255,255,255,255,149, + 0,0,0,0,0,0,0,5,220,255,255,255,255,255,237,114,26,0,0,0,29,121,242,255, + 255,255,255,255,233,19,0,0,0,0,0,0,0,0,67,254,255,255,255,255,255,255,255,236, + 222,238,255,255,255,255,255,255,255,253,71,0,0,0,0,0,0,0,0,0,0,121,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,253,98,0,0,0,0,0,0,0,0, + 0,0,0,0,114,252,255,255,255,255,255,255,255,255,255,255,255,255,255,239,77,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,51,194,255,255,255,255,255,255,255,255,255,255,251, + 158,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50,133,190,231,249, + 254,243,217,173,107,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255, + 255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0, + 255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0, + 0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0, + 0,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0, + 0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,0, + 0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255, + 255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,255,255,255,255,255,255,69,0, + 0,0,0,0,0,255,255,255,255,255,255,55,0,0,0,0,0,0,0,0,10,255,255,255, + 23,0,0,0,0,0,0,0,0,46,255,255,227,0,0,0,0,0,0,0,0,0,117,255, + 255,151,0,0,0,0,0,0,0,0,36,235,255,255,48,0,0,0,0,0,0,0,124,240, + 255,255,159,0,0,0,0,0,0,0,0,255,255,255,200,12,0,0,0,0,0,0,0,0, + 255,252,147,9,0,0,0,0,0,0,0,0,0,133,34,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,57,147,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,42,132,221,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,117,207,255,255, + 255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,102,193,254,255,255, + 255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,10,87,178,250,255,255,255, + 255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,4,73,163,243,255,255,255,255, + 255,255,255,255,255,255,238,157,72,0,0,0,0,0,0,1,58,148,234,255,255,255,255,255, + 255,255,255,255,255,241,162,75,6,0,0,0,0,0,0,0,133,222,255,255,255,255,255,255, + 255,255,255,255,243,166,79,7,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 255,255,255,245,169,82,9,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,247,172,86,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,205,108,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,255,255,254,195,109,26,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,212,255,255,255,255,255,255,255,255,255,255,254,195,110,27,0,0,0,0,0,0,0, + 0,0,0,0,0,44,132,220,255,255,255,255,255,255,255,255,255,255,254,196,111,27,0,0, + 0,0,0,0,0,0,0,0,0,0,52,140,227,255,255,255,255,255,255,255,255,255,255,254, + 196,111,27,0,0,0,0,0,0,0,0,0,0,0,1,60,148,233,255,255,255,255,255,255, + 255,255,255,255,254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,68,156,238,255, + 255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 6,76,164,243,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,9,84,172,247,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,13,92,180,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,220,220,220,220,220,220, + 220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,0,0,0,220,220,220,220,220,220,220,220,220,220, + 220,220,220,220,220,220,220,220,220,220,220,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,100,18,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,250,180,92,13,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,247,172,84,9, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 255,243,164,76,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,238,156,68,3,0,0,0,0,0,0,0,0,0,0,0,0,107,192, + 253,255,255,255,255,255,255,255,255,255,255,233,148,60,1,0,0,0,0,0,0,0,0,0, + 0,0,24,106,191,253,255,255,255,255,255,255,255,255,255,255,227,140,52,0,0,0,0,0, + 0,0,0,0,0,0,0,23,105,191,253,255,255,255,255,255,255,255,255,255,255,220,132,0, + 0,0,0,0,0,0,0,0,0,0,0,0,23,104,189,253,255,255,255,255,255,255,255,255, + 255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,104,189,253,255,255,255, + 255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,92,200,255, + 255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,88,175,248,255, + 255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,10,85,172,246,255,255, + 255,255,255,255,255,255,255,255,213,0,0,0,0,0,0,0,0,9,81,168,245,255,255,255, + 255,255,255,255,255,255,255,227,138,46,0,0,0,0,0,0,7,78,164,243,255,255,255,255, + 255,255,255,255,255,255,237,152,62,2,0,0,0,0,0,0,0,0,241,255,255,255,255,255, + 255,255,255,255,255,246,168,78,5,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,255,255,252,182,91,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,197,107,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,212,122,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,226,137,46,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,46,123,182,220,246,254,251,235,203,155,86,10,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,76,203,255,255,255,255,255,255,255,255,255,255,255,241,131,11,0,0,0,0, + 0,0,0,0,0,0,18,175,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,222, + 49,0,0,0,0,0,0,0,0,28,217,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,241,49,0,0,0,0,0,0,12,213,255,255,255,255,255,255,255,255,237,222, + 231,255,255,255,255,255,255,255,255,223,13,0,0,0,0,0,155,255,255,255,255,255,255,223, + 105,25,0,0,0,15,96,224,255,255,255,255,255,255,124,0,0,0,0,47,254,255,255,255, + 255,255,167,10,0,0,0,0,0,0,0,19,216,255,255,255,255,255,224,0,0,0,0,158, + 255,255,255,255,255,195,4,0,0,0,0,0,0,0,0,0,79,255,255,255,255,255,255,31, + 0,0,4,240,255,255,255,255,255,52,0,0,0,0,0,0,0,0,0,0,14,255,255,255, + 255,255,255,63,0,0,50,255,255,255,255,255,216,0,0,0,0,0,0,0,0,0,0,0, + 6,255,255,255,255,255,255,69,0,0,35,108,108,108,108,108,74,0,0,0,0,0,0,0, + 0,0,0,0,49,255,255,255,255,255,255,52,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,164,255,255,255,255,255,251,11,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,113,255,255,255,255,255,255,179,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,5,144,255,255,255,255,255,255,255,56,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,199,255,255,255,255,255,255, + 255,141,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,62,236,255,255,255, + 255,255,255,255,165,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69,247, + 255,255,255,255,255,255,255,140,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,39,243,255,255,255,255,255,255,244,87,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,193,255,255,255,255,255,255,226,45,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,61,255,255,255,255,255,255,228,30,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,151,255,255,255,255,255,254,60,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,208,255,255,255,255,255,179,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,241,255,255,255, + 255,255,104,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 194,196,196,196,196,196,58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,3,53,113,160,196,224,242,252,255,249,233,207,169,119,57,3, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 59,155,237,255,255,255,255,255,255,255,255,255,255,255,255,255,255,231,139,30,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,201,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,250,145,14,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,28,180,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,225,54,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,73,238,255,255,255,255,255,255,234,165,111,66,36,14,4,1,10,28,58,103,165,238,255, + 255,255,255,255,247,75,0,0,0,0,0,0,0,0,0,0,0,0,97,252,255,255,255,255, + 254,176,70,3,0,0,0,0,0,0,0,0,0,0,0,0,7,91,218,255,255,255,255,248, + 57,0,0,0,0,0,0,0,0,0,0,92,254,255,255,255,255,191,48,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,6,138,255,255,255,255,226,14,0,0,0,0, + 0,0,0,0,56,249,255,255,255,252,113,1,0,0,0,45,146,211,246,253,232,179,82,1, + 0,0,0,0,0,0,0,0,106,255,255,255,255,142,0,0,0,0,0,0,0,11,223,255, + 255,255,247,73,0,0,0,17,161,255,255,255,255,255,255,255,255,168,4,12,124,124,124,124, + 124,43,0,150,255,255,255,246,17,0,0,0,0,0,0,138,255,255,255,254,79,0,0,0, + 36,222,255,255,255,255,255,255,255,255,255,255,129,71,255,255,255,255,255,36,0,14,237,255, + 255,255,102,0,0,0,0,0,26,248,255,255,255,135,0,0,0,27,227,255,255,255,255,255, + 255,255,255,255,255,255,244,145,255,255,255,255,225,0,0,0,142,255,255,255,171,0,0,0, + 0,0,134,255,255,255,221,7,0,0,2,192,255,255,255,255,255,255,255,233,228,255,255,255, + 255,245,255,255,255,255,157,0,0,0,69,255,255,255,218,0,0,0,0,2,231,255,255,255, + 97,0,0,0,97,255,255,255,255,255,255,173,38,0,0,29,177,255,255,255,255,255,255,255, + 88,0,0,0,25,255,255,255,245,0,0,0,0,59,255,255,255,237,7,0,0,2,222,255, + 255,255,255,255,149,1,0,0,0,0,6,210,255,255,255,255,255,255,22,0,0,0,4,255, + 255,255,254,0,0,0,0,126,255,255,255,160,0,0,0,69,255,255,255,255,255,217,6,0, + 0,0,0,0,0,105,255,255,255,255,255,209,0,0,0,0,4,255,255,255,245,0,0,0, + 0,177,255,255,255,95,0,0,0,146,255,255,255,255,255,105,0,0,0,0,0,0,0,59, + 255,255,255,255,255,141,0,0,0,0,22,255,255,255,220,0,0,0,0,216,255,255,255,47, + 0,0,0,201,255,255,255,255,255,35,0,0,0,0,0,0,0,53,255,255,255,255,255,73, + 0,0,0,0,62,255,255,255,180,0,0,0,0,240,255,255,255,18,0,0,0,235,255,255, + 255,255,255,5,0,0,0,0,0,0,0,85,255,255,255,255,250,10,0,0,0,0,122,255, + 255,255,120,0,0,0,0,253,255,255,255,2,0,0,0,252,255,255,255,255,255,7,0,0, + 0,0,0,0,0,157,255,255,255,255,192,0,0,0,0,0,204,255,255,255,39,0,0,0, + 0,254,255,255,255,4,0,0,0,250,255,255,255,255,255,54,0,0,0,0,0,0,29,246, + 255,255,255,255,126,0,0,0,0,58,255,255,255,196,0,0,0,0,0,241,255,255,255,22, + 0,0,0,227,255,255,255,255,255,166,0,0,0,0,0,9,193,255,255,255,255,255,75,0, + 0,0,3,198,255,255,255,72,0,0,0,0,0,215,255,255,255,60,0,0,0,177,255,255, + 255,255,255,255,147,19,0,1,59,203,255,255,255,255,255,255,59,0,0,0,142,255,255,255, + 184,0,0,0,0,0,0,172,255,255,255,121,0,0,0,95,255,255,255,255,255,255,255,253, + 224,238,255,255,255,237,255,255,255,255,142,8,43,171,255,255,255,237,28,0,0,0,0,0, + 0,111,255,255,255,206,0,0,0,6,222,255,255,255,255,255,255,255,255,255,255,255,151,173, + 255,255,255,255,255,255,255,255,255,255,248,63,0,0,0,0,0,0,0,34,255,255,255,255, + 69,0,0,0,59,249,255,255,255,255,255,255,255,255,255,187,8,89,255,255,255,255,255,255, + 255,255,255,239,66,0,0,0,0,0,0,0,0,0,188,255,255,255,214,8,0,0,0,70, + 236,255,255,255,255,255,255,255,153,7,0,1,173,255,255,255,255,255,255,255,187,33,0,0, + 0,0,0,0,0,0,0,0,63,255,255,255,255,164,1,0,0,0,20,127,207,245,253,225, + 160,55,0,0,0,0,3,107,205,249,251,221,161,65,0,0,0,0,0,0,0,0,0,0, + 0,0,0,165,255,255,255,255,157,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,220,255, + 255,255,255,201,43,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,233,255,255,255,255,252,161, + 50,0,0,0,0,0,0,0,0,0,0,0,0,0,7,72,160,224,7,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,38,220,255,255,255,255,255,255,216,145,91,52,23, + 7,1,6,21,45,81,127,185,245,255,255,255,88,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16,168,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,71,207,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 220,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,69,169, + 245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,243,169,76,3,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,67,124,169,204,230, + 246,254,253,245,227,200,165,118,60,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,13,246,255,255,255,255,255,255,126,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96, + 255,255,255,255,255,255,255,219,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,189,255,255,255,255,255,255,255,255,56,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,254,255,255, + 255,255,255,255,255,255,149,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,120,255,255,255,255,255,255,255,255,255,237,5,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,255,255,255,255,252, + 255,255,255,255,255,79,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,50,255,255,255,255,255,128,255,255,255,255,255,172,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,144,255,255,255,255,245,11,209,255, + 255,255,255,248,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,4,232,255,255,255,255,168,0,121,255,255,255,255,255,102,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,74,255,255,255,255,255,82,0,35,255,255,255, + 255,255,195,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,168, + 255,255,255,255,242,8,0,0,202,255,255,255,255,255,33,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,14,246,255,255,255,255,162,0,0,0,114,255,255,255,255, + 255,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,98,255,255,255, + 255,255,73,0,0,0,29,254,255,255,255,255,217,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,192,255,255,255,255,237,5,0,0,0,0,196,255,255,255,255,255, + 54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,254,255,255,255,255,153, + 0,0,0,0,0,108,255,255,255,255,255,147,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,122,255,255,255,255,255,66,0,0,0,0,0,24,253,255,255,255,255,235,5, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,215,255,255,255,255,231,3,0,0, + 0,0,0,0,190,255,255,255,255,255,77,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,52,255,255,255,255,255,146,0,0,0,0,0,0,0,103,255,255,255,255,255,170,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,146,255,255,255,255,255,58,0,0,0,0,0, + 0,0,19,251,255,255,255,255,248,15,0,0,0,0,0,0,0,0,0,0,0,0,4,234, + 255,255,255,255,225,1,0,0,0,0,0,0,0,0,184,255,255,255,255,255,100,0,0,0, + 0,0,0,0,0,0,0,0,0,76,255,255,255,255,255,138,0,0,0,0,0,0,0,0, + 0,96,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,169,255,255,255, + 255,255,234,220,220,220,220,220,220,220,220,220,229,255,255,255,255,255,254,30,0,0,0,0, + 0,0,0,0,0,0,15,247,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,123,0,0,0,0,0,0,0,0,0,0,100,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,215,0,0,0,0,0,0, + 0,0,0,0,192,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,52,0,0,0,0,0,0,0,0,31,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,146,0,0,0,0,0,0,0, + 0,124,255,255,255,255,255,124,0,0,0,0,0,0,0,0,0,0,0,0,0,84,255,255, + 255,255,255,234,4,0,0,0,0,0,0,0,216,255,255,255,255,255,36,0,0,0,0,0, + 0,0,0,0,0,0,0,0,9,243,255,255,255,255,255,75,0,0,0,0,0,0,54,255, + 255,255,255,255,204,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,164,255,255,255, + 255,255,169,0,0,0,0,0,0,148,255,255,255,255,255,115,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,77,255,255,255,255,255,247,14,0,0,0,0,5,235,255,255,255, + 255,254,29,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,239,255,255,255,255, + 255,98,0,0,0,0,78,255,255,255,255,255,196,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,158,255,255,255,255,255,191,0,0,0,0,171,255,255,255,255,255,108, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,255,255,255,255,255,254, + 30,0,0,16,248,255,255,255,255,253,23,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,4,235,255,255,255,255,255,120,0,0,101,255,255,255,255,255,188,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,152,255,255,255,255,255,213,0, + 0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,239,209,157,83,6, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,228,93,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,160,4,0,0,0,0,0,0,0, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,151, + 0,0,0,0,0,0,0,255,255,255,255,255,255,231,220,220,220,220,220,220,220,228,251,255, + 255,255,255,255,255,255,255,66,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,8,78,213,255,255,255,255,255,255,186,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,14,213,255,255,255,255,255,251,11,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,83,255, + 255,255,255,255,255,52,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,19,255,255,255,255,255,255,69,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,3,255,255,255,255,255,255,63,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,24,255,255,255,255, + 255,255,28,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,97,255,255,255,255,255,217,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,25,227,255,255,255,255,255,110,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,15,96,227,255,255,255,255,255,204,5,0, + 0,0,0,0,0,255,255,255,255,255,255,231,220,220,220,220,220,220,220,230,254,255,255,255, + 255,255,255,220,27,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,168,18,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,126,2,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,210, + 56,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,250,92,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,3,27,87,202,255,255,255,255,255,253,72,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,3,149,255,255,255,255,255,232, + 16,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 3,201,255,255,255,255,255,125,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,87,255,255,255,255,255,219,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,25,255,255,255,255,255,255,24,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,2,255, + 255,255,255,255,255,60,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,13,255,255,255,255,255,255,70,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,60,255,255,255,255,255,255,60,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,158,255,255,255,255, + 255,255,24,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,79,254,255,255,255,255,255,218,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,33,138,252,255,255,255,255,255,255,124,0,0,0,0,0,255,255,255, + 255,255,255,231,220,220,220,220,220,220,220,221,236,255,255,255,255,255,255,255,255,232,15,0, + 0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,251,69,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,245,81,0,0,0,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,184,38,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,250,231,191,130,44,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,54,124,178,216,242,254, + 253,239,212,170,112,38,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,11,113,219,255,255,255,255,255,255,255,255,255,255,255,255,198,83,1,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,90,230,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,199,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 10,165,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,247,93,0, + 0,0,0,0,0,0,0,0,0,0,0,15,199,255,255,255,255,255,255,255,255,255,245,224, + 225,247,255,255,255,255,255,255,255,255,255,112,0,0,0,0,0,0,0,0,0,0,7,196, + 255,255,255,255,255,255,255,201,101,32,0,0,0,1,37,113,218,255,255,255,255,255,255,255, + 88,0,0,0,0,0,0,0,0,0,154,255,255,255,255,255,255,234,82,0,0,0,0,0, + 0,0,0,0,4,116,250,255,255,255,255,255,245,36,0,0,0,0,0,0,0,73,255,255, + 255,255,255,255,217,30,0,0,0,0,0,0,0,0,0,0,0,0,69,248,255,255,255,255, + 255,184,0,0,0,0,0,0,5,219,255,255,255,255,255,234,30,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,100,255,255,255,255,255,255,57,0,0,0,0,0,93,255,255,255, + 255,255,255,81,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,198,255,255,255, + 255,255,162,0,0,0,0,0,202,255,255,255,255,255,191,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,84,255,255,255,255,255,240,4,0,0,0,35,255,255,255,255, + 255,255,77,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,248,255,255, + 255,255,255,51,0,0,0,108,255,255,255,255,255,238,4,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,188,236,236,236,236,236,87,0,0,0,163,255,255,255,255, + 255,175,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,206,255,255,255,255,255,127,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,234,255,255,255,255, + 255,95,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,250,255,255,255,255,255,79,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,73,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,249,255,255,255,255,255,82,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,230,255,255,255,255, + 255,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,199,255,255,255,255,255,136,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,155,255,255,255,255, + 255,187,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,96,255,255,255,255,255,246,10,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,162,216,216,216,216,216,103,0,0,0,24,254,255,255,255, + 255,255,92,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,243,255,255, + 255,255,255,74,0,0,0,0,187,255,255,255,255,255,207,2,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,81,255,255,255,255,255,249,12,0,0,0,0,79,255,255,255, + 255,255,255,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,200,255,255,255, + 255,255,173,0,0,0,0,0,1,206,255,255,255,255,255,244,45,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,104,255,255,255,255,255,255,65,0,0,0,0,0,0,58,254,255, + 255,255,255,255,230,46,0,0,0,0,0,0,0,0,0,0,0,0,75,250,255,255,255,255, + 255,189,0,0,0,0,0,0,0,0,135,255,255,255,255,255,255,244,105,2,0,0,0,0, + 0,0,0,0,5,123,252,255,255,255,255,255,247,40,0,0,0,0,0,0,0,0,3,179, + 255,255,255,255,255,255,255,215,112,37,1,0,0,1,37,115,220,255,255,255,255,255,255,255, + 96,0,0,0,0,0,0,0,0,0,0,8,182,255,255,255,255,255,255,255,255,255,246,224, + 224,246,255,255,255,255,255,255,255,255,255,123,0,0,0,0,0,0,0,0,0,0,0,0, + 4,144,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,250,106,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,220,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,207,51,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,6,101,211,255,255,255,255,255,255,255,255,255,255,255,255,205,91,3,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,121,175,216,242,254, + 253,240,212,173,116,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,255,255,250,238,215,183,138,80,15,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,250,178,76,1,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,208,68,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,157,11,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,231,220,220,220,220,220,220,227,246,255,255,255,255,255,255,255,255,255,255,207,27,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,26, + 79,153,241,255,255,255,255,255,255,255,220,23,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,14,132,250,255,255,255,255,255,255,200, + 6,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,52,229,255,255,255,255,255,255,133,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,35,233,255,255,255,255,255, + 251,39,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,65,254,255,255,255,255,255,166,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,158,255,255,255,255, + 255,252,24,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,34,254,255,255,255,255,255,109,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,191,255,255,255, + 255,255,181,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,116,255,255,255,255,255,236,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,62,255,255,255, + 255,255,255,22,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,25,255,255,255,255,255,255,49,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,255,255,255, + 255,255,255,66,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,2,255,255,255,255,255,255,70,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,255,255,255, + 255,255,255,64,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,36,255,255,255,255,255,255,45,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,75,255,255,255, + 255,255,255,14,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,134,255,255,255,255,255,227,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,213,255,255,255, + 255,255,167,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,59,255,255,255,255,255,255,93,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,188,255,255,255,255, + 255,243,12,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,94,255,255,255,255,255,255,143,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,56,246,255,255,255,255,255, + 242,23,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,74,241,255,255,255,255,255,255,106,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,23,153,254,255,255,255,255,255,255,176, + 1,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,1,28, + 85,164,247,255,255,255,255,255,255,255,200,12,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,231,220,220,220,220,220,220,227,246,255,255,255,255,255,255,255,255,255,255,190,15,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,254,139,5,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,198,57,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,249,173,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,255,255,251,239,216,183,138,80,13,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,128,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,128,0,0,0,0,0,255,255,255,255,255,255,231,220,220,220,220,220,220,220,220,220, + 220,220,220,220,220,220,220,220,110,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0, + 0,0,255,255,255,255,255,255,231,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220, + 220,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,231,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,128,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,128,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,255,255, + 255,255,255,255,231,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,110,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,231,220,220,220,220,220,220,220, + 220,220,220,220,220,220,220,220,110,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128, + 0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,128,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,59,127,180,218,241, + 252,253,241,216,176,123,51,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,14,121,226,255,255,255,255,255,255,255,255,255,255,255,255,218,110,9,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,95,235,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,225,78,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,12,170,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,148,4,0,0,0,0,0,0,0,0,0,0,0,0,18,203,255,255,255,255,255,255, + 255,255,255,245,224,224,245,255,255,255,255,255,255,255,255,255,173,4,0,0,0,0,0,0, + 0,0,0,0,9,198,255,255,255,255,255,255,255,201,103,32,1,0,0,0,31,99,201,255, + 255,255,255,255,255,255,146,0,0,0,0,0,0,0,0,0,0,155,255,255,255,255,255,255, + 233,82,0,0,0,0,0,0,0,0,0,0,89,240,255,255,255,255,255,255,74,0,0,0, + 0,0,0,0,0,75,255,255,255,255,255,255,213,28,0,0,0,0,0,0,0,0,0,0, + 0,0,45,240,255,255,255,255,255,216,3,0,0,0,0,0,0,4,219,255,255,255,255,255, + 231,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84,255,255,255,255,255,255,79, + 0,0,0,0,0,0,94,255,255,255,255,255,255,76,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,203,255,255,255,255,255,165,0,0,0,0,0,0,201,255,255,255,255, + 255,188,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,113,255,255,255,255, + 255,227,0,0,0,0,0,33,255,255,255,255,255,255,75,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,29,104,104,104,104,104,104,1,0,0,0,0,103,255,255,255, + 255,255,239,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,158,255,255,255,255,255,177,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,200,255, + 255,255,255,255,129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,229,255,255,255,255,255,97,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 248,255,255,255,255,255,79,0,0,0,0,0,0,0,0,0,0,36,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,72,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,36,255,255,255,255,255,255,255,255,255,255,255,255,255,255,72,0,0, + 0,0,250,255,255,255,255,255,79,0,0,0,0,0,0,0,0,0,0,36,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,72,0,0,0,0,236,255,255,255,255,255,97,0,0, + 0,0,0,0,0,0,0,0,36,255,255,255,255,255,255,255,255,255,255,255,255,255,255,72, + 0,0,0,0,211,255,255,255,255,255,129,0,0,0,0,0,0,0,0,0,0,31,220,220, + 220,220,220,220,220,220,255,255,255,255,255,255,72,0,0,0,0,173,255,255,255,255,255,176, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,120,255,255,255,255,255,237,3,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,52,255,255,255,255, + 255,255,73,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,1,225,255,255,255,255,255,185,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,125,255, + 255,255,255,255,255,73,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,18,241,255,255,255,255,255,231,29,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,113,255,255,255,255,255,255,219,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 51,255,255,255,255,255,255,72,0,0,0,0,0,0,3,193,255,255,255,255,255,255,239,101, + 3,0,0,0,0,0,0,0,0,0,0,3,105,245,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,23,223,255,255,255,255,255,255,255,223,129,57,10,0,0,0,0,13,60,130, + 223,255,255,255,255,255,255,255,249,47,0,0,0,0,0,0,0,0,34,223,255,255,255,255, + 255,255,255,255,255,255,236,222,225,239,255,255,255,255,255,255,255,255,255,255,238,66,0,0, + 0,0,0,0,0,0,0,0,22,190,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,195,33,0,0,0,0,0,0,0,0,0,0,0,0,2,107, + 238,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,231,101,2,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,14,117,219,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,200,99,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,46,113,166,204,232,248,255,251,241,220,190,149,95,27,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255, + 255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255, + 255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255, + 255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255, + 231,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,231,255,255,255,255,255,255,0, + 0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0, + 0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72, + 255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255, + 255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255, + 255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255, + 255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,2,255,255,255,255,255,255,71,0,0,0,0,0,0,0,0,0,0,0,0,0,26, + 255,255,255,255,255,255,60,0,0,0,0,0,0,0,0,0,0,0,0,0,90,255,255,255, + 255,255,255,31,0,0,0,0,0,0,0,0,0,0,0,0,18,218,255,255,255,255,255,235, + 1,0,0,0,155,80,26,0,0,0,0,23,97,223,255,255,255,255,255,255,154,0,0,0, + 0,255,255,255,245,224,222,238,255,255,255,255,255,255,255,255,251,41,0,0,0,0,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,124,0,0,0,0,0,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,135,1,0,0,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,205,67,0,0,0,0,0,0,0,76,144,190,222,242,254,253,243,219,182,128, + 51,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,143,255,255,255,255,255,255,255,193,11,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,134,255,255,255,255,255,255, + 255,193,11,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,124,255,255,255,255,255,255,255,193,11,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,116,255,255,255,255,255,255,255,193,11,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,107,255,255,255, + 255,255,255,255,193,11,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,98,255,255,255,255,255,255,255,193,11,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,90,253,255,255,255,255,255,255,193,11, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,81, + 252,255,255,255,255,255,255,193,11,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,74,250,255,255,255,255,255,255,193,11,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,67,248,255,255,255,255,255, + 255,193,11,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,60,245,255,255,255,255,255,255,192,11,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,54,242,255,255,255,255,255,255,190,10,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,48,238,255,255, + 255,255,255,255,190,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,43,235,255,255,255,255,255,255,190,10,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,108,230,255,255,255,255,255,255,190,10, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 251,255,255,255,255,255,255,190,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,190,10,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,190,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,255,255,255,255,255,255,255,190,10,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,128,242,255,255,255,255,255,255, + 190,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,64,246,255,255,255,255,255,255,190,10,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,71,249,255,255,255,255,255,255,190,10, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,79,251,255,255,255,255,255,255,190,10,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,89,253,255,255,255,255,255,255,190,10,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,97, + 255,255,255,255,255,255,255,190,10,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,107,255,255,255,255,255,255,255,190,10,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,118,255,255, + 255,255,255,255,255,190,10,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,128,255,255,255,255,255,255,255,190,10,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,138,255,255,255,255, + 255,255,255,190,10,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,1,149,255,255,255,255,255,255,255,190,10,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,1,159,255,255,255,255,255,255, + 255,190,10,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,3,167,255,255,255,255,255,255,255,190,10,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,5,177,255,255,255,255,255,255,255,190, + 10,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,7,185,255,255,255,255,255,255,255,190,10,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,10,192,255,255,255,255,255,255,255,190,10,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,231,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220, + 220,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255, + 255,255,255,255,255,255,255,255,55,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,175,255,255,255,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,255, + 255,255,255,134,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,245,255,255,255, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,214,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,255,255,255,255,255,255,255,255,255, + 255,72,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,35,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,155,255,255,255,255,255,255,255,255,255,255,72,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,114,0,0,0,0,0,0,0,0,0,0, + 0,0,0,2,232,255,255,255,255,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,255,255,255,255,255,194,0,0,0,0,0,0,0,0,0,0,0,0,0,58,255, + 255,255,255,240,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,235,255, + 255,255,255,252,18,0,0,0,0,0,0,0,0,0,0,0,0,136,255,255,255,255,166,255, + 255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,158,255,255,255,255,255,94, + 0,0,0,0,0,0,0,0,0,0,0,0,214,255,255,255,255,86,255,255,255,255,255,255, + 72,0,0,0,0,0,255,255,255,255,255,255,85,247,255,255,255,255,174,0,0,0,0,0, + 0,0,0,0,0,0,38,255,255,255,255,250,15,255,255,255,255,255,255,72,0,0,0,0, + 0,255,255,255,255,255,255,72,178,255,255,255,255,243,7,0,0,0,0,0,0,0,0,0, + 0,117,255,255,255,255,186,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255, + 255,255,72,98,255,255,255,255,255,74,0,0,0,0,0,0,0,0,0,0,194,255,255,255, + 255,106,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,21,253, + 255,255,255,255,154,0,0,0,0,0,0,0,0,0,21,253,255,255,255,255,30,0,255,255, + 255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,194,255,255,255,255,229, + 1,0,0,0,0,0,0,0,0,97,255,255,255,255,206,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,255,255,255,255,255,255,72,0,113,255,255,255,255,255,54,0,0,0,0, + 0,0,0,0,175,255,255,255,255,126,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,31,255,255,255,255,255,133,0,0,0,0,0,0,0,9,245, + 255,255,255,255,49,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,206,255,255,255,255,210,0,0,0,0,0,0,0,78,255,255,255,255,225,1, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,126, + 255,255,255,255,255,34,0,0,0,0,0,0,155,255,255,255,255,146,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,46,255,255,255,255,255, + 112,0,0,0,0,0,2,232,255,255,255,255,70,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,221,255,255,255,255,190,0,0,0,0, + 0,58,255,255,255,255,240,6,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,140,255,255,255,255,252,18,0,0,0,0,136,255,255,255, + 255,166,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,58,255,255,255,255,255,92,0,0,0,0,214,255,255,255,255,90,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,2, + 232,255,255,255,255,170,0,0,0,38,255,255,255,255,250,16,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,154,255,255,255,255, + 243,7,0,0,117,255,255,255,255,186,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,73,255,255,255,255,255,72,0,0,194, + 255,255,255,255,110,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,7,241,255,255,255,255,150,0,21,253,255,255,255,255,31, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,167,255,255,255,255,229,1,97,255,255,255,255,206,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 86,255,255,255,255,255,52,175,255,255,255,255,130,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,13,249,255,255,255, + 255,139,245,255,255,255,255,50,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,182,255,255,255,255,249,255,255,255, + 255,226,1,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,102,255,255,255,255,255,255,255,255,255,150,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,21,253,255,255,255,255,255,255,255,255,70,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,194,255,255,255,255,255,255,255,241,6,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,114,255,255,255, + 255,255,255,255,170,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,34,255,255,255,255,255,255,255,90, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,210,255,255,255,255,255,251,16,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,255,255,54, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,255,255,255,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0, + 72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,75,0, + 0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0, + 255,255,255,255,255,255,255,255,255,212,3,0,0,0,0,0,0,0,0,0,0,0,72,255, + 255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,97,0,0, + 0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255, + 255,255,255,255,255,255,255,255,228,9,0,0,0,0,0,0,0,0,0,0,72,255,255,255, + 255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,121,0,0,0, + 0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255, + 255,255,254,255,255,255,255,240,19,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255, + 255,0,0,0,0,0,0,255,255,255,255,255,255,162,255,255,255,255,255,142,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255, + 73,206,255,255,255,255,249,32,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0, + 0,0,0,0,0,255,255,255,255,255,255,72,67,255,255,255,255,255,164,0,0,0,0,0, + 0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 185,255,255,255,255,254,50,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,47,254,255,255,255,255,187,0,0,0,0,0,0, + 0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,161, + 255,255,255,255,255,69,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,31,248,255,255,255,255,207,2,0,0,0,0,0,72, + 255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,139,255, + 255,255,255,255,92,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,17,239,255,255,255,255,224,7,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,117,255,255, + 255,255,255,115,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,8,225,255,255,255,255,238,16,0,0,0,72,255,255,255,255, + 255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,95,255,255,255, + 255,255,135,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,2,209,255,255,255,255,247,28,0,0,72,255,255,255,255,255,255, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,71,255,255,255,255, + 255,159,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,189,255,255,255,255,253,44,0,72,255,255,255,255,255,255,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,50,255,255,255,255,255, + 181,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,166,255,255,255,255,255,64,72,255,255,255,255,255,255,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,34,250,255,255,255,255,202, + 73,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,144,255,255,255,255,255,158,255,255,255,255,255,255,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,19,241,255,255,255,255,253,255, + 255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,121,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,10,228,255,255,255,255,255,255,255, + 255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,0,0,99,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,3,213,255,255,255,255,255,255,255,255, + 255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,76,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,193,255,255,255,255,255,255,255,255,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 0,54,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,49,118,173,213,239,252,253,242,219,184,133,68,5,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,9,109,216,255,255,255,255,255,255,255,255, + 255,255,255,255,236,139,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,87,229,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,247,128,7,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,165,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,208,35,0,0,0,0,0,0,0,0,0,0, + 0,0,0,18,201,255,255,255,255,255,255,255,255,255,247,226,224,241,255,255,255,255,255,255, + 255,255,255,236,51,0,0,0,0,0,0,0,0,0,0,0,10,201,255,255,255,255,255,255, + 255,212,111,38,2,0,0,0,26,90,186,255,255,255,255,255,255,255,237,41,0,0,0,0, + 0,0,0,0,0,0,163,255,255,255,255,255,255,241,95,1,0,0,0,0,0,0,0,0, + 0,61,217,255,255,255,255,255,255,217,12,0,0,0,0,0,0,0,0,83,255,255,255,255, + 255,255,224,37,0,0,0,0,0,0,0,0,0,0,0,0,13,188,255,255,255,255,255,255, + 150,0,0,0,0,0,0,0,8,226,255,255,255,255,255,238,36,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,10,202,255,255,255,255,255,254,47,0,0,0,0,0,0,106,255, + 255,255,255,255,255,85,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,246, + 255,255,255,255,255,172,0,0,0,0,0,0,212,255,255,255,255,255,193,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,130,255,255,255,255,255,252,26,0,0,0, + 0,43,255,255,255,255,255,255,77,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,20,249,255,255,255,255,255,111,0,0,0,0,113,255,255,255,255,255,238,4,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,177,255,255,255,255,255, + 181,0,0,0,0,167,255,255,255,255,255,174,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,107,255,255,255,255,255,237,0,0,0,0,208,255,255,255,255, + 255,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,56,255, + 255,255,255,255,255,22,0,0,0,235,255,255,255,255,255,95,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,24,255,255,255,255,255,255,49,0,0,0,250, + 255,255,255,255,255,77,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,7,255,255,255,255,255,255,66,0,0,0,255,255,255,255,255,255,73,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,255,255,255,255,255,255,70, + 0,0,0,249,255,255,255,255,255,82,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,10,255,255,255,255,255,255,65,0,0,0,230,255,255,255,255,255,104, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,33,255,255,255, + 255,255,255,46,0,0,0,198,255,255,255,255,255,142,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,15,0,0,0,154,255,255, + 255,255,255,195,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 128,255,255,255,255,255,227,0,0,0,0,96,255,255,255,255,255,252,17,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,202,255,255,255,255,255,169,0,0,0, + 0,24,253,255,255,255,255,255,107,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,43,255,255,255,255,255,255,94,0,0,0,0,0,187,255,255,255,255,255,223,6, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,166,255,255,255,255,255,245, + 14,0,0,0,0,0,77,255,255,255,255,255,255,124,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,64,255,255,255,255,255,255,149,0,0,0,0,0,0,1,205,255,255, + 255,255,255,251,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,229,255,255,255, + 255,255,247,28,0,0,0,0,0,0,0,58,254,255,255,255,255,255,241,63,0,0,0,0, + 0,0,0,0,0,0,0,0,29,214,255,255,255,255,255,255,123,0,0,0,0,0,0,0, + 0,0,135,255,255,255,255,255,255,250,122,6,0,0,0,0,0,0,0,0,0,83,234,255, + 255,255,255,255,255,199,5,0,0,0,0,0,0,0,0,0,3,179,255,255,255,255,255,255, + 255,224,120,44,2,0,0,0,29,99,202,255,255,255,255,255,255,255,225,26,0,0,0,0, + 0,0,0,0,0,0,0,9,183,255,255,255,255,255,255,255,255,255,247,226,224,243,255,255, + 255,255,255,255,255,255,255,224,35,0,0,0,0,0,0,0,0,0,0,0,0,0,5,147, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,194,23,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,220,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,241,114,3,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,5,99,207,255,255,255,255,255,255,255,255,255,255,255,255,230,130,20,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,42,114, + 170,211,238,252,254,242,220,182,131,62,3,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,251,233,203,155,89,12, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,243,137,16,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,230,63,0,0,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,249,74,0,0,0,0,0,0,255,255,255,255,255,255,230,220,220,220,220,220,220,220, + 220,222,237,255,255,255,255,255,255,255,255,244,37,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,26,111,233,255,255,255,255,255,255,185,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,23,212,255, + 255,255,255,255,255,51,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,38,250,255,255,255,255,255,140,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,168,255,255,255,255,255,200,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103, + 255,255,255,255,255,237,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,76,255,255,255,255,255,254,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,80,255,255,255,255,255,250,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,115, + 255,255,255,255,255,228,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,190,255,255,255,255,255,186,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,66,255,255,255,255,255,255,115,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,50,234,255, + 255,255,255,255,250,26,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,1,17,62,150,250,255,255,255,255,255,255,143,0,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,215,12,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,225,33,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,191,26,0,0,0,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,211,91,1,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,231,220,220,220,220,220,220,220,220,220,215,198,165,117,47,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,49,118,173,213,239,252,253,242,219,184,133,68,5,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,109,216,255,255,255,255,255, + 255,255,255,255,255,255,255,236,139,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,87,229,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 247,128,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,165,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,208,35,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,18,201,255,255,255,255,255,255,255,255,255,247,226,224,241, + 255,255,255,255,255,255,255,255,255,236,51,0,0,0,0,0,0,0,0,0,0,0,0,10, + 201,255,255,255,255,255,255,255,209,108,37,1,0,0,0,25,90,184,255,255,255,255,255,255, + 255,237,41,0,0,0,0,0,0,0,0,0,0,0,163,255,255,255,255,255,255,238,92,1, + 0,0,0,0,0,0,0,0,0,57,214,255,255,255,255,255,255,217,12,0,0,0,0,0, + 0,0,0,0,83,255,255,255,255,255,255,220,35,0,0,0,0,0,0,0,0,0,0,0, + 0,12,183,255,255,255,255,255,255,150,0,0,0,0,0,0,0,0,8,226,255,255,255,255, + 255,234,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,196,255,255,255,255,255, + 254,47,0,0,0,0,0,0,0,106,255,255,255,255,255,255,78,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,30,243,255,255,255,255,255,172,0,0,0,0,0,0,0, + 212,255,255,255,255,255,187,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,123,255,255,255,255,255,252,26,0,0,0,0,0,43,255,255,255,255,255,255,69,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,246,255,255,255,255,255,111, + 0,0,0,0,0,113,255,255,255,255,255,235,3,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,170,255,255,255,255,255,181,0,0,0,0,0,167,255,255,255, + 255,255,171,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101, + 255,255,255,255,255,237,0,0,0,0,0,208,255,255,255,255,255,123,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,53,255,255,255,255,255,255,22,0,0, + 0,0,235,255,255,255,255,255,92,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,21,255,255,255,255,255,255,49,0,0,0,0,250,255,255,255,255,255,76, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,255,255,255, + 255,255,255,66,0,0,0,0,255,255,255,255,255,255,74,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,2,255,255,255,255,255,255,70,0,0,0,0,248, + 255,255,255,255,255,82,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,12,255,255,255,255,255,255,62,0,0,0,0,229,255,255,255,255,255,105,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,255,255,255,255,255,255, + 42,0,0,0,0,200,255,255,255,255,255,142,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,74,255,255,255,255,255,254,10,0,0,0,0,158,255,255,255, + 255,255,196,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128, + 255,255,255,255,255,213,0,0,0,0,0,101,255,255,255,255,255,251,17,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,202,255,255,255,255,255,149,0,0,0, + 0,0,31,255,255,255,255,255,255,105,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,41,255,255,255,255,255,255,67,0,0,0,0,0,0,197,255,255,255,255,255, + 220,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,161,255,255,255,255, + 255,221,2,0,0,0,0,0,0,91,255,255,255,255,255,255,113,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,56,254,255,255,255,255,255,97,0,0,0,0,0,0,0, + 4,217,255,255,255,255,255,248,54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19, + 221,255,255,255,255,255,199,3,0,0,0,0,0,0,0,0,71,255,255,255,255,255,255,235, + 51,0,0,0,0,0,0,0,0,0,0,0,0,21,205,255,255,255,255,255,237,33,0,0, + 0,0,0,0,0,0,0,0,151,255,255,255,255,255,255,247,109,3,0,0,0,0,0,0, + 0,0,0,73,227,255,255,255,255,255,240,55,0,0,0,0,0,0,0,0,0,0,0,7, + 194,255,255,255,255,255,255,255,218,116,41,2,0,0,0,27,96,195,255,255,255,255,255,255, + 218,53,26,38,54,70,86,101,0,0,0,0,0,0,0,15,197,255,255,255,255,255,255,255, + 255,255,247,225,224,241,255,255,255,255,255,255,255,255,255,254,255,255,255,255,255,255,255,0, + 0,0,0,0,0,0,0,8,162,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0, + 84,229,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,9,111,218,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,49,119,173,213,239,252,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,251,236,205,159,93,16,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,247,152,23,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,240,82,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,100,0,0,0,0,0,0,0,255, + 255,255,255,255,255,230,220,220,220,220,220,220,220,221,233,255,255,255,255,255,255,255,255,252, + 59,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,16, + 88,209,255,255,255,255,255,255,211,2,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,5,168,255,255,255,255,255,255,75,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,10,230,255,255,255,255, + 255,159,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,0,136,255,255,255,255,255,214,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,0,0,85,255,255,255,255,255,245,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,75,255,255,255,255, + 255,254,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,0,98,255,255,255,255,255,244,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,0,0,160,255,255,255,255,255,208,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,35,247,255,255,255,255, + 255,149,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,29,212,255,255,255,255,255,255,61,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,1,14,56,134,241,255,255,255,255,255,255,193,0,0,0,0,0,0,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,244, + 39,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,247,71,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,219,54,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,225,113,8,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,231,248,255,255,255,255,255,255,247,216,199, + 169,123,57,2,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,54,246, + 255,255,255,255,255,249,75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,75,252,255,255,255,255,255,249,75,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,100,255,255,255,255,255,255,249, + 75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,127,255,255,255,255,255,255,249,75,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,156,255,255,255,255,255,255,249,75,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,4,179,255,255,255,255, + 255,255,248,72,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,10,201,255,255,255,255,255,255,248,72,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,20,218,255,255,255,255,255,255,248,72,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,34,233,255, + 255,255,255,255,255,248,72,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,51,244,255,255,255,255,255,255,248,72,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,71,251,255,255,255,255,255,255,248, + 72,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 95,255,255,255,255,255,255,255,247,69,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,122,255,255,255,255,255,255,255,247,69,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,150,255,255,255,255,255, + 255,255,247,69,0,0,0,0,0,0,0,0,0,18,94,156,202,231,249,254,248,230,201,154, + 91,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,162,249,255,255,255, + 255,255,255,255,255,255,255,255,248,156,29,0,0,0,0,0,0,0,0,0,0,0,0,1, + 118,249,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,245,101,0,0,0,0,0, + 0,0,0,0,0,0,150,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,136,0,0,0,0,0,0,0,0,0,109,255,255,255,255,255,255,255,255,250,227,222, + 238,255,255,255,255,255,255,255,255,255,111,0,0,0,0,0,0,0,23,244,255,255,255,255, + 255,253,154,54,5,0,0,0,22,92,202,255,255,255,255,255,255,250,40,0,0,0,0,0, + 0,123,255,255,255,255,255,250,71,0,0,0,0,0,0,0,0,2,128,255,255,255,255,255, + 255,170,0,0,0,0,0,0,196,255,255,255,255,255,146,0,0,0,0,0,0,0,0,0, + 0,0,158,255,255,255,255,255,252,21,0,0,0,0,0,239,255,255,255,255,255,80,0,0, + 0,0,0,0,0,0,0,0,0,24,249,255,255,255,255,255,92,0,0,0,0,0,254,255, + 255,255,255,255,87,0,0,0,0,0,0,0,0,0,0,0,0,183,255,255,255,255,255,145, + 0,0,0,0,0,244,255,255,255,255,255,159,0,0,0,0,0,0,0,0,0,0,0,0, + 46,84,84,84,84,84,56,0,0,0,0,0,207,255,255,255,255,255,253,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,138,255,255,255,255, + 255,255,251,140,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,35,251,255,255,255,255,255,255,255,251,183,104,27,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,130,255,255,255,255,255,255,255,255,255,255,255,206,130, + 52,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,167,255,255,255,255,255, + 255,255,255,255,255,255,255,255,231,154,72,4,0,0,0,0,0,0,0,0,0,0,0,0, + 0,2,127,251,255,255,255,255,255,255,255,255,255,255,255,255,255,255,229,123,14,0,0,0, + 0,0,0,0,0,0,0,0,0,0,44,177,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,234,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,144,229,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,144,1,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,2,57,131,206,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,102,176,244,255,255,255,255,255, + 255,255,255,253,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,7,83,197,255,255,255,255,255,255,255,167,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,105,254,255,255,255,255,255,245,5,0,0,63,96, + 96,96,96,96,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,255,255,255,255, + 255,255,43,0,0,139,255,255,255,255,255,170,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,42,255,255,255,255,255,255,67,0,0,84,255,255,255,255,255,242,11,0,0,0,0, + 0,0,0,0,0,0,0,0,0,4,255,255,255,255,255,255,66,0,0,16,249,255,255,255, + 255,255,127,0,0,0,0,0,0,0,0,0,0,0,0,0,19,255,255,255,255,255,255,46, + 0,0,0,166,255,255,255,255,255,251,70,0,0,0,0,0,0,0,0,0,0,0,0,101, + 255,255,255,255,255,249,9,0,0,0,45,252,255,255,255,255,255,249,111,3,0,0,0,0, + 0,0,0,0,0,50,238,255,255,255,255,255,184,0,0,0,0,0,138,255,255,255,255,255, + 255,255,218,115,45,4,0,0,0,5,55,146,249,255,255,255,255,255,255,76,0,0,0,0, + 0,5,186,255,255,255,255,255,255,255,255,255,250,230,222,230,252,255,255,255,255,255,255,255, + 255,179,0,0,0,0,0,0,0,10,178,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,207,16,0,0,0,0,0,0,0,0,2,116,246,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,170,13,0,0,0,0,0,0,0,0,0, + 0,0,26,141,237,255,255,255,255,255,255,255,255,255,255,255,255,255,192,70,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,5,67,131,179,215,237,250,255,248,231,203,163,108, + 33,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0, + 0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,220,220,220,220,220,220, + 220,220,220,220,220,230,255,255,255,255,255,255,220,220,220,220,220,220,220,220,220,220,220,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,250,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,66,0,0,0,0,0,238,255, + 255,255,255,255,78,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,255,255,255,255, + 255,255,54,0,0,0,0,0,214,255,255,255,255,255,96,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,24,255,255,255,255,255,255,30,0,0,0,0,0,182,255,255,255,255,255, + 130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,56,255,255,255,255,255,249,5,0, + 0,0,0,0,136,255,255,255,255,255,183,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,112,255,255,255,255,255,208,0,0,0,0,0,0,75,255,255,255,255,255,248,15,0,0, + 0,0,0,0,0,0,0,0,0,0,0,191,255,255,255,255,255,145,0,0,0,0,0,0, + 10,244,255,255,255,255,255,124,0,0,0,0,0,0,0,0,0,0,0,0,51,255,255,255, + 255,255,255,68,0,0,0,0,0,0,0,158,255,255,255,255,255,245,46,0,0,0,0,0, + 0,0,0,0,0,9,207,255,255,255,255,255,225,2,0,0,0,0,0,0,0,40,253,255, + 255,255,255,255,230,55,0,0,0,0,0,0,0,0,20,191,255,255,255,255,255,255,106,0, + 0,0,0,0,0,0,0,0,144,255,255,255,255,255,255,252,157,59,5,0,0,1,38,123, + 236,255,255,255,255,255,255,208,4,0,0,0,0,0,0,0,0,0,9,208,255,255,255,255, + 255,255,255,255,250,227,224,244,255,255,255,255,255,255,255,255,243,42,0,0,0,0,0,0, + 0,0,0,0,0,26,217,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,244,64,0,0,0,0,0,0,0,0,0,0,0,0,0,18,177,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,214,45,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,80,202,255,255,255,255,255,255,255,255,255,255,255,255,225,111,6,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,119,175,212,240,251,252,242, + 220,185,132,62,3,0,0,0,0,0,0,0,0,0,0,0,122,255,255,255,255,255,250,18, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,136,255,255,255,255,255, + 169,0,0,30,254,255,255,255,255,255,99,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,221,255,255,255,255,255,77,0,0,0,192,255,255,255,255,255,186,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,54,255,255,255,255,255,235,5,0, + 0,0,100,255,255,255,255,255,252,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,142,255,255,255,255,255,148,0,0,0,0,14,247,255,255,255,255,255,104,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,2,227,255,255,255,255,255,55,0,0,0,0, + 0,169,255,255,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60, + 255,255,255,255,255,218,0,0,0,0,0,0,76,255,255,255,255,255,254,25,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,148,255,255,255,255,255,125,0,0,0,0,0,0,4, + 234,255,255,255,255,255,110,0,0,0,0,0,0,0,0,0,0,0,0,0,3,231,255,255, + 255,255,255,34,0,0,0,0,0,0,0,146,255,255,255,255,255,197,0,0,0,0,0,0, + 0,0,0,0,0,0,0,66,255,255,255,255,255,196,0,0,0,0,0,0,0,0,52,255, + 255,255,255,255,255,29,0,0,0,0,0,0,0,0,0,0,0,0,153,255,255,255,255,255, + 104,0,0,0,0,0,0,0,0,0,215,255,255,255,255,255,116,0,0,0,0,0,0,0, + 0,0,0,0,4,236,255,255,255,255,249,18,0,0,0,0,0,0,0,0,0,123,255,255, + 255,255,255,204,0,0,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,174,0,0, + 0,0,0,0,0,0,0,0,30,254,255,255,255,255,255,35,0,0,0,0,0,0,0,0, + 0,0,158,255,255,255,255,255,82,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255, + 255,255,122,0,0,0,0,0,0,0,0,0,7,239,255,255,255,255,237,7,0,0,0,0, + 0,0,0,0,0,0,0,100,255,255,255,255,255,209,0,0,0,0,0,0,0,0,0,78, + 255,255,255,255,255,151,0,0,0,0,0,0,0,0,0,0,0,0,14,247,255,255,255,255, + 255,40,0,0,0,0,0,0,0,0,164,255,255,255,255,255,60,0,0,0,0,0,0,0, + 0,0,0,0,0,0,169,255,255,255,255,255,128,0,0,0,0,0,0,0,9,243,255,255, + 255,255,222,1,0,0,0,0,0,0,0,0,0,0,0,0,0,76,255,255,255,255,255,215, + 0,0,0,0,0,0,0,84,255,255,255,255,255,130,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,4,234,255,255,255,255,255,46,0,0,0,0,0,0,170,255,255,255,255,255, + 37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,146,255,255,255,255,255,133,0, + 0,0,0,0,12,246,255,255,255,255,200,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,53,255,255,255,255,255,220,0,0,0,0,0,88,255,255,255,255,255,108,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,215,255,255,255,255,255,52,0,0, + 0,0,176,255,255,255,255,251,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,123,255,255,255,255,255,140,0,0,0,15,249,255,255,255,255,179,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,255,255,226,1,0,0,94, + 255,255,255,255,255,86,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,193,255,255,255,255,255,58,0,0,182,255,255,255,255,241,8,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,100,255,255,255,255,255,145,0,18,251,255,255, + 255,255,156,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15, + 248,255,255,255,255,230,2,100,255,255,255,255,255,64,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,170,255,255,255,255,255,64,188,255,255,255,255,226, + 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,76,255, + 255,255,255,255,172,253,255,255,255,255,133,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,5,235,255,255,255,255,255,255,255,255,255,255,42,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,147,255,255, + 255,255,255,255,255,255,255,205,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,54,255,255,255,255,255,255,255,255,255,112,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,216,255,255,255, + 255,255,255,255,252,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,124,255,255,255,255,255,255,255,183,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,255,255,255,255,255, + 255,255,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,245,255,255,255,255,255, + 86,0,0,0,0,0,0,0,0,0,0,0,0,0,119,255,255,255,255,255,255,246,5,0, + 0,0,0,0,0,0,0,0,0,0,0,0,226,255,255,255,255,255,40,0,0,0,180,255, + 255,255,255,255,152,0,0,0,0,0,0,0,0,0,0,0,0,0,181,255,255,255,255,255, + 255,255,56,0,0,0,0,0,0,0,0,0,0,0,0,36,255,255,255,255,255,223,0,0, + 0,0,106,255,255,255,255,255,220,0,0,0,0,0,0,0,0,0,0,0,0,3,241,255, + 255,255,255,255,255,255,118,0,0,0,0,0,0,0,0,0,0,0,0,105,255,255,255,255, + 255,150,0,0,0,0,34,255,255,255,255,255,255,32,0,0,0,0,0,0,0,0,0,0, + 0,51,255,255,255,255,255,255,255,255,180,0,0,0,0,0,0,0,0,0,0,0,0,172, + 255,255,255,255,255,78,0,0,0,0,0,217,255,255,255,255,255,99,0,0,0,0,0,0, + 0,0,0,0,0,112,255,255,255,255,255,255,255,255,238,2,0,0,0,0,0,0,0,0, + 0,0,2,237,255,255,255,255,250,12,0,0,0,0,0,144,255,255,255,255,255,165,0,0, + 0,0,0,0,0,0,0,0,0,176,255,255,255,255,255,255,255,255,255,46,0,0,0,0, + 0,0,0,0,0,0,50,255,255,255,255,255,189,0,0,0,0,0,0,71,255,255,255,255, + 255,233,1,0,0,0,0,0,0,0,0,0,1,236,255,255,255,255,242,255,255,255,255,108, + 0,0,0,0,0,0,0,0,0,0,117,255,255,255,255,255,116,0,0,0,0,0,0,9, + 246,255,255,255,255,255,45,0,0,0,0,0,0,0,0,0,44,255,255,255,255,227,160,255, + 255,255,255,170,0,0,0,0,0,0,0,0,0,0,185,255,255,255,255,255,44,0,0,0, + 0,0,0,0,181,255,255,255,255,255,112,0,0,0,0,0,0,0,0,0,108,255,255,255, + 255,160,96,255,255,255,255,231,0,0,0,0,0,0,0,0,0,6,246,255,255,255,255,226, + 0,0,0,0,0,0,0,0,110,255,255,255,255,255,180,0,0,0,0,0,0,0,0,0, + 169,255,255,255,255,96,32,255,255,255,255,255,36,0,0,0,0,0,0,0,0,63,255,255, + 255,255,255,153,0,0,0,0,0,0,0,0,37,255,255,255,255,255,242,4,0,0,0,0, + 0,0,0,0,231,255,255,255,255,32,0,224,255,255,255,255,98,0,0,0,0,0,0,0, + 0,130,255,255,255,255,255,82,0,0,0,0,0,0,0,0,0,221,255,255,255,255,255,59, + 0,0,0,0,0,0,0,37,255,255,255,255,224,0,0,160,255,255,255,255,160,0,0,0, + 0,0,0,0,0,197,255,255,255,255,251,15,0,0,0,0,0,0,0,0,0,148,255,255, + 255,255,255,126,0,0,0,0,0,0,0,100,255,255,255,255,156,0,0,96,255,255,255,255, + 220,0,0,0,0,0,0,0,13,251,255,255,255,255,192,0,0,0,0,0,0,0,0,0, + 0,76,255,255,255,255,255,193,0,0,0,0,0,0,0,163,255,255,255,255,92,0,0,32, + 255,255,255,255,255,27,0,0,0,0,0,0,76,255,255,255,255,255,121,0,0,0,0,0, + 0,0,0,0,0,10,248,255,255,255,255,250,10,0,0,0,0,0,0,224,255,255,255,255, + 28,0,0,0,224,255,255,255,255,89,0,0,0,0,0,0,143,255,255,255,255,255,48,0, + 0,0,0,0,0,0,0,0,0,0,186,255,255,255,255,255,71,0,0,0,0,0,32,255, + 255,255,255,220,0,0,0,0,160,255,255,255,255,148,0,0,0,0,0,0,210,255,255,255, + 255,230,1,0,0,0,0,0,0,0,0,0,0,0,112,255,255,255,255,255,140,0,0,0, + 0,0,93,255,255,255,255,153,0,0,0,0,96,255,255,255,255,211,0,0,0,0,0,22, + 255,255,255,255,255,158,0,0,0,0,0,0,0,0,0,0,0,0,40,255,255,255,255,255, + 207,0,0,0,0,0,156,255,255,255,255,89,0,0,0,0,32,255,255,255,255,254,18,0, + 0,0,0,88,255,255,255,255,255,86,0,0,0,0,0,0,0,0,0,0,0,0,0,224, + 255,255,255,255,254,20,0,0,0,0,219,255,255,255,255,24,0,0,0,0,0,224,255,255, + 255,255,78,0,0,0,0,156,255,255,255,255,253,17,0,0,0,0,0,0,0,0,0,0, + 0,0,0,151,255,255,255,255,255,85,0,0,0,25,255,255,255,255,215,0,0,0,0,0, + 0,160,255,255,255,255,140,0,0,0,0,224,255,255,255,255,196,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,78,255,255,255,255,255,152,0,0,0,87,255,255,255,255,149,0, + 0,0,0,0,0,96,255,255,255,255,201,0,0,0,35,255,255,255,255,255,124,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,12,250,255,255,255,255,220,0,0,0,148,255,255, + 255,255,84,0,0,0,0,0,0,32,255,255,255,255,251,11,0,0,101,255,255,255,255,255, + 52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,188,255,255,255,255,255,32,0, + 0,212,255,255,255,255,21,0,0,0,0,0,0,0,224,255,255,255,255,68,0,0,169,255, + 255,255,255,233,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,116,255,255,255, + 255,255,99,0,19,255,255,255,255,212,0,0,0,0,0,0,0,0,160,255,255,255,255,129, + 0,1,235,255,255,255,255,162,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 44,255,255,255,255,255,165,0,80,255,255,255,255,146,0,0,0,0,0,0,0,0,96,255, + 255,255,255,191,0,48,255,255,255,255,255,89,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,226,255,255,255,255,233,1,144,255,255,255,255,80,0,0,0,0,0,0, + 0,0,32,255,255,255,255,246,6,115,255,255,255,255,254,20,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,153,255,255,255,255,255,45,204,255,255,255,254,18,0,0, + 0,0,0,0,0,0,0,220,255,255,255,255,59,181,255,255,255,255,200,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,82,255,255,255,255,255,127,253,255,255,255, + 208,0,0,0,0,0,0,0,0,0,0,156,255,255,255,255,125,244,255,255,255,255,128,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,250,255,255,255,255,240, + 255,255,255,255,143,0,0,0,0,0,0,0,0,0,0,92,255,255,255,255,232,255,255,255, + 255,255,57,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255, + 255,255,255,255,255,255,255,255,76,0,0,0,0,0,0,0,0,0,0,28,255,255,255,255, + 255,255,255,255,255,236,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,120,255,255,255,255,255,255,255,255,253,15,0,0,0,0,0,0,0,0,0,0,0, + 220,255,255,255,255,255,255,255,255,167,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,46,255,255,255,255,255,255,255,255,204,0,0,0,0,0,0,0,0, + 0,0,0,0,156,255,255,255,255,255,255,255,255,94,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,1,230,255,255,255,255,255,255,255,139,0,0,0,0, + 0,0,0,0,0,0,0,0,92,255,255,255,255,255,255,255,254,23,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,158,255,255,255,255,255,255,255,73, + 0,0,0,0,0,0,0,0,0,0,0,0,28,255,255,255,255,255,255,255,204,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84,255,255,255,255, + 255,255,252,13,0,0,0,0,0,0,0,0,0,0,0,0,0,220,255,255,255,255,255,255, + 132,0,0,0,0,0,0,0,0,0,0,0,0,0,142,255,255,255,255,255,255,185,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,81,255,255,255,255,255,255,74,0,0,0, + 8,216,255,255,255,255,255,255,95,0,0,0,0,0,0,0,0,0,0,0,0,0,19,233, + 255,255,255,255,255,148,0,0,0,0,0,54,252,255,255,255,255,255,237,22,0,0,0,0, + 0,0,0,0,0,0,0,0,167,255,255,255,255,255,214,9,0,0,0,0,0,0,133,255, + 255,255,255,255,255,168,0,0,0,0,0,0,0,0,0,0,0,81,255,255,255,255,255,249, + 47,0,0,0,0,0,0,0,6,210,255,255,255,255,255,255,76,0,0,0,0,0,0,0, + 0,0,19,233,255,255,255,255,255,116,0,0,0,0,0,0,0,0,0,48,250,255,255,255, + 255,255,227,14,0,0,0,0,0,0,0,0,167,255,255,255,255,255,189,2,0,0,0,0, + 0,0,0,0,0,0,125,255,255,255,255,255,255,149,0,0,0,0,0,0,0,83,255,255, + 255,255,255,237,27,0,0,0,0,0,0,0,0,0,0,0,4,204,255,255,255,255,255,254, + 59,0,0,0,0,0,20,233,255,255,255,255,255,83,0,0,0,0,0,0,0,0,0,0, + 0,0,0,41,248,255,255,255,255,255,216,7,0,0,0,0,168,255,255,255,255,255,156,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,117,255,255,255,255,255,255,131,0,0, + 0,83,255,255,255,255,255,219,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 2,195,255,255,255,255,255,250,45,0,20,233,255,255,255,255,251,54,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,37,245,255,255,255,255,255,201,3,168,255,255,255, + 255,255,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,107,255, + 255,255,255,255,255,179,255,255,255,255,255,196,3,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,1,189,255,255,255,255,255,255,255,255,255,255,242,32,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,242,255,255,255, + 255,255,255,255,255,255,91,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,100,255,255,255,255,255,255,255,255,167,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,181,255,255,255,255,255,255, + 225,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,105,255,255,255,255,255,255,195,2,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,32,241,255,255,255,255,255,255,255,110,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,196,255, + 255,255,255,255,255,255,255,246,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,121,255,255,255,255,255,255,255,255,255,255,200,3,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,251,255,255,255,255,243,255, + 255,255,255,255,255,121,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,11,218,255,255,255,255,254,64,189,255,255,255,255,255,249,45,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,155,255,255,255,255,255,146,0,35,246,255,255,255, + 255,255,206,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,81,255,255,255, + 255,255,220,11,0,0,113,255,255,255,255,255,255,130,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,26,237,255,255,255,255,254,61,0,0,0,3,203,255,255,255,255,255,252, + 52,0,0,0,0,0,0,0,0,0,0,0,0,0,1,184,255,255,255,255,255,144,0,0, + 0,0,0,45,250,255,255,255,255,255,214,8,0,0,0,0,0,0,0,0,0,0,0,0, + 111,255,255,255,255,255,219,10,0,0,0,0,0,0,130,255,255,255,255,255,255,139,0,0, + 0,0,0,0,0,0,0,0,0,45,248,255,255,255,255,254,61,0,0,0,0,0,0,0, + 7,213,255,255,255,255,255,254,61,0,0,0,0,0,0,0,0,0,8,211,255,255,255,255, + 255,144,0,0,0,0,0,0,0,0,0,58,254,255,255,255,255,255,220,11,0,0,0,0, + 0,0,0,0,145,255,255,255,255,255,219,10,0,0,0,0,0,0,0,0,0,0,145,255, + 255,255,255,255,255,150,0,0,0,0,0,0,0,72,255,255,255,255,255,253,58,0,0,0, + 0,0,0,0,0,0,0,0,11,224,255,255,255,255,255,255,68,0,0,0,0,0,19,230, + 255,255,255,255,255,139,0,0,0,0,0,0,0,0,0,0,0,0,0,70,255,255,255,255, + 255,255,226,14,0,0,0,0,175,255,255,255,255,255,216,8,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,161,255,255,255,255,255,255,159,0,0,0,101,255,255,255,255,255,253, + 56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,233,255,255,255,255,255,255, + 77,0,0,166,255,255,255,255,255,255,216,5,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,15,233,255,255,255,255,255,223,11,0,22,236,255,255,255,255,255,255,115,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,141,255,255,255,255,255,255,71,0,0,0, + 95,255,255,255,255,255,255,242,24,0,0,0,0,0,0,0,0,0,0,0,0,0,40,251, + 255,255,255,255,255,162,0,0,0,0,0,187,255,255,255,255,255,255,162,0,0,0,0,0, + 0,0,0,0,0,0,0,0,186,255,255,255,255,255,236,20,0,0,0,0,0,34,245,255, + 255,255,255,255,255,58,0,0,0,0,0,0,0,0,0,0,0,82,255,255,255,255,255,255, + 91,0,0,0,0,0,0,0,114,255,255,255,255,255,255,207,3,0,0,0,0,0,0,0, + 0,0,9,223,255,255,255,255,255,184,0,0,0,0,0,0,0,0,3,204,255,255,255,255, + 255,255,104,0,0,0,0,0,0,0,0,0,125,255,255,255,255,255,245,31,0,0,0,0, + 0,0,0,0,0,48,251,255,255,255,255,255,237,19,0,0,0,0,0,0,0,30,246,255, + 255,255,255,255,113,0,0,0,0,0,0,0,0,0,0,0,136,255,255,255,255,255,255,151, + 0,0,0,0,0,0,0,172,255,255,255,255,255,203,3,0,0,0,0,0,0,0,0,0, + 0,0,8,220,255,255,255,255,255,253,49,0,0,0,0,0,67,255,255,255,255,255,251,46, + 0,0,0,0,0,0,0,0,0,0,0,0,0,65,255,255,255,255,255,255,197,1,0,0, + 0,4,212,255,255,255,255,255,135,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 156,255,255,255,255,255,255,93,0,0,0,110,255,255,255,255,255,218,8,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,16,232,255,255,255,255,255,231,13,0,22,240,255,255, + 255,255,255,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85,255,255, + 255,255,255,255,140,0,157,255,255,255,255,255,154,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,177,255,255,255,255,255,251,93,254,255,255,255,255,232,16,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,242,255,255,255,255, + 255,255,255,255,255,255,255,84,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,105,255,255,255,255,255,255,255,255,255,255,176,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,195,255,255,255,255,255,255,255, + 255,242,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,40,249,255,255,255,255,255,255,255,105,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,126,255,255,255,255,255,255,195,2,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6, + 255,255,255,255,255,255,78,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,220,220,220,220,220, + 220,220,220,220,220,220,220,220,220,220,220,220,255,255,255,255,255,255,255,207,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,93,255,255,255,255,255,255, + 249,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,242, + 255,255,255,255,255,255,119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,2,192,255,255,255,255,255,255,197,3,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,118,255,255,255,255,255,255,245,37,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,249,255,255,255,255,255,255,106,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,211,255,255,255, + 255,255,255,188,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,142,255,255,255,255,255,255,240,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,67,254,255,255,255,255,255,255,95,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,16,227,255,255,255,255,255,255,176,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,166,255,255,255,255,255,255, + 235,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,90,255, + 255,255,255,255,255,255,83,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,29,240,255,255,255,255,255,255,166,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,2,189,255,255,255,255,255,255,229,17,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,114,255,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,248,255,255,255, + 255,255,255,154,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 7,209,255,255,255,255,255,255,222,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,138,255,255,255,255,255,255,254,63,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,64,254,255,255,255,255,255,255,142,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,224,255,255,255,255,255,255, + 215,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163,255, + 255,255,255,255,255,252,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,85,255,255,255,255,255,255,255,130,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,27,237,255,255,255,255,255,255,205,5,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,1,185,255,255,255,255,255,255,249,44,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,110,255,255,255,255, + 255,255,255,117,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 41,247,255,255,255,255,255,255,196,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,205,255,255,255,255,255,255,255,236,220,220,220,220,220,220,220,220,220, + 220,220,220,220,220,220,220,220,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,128,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,128,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0, + 0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0, + 0,0,255,255,255,255,255,255,230,220,220,220,220,220,220,220,110,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,230,220,220,220,220,220,220,220,110,0,0,0,0,0,0,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,128,0,0,0,0,0,0,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,128,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,128,0,0,0,21,253,255,255,255,255,57,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,193,255,255,255,255,137,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,111,255,255,255,255,217,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 31,255,255,255,255,255,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,207,255, + 255,255,255,122,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,126,255,255,255,255, + 201,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,255,255,255,255,255,27,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,223,255,255,255,255,106,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,142,255,255,255,255,186,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,62,255,255,255,255,250,15,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,3,235,255,255,255,255,90,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,158,255,255,255,255,170,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 78,255,255,255,255,243,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,245,255, + 255,255,255,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,174,255,255,255,255, + 154,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,255,255,255,255,232,2,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,18,252,255,255,255,255,58,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,190,255,255,255,255,138,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,110,255,255,255,255,218,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,31,255,255,255,255,255,42,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,206,255,255,255,255,122,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 126,255,255,255,255,202,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,255,255, + 255,255,255,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,222,255,255,255,255, + 106,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,142,255,255,255,255,186,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,62,255,255,255,255,250,16,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,3,235,255,255,255,255,90,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,158,255,255,255,255,170,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,78,255,255,255,255,243,7,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,9,245,255,255,255,255,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 174,255,255,255,255,154,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,93,255,255, + 255,255,232,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,252,255,255,255,255, + 58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,190,255,255,255,255,139,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,110,255,255,255,255,218,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,30,255,255,255,255,255,42,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,206,255,255,255,255,123,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,126,255,255,255,255,203,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,46,255,255,255,255,255,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 220,255,255,255,255,106,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,72,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,72,0, + 0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,72,0,0,0,0, + 0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,72,0,0,0,0,0,220,220, + 220,220,220,220,220,220,220,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,220,220, + 220,220,220,220,220,220,220,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,2,200,255,255,255,255,228,13,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,255,255,255,255,255,255,144,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,239,255,255,255,255,255,255, + 252,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,160,255,255,255,255,255, + 255,255,255,198,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,62,255,255,255,255, + 242,222,255,255,255,255,101,0,0,0,0,0,0,0,0,0,0,0,0,0,5,212,255,255, + 255,255,92,56,252,255,255,255,236,20,0,0,0,0,0,0,0,0,0,0,0,0,119,255, + 255,255,255,167,0,0,127,255,255,255,255,158,0,0,0,0,0,0,0,0,0,0,0,31, + 245,255,255,255,227,17,0,0,4,198,255,255,255,255,60,0,0,0,0,0,0,0,0,0, + 0,176,255,255,255,254,65,0,0,0,0,34,242,255,255,255,211,5,0,0,0,0,0,0, + 0,0,76,255,255,255,255,138,0,0,0,0,0,0,96,255,255,255,255,117,0,0,0,0, + 0,0,0,10,223,255,255,255,208,7,0,0,0,0,0,0,0,172,255,255,255,244,29,0, + 0,0,0,0,0,133,255,255,255,248,43,0,0,0,0,0,0,0,0,19,228,255,255,255, + 173,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,2,160,255,255,255,255,255,255, + 255,128,0,0,0,0,0,0,0,0,3,165,255,255,255,255,255,255,249,39,0,0,0,0, + 0,0,0,0,4,169,255,255,255,255,255,255,191,1,0,0,0,0,0,0,0,0,5,176, + 255,255,255,255,255,255,94,0,0,0,0,0,0,0,0,0,6,179,255,255,255,255,255,234, + 19,0,0,0,0,0,0,0,0,0,7,183,255,255,255,255,255,157,0,0,0,0,0,0, + 0,0,0,0,8,187,255,255,255,255,255,61,0,0,0,0,0,0,0,0,0,0,10,191, + 255,255,255,255,214,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,43,123,182,221,244,254,250,234,204,155,82,7,0,0, + 0,0,0,0,0,0,0,0,0,0,0,49,190,255,255,255,255,255,255,255,255,255,255,255, + 231,96,0,0,0,0,0,0,0,0,0,0,0,108,251,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,155,2,0,0,0,0,0,0,0,0,108,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,123,0,0,0,0,0,0,0,44,251,255,255,255,255,255, + 255,255,237,222,238,255,255,255,255,255,255,255,249,27,0,0,0,0,0,0,170,255,255,255, + 255,255,248,123,28,0,0,0,41,178,255,255,255,255,255,255,124,0,0,0,0,0,10,248, + 255,255,255,255,255,76,0,0,0,0,0,0,4,205,255,255,255,255,255,192,0,0,0,0, + 0,37,192,192,192,192,192,162,0,0,0,0,0,0,0,0,108,255,255,255,255,255,232,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,75,255,255,255,255, + 255,252,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,41,126,255, + 255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,13,62,106,144,182,220,252, + 255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,22,119,200,253,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,10,136,247,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,27,211,255,255, + 255,255,255,255,255,255,231,193,154,116,132,255,255,255,255,255,255,0,0,0,0,0,6,206, + 255,255,255,255,255,243,141,63,14,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,103,255,255,255,255,255,228,33,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0, + 0,0,0,0,195,255,255,255,255,255,110,0,0,0,0,0,0,0,0,95,255,255,255,255, + 255,255,0,0,0,0,0,242,255,255,255,255,255,77,0,0,0,0,0,0,0,0,176,255, + 255,255,255,255,255,0,0,0,0,0,253,255,255,255,255,255,132,0,0,0,0,0,0,0, + 118,255,255,255,255,255,255,255,0,0,0,0,0,232,255,255,255,255,255,250,110,11,0,0, + 7,69,188,255,255,255,255,255,255,255,255,0,0,0,0,0,172,255,255,255,255,255,255,255, + 252,226,226,251,255,255,255,255,255,255,255,255,255,255,4,0,0,0,0,62,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,177,255,255,255,255,255,255,17,0,0,0,0,0,138, + 255,255,255,255,255,255,255,255,255,255,255,255,161,47,255,255,255,255,255,255,45,0,0,0, + 0,0,0,117,247,255,255,255,255,255,255,255,255,246,118,1,10,254,255,255,255,255,255,88, + 0,0,0,0,0,0,0,25,122,193,234,252,252,232,189,118,22,0,0,0,207,255,255,255, + 255,255,143,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,2,78,162,216,245,254,244,211,152,64,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,51,210,255,255,255,255,255,255,255,255,255,197,43,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,72,248,255,255,255,255,255,255,255, + 255,255,255,255,245,77,0,0,0,0,0,0,0,0,255,255,255,255,255,255,101,241,255,255, + 255,255,255,255,255,255,255,255,255,255,255,249,62,0,0,0,0,0,0,0,255,255,255,255, + 255,255,222,255,255,255,255,241,222,240,255,255,255,255,255,255,255,255,228,13,0,0,0,0, + 0,0,255,255,255,255,255,255,255,255,255,161,46,0,0,0,44,154,254,255,255,255,255,255, + 255,132,0,0,0,0,0,0,255,255,255,255,255,255,255,254,96,0,0,0,0,0,0,0, + 86,253,255,255,255,255,255,239,9,0,0,0,0,0,255,255,255,255,255,255,255,140,0,0, + 0,0,0,0,0,0,0,131,255,255,255,255,255,255,82,0,0,0,0,0,255,255,255,255, + 255,255,244,15,0,0,0,0,0,0,0,0,0,12,240,255,255,255,255,255,151,0,0,0, + 0,0,255,255,255,255,255,255,169,0,0,0,0,0,0,0,0,0,0,0,165,255,255,255, + 255,255,199,0,0,0,0,0,255,255,255,255,255,255,112,0,0,0,0,0,0,0,0,0, + 0,0,110,255,255,255,255,255,231,0,0,0,0,0,255,255,255,255,255,255,83,0,0,0, + 0,0,0,0,0,0,0,0,83,255,255,255,255,255,249,0,0,0,0,0,255,255,255,255, + 255,255,74,0,0,0,0,0,0,0,0,0,0,0,74,255,255,255,255,255,255,0,0,0, + 0,0,255,255,255,255,255,255,83,0,0,0,0,0,0,0,0,0,0,0,83,255,255,255, + 255,255,246,0,0,0,0,0,255,255,255,255,255,255,111,0,0,0,0,0,0,0,0,0, + 0,0,112,255,255,255,255,255,227,0,0,0,0,0,255,255,255,255,255,255,167,0,0,0, + 0,0,0,0,0,0,0,0,169,255,255,255,255,255,190,0,0,0,0,0,255,255,255,255, + 255,255,242,14,0,0,0,0,0,0,0,0,0,13,242,255,255,255,255,255,137,0,0,0, + 0,0,255,255,255,255,255,255,255,136,0,0,0,0,0,0,0,0,0,134,255,255,255,255, + 255,255,65,0,0,0,0,0,255,255,255,255,255,255,255,254,92,0,0,0,0,0,0,0, + 84,253,255,255,255,255,255,223,2,0,0,0,0,0,255,255,255,255,255,255,255,255,255,160, + 46,1,0,0,40,152,254,255,255,255,255,255,255,104,0,0,0,0,0,0,255,255,255,255, + 255,255,241,255,255,255,255,241,222,239,255,255,255,255,255,255,255,255,205,4,0,0,0,0, + 0,0,255,255,255,255,255,255,124,253,255,255,255,255,255,255,255,255,255,255,255,255,255,235, + 36,0,0,0,0,0,0,0,255,255,255,255,255,255,72,111,255,255,255,255,255,255,255,255, + 255,255,255,255,229,47,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,87,236, + 255,255,255,255,255,255,255,255,254,166,22,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,14,107,182,228,251,254,235,196,133,41,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,28,113,177,221,246,254,246,223,178,113,28,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,31,164,252,255,255,255,255,255,255,255,255,255,252,161,29,0, + 0,0,0,0,0,0,0,0,0,0,93,244,255,255,255,255,255,255,255,255,255,255,255,255, + 255,241,82,0,0,0,0,0,0,0,0,0,118,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,254,97,0,0,0,0,0,0,0,93,255,255,255,255,255,255,255,255,244, + 224,235,255,255,255,255,255,255,255,251,60,0,0,0,0,0,31,244,255,255,255,255,255,255, + 169,53,2,0,0,29,130,247,255,255,255,255,255,216,4,0,0,0,0,166,255,255,255,255, + 255,255,107,0,0,0,0,0,0,0,54,242,255,255,255,255,255,90,0,0,0,30,253,255, + 255,255,255,255,152,0,0,0,0,0,0,0,0,0,95,255,255,255,255,255,183,0,0,0, + 114,255,255,255,255,255,248,20,0,0,0,0,0,0,0,0,0,3,212,232,232,232,232,220, + 1,0,0,180,255,255,255,255,255,175,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,223,255,255,255,255,255,116,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,247,255,255,255,255,255,84,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,254,255,255,255,255,255,74,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,246,255,255,255,255,255,86,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,219,255,255,255,255,255, + 119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,174,255,255, + 255,255,255,178,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 107,255,255,255,255,255,249,24,0,0,0,0,0,0,0,0,0,3,216,236,236,236,236,224, + 1,0,0,24,250,255,255,255,255,255,157,0,0,0,0,0,0,0,0,0,97,255,255,255, + 255,255,181,0,0,0,0,157,255,255,255,255,255,255,115,0,0,0,0,0,0,0,54,243, + 255,255,255,255,255,89,0,0,0,0,25,240,255,255,255,255,255,255,176,57,2,0,0,30, + 130,248,255,255,255,255,255,213,4,0,0,0,0,0,86,255,255,255,255,255,255,255,255,245, + 224,235,255,255,255,255,255,255,255,250,55,0,0,0,0,0,0,0,113,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,252,87,0,0,0,0,0,0,0,0,0,88,242, + 255,255,255,255,255,255,255,255,255,255,255,255,255,237,75,0,0,0,0,0,0,0,0,0, + 0,0,29,163,252,255,255,255,255,255,255,255,255,255,251,156,24,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,27,112,177,221,246,255,246,223,177,111,25,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,29,122,189, + 231,252,252,233,193,124,27,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,14,151,251,255,255,255,255,255,255,255,255,249,128,3,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,39,221,255,255,255,255,255,255,255,255,255,255,255,255,171,2,255,255, + 255,255,255,255,72,0,0,0,0,0,0,31,230,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,115,255,255,255,255,255,255,72,0,0,0,0,0,3,198,255,255,255,255,255,255, + 255,255,244,224,237,255,255,255,255,239,255,255,255,255,255,255,72,0,0,0,0,0,100,255, + 255,255,255,255,255,255,170,51,1,0,0,30,130,246,255,255,255,255,255,255,255,255,72,0, + 0,0,0,2,222,255,255,255,255,255,255,105,0,0,0,0,0,0,0,48,236,255,255,255, + 255,255,255,255,72,0,0,0,0,64,255,255,255,255,255,255,146,0,0,0,0,0,0,0, + 0,0,70,255,255,255,255,255,255,255,72,0,0,0,0,137,255,255,255,255,255,244,17,0, + 0,0,0,0,0,0,0,0,0,186,255,255,255,255,255,255,72,0,0,0,0,190,255,255, + 255,255,255,170,0,0,0,0,0,0,0,0,0,0,0,96,255,255,255,255,255,255,72,0, + 0,0,0,227,255,255,255,255,255,115,0,0,0,0,0,0,0,0,0,0,0,39,255,255, + 255,255,255,255,72,0,0,0,0,246,255,255,255,255,255,83,0,0,0,0,0,0,0,0, + 0,0,0,11,255,255,255,255,255,255,72,0,0,0,0,255,255,255,255,255,255,74,0,0, + 0,0,0,0,0,0,0,0,0,2,255,255,255,255,255,255,72,0,0,0,0,249,255,255, + 255,255,255,83,0,0,0,0,0,0,0,0,0,0,0,11,255,255,255,255,255,255,72,0, + 0,0,0,231,255,255,255,255,255,112,0,0,0,0,0,0,0,0,0,0,0,43,255,255, + 255,255,255,255,72,0,0,0,0,199,255,255,255,255,255,169,0,0,0,0,0,0,0,0, + 0,0,0,98,255,255,255,255,255,255,72,0,0,0,0,148,255,255,255,255,255,244,16,0, + 0,0,0,0,0,0,0,0,0,189,255,255,255,255,255,255,72,0,0,0,0,79,255,255, + 255,255,255,255,141,0,0,0,0,0,0,0,0,0,71,255,255,255,255,255,255,255,72,0, + 0,0,0,7,237,255,255,255,255,255,255,101,0,0,0,0,0,0,0,47,236,255,255,255, + 255,255,255,255,72,0,0,0,0,0,127,255,255,255,255,255,255,255,169,52,2,0,0,29, + 126,246,255,255,255,255,255,255,255,255,72,0,0,0,0,0,10,222,255,255,255,255,255,255, + 255,255,244,223,234,255,255,255,255,247,255,255,255,255,255,255,72,0,0,0,0,0,0,51, + 245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,124,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,62,238,255,255,255,255,255,255,255,255,255,255,255,255,162,2,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,30,177,255,255,255,255,255,255,255,255,255, + 240,109,1,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,44,136,199, + 235,254,251,227,181,107,15,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,19,100,165,211,240,253,252,236,203,151,80,5,0,0,0,0,0,0,0,0, + 0,0,0,0,0,25,155,248,255,255,255,255,255,255,255,255,255,255,231,109,4,0,0,0, + 0,0,0,0,0,0,0,86,240,255,255,255,255,255,255,255,255,255,255,255,255,255,255,200, + 28,0,0,0,0,0,0,0,0,118,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,224,29,0,0,0,0,0,0,93,255,255,255,255,255,255,255,255,247,224,228,253, + 255,255,255,255,255,255,255,204,6,0,0,0,0,34,245,255,255,255,255,255,255,172,58,3, + 0,0,13,88,217,255,255,255,255,255,255,121,0,0,0,0,167,255,255,255,255,255,255,103, + 0,0,0,0,0,0,0,10,191,255,255,255,255,255,239,11,0,0,30,253,255,255,255,255, + 255,141,0,0,0,0,0,0,0,0,0,26,245,255,255,255,255,255,93,0,0,116,255,255, + 255,255,255,244,16,0,0,0,0,0,0,0,0,0,0,164,255,255,255,255,255,165,0,0, + 180,255,255,255,255,255,169,0,0,0,0,0,0,0,0,0,0,0,105,255,255,255,255,255, + 214,0,0,223,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,243,0,0,247,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,254,0,0,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,248,0,0,246,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,220,0,0,220,255,255,255,255,255,110,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,176,255,255,255,255,255, + 165,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,109,255,255, + 255,255,255,242,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 24,251,255,255,255,255,255,137,0,0,0,0,0,0,0,0,0,0,133,176,176,176,176,176, + 31,0,0,0,159,255,255,255,255,255,254,102,0,0,0,0,0,0,0,0,101,255,255,255, + 255,255,212,1,0,0,0,25,240,255,255,255,255,255,255,176,63,5,0,0,3,58,165,255, + 255,255,255,255,255,81,0,0,0,0,0,82,255,255,255,255,255,255,255,255,250,226,225,249, + 255,255,255,255,255,255,255,169,0,0,0,0,0,0,0,104,254,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,200,12,0,0,0,0,0,0,0,0,75,235,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,182,14,0,0,0,0,0,0,0,0,0,0,20, + 144,245,255,255,255,255,255,255,255,255,255,255,230,100,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,15,93,161,209,239,253,252,238,205,154,81,5,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,1,67,150,205,238,252,253,234,187,0,0,0,0,0,0,0,0, + 38,198,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,42,238,255,255,255,255,255, + 255,255,255,255,0,0,0,0,0,0,4,209,255,255,255,255,255,255,255,255,255,255,0,0, + 0,0,0,0,85,255,255,255,255,255,255,255,254,229,225,248,0,0,0,0,0,0,171,255, + 255,255,255,255,255,145,20,0,0,6,0,0,0,0,0,0,223,255,255,255,255,255,185,0, + 0,0,0,0,0,0,0,0,0,0,248,255,255,255,255,255,99,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,75,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,128,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,128,0,128,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,128, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,0,128,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,128,0,110,220,220,220,220,255,255,255,255,255,255,231,220, + 220,220,220,110,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,0,23,113,182,227,249,254,237,197,131,34,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,9,137,247,255,255,255,255,255, + 255,255,255,252,141,6,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,29,210,255, + 255,255,255,255,255,255,255,255,255,255,255,184,5,255,255,255,255,255,255,72,0,0,0,0, + 0,0,23,222,255,255,255,255,255,255,255,255,255,255,255,255,255,255,132,255,255,255,255,255, + 255,72,0,0,0,0,0,1,188,255,255,255,255,255,255,255,255,244,224,237,255,255,255,255, + 247,255,255,255,255,255,255,72,0,0,0,0,0,91,255,255,255,255,255,255,255,170,53,1, + 0,0,30,130,246,255,255,255,255,255,255,255,255,72,0,0,0,0,1,216,255,255,255,255, + 255,255,106,0,0,0,0,0,0,0,49,236,255,255,255,255,255,255,255,72,0,0,0,0, + 59,255,255,255,255,255,255,147,0,0,0,0,0,0,0,0,0,70,255,255,255,255,255,255, + 255,72,0,0,0,0,133,255,255,255,255,255,246,18,0,0,0,0,0,0,0,0,0,0, + 188,255,255,255,255,255,255,72,0,0,0,0,189,255,255,255,255,255,171,0,0,0,0,0, + 0,0,0,0,0,0,97,255,255,255,255,255,255,72,0,0,0,0,227,255,255,255,255,255, + 115,0,0,0,0,0,0,0,0,0,0,0,40,255,255,255,255,255,255,72,0,0,0,0, + 246,255,255,255,255,255,83,0,0,0,0,0,0,0,0,0,0,0,11,255,255,255,255,255, + 255,72,0,0,0,0,255,255,255,255,255,255,74,0,0,0,0,0,0,0,0,0,0,0, + 2,255,255,255,255,255,255,72,0,0,0,0,249,255,255,255,255,255,83,0,0,0,0,0, + 0,0,0,0,0,0,11,255,255,255,255,255,255,72,0,0,0,0,231,255,255,255,255,255, + 111,0,0,0,0,0,0,0,0,0,0,0,42,255,255,255,255,255,255,72,0,0,0,0, + 196,255,255,255,255,255,168,0,0,0,0,0,0,0,0,0,0,0,97,255,255,255,255,255, + 255,72,0,0,0,0,144,255,255,255,255,255,244,16,0,0,0,0,0,0,0,0,0,0, + 189,255,255,255,255,255,255,72,0,0,0,0,73,255,255,255,255,255,255,141,0,0,0,0, + 0,0,0,0,0,71,255,255,255,255,255,255,255,72,0,0,0,0,4,230,255,255,255,255, + 255,255,101,0,0,0,0,0,0,0,47,236,255,255,255,255,255,255,255,72,0,0,0,0, + 0,115,255,255,255,255,255,255,255,169,52,2,0,0,29,126,246,255,255,255,255,255,255,255, + 255,72,0,0,0,0,0,6,211,255,255,255,255,255,255,255,255,244,223,234,255,255,255,255, + 234,255,255,255,255,255,255,72,0,0,0,0,0,0,42,240,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,100,255,255,255,255,255,255,72,0,0,0,0,0,0,0,53,233,255, + 255,255,255,255,255,255,255,255,255,255,255,140,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,26,173,255,255,255,255,255,255,255,255,255,235,93,0,0,255,255,255,255,255, + 255,71,0,0,0,0,0,0,0,0,0,0,45,137,201,238,254,249,225,176,101,11,0,0, + 4,255,255,255,255,255,255,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,28,255,255,255,255,255,255,36,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,84,255,255,255,255,255,245,5,0,0,0,0, + 5,100,100,100,100,100,75,0,0,0,0,0,0,0,0,0,0,1,192,255,255,255,255,255, + 181,0,0,0,0,0,0,226,255,255,255,255,250,60,0,0,0,0,0,0,0,0,0,130, + 255,255,255,255,255,255,87,0,0,0,0,0,0,134,255,255,255,255,255,249,145,51,4,0, + 0,0,13,79,190,255,255,255,255,255,255,211,3,0,0,0,0,0,0,17,233,255,255,255, + 255,255,255,255,250,228,222,234,255,255,255,255,255,255,255,255,248,52,0,0,0,0,0,0, + 0,0,61,247,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,251,81,0, + 0,0,0,0,0,0,0,0,0,56,225,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,232,65,0,0,0,0,0,0,0,0,0,0,0,0,13,131,239,255,255,255,255,255, + 255,255,255,255,255,255,242,136,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 8,80,147,196,229,248,254,248,231,200,153,84,10,0,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,28,123,191,233,252,251,228,181,102,10, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,8,140,250,255,255,255,255, + 255,255,255,255,228,68,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,21,201,255, + 255,255,255,255,255,255,255,255,255,255,251,77,0,0,0,0,0,0,0,255,255,255,255,255, + 255,81,204,255,255,255,255,255,255,255,255,255,255,255,255,255,240,25,0,0,0,0,0,0, + 255,255,255,255,255,255,214,255,255,255,255,245,224,236,255,255,255,255,255,255,255,255,143,0, + 0,0,0,0,0,255,255,255,255,255,255,255,255,255,170,56,2,0,0,40,176,255,255,255, + 255,255,255,232,1,0,0,0,0,0,255,255,255,255,255,255,255,253,94,0,0,0,0,0, + 0,2,190,255,255,255,255,255,255,34,0,0,0,0,0,255,255,255,255,255,255,255,126,0, + 0,0,0,0,0,0,0,71,255,255,255,255,255,255,63,0,0,0,0,0,255,255,255,255, + 255,255,230,6,0,0,0,0,0,0,0,0,16,255,255,255,255,255,255,72,0,0,0,0, + 0,255,255,255,255,255,255,148,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255, + 72,0,0,0,0,0,255,255,255,255,255,255,103,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,80,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,3,255,255,255,255,255,255,71,0,0,0,0, + 0,0,39,255,255,255,255,255,255,61,0,0,9,0,0,17,183,255,255,255,255,255,255,27, + 0,0,128,234,223,251,255,255,255,255,255,255,218,0,0,0,128,255,255,255,255,255,255,255, + 255,255,114,0,0,0,128,255,255,255,255,255,255,255,255,201,6,0,0,0,128,255,255,255, + 255,255,255,255,187,19,0,0,0,0,85,209,245,254,244,213,158,68,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,7, + 189,255,255,255,255,255,255,130,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,2,169,255,255,255,255,255,255,142,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,147,255,255,255,255,255,255,155,1,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,122,255,255,255,255,255,255,166,2,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,98,255,255,255,255,255,255,178,5,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,76,252,255,255,255,255,255,187,7,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,58,246,255,255,255,255,255,196,10,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,41,237,255,255,255,255,255,205,15,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,28,226,255,255,255,255,255,212, + 20,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,89,213,255,255,255,255, + 255,221,26,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,238,255,255, + 255,255,255,227,31,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,239,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,255,255,255,255,255,254,87,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,247,255,255,255,255,255,249,65,0,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,104,233,255,255,255,255,255,240,45,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,53,246,255,255,255,255,255,228,29,0,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,79,254,255,255,255,255,255, + 212,17,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,112,255,255, + 255,255,255,255,193,8,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,148,255,255,255,255,255,255,172,2,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,3,180,255,255,255,255,255,255,146,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,12,207,255,255,255,255,255,255,117,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,26,227,255,255,255,255,255,255,91,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,45,243,255,255,255,255,255,250, + 68,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,72,252,255,255, + 255,255,255,242,49,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 102,255,255,255,255,255,255,230,32,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,27,121,191,235,253,249,223,173,89,4,0,0,0,0, + 0,0,6,86,165,218,246,253,241,206,143,46,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,6,134,249,255,255,255,255,255,255,255,255,215,47,0,0,0,0,84,225, + 255,255,255,255,255,255,255,255,255,157,9,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,18,197,255,255,255,255,255,255,255,255,255,255,255,242,50,0,3,148,255,255,255,255,255, + 255,255,255,255,255,255,255,192,7,0,0,0,0,0,0,255,255,255,255,255,255,80,202,255, + 255,255,255,255,255,255,255,255,255,255,255,255,221,9,152,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,138,0,0,0,0,0,0,255,255,255,255,255,255,211,255,255,255,255,245, + 224,239,255,255,255,255,255,255,255,255,184,255,255,255,255,255,236,224,246,255,255,255,255,255, + 255,255,248,23,0,0,0,0,0,255,255,255,255,255,255,255,255,255,163,51,1,0,0,52, + 198,255,255,255,255,255,255,255,255,255,242,122,28,0,0,4,80,230,255,255,255,255,255,255, + 102,0,0,0,0,0,255,255,255,255,255,255,255,251,83,0,0,0,0,0,0,11,222,255, + 255,255,255,255,255,255,221,34,0,0,0,0,0,0,48,254,255,255,255,255,255,155,0,0, + 0,0,0,255,255,255,255,255,255,255,112,0,0,0,0,0,0,0,0,124,255,255,255,255, + 255,255,249,42,0,0,0,0,0,0,0,0,194,255,255,255,255,255,179,0,0,0,0,0, + 255,255,255,255,255,255,222,3,0,0,0,0,0,0,0,0,80,255,255,255,255,255,255,150, + 0,0,0,0,0,0,0,0,0,152,255,255,255,255,255,184,0,0,0,0,0,255,255,255, + 255,255,255,139,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,63,0,0,0, + 0,0,0,0,0,0,144,255,255,255,255,255,184,0,0,0,0,0,255,255,255,255,255,255, + 98,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,17,0,0,0,0,0,0, + 0,0,0,144,255,255,255,255,255,184,0,0,0,0,0,255,255,255,255,255,255,76,0,0, + 0,0,0,0,0,0,0,72,255,255,255,255,255,255,1,0,0,0,0,0,0,0,0,0, + 144,255,255,255,255,255,184,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,144,255,255, + 255,255,255,184,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,144,255,255,255,255,255, + 184,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255, + 255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,144,255,255,255,255,255,184,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255,255,255,255, + 255,255,0,0,0,0,0,0,0,0,0,0,144,255,255,255,255,255,184,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0, + 0,0,0,0,0,0,0,0,0,144,255,255,255,255,255,184,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,144,255,255,255,255,255,184,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0, + 0,0,0,144,255,255,255,255,255,184,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0, + 144,255,255,255,255,255,184,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,144,255,255, + 255,255,255,184,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,144,255,255,255,255,255, + 184,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255, + 255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,144,255,255,255,255,255,184,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255,255,255,255, + 255,255,0,0,0,0,0,0,0,0,0,0,144,255,255,255,255,255,184,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0, + 0,0,0,0,0,0,0,0,0,144,255,255,255,255,255,184,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,28,123,191,233,252,251,228,181,102,10,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,8,140,250,255,255,255,255,255,255,255,255,228,68,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,21,201,255,255,255,255,255,255,255,255, + 255,255,255,251,77,0,0,0,0,0,0,0,255,255,255,255,255,255,81,204,255,255,255,255, + 255,255,255,255,255,255,255,255,255,240,25,0,0,0,0,0,0,255,255,255,255,255,255,214, + 255,255,255,255,245,224,236,255,255,255,255,255,255,255,255,143,0,0,0,0,0,0,255,255, + 255,255,255,255,255,255,255,170,56,2,0,0,40,176,255,255,255,255,255,255,232,1,0,0, + 0,0,0,255,255,255,255,255,255,255,253,94,0,0,0,0,0,0,2,190,255,255,255,255, + 255,255,34,0,0,0,0,0,255,255,255,255,255,255,255,126,0,0,0,0,0,0,0,0, + 71,255,255,255,255,255,255,63,0,0,0,0,0,255,255,255,255,255,255,230,6,0,0,0, + 0,0,0,0,0,16,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255, + 148,0,0,0,0,0,0,0,0,0,1,255,255,255,255,255,255,72,0,0,0,0,0,255, + 255,255,255,255,255,103,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,255,255,255,255,255,255,80,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,0,15,93,161,208,238,252,252,239,209,162,97,16,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,21,145,245,255,255,255,255,255,255,255,255,255,255,246, + 150,23,0,0,0,0,0,0,0,0,0,0,0,0,84,237,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,239,84,0,0,0,0,0,0,0,0,0,0,116,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,117,0,0,0,0,0,0,0,0,95,255, + 255,255,255,255,255,255,255,252,227,226,251,255,255,255,255,255,255,255,255,95,0,0,0,0, + 0,0,34,245,255,255,255,255,255,255,206,82,10,0,0,8,78,201,255,255,255,255,255,255, + 245,34,0,0,0,0,0,172,255,255,255,255,255,255,152,4,0,0,0,0,0,0,3,144, + 255,255,255,255,255,255,172,0,0,0,0,33,254,255,255,255,255,255,175,1,0,0,0,0, + 0,0,0,0,1,172,255,255,255,255,255,254,33,0,0,0,120,255,255,255,255,255,249,28, + 0,0,0,0,0,0,0,0,0,0,25,249,255,255,255,255,255,119,0,0,0,182,255,255, + 255,255,255,176,0,0,0,0,0,0,0,0,0,0,0,0,175,255,255,255,255,255,181,0, + 0,0,224,255,255,255,255,255,114,0,0,0,0,0,0,0,0,0,0,0,0,114,255,255, + 255,255,255,224,0,0,0,248,255,255,255,255,255,83,0,0,0,0,0,0,0,0,0,0, + 0,0,83,255,255,255,255,255,248,0,0,0,255,255,255,255,255,255,75,0,0,0,0,0, + 0,0,0,0,0,0,0,76,255,255,255,255,255,254,0,0,0,245,255,255,255,255,255,89, + 0,0,0,0,0,0,0,0,0,0,0,0,89,255,255,255,255,255,245,0,0,0,218,255, + 255,255,255,255,128,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,218, + 0,0,0,173,255,255,255,255,255,197,0,0,0,0,0,0,0,0,0,0,0,0,194,255, + 255,255,255,255,171,0,0,0,105,255,255,255,255,255,255,50,0,0,0,0,0,0,0,0, + 0,0,44,255,255,255,255,255,255,103,0,0,0,21,249,255,255,255,255,255,200,5,0,0, + 0,0,0,0,0,0,3,194,255,255,255,255,255,248,19,0,0,0,0,154,255,255,255,255, + 255,255,170,8,0,0,0,0,0,0,7,163,255,255,255,255,255,255,148,0,0,0,0,0, + 23,237,255,255,255,255,255,255,215,90,13,0,0,12,88,213,255,255,255,255,255,255,234,20, + 0,0,0,0,0,0,77,254,255,255,255,255,255,255,255,253,230,227,253,255,255,255,255,255, + 255,255,252,69,0,0,0,0,0,0,0,0,98,253,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,251,91,0,0,0,0,0,0,0,0,0,0,72,233,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,229,65,0,0,0,0,0,0,0,0,0,0,0,0, + 17,139,243,255,255,255,255,255,255,255,255,255,255,242,135,15,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,12,90,159,207,238,253,253,238,207,159,89,12,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,8,91,170,219,247,254,241,207, + 148,58,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,83,229,255, + 255,255,255,255,255,255,255,255,189,37,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,125,255,255,255,255,255,255,255,255,255,255,255,255,243,70,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,157,255,255,255,255,255,255,255,255,255,255,255,255,255,255,247,55, + 0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,241,222,240,255,255,255, + 255,255,255,255,255,224,11,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,161,46, + 0,0,0,44,154,254,255,255,255,255,255,255,128,0,0,0,0,0,0,255,255,255,255,255, + 255,255,254,96,0,0,0,0,0,0,0,86,253,255,255,255,255,255,237,8,0,0,0,0, + 0,255,255,255,255,255,255,255,140,0,0,0,0,0,0,0,0,0,131,255,255,255,255,255, + 255,80,0,0,0,0,0,255,255,255,255,255,255,244,15,0,0,0,0,0,0,0,0,0, + 12,240,255,255,255,255,255,148,0,0,0,0,0,255,255,255,255,255,255,169,0,0,0,0, + 0,0,0,0,0,0,0,165,255,255,255,255,255,199,0,0,0,0,0,255,255,255,255,255, + 255,112,0,0,0,0,0,0,0,0,0,0,0,110,255,255,255,255,255,231,0,0,0,0, + 0,255,255,255,255,255,255,83,0,0,0,0,0,0,0,0,0,0,0,83,255,255,255,255, + 255,249,0,0,0,0,0,255,255,255,255,255,255,74,0,0,0,0,0,0,0,0,0,0, + 0,74,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,83,0,0,0,0, + 0,0,0,0,0,0,0,83,255,255,255,255,255,246,0,0,0,0,0,255,255,255,255,255, + 255,111,0,0,0,0,0,0,0,0,0,0,0,112,255,255,255,255,255,227,0,0,0,0, + 0,255,255,255,255,255,255,167,0,0,0,0,0,0,0,0,0,0,0,169,255,255,255,255, + 255,190,0,0,0,0,0,255,255,255,255,255,255,242,14,0,0,0,0,0,0,0,0,0, + 13,242,255,255,255,255,255,137,0,0,0,0,0,255,255,255,255,255,255,255,136,0,0,0, + 0,0,0,0,0,0,134,255,255,255,255,255,255,61,0,0,0,0,0,255,255,255,255,255, + 255,255,254,92,0,0,0,0,0,0,0,84,253,255,255,255,255,255,220,2,0,0,0,0, + 0,255,255,255,255,255,255,255,255,255,160,46,1,0,0,40,152,254,255,255,255,255,255,255, + 100,0,0,0,0,0,0,255,255,255,255,255,255,247,255,255,255,255,241,222,239,255,255,255, + 255,255,255,255,255,199,3,0,0,0,0,0,0,255,255,255,255,255,255,136,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,232,31,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,127,255,255,255,255,255,255,255,255,255,255,255,255,225,43,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,102,242,255,255,255,255,255,255,255,255,253,161,19,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,19,115,187,231,252,252,234,193, + 129,35,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,24,115,184,228,249,254,238,200,135,40,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,11,141,248,255,255,255,255,255,255,255,255,254,156,12,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,34,215,255,255,255,255,255,255,255,255, + 255,255,255,255,205,13,255,255,255,255,255,255,72,0,0,0,0,0,0,26,226,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,162,255,255,255,255,255,255,72,0,0,0,0,0, + 2,193,255,255,255,255,255,255,255,255,243,223,236,255,255,255,255,255,255,255,255,255,255,255, + 72,0,0,0,0,0,96,255,255,255,255,255,255,255,160,48,1,0,0,29,125,245,255,255, + 255,255,255,255,255,255,72,0,0,0,0,1,219,255,255,255,255,255,254,92,0,0,0,0, + 0,0,0,43,234,255,255,255,255,255,255,255,72,0,0,0,0,61,255,255,255,255,255,255, + 137,0,0,0,0,0,0,0,0,0,67,255,255,255,255,255,255,255,72,0,0,0,0,136, + 255,255,255,255,255,242,14,0,0,0,0,0,0,0,0,0,0,184,255,255,255,255,255,255, + 72,0,0,0,0,190,255,255,255,255,255,169,0,0,0,0,0,0,0,0,0,0,0,93, + 255,255,255,255,255,255,72,0,0,0,0,227,255,255,255,255,255,112,0,0,0,0,0,0, + 0,0,0,0,0,39,255,255,255,255,255,255,72,0,0,0,0,246,255,255,255,255,255,83, + 0,0,0,0,0,0,0,0,0,0,0,11,255,255,255,255,255,255,72,0,0,0,0,255, + 255,255,255,255,255,74,0,0,0,0,0,0,0,0,0,0,0,2,255,255,255,255,255,255, + 72,0,0,0,0,249,255,255,255,255,255,83,0,0,0,0,0,0,0,0,0,0,0,11, + 255,255,255,255,255,255,72,0,0,0,0,231,255,255,255,255,255,111,0,0,0,0,0,0, + 0,0,0,0,0,41,255,255,255,255,255,255,72,0,0,0,0,197,255,255,255,255,255,165, + 0,0,0,0,0,0,0,0,0,0,0,97,255,255,255,255,255,255,72,0,0,0,0,147, + 255,255,255,255,255,242,13,0,0,0,0,0,0,0,0,0,0,187,255,255,255,255,255,255, + 72,0,0,0,0,78,255,255,255,255,255,255,134,0,0,0,0,0,0,0,0,0,67,255, + 255,255,255,255,255,255,72,0,0,0,0,7,234,255,255,255,255,255,253,90,0,0,0,0, + 0,0,0,43,234,255,255,255,255,255,255,255,72,0,0,0,0,0,121,255,255,255,255,255, + 255,255,159,48,1,0,0,27,122,244,255,255,255,255,255,255,255,255,72,0,0,0,0,0, + 8,218,255,255,255,255,255,255,255,255,243,223,234,255,255,255,255,255,255,255,255,255,255,255, + 72,0,0,0,0,0,0,46,242,255,255,255,255,255,255,255,255,255,255,255,255,255,255,168, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,55,234,255,255,255,255,255,255,255,255, + 255,255,255,255,193,11,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,24,169,255, + 255,255,255,255,255,255,255,255,248,133,6,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,41,130,195,235,252,252,231,187,117,24,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,28,138,211,248,255,128,0,0,0,0,255,255,255,255,255,255,72,0,95,245, + 255,255,255,255,128,0,0,0,0,255,255,255,255,255,255,72,96,255,255,255,255,255,255,128, + 0,0,0,0,255,255,255,255,255,255,107,246,255,255,255,255,255,255,128,0,0,0,0,255, + 255,255,255,255,255,228,255,255,255,255,255,255,255,128,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,128,0,0,0,0,255,255,255,255,255,255,255,255,244,143,65, + 21,3,3,10,0,0,0,0,255,255,255,255,255,255,255,219,33,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,252,44,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,180,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,120, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,90,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,74,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0, + 0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,112,173,215,241,253,252, + 240,211,167,102,20,0,0,0,0,0,0,0,0,0,0,25,163,253,255,255,255,255,255,255, + 255,255,255,255,248,145,16,0,0,0,0,0,0,0,50,234,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,224,42,0,0,0,0,0,20,232,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,232,27,0,0,0,0,135,255,255,255,255,255,255,255,247,225,227, + 250,255,255,255,255,255,255,255,172,0,0,0,0,215,255,255,255,255,255,214,66,3,0,0, + 5,64,193,255,255,255,255,255,255,33,0,0,0,250,255,255,255,255,255,45,0,0,0,0, + 0,0,5,201,255,255,255,255,255,104,0,0,0,248,255,255,255,255,255,11,0,0,0,0, + 0,0,0,67,152,152,152,152,152,82,0,0,0,206,255,255,255,255,255,133,1,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,112,255,255,255,255,255,255,202,103,32,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,6,205,255,255,255,255,255,255,255,255,230, + 174,118,62,8,0,0,0,0,0,0,0,0,0,0,21,200,255,255,255,255,255,255,255,255, + 255,255,255,247,178,78,1,0,0,0,0,0,0,0,0,4,109,229,255,255,255,255,255,255, + 255,255,255,255,255,255,202,40,0,0,0,0,0,0,0,0,0,4,74,151,215,255,255,255, + 255,255,255,255,255,255,255,241,54,0,0,0,0,0,0,0,0,0,0,0,0,17,74,130, + 187,244,255,255,255,255,255,255,228,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,6,84,226,255,255,255,255,255,126,0,2,72,72,72,72,72,64,0,0,0,0,0,0, + 0,0,0,55,255,255,255,255,255,211,0,0,243,255,255,255,255,252,20,0,0,0,0,0, + 0,0,0,6,255,255,255,255,255,249,0,0,188,255,255,255,255,255,165,2,0,0,0,0, + 0,0,0,72,255,255,255,255,255,250,0,0,98,255,255,255,255,255,255,198,80,13,0,0, + 0,15,99,237,255,255,255,255,255,212,0,0,6,215,255,255,255,255,255,255,255,255,233,221, + 231,254,255,255,255,255,255,255,255,133,0,0,0,46,241,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,230,18,0,0,0,0,48,223,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,232,48,0,0,0,0,0,0,11,131,239,255,255,255,255,255,255, + 255,255,255,255,255,251,157,22,0,0,0,0,0,0,0,0,0,10,82,147,197,229,248,255, + 251,235,208,164,103,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,49,140,57,0, + 0,0,0,0,0,0,0,0,0,0,0,0,31,121,213,255,255,72,0,0,0,0,0,0, + 0,0,0,0,0,0,194,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, + 255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,128,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,0,128,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,0,128,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,0,128,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,110, + 220,220,220,220,255,255,255,255,255,255,231,220,220,220,220,220,220,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0, + 0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,72,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,73,0, + 0,0,0,0,0,0,0,0,0,0,0,245,255,255,255,255,255,96,0,0,0,0,0,0, + 0,0,0,0,0,0,217,255,255,255,255,255,179,0,0,0,0,0,0,0,0,0,0,0, + 0,165,255,255,255,255,255,255,145,23,0,0,1,41,0,0,0,0,0,0,84,255,255,255, + 255,255,255,255,255,231,222,244,255,0,0,0,0,0,0,4,214,255,255,255,255,255,255,255, + 255,255,255,255,0,0,0,0,0,0,0,47,242,255,255,255,255,255,255,255,255,255,255,0, + 0,0,0,0,0,0,0,47,210,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0, + 0,0,0,2,76,156,209,240,252,253,238,204,143,0,0,0,0,255,255,255,255,255,255,72, + 0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255, + 255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0, + 0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255,255,255,255, + 255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0, + 72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0, + 0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255, + 72,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255, + 255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0, + 0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255,255,255, + 255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0, + 0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0, + 0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255, + 255,72,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0,0,0,0,0,0, + 255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255,255,255,255,255,255,0, + 0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0,0,0,72,255,255, + 255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0,0,0,0,0,0, + 0,0,80,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,255,72,0,0, + 0,0,0,0,0,0,0,105,255,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255, + 255,255,73,0,0,0,0,0,0,0,0,0,152,255,255,255,255,255,255,0,0,0,0,0, + 0,255,255,255,255,255,255,88,0,0,0,0,0,0,0,0,9,236,255,255,255,255,255,255, + 0,0,0,0,0,0,246,255,255,255,255,255,144,0,0,0,0,0,0,0,0,134,255,255, + 255,255,255,255,255,0,0,0,0,0,0,213,255,255,255,255,255,239,25,0,0,0,0,0, + 0,94,254,255,255,255,255,255,255,255,0,0,0,0,0,0,152,255,255,255,255,255,255,216, + 65,2,0,1,47,162,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,56,255,255, + 255,255,255,255,255,255,242,222,242,255,255,255,255,213,255,255,255,255,255,255,0,0,0,0, + 0,0,0,173,255,255,255,255,255,255,255,255,255,255,255,255,255,207,83,255,255,255,255,255, + 255,0,0,0,0,0,0,0,18,215,255,255,255,255,255,255,255,255,255,255,255,207,23,72, + 255,255,255,255,255,255,0,0,0,0,0,0,0,0,19,179,255,255,255,255,255,255,255,255, + 251,144,9,0,72,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,66,161,220, + 249,254,236,196,128,32,0,0,0,72,255,255,255,255,255,255,0,0,0,147,255,255,255,255, + 255,235,4,0,0,0,0,0,0,0,0,0,0,7,241,255,255,255,255,255,86,45,255,255, + 255,255,255,255,70,0,0,0,0,0,0,0,0,0,0,79,255,255,255,255,255,235,7,0, + 201,255,255,255,255,255,157,0,0,0,0,0,0,0,0,0,0,166,255,255,255,255,255,141, + 0,0,101,255,255,255,255,255,238,6,0,0,0,0,0,0,0,0,9,244,255,255,255,255, + 255,40,0,0,13,244,255,255,255,255,255,76,0,0,0,0,0,0,0,0,84,255,255,255, + 255,255,195,0,0,0,0,156,255,255,255,255,255,163,0,0,0,0,0,0,0,0,172,255, + 255,255,255,255,94,0,0,0,0,56,255,255,255,255,255,242,8,0,0,0,0,0,0,12, + 246,255,255,255,255,240,10,0,0,0,0,0,212,255,255,255,255,255,82,0,0,0,0,0, + 0,89,255,255,255,255,255,148,0,0,0,0,0,0,111,255,255,255,255,255,168,0,0,0, + 0,0,0,176,255,255,255,255,255,49,0,0,0,0,0,0,19,248,255,255,255,255,245,11, + 0,0,0,0,15,249,255,255,255,255,204,0,0,0,0,0,0,0,0,167,255,255,255,255, + 255,87,0,0,0,0,94,255,255,255,255,255,103,0,0,0,0,0,0,0,0,66,255,255, + 255,255,255,174,0,0,0,0,182,255,255,255,255,244,14,0,0,0,0,0,0,0,0,1, + 221,255,255,255,255,247,13,0,0,18,251,255,255,255,255,158,0,0,0,0,0,0,0,0, + 0,0,122,255,255,255,255,255,92,0,0,100,255,255,255,255,255,58,0,0,0,0,0,0, + 0,0,0,0,25,252,255,255,255,255,179,0,0,188,255,255,255,255,212,0,0,0,0,0, + 0,0,0,0,0,0,0,178,255,255,255,255,249,17,22,252,255,255,255,255,112,0,0,0, + 0,0,0,0,0,0,0,0,0,77,255,255,255,255,255,98,106,255,255,255,255,248,19,0, + 0,0,0,0,0,0,0,0,0,0,0,4,229,255,255,255,255,184,192,255,255,255,255,167, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,132,255,255,255,255,252,254,255,255,255, + 255,66,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,254,255,255,255,255,255,255, + 255,255,221,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,188,255,255,255,255, + 255,255,255,255,122,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,87,255,255, + 255,255,255,255,255,252,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7, + 236,255,255,255,255,255,255,175,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,142,255,255,255,255,255,255,75,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,42,255,255,255,255,255,227,3,0,0,0,0,0,0,0,0,0,24,254,255, + 255,255,255,255,59,0,0,0,0,0,0,0,0,161,255,255,255,255,255,45,0,0,0,0, + 0,0,0,0,174,255,255,255,255,255,122,0,201,255,255,255,255,255,118,0,0,0,0,0, + 0,0,2,233,255,255,255,255,255,121,0,0,0,0,0,0,0,0,232,255,255,255,255,255, + 45,0,125,255,255,255,255,255,178,0,0,0,0,0,0,0,52,255,255,255,255,255,255,196, + 0,0,0,0,0,0,0,37,255,255,255,255,255,224,0,0,48,255,255,255,255,255,237,1, + 0,0,0,0,0,0,127,255,255,255,255,255,255,253,19,0,0,0,0,0,0,96,255,255, + 255,255,255,148,0,0,1,226,255,255,255,255,255,43,0,0,0,0,0,0,201,255,255,255, + 255,255,255,255,90,0,0,0,0,0,0,155,255,255,255,255,255,71,0,0,0,150,255,255, + 255,255,255,102,0,0,0,0,0,21,254,255,255,255,255,255,255,255,166,0,0,0,0,0, + 0,216,255,255,255,255,243,7,0,0,0,74,255,255,255,255,255,162,0,0,0,0,0,93, + 255,255,255,255,255,255,255,255,237,3,0,0,0,0,19,255,255,255,255,255,174,0,0,0, + 0,9,245,255,255,255,255,222,0,0,0,0,0,167,255,255,255,255,213,255,255,255,255,60, + 0,0,0,0,78,255,255,255,255,255,97,0,0,0,0,0,176,255,255,255,255,255,27,0, + 0,0,3,237,255,255,255,211,111,255,255,255,255,135,0,0,0,0,138,255,255,255,255,254, + 23,0,0,0,0,0,100,255,255,255,255,255,87,0,0,0,59,255,255,255,255,135,34,255, + 255,255,255,210,0,0,0,0,197,255,255,255,255,199,0,0,0,0,0,0,24,255,255,255, + 255,255,147,0,0,0,132,255,255,255,255,59,0,214,255,255,255,255,30,0,0,8,248,255, + 255,255,255,123,0,0,0,0,0,0,0,204,255,255,255,255,207,0,0,0,206,255,255,255, + 236,3,0,138,255,255,255,255,104,0,0,59,255,255,255,255,255,46,0,0,0,0,0,0, + 0,126,255,255,255,255,253,14,0,25,255,255,255,255,163,0,0,62,255,255,255,255,180,0, + 0,118,255,255,255,255,225,0,0,0,0,0,0,0,0,50,255,255,255,255,255,70,0,98, + 255,255,255,255,86,0,0,4,238,255,255,255,246,9,0,178,255,255,255,255,150,0,0,0, + 0,0,0,0,0,1,229,255,255,255,255,131,0,172,255,255,255,252,16,0,0,0,166,255, + 255,255,255,74,1,237,255,255,255,255,72,0,0,0,0,0,0,0,0,0,152,255,255,255, + 255,191,4,241,255,255,255,191,0,0,0,0,89,255,255,255,255,149,40,255,255,255,255,245, + 7,0,0,0,0,0,0,0,0,0,76,255,255,255,255,246,69,255,255,255,255,115,0,0, + 0,0,17,252,255,255,255,224,101,255,255,255,255,175,0,0,0,0,0,0,0,0,0,0, + 9,246,255,255,255,255,192,255,255,255,255,38,0,0,0,0,0,192,255,255,255,255,204,255, + 255,255,255,99,0,0,0,0,0,0,0,0,0,0,0,178,255,255,255,255,255,255,255,255, + 218,0,0,0,0,0,0,116,255,255,255,255,255,255,255,255,254,24,0,0,0,0,0,0, + 0,0,0,0,0,102,255,255,255,255,255,255,255,255,142,0,0,0,0,0,0,40,255,255, + 255,255,255,255,255,255,202,0,0,0,0,0,0,0,0,0,0,0,0,26,255,255,255,255, + 255,255,255,255,66,0,0,0,0,0,0,0,218,255,255,255,255,255,255,255,125,0,0,0, + 0,0,0,0,0,0,0,0,0,0,204,255,255,255,255,255,255,240,6,0,0,0,0,0, + 0,0,143,255,255,255,255,255,255,255,48,0,0,0,0,0,0,0,0,0,0,0,0,0, + 128,255,255,255,255,255,255,170,0,0,0,0,0,0,0,0,67,255,255,255,255,255,255,226, + 1,0,0,0,0,0,0,0,0,0,0,0,0,0,51,255,255,255,255,255,255,95,0,0, + 0,0,0,0,0,0,6,241,255,255,255,255,255,150,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,229,255,255,255,255,253,21,0,0,0,0,0,0,0,0,0,170,255,255, + 255,255,255,74,0,0,0,0,0,0,0,0,0,85,255,255,255,255,255,255,131,0,0,0, + 0,0,0,0,0,0,133,255,255,255,255,255,249,51,0,0,0,0,154,255,255,255,255,255, + 251,51,0,0,0,0,0,0,0,57,253,255,255,255,255,255,108,0,0,0,0,0,9,211, + 255,255,255,255,255,211,7,0,0,0,0,0,11,219,255,255,255,255,255,173,0,0,0,0, + 0,0,0,43,245,255,255,255,255,255,133,0,0,0,0,0,150,255,255,255,255,255,223,16, + 0,0,0,0,0,0,0,0,100,255,255,255,255,255,252,52,0,0,0,72,255,255,255,255, + 255,250,54,0,0,0,0,0,0,0,0,0,0,166,255,255,255,255,255,214,8,0,19,230, + 255,255,255,255,255,112,0,0,0,0,0,0,0,0,0,0,0,14,221,255,255,255,255,255, + 139,0,170,255,255,255,255,255,175,1,0,0,0,0,0,0,0,0,0,0,0,0,51,250, + 255,255,255,255,253,142,255,255,255,255,255,223,18,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,113,255,255,255,255,255,255,255,255,255,255,251,57,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,1,179,255,255,255,255,255,255,255,255,255,116,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,19,228,255,255,255,255,255,255,255,179,1,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,255,255, + 226,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,67,255,255, + 255,255,255,255,187,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 26,233,255,255,255,255,255,255,255,114,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,3,193,255,255,255,255,255,255,255,255,250,52,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,133,255,255,255,255,255,255,255,255,255,255,221,14,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,73,254,255,255,255,255,230,189,255,255,255,255,255,166, + 0,0,0,0,0,0,0,0,0,0,0,0,0,28,235,255,255,255,255,255,72,27,240,255, + 255,255,255,255,97,0,0,0,0,0,0,0,0,0,0,0,5,198,255,255,255,255,255,145, + 0,0,94,255,255,255,255,255,245,40,0,0,0,0,0,0,0,0,0,0,139,255,255,255, + 255,255,211,8,0,0,0,176,255,255,255,255,255,209,8,0,0,0,0,0,0,0,0,77, + 255,255,255,255,255,249,46,0,0,0,0,24,237,255,255,255,255,255,148,0,0,0,0,0, + 0,0,33,238,255,255,255,255,255,113,0,0,0,0,0,0,88,255,255,255,255,255,255,80, + 0,0,0,0,0,6,202,255,255,255,255,255,188,1,0,0,0,0,0,0,0,172,255,255, + 255,255,255,238,29,0,0,0,0,146,255,255,255,255,255,237,26,0,0,0,0,0,0,0, + 0,21,233,255,255,255,255,255,195,4,0,0,84,255,255,255,255,255,255,83,0,0,0,0, + 0,0,0,0,0,0,82,255,255,255,255,255,255,130,0,144,255,255,255,255,255,244,12,0, + 0,0,0,0,0,0,0,0,0,24,252,255,255,255,255,255,83,42,255,255,255,255,255,255, + 94,0,0,0,0,0,0,0,0,0,0,116,255,255,255,255,255,227,4,0,194,255,255,255, + 255,255,188,0,0,0,0,0,0,0,0,0,0,209,255,255,255,255,255,124,0,0,91,255, + 255,255,255,255,254,30,0,0,0,0,0,0,0,0,48,255,255,255,255,255,250,23,0,0, + 8,236,255,255,255,255,255,124,0,0,0,0,0,0,0,0,144,255,255,255,255,255,167,0, + 0,0,0,142,255,255,255,255,255,218,0,0,0,0,0,0,0,5,234,255,255,255,255,255, + 59,0,0,0,0,39,255,255,255,255,255,255,56,0,0,0,0,0,0,76,255,255,255,255, + 255,207,0,0,0,0,0,0,191,255,255,255,255,255,152,0,0,0,0,0,0,172,255,255, + 255,255,255,101,0,0,0,0,0,0,88,255,255,255,255,255,240,8,0,0,0,0,19,249, + 255,255,255,255,239,10,0,0,0,0,0,0,7,235,255,255,255,255,255,84,0,0,0,0, + 108,255,255,255,255,255,143,0,0,0,0,0,0,0,0,138,255,255,255,255,255,180,0,0, + 0,0,200,255,255,255,255,254,37,0,0,0,0,0,0,0,0,36,255,255,255,255,255,252, + 24,0,0,40,255,255,255,255,255,185,0,0,0,0,0,0,0,0,0,0,188,255,255,255, + 255,255,115,0,0,136,255,255,255,255,255,77,0,0,0,0,0,0,0,0,0,0,85,255, + 255,255,255,255,209,0,2,229,255,255,255,255,223,3,0,0,0,0,0,0,0,0,0,0, + 5,232,255,255,255,255,255,48,68,255,255,255,255,255,120,0,0,0,0,0,0,0,0,0, + 0,0,0,134,255,255,255,255,255,144,164,255,255,255,255,248,20,0,0,0,0,0,0,0, + 0,0,0,0,0,34,254,255,255,255,255,236,246,255,255,255,255,161,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,186,255,255,255,255,255,255,255,255,255,255,54,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,83,255,255,255,255,255,255,255,255,255,204,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,4,230,255,255,255,255,255,255,255,255,96,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132,255,255,255,255,255,255,255,236, + 8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,253,255,255,255,255,255, + 255,137,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,181,255,255,255, + 255,255,254,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,95,255, + 255,255,255,255,179,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 167,255,255,255,255,255,71,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,27,251,255,255,255,255,220,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,134,255,255,255,255,255,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,10,237,255,255,255,255,246,17,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,103,255,255,255,255,255,156,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,1,213,255,255,255,255,255,49,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,70,255,255,255,255,255,197,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,183,255,255,255,255,255,90,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,40,255,255,255,255,255,234,7,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,153,255,255,255,255,255,132,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,246,255,255,255,255,252,29,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,220,220,220,220,220, + 220,220,220,220,220,220,220,255,255,255,255,255,255,255,217,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,105,255,255,255,255,255,255,244,42,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,52,249,255,255,255,255,255,255,85,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,18,223,255,255,255,255,255,255,137,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,1,180,255,255,255,255,255,255,189,3,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,122,255,255,255,255,255,255,226,21,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,65,253,255,255,255,255,255,248,52,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,26,232,255,255,255,255,255,255,99,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,4,194,255,255,255,255,255,255,154,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,139,255,255,255,255,255,255,201,7,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,80,255,255,255,255,255,255,234,29,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,35,240,255,255,255,255,255,252,65,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,8,207,255,255,255,255,255,255,116,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,154,255,255,255,255,255,255,170,1,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,94,255,255,255,255,255,255,212,12,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,45,246,255,255,255,255,255,240,38,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,218,255,255,255,255,255,255,244,220,220,220,220,220, + 220,220,220,220,220,220,220,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,23, + 117,185,225,249,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,113,246,255,255,255, + 255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,133,255,255,255,255,255,255,255,255, + 255,255,255,0,0,0,0,0,0,0,0,49,254,255,255,255,255,255,255,255,255,255,255,255, + 0,0,0,0,0,0,0,0,148,255,255,255,255,255,255,253,225,220,220,220,220,0,0,0, + 0,0,0,0,0,200,255,255,255,255,255,173,20,0,0,0,0,0,0,0,0,0,0,0, + 0,0,216,255,255,255,255,244,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,200, + 255,255,255,255,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,161,255,255,255, + 255,238,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,255,255,255,255,255,43, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,255,255,255,255,255,104,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,6,248,255,255,255,255,167,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,201,255,255,255,255,230,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,146,255,255,255,255,255,34,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,111,255,255,255,255,255,79,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 118,255,255,255,255,255,89,0,0,0,0,0,0,0,0,0,0,0,0,0,2,198,255,255, + 255,255,255,56,0,0,0,0,0,0,0,0,0,0,0,9,53,174,255,255,255,255,255,220, + 3,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,250,64,0,0,0, + 0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,225,67,0,0,0,0,0,0,0, + 0,0,0,0,255,255,255,255,255,255,255,207,32,0,0,0,0,0,0,0,0,0,0,0, + 0,255,255,255,255,255,255,255,255,245,96,0,0,0,0,0,0,0,0,0,0,0,220,220, + 229,254,255,255,255,255,255,255,85,0,0,0,0,0,0,0,0,0,0,0,0,0,18,136, + 255,255,255,255,255,231,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,180,255,255, + 255,255,255,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,114,255,255,255,255,255, + 89,0,0,0,0,0,0,0,0,0,0,0,0,0,0,114,255,255,255,255,255,75,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,151,255,255,255,255,255,30,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,204,255,255,255,255,225,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,8,251,255,255,255,255,164,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,57,255,255,255,255,255,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112, + 255,255,255,255,255,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,165,255,255,255, + 255,237,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,203,255,255,255,255,211,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,215,255,255,255,255,244,12,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,198,255,255,255,255,255,174,20,0,0,0,0,0, + 0,0,0,0,0,0,0,0,143,255,255,255,255,255,255,253,224,220,220,220,220,0,0,0, + 0,0,0,0,0,45,253,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0, + 0,0,0,129,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0, + 0,110,245,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,22, + 116,183,226,249,255,255,255,255,255,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0, + 0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255, + 255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255, + 255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255, + 0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0, + 0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0, + 0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0, + 255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255, + 255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255, + 255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0, + 0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0, + 0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0, + 0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255, + 255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255, + 255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255, + 0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0, + 0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0, + 0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0, + 255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255, + 255,255,255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255, + 255,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,255,255,255,255,255,249, + 227,185,120,26,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255, + 247,120,1,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,140, + 0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,57,0,0, + 0,0,0,0,0,0,220,220,220,220,224,253,255,255,255,255,255,255,157,0,0,0,0,0, + 0,0,0,0,0,0,0,0,20,171,255,255,255,255,255,212,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,11,244,255,255,255,255,226,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,210,255,255,255,255,208,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,238,255,255,255,255,165,0,0,0,0,0,0,0,0,0,0,0,0,0,0,42,255,255, + 255,255,255,110,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,255,255,255,255,255, + 54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,164,255,255,255,255,249,7,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,227,255,255,255,255,201,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,31,255,255,255,255,255,146,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,74,255,255,255,255,255,111,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,85,255,255,255,255,255,119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,51, + 255,255,255,255,255,199,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,216,255,255, + 255,255,255,175,54,10,0,0,0,0,0,0,0,0,0,0,0,0,58,248,255,255,255,255, + 255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,62,223,255,255,255,255,255,255, + 255,255,0,0,0,0,0,0,0,0,0,0,0,0,31,206,255,255,255,255,255,255,255,0, + 0,0,0,0,0,0,0,0,0,0,90,243,255,255,255,255,255,255,255,255,0,0,0,0, + 0,0,0,0,0,0,79,254,255,255,255,255,255,254,230,220,220,0,0,0,0,0,0,0, + 0,0,4,227,255,255,255,255,255,140,20,0,0,0,0,0,0,0,0,0,0,0,0,57, + 255,255,255,255,255,180,0,0,0,0,0,0,0,0,0,0,0,0,0,0,84,255,255,255, + 255,255,114,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,255,255,255,255,255,114, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,255,255,255,255,255,151,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,223,255,255,255,255,205,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,161,255,255,255,255,251,9,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,100,255,255,255,255,255,60,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,39,255,255,255,255,255,114,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,237,255,255,255,255,169,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,255, + 255,255,255,211,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,244,255,255,255,255, + 226,0,0,0,0,0,0,0,0,0,0,0,0,0,20,171,255,255,255,255,255,209,0,0, + 0,0,0,0,0,0,220,220,220,220,224,253,255,255,255,255,255,255,153,0,0,0,0,0, + 0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,53,0,0,0,0,0,0,0,0, + 255,255,255,255,255,255,255,255,255,255,255,134,0,0,0,0,0,0,0,0,0,255,255,255, + 255,255,255,255,255,255,247,117,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,249, + 227,185,119,26,0,0,0,0,0,0,0,0,0,0,0,3,82,162,205,218,208,179,128,55, + 1,0,0,0,0,0,0,0,0,3,96,96,96,96,4,0,0,37,208,255,255,255,255,255, + 255,255,255,224,121,19,0,0,0,0,0,0,77,255,255,255,244,0,0,30,231,255,255,255, + 255,255,255,255,255,255,255,255,247,158,59,3,0,1,73,235,255,255,255,195,0,0,183,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,246,224,241,255,255,255,255,255,116,0, + 51,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 238,17,0,137,255,255,255,255,137,31,2,21,79,173,251,255,255,255,255,255,255,255,255,255, + 255,255,254,84,0,0,193,255,255,255,147,0,0,0,0,0,0,27,138,239,255,255,255,255, + 255,255,255,255,245,93,0,0,0,118,136,136,136,29,0,0,0,0,0,0,0,0,8,83, + 158,212,241,254,245,207,135,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0, +}; +static const struct nvfont nvfont_title2 = { 47, 12, nvg_title2, nvb_title2 }; + diff --git a/include/gfx/qr.h b/include/gfx/qr.h index a261855..a2151d1 100644 --- a/include/gfx/qr.h +++ b/include/gfx/qr.h @@ -8,25 +8,26 @@ // no second machine on the desk. A phone camera is a working out-of-band // channel that needs no drivers on our side at all. See docs/architecture/graphics.md. // -// Deliberately small, in three ways: +// Deliberately small, in two ways: // // - Byte mode only. The report is arbitrary bytes; alphanumeric mode would // pack tighter but only for a restricted character set. // - Error correction level L only. The screen is a clean, evenly lit, // high-contrast source -- the failure mode QR's stronger levels protect // against (dirt, print damage, curved packaging) does not apply. -// - Versions 1 to 6 only. Version 7 is where QR starts requiring an extra -// 18-bit version-information block in two more places on the symbol, and -// version 10 is where the character count field grows and the alignment -// pattern grid becomes 2-dimensional. Stopping at 6 avoids both, and -// 134 bytes is a large crash report. // -// If a payload ever needs more than 134 bytes, the honest fix is to shorten the -// report -- not to reach for version 10 and its two extra tables. +// Every version, 1 to 40. It stopped at 6 -- 134 bytes, enough for the crash +// card -- until PSOGM mode wanted the log itself off the machine, a page of it +// per symbol: version 40 at level L carries 2,953 bytes. A struct qr is +// therefore about 62 KB; keep one static, never on a stack. -#define QR_MAX_VERSION 6 -#define QR_MAX_SIZE (17 + 4 * QR_MAX_VERSION) // 41 modules per side -#define QR_MAX_BYTES 134 +#define QR_MAX_VERSION 40 +#define QR_MAX_SIZE (17 + 4 * QR_MAX_VERSION) // 177 modules per side +#define QR_MAX_BYTES 2953 + +// The crash card on the PSOGM stays small, so a phone reads it from across a +// desk: a version-6 symbol at most. +#define QR_CARD_BYTES 134 struct qr { int size; // modules per side; 0 if unencoded diff --git a/include/gmmos/gmmos.h b/include/gmmos/gmmos.h new file mode 100644 index 0000000..5fd2343 --- /dev/null +++ b/include/gmmos/gmmos.h @@ -0,0 +1,70 @@ +#pragma once + +#include "core/types.h" + +// GMMOS -- Guru Meditation Micro OS. What runs after a panic. +// +// The PSOGM used to be the last thing the kernel did: a report, blinking +// keyboard lights, and a machine that needed its power button. GMMOS keeps the +// report and goes on living under it. The real kernel is dead and stays dead -- +// every other processor is halted (see trap()), and nothing of it is called +// again that takes a lock, sleeps, allocates or waits for an interrupt. What +// GMMOS has instead is its own: the framebuffer, a clock read from the TSC, and +// keyboards it polls itself through micro drivers. +// +// A micro driver is a cut-down copy of a real one that does only what a +// keyboard needs. The real drivers run on interrupts, threads and locks, all of +// which belong to the kernel that just died. +// +// See kernel/gmmos/gmmos.c. + +void gmmos_run(char *reason, addr_t *pcs, int npcs) __attribute__((noreturn)); + +// One keyboard GMMOS can read by polling. +struct gmmos_kbd { + const char *name; + int (*probe)(void); // nonzero if it is there and usable + // The next key, or 0 when there is none: a Unicode codepoint, or one of the + // KBD_* codes from include/dev/kbd.h for keys that are not characters. + int (*poll)(void); + void (*leds)(int on); // optional: all three lights on or off +}; + +extern const struct gmmos_kbd gmmos_ps2; +extern const struct gmmos_kbd gmmos_i2c; +extern const struct gmmos_kbd gmmos_xhci; + +// A USB or I2C keyboard report in the boot layout -- modifiers, a reserved +// byte, six usages -- turned into keys. A report is a state, not an event, so +// a key is a usage that was not in the previous one. kernel/gmmos/hidkeys.c. +struct gmmos_hidkeys { + uchar prev[8]; + int q[16]; + int qh, qt; +}; +void gmmos_hid_report(struct gmmos_hidkeys *k, const uchar *boot); +int gmmos_hid_next(struct gmmos_hidkeys *k); + +// I2C keyboards, as kernel/acpi/i2c_hid.c found them at boot. GMMOS cannot run +// ACPI to find them again, so the live driver hands over what it learnt: the +// DesignWare controller, the device's address, its input report size, the +// interrupt pin when there is one, and the parsed report descriptor. +struct gmmos_i2c_kbd { + volatile uint32 *regs; + int txdepth, rxdepth; + int addr; + uint32 maxin; + volatile uint32 *pin; // AMD GPIO pin register, or 0: poll blindly + int active_low; + void *hid; +}; +int i2c_hid_keyboards(struct gmmos_i2c_kbd *out, int max); + +// The xHCI controllers kernel/drivers/bus/xhci/xhci.c brought up: their +// registers, mapped. The micro driver resets each and enumerates again. +int xhci_gmmos_controllers(volatile uchar **caps, int max); + +// From kernel/core/panic.c: draw the report again, and where its closing +// lines are, which GMMOS writes its status and its keys into. +void psogm_repaint(void); +void psogm_footer(int *x, int *y, int *step); diff --git a/include/net/socket.h b/include/net/socket.h index ccfdcdf..583f140 100644 --- a/include/net/socket.h +++ b/include/net/socket.h @@ -24,10 +24,14 @@ #define AF_NETLINK 16 #define PF_NETLINK AF_NETLINK #define NETLINK_ROUTE 0 +#define NETLINK_KOBJECT_UEVENT 15 +#define UEVENT_GROUP_KERNEL 1 // what the kernel announces +#define UEVENT_GROUP_UDEV 2 // what udevd re-announces after its rules #define SOCK_STREAM 1 #define SOCK_DGRAM 2 #define SOCK_RAW 3 +#define SOCK_SEQPACKET 5 // Flags ORed into socket()'s and accept4()'s `type`/`flags` argument. Linux's // values, because a musl program passes Linux's constants. @@ -63,7 +67,7 @@ // is not a number: it names an object the kernel already knows, and handing it // over is the difference between "here is a buffer" and "here is a *copy* of a // buffer". Wayland's wl_shm_create_pool passes one and has no integer-shaped -// alternative; docs/plan/hybrid.md's mechanism 1 is this and nothing else. +// alternative; docs/architecture/devices.md's mechanism 1 is this and nothing else. // // Every structure below is Linux's, byte for byte, because a musl-linked // program builds them with Linux's headers and we do not get a vote. The @@ -337,6 +341,15 @@ struct socket { char data[SOCKBUF]; uint nread, nwrite; + // AF_NETLINK only: which protocol, and the multicast groups bound. + int nlproto; + uint nlgroups; + uint nlpid; // port id, assigned at bind + uint nllast; // groups of the datagram read last + uint nlfrom; // port id (netlink) or pid (Unix) of its sender + uint pk_uid, pk_gid; // Unix packet: credentials of its sender + int pk_valid; // a packet has been read + // Connections waiting to be accepted, on a listener. struct socket *queue[NBACKLOG]; int nqueue; @@ -349,12 +362,26 @@ struct socket { int o_keepalive; int o_broadcast; int o_nodelay; + int o_passcred; // SO_PASSCRED: SCM_CREDENTIALS on every recvmsg // Descriptors sent to this socket and not yet received. They live on the // *receiving* side, next to the bytes they were sent with, because that is // the only side that can say when they have been delivered. struct scm_fds scm[NSCM]; int nscm; + + // Who is at this end, for SO_PEERCRED on the other: the process that made + // the socket, or for the server's end of a connection, the one that made + // the listener -- which is what Linux records too. + int cred_pid; + uint cred_uid, cred_gid; + + // Who last wrote at this end, for SCM_CREDENTIALS on the other. Not the + // same thing as cred_*: one end of a socketpair is made by the parent and + // written by the child it is handed to, and WebKit learns its children's + // pids from exactly that. 0 until the first write. + int tx_pid; + uint tx_uid, tx_gid; }; #endif diff --git a/include/uapi/errno.h b/include/uapi/errno.h index a061593..0edee24 100644 --- a/include/uapi/errno.h +++ b/include/uapi/errno.h @@ -39,6 +39,7 @@ #define ENAMETOOLONG 36 // File name too long #define ENOLCK 37 // No record locks available #define ENOSYS 38 // Function not implemented +#define ETIMEDOUT 110 // Connection timed out (and a futex wait that ran out) #define ENOTEMPTY 39 // Directory not empty #define ELOOP 40 // Too many symbolic links encountered diff --git a/include/uapi/fcntl.h b/include/uapi/fcntl.h index f6327b9..00020b8 100644 --- a/include/uapi/fcntl.h +++ b/include/uapi/fcntl.h @@ -46,5 +46,15 @@ #define F_OFD_GETLK 36 #define F_OFD_SETLK 37 #define F_OFD_SETLKW 38 +// memfd seals (Linux values). +#define F_ADD_SEALS 1033 +#define F_GET_SEALS 1034 +#define F_SEAL_SEAL 0x01 +#define F_SEAL_SHRINK 0x02 +#define F_SEAL_GROW 0x04 +#define F_SEAL_WRITE 0x08 +#define F_SEAL_FUTURE_WRITE 0x10 +#define MFD_CLOEXEC 0x01 +#define MFD_ALLOW_SEALING 0x02 #define FD_CLOEXEC 1 diff --git a/include/uapi/inotify.h b/include/uapi/inotify.h new file mode 100644 index 0000000..11b609d --- /dev/null +++ b/include/uapi/inotify.h @@ -0,0 +1,29 @@ +// inotify(7) event masks, Linux's values. +#ifndef UAPI_INOTIFY_H +#define UAPI_INOTIFY_H + +#define IN_ACCESS 0x00000001 +#define IN_MODIFY 0x00000002 +#define IN_ATTRIB 0x00000004 +#define IN_CLOSE_WRITE 0x00000008 +#define IN_CLOSE_NOWRITE 0x00000010 +#define IN_OPEN 0x00000020 +#define IN_MOVED_FROM 0x00000040 +#define IN_MOVED_TO 0x00000080 +#define IN_CREATE 0x00000100 +#define IN_DELETE 0x00000200 +#define IN_DELETE_SELF 0x00000400 +#define IN_MOVE_SELF 0x00000800 +#define IN_ALL_EVENTS 0x00000fff +#define IN_UNMOUNT 0x00002000 +#define IN_Q_OVERFLOW 0x00004000 +#define IN_IGNORED 0x00008000 +#define IN_ONLYDIR 0x01000000 +#define IN_DONT_FOLLOW 0x02000000 +#define IN_EXCL_UNLINK 0x04000000 +#define IN_MASK_CREATE 0x10000000 +#define IN_MASK_ADD 0x20000000 +#define IN_ISDIR 0x40000000 +#define IN_ONESHOT 0x80000000 + +#endif diff --git a/include/uapi/posix.h b/include/uapi/posix.h index d39fe7c..c416f4c 100644 --- a/include/uapi/posix.h +++ b/include/uapi/posix.h @@ -354,6 +354,7 @@ struct statfs { #define AT_FDCWD (-100) #define AT_REMOVEDIR 0x200 #define AT_SYMLINK_NOFOLLOW 0x100 +#define AT_EACCESS 0x200 #define AT_EMPTY_PATH 0x1000 #define AT_NO_AUTOMOUNT 0x800 @@ -431,6 +432,7 @@ struct statfs { #define MAP_PRIVATE 0x02 #define MAP_FIXED 0x10 #define MAP_ANONYMOUS 0x20 +#define MAP_NORESERVE 0x4000 // waitpid() options. The kernel tests `options & 1`; this is that bit, named. #define WNOHANG 0x001 diff --git a/include/uapi/user.h b/include/uapi/user.h index c2c2867..ea4ef76 100644 --- a/include/uapi/user.h +++ b/include/uapi/user.h @@ -46,8 +46,7 @@ int alarm(int seconds); int waitpid(int pid, int *status, int options); int setsid(void); void* mmap(void*, uint64, int, int, int, uint64); -// memfd_create(2). The flags argument exists for MFD_CLOEXEC and MFD_ALLOW_ -// SEALING, neither of which this kernel implements; pass 0. +// memfd_create(2). flags: MFD_CLOEXEC, MFD_ALLOW_SEALING. int memfd_create(const char *name, uint flags); // Shared memory. shmat returns the attach address, or a small negative value // on failure — there is no errno in this runtime, so check for < 0. diff --git a/kernel/acpi/aml.c b/kernel/acpi/aml.c index c4b894b..7368449 100644 --- a/kernel/acpi/aml.c +++ b/kernel/acpi/aml.c @@ -32,10 +32,20 @@ #include #include #include +#include +#include +#include +#include static int ready; +// \_S5's SLP_TYP values, read once at boot for acpi_poweroff_now(): -1 until +// then, or if the firmware has none. +static int s5_typ_a = -1, s5_typ_b = -1; + static uacpi_interrupt_ret power_button(uacpi_handle ctx); +static void cstate_find(void); +static void ec_init(void); void acpi_aml_init(void) @@ -56,6 +66,10 @@ acpi_aml_init(void) return; } + // The Embedded Controller, before _INI: methods that run there may already + // read it, and on laptops it is where the battery and the adapter live. + ec_init(); + // \_PIC(1): tell the firmware the I/O APIC is in use, so that _PRT answers // with I/O APIC inputs rather than PIC lines. Before _INI runs, which on // some boards reads the answer. @@ -74,8 +88,171 @@ acpi_aml_init(void) kinfo("acpi: no fixed power button: %s\n", uacpi_status_to_string(st)); uacpi_finalize_gpe_initialization(); + + { + uacpi_object *o = 0; + uacpi_object_array p; + uacpi_u64 ta, tb; + if (uacpi_eval_package(0, "\\_S5", 0, &o) == UACPI_STATUS_OK) { + if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK && p.count >= 1 && + uacpi_object_get_integer(p.objects[0], &ta) == UACPI_STATUS_OK) { + tb = ta; + if (p.count >= 2) + uacpi_object_get_integer(p.objects[1], &tb); + s5_typ_a = ta & 7; + s5_typ_b = tb & 7; + } + uacpi_object_unref(o); + } + } ready = 1; kinfo("acpi: namespace loaded\n"); + cstate_find(); +} + +// --- the Embedded Controller ---------------------------------------------------- +// +// A laptop's EC is a microcontroller behind two I/O ports that owns the +// battery gauge, the charger, the lid and the fans; AML reaches it through an +// OperationRegion in the EmbeddedControl space, and only the OS can serve +// that space (ACPI 12). Without a handler every EC field read fails -- and +// firmware guards against that: _REG(EmbeddedControl, 1) sets a flag (ASUS +// calls it ECAV), and until it is set _BST answers "full" and _PSR "on AC". +// That is what the TUF A15 reported, unplugged. +// +// The protocol (ACPI 12.3): wait for the input buffer to empty, write a +// command to the command port, then the address -- and for a read wait for +// the output buffer and take the byte from the data port. Ports come from +// the EC device's _CRS, data first; or from the ECDT when the DSDT has no +// PNP0C09 early enough. SCI events (_Qxx) are not taken yet: nothing here +// needs to be told the adapter changed, because everything reads on demand. + +#define EC_OBF 0x01 +#define EC_IBF 0x02 +#define EC_RD 0x80 +#define EC_WR 0x81 + +static uint16 ec_data, ec_cmd; +static uacpi_namespace_node *ec_node; +static struct spinlock eclock; + +static int +ec_wait(int want_obf) +{ + for (int i = 0; i < 100000; i++) { // ~100 ms; an EC answers in tens of µs + uint8 st = inb(ec_cmd); + if (want_obf ? (st & EC_OBF) : !(st & EC_IBF)) + return 0; + microdelay(1); + } + return -1; +} + +static int +ec_byte(int write, uint8 addr, uint8 *v) +{ + int r = -1; + acquire(&eclock); + if (ec_wait(0) < 0) goto out; + outb(ec_cmd, write ? EC_WR : EC_RD); + if (ec_wait(0) < 0) goto out; + outb(ec_data, addr); + if (write) { + if (ec_wait(0) < 0) goto out; + outb(ec_data, *v); + if (ec_wait(0) < 0) goto out; + } else { + if (ec_wait(1) < 0) goto out; + *v = inb(ec_data); + } + r = 0; +out: + release(&eclock); + return r; +} + +static uacpi_status +ec_handler(uacpi_region_op op, uacpi_handle opdata) +{ + if (op == UACPI_REGION_OP_ATTACH || op == UACPI_REGION_OP_DETACH) + return UACPI_STATUS_OK; + if (op != UACPI_REGION_OP_READ && op != UACPI_REGION_OP_WRITE) + return UACPI_STATUS_INVALID_ARGUMENT; + + uacpi_region_rw_data *d = opdata; + int write = op == UACPI_REGION_OP_WRITE; + if (!write) + d->value = 0; + // Wider fields are consecutive bytes, low first. + for (int i = 0; i < d->byte_width; i++) { + uint8 b = write ? (uint8)(d->value >> (8 * i)) : 0; + if (d->address + i > 0xFF || ec_byte(write, (uint8)(d->address + i), &b) < 0) + return UACPI_STATUS_HARDWARE_TIMEOUT; + if (!write) + d->value |= (uacpi_u64)b << (8 * i); + } + return UACPI_STATUS_OK; +} + +struct ec_ports { int n; uint16 p[2]; }; + +static uacpi_iteration_decision +ec_port(void *user, uacpi_resource *r) +{ + struct ec_ports *e = user; + if (e->n < 2 && r->type == UACPI_RESOURCE_TYPE_IO) + e->p[e->n++] = r->io.minimum; + else if (e->n < 2 && r->type == UACPI_RESOURCE_TYPE_FIXED_IO) + e->p[e->n++] = r->fixed_io.address; + return UACPI_ITERATION_DECISION_CONTINUE; +} + +static uacpi_iteration_decision +found_ec(void *user, uacpi_namespace_node *node, uacpi_u32 depth) +{ + (void)user; (void)depth; + struct ec_ports e = { 0, { 0, 0 } }; + uacpi_for_each_device_resource(node, "_CRS", ec_port, &e); + if (e.n == 2) { + ec_node = node; + ec_data = e.p[0]; + ec_cmd = e.p[1]; + return UACPI_ITERATION_DECISION_BREAK; + } + return UACPI_ITERATION_DECISION_CONTINUE; +} + +static void +ec_init(void) +{ + uacpi_table t; + + initlock(&eclock, "ec"); + uacpi_find_devices("PNP0C09", found_ec, 0); + if (!ec_node && + uacpi_table_find_by_signature("ECDT", &t) == UACPI_STATUS_OK) { + struct acpi_ecdt *e = t.ptr; + if (e->ec_control.address && e->ec_data.address) { + ec_cmd = (uint16)e->ec_control.address; + ec_data = (uint16)e->ec_data.address; + ec_node = uacpi_namespace_root(); + } + uacpi_table_unref(&t); + } + if (!ec_node) + return; + + // Installing runs _REG(3, 1) for the regions under it. + uacpi_status st = uacpi_install_address_space_handler( + ec_node, UACPI_ADDRESS_SPACE_EMBEDDED_CONTROLLER, ec_handler, 0); + if (uacpi_unlikely_error(st)) { + kinfo("acpi: EC at 0x%x/0x%x, handler refused: %s\n", ec_data, ec_cmd, + uacpi_status_to_string(st)); + return; + } + uint8 probe = 0; + kinfo("acpi: EC at data 0x%x, command 0x%x%s\n", ec_data, ec_cmd, + ec_byte(0, 0, &probe) < 0 ? " -- not answering" : ""); } // --- power ------------------------------------------------------------------- @@ -118,6 +295,28 @@ acpi_poweroff(void) return -1; } +// Power off from a dead kernel: the PSOGM, after it has been up long enough to +// be read. acpi_poweroff() runs _PTS and other AML, which can sleep and take +// locks that another processor died holding, so it is no use here. This is +// only the last step of it -- SLP_TYP from \_S5, then SLP_EN, written straight +// to the PM1 control registers. The firmware's preparations are skipped; a +// crashed machine has nothing to save. Returns only if the machine is still on. +void +acpi_poweroff_now(void) +{ + uacpi_u64 cnt = 0, a, b; + + if (!ready || s5_typ_a < 0) + return; + uacpi_read_register(UACPI_REGISTER_PM1_CNT, &cnt); + cnt &= ~(uacpi_u64)((7 << 10) | (1 << 13)); + a = cnt | ((uacpi_u64)s5_typ_a << 10); + b = cnt | ((uacpi_u64)s5_typ_b << 10); + uacpi_write_registers(UACPI_REGISTER_PM1_CNT, a, b); + uacpi_write_registers(UACPI_REGISTER_PM1_CNT, a | (1 << 13), b | (1 << 13)); + microdelay(100000); +} + int acpi_reboot(void) { @@ -228,7 +427,7 @@ prt_lookup(uacpi_pci_routing_table *t, int slot, int pin, int *mode) if (f.gsi <= 0) { uacpi_object_name nm = uacpi_namespace_node_name(e->source); char name[5] = { nm.text[0], nm.text[1], nm.text[2], nm.text[3], 0 }; - cprintf("acpi: link %s for slot %d is disabled\n", name, slot); + kinfo("acpi: link %s for slot %d is disabled\n", name, slot); return -1; } *mode = f.mode; @@ -340,3 +539,253 @@ acpi_pci_route(void) kinfo("acpi: %d more on a second I/O APIC, which is not driven; " "those devices need MSI\n", beyond); } + +// --- batteries and adapters -------------------------------------------------- +// +// A laptop's battery is an ACPI device, PNP0C0A, with methods that answer in +// packages: _BIX (or the older _BIF) for what it is -- capacities, units, +// names -- and _BST for how it is now. The adapter is ACPI0003 and _PSR says +// whether it is plugged in. Read on demand, from /sys/class/power_supply, +// which is where fastfetch and every battery widget look. + +#define MAXPSU 4 +static uacpi_namespace_node *psu_node[MAXPSU]; +static int psu_isbat[MAXPSU]; +static int npsu; + +static uacpi_iteration_decision +found_psu(void *user, uacpi_namespace_node *node, uacpi_u32 depth) +{ + (void)depth; + if (npsu < MAXPSU) { + psu_node[npsu] = node; + psu_isbat[npsu] = user != 0; + npsu++; + } + return UACPI_ITERATION_DECISION_CONTINUE; +} + +static int psu_found; + +static void +psu_find(void) +{ + if (psu_found || !ready) + return; + psu_found = 1; + uacpi_find_devices("PNP0C0A", found_psu, (void *)1); + uacpi_find_devices("ACPI0003", found_psu, 0); +} + +int +acpi_psu_count(void) +{ + psu_find(); + return npsu; +} + +static uint32 +pkg_int(uacpi_object_array *p, uacpi_size i) +{ + uacpi_u64 v; + if (i >= p->count || uacpi_object_get_integer(p->objects[i], &v) != UACPI_STATUS_OK) + return ACPI_UNKNOWN; + return (uint32)v; +} + +static void +pkg_str(uacpi_object_array *p, uacpi_size i, char *out, int cap) +{ + uacpi_data_view d; + out[0] = 0; + if (i >= p->count || + uacpi_object_get_string_or_buffer(p->objects[i], &d) != UACPI_STATUS_OK) + return; + int n = 0; + for (uacpi_size k = 0; k < d.length && n < cap - 1 && d.const_text[k]; k++) + if (d.const_text[k] >= ' ' && d.const_text[k] < 127) + out[n++] = d.const_text[k]; + // Firmware pads with spaces. + while (n > 0 && out[n - 1] == ' ') + n--; + out[n] = 0; +} + +int +acpi_psu_get(int i, struct acpi_psu *b) +{ + uacpi_object *o; + uacpi_object_array p; + uacpi_u32 sta = 0xF; + + psu_find(); + if (i < 0 || i >= npsu) + return -1; + memset(b, 0, sizeof(*b)); + b->battery = psu_isbat[i]; + + if (!b->battery) { + uacpi_u64 v = 0; + uacpi_eval_simple_integer(psu_node[i], "_PSR", &v); + b->online = v != 0; + return 0; + } + + // Bit 4 of _STA: a battery is in the bay. A missing _STA means present. + uacpi_eval_sta(psu_node[i], &sta); + b->present = (sta & 0x10) != 0; + b->rate = b->remaining = b->voltage = ACPI_UNKNOWN; + b->design = b->full = b->design_voltage = b->cycles = ACPI_UNKNOWN; + if (!b->present) + return 0; + + // _BIX: a revision first, then _BIF's fields shifted by one, with the + // cycle count after the design voltage's neighbours. + if (uacpi_eval_package(psu_node[i], "_BIX", 0, &o) == UACPI_STATUS_OK) { + if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) { + b->unit = (int)pkg_int(&p, 1); + b->design = pkg_int(&p, 2); + b->full = pkg_int(&p, 3); + b->technology = (int)pkg_int(&p, 4); + b->design_voltage = pkg_int(&p, 5); + b->cycles = pkg_int(&p, 8); + pkg_str(&p, 16, b->model, sizeof(b->model)); + pkg_str(&p, 17, b->serial, sizeof(b->serial)); + pkg_str(&p, 18, b->kind, sizeof(b->kind)); + pkg_str(&p, 19, b->oem, sizeof(b->oem)); + } + uacpi_object_unref(o); + } else if (uacpi_eval_package(psu_node[i], "_BIF", 0, &o) == UACPI_STATUS_OK) { + if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) { + b->unit = (int)pkg_int(&p, 0); + b->design = pkg_int(&p, 1); + b->full = pkg_int(&p, 2); + b->technology = (int)pkg_int(&p, 3); + b->design_voltage = pkg_int(&p, 4); + pkg_str(&p, 9, b->model, sizeof(b->model)); + pkg_str(&p, 10, b->serial, sizeof(b->serial)); + pkg_str(&p, 11, b->kind, sizeof(b->kind)); + pkg_str(&p, 12, b->oem, sizeof(b->oem)); + } + uacpi_object_unref(o); + } + + if (uacpi_eval_package(psu_node[i], "_BST", 0, &o) == UACPI_STATUS_OK) { + if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) { + b->state = pkg_int(&p, 0); + b->rate = pkg_int(&p, 1); + b->remaining = pkg_int(&p, 2); + b->voltage = pkg_int(&p, 3); + } + uacpi_object_unref(o); + } + return 0; +} + +// --- idle states -------------------------------------------------------------- +// +// An idle CPU used to run hlt and nothing else. hlt is C1: the core stops +// executing and stays powered. The states below it -- CC6 on Zen, C3 and +// deeper on Intel -- switch the core off, and the firmware lists how to reach +// them in each processor's _CST. Without them a laptop sitting at a shell +// keeps every core warm and the fans going. +// +// _CST is a count followed by one package per state: a register (a Generic +// Register descriptor in a buffer), a type (1 = C1, 2 = C2, 3 = C3), the +// worst-case wake-up latency in microseconds, and the power. A register in +// system I/O space is entered by reading it; one in functional fixed hardware +// is MWAIT, with the hint in the address. +// +// Only types 1 and 2 are taken. C3 asks the OS to stop bus-master activity +// or flush caches first (ACPI 8.1.4), which this does not do. + +struct acpi_idle acpi_idle; + +static int cst_done; + +static void +cst_parse(uacpi_namespace_node *node) +{ + uacpi_object *o = 0; + uacpi_object_array p, e; + uacpi_data_view d; + + if (uacpi_eval_package(node, "_CST", 0, &o) != UACPI_STATUS_OK) + return; + if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) { + for (uacpi_size i = 1; i < p.count; i++) { + if (uacpi_object_get_package(p.objects[i], &e) != UACPI_STATUS_OK || + e.count < 4) + continue; + uint32 type = pkg_int(&e, 1), lat = pkg_int(&e, 2); + if (type != 2 || lat > 1000) + continue; + if (uacpi_object_get_buffer(e.objects[0], &d) != UACPI_STATUS_OK || + d.length < 15 || d.const_bytes[0] != 0x82) + continue; + uint8 space = d.const_bytes[3]; + uint64 addr = 0; + for (int k = 0; k < 8; k++) + addr |= (uint64)d.const_bytes[7 + k] << (8 * k); + if (space == UACPI_ADDRESS_SPACE_SYSTEM_IO && addr && addr < 0x10000) { + acpi_idle.kind = ACPI_IDLE_IO; + } else if (space == UACPI_ADDRESS_SPACE_FFIXEDHW && (d.const_bytes[4] == 1)) { + // Intel's FFH encoding: vendor 1, class 2 (native C-state), and + // the MWAIT hint in the low byte of the address. + acpi_idle.kind = ACPI_IDLE_MWAIT; + } else { + continue; + } + acpi_idle.addr = addr; + acpi_idle.latency = lat; + break; + } + } + uacpi_object_unref(o); +} + +static uacpi_iteration_decision +found_cpu(void *user, uacpi_namespace_node *node, uacpi_u32 depth) +{ + (void)user; (void)depth; + if (cst_done) + return UACPI_ITERATION_DECISION_BREAK; + cst_parse(node); + cst_done = 1; // every processor's _CST is the same in practice + return UACPI_ITERATION_DECISION_BREAK; +} + +static void +cstate_find(void) +{ + uint32 a, b, c, dd; + + // The local APIC timer is what wakes an idle CPU for the scheduler tick. + // Below C1 it may stop, unless the CPU says it keeps running (ARAT, + // CPUID 6 EAX bit 2); without that an idle CPU would sleep for good. + cpuid_leaf(6, &a, &b, &c, &dd); + if (!(a & 4)) { + kinfo("acpi: no always-running APIC timer; idle stays at C1\n"); + return; + } + + uacpi_namespace_for_each_child(uacpi_namespace_root(), found_cpu, 0, + UACPI_OBJECT_PROCESSOR_BIT, + UACPI_MAX_DEPTH_ANY, 0); + if (!cst_done) + uacpi_find_devices("ACPI0007", found_cpu, 0); + + if (acpi_idle.kind == ACPI_IDLE_MWAIT) { + cpuid_leaf(1, &a, &b, &c, &dd); + if (!(c & (1u << 3))) // no MONITOR/MWAIT after all + acpi_idle.kind = ACPI_IDLE_HLT; + } + if (acpi_idle.kind == ACPI_IDLE_IO) + kinfo("acpi: idle CPUs enter C2 through port %x (wake-up %d us)\n", + (uint)acpi_idle.addr, acpi_idle.latency); + else if (acpi_idle.kind == ACPI_IDLE_MWAIT) + kinfo("acpi: idle CPUs enter C2 by MWAIT hint %x (wake-up %d us)\n", + (uint)acpi_idle.addr, acpi_idle.latency); + else + kinfo("acpi: no usable C2 in _CST; idle stays at C1\n"); +} diff --git a/kernel/acpi/host.c b/kernel/acpi/host.c index 44915c6..f0c7f95 100644 --- a/kernel/acpi/host.c +++ b/kernel/acpi/host.c @@ -121,7 +121,7 @@ uacpi_kernel_log(uacpi_log_level level, const uacpi_char *msg) switch (level) { case UACPI_LOG_ERROR: case UACPI_LOG_WARN: - cprintf("acpi: %s", msg); + kinfo("acpi: %s", msg); break; case UACPI_LOG_INFO: kinfo("acpi: %s", msg); diff --git a/kernel/acpi/i2c_hid.c b/kernel/acpi/i2c_hid.c index d62ffc2..8578c0e 100644 --- a/kernel/acpi/i2c_hid.c +++ b/kernel/acpi/i2c_hid.c @@ -50,6 +50,11 @@ #include #include #include +#include "gmmos/gmmos.h" + +// Each step said before it is taken, in a verbose boot: a machine that stops +// here stops with the step it was on as the last line on the screen. +#define STEP(...) do { if(boot_verbose) cprintf(__VA_ARGS__); } while(0) // --------------------------------------------------------------------------- // DesignWare I2C @@ -171,9 +176,10 @@ dw_init(struct dwi2c *c, uacpi_namespace_node *node, uint32 speed) c->regs = ioremap(c->phys, 0x1000); if(c->regs == 0) return -1; + STEP("i2c: controller registers at 0x%p\n", c->phys); type = rd(c, IC_COMP_TYPE); if(type != DW_COMP_TYPE){ - cprintf("i2c: controller at 0x%lx is not DesignWare (0x%x)\n", + cprintf("i2c: controller at 0x%p is not DesignWare (0x%x)\n", c->phys, type); return -1; } @@ -415,7 +421,7 @@ controller(const char *path, uint32 speed) if(dw_init(c, node, speed) < 0) return 0; nctl++; - cprintf("i2c: %s at 0x%lx, %s mode, FIFOs %d/%d\n", path, c->phys, + kinfo("i2c: %s at 0x%p, %s mode, FIFOs %d/%d\n", path, c->phys, c->fast ? "fast" : "standard", c->txdepth, c->rxdepth); return c; } @@ -533,19 +539,24 @@ probe(void *user, uacpi_namespace_node *node, uacpi_u32 depth) memset(d, 0, sizeof(*d)); memmove(d->name, uacpi_namespace_node_name(node).text, 4); + STEP("i2c-hid: %s: reading its resources\n", d->name); if(resources(node, &f) < 0 || !f.i2c){ - cprintf("i2c-hid: %s has no I2C connection\n", d->name); + kinfo("i2c-hid: %s has no I2C connection\n", d->name); return UACPI_ITERATION_DECISION_CONTINUE; } + STEP("i2c-hid: %s: _DSM\n", d->name); if(hid_desc_register(node, &descreg) < 0){ - cprintf("i2c-hid: %s: _DSM gave no descriptor register\n", d->name); + kinfo("i2c-hid: %s: _DSM gave no descriptor register\n", d->name); return UACPI_ITERATION_DECISION_CONTINUE; } + STEP("i2c-hid: %s: controller %s, address 0x%x\n", d->name, f.bus, f.addr); if((d->c = controller(f.bus, f.speed)) == 0) return UACPI_ITERATION_DECISION_CONTINUE; d->addr = f.addr; + STEP("i2c-hid: %s: _PS0\n", d->name); uacpi_execute(node, "_PS0", 0); + STEP("i2c-hid: %s: HID descriptor at register 0x%x\n", d->name, descreg); if(reg_read(d, descreg, desc, sizeof(desc)) < 0 || (desc[0] | desc[1] << 8) != 30){ cprintf("i2c-hid: %s at 0x%x does not answer\n", d->name, d->addr); @@ -563,6 +574,8 @@ probe(void *user, uacpi_namespace_node *node, uacpi_u32 depth) d->pin = interrupt_pin(&f); d->active_low = f.active_low; + STEP("i2c-hid: %s: power on and reset, pin %s\n", d->name, + d->pin ? "watched" : "none"); command(d, OP_SET_POWER, 0); // 0 = on wait_ticks(2); command(d, OP_RESET, 0); @@ -577,6 +590,7 @@ probe(void *user, uacpi_namespace_node *node, uacpi_u32 depth) if(dw_xfer(d->c, d->addr, 0, 0, d->buf, d->maxin) < 0) break; + STEP("i2c-hid: %s: report descriptor, %d bytes\n", d->name, rlen); if(reg_read(d, rreg, rdesc, rlen) < 0){ cprintf("i2c-hid: %s: report descriptor unreadable\n", d->name); kfree((char *)rdesc); @@ -587,23 +601,38 @@ probe(void *user, uacpi_namespace_node *node, uacpi_u32 depth) d->hid = hid_attach(rdesc, rlen, what); kfree((char *)rdesc); if(d->hid == 0){ - cprintf("i2c-hid: %s is neither a keyboard nor a pointer\n", d->name); + kinfo("i2c-hid: %s is neither a keyboard nor a pointer\n", d->name); return UACPI_ITERATION_DECISION_CONTINUE; } - cprintf("i2c-hid: %s: %04x:%04x at 0x%x, %s\n", d->name, + kinfo("i2c-hid: %s: %x:%x at 0x%x, %s\n", d->name, desc[20] | desc[21] << 8, desc[22] | desc[23] << 8, d->addr, d->pin ? "interrupt pin watched" : "polled"); ndev++; return UACPI_ITERATION_DECISION_CONTINUE; } -static void -i2c_hid_thread(void) +// **The thread runs on a stack of its own, 64 KB.** A kernel thread's stack +// is 8 KB, and this one runs the firmware's AML -- _DSM, _PS0, _CRS, and +// whatever those call -- through uACPI's interpreter, built at -O0. On the +// ASUS TUF A15 that ran off the end: kernel stacks are one array, so it ran +// into the next thread's, and the machine died a moment later in whatever +// that thread was doing, with a crash report about the wrong code. +// call_on_stack() is SCFF's (kernel/scff/scff.c). Sleeping here is fine: +// the context is saved on this stack, and only this thread ever uses it. +static unsigned char i2c_stack[64 * 1024] __attribute__((aligned(16))); +long call_on_stack(long (*fn)(void *), void *arg, void *top); + +static long +i2c_hid_main(void *arg) { static const char *const ids[] = { "PNP0C50", "ACPI0C50", 0 }; int i, n; + (void)arg; + + STEP("i2c-hid: looking for touchpads\n"); uacpi_find_devices_at(uacpi_namespace_root(), ids, probe, 0); + STEP("i2c-hid: %d found\n", ndev); if(ndev == 0) for(;;) wait_ticks(1000000); @@ -625,6 +654,36 @@ i2c_hid_thread(void) } } +// For GMMOS. Read without a lock: `ndev` only ever grows, and only before the +// polling loop starts, so a panic sees a list that was finished or not begun. +int +i2c_hid_keyboards(struct gmmos_i2c_kbd *out, int max) +{ + int i, n = 0; + + for(i = 0; i < ndev && n < max; i++){ + struct i2chid *d = &devs[i]; + if(!hid_is_keyboard(d->hid)) + continue; + out[n].regs = d->c->regs; + out[n].txdepth = d->c->txdepth; + out[n].rxdepth = d->c->rxdepth; + out[n].addr = d->addr; + out[n].maxin = d->maxin; + out[n].pin = d->pin; + out[n].active_low = d->active_low; + out[n].hid = d->hid; + n++; + } + return n; +} + +static void +i2c_hid_thread(void) +{ + call_on_stack(i2c_hid_main, 0, i2c_stack + sizeof(i2c_stack)); +} + void i2c_hid_start(void) { diff --git a/kernel/arch/x86_64/ioperm.c b/kernel/arch/x86_64/ioperm.c index cf6d238..b27bcb4 100644 --- a/kernel/arch/x86_64/ioperm.c +++ b/kernel/arch/x86_64/ioperm.c @@ -56,6 +56,14 @@ struct tssblk { static struct tssblk tssblk[NCPU]; +// The stack an NMI arrives on: IST slot 1, one per CPU. Without it an NMI runs +// on whatever stack was live, and for a few instructions at the top of +// syscall_entry that is the user's -- at ring 0, so the CPU would push the +// trap frame there, and a fault on that push is a double fault on the same +// stack. Panic stops the other processors with an NMI; it must not be the +// thing that resets the machine instead of the report. +static uint8 nmistack[NCPU][4096] __attribute__((aligned(16))); + // What each CPU currently has open. Kept per CPU rather than read back from the // process, because switching *away* has to close what was opened and the // process that opened it may be part-way through exit() by then. @@ -89,6 +97,7 @@ ioperm_tss(int cpu, uint64 *limit) memset(b->map, 0xFF, sizeof(b->map)); b->tail = 0xFF; b->ts.iomb = (uint16)__builtin_offsetof(struct tssblk, map); + b->ts.ist[0] = (uint64)(nmistack[cpu] + sizeof(nmistack[cpu])); installed[cpu].n = 0; // The limit is the offset of the last valid byte, and it has to include the diff --git a/kernel/arch/x86_64/lapic.c b/kernel/arch/x86_64/lapic.c index 5830d05..e42fd98 100644 --- a/kernel/arch/x86_64/lapic.c +++ b/kernel/arch/x86_64/lapic.c @@ -402,6 +402,21 @@ cpunum(void) panic("unknown apicid\n"); } +// An NMI to every processor but this one: how a panic stops the others. An +// NMI is delivered with interrupts off too, so it reaches a processor spinning +// on a lock with cli as surely as one running a program; trap() halts any that +// takes one while panic_stop is set. Bounded, because the caller is dying. +void +lapic_nmi_others(void) +{ + if (!lapic) + return; + lapicw(ICRHI, 0); + lapicw(ICRLO, 0x000C0000 | 0x00000400 | ASSERT); // all but self, NMI + for (int i = 0; i < 100000 && (lapic[ICRLO] & DELIVS); i++) + microdelay(1); +} + // Acknowledge interrupt. void lapiceoi(void) @@ -566,3 +581,19 @@ void cmostime(struct rtcdate *r) *r = t1; r->year += 2000; } + +// A fixed interrupt to one processor, by APIC id. Waits for the local APIC +// to say it has sent the last one before writing the next: two ICR writes +// back to back lose the first. +void +lapic_send_ipi(int apicid, int vector) +{ + if (!lapic) + return; + while (lapic[ICRLO] & DELIVS) + ; + lapicw(ICRHI, apicid << 24); + lapicw(ICRLO, vector); // fixed, physical, edge, no shorthand + while (lapic[ICRLO] & DELIVS) + ; +} diff --git a/kernel/arch/x86_64/mp.c b/kernel/arch/x86_64/mp.c index cf24e4c..ebd2502 100644 --- a/kernel/arch/x86_64/mp.c +++ b/kernel/arch/x86_64/mp.c @@ -111,7 +111,7 @@ mpinit(void) struct mpioapic *ioapic; if((conf = mpconfig(&mp)) == 0) { - cprintf("No other CPUs found.\n"); + kinfo("No other CPUs found.\n"); return; } lapic = (uint*)P2V(conf->lapicaddr_p); @@ -157,7 +157,7 @@ mpinit(void) // See docs/architecture/boot.md -- this is half of the SMP bug, and the // reason the other half (AP startup triple-faulting) cannot be reproduced // under QEMU until the MADT is parsed. - cprintf("Seems we are SMP, ncpu = %d\n",ncpu); + kinfo("Seems we are SMP, ncpu = %d\n",ncpu); if(mp->imcrp){ // Bochs doesn't support IMCR, so this doesn't run on Bochs. // But it would on real hardware. diff --git a/kernel/arch/x86_64/trap.c b/kernel/arch/x86_64/trap.c index 6c90fa4..c98f44d 100644 --- a/kernel/arch/x86_64/trap.c +++ b/kernel/arch/x86_64/trap.c @@ -48,6 +48,8 @@ void tvinit(void) for (n = 0; n < 256; n++) mkgate(idt, n, vectors[n], 0); mkgate(idt, T_SYSCALL, vectors[T_SYSCALL], 3); + // NMI on its own stack, IST slot 1; see nmistack in ioperm.c. + idt[T_NMI * 4 + 1] |= 1; } uint @@ -81,6 +83,17 @@ trapsig(uint64 trapno) void trap(struct trapframe *tf) { + // Another processor has panicked and wants this one stopped: a processor + // left running carries on with a kernel that is already dead, and on a + // laptop that showed as the report flashing up and being drawn over, and + // as fans that stayed loud until somebody held the power button. First, and + // before the canary check, because this stack may be what is broken. + if(tf->trapno == T_NMI && panic_stop) + for(;;){ + cli(); + hlt(); + } + kstack_check_canary("trap entry"); switch(tf->trapno){ case T_IRQ0 + IRQ_TIMER: @@ -147,6 +160,11 @@ trap(struct trapframe *tf) } unexpected: + // A user page fault on anonymous memory that is reserved but not backed + // yet: the page is made and the instruction runs again. See vm_fault(). + if(tf->trapno == T_PGFLT && myproc() && (tf->cs&3) == DPL_USER && + !(tf->err & 1) && vm_fault(rcr2()) == 0) + break; if(myproc() == 0 || (tf->cs&3) == 0){ // A kernel-mode trap ends in panic() a dozen lines down, so the console // can be put into its panic mode *now* -- and it has to be, because the diff --git a/kernel/arch/x86_64/vm.c b/kernel/arch/x86_64/vm.c index 04e61ea..72a9e21 100644 --- a/kernel/arch/x86_64/vm.c +++ b/kernel/arch/x86_64/vm.c @@ -10,6 +10,8 @@ #include "core/spinlock.h" #include "arch/vmap.h" #include "ldls/kldls.h" +#include "core/irq.h" +#include "arch/traps.h" int mappages(pml4e_t *pgdir, void *va, addr_t size, addr_t pa, int perm); static pte_t * walkpgdir(pml4e_t *pml4, const void *va, int alloc); @@ -636,6 +638,11 @@ oom: return 0; } +// CR3 loads per processor, and what each has loaded; see "Pages another +// processor may still have in its TLB" below. +static volatile uint64 cr3_loads[NCPU]; +static pml4e_t *volatile cpu_pgdir[NCPU]; + void switchuvm(struct proc *p) { @@ -650,6 +657,8 @@ switchuvm(struct proc *p) // every process that is not hosting a driver. ioperm_switch(p); + cr3_loads[cpunum()]++; + cpu_pgdir[cpunum()] = p->pgdir; lcr3(v2p(p->pgdir)); popcli(); } @@ -695,9 +704,193 @@ walkpgdir(pml4e_t *pml4, const void *va, int alloc) return (pte_t*)&pgtab[PTX(va)]; } +// --- Pages another processor may still have in its TLB ----------------- +// +// Threads put one address space on several processors at once, and there is +// no shootdown IPI: unmapping a page here invalidates it in this TLB only. +// Freed at once, it could be handed to someone else while a thread on another +// CPU still reaches it through a stale entry -- a write into a stranger's +// memory with no fault to show for it. +// +// So a page unmapped from a shared address space waits. Every CR3 load is +// counted per processor, and a batch of pages is freed once every processor +// has reloaded CR3 since the batch was closed, or is not running a user +// address space at all. A processor reloads at every switch, so a batch waits +// a scheduling slice or two. +#define DEFER_MAX 2048 +struct defer_batch { + int n; + uint64 snap[NCPU]; + uint64 pa[DEFER_MAX]; +}; +static struct defer_batch defer_q[2]; +static int defer_open; // which of the two is filling +static int defer_closed_valid; // the other one is waiting to be freed +static struct spinlock deferlock; +static int defer_init; + +static int +defer_ready(struct defer_batch *b) +{ + for (int c = 0; c < ncpu; c++) + if (cr3_loads[c] == b->snap[c] && cpu_pgdir[c] != 0) + return 0; + return 1; +} + +// With deferlock. Frees the closed batch if it can, then closes the open one. +static void +defer_reclaim(void) +{ + struct defer_batch *closed = &defer_q[defer_open ^ 1]; + if (defer_closed_valid && defer_ready(closed)) { + for (int i = 0; i < closed->n; i++) + kfree((char*)P2V(closed->pa[i])); + closed->n = 0; + defer_closed_valid = 0; + } + struct defer_batch *open = &defer_q[defer_open]; + if (!defer_closed_valid && open->n > 0) { + for (int c = 0; c < ncpu; c++) + open->snap[c] = cr3_loads[c]; + defer_closed_valid = 1; + defer_open ^= 1; + } +} + +void +vm_defer_tick(void) +{ + if (!defer_init) + return; + acquire(&deferlock); + defer_reclaim(); + release(&deferlock); +} + +static void +defer_free(uint64 pa) +{ + if (!defer_init) { + initlock(&deferlock, "vmdefer"); + defer_init = 1; + } + for (;;) { + acquire(&deferlock); + struct defer_batch *open = &defer_q[defer_open]; + if (open->n == DEFER_MAX) + defer_reclaim(); + open = &defer_q[defer_open]; + if (open->n < DEFER_MAX) { + open->pa[open->n++] = pa; + release(&deferlock); + return; + } + release(&deferlock); + yield(); // both batches full: let the CPUs switch + } +} + +// --- TLB shootdown ------------------------------------------------------------ +// +// The deferred free above keeps a stale translation from reaching a page +// someone else now owns. It does not keep one address space coherent: when a +// thread unmaps a range and mmap hands the same addresses straight back, a +// thread on another processor goes on reading and writing the *old* pages +// through its TLB. Its stores vanish and its loads are stale. musl's malloc +// noticed first -- a group's metadata no longer pointing back at it -- and +// Epiphany, forty threads on four processors, died at a different address +// every run. +// +// So whoever unmaps from a shared address space interrupts the processors +// running it and waits until each has reloaded CR3. The vector is one of the +// MSI lines, which trap() already dispatches with an EOI. + +static int tlb_irq = -1; +static volatile int tlb_busy; +static volatile int tlb_acks; + +static void +tlb_ipi(void *arg) +{ + (void)arg; + lcr3(rcr3()); // everything but global pages + __atomic_add_fetch(&tlb_acks, 1, __ATOMIC_RELEASE); +} + +void +tlb_init(void) +{ + tlb_irq = irq_alloc_msi(); + if (tlb_irq < 0 || irq_register(tlb_irq, tlb_ipi, 0, "tlb", IRQ_EDGE_HIGH | IRQ_QUIET) < 0) { + cprintf("vm: no vector for TLB shootdown; threads on several CPUs are unsafe\n"); + tlb_irq = -1; + } +} + +// Every other processor now running pgdir forgets its translations. +static void +tlb_shootdown(pml4e_t *pgdir) +{ + int targets = 0, me; + + if (tlb_irq < 0 || ncpu_online < 2) + return; + // Only one shootdown at a time, taken without disabling interrupts: two + // processors each waiting with interrupts off for the other to answer + // would wait for ever. + while (__atomic_exchange_n(&tlb_busy, 1, __ATOMIC_ACQUIRE)) + __builtin_ia32_pause(); + __atomic_store_n(&tlb_acks, 0, __ATOMIC_RELAXED); + pushcli(); + me = cpunum(); + for (int c = 0; c < ncpu; c++) { + if (c == me || cpu_pgdir[c] != pgdir || !cpus[c].started) + continue; + lapic_send_ipi(cpus[c].apicid, T_IRQ0 + tlb_irq); + targets++; + } + popcli(); + // A processor that switched away in the meantime still answers: the + // reload is harmless. One that never does is said once, not waited on. + uint64 spins = 0; + while (__atomic_load_n(&tlb_acks, __ATOMIC_ACQUIRE) < targets) { + __builtin_ia32_pause(); + if (++spins == 200000000ULL) { + static int said; + if (!said++) + cprintf("vm: a processor did not answer a TLB shootdown\n"); + break; + } + } + __atomic_store_n(&tlb_busy, 0, __ATOMIC_RELEASE); +} + +// After unmapping from pgdir: shoot down if other threads may hold it. +static void +tlb_after_unmap(pml4e_t *pgdir) +{ + struct proc *p = myproc(); + if (p && p->pgdir == pgdir && mm_shared(p->mm)) + tlb_shootdown(pgdir); +} + +// Free a user page just unmapped from pgdir: now, or once no TLB can hold it. +static void +user_page_free(pml4e_t *pgdir, uint64 pa) +{ + struct proc *p = myproc(); + if (p && p->pgdir == pgdir && mm_shared(p->mm)) + defer_free(pa); + else + kfree((char*)P2V(pa)); +} + void switchkvm(void) { + cr3_loads[cpunum()]++; + cpu_pgdir[cpunum()] = 0; lcr3(v2p(kpml4)); } @@ -729,15 +922,19 @@ vm_unmap_range(pml4e_t *pgdir, uint64 va, uint64 length) { uint64 a = PGROUNDDOWN(va); uint64 end = PGROUNDUP(va + length); + int any = 0; for (; a < end; a += PGSIZE) { pte_t *pte = walkpgdir(pgdir, (void*)a, 0); if (pte && (*pte & PTE_P)) { uint64 pa = PTE_ADDR(*pte); - kfree((char*)P2V(pa)); *pte = 0; invlpg((void*)a); + user_page_free(pgdir, pa); + any = 1; } } + if (any) + tlb_after_unmap(pgdir); } // Tear down a mapping without freeing what is underneath it. @@ -753,13 +950,17 @@ vm_unmap_norelease(pml4e_t *pgdir, uint64 va, uint64 length) { uint64 a = PGROUNDDOWN(va); uint64 end = PGROUNDUP(va + length); + int any = 0; for (; a < end; a += PGSIZE) { pte_t *pte = walkpgdir(pgdir, (void*)a, 0); if (pte && (*pte & PTE_P)) { *pte = 0; invlpg((void*)a); + any = 1; } } + if (any) + tlb_after_unmap(pgdir); } // Duplicate an mmap()'d VMA region from src into dst for fork(). @@ -883,13 +1084,13 @@ deallocuvm(pml4e_t *pgdir, uint64 oldsz, uint64 newsz) pa = PTE_ADDR(*pte); if(pa == 0) panic("kfree"); - if(pa < PHYSTOP) { - char *v = P2V(pa); - kfree(v); - } *pte = 0; + invlpg((void*)a); + if(pa < PHYSTOP) + user_page_free(pgdir, pa); } } + tlb_after_unmap(pgdir); return newsz; } @@ -1028,6 +1229,13 @@ copyuvm(pml4e_t *pgdir, uint64 sz) return 0; } +// The PTE for va, if its page tables exist. For vm_fault(). +pte_t * +walkpte(pml4e_t *pgdir, const void *va) +{ + return walkpgdir(pgdir, va, 0); +} + char* uva2ka(pml4e_t *pgdir, char *uva) { @@ -1036,6 +1244,11 @@ uva2ka(pml4e_t *pgdir, char *uva) pte_t *pte; pte = walkpgdir(pgdir, uva, 0); + // Anonymous memory not touched yet: backed now, as the program's own + // access would have done. Only for the running process's own table. + if((pte == 0 || (*pte & PTE_P) == 0) && myproc() && pgdir == myproc()->pgdir && + vm_fault((addr_t)uva) == 0) + pte = walkpgdir(pgdir, uva, 0); if(pte == 0 || (*pte & PTE_P) == 0) return 0; if((*pte & PTE_U) == 0) diff --git a/kernel/core/console.c b/kernel/core/console.c index 2e97a19..6b39994 100644 --- a/kernel/core/console.c +++ b/kernel/core/console.c @@ -119,20 +119,39 @@ vcprintf(char *fmt, va_list ap) continue; } c = fmt[++i] & 0xff; + // Flags and a width are stepped over, and l/ll/z make the conversion + // 64-bit. Not because the output honours them -- it does not pad -- but + // because a conversion this function did not understand used to consume + // no argument, and every one after it then read the wrong one. "%04x" + // before a "%s" handed the %s an integer as a string, and the touchpad + // driver's first message on real hardware was a page fault in here. + while (c == '0' || c == '-' || c == '+' || c == ' ' || c == '#') + c = fmt[++i] & 0xff; + while (c >= '1' && c <= '9') + c = fmt[++i] & 0xff; + while (c >= '0' && c <= '9') + c = fmt[++i] & 0xff; + int wide = 0; + while (c == 'l' || c == 'z') { + wide = 1; + c = fmt[++i] & 0xff; + } if (c == 0) break; switch(c) { case 'd': - print_int(va_arg(ap, int), 10, 1); + case 'i': + print_int(wide ? va_arg(ap, int64) : va_arg(ap, int), 10, 1); break; case 'u': - print_int(va_arg(ap, uint32), 10, 0); - break; - case 'l': - print_int(va_arg(ap, int64), 10, 1); + print_int(wide ? va_arg(ap, uint64) : va_arg(ap, uint32), 10, 0); break; case 'x': - print_int(va_arg(ap, uint32), 16, 0); + case 'X': + print_int(wide ? va_arg(ap, uint64) : va_arg(ap, uint32), 16, 0); + break; + case 'c': + consputc(va_arg(ap, int)); break; case 'p': print_int(va_arg(ap, uint64), 16, 0); @@ -283,7 +302,7 @@ consputc(int c) uartputc('\b'); uartputc(' '); uartputc('\b'); if (!cons_serial_only) { struct vt *v = vt_current(); - vt_putc(v, '\b'); vt_putc(v, ' '); vt_putc(v, '\b'); + vt_feed(v, '\b'); vt_feed(v, ' '); vt_feed(v, '\b'); } return; } @@ -317,7 +336,7 @@ static struct spinlock input_lock; // 1 /dev/console the active terminal, so kernel messages and a // shell started on the console both follow a switch // 10 /dev/tty the caller's controlling terminal -// 11..16 /dev/tty1..6 that terminal, whether or not it is on screen +// 11..22 /dev/tty1..12 that terminal, whether or not it is on screen // // Anything else is the console, which is what an unrecognised minor meant // before there was more than one terminal to get wrong. @@ -433,8 +452,12 @@ erase_one(struct vt *v) v->in.e--; if (v->tio.c_lflag & ECHOE) { if (v == vt_current()) { uartputc('\b'); uartputc(' '); uartputc('\b'); } - if (!cons_serial_only) - vt_putc(v, '\b'); + // Back, blank, back -- and through vt_feed, which keeps the text cursor + // in step. A bare \b only moves (see vt.c), so this used to leave the + // rubbed-out character on screen and the cursor block behind it. + if (!cons_serial_only) { + vt_feed(v, '\b'); vt_feed(v, ' '); vt_feed(v, '\b'); + } } } @@ -442,7 +465,7 @@ erase_one(struct vt *v) // Keyboard input // --------------------------------------------------------------------------- -// Alt+F1..F6, Ctrl+Alt+F1..F6 and the settable chord (AltGr+F1..F6 by +// Alt+F-key, Ctrl+Alt+F-key and the settable chord (Ctrl+Alt+F1..F12 by // default) switch terminals. kbd.c reports them as // KBD_VT_SWITCH+n so that the // decision of what a chord *means* is made here, where the terminals are, @@ -605,6 +628,21 @@ consoleintr(int (*getc)(void)) case KBD_PGDN: esc = "\033[6~"; break; case KBD_INS: esc = "\033[2~"; break; case KBD_DEL: esc = "\033[3~"; break; + // The function keys, as the Linux console sends them -- the strings + // the `linux` terminfo entry lists. Nothing sent them at all, so mc's + // F10 never quit and htop's F-key bar was decoration. + case KBD_F1: esc = "\033[[A"; break; + case KBD_F1 + 1: esc = "\033[[B"; break; + case KBD_F1 + 2: esc = "\033[[C"; break; + case KBD_F1 + 3: esc = "\033[[D"; break; + case KBD_F1 + 4: esc = "\033[[E"; break; + case KBD_F1 + 5: esc = "\033[17~"; break; + case KBD_F1 + 6: esc = "\033[18~"; break; + case KBD_F1 + 7: esc = "\033[19~"; break; + case KBD_F1 + 8: esc = "\033[20~"; break; + case KBD_F1 + 9: esc = "\033[21~"; break; + case KBD_F1 + 10: esc = "\033[23~"; break; + case KBD_F12: esc = "\033[24~"; break; } if (esc) { // Not echoed. In raw mode -- which is where a line editor lives, and @@ -677,9 +715,21 @@ consoleread(struct inode *ip, uint64 off, char *dst, int n) iunlock(ip); target = n; + uint t0 = ticks; acquire(&input_lock); while(n > 0){ while(v->in.r == v->in.w){ + // Raw mode with VMIN 0 is a poll: VTIME tenths of a second at most, and + // none at all when that is 0 too. It waited for a key regardless, and + // btop -- which drains its input with exactly such reads -- hung on the + // read after the last key, so `q` never quit it. + if (!(v->tio.c_lflag & ICANON) && v->tio.c_cc[VMIN] == 0 && + (target - n > 0 || + (uint)(ticks - t0) >= (uint)v->tio.c_cc[VTIME] * (HZ / 10))) { + release(&input_lock); + ilock(ip); + return target - n; + } if (v->in.eof) { v->in.eof = 0; release(&input_lock); @@ -708,7 +758,21 @@ consoleread(struct inode *ip, uint64 off, char *dst, int n) ilock(ip); return -EINTR; } - sleep(&v->in.r, &input_lock); + if (sig_attention(myproc())) { // Ctrl-Z, or a handler to run + release(&input_lock); + int e = sig_blocking_check(); + if (e) { + ilock(ip); + return e; + } + acquire(&input_lock); + continue; + } + // A VTIME wait has to see the clock move as well as a key arrive. + if (!(v->tio.c_lflag & ICANON) && v->tio.c_cc[VMIN] == 0) + sleep(&ticks, &input_lock); + else + sleep(&v->in.r, &input_lock); } c = v->in.buf[v->in.r++ % VT_INPUT_BUF]; *dst++ = c; diff --git a/kernel/core/klog.c b/kernel/core/klog.c index fe034c7..20eb165 100644 --- a/kernel/core/klog.c +++ b/kernel/core/klog.c @@ -267,6 +267,17 @@ klog_len(void) } uint64 klog_prev_bytes(void) { return prev_bytes; } + +// How much of what the ring holds this boot wrote: the tail of it, since the +// ring is read oldest first. GMMOS shows this boot's log, not the last one's. +uint64 +klog_boot_bytes(void) +{ + if (!klog) + return 0; + uint64 n = klog->head - klog->mark; + return n < klog_len() ? n : klog_len(); +} int klog_recovered(void) { return recovered; } // Read at a byte offset into the log, where 0 is the oldest byte still held. diff --git a/kernel/core/main.c b/kernel/core/main.c index dd3c0a2..ce6fa03 100644 --- a/kernel/core/main.c +++ b/kernel/core/main.c @@ -39,6 +39,7 @@ int boot_debug; // the VBE info block, not 0x7000 (startothers() below uses that address for // the AP boot trampoline). #include "arch/schboot.h" +#include "dev/pty.h" // Bootstrap processor starts running C code here. // Allocate a real stack and switch to it, first @@ -265,10 +266,12 @@ kmain(uint64 fb_addr) ioapicinit(); if (boot_verbose) cprintf("verbose boot: ioapicinit done\n"); irq_init(); // must precede any driver init that registers a handler + tlb_init(); // the shootdown vector, before any second CPU runs threads if (boot_verbose) cprintf("verbose boot: irq_init done\n"); // Before the keyboard and mouse, which start producing events the moment // their handlers are registered. inputinit(); + pty_init(); if (boot_verbose) cprintf("verbose boot: inputinit done\n"); // Before consoleinit, because the console writes to a terminal and there // has to be one. vt_init reads the framebuffer geometry, so it also has to @@ -405,6 +408,7 @@ kmain(uint64 fb_addr) netuser_init(); dbg_thread_start(); alarm_thread_start(); + thread_reaper_start(); if (boot_verbose) cprintf("verbose boot: dbg thread started\n"); // The greeting is not printed here. diff --git a/kernel/core/panic.c b/kernel/core/panic.c index 7117cc1..98be107 100644 --- a/kernel/core/panic.c +++ b/kernel/core/panic.c @@ -45,6 +45,8 @@ #include "gfx/theme.h" #include "gfx/logo.h" /* LOGO_H -- the mark's height, for the layout below */ #include "gfx/qr.h" +#include "dev/acpi.h" +#include "gmmos/gmmos.h" extern uint ticks; extern int panicked; @@ -325,7 +327,7 @@ text_at(int x, int y, const char *s, uint32 color) static int draw_qr_panel(int sw, int sh, int ch, char *reason, addr_t *pcs, int npcs) { - char payload[QR_MAX_BYTES]; + char payload[QR_CARD_BYTES]; int n = build_qr_payload(payload, sizeof(payload), reason, pcs, npcs); if (n <= 0 || qr_encode(&panic_qr, (unsigned char *)payload, n) != 0) @@ -388,6 +390,9 @@ trace_line(char *buf, int cap, int i, addr_t pc) /* ------------------------------------------------------------------ */ +// Where paint() put its closing lines, for GMMOS to write into. +static int foot_x, foot_y, foot_step; + static void paint(char *reason, addr_t *pcs, int npcs) { @@ -422,7 +427,7 @@ paint(char *reason, addr_t *pcs, int npcs) // the tops of "Call chain" underlining the line above. So the layout is // measured first -- the text, and room for two frames of the chain -- and // what gives way is the margin, then the logo. Never the report. - int foot = sh - ch * 4 - ch / 2; + int foot = sh - ch * 5 - 4 - ch / 2; int body = (ch + ch / 2) + ch * 2 + ch + nfields * (ch + 2) + ch + (ch + 2) + ch * 2; int logo = 1; @@ -499,11 +504,20 @@ paint(char *reason, addr_t *pcs, int npcs) // // Kept under about 45 characters each: the QR card is on the right, and the // 62-character command line this replaces used to run underneath it. - text_at(left, sh - ch * 4, + // + // Four lines now, the second and fourth GMMOS's: the second says whether + // and when the machine powers off, the fourth which keys do what. Drawn + // here as they stand before GMMOS has looked for a keyboard. + foot_x = left; + foot_y = sh - ch * 5 - 4; + foot_step = ch + 4; + text_at(left, foot_y, "This report is in /proc/kmsg after a restart.", PSOGM_DIM); - text_at(left, sh - ch * 4 + ch + 4, - "schdbg, or Ctrl-G on the console, asks more.", PSOGM_DIM); - text_at(left, sh - ch * 4 + (ch + 4) * 2, + text_at(left, foot_y + foot_step, + boot_verbose ? "schdbg, or Ctrl-G on the console, asks more." + : "The machine powers itself off in 3 minutes.", + PSOGM_DIM); + text_at(left, foot_y + foot_step * 2, "PSOGM -- Purple Screen of Guru Meditation", PSOGM_DIM); gfx_flush_all(); } @@ -554,6 +568,28 @@ report_serial(char *reason, addr_t *pcs, int npcs) static void panic_blink(void) __attribute__((noreturn)); +// The report as it was drawn, so GMMOS can draw it again on the way back +// from one of its own screens. +static char *saved_reason; +static addr_t saved_pcs[10]; + +void +psogm_repaint(void) +{ + paint(saved_reason, saved_pcs, 10); +} + +void +psogm_footer(int *x, int *y, int *step) +{ + *x = foot_x; + *y = foot_y; + *step = foot_step; +} + +volatile int panic_stop; +static volatile int panic_owner = -1; + __attribute__((noreturn)) void panic(char *s) @@ -561,6 +597,21 @@ panic(char *s) addr_t pcs[10]; cli(); + + // One processor reports; any other that panics meanwhile -- the same broken + // state tends to reach several at once -- stops where it is. The owner may + // panic again while drawing, and that nested report is let through. + int me = cpunum(); + int owner = __sync_val_compare_and_swap(&panic_owner, -1, me); + if (owner != -1 && owner != me) + for (;;) { + cli(); + hlt(); + } + // And the rest are stopped outright, see trap(). + panic_stop = 1; + lapic_nmi_others(); + // Stop every other path from writing to the console. consputc checks this // and spins, which is what keeps another CPU from painting over the report // while it is being drawn. @@ -570,8 +621,17 @@ panic(char *s) pcs[i] = 0; getcallerpcs(&s, pcs); + // Counted for the owner alone. A second panic means the first report, or + // GMMOS running after it, is what broke: report that one too, and then only + // blink. Re-entering GMMOS would most likely crash it the same way again. + static int depth; + depth++; + gather(s, pcs, 10); report_serial(s, pcs, 10); + saved_reason = s; + for (int i = 0; i < 10; i++) + saved_pcs[i] = pcs[i]; if (gfx_ready()) { paint(s, pcs, 10); @@ -582,6 +642,8 @@ panic(char *s) } panicked = 1; + if (gfx_ready() && depth == 1) + gmmos_run(s, pcs, 10); panic_blink(); } @@ -634,12 +696,24 @@ kbd_drain_ack(void) // Scroll, Num and Caps, which is bits 0, 1 and 2 of the byte after 0xED. #define LED_ALL 0x07 +// How long the report stays up before the machine powers itself off, on an +// ordinary boot. A crashed laptop otherwise sits with its fans up -- the other +// processors are still running -- until somebody holds the power button, and +// three minutes is long enough to photograph the screen or scan the QR code. +// A verbose boot is somebody debugging on purpose, and keeps it up for ever. +#define PANIC_OFF_MS (3 * 60 * 1000) + static void panic_blink(void) { int on = 0; + uint elapsed = 0; for (;;) { + if (!boot_verbose && elapsed >= PANIC_OFF_MS) { + acpi_poweroff_now(); + elapsed = 0; // still on: blink on, and try again later + } kbd_out(0xED); kbd_drain_ack(); kbd_out(on ? LED_ALL : 0x00); @@ -655,5 +729,6 @@ panic_blink(void) // kernel that died during boot may be missing. for (int i = 0; i < 300; i++) microdelay(1000); + elapsed += 300; } } diff --git a/kernel/core/qr.c b/kernel/core/qr.c index 6d70fbf..a3e6421 100644 --- a/kernel/core/qr.c +++ b/kernel/core/qr.c @@ -2,8 +2,8 @@ // // Freestanding on purpose: no allocation, no floating point, no libc beyond // memset/memmove. It has to run in panic context, where the heap may be exactly -// what is broken. Every buffer here is either on the caller's struct or a -// bounded local. +// what is broken. Every buffer here is on the caller's struct, a bounded +// local, or static. // // The structure follows ISO/IEC 18004 in the order the standard describes it, // which is also the only order in which any of it can be checked: @@ -40,42 +40,66 @@ // every row below, because levels 1..6 at L happen to have equally sized // blocks. qr_encode() asserts exactly that at runtime, which is what turns a // mistyped number here into a visible failure instead of an unscannable symbol. -static const struct { - short total; // total codewords in the symbol - unsigned char ecc; // EC codewords per block - unsigned char blocks; // number of RS blocks -} vinfo[QR_MAX_VERSION + 1] = { - { 0, 0, 0 }, // version 0 does not exist; keeps indexing natural - { 26, 7, 1 }, - { 44, 10, 1 }, - { 70, 15, 1 }, - { 100, 20, 1 }, - { 134, 26, 1 }, - { 172, 18, 2 }, +// Error correction level L, every version. Two numbers per version are +// transcribed from ISO/IEC 18004 table 9 -- EC codewords per block, and how +// many blocks -- and everything else is computed: the symbol's total codeword +// count from its geometry, and the block lengths from the total. Above +// version 5 the blocks are not all one length; the first ones are a codeword +// shorter, and interleaving has to step around the gap. +static const unsigned char ecc_per_block[QR_MAX_VERSION + 1] = { + 0, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, + 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, }; +static const unsigned char ecc_blocks[QR_MAX_VERSION + 1] = { + 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, + 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, + 25, +}; + +// Modules left for data once every function pattern is drawn: the whole +// square, less finders and separators (192), timing (2 * (n - 16)), format +// (31), alignment patterns and, from version 7, the two version blocks (36). +static int +raw_modules(int v) +{ + int r = (16 * v + 128) * v + 64; + if (v >= 2) { + int na = v / 7 + 2; + r -= (25 * na - 10) * na - 55; + if (v >= 7) + r -= 36; + } + return r; +} + +static int +total_codewords(int v) +{ + return raw_modules(v) / 8; +} + +static int +data_codewords(int v) +{ + return total_codewords(v) - ecc_per_block[v] * ecc_blocks[v]; +} + +// Byte mode's character count is 8 bits up to version 9 and 16 from 10. +static int +count_bits(int v) +{ + return v <= 9 ? 8 : 16; +} int qr_capacity(int version) { if (version < 1 || version > QR_MAX_VERSION) return 0; - // Data codewords, less the two bytes of framing: a 4-bit mode indicator, an - // 8-bit character count (versions 1-9), and a 4-bit terminator come to - // exactly two codewords' worth of overhead. - int data = vinfo[version].total - vinfo[version].ecc * vinfo[version].blocks; - return data - 2; + return (data_codewords(version) * 8 - 4 - count_bits(version)) / 8; } -// --------------------------------------------------------------------------- -// GF(256) and Reed-Solomon -// --------------------------------------------------------------------------- - -// Multiply in GF(256) modulo x^8 + x^4 + x^3 + x^2 + 1 (0x11D). -// -// Done by shift-and-add rather than log/exp tables. Two 256-byte tables plus -// their initialisation is more code than this loop, and this needs no init step -// at all -- which matters when the caller is a panic handler that cannot assume -// any particular earlier function ran. static unsigned char gmul(unsigned char x, unsigned char y) { @@ -231,11 +255,37 @@ draw_function_patterns(struct qr *q) draw_finder(q, n - 4, 3); draw_finder(q, 3, n - 4); - // For versions 2-6 the alignment centres are {6, n-7}, and three of those - // four combinations collide with a finder. Only the bottom-right survives, - // which is why there is no position table here. Version 7 would need one. - if (q->size > 21) - draw_alignment(q, n - 7, n - 7); + // Alignment patterns on a grid: the same list of centres on both axes, + // every combination drawn except the three that would sit on a finder. The + // list starts at 6, ends at n-7, and steps evenly (in even numbers) between, + // except version 32, which the standard spaces by hand. + int v = (n - 17) / 4; + if (v >= 2) { + int na = v / 7 + 2, pos[7]; + int step = v == 32 ? 26 : (v * 4 + na * 2 + 1) / (na * 2 - 2) * 2; + pos[0] = 6; + for (int i = na - 1, p = n - 7; i >= 1; i--, p -= step) + pos[i] = p; + for (int i = 0; i < na; i++) + for (int j = 0; j < na; j++) + if (!((i == 0 && j == 0) || (i == 0 && j == na - 1) || + (i == na - 1 && j == 0))) + draw_alignment(q, pos[i], pos[j]); + } + + // From version 7 the version number is written out too, 6 bits and a + // BCH(18,6) check, in a 3x6 block beside each of the two far finders. + if (v >= 7) { + int rem = v; + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + long bits = ((long)v << 12) | rem; + for (int i = 0; i < 18; i++) { + int bit = (bits >> i) & 1, a = n - 11 + i % 3, b = i / 3; + set_fn(q, a, b, bit); + set_fn(q, b, a, bit); + } + } draw_format(q, 0); // reserve; overwritten once a mask is chosen } @@ -357,25 +407,24 @@ qr_encode(struct qr *q, const unsigned char *data, int len) if (v > QR_MAX_VERSION) return -1; - int total = vinfo[v].total; - int ecclen = vinfo[v].ecc; - int blocks = vinfo[v].blocks; - int datacw = total - ecclen * blocks; - - // The table claims equal-sized blocks for every version this file supports. - // If that is ever untrue the interleave below is silently wrong, so check it - // rather than trust it. - if (datacw % blocks != 0) - return -1; - int blocklen = datacw / blocks; + int blocks = ecc_blocks[v]; + int ecclen = ecc_per_block[v]; + int total = total_codewords(v); + int datacw = data_codewords(v); + int nshort = blocks - total % blocks; // blocks one data codeword short + int shortlen = total / blocks - ecclen; // their data length // --- 1. payload -> data codewords --------------------------------------- - unsigned char cw[172]; // >= max total codewords + // + // Static, not on the stack: version 40 is 3,706 codewords, and this runs on + // whatever stack a panic left. Not reentrant, which nothing here needs. + static unsigned char cw[3706], out[3706]; + static unsigned char ec[25][30]; memset(cw, 0, sizeof(cw)); int pos = 0; put_bits(cw, &pos, 0x4, 4); // byte mode - put_bits(cw, &pos, (unsigned)len, 8); + put_bits(cw, &pos, (unsigned)len, count_bits(v)); for (int i = 0; i < len; i++) put_bits(cw, &pos, data[i], 8); @@ -393,20 +442,25 @@ qr_encode(struct qr *q, const unsigned char *data, int len) cw[i] = (i - pos / 8) % 2 == 0 ? 0xEC : 0x11; // --- 2 and 3. error correction, then interleave ------------------------- + // + // Block b starts at start(b): the short blocks first, then the long ones. unsigned char gen[30]; - unsigned char ec[4][30]; rs_divisor(ecclen, gen); +#define BSTART(b) ((b) * shortlen + ((b) > nshort ? (b) - nshort : 0)) +#define BLEN(b) (shortlen + ((b) >= nshort)) for (int b = 0; b < blocks; b++) - rs_remainder(&cw[b * blocklen], blocklen, gen, ecclen, ec[b]); + rs_remainder(&cw[BSTART(b)], BLEN(b), gen, ecclen, ec[b]); - unsigned char out[172]; int o = 0; - for (int i = 0; i < blocklen; i++) + for (int i = 0; i <= shortlen; i++) for (int b = 0; b < blocks; b++) - out[o++] = cw[b * blocklen + i]; + if (i < BLEN(b)) + out[o++] = cw[BSTART(b) + i]; for (int i = 0; i < ecclen; i++) for (int b = 0; b < blocks; b++) out[o++] = ec[b][i]; +#undef BSTART +#undef BLEN // --- 4. function patterns ------------------------------------------------ memset(q, 0, sizeof(*q)); diff --git a/kernel/core/spinlock.c b/kernel/core/spinlock.c index 4484054..2eda2d5 100644 --- a/kernel/core/spinlock.c +++ b/kernel/core/spinlock.c @@ -90,7 +90,19 @@ getstackpcs(addr_t *rbp, addr_t pcs[]) if(((addr_t)rbp & 7) != 0) break; pcs[i] = rbp[1]; - rbp = (addr_t*)rbp[0]; + // The next frame is the caller's, further up the same stack: higher, and + // by less than a whole kernel stack. The chain ends at the system call or + // interrupt entry, and the rbp saved there is the user's -- which an + // optimised program uses as an ordinary register. lbreakouthd left -1024 + // in it, 0xfffffffffffffc00, which passed every test above and faulted + // here under the syscall tracer, taking the kernel down from inside the + // lock it was recording. + addr_t *next = (addr_t*)rbp[0]; + if(next <= rbp || (addr_t)next - (addr_t)rbp >= KSTACKSIZE){ + i++; // keep the return address just read + break; + } + rbp = next; } for(; i < 10; i++) pcs[i] = 0; diff --git a/kernel/dev/audio.c b/kernel/dev/audio.c index 9fa174a..02a151c 100644 --- a/kernel/dev/audio.c +++ b/kernel/dev/audio.c @@ -26,6 +26,16 @@ static int audio_lock_init; // to be told once and then have it stay told. static struct audio_format current_fmt = { 48000, 2, AUDIO_FMT_S16LE }; +// The OSS view of the stream, for clients that track a play pointer in a ring +// rather than just writing: bytes accepted by the driver since boot, where the +// last reset left the count, the ring geometry SETFRAGMENT asked for, and the +// played count GETOPTR last reported (for its fragment count). +static uint64 queued_total; +static uint64 reset_base; +static uint64 last_optr; +static int frag_size = 4096; +static int frag_count = 16; + static void lock_once(void) { @@ -114,6 +124,10 @@ audio_write(const void *pcm, int bytes) } done += n; + lock_once(); + acquire(&audio_lock); + queued_total += (uint64)n; + release(&audio_lock); } if (o->start) @@ -211,6 +225,45 @@ dspclose(struct inode *ip) audio_stop(); } +// Bytes the speaker has played since boot: everything queued, less what is +// still waiting. A reset discards the waiting part, which then counts as played +// -- the count only goes forward, and GETOPTR measures from the reset. +static uint64 +played_total(struct device *d, const struct audio_ops *o) +{ + uint64 q, p; + int pend = o->pending ? o->pending(d) : 0; + + lock_once(); + acquire(&audio_lock); + q = queued_total; + release(&audio_lock); + p = (pend > 0 && (uint64)pend <= q) ? q - (uint64)pend : q; + return p; +} + +// Writable once a whole fragment fits, as OSS defines it. ALSA's plugin sets +// the fragment to its period and waits in poll() for one to be free; reporting +// writable at the first free byte had it wake, find less than a period of +// room, and poll again -- 76% of a CPU to play a sine wave. +int +dsp_writable(void) +{ + struct device *d = audio_get(); + const struct audio_ops *o = ops_of(d); + int want; + + if (!o || !o->space) + return 0; + lock_once(); + acquire(&audio_lock); + want = frag_size; + release(&audio_lock); + if (want > 65536) + want = 65536; // never more than a driver's ring can free at once + return o->space(d) >= want; +} + int dsp_ioctl(int request, uint64 argp) { @@ -262,18 +315,78 @@ dsp_ioctl(int request, uint64 argp) audio_drain(); return 0; - case SNDCTL_DSP_RESET: + case SNDCTL_DSP_RESET: { audio_stop(); + uint64 p = played_total(d, o); + acquire(&audio_lock); + reset_base = last_optr = p; + release(&audio_lock); return 0; + } + + // The ring the client thinks in. The driver's own ring is whatever it is; + // these only shape the numbers GETOPTR and GETOSPACE report, which is all a + // client does with them. Fragments of 256 bytes to 64 KiB, 2 to 1024 of them. + case SNDCTL_DSP_SETFRAGMENT: { + if (copy_from_user(&val, argp, sizeof(val)) < 0) + return -EFAULT; + int shift = val & 0xffff, count = (val >> 16) & 0xffff; + if (shift < 8) shift = 8; + if (shift > 16) shift = 16; + if (count < 2) count = 2; + if (count > 1024 || count == 0x7fff) count = 1024; + acquire(&audio_lock); + frag_size = 1 << shift; + frag_count = count; + release(&audio_lock); + val = (count << 16) | shift; + return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0; + } + + case SNDCTL_DSP_GETFMTS: + val = AFMT_S16_LE; + return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0; + + case SNDCTL_DSP_GETCAPS: + val = DSP_CAP_TRIGGER; + return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0; + + // Enabling output starts whatever is queued. Disabling is accepted and does + // nothing: the stream stops by itself when the queue runs dry, and stopping + // it here would throw away what is queued, which a pause must not. + case SNDCTL_DSP_SETTRIGGER: + if (copy_from_user(&val, argp, sizeof(val)) < 0) + return -EFAULT; + if ((val & PCM_ENABLE_OUTPUT) && o->start) + o->start(d); + return 0; + + case SNDCTL_DSP_GETOPTR: { + struct count_info ci; + uint64 p = played_total(d, o); + acquire(&audio_lock); + uint64 since = p - reset_base; + uint64 ring = (uint64)frag_size * (uint64)frag_count; + ci.bytes = (int)since; + ci.blocks = (int)((p - last_optr) / (uint64)frag_size); + ci.ptr = (int)(since % ring); + last_optr = p; + release(&audio_lock); + return copy_to_user(argp, &ci, sizeof(ci)) < 0 ? -EFAULT : 0; + } + + case SNDCTL_DSP_GETODELAY: + val = o->pending ? o->pending(d) : 0; + return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0; case SNDCTL_DSP_GETOSPACE: { struct audio_buf_info bi; int space = o->space ? o->space(d) : 0; - bi.fragsize = 4096; + bi.fragsize = frag_size; bi.bytes = space; bi.fragments = space / bi.fragsize; - bi.fragstotal = 0; + bi.fragstotal = frag_count; if (copy_to_user(argp, &bi, sizeof(bi)) < 0) return -EFAULT; return 0; diff --git a/kernel/dev/cdev.c b/kernel/dev/cdev.c index 41e7406..9dbe67e 100644 --- a/kernel/dev/cdev.c +++ b/kernel/dev/cdev.c @@ -7,6 +7,7 @@ #include "fs/file.h" #include "arch/x86.h" #include "uapi/errno.h" +#include "dev/pty.h" extern int consoleread(struct inode*, uint64, char*, int); extern int consolewrite(struct inode*, uint64, char*, int); @@ -148,9 +149,10 @@ memread(struct inode *ip, uint64 off, char *dst, int n) case 1: return consoleread(ip, off, dst, n); case 2: return nullread(ip, off, dst, n); case 3: return consoleread(ip, off, dst, n); - /* 10 = /dev/tty, 11..16 = /dev/tty1..tty6. consoleread resolves the + /* 10 = /dev/tty, 11..22 = /dev/tty1..tty12. consoleread resolves the minor to a terminal; see vt_for_minor(). */ case 10: case 11: case 12: case 13: case 14: case 15: case 16: + case 17: case 18: case 19: case 20: case 21: case 22: return consoleread(ip, off, dst, n); case 5: return zeroread(ip, off, dst, n); case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros @@ -168,6 +170,7 @@ memwrite(struct inode *ip, uint64 off, char *dst, int n) switch(ip->minor) { case 1: return consolewrite(ip, off, dst, n); case 10: case 11: case 12: case 13: case 14: case 15: case 16: + case 17: case 18: case 19: case 20: case 21: case 22: return consolewrite(ip, off, dst, n); case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC case 3: // /dev/keyboard: discarded @@ -216,4 +219,10 @@ struct devsw devsw[] = { // The close hook plays out what is still queued: a program that writes a // sound and exits expects to hear the end of it. [12] = { dspread, dspwrite, 0, dspclose }, + // Pseudo-terminals, kernel/dev/pty.c. /dev/ptmx (13) is never read or + // written: open() swaps it for a new master (14) before it gets the chance. + // The slaves (15) are /dev/pts/N. + [PTMX_MAJOR] = { 0, 0 }, + [PTM_MAJOR] = { ptm_read, ptm_write, ptm_ready, ptm_close, ptm_open }, + [PTS_MAJOR] = { pts_read, pts_write, pts_ready, pts_close, pts_open }, }; diff --git a/kernel/dev/dmi.c b/kernel/dev/dmi.c index 25ec0e7..550a049 100644 --- a/kernel/dev/dmi.c +++ b/kernel/dev/dmi.c @@ -273,7 +273,7 @@ dmi_init(void) find(); if (tab == 0) { - cprintf("DMI: not present or invalid.\n"); + kinfo("DMI: not present or invalid.\n"); return; } if (dmi_field(DMI_SYS_VENDOR, v, sizeof v) < 0) v[0] = 0; @@ -281,5 +281,5 @@ dmi_init(void) if (dmi_field(DMI_BOARD_NAME, b, sizeof b) < 0) b[0] = 0; if (dmi_field(DMI_BIOS_VERSION, bv, sizeof bv) < 0) bv[0] = 0; if (dmi_field(DMI_BIOS_DATE, bd, sizeof bd) < 0) bd[0] = 0; - cprintf("DMI: %s %s/%s, BIOS %s %s\n", v, p, b, bv, bd); + kinfo("DMI: %s %s/%s, BIOS %s %s\n", v, p, b, bv, bd); } diff --git a/kernel/dev/fb.c b/kernel/dev/fb.c index 0a62efd..268f30f 100644 --- a/kernel/dev/fb.c +++ b/kernel/dev/fb.c @@ -147,6 +147,23 @@ fbclose(struct inode *ip) // the largest struct here -- fb_fix_screeninfo is half the size of // fb_var_screeninfo, and a buffer sitting near the end of a mapping is a // legitimate place to put the smaller one. +// Timings for a mode the firmware set and never described. GOP and VBE give a +// size and a pixel format and nothing about the signal, so these are made up -- +// and made up the way Linux's efifb makes them up, which is what every program +// that reads them was written against. They used to be zero, which is not "no +// answer" but the answer "0 Hz": Xorg's fbdev driver built a RandR mode with a +// dot clock of 0 from it, and SDL 3 sized a fullscreen window from that mode +// as 1x1. Physical size stays zero; nothing reads it as a number. +static void +nominal_timings(struct fb_var_screeninfo *v) +{ + if (v->xres == 0 || v->yres == 0) + return; + v->pixclock = 10000000 / v->xres * 1000 / v->yres; + v->left_margin = (v->xres / 8) & 0xf8; + v->hsync_len = (v->xres / 8) & 0xf8; +} + int fb_ioctl(int request, addr_t arg) { @@ -169,8 +186,7 @@ fb_ioctl(int request, addr_t arg) v->bits_per_pixel = gfx_fb_bpp(); v->activate = FB_ACTIVATE_NOW; pixel_format(v, v->bits_per_pixel); - // height/width (physical millimetres) and every timing field stay zero: - // VBE does not report them and a guess would be worse than an admission. + nominal_timings(v); return 0; } @@ -212,6 +228,7 @@ fb_ioctl(int request, addr_t arg) v->bits_per_pixel = gfx_fb_bpp(); v->activate = act; pixel_format(v, v->bits_per_pixel); + nominal_timings(v); return 0; } diff --git a/kernel/dev/gfx.c b/kernel/dev/gfx.c index d5c9b50..c64fa08 100644 --- a/kernel/dev/gfx.c +++ b/kernel/dev/gfx.c @@ -406,6 +406,11 @@ draw_glyph_px(int x, int y, uint32 cp, uint32 fg, uint32 bg) for (int c = 0; c < cell_w; c++) gfx_draw_pixel(x + c, y + r, bg); + // Dirty before the early return: a blank cell is a background fill and + // nothing else, and that fill is what rubs the text cursor out. Marked + // only after the glyph, the blank was drawn to the back buffer and never + // copied to the screen -- a block left at the end of every line. + dirty(x, y, x + cell_w - 1, y + cell_h - 1); if (cp == ' ') return; // nothing to draw over the background @@ -473,10 +478,16 @@ gfx_font_init(void) } } - // 16 pixels per em. The bitmap it replaces is 16 tall, so the terminal keeps - // the number of rows it had and nothing that assumed a screenful has to - // change. - if (ttf_open(&consttf, data, len, 16) != 0) { + // The size follows the screen: about 45 rows whatever the resolution, and + // never under 16 pixels per em, the bitmap's height. A fixed 16 was right + // on 1024x768 and unreadable on a 1440p monitor -- and asking for a smaller + // mode to get bigger text is no answer, because firmware on real machines + // often "sets" a smaller mode by leaving the panel at its native size and + // misreporting the row length, which is the sheared, repeated screen. + int em = scanout.height / 45; + if (em < 16) em = 16; + if (em > 40) em = 40; + if (ttf_open(&consttf, data, len, em) != 0) { // A bad file on the ESP must not cost the console its font. Nothing has // been changed yet at this point, so the built-in one is still there to // fall back to -- and it is worth saying, because a font that silently @@ -487,7 +498,7 @@ gfx_font_init(void) "using the built-in one\n"); data = _binary_consfont_start; len = (uint32)(_binary_consfont_end - _binary_consfont_start); - if (ttf_open(&consttf, data, len, 16) != 0) + if (ttf_open(&consttf, data, len, em) != 0) return; } diff --git a/kernel/dev/input.c b/kernel/dev/input.c index 228e6c2..a37c82e 100644 --- a/kernel/dev/input.c +++ b/kernel/dev/input.c @@ -19,6 +19,7 @@ #include "fs/fs.h" #include "fs/file.h" #include "dev/input.h" +#include "dev/kbd.h" #include "core/uaccess.h" #include "gfx/gfx.h" #include "uapi/errno.h" @@ -230,6 +231,9 @@ input_grab(int dev, int on) // accident unless the kernel says which it was. cprintf("input: event%d %s by pid %d\n", dev, on ? "grabbed" : "released", myproc() ? myproc()->pid : -1); + // The holder kept its own lights; the console's come back with it. + if(!on && dev == INPUT_KBD) + kbd_set_leds(kbd_lock_state()); } return 0; } @@ -433,11 +437,31 @@ inputread(struct inode *ip, uint64 off, char *dst, int n) return got; } -// evdev accepts writes to set keyboard LEDs. There are no LEDs to set here, -// and a program that writes one should not see an error for it. +// evdev accepts writes to set keyboard LEDs, and X sets them this way: it +// keeps its own lock state while it holds the keyboard. Each write is +// struct input_event -- a 16-byte time, type, code, value -- and EV_LED's +// codes 0, 1 and 2 are Num, Caps and Scroll Lock. Anything else is accepted +// and ignored. int inputwrite(struct inode *ip, uint64 off, char *src, int n) { + static uint xleds; + int changed = 0; + + for (int k = 0; k + 24 <= n; k += 24) { + uint16 type, code; + int32 value; + memmove(&type, src + k + 16, 2); + memmove(&code, src + k + 18, 2); + memmove(&value, src + k + 20, 4); + if (type != 0x11 || code > 2) // EV_LED + continue; + uint bit = code == 0 ? NUMLOCK : code == 1 ? CAPSLOCK : SCROLLLOCK; + xleds = value ? (xleds | bit) : (xleds & ~bit); + changed = 1; + } + if (changed) + kbd_set_leds(xleds); return n; } diff --git a/kernel/dev/kbd.c b/kernel/dev/kbd.c index f7cc3b5..cb36b51 100644 --- a/kernel/dev/kbd.c +++ b/kernel/dev/kbd.c @@ -1,6 +1,7 @@ #include "core/types.h" #include "arch/x86.h" #include "core/defs.h" +#include "core/spinlock.h" #include "dev/kbd.h" #include "uapi/posix.h" #include "dev/vt.h" /* NVT -- how many terminals the chords reach */ @@ -9,19 +10,40 @@ // The chord, settable at run time through VT_SETCHORD. // -// AltGr+F1..F6 by default. Every other candidate is taken by the host, by -// applications, or by the keyboard layout -- see include/dev/kbd.h -- and AltGr -// with a function key is taken by none of the three: a Polish layout wants -// AltGr+*letter*, which still reaches the shell untouched, and no window -// manager binds AltGr with an F-key. +// Ctrl+Alt+F1..F12 by default, the same keys X switches with. It used to be +// AltGr+F-key, chosen because nothing else claimed it -- but under X the +// server owns the keyboard and only Ctrl+Alt+F-key works, so the console and +// the desktop answered different keys for the same thing. // -// AltGr+minus and AltGr+equals step to the previous and next terminal on the -// same modifiers, for when you have two and do not care which number they are. +// Ctrl+Alt+minus and Ctrl+Alt+equals step to the previous and next terminal, +// for when you have two and do not care which number they are. // -// Zero modifiers disables all of it; `chvt N` still works. -uint vt_chord_mods = RALT; +// Zero modifiers disables the chord; `chvt N` still works. +uint vt_chord_mods = CTL | ALT; uint vt_chord_base = SCAN_F1; +// Which terminal an F-key scancode names: F1..F10 are 0x3B..0x44 and F11 and +// F12 sit apart, at 0x57 and 0x58. -1 for anything else. +static int +fkey_index(uint data) +{ + if (data >= SCAN_F1 && data < SCAN_F1 + 10) return (int)(data - SCAN_F1); + if (data == 0x57) return 10; + if (data == 0x58) return 11; + return -1; +} + +// The terminal a chord key names, from `base`: the F-keys by fkey_index, any +// other base as a run of ten consecutive scancodes -- a digit row is 1..9, 0, +// and the minus and equals after it are already the previous and next keys. +static int +chord_index(uint data, uint base) +{ + int i = base == SCAN_F1 ? fkey_index(data) + : (data >= base && data < base + 10 ? (int)(data - base) : -1); + return i < NVT ? i : -1; +} + // Which scancodes are modifiers, and which are locks. Hardware facts rather // than layout ones -- every layout agrees that 0x2A is a Shift key -- so they // stay here while the character tables live in keymap.c. @@ -78,6 +100,104 @@ queue_utf8(uint32 cp) return pend[pend_i++]; } +// --- the lock keys and their lights ------------------------------------------- +// +// Caps, Num and Scroll Lock are one state for the machine, whichever keyboard +// pressed them -- the laptop's own and a USB one agree -- and the lights on +// every keyboard show it. They never did: caps lock worked and its light +// stayed dark. Under X the server keeps its own lock state and sets the +// lights through /dev/input (see inputwrite), so these are left to it while +// it holds the keyboard. + +static uint kbd_locks; // CAPSLOCK | NUMLOCK | SCROLLLOCK +static uint lock_held; // lock keys down now: a held key repeats +static struct spinlock ledlock; +static int ledlock_ready; + +// The PS/2 side is a two-byte command, 0xED and the lights, and each byte +// must be acknowledged (0xFA) before the next is sent. That cannot be waited +// for here -- this runs in the keyboard's interrupt, and the acknowledgement +// *is* a keyboard interrupt -- so the second byte goes out when kbdgetc sees +// the first one acknowledged. +static int led_want = -1; // PS/2 light byte still to send +static int led_stage; // 0 idle, 1 0xED sent, 2 the byte sent +static uint led_since; + +static void +ps2_led_kick(void) +{ + if (led_stage != 0 && ticks - led_since > HZ) + led_stage = 0; // no keyboard answered: start over + if (led_stage == 0 && led_want >= 0 && !(inb(KBSTATP) & 0x02)) { + outb(KBDATAP, 0xED); + led_stage = 1; + led_since = ticks; + } +} + +// Show `locks` on every keyboard. +void +kbd_set_leds(uint locks) +{ + if (!ledlock_ready) { + initlock(&ledlock, "kbdled"); + ledlock_ready = 1; + } + acquire(&ledlock); + led_want = ((locks & SCROLLLOCK) ? 1 : 0) | ((locks & NUMLOCK) ? 2 : 0) | + ((locks & CAPSLOCK) ? 4 : 0); + ps2_led_kick(); + release(&ledlock); + // USB numbers them the other way round: Num, Caps, Scroll. + hid_leds_want(((locks & NUMLOCK) ? 1 : 0) | ((locks & CAPSLOCK) ? 2 : 0) | + ((locks & SCROLLLOCK) ? 4 : 0)); +} + +// A lock key pressed, on any keyboard. `down` 0 is its release. +void +kbd_lock_key(uint lock, int down) +{ + if (!down) { + lock_held &= ~lock; + return; + } + if (lock_held & lock) + return; // the keyboard's repeat, not a new press + lock_held |= lock; + kbd_locks ^= lock; + if (!input_is_grabbed(INPUT_KBD)) + kbd_set_leds(kbd_locks); +} + +uint +kbd_lock_state(void) +{ + return kbd_locks; +} + +// 0xFA and 0xFE from the keyboard: answers to a light command, not keys. +static int +ps2_led_answer(uint data) +{ + if (led_stage == 0 || (data != 0xFA && data != 0xFE)) + return 0; + if (!ledlock_ready) + return 0; + acquire(&ledlock); + if (data == 0xFE) { + led_stage = 0; // "resend": try again with the next change + } else if (led_stage == 1 && led_want >= 0) { + outb(KBDATAP, (uchar)led_want); + led_want = -1; + led_stage = 2; + } else { + led_stage = 0; + ps2_led_kick(); // changed again meanwhile + } + release(&ledlock); + return 1; +} + int kbdgetc(void) { @@ -98,6 +218,9 @@ kbdgetc(void) data = inb(KBDATAP); + if(ps2_led_answer(data)) + return 0; + if(data == 0xE0){ shift |= E0ESC; return 0; @@ -116,9 +239,10 @@ kbdgetc(void) if(data & 0x80){ data = (shift & E0ESC ? data : data & 0x7F); shift &= ~(shiftcode[data] | E0ESC); + if(togglecode[data]) kbd_lock_key(togglecode[data], 0); } else { shift |= shiftcode[data]; - shift ^= togglecode[data]; + if(togglecode[data]) kbd_lock_key(togglecode[data], 1); } return 0; } @@ -126,6 +250,7 @@ kbdgetc(void) if(data & 0x80){ data = (shift & E0ESC ? data : data & 0x7F); shift &= ~(shiftcode[data] | E0ESC); + if(togglecode[data]) kbd_lock_key(togglecode[data], 0); return 0; } else if(shift & E0ESC){ data |= 0x80; @@ -133,7 +258,9 @@ kbdgetc(void) } shift |= shiftcode[data]; - shift ^= togglecode[data]; + if(togglecode[data]) kbd_lock_key(togglecode[data], 1); + // The locks are the machine's, not this keyboard's: see kbd_lock_key(). + shift = (shift & ~(CAPSLOCK | NUMLOCK | SCROLLLOCK)) | kbd_locks; // The switch chord, before the character tables, because there is no // character to look up: it is a combination that means "show me a different @@ -146,21 +273,20 @@ kbdgetc(void) // modifiers named, and nothing else held. if (vt_chord_mods != 0 && (shift & (CTL | SHIFT | ALT | RALT)) == vt_chord_mods) { - if (data >= vt_chord_base && data < vt_chord_base + NVT) - return KBD_VT_SWITCH + (data - vt_chord_base); + int i = chord_index(data, vt_chord_base); + if (i >= 0) + return KBD_VT_SWITCH + i; if (data == SCAN_MINUS) return KBD_VT_PREV; if (data == SCAN_EQUALS) return KBD_VT_NEXT; } - // And Linux's own: Alt+F1..F6 and Ctrl+Alt+F1..F6, whatever the chord is set - // to. These are what anyone who has used Linux presses, on a real machine, - // and nothing reached them: SchOS answered only AltGr+F-keys, which no one - // thinks to try. Under X the terminal is in VT_PROCESS, console_vt_hotkey() - // lets the keys through, and X switches with them itself -- as on Linux. - if (data >= SCAN_F1 && data < SCAN_F1 + NVT && + // And Linux's own: Alt+F-key and Ctrl+Alt+F-key, whatever the chord is set + // to. Under X the terminal is in VT_PROCESS, console_vt_hotkey() lets the + // keys through, and X switches with them itself -- as on Linux. + if (chord_index(data, SCAN_F1) >= 0 && ((shift & (CTL | SHIFT | ALT | RALT)) == ALT || (shift & (CTL | SHIFT | ALT | RALT)) == (CTL | ALT))) - return KBD_VT_SWITCH + (data - SCAN_F1); + return KBD_VT_SWITCH + chord_index(data, SCAN_F1); cp = keymap_lookup(data, shift); if (cp == 0) diff --git a/kernel/dev/kdev.c b/kernel/dev/kdev.c new file mode 100644 index 0000000..a130091 --- /dev/null +++ b/kernel/dev/kdev.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// See include/dev/kdev.h. + +#include "core/types.h" +#include "core/defs.h" +#include "core/param.h" +#include "core/spinlock.h" +#include "core/sleeplock.h" +#include "fs/fs.h" +#include "fs/blkdev.h" +#include "dev/kdev.h" +#include "dev/pty.h" +#include "net/socket.h" + +static const char *const subsys[] = { + "input", "mem", "tty", "graphics", "block", +}; +#define NSUBSYS ((int)(sizeof subsys / sizeof subsys[0])) + +#define NO_PARENT (-1) + +// Majors and minors are the ones /dev reports, devsw indices and all -- the +// devnodes table in kernel/fs/devfs.c. udev compares what it reads here with +// what stat() says about the node, so these have to be the same numbers. +static const struct kdev table[] = { + { "input", "input0", "", 0, 0, 0, NO_PARENT, KD_KEYBOARD }, + { "input", "event0", "input/event0", 7, 0, 0, 0, KD_NONE }, + { "input", "input1", "", 0, 0, 0, NO_PARENT, KD_MOUSE }, + { "input", "event1", "input/event1", 7, 1, 0, 2, KD_NONE }, + + { "mem", "null", "null", 1, 2, 0, NO_PARENT, KD_NONE }, + { "mem", "zero", "zero", 1, 5, 0, NO_PARENT, KD_NONE }, + { "mem", "full", "full", 1, 7, 0, NO_PARENT, KD_NONE }, + { "mem", "random", "random", 1, 8, 0, NO_PARENT, KD_NONE }, + { "mem", "urandom", "urandom", 1, 9, 0, NO_PARENT, KD_NONE }, + + { "tty", "console", "console", 1, 1, 0, NO_PARENT, KD_NONE }, + { "tty", "tty", "tty", 1, 10, 0, NO_PARENT, KD_NONE }, + { "tty", "tty0", "tty0", 1, 0, 0, NO_PARENT, KD_NONE }, + { "tty", "tty1", "tty1", 1, 11, 0, NO_PARENT, KD_NONE }, + { "tty", "tty2", "tty2", 1, 12, 0, NO_PARENT, KD_NONE }, + { "tty", "tty3", "tty3", 1, 13, 0, NO_PARENT, KD_NONE }, + { "tty", "tty4", "tty4", 1, 14, 0, NO_PARENT, KD_NONE }, + { "tty", "tty5", "tty5", 1, 15, 0, NO_PARENT, KD_NONE }, + { "tty", "tty6", "tty6", 1, 16, 0, NO_PARENT, KD_NONE }, + { "tty", "tty7", "tty7", 1, 17, 0, NO_PARENT, KD_NONE }, + { "tty", "ptmx", "ptmx", PTMX_MAJOR, 0, 0, NO_PARENT, KD_NONE }, + + { "graphics", "fb0", "fb0", 2, 0, 0, NO_PARENT, KD_NONE }, +}; +#define NTABLE ((int)(sizeof table / sizeof table[0])) + +int kdev_nsubsys(void) { return NSUBSYS; } +const char *kdev_subsys(int s) { return s >= 0 && s < NSUBSYS ? subsys[s] : 0; } + +int +kdev_nth(int n) +{ + if (n < NTABLE) + return n < 0 ? -1 : n; + n -= NTABLE; + for (int k = 0, b; (b = blkdev_nth(k)) >= 0; k++) + if (n-- == 0) + return KDEV_BLK0 + b; + return -1; +} + +static void +copy(char *d, const char *s, int cap) +{ + int i = 0; + while (s[i] && i < cap - 1) { d[i] = s[i]; i++; } + d[i] = 0; +} + +int +kdev_get(int i, struct kdev *k) +{ + if (i >= 0 && i < NTABLE) { + *k = table[i]; + return 0; + } + if (i >= KDEV_BLK0 && blkdev_present((uint)(i - KDEV_BLK0))) { + memset(k, 0, sizeof(*k)); + copy(k->subsys, "block", sizeof k->subsys); + copy(k->name, blkdev_name((uint)(i - KDEV_BLK0)), sizeof k->name); + copy(k->devname, k->name, sizeof k->devname); + k->major = 8; + k->minor = (short)(i - KDEV_BLK0); + k->block = 1; + k->parent = NO_PARENT; + return 0; + } + return -1; +} + +void +kdev_numbers(const struct kdev *k, int *maj, int *min) +{ + if (k->block) { + *maj = k->major; + *min = k->minor; + return; + } + dev_stat_numbers(k->major, k->minor, maj, min); +} + +int +kdev_path(int i, char *buf, int cap) +{ + struct kdev k; + char tmp[160]; + int n = 0; + + if (kdev_get(i, &k) < 0) + return -1; + // The top ancestor names the subsystem directory; below it, one directory + // per level. + int chain[4], depth = 0; + for (int c = i; c >= 0 && depth < 4; ) { + struct kdev ck; + if (kdev_get(c, &ck) < 0) + break; + chain[depth++] = c; + c = ck.parent; + } + copy(tmp, "devices/virtual/", sizeof tmp); + n = (int)strlen(tmp); + { + struct kdev top; + kdev_get(chain[depth - 1], &top); + copy(tmp + n, top.subsys, (int)sizeof tmp - n); + n += (int)strlen(tmp + n); + } + for (int d = depth - 1; d >= 0 && n < (int)sizeof tmp - 2; d--) { + struct kdev ck; + kdev_get(chain[d], &ck); + tmp[n++] = '/'; + copy(tmp + n, ck.name, (int)sizeof tmp - n); + n += (int)strlen(tmp + n); + } + copy(buf, tmp, cap); + return (int)strlen(buf); +} + +static uint kseq; + +// One counter for every uevent the kernel sends, whoever builds it. +uint +kdev_seqnum(void) +{ + return ++kseq; +} + +static int +put(char *b, int cap, int n, const char *s) +{ + while (*s && n < cap - 1) + b[n++] = *s++; + return n; +} + +static int +putnum(char *b, int cap, int n, uint v) +{ + char t[12]; + int k = 0; + do { t[k++] = '0' + v % 10; v /= 10; } while (v); + while (k && n < cap - 1) + b[n++] = t[--k]; + return n; +} + +// KEY=value pairs, NUL separated, as the kernel sends them; `nl` swaps the +// NULs for newlines, which is what the uevent file shows. +static int +vars(int i, char *buf, int cap, const char *action, int nl) +{ + struct kdev k; + char path[160]; + int n = 0; + char sep = nl ? '\n' : 0; + + if (kdev_get(i, &k) < 0 || kdev_path(i, path, sizeof path) < 0) + return -1; + if (action) { + n = put(buf, cap, n, "ACTION="); + n = put(buf, cap, n, action); + buf[n++] = sep; + } + n = put(buf, cap, n, "DEVPATH=/"); + n = put(buf, cap, n, path); + buf[n++] = sep; + n = put(buf, cap, n, "SUBSYSTEM="); + n = put(buf, cap, n, k.subsys); + buf[n++] = sep; + if (action) { + n = put(buf, cap, n, "SEQNUM="); + n = putnum(buf, cap, n, kdev_seqnum()); + buf[n++] = sep; + } + if (k.devname[0]) { + int maj, min; + kdev_numbers(&k, &maj, &min); + n = put(buf, cap, n, "MAJOR="); + n = putnum(buf, cap, n, (uint)maj); + buf[n++] = sep; + n = put(buf, cap, n, "MINOR="); + n = putnum(buf, cap, n, (uint)min); + buf[n++] = sep; + n = put(buf, cap, n, "DEVNAME="); + n = put(buf, cap, n, k.devname); + buf[n++] = sep; + } + if (k.block) { + n = put(buf, cap, n, "DEVTYPE=disk"); + buf[n++] = sep; + } + return n; +} + +int +kdev_uevent_text(int i, char *buf, int cap) +{ + return vars(i, buf, cap, 0, 1); +} + +void +kdev_uevent(int i, const char *action) +{ + char msg[512]; + char path[160]; + int n = 0; + + if (kdev_path(i, path, sizeof path) < 0) + return; + n = put(msg, sizeof msg, n, action); + msg[n++] = '@'; + n = put(msg, sizeof msg, n, "/"); + n = put(msg, sizeof msg, n, path); + msg[n++] = 0; + int v = vars(i, msg + n, (int)sizeof msg - n, action, 0); + if (v < 0) + return; + uevent_send(UEVENT_GROUP_KERNEL, msg, n + v, 0); +} diff --git a/kernel/dev/keymap.c b/kernel/dev/keymap.c index f407c3c..6cc7025 100644 --- a/kernel/dev/keymap.c +++ b/kernel/dev/keymap.c @@ -24,6 +24,12 @@ [0xCB] = KBD_LF, [0xCD] = KBD_RT, \ [0x97] = KBD_HOME, [0xCF] = KBD_END, \ [0xD2] = KBD_INS, [0xD3] = KBD_DEL, \ + [0x3B] = KBD_F1, [0x3C] = KBD_F1 + 1, \ + [0x3D] = KBD_F1 + 2, [0x3E] = KBD_F1 + 3, \ + [0x3F] = KBD_F1 + 4, [0x40] = KBD_F1 + 5, \ + [0x41] = KBD_F1 + 6, [0x42] = KBD_F1 + 7, \ + [0x43] = KBD_F1 + 8, [0x44] = KBD_F1 + 9, \ + [0x57] = KBD_F1 + 10, [0x58] = KBD_F12, \ [0x9C] = '\n', /* KP_Enter */ \ [0xB5] = '/' /* KP_Div */ diff --git a/kernel/dev/pci.c b/kernel/dev/pci.c index e938763..3b8f18a 100644 --- a/kernel/dev/pci.c +++ b/kernel/dev/pci.c @@ -678,6 +678,41 @@ pci_dump(struct sbuf *out, const char *arg) } } +// ASPM off on Realtek NICs, as Linux's r8169 does at probe through +// pci_disable_link_state(): "Disable ASPM L1 as that cause random device stop +// working problems as well as full system hangs". Here because the driver now +// runs in a process, and /dev/pci gives a process no config writes. Both ends +// of the link: the NIC's PCIe Link Control and the bridge above it, low two +// bits, L0s and L1. +static void +link_aspm_off(struct pci_dev *d) +{ + uint8 cap = pci_find_cap(d, 0x10); + if (cap) { + uint32 v = pci_read32(d->bus, d->slot, d->func, cap + 0x10); + if (v & 3) + pci_write32(d->bus, d->slot, d->func, cap + 0x10, v & ~3u); + } +} + +static void +realtek_aspm_off(void) +{ + for (int i = 0; i < ndevs; i++) { + struct pci_dev *d = &devs[i]; + if (d->vendor != 0x10EC || (d->device != 0x8168 && d->device != 0x8161 && + d->device != 0x8125 && d->device != 0x8136)) + continue; + link_aspm_off(d); + for (int j = 0; j < ndevs; j++) + if ((devs[j].header_type & 0x7F) == 1 && + pci_read8(devs[j].bus, devs[j].slot, devs[j].func, PCI_SECONDARY_BUS) == d->bus) + link_aspm_off(&devs[j]); + kinfo("pci: %02x:%02x.%d Realtek NIC, ASPM off on its link\n", + d->bus, d->slot, d->func); + } +} + void pci_init(void) { @@ -693,6 +728,7 @@ pci_init(void) scan_bus(0); kinfo("pci: %d device%s\n", ndevs, ndevs == 1 ? "" : "s"); + realtek_aspm_off(); // Before any driver binds: the line each device's config space names is // the 8259's number, which is not the IOAPIC pin it is wired to. ACPI's diff --git a/kernel/dev/pciuser.c b/kernel/dev/pciuser.c index ffd1293..8d4eb20 100644 --- a/kernel/dev/pciuser.c +++ b/kernel/dev/pciuser.c @@ -302,7 +302,7 @@ pciuser_mmio_ok(uint64 phys, uint64 len) // // Nothing had noticed because the only consumer was virtio-gpu, whose // BARs are large. The first foreign driver found it in its first minute, - // which is the thing docs/plan/hybrid.md said a foreign driver would be + // which is the thing docs/architecture/devices.md said a foreign driver would be // good for. // // The widening is real and is worth naming: a sub-page BAR hands the @@ -477,13 +477,13 @@ do_claim(uint64 argp) // // Two drivers on one device is not a subtle failure -- it is two writers of // the same registers, and what it looks like from outside is hardware that - // has started lying. The eviction path (docs/plan/hybrid.md) is for the + // has started lying. The eviction path (docs/architecture/devices.md) is for the // kernel to *hand over* a device deliberately; until a driver can be told to // let go, the honest answer to "may I have the one you are using" is no. if (d->driver) { // Ask it to let go. A driver with no release hook keeps its device, which // is always safe and, for anything on the path to a shell, correct: the - // rule in docs/plan/hybrid.md is that the kernel keeps a minimal driver + // rule in docs/architecture/devices.md is that the kernel keeps a minimal driver // for everything needed to reach a prompt and userspace owns the good one. if (!d->drv || !d->drv->release) return -EBUSY; @@ -664,7 +664,7 @@ do_dma(uint64 argp) struct vma *v = 0; for (int i = 0; i < NVMA; i++) - if (!p->vma[i].used) { v = &p->vma[i]; break; } + if (!p->mm->vma[i].used) { v = &p->mm->vma[i]; break; } if (!v) { blk->proc = 0; return -ENOMEM; @@ -672,9 +672,9 @@ do_dma(uint64 argp) // Its own mapping in the caller, taken from the same bump allocator mmap // uses so the two cannot collide. - if (p->mmap_next == 0) - p->mmap_next = 0x40000000; - uint64 va = p->mmap_next; + if (p->mm->mmap_next == 0) + p->mm->mmap_next = 0x40000000; + uint64 va = p->mm->mmap_next; if (va + size < va || va + size > KERNBASE) { blk->proc = 0; return -ENOMEM; @@ -685,7 +685,7 @@ do_dma(uint64 argp) blk->proc = 0; return -ENOMEM; } - p->mmap_next += size; + p->mm->mmap_next += size; uint64 phys = dma_phys(kva); if (mappages(p->pgdir, (void *)va, size, phys, PTE_W | PTE_U) < 0) { diff --git a/kernel/dev/procfs.c b/kernel/dev/procfs.c index 676a770..363b05b 100644 --- a/kernel/dev/procfs.c +++ b/kernel/dev/procfs.c @@ -194,16 +194,36 @@ static void gen_stat(struct sbuf *sb) { // USER_HZ, like times() -- everything that parses /proc/stat divides by 100. - uint t = (uint)((uint64)uptime() * USER_HZ / HZ); + // + // Busy is what processes were charged; idle is the rest of every online + // processor's time. Calling it all user time, as this did, is a machine at + // 100% forever in htop and top. The charge is not kept per processor, so + // each cpuN line is an equal share of the total -- right on average, which + // is all a meter can show anyway. + uint64 n = ncpu_online > 0 ? ncpu_online : 1; + uint64 all = (uint64)uptime() * USER_HZ / HZ * n; + uint64 user, sys; + proc_cputime(&user, &sys); + user = user * USER_HZ / HZ; + sys = sys * USER_HZ / HZ; + if (user + sys > all) + all = user + sys; + uint64 idle = all - user - sys; + + // user nice system idle iowait irq softirq steal guest guest_nice sb_puts(sb, "cpu "); - sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n"); + sb_putu(sb, user); sb_puts(sb, " 0 "); + sb_putu(sb, sys); sb_putc(sb, ' '); + sb_putu(sb, idle); sb_puts(sb, " 0 0 0 0 0 0\n"); // Online, for the same reason as cpuinfo: top and friends count the cpuN // lines here to decide how many cores to draw. for (int i = 0; i < ncpu_online; i++) { sb_puts(sb, "cpu"); sb_putu(sb, i); sb_putc(sb, ' '); - sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n"); + sb_putu(sb, user / n); sb_puts(sb, " 0 "); + sb_putu(sb, sys / n); sb_putc(sb, ' '); + sb_putu(sb, idle / n); sb_puts(sb, " 0 0 0 0 0 0\n"); } sb_kv(sb, "processes ", (uint64)proc_num(), 0); sb_kv(sb, "procs_running ", 1, 0); @@ -388,12 +408,42 @@ gen_filesystems(struct sbuf *sb) #define PROC_BOOTDEBUG 30 #define PROC_SLAB 20 #define PROC_PARTITIONS 21 +#define PROC_STRACE 31 // Render global file `id` (a PROC_* constant) into `buf`. Returns its length. // // Split out of procread() so the /proc *filesystem* (kernel/fs/procfs.c) can // use the same generators: that file owns the namespace, this one owns the // content, and they have no other business with each other. +// /proc/strace: the name of the program to trace, or empty. Read by exec, so +// a change takes effect at the next program started, not in running ones. +static char strace_name[16]; + +int +strace_wanted(const char *name) +{ + return strace_name[0] && strncmp(name, strace_name, sizeof(strace_name)) == 0; +} + +int +proc_write_strace(int user_src, uint64 src, uint n) +{ + char tmp[16]; + uint len = n < sizeof(tmp) - 1 ? n : sizeof(tmp) - 1; + + if (user_src) { + if (copy_from_user(tmp, src, len) < 0) + return -EFAULT; + } else { + memmove(tmp, (char *)src, len); + } + while (len > 0 && (tmp[len - 1] == '\n' || tmp[len - 1] == ' ')) + len--; + tmp[len] = 0; + memmove(strace_name, tmp, sizeof(strace_name)); + return (int)n; +} + int proc_gen_global(int id, char *buf, int cap) { @@ -442,6 +492,10 @@ proc_gen_global(int id, char *buf, int cap) sb_puts(&sb, boot_debug ? "on\n" : "off\n"); break; case PROC_DEVICES: gen_devices(&sb); break; + case PROC_STRACE: + sb_puts(&sb, strace_name); + sb_putc(&sb, '\n'); + break; // The block layer renders its own table, like PCI and SchFS above. case PROC_PARTITIONS: sb.len = blkdev_info(buf, cap); break; default: return 0; diff --git a/kernel/dev/pty.c b/kernel/dev/pty.c new file mode 100644 index 0000000..d2b1093 --- /dev/null +++ b/kernel/dev/pty.c @@ -0,0 +1,720 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Pseudo-terminals. +// +// A terminal emulator, tmux, sshd and script(1) all want the same thing: a +// terminal with a program on one side and *themselves* on the other, instead of +// a keyboard and a screen. Unix98 spells that /dev/ptmx -- each open makes a new +// pair and hands back its master -- and /dev/pts/N, the slave, which the program +// gets as its stdin, stdout and controlling terminal. There were none of these, +// so xterm had nothing to run a shell in and tmux could not start a server. +// +// Between the two sides sits a line discipline, the console's in miniature +// (kernel/core/console.c): what the master writes is the slave's *input* -- +// canonical editing, echo, ^C as SIGINT to the foreground group -- and what the +// slave writes is its *output*, NL turned into CR NL, read back by the master. +// +// Every pair shares one lock. The paths are short and a second lock per pair +// would only be one more thing to take in the wrong order. + +#include "core/types.h" +#include "core/defs.h" +#include "core/param.h" +#include "core/spinlock.h" +#include "core/sleeplock.h" +#include "core/proc.h" +#include "fs/fs.h" +#include "fs/file.h" +#include "core/uaccess.h" +#include "uapi/posix.h" +#include "uapi/errno.h" +#include "dev/pty.h" + +#define PTY_BUF 4096 + +#define TIOCGPTN 0x80045430 +#define TIOCSPTLCK 0x40045431 +#define TIOCGPTPEER 0x5441 +#define TIOCNOTTY 0x5422 +#define TIOCGSID 0x5429 +#define TIOCOUTQ 0x5411 +#define TIOCPKT 0x5420 + +#define POLLIN_ 0x001 +#define POLLOUT_ 0x004 +#define POLLHUP_ 0x010 + +struct pty { + int used; + int mopen, sopen; // open file descriptions on each side + int slave_seen; // the slave has been opened at least once + struct termios tio; + struct winsize ws; + int fg_pgrp, sid; + + // The slave's input, after the line discipline. [r, w) is ready to read; + // [w, e) is the line still being edited in canonical mode. + char in[PTY_BUF]; + uint ir, iw, ie; + int eof; // ^D on an empty line: the next read returns 0 + int abort_read; // ^C: wake the reader with EINTR + + // The slave's output, for the master to read. + char out[PTY_BUF]; + uint or, ow; +}; + +static struct spinlock ptylock; +static struct pty ptys[NPTY]; + +#define CTRL(x) ((x) & 0x1f) + +static void +termios_defaults(struct termios *t) +{ + memset(t, 0, sizeof(*t)); + t->c_iflag = ICRNL | IUTF8; + t->c_oflag = OPOST | ONLCR; + t->c_cflag = CS8 | CREAD; + t->c_lflag = ISIG | ICANON | ECHO | ECHOE | IEXTEN; + t->c_cc[VINTR] = CTRL('C'); + t->c_cc[VQUIT] = CTRL('\\'); + t->c_cc[VERASE] = 0x7f; + t->c_cc[VKILL] = CTRL('U'); + t->c_cc[VEOF] = CTRL('D'); + t->c_cc[VSUSP] = CTRL('Z'); + t->c_cc[VWERASE]= CTRL('W'); + t->c_cc[VMIN] = 1; + t->c_ispeed = 15; + t->c_ospeed = 15; +} + +void +pty_init(void) +{ + initlock(&ptylock, "pty"); +} + +int +pty_alloc(void) +{ + acquire(&ptylock); + for (int i = 0; i < NPTY; i++) { + struct pty *p = &ptys[i]; + if (p->used) + continue; + memset(p, 0, sizeof(*p)); + p->used = 1; + termios_defaults(&p->tio); + p->ws.ws_row = 24; + p->ws.ws_col = 80; + release(&ptylock); + return i; + } + release(&ptylock); + return -1; +} + +int +pty_used(int n) +{ + return n >= 0 && n < NPTY && ptys[n].used; +} + +static struct pty * +pty_of(struct inode *ip) +{ + if (ip->minor < 0 || ip->minor >= NPTY) + return 0; + return &ptys[ip->minor]; +} + +// /dev/ptmx, opened: a new pair, and the master in place of the ptmx inode. +// Called with ip locked, as open() holds it; hands back the master the same way. +struct inode * +ptmx_open(struct inode *ip) +{ + int n = pty_alloc(); + struct inode *m; + + if (n < 0) + return 0; + m = devfs_pty_master(n); + if (m == 0) { + ptys[n].used = 0; + return 0; + } + devfs_pty_claim(n, myproc() ? myproc()->euid : 0); + iunlockput(ip); + ilock(m); + return m; +} + +static void +maybe_free(struct pty *p) +{ + if (p->mopen == 0 && p->sopen == 0) + p->used = 0; +} + +// --- output: the slave writes, the master reads ------------------------------ + +static int +out_room(struct pty *p) +{ + return PTY_BUF - (int)(p->ow - p->or); +} + +// One byte of output, with OPOST processing. Caller holds the lock and has made +// sure there is room for two. +static void +out_put(struct pty *p, int c) +{ + if ((p->tio.c_oflag & OPOST) && (p->tio.c_oflag & ONLCR) && c == '\n') + p->out[p->ow++ % PTY_BUF] = '\r'; + p->out[p->ow++ % PTY_BUF] = (char)c; +} + +// Echo goes into the same queue, and is dropped rather than waited for when +// the master has stopped reading: the writer here is whoever typed, and +// blocking it on its own echo would deadlock a master that writes and reads +// from one thread. +static void +echo_put(struct pty *p, int c) +{ + if (out_room(p) < 2) + return; + out_put(p, c); +} + +static void +echo(struct pty *p, int c) +{ + if (c == '\n') { + if (p->tio.c_lflag & (ECHO | ECHONL)) + echo_put(p, '\n'); + return; + } + if (!(p->tio.c_lflag & ECHO)) + return; + if (c < 32 && c != '\t' && c != '\r') { + echo_put(p, '^'); + echo_put(p, c + '@'); + } else { + echo_put(p, c); + } +} + +static void +erase_one(struct pty *p) +{ + if (p->ie == p->iw) + return; + p->ie--; + if ((p->tio.c_lflag & ECHO) && (p->tio.c_lflag & ECHOE)) { + echo_put(p, '\b'); + echo_put(p, ' '); + echo_put(p, '\b'); + } +} + +// --- input: the master writes, the slave reads ------------------------------- + +// One byte through the line discipline. Returns a signal to send to the +// foreground group, or 0. Caller holds the lock. +static int +input_byte(struct pty *p, int c) +{ + struct termios *t = &p->tio; + + if (t->c_iflag & ISTRIP) + c &= 0x7f; + if (c == '\r') { + if (t->c_iflag & IGNCR) + return 0; + if (t->c_iflag & ICRNL) + c = '\n'; + } else if (c == '\n' && (t->c_iflag & INLCR)) { + c = '\r'; + } + + if (t->c_lflag & ISIG) { + int sig = 0; + if (c == t->c_cc[VINTR]) sig = SIGINT; + else if (c == t->c_cc[VQUIT]) sig = SIGQUIT; + else if (c == t->c_cc[VSUSP]) sig = SIGTSTP; + if (sig && c != 0) { + echo(p, c); + if (!(t->c_lflag & NOFLSH)) + p->ie = p->iw = p->ir; + p->abort_read = 1; + return sig; + } + } + + if (t->c_lflag & ICANON) { + if (c == t->c_cc[VERASE] || c == CTRL('H')) { + erase_one(p); + return 0; + } + if (c == t->c_cc[VKILL]) { + while (p->ie != p->iw && p->in[(p->ie - 1) % PTY_BUF] != '\n') + erase_one(p); + return 0; + } + if (c == t->c_cc[VWERASE] && (t->c_lflag & IEXTEN)) { + while (p->ie != p->iw && p->in[(p->ie - 1) % PTY_BUF] == ' ') + erase_one(p); + while (p->ie != p->iw && p->in[(p->ie - 1) % PTY_BUF] != ' ') + erase_one(p); + return 0; + } + if (c == t->c_cc[VEOF]) { + if (p->ie == p->iw) + p->eof = 1; + p->iw = p->ie; + return 0; + } + } + + if (p->ie - p->ir >= PTY_BUF) + return 0; // full: dropped, as a console does + p->in[p->ie++ % PTY_BUF] = (char)c; + echo(p, c); + if (!(t->c_lflag & ICANON) || c == '\n' || p->ie - p->ir >= PTY_BUF) + p->iw = p->ie; + return 0; +} + +int +ptm_write(struct inode *ip, uint64 off, char *src, int n) +{ + struct pty *p = pty_of(ip); + char kbuf[128]; + int done = 0; + + (void)off; + if (p == 0 || !p->used) + return -EIO; + iunlock(ip); + while (done < n) { + int chunk = n - done < (int)sizeof(kbuf) ? n - done : (int)sizeof(kbuf); + if (copy_from_user(kbuf, (addr_t)(src + done), chunk) < 0) { + ilock(ip); + return done ? done : -EFAULT; + } + acquire(&ptylock); + for (int i = 0; i < chunk; i++) { + // Raw-mode input waits for room rather than vanishing: a paste into an + // editor is a burst of thousands of bytes, and dropping the tail of it + // is worse than making the writer wait for the reader. + while (!(p->tio.c_lflag & ICANON) && p->ie - p->ir >= PTY_BUF && + p->sopen > 0) { + if (myproc()->killed) { + release(&ptylock); + ilock(ip); + return done + i ? done + i : -EINTR; + } + sleep(&p->ie, &ptylock); + } + int sig = input_byte(p, kbuf[i] & 0xff); + if (sig) { + int pg = p->fg_pgrp; + wakeup(&p->ir); + release(&ptylock); + if (pg > 0) + sigsend_pgrp(pg, sig); + acquire(&ptylock); + } + } + wakeup(&p->ir); + wakeup(&p->or); // the echo + release(&ptylock); + done += chunk; + } + poll_kick(); + ilock(ip); + return n; +} + +int +ptm_read(struct inode *ip, uint64 off, char *dst, int n) +{ + struct pty *p = pty_of(ip); + char kbuf[256]; + int got = 0; + + (void)off; + if (p == 0 || !p->used) + return -EIO; + iunlock(ip); + acquire(&ptylock); + while (p->or == p->ow) { + // Every slave descriptor closed, after there was one: the program on the + // other side has gone. Linux says EIO, and that is what terminal + // emulators and tmux take as "the shell exited". + if (p->slave_seen && p->sopen == 0) { + release(&ptylock); + ilock(ip); + return -EIO; + } + if (myproc()->killed) { + release(&ptylock); + ilock(ip); + return -EINTR; + } + sleep(&p->or, &ptylock); + } + while (got < n && got < (int)sizeof(kbuf) && p->or != p->ow) + kbuf[got++] = p->out[p->or++ % PTY_BUF]; + wakeup(&p->ow); + release(&ptylock); + poll_kick(); + ilock(ip); + if (copy_to_user((addr_t)dst, kbuf, got) < 0) + return -EFAULT; + return got; +} + +int +ptm_ready(struct inode *ip) +{ + struct pty *p = pty_of(ip); + return p && (p->or != p->ow || (p->slave_seen && p->sopen == 0)); +} + +void +ptm_open(struct inode *ip) +{ + struct pty *p = pty_of(ip); + if (p) { + acquire(&ptylock); + p->mopen++; + release(&ptylock); + } +} + +// The master is gone: the slave side has been hung up on. Its readers see end +// of file, its writers EIO, and its session SIGHUP -- which is how closing an +// xterm window ends the shell inside it. +void +ptm_close(struct inode *ip) +{ + struct pty *p = pty_of(ip); + int sid = 0, pg = 0; + + if (p == 0) + return; + acquire(&ptylock); + if (p->mopen > 0) + p->mopen--; + if (p->mopen == 0) { + sid = p->sid; + pg = p->fg_pgrp; + wakeup(&p->ir); + wakeup(&p->ow); + wakeup(&p->ie); + } + maybe_free(p); + release(&ptylock); + if (sid > 0) + sigsend_pgrp(sid, SIGHUP); + if (pg > 0 && pg != sid) + sigsend_pgrp(pg, SIGHUP); + poll_kick(); +} + +// --- the slave --------------------------------------------------------------- + +int +pts_read(struct inode *ip, uint64 off, char *dst, int n) +{ + struct pty *p = pty_of(ip); + char kbuf[256]; + int got = 0, c; + + (void)off; + if (p == 0 || !p->used) + return -EIO; + if (n > (int)sizeof(kbuf)) + n = sizeof(kbuf); + iunlock(ip); + acquire(&ptylock); + while (got < n) { + while (p->ir == p->iw) { + if (p->eof) { + p->eof = 0; + goto done; + } + if (p->mopen == 0) + goto done; // hung up: end of file + // VMIN 0 is a poll, and a program that asked for one must not block. + if (!(p->tio.c_lflag & ICANON) && p->tio.c_cc[VMIN] == 0) + goto done; + if (got > 0 && !(p->tio.c_lflag & ICANON)) + goto done; + if (myproc()->killed || p->abort_read) { + p->abort_read = 0; + release(&ptylock); + ilock(ip); + return got ? got : -EINTR; + } + if (sig_attention(myproc())) { + release(&ptylock); + int e = sig_blocking_check(); + if (e) { + ilock(ip); + return got ? got : e; + } + acquire(&ptylock); + continue; + } + sleep(&p->ir, &ptylock); + } + c = p->in[p->ir++ % PTY_BUF]; + kbuf[got++] = (char)c; + if ((p->tio.c_lflag & ICANON) && c == '\n') + break; + if (!(p->tio.c_lflag & ICANON) && + got >= (p->tio.c_cc[VMIN] ? p->tio.c_cc[VMIN] : 1) && p->ir == p->iw) + break; + } +done: + wakeup(&p->ie); + release(&ptylock); + poll_kick(); + ilock(ip); + if (got && copy_to_user((addr_t)dst, kbuf, got) < 0) + return -EFAULT; + return got; +} + +int +pts_write(struct inode *ip, uint64 off, char *src, int n) +{ + struct pty *p = pty_of(ip); + char kbuf[128]; + int done = 0; + + (void)off; + if (p == 0 || !p->used) + return -EIO; + iunlock(ip); + while (done < n) { + int chunk = n - done < (int)sizeof(kbuf) ? n - done : (int)sizeof(kbuf); + if (copy_from_user(kbuf, (addr_t)(src + done), chunk) < 0) { + ilock(ip); + return done ? done : -EFAULT; + } + acquire(&ptylock); + for (int i = 0; i < chunk; i++) { + while (out_room(p) < 2) { + if (p->mopen == 0 || myproc()->killed) { + release(&ptylock); + ilock(ip); + return done + i ? done + i : (p->mopen == 0 ? -EIO : -EINTR); + } + wakeup(&p->or); + sleep(&p->ow, &ptylock); + } + if (p->mopen == 0) { + release(&ptylock); + ilock(ip); + return done + i ? done + i : -EIO; + } + out_put(p, kbuf[i] & 0xff); + } + wakeup(&p->or); + release(&ptylock); + done += chunk; + } + poll_kick(); + ilock(ip); + return n; +} + +int +pts_ready(struct inode *ip) +{ + struct pty *p = pty_of(ip); + return p && (p->ir != p->iw || p->eof || p->mopen == 0); +} + +// Opening a terminal makes it the caller's controlling terminal when the +// caller leads a session that has none -- the System V rule, which Linux keeps +// and which xterm relies on: its child calls setsid() and opens the slave, and +// expects ^C to work from then on without a TIOCSCTTY. +void +pts_open(struct inode *ip) +{ + struct pty *p = pty_of(ip); + struct proc *me = myproc(); + + if (p == 0) + return; + acquire(&ptylock); + p->sopen++; + p->slave_seen = 1; + if (me && me->sid == me->pid && me->ctty == 0 && p->sid == 0) { + me->ctty = PTY_CTTY + ip->minor; + p->sid = me->sid; + p->fg_pgrp = me->pgid; + } + release(&ptylock); +} + +void +pts_close(struct inode *ip) +{ + struct pty *p = pty_of(ip); + + if (p == 0) + return; + acquire(&ptylock); + if (p->sopen > 0) + p->sopen--; + if (p->sopen == 0) { + p->sid = 0; + p->fg_pgrp = 0; + wakeup(&p->or); // the master reads EIO now + } + maybe_free(p); + release(&ptylock); + poll_kick(); +} + +// --- ioctl and poll ---------------------------------------------------------- + +int +pty_ioctl(struct inode *ip, uint request, addr_t arg) +{ + struct pty *p = pty_of(ip); + int master = ip->major == PTM_MAJOR; + int v; + + if (p == 0 || !p->used) + return -EIO; + + switch (request) { + case TIOCGPTN: + if (!master) + return -ENOTTY; + v = ip->minor; + return put_user(v, arg) < 0 ? -EFAULT : 0; + case TIOCSPTLCK: + case TIOCPKT: + // Slaves are never locked here, so unlockpt() has nothing to undo; packet + // mode is not offered, and nothing in the image asks for it. + return master ? 0 : -ENOTTY; + case TCGETS: { + struct termios t; + acquire(&ptylock); + t = p->tio; + release(&ptylock); + return copy_to_user(arg, &t, sizeof(t)) < 0 ? -EFAULT : 0; + } + case TCSETS: case TCSETSW: case TCSETSF: { + struct termios t; + if (copy_from_user(&t, arg, sizeof(t)) < 0) + return -EFAULT; + acquire(&ptylock); + p->tio = t; + if (request == TCSETSF) + p->ie = p->iw = p->ir; + // Leaving canonical mode hands over the line being edited, as on the + // console: a shell that switches to raw mid-line must not lose it. + if (!(t.c_lflag & ICANON)) + p->iw = p->ie; + wakeup(&p->ir); + release(&ptylock); + poll_kick(); + return 0; + } + case TCFLSH: + acquire(&ptylock); + if (arg == TCIFLUSH || arg == TCIOFLUSH) { + p->ie = p->iw = p->ir; + p->eof = 0; + } + if (arg == TCOFLUSH || arg == TCIOFLUSH) + p->or = p->ow; + wakeup(&p->ie); + wakeup(&p->ow); + release(&ptylock); + return 0; + case TIOCGWINSZ: + return copy_to_user(arg, &p->ws, sizeof(p->ws)) < 0 ? -EFAULT : 0; + case TIOCSWINSZ: { + struct winsize ws; + int changed, pg; + if (copy_from_user(&ws, arg, sizeof(ws)) < 0) + return -EFAULT; + acquire(&ptylock); + changed = ws.ws_row != p->ws.ws_row || ws.ws_col != p->ws.ws_col; + p->ws = ws; + pg = p->fg_pgrp; + release(&ptylock); + // The program inside learns of a resize the way it would on Linux. + if (changed && pg > 0) + sigsend_pgrp(pg, SIGWINCH); + return 0; + } + case FIONREAD: + v = master ? (int)(p->ow - p->or) : (int)(p->iw - p->ir); + return put_user(v, arg) < 0 ? -EFAULT : 0; + case TIOCOUTQ: + v = master ? 0 : (int)(p->ow - p->or); + return put_user(v, arg) < 0 ? -EFAULT : 0; + case TIOCGPGRP: + v = p->fg_pgrp; + return put_user(v, arg) < 0 ? -EFAULT : 0; + case TIOCSPGRP: + if (copy_from_user(&v, arg, sizeof(v)) < 0) + return -EFAULT; + if (v <= 0) + return -EINVAL; + p->fg_pgrp = v; + return 0; + case TIOCGSID: + if (p->sid == 0) + return -ENOTTY; + v = p->sid; + return put_user(v, arg) < 0 ? -EFAULT : 0; + case TIOCSCTTY: + if (master) + return -EINVAL; + myproc()->ctty = PTY_CTTY + ip->minor; + p->sid = myproc()->sid; + p->fg_pgrp = myproc()->pgid; + return 0; + case TIOCNOTTY: + if (myproc()->ctty == PTY_CTTY + ip->minor) + myproc()->ctty = 0; + return 0; + } + return -ENOTTY; +} + +int +pty_poll(struct inode *ip, int events, int readable, int writable) +{ + struct pty *p = pty_of(ip); + int rev = 0; + + if (p == 0 || !p->used) + return POLLHUP_; + if (ip->major == PTM_MAJOR) { + if (readable && (events & POLLIN_) && p->or != p->ow) + rev |= POLLIN_; + if (p->slave_seen && p->sopen == 0) + rev |= POLLHUP_ | (events & POLLIN_); + } else { + if (readable && (events & POLLIN_) && (p->ir != p->iw || p->eof)) + rev |= POLLIN_; + if (p->mopen == 0) + rev |= POLLHUP_ | (events & POLLIN_); + } + if (writable && (events & POLLOUT_)) + rev |= POLLOUT_; + return rev; +} diff --git a/kernel/dev/ramdisk.c b/kernel/dev/ramdisk.c index e988716..2e5747a 100644 --- a/kernel/dev/ramdisk.c +++ b/kernel/dev/ramdisk.c @@ -110,7 +110,7 @@ ramdiskinit(void) rd_scff = h; rd_blocks = (uint)(h->image_bytes / BSIZE); rd_first = 0; - cprintf("ramdisk: SCFF, %u MB in %u MB, %u chunks of %u KB\n", + kinfo("ramdisk: SCFF, %u MB in %u MB, %u chunks of %u KB\n", (uint)(h->image_bytes >> 20), (uint)(rd_size >> 20), h->nchunks, h->chunk_bytes >> 10); } @@ -124,7 +124,7 @@ ramdiskinit(void) panic("ramdiskinit: embedded image smaller than a superblock"); if(!rd_scff) - cprintf("ramdisk: %u blocks (%u KB) at %p\n", + kinfo("ramdisk: %u blocks (%u KB) at %p\n", rd_blocks, (uint)(rd_size / 1024), rd_base); #else rd_base = 0; @@ -155,7 +155,7 @@ ramdisk_blocks(void) if (extra > room) extra = room; rd_total = rd_blocks + extra; - cprintf("ramdisk: %u MB image, %u MB of memory to grow into\n", + kinfo("ramdisk: %u MB image, %u MB of memory to grow into\n", (uint)(rd_size >> 20), (uint)(extra * BSIZE >> 20)); } release(&rd_lock); diff --git a/kernel/dev/vt.c b/kernel/dev/vt.c index b2e005d..bfb8412 100644 --- a/kernel/dev/vt.c +++ b/kernel/dev/vt.c @@ -235,11 +235,61 @@ vt_current(void) // Output // --------------------------------------------------------------------------- +// The text cursor. See struct vt. Drawn as the cell with its colours swapped +// -- relative to what the cell would show, so a cursor on reverse-video text +// is the plain text and every cursor on screen is visible against its cell. +static void +cursor_erase(struct vt *v) +{ + if (!v->curs_drawn) + return; + v->curs_drawn = 0; + if (v->curs_x < v->cols && v->curs_y < v->rows) + show(v, v->curs_x, v->curs_y); +} + +static void +cursor_draw(struct vt *v) +{ + if (v->curs_hidden || !is_active(v)) + return; + // A glyph in the last column leaves the cursor *on* it with the wrap + // pending, and that is where it is drawn. + int col = v->cx >= v->cols ? v->cols - 1 : v->cx; + int row = v->cy >= v->rows ? v->rows - 1 : v->cy; + if (col < 0 || row < 0) + return; + struct vtcell *k = cell_at(v, col, row); + if (k->attr & VT_ATTR_REVERSE) + gfx_draw_cell(col, row, k->ch, vt_rgb(k->fg), vt_rgb(k->bg)); + else + gfx_draw_cell(col, row, k->ch, vt_rgb(k->bg), vt_rgb(k->fg)); + v->curs_x = col; + v->curs_y = row; + v->curs_drawn = 1; +} + void vt_repaint(struct vt *v) { if (!is_active(v)) return; + v->curs_drawn = 0; // the repaint below paints over it + + // The margins first: the screen is rarely a whole number of cells, and the + // strip right of the last column and below the last row belongs to nobody. + // Coming back from X, that strip still held the bottom of the desktop. + int cw, ch; + gfx_cell_size(&cw, &ch); + if (cw > 0 && ch > 0) { + int w = (int)gfx_width(), h = (int)gfx_height(); + uint32 bg = vt_rgb(v->bg); + if (w > v->cols * cw) + gfx_fill_rect(v->cols * cw, 0, w - v->cols * cw, h, bg); + if (h > v->rows * ch) + gfx_fill_rect(0, v->rows * ch, w, h - v->rows * ch, bg); + } + for (int r = 0; r < v->rows; r++) for (int c = 0; c < v->cols; c++) { struct vtcell *k = cell_at(v, c, r); @@ -248,6 +298,7 @@ vt_repaint(struct vt *v) else gfx_draw_cell(c, r, k->ch, vt_rgb(k->fg), vt_rgb(k->bg)); } + cursor_draw(v); } void @@ -271,6 +322,99 @@ vt_scroll(struct vt *v) v->cy--; } +// --- the scrolling region ---------------------------------------------------- +// +// tmux, htop, less and every curses program on TERM=linux scroll part of the +// screen: a status line stays put while the rest moves. Without a region the +// whole screen scrolled instead, and a pane's output smeared into its +// neighbours and over the status bar. + +static int +region_bot(struct vt *v) +{ + if (v->scroll_bot > v->scroll_top && v->scroll_bot < v->rows) + return v->scroll_bot; + return v->rows - 1; +} + +static int +region_top(struct vt *v) +{ + return v->scroll_top < region_bot(v) ? v->scroll_top : 0; +} + +static void +draw_rows(struct vt *v, int r0, int r1) +{ + if (!is_active(v)) + return; + for (int r = r0; r <= r1; r++) + for (int c = 0; c < v->cols; c++) + show(v, c, r); +} + +// Rows top..bot move up n; the n exposed at the bottom are blank. +static void +region_up(struct vt *v, int top, int bot, int n) +{ + if (n <= 0 || top > bot) + return; + if (n > bot - top + 1) + n = bot - top + 1; + if (top == 0 && bot == v->rows - 1) { + // The whole screen: the framebuffer can be moved instead of redrawn. + while (n-- > 0) { + memmove(v->cells, cell_at(v, 0, 1), + (uint64)(v->rows - 1) * v->cols * sizeof(struct vtcell)); + blank_row(v, v->rows - 1); + if (is_active(v)) + gfx_scroll_up(vt_rgb(v->bg)); + } + return; + } + memmove(cell_at(v, 0, top), cell_at(v, 0, top + n), + (uint64)(bot - top + 1 - n) * v->cols * sizeof(struct vtcell)); + for (int r = bot - n + 1; r <= bot; r++) + blank_row(v, r); + draw_rows(v, top, bot); +} + +// Rows top..bot move down n; the n exposed at the top are blank. +static void +region_down(struct vt *v, int top, int bot, int n) +{ + if (n <= 0 || top > bot) + return; + if (n > bot - top + 1) + n = bot - top + 1; + memmove(cell_at(v, 0, top + n), cell_at(v, 0, top), + (uint64)(bot - top + 1 - n) * v->cols * sizeof(struct vtcell)); + for (int r = top; r < top + n; r++) + blank_row(v, r); + draw_rows(v, top, bot); +} + +// Down a line, scrolling the region when the cursor is on its last row. Below +// the region the cursor only moves, and stops at the bottom of the screen. +static void +linefeed(struct vt *v) +{ + if (v->cy == region_bot(v)) + region_up(v, region_top(v), region_bot(v), 1); + else if (v->cy < v->rows - 1) + v->cy++; +} + +// Up a line, the reverse: the region scrolls down from its first row. +static void +reverse_index(struct vt *v) +{ + if (v->cy == region_top(v)) + region_down(v, region_top(v), region_bot(v), 1); + else if (v->cy > 0) + v->cy--; +} + void vt_cursor_set(struct vt *v, int col, int row) { @@ -313,19 +457,23 @@ wrap_now(struct vt *v) return; v->wrap_pending = 0; v->cx = 0; - v->cy++; - while (v->cy >= v->rows) - vt_scroll(v); + linefeed(v); } void vt_putc(struct vt *v, int glyph) { switch (glyph) { + // LF is a line feed: down one row, same column. The carriage return a + // shell expects with it is ONLCR's, so it happens only while the terminal + // asks for it. Doing it always broke every program that turns OPOST off and + // moves down with a bare LF -- tmux does, row after row, and each row of a + // right-hand pane landed in column 0, where its ESC [ K erased the left one. case '\n': v->wrap_pending = 0; - v->cx = 0; - v->cy++; + if ((v->tio.c_oflag & (OPOST | ONLCR)) == (OPOST | ONLCR)) + v->cx = 0; + linefeed(v); break; case '\r': @@ -744,6 +892,38 @@ vt_csi(struct vt *v, int final) case 's': v->saved_cx = col; v->saved_cy = row; break; case 'u': vt_cursor_set(v, v->saved_cx, v->saved_cy); break; + // DECSTBM: the scrolling region, 1-based and inclusive, and the cursor home. + case 'r': { + int t = v->esc_nparm > 0 && v->esc_parm[0] ? v->esc_parm[0] : 1; + int b = v->esc_nparm > 1 && v->esc_parm[1] ? v->esc_parm[1] : v->rows; + if (b > v->rows) b = v->rows; + if (t < b) { + v->scroll_top = t - 1; + v->scroll_bot = b - 1; + } else { + v->scroll_top = v->scroll_bot = 0; + } + vt_cursor_set(v, 0, 0); + break; + } + + // Insert and delete lines at the cursor, within the region: what a curses + // program sends to open or close a gap rather than repaint everything below. + case 'L': + if (row >= region_top(v) && row <= region_bot(v)) + region_down(v, row, region_bot(v), n1); + v->wrap_pending = 0; + break; + case 'M': + if (row >= region_top(v) && row <= region_bot(v)) + region_up(v, row, region_bot(v), n1); + v->wrap_pending = 0; + break; + + // Scroll the region without moving the cursor. + case 'S': region_up(v, region_top(v), region_bot(v), n1); break; + case 'T': region_down(v, region_top(v), region_bot(v), n1); break; + // Cursor visibility (DECTCEM) and the other private modes. There is no // hardware cursor being drawn, so there is nothing to show or hide -- // swallowing them silently is correct, and far better than printing @@ -762,10 +942,41 @@ vt_feed_1(struct vt *v, int c) switch (v->esc_state) { case VE_ESC: if (c == '[') { v->esc_state = VE_CSI; - v->esc_nparm = 0; v->esc_private = 0; + v->esc_nparm = 0; v->esc_private = 0; v->esc_qmark = 0; for (int i = 0; i < VT_MAXPARM; i++) v->esc_parm[i] = 0; return; } if (c == ']') { v->esc_state = VE_OSC; return; } + v->esc_state = VE_NORMAL; + switch (c) { + case '7': // DECSC + v->saved_cx = v->cx; + v->saved_cy = v->cy; + break; + case '8': // DECRC + vt_cursor_set(v, v->saved_cx, v->saved_cy); + break; + case 'M': // RI: up, scrolling back + v->wrap_pending = 0; + reverse_index(v); + break; + case 'D': // IND: down, scrolling + v->wrap_pending = 0; + linefeed(v); + break; + case 'E': // NEL: CR and IND + v->wrap_pending = 0; + v->cx = 0; + linefeed(v); + break; + case '(': case ')': case '*': case '+': + // A character set designation. The set itself is not offered, but the + // byte naming it must be swallowed: ESC ( B printed a stray "B". + v->esc_state = VE_CHARSET; + break; + } + return; + + case VE_CHARSET: v->esc_state = VE_NORMAL; return; @@ -783,7 +994,16 @@ vt_feed_1(struct vt *v, int c) return; case VE_CSI: - if (c == '?') { v->esc_private = 1; return; } + // Private markers (< = > ?) and intermediates (space to /): sequences + // this terminal does not speak, but must still consume to the final byte. + // Ending at the marker instead printed the rest -- tmux asking for + // extended keys as ESC [ > 4 ; 1 m left "4;1m" on the screen. + if (c >= '<' && c <= '?') { + v->esc_private = 1; + if (c == '?') v->esc_qmark = 1; + return; + } + if (c >= 0x20 && c <= 0x2F) { v->esc_private = 1; return; } if (c >= '0' && c <= '9') { if (v->esc_nparm == 0) v->esc_nparm = 1; if (v->esc_nparm <= VT_MAXPARM) @@ -795,8 +1015,15 @@ vt_feed_1(struct vt *v, int c) return; } v->esc_state = VE_NORMAL; - if (!v->esc_private) + if (!v->esc_private) { vt_csi(v, c); + } else if (v->esc_qmark && (c == 'h' || c == 'l')) { + // DECSET/DECRST. Only the cursor's visibility is ours; the rest of + // ?1049 and its kin are still ignored, as before. + for (int i = 0; i < v->esc_nparm && i < VT_MAXPARM; i++) + if (v->esc_parm[i] == 25) + v->curs_hidden = (c == 'l'); + } return; } @@ -1014,5 +1241,26 @@ vt_cycle(int dir) void vt_feed(struct vt *v, int c) { + // A plain glyph is drawn over the cell the cursor sits on, which repaints it + // anyway (unless a wrap is pending, when it lands on the next line), so the + // cursor needs no separate erase there. + int mid = v->esc_state != VE_NORMAL; + int over = !mid && !v->wrap_pending && c >= 0x20 && c < 0x7f && + v->u8_need == 0; + + if (over) { + vt_feed_1(v, c); + v->curs_drawn = 0; + cursor_draw(v); + return; + } + + // Anything else may move the cursor, and a line feed at the bottom scrolls + // the *pixels*, block and all. So the cursor comes off first. Erased after + // the scroll, it was repainted where the cell had been and the old block + // went up a row with the text: a white block at the end of every line of a + // verbose boot. + cursor_erase(v); vt_feed_1(v, c); + cursor_draw(v); } diff --git a/kernel/drivers/audio/ac97.c b/kernel/drivers/audio/ac97.c index e0c3d01..9371ea9 100644 --- a/kernel/drivers/audio/ac97.c +++ b/kernel/drivers/audio/ac97.c @@ -234,6 +234,12 @@ ac97_queue(struct device *d, const void *pcm, int bytes) // just written is what makes this a queue rather than a one-shot. outb(a->nabm + PO_LVI, (uint8)idx); + // An engine that ran off the end of the queue halts. QEMU resumes it when + // LVI moves; the datasheet asks for the run bit again, so if it is still + // halted after the write above, give it that. + if (a->running && (inw(a->nabm + PO_SR) & SR_DCH)) + outb(a->nabm + PO_CR, CR_RPBM | CR_IOCE); + release(&a->lock); return take; } @@ -263,6 +269,13 @@ ac97_stop(struct device *d) acquire(&a->lock); outb(a->nabm + PO_CR, 0); outb(a->nabm + PO_CR, CR_RR); // reset the box: CIV, LVI, PICB to 0 + for (int spin = 0; spin < 100000 && (inb(a->nabm + PO_CR) & CR_RR); spin++) + ; + // The reset clears the descriptor list's address too, and the engine then + // walks a "list" at physical address 0: every write after the first stop + // played zeros. ALSA resets the device before each stream, so that was all + // of them. Linux's intel8x0 rewrites it after every reset for this reason. + outl(a->nabm + PO_BDBAR, a->bdl_phys); for (int i = 0; i < NDESC; i++) a->filled[i] = 0; a->head = 0; diff --git a/kernel/drivers/bus/xhci/xhci.c b/kernel/drivers/bus/xhci/xhci.c index 27a7af9..b0aff15 100644 --- a/kernel/drivers/bus/xhci/xhci.c +++ b/kernel/drivers/bus/xhci/xhci.c @@ -32,6 +32,7 @@ #include "fs/file.h" #include "dev/pci.h" #include "dev/usb.h" +#include "core/hwinfo.h" #include "xhci.h" #define RING_TRBS 64 // TRBs per ring, including the link @@ -1059,11 +1060,10 @@ attach_device(struct xhci *x, const struct usb_dev *proto, int parent, goto fail; } - // cprintf understands %d %u %l %x %p %b %s and nothing else -- no field - // widths. "%04x" is not a directive to it: it prints '%' and '0' literally, - // consumes no argument, and every argument after it lands in the wrong - // place. That is what turned this line into rubbish, not the enumeration. - cprintf("usb: port %d%s slot %d %x:%x %s, %s speed, %d configuration%s\n", + // cprintf does not pad: "%04x" prints like "%x". It used to be worse -- an + // unknown directive consumed no argument and every one after it landed in + // the wrong place, which is what once turned this line into rubbish. + kinfo("usb: port %d%s slot %d %x:%x %s, %s speed, %d configuration%s\n", proto->port, proto->route ? " (via hub)" : "", slot, desc->idVendor, desc->idProduct, class_name(desc->bDeviceClass), speed_name(speed), @@ -1193,7 +1193,7 @@ xhci_detach(struct usb_dev *hub, int hub_port) for(i = 1; i <= MAX_SLOTS; i++) if(x->slot[i].used && x->slot[i].parent == hub->slot && x->slot[i].pport == hub_port){ - cprintf("usb: slot %d unplugged from hub slot %d port %d\n", + kinfo("usb: slot %d unplugged from hub slot %d port %d\n", i, hub->slot, hub_port); free_slot(x, i); } @@ -1493,7 +1493,7 @@ bios_handoff(struct xhci *x) for(i = 0; i < 1000 && (rd32(x->cap, off) & LEGSUP_BIOS_OWNED); i++) microdelay(1000); if(rd32(x->cap, off) & LEGSUP_BIOS_OWNED){ - cprintf("xhci: firmware would not hand over; taking it anyway\n"); + kinfo("xhci: firmware would not hand over; taking it anyway\n"); wr32(x->cap, off, (rd32(x->cap, off) & ~LEGSUP_BIOS_OWNED) | LEGSUP_OS_OWNED); } else @@ -1584,7 +1584,7 @@ xhci_probe(struct pci_dev *d) // dword and take the top half. { uint32 v = rd32(x->cap, 0) >> 16; - cprintf("xhci: version %d.%d%d, %d ports, %d slots, %d-byte contexts\n", + kinfo("xhci: version %d.%d%d, %d ports, %d slots, %d-byte contexts\n", (v >> 8) & 0xFF, (v >> 4) & 0xF, v & 0xF, x->maxports, x->maxslots, x->ctxsize); } @@ -1671,9 +1671,14 @@ xhci_probe(struct pci_dev *d) } if(x->ndevices == 0) - cprintf("xhci: no devices attached\n"); + kinfo("xhci: no devices attached\n"); + // Number the bus and announce its root hub before the first port can be + // scanned, so `usbN` is in /sys, and udev knows of it, ahead of anything + // plugged into it. + usb_bus_add(x, d->bus, d->slot, d->func); + // Now, and only now, turn interrupts on. The order is: claim the line // (which unmasks it and sets level/active-low), clear anything the // controller already considers pending so that it drives INTx low, and then @@ -1690,7 +1695,7 @@ xhci_probe(struct pci_dev *d) // can sleep rather than spin -- but only if something will wake it. With // no interrupt at all, transfers go on spinning as they did during probe. if(pci_irq_attach(d, xhci_irq, x, "xhci", PCI_IRQF_MSIX) < 0){ - cprintf("xhci: no interrupt; transfers will be polled\n"); + kinfo("xhci: no interrupt; transfers will be polled\n"); } else { x->can_sleep = 1; x->running = 1; @@ -1704,6 +1709,20 @@ xhci_probe(struct pci_dev *d) return 0; } +// For GMMOS, after a panic: each controller's registers, as mapped here. Its +// micro driver resets the controller and starts again, so this is all it +// takes -- a BAR above 4 GB has no address of its own without ioremap(), +// which is not a thing to call on a dead kernel. +int +xhci_gmmos_controllers(volatile uchar **caps, int max) +{ + int i; + + for(i = 0; i < nxhci && i < max; i++) + caps[i] = xhcis[i].cap; + return i; +} + // --------------------------------------------------------------------------- // Hot-plug // --------------------------------------------------------------------------- @@ -1741,13 +1760,13 @@ scan_ports(struct xhci *x) if(!(sc & PORT_CCS)){ x->port_failed[port] = 0; if(slot){ - cprintf("usb: port %d: unplugged\n", port); + kinfo("usb: port %d: unplugged\n", port); free_slot(x, slot); } continue; } if(slot && (chg & PORT_CSC)){ - cprintf("usb: port %d: replugged\n", port); + kinfo("usb: port %d: replugged\n", port); free_slot(x, slot); slot = 0; } @@ -1759,7 +1778,7 @@ scan_ports(struct xhci *x) sleep_ticks(100 / MSEC_PER_TICK); if(!(rd32(x->op, XHCI_PORTSC(port)) & PORT_CCS)) continue; - cprintf("usb: port %d: plugged in\n", port); + kinfo("usb: port %d: plugged in\n", port); if(enumerate_port(x, port) < 0) x->port_failed[port] = 1; } diff --git a/kernel/drivers/gpu/vga/vga_fb.c b/kernel/drivers/gpu/vga/vga_fb.c index 40017bb..c0e3c0f 100644 --- a/kernel/drivers/gpu/vga/vga_fb.c +++ b/kernel/drivers/gpu/vga/vga_fb.c @@ -187,7 +187,7 @@ vga_disp_acquire(struct device *d, uint32 *stride_out) return 0; } memset(back, 0, back_bytes); - cprintf("display: %d KB back buffer, double buffered\n", + kinfo("display: %d KB back buffer, double buffered\n", (int)(back_bytes / 1024)); return back; } @@ -272,7 +272,7 @@ const struct display_ops vga_display_ops = { // driver that leaves has to name its replacement rather than simply detaching; // detaching alone leaves gfx pointing at a device nobody is driving. // -// This is the fallback rule from docs/plan/hybrid.md made real: the kernel +// This is the fallback rule from docs/architecture/devices.md made real: the kernel // keeps a minimal driver for everything on the path to a shell, so a fancy // driver leaving degrades to a working screen instead of to no screen. int diff --git a/kernel/drivers/gpu/vga/vga_pci.c b/kernel/drivers/gpu/vga/vga_pci.c index d26b0cc..6b33e41 100644 --- a/kernel/drivers/gpu/vga/vga_pci.c +++ b/kernel/drivers/gpu/vga/vga_pci.c @@ -79,7 +79,7 @@ vga_pci_probe(struct pci_dev *d) // displayed, which is the thing vga.c is writing into. The BAR is the // card's whole aperture and the mode may sit at an offset within it. Say // so rather than silently preferring one. - cprintf("vga: framebuffer disagreement -- VBE says %x, BAR says %x\n", + kinfo("vga: framebuffer disagreement -- VBE says %x, BAR says %x\n", (uint32)vbe, (uint32)bar->base); } diff --git a/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c b/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c index 7a1d961..0ae5f6c 100644 --- a/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c +++ b/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c @@ -557,7 +557,7 @@ static struct { // Hand the device to a userspace driver. // -// docs/plan/hybrid.md's first eviction, and the reason it is this device: the +// docs/architecture/devices.md's first eviction, and the reason it is this device: the // boundary already exists (struct display_ops), it has exactly one consumer, // and there is a working fallback underneath. So letting go is safe in a way // it would not be for the disk the root filesystem is on. @@ -580,7 +580,7 @@ vgpu_release(struct pci_dev *d) if (!g) return -1; - // The rule from docs/plan/hybrid.md, enforced rather than described: **the + // The rule from docs/architecture/devices.md, enforced rather than described: **the // kernel keeps a minimal driver for everything on the path to a shell.** If // there is no framebuffer to fall back to, this device is the only screen the // machine has, and handing it to a process that might crash would make a @@ -645,7 +645,7 @@ vgpu_release(struct pci_dev *d) // --- the crash screen, on a device this driver was told to let go ---------- // -// docs/plan/hybrid.md's bargain has a clause nobody wrote down: a device the +// docs/architecture/devices.md's bargain has a clause nobody wrote down: a device the // kernel hands away is a device the kernel cannot use to tell you the machine // died. The second monitor sat there showing a shell prompt from before the // panic, which is the most misleading thing a screen can do. diff --git a/kernel/drivers/net/e1000/e1000.c b/kernel/drivers/net/e1000/e1000.c index 4007bba..0481ca1 100644 --- a/kernel/drivers/net/e1000/e1000.c +++ b/kernel/drivers/net/e1000/e1000.c @@ -423,7 +423,7 @@ e1000_link_up(struct device *dev) // Let go of the device, so a userspace driver can have it. // -// The same hook virtio-gpu grew for H1, on a NIC. docs/plan/hybrid.md's rule +// The same hook virtio-gpu grew for H1, on a NIC. docs/architecture/devices.md's rule // is that the kernel keeps a minimal driver for everything on the path to a // shell -- and a network interface is not on that path, so this one may leave. // diff --git a/kernel/drivers/net/r8169/r8169.c b/kernel/drivers/net/r8169/r8169.c index d74650c..7fae24e 100644 --- a/kernel/drivers/net/r8169/r8169.c +++ b/kernel/drivers/net/r8169/r8169.c @@ -49,8 +49,23 @@ #define R_RCR 0x44 // Rx config #define R_9346CR 0x50 // unlocks the config registers #define R_CONFIG1 0x52 +#define R_CONFIG2 0x53 +#define R_CONFIG3 0x54 +#define R_CONFIG5 0x56 +#define R_EEE_LED 0x1B +#define R_EPHYAR 0x80 // the PCIe PHY's registers +#define R_DLLPR 0xD0 +#define R_MISC1 0xF2 +#define R_INTRMIT 0xE2 // interrupt mitigation #define R_PHYAR 0x60 // the MII window #define R_PHYSTATUS 0x6C +#define R_PMCH 0x6F // PLL power +#define R_ERIDR 0x70 // extended register window: data +#define R_ERIAR 0x74 // and address +#define R_GPHY_OCP 0xB8 // the PHY's registers, 8168G and later +#define R_MAC_OCP 0xB0 // the MAC's own OCP registers +#define R_MCU 0xD3 +#define R_MISC 0xF0 #define R_RMS 0xDA // largest frame the chip will accept #define R_CPCR 0xE0 // "C+" command #define R_RDSAR 0xE4 // Rx descriptors, 64-bit @@ -77,6 +92,15 @@ #define PHYSTATUS_LINK 0x02 +#define MCU_NOW_IS_OOB 0x80 // the firmware, not a driver, owns the MAC +#define MCU_LL_READY 0x02 // link-list FIFO ready +#define MISC_RXDV_GATE 0x00080000 +#define OCP_FLAG 0x80000000 +#define OCP_STD_PHY_BASE 0xA400 +#define BMCR_PDOWN 0x0800 +#define BMCR_ANENABLE 0x1000 +#define BMCR_ANRESTART 0x0200 + // Rx config: accept frames addressed to us, broadcast and multicast, with the // largest DMA burst and Rx FIFO threshold the chip offers. Promiscuous is // deliberately not set -- a NIC that hands the stack every frame on the wire @@ -91,6 +115,11 @@ // MXDMA short. It would have worked and been slower, which is the worst kind // of wrong to leave in a register constant. #define RCR_UNLIMITED 0x0000E700 +// What Linux's rtl_init_rxcfg() writes on the 8168G and later instead: +// 128-byte early interrupt, multiple descriptors per DMA, unlimited burst, +// early receive off. +#define RCR_GEN_G (0x8000 | 0x4000 | 0x0700 | 0x0800) +#define TCR_AUTO_FIFO 0x00000080 // 8168E-VL and later: the chip sizes its FIFOs #define TCR_IFG_STD 0x03000000 #define TCR_MXDMA 0x00000700 @@ -112,7 +141,12 @@ struct rtl_desc { #define DESC_LS 0x10000000 // last fragment #define DESC_LEN 0x00003FFF -#define NRX 32 +// 256, as the e1000 has. It was 32, and TCP advertises a 256 KB window -- +// some 180 full frames in flight -- which a gigabit link fills 32 slots with +// in under half a millisecond. Everything past them was dropped, TCP read the +// drops as congestion, and a download on the laptop crawled: apk sat on its +// first index for minutes. The chip takes up to 1024. +#define NRX 256 #define NTX 32 #define BUFSZ 2048 // one frame, with room for a VLAN tag @@ -128,6 +162,8 @@ struct r8169 { int tx_next; uint8 mac[6]; int ready; + int gen_g; // 8168G or later: see r8169_gen_g() + uint16 ocp_base; // the PHY page selected through register 0x1F uint64 rx_packets, tx_packets; struct spinlock lock; @@ -168,7 +204,7 @@ wr64(struct r8169 *r, int o, uint64 v) #define PHYAR_FLAG 0x80000000 static void -mii_write(struct r8169 *r, int reg, uint16 val) +phyar_write(struct r8169 *r, int reg, uint16 val) { wr32(r, R_PHYAR, PHYAR_FLAG | ((reg & 0x1F) << 16) | val); for (int i = 0; i < 1000; i++) { @@ -179,7 +215,7 @@ mii_write(struct r8169 *r, int reg, uint16 val) } static int -mii_read(struct r8169 *r, int reg) +phyar_read(struct r8169 *r, int reg) { wr32(r, R_PHYAR, (reg & 0x1F) << 16); for (int i = 0; i < 1000; i++) { @@ -191,6 +227,374 @@ mii_read(struct r8169 *r, int reg) return -1; } +// From the 8168G on, the PHY sits behind an OCP window instead: its standard +// registers are at 0xA400 + 2*reg, and "select page N" through register 0x1F +// becomes a base address. PHYAR still answers reads on some of these chips, +// which is how this driver got away without it -- until a write mattered. +static void +ocp_phy_write(struct r8169 *r, uint16 addr, uint16 val) +{ + wr32(r, R_GPHY_OCP, OCP_FLAG | ((uint32)(addr >> 1) << 16) | val); + for (int i = 0; i < 1000; i++) { + microdelay(20); + if (!(rd32(r, R_GPHY_OCP) & OCP_FLAG)) + return; + } +} + +static int +ocp_phy_read(struct r8169 *r, uint16 addr) +{ + wr32(r, R_GPHY_OCP, (uint32)(addr >> 1) << 16); + for (int i = 0; i < 1000; i++) { + microdelay(20); + uint32 v = rd32(r, R_GPHY_OCP); + if (v & OCP_FLAG) + return (int)(v & 0xFFFF); + } + return -1; +} + +static void +mii_write(struct r8169 *r, int reg, uint16 val) +{ + if (!r->gen_g) { + phyar_write(r, reg, val); + return; + } + if (reg == 0x1F) { + r->ocp_base = val ? (uint16)(val << 4) : OCP_STD_PHY_BASE; + return; + } + if (r->ocp_base != OCP_STD_PHY_BASE) + reg -= 0x10; + ocp_phy_write(r, (uint16)(r->ocp_base + reg * 2), val); +} + +static int +mii_read(struct r8169 *r, int reg) +{ + if (!r->gen_g) + return phyar_read(r, reg); + if (r->ocp_base != OCP_STD_PHY_BASE) + reg -= 0x10; + return ocp_phy_read(r, (uint16)(r->ocp_base + reg * 2)); +} + +// The MAC's OCP registers. No completion flag to wait on. +static uint16 +mac_ocp_read(struct r8169 *r, uint16 reg) +{ + wr32(r, R_MAC_OCP, (uint32)(reg >> 1) << 16); + return (uint16)rd32(r, R_MAC_OCP); +} + +static void +mac_ocp_write(struct r8169 *r, uint16 reg, uint16 val) +{ + wr32(r, R_MAC_OCP, OCP_FLAG | ((uint32)(reg >> 1) << 16) | val); +} + +// The extended (ERI) registers. `mask` picks the bytes, bit 0 the lowest: +// several of these registers share a word with ones that must not change. +static void +eri_write_mask(struct r8169 *r, int addr, int mask, uint32 val) +{ + wr32(r, R_ERIDR, val); + wr32(r, R_ERIAR, OCP_FLAG | ((uint32)mask << 12) | addr); + for (int i = 0; i < 100; i++) { + microdelay(10); + if (!(rd32(r, R_ERIAR) & OCP_FLAG)) + return; + } +} + +// All four bytes at once. +static uint32 +eri_read(struct r8169 *r, int addr) +{ + wr32(r, R_ERIAR, (0xFu << 12) | addr); + for (int i = 0; i < 100; i++) { + microdelay(10); + if (rd32(r, R_ERIAR) & OCP_FLAG) + return rd32(r, R_ERIDR); + } + return 0; +} + +static void +eri_write(struct r8169 *r, int addr, uint32 val) +{ + wr32(r, R_ERIDR, val); + wr32(r, R_ERIAR, OCP_FLAG | (0xFu << 12) | addr); + for (int i = 0; i < 100; i++) { + microdelay(10); + if (!(rd32(r, R_ERIAR) & OCP_FLAG)) + return; + } +} + +// Which XIDs are the 8168G generation and later: 8168G, GU, 8411B, EP, H. +// They share the OCP windows and the firmware handover below. +static int +r8169_gen_g(uint32 xid) +{ + switch (xid) { + case 0x4C0: case 0x4C1: case 0x5C8: case 0x509: + case 0x500: case 0x501: case 0x502: + case 0x540: case 0x541: case 0x6C0: + return 1; + } + return 0; +} + +static void +wait_ll_ready(struct r8169 *r) +{ + for (int i = 0; i < 42; i++) { + if (rd8(r, R_MCU) & MCU_LL_READY) + return; + microdelay(100); + } +} + +// Taking the chip from the firmware. A laptop that was last running Windows, +// or that keeps Wake-on-LAN armed, hands it over in out-of-band mode with the +// receive path gated off: the MAC answers its registers and moves no frames. +static void +r8169_takeover_g(struct r8169 *r) +{ + wr32(r, R_MISC, rd32(r, R_MISC) | MISC_RXDV_GATE); + wr8(r, R_CR, rd8(r, R_CR) & ~(CR_TE | CR_RE)); + microdelay(1000); + wr8(r, R_MCU, rd8(r, R_MCU) & ~MCU_NOW_IS_OOB); + mac_ocp_write(r, 0xE8DE, mac_ocp_read(r, 0xE8DE) & ~(1u << 14)); + wait_ll_ready(r); + mac_ocp_write(r, 0xE8DE, mac_ocp_read(r, 0xE8DE) | (1u << 15)); + wait_ll_ready(r); +} + +// --- the 8168H as Linux sets it up ------------------------------------------ +// +// From Linux 6.18 drivers/net/ethernet/realtek/r8169_main.c +// (rtl_hw_start_8168h_1, rtl_hw_aspm_clkreq_enable) and r8169_phy_config.c +// (rtl8168h_2_hw_phy_config), GPL-2.0, carried over register for register. +// Without them the chip runs on its power-on FIFO sizes and PHY tuning and +// with ASPM as the firmware left it, and moves frames, slowly and with drops. +// The one part left out is the PHY firmware patch (rtl_nic/rtl8168h-2.fw), +// which needs the root filesystem this runs before. + +static void +ephy_write(struct r8169 *r, int reg, uint16 val) +{ + wr32(r, R_EPHYAR, 0x80000000u | ((uint32)(reg & 0x1F) << 16) | val); + for (int i = 0; i < 100; i++) { + microdelay(10); + if (!(rd32(r, R_EPHYAR) & 0x80000000u)) + break; + } + microdelay(10); +} + +static uint16 +ephy_read(struct r8169 *r, int reg) +{ + wr32(r, R_EPHYAR, (uint32)(reg & 0x1F) << 16); + for (int i = 0; i < 100; i++) { + microdelay(10); + uint32 v = rd32(r, R_EPHYAR); + if (v & 0x80000000u) + return (uint16)v; + } + return 0xFFFF; +} + +static void +mac_ocp_modify(struct r8169 *r, uint16 reg, uint16 clear, uint16 set) +{ + mac_ocp_write(r, reg, (mac_ocp_read(r, reg) & ~clear) | set); +} + +static int +phy_read_paged(struct r8169 *r, int page, int reg) +{ + mii_write(r, 0x1F, (uint16)page); + int v = mii_read(r, reg); + mii_write(r, 0x1F, 0); + return v; +} + +static void +phy_write_paged(struct r8169 *r, int page, int reg, uint16 val) +{ + mii_write(r, 0x1F, (uint16)page); + mii_write(r, reg, val); + mii_write(r, 0x1F, 0); +} + +static void +phy_modify_paged(struct r8169 *r, int page, int reg, uint16 clear, uint16 set) +{ + mii_write(r, 0x1F, (uint16)page); + int v = mii_read(r, reg); + if (v >= 0) + mii_write(r, reg, (uint16)((v & ~clear) | set)); + mii_write(r, 0x1F, 0); +} + +static void +phy_param(struct r8169 *r, uint16 parm, uint16 clear, uint16 set) +{ + mii_write(r, 0x1F, 0x0A43); + mii_write(r, 0x13, parm); + int v = mii_read(r, 0x14); + if (v >= 0) + mii_write(r, 0x14, (uint16)((v & ~clear) | set)); + mii_write(r, 0x1F, 0); +} + +// PCIe Link Control, low two bits: L0s and L1. Linux clears L1 on this chip +// and on the bridge above it unless the vendor has marked it safe, for "random +// device stop working problems as well as full system hangs". Firmware +// leaves it on. A link that drops into L1 between frames costs a wake-up per +// burst and on some boards stalls outright, which is a download that crawls. +static void +aspm_off(struct pci_dev *d) +{ + uint8 cap = pci_find_cap(d, 0x10); + if (cap) { + uint32 v = pci_read32(d->bus, d->slot, d->func, cap + 0x10); + pci_write32(d->bus, d->slot, d->func, cap + 0x10, v & ~3u); + } +} + +static void +r8169_aspm_off(struct r8169 *r) +{ + struct pci_dev *d = r->pdev; + + // The vendor's flag that ASPM is safe on this board (rtl_aspm_is_safe). + if (mac_ocp_read(r, 0xC0B2) & 0xF) + return; + aspm_off(d); + for (int i = 0; i < pci_count(); i++) { + struct pci_dev *b = pci_get(i); + if (b && (b->header_type & 0x7F) == 1 && + pci_read8(b->bus, b->slot, b->func, 0x19) == d->bus) + aspm_off(b); + } + // And in the chip: no L1.2 trigger, no clock request, ASPM off. + mac_ocp_modify(r, 0xE092, 0x00FF, 0); + wr8(r, R_CONFIG2, rd8(r, R_CONFIG2) & ~0x80); + wr8(r, R_CONFIG5, rd8(r, R_CONFIG5) & ~0x01); +} + +// rtl_hw_start_8168h_1. Config registers must be unlocked. +static void +r8169_start_8168h(struct r8169 *r) +{ + static const struct { uint8 reg; uint16 clear, set; } ephy[] = { + { 0x1E, 0x0800, 0x0001 }, + { 0x1D, 0x0000, 0x0800 }, + { 0x05, 0xFFFF, 0x2089 }, + { 0x06, 0xFFFF, 0x5881 }, + { 0x04, 0xFFFF, 0x854A }, + { 0x01, 0xFFFF, 0x068B }, + }; + for (unsigned i = 0; i < sizeof ephy / sizeof ephy[0]; i++) + ephy_write(r, ephy[i].reg, + (ephy_read(r, ephy[i].reg) & ~ephy[i].clear) | ephy[i].set); + + eri_write(r, 0xC8, (0x08u << 16) | 0x02); // FIFO sizes + eri_write(r, 0xE8, (0x10u << 16) | 0x06); + eri_write_mask(r, 0xCC, 0x1, 0x38); // pause thresholds + eri_write_mask(r, 0xD0, 0x1, 0x48); + + eri_write(r, 0xDC, eri_read(r, 0xDC) & ~1u); // reset the packet filter + eri_write(r, 0xDC, eri_read(r, 0xDC) | 1u); + eri_write(r, 0xDC, eri_read(r, 0xDC) | 0x1C); + eri_write_mask(r, 0x5F0, 0x3, 0x4F87); + wr32(r, R_MISC, rd32(r, R_MISC) & ~MISC_RXDV_GATE); + eri_write_mask(r, 0xC0, 0x3, 0); + eri_write_mask(r, 0xB8, 0x3, 0); + + wr8(r, R_EEE_LED, rd8(r, R_EEE_LED) & ~0x07); // rtl8168_config_eee_mac + eri_write(r, 0x1B0, eri_read(r, 0x1B0) | 0x3); + + wr8(r, R_DLLPR, rd8(r, R_DLLPR) & ~0x40); // PFM off + wr8(r, R_MISC1, rd8(r, R_MISC1) & ~0x40); // PFM in D3cold off + wr8(r, R_DLLPR, rd8(r, R_DLLPR) & ~0x80); // 10M power saving off + eri_write(r, 0x1B0, eri_read(r, 0x1B0) & ~(1u << 12)); + wr8(r, R_CONFIG3, rd8(r, R_CONFIG3) & ~0x02); // Rdy_to_L23 off + + int saw = phy_read_paged(r, 0x0C42, 0x13); + if (saw > 0 && (saw & 0x3FFF) > 0) + mac_ocp_modify(r, 0xD412, 0x0FFF, (16000000 / (saw & 0x3FFF)) & 0x0FFF); + + mac_ocp_modify(r, 0xE056, 0x00F0, 0x0000); + mac_ocp_modify(r, 0xE052, 0x6000, 0x8008); + mac_ocp_modify(r, 0xE0D6, 0x01FF, 0x017F); + mac_ocp_modify(r, 0xD420, 0x0FFF, 0x047F); + mac_ocp_write(r, 0xE63E, 0x0001); + mac_ocp_write(r, 0xE63E, 0x0000); + mac_ocp_write(r, 0xC094, 0x0000); + mac_ocp_write(r, 0xC09E, 0x0000); +} + +// rtl8168h_2_hw_phy_config, less the firmware patch. +static void +r8169_phy_8168h(struct r8169 *r) +{ + phy_param(r, 0x808A, 0x003F, 0x000A); // CHIN EST parameter + phy_param(r, 0x0811, 0x0000, 0x0800); // R-tune and PGA-retune on + phy_modify_paged(r, 0x0A42, 0x16, 0x0000, 0x0002); + phy_modify_paged(r, 0x0A44, 0x11, 0, 1u << 11); // gphy 10M + + // ADC bias offset, from the MAC's OCP space. + mac_ocp_write(r, 0xDD02, 0x807D); + uint16 d1 = mac_ocp_read(r, 0xDD02), d2 = mac_ocp_read(r, 0xDD00); + uint16 ioffset = ((d2 >> 1) & 0x7FF8) | (d2 & 0x0007) | ((d1 & 0x80) ? 0x8000 : 0); + if (ioffset != 0xFFFF) + phy_write_paged(r, 0x0BCF, 0x16, ioffset); + + // TX low-pass corner. + int data = phy_read_paged(r, 0x0BCD, 0x16); + if (data >= 0) { + uint16 rlen = (data & 0xF) > 3 ? (data & 0xF) - 3 : 0; + phy_write_paged(r, 0x0BCD, 0x17, rlen | rlen << 4 | rlen << 8 | rlen << 12); + } + + phy_modify_paged(r, 0x0A44, 0x11, 1u << 7, 0); // PHY PFM off + phy_modify_paged(r, 0x0A43, 0x10, 1u << 0, 0); // 10M PLL off: off + phy_modify_paged(r, 0x0A43, 0x10, 1u << 2, 0); // ALDPS off + phy_modify_paged(r, 0x0A43, 0x11, 0, 1u << 4); // EEE +} + +// The PHY up and negotiating: page 0, out of power-down, autoneg on and +// restarted. Power-down is what firmware leaves set after a shutdown, and +// the old code ORed autoneg into BMCR and kept it. +static void +r8169_phy_up(struct r8169 *r) +{ + mii_write(r, 0x1F, 0); + int bmcr = mii_read(r, 0); + if (bmcr < 0) + return; + bmcr &= ~BMCR_PDOWN; + mii_write(r, 0, (uint16)bmcr); + if (r->gen_g) { + // Wait for the PHY to report it is on: its state, low three bits of + // OCP 0xA420, reads 3 once it is. + for (int i = 0; i < 100; i++) { + int st = ocp_phy_read(r, 0xA420); + if (st >= 0 && (st & 7) == 3) + break; + microdelay(1000); + } + } + mii_write(r, 0, (uint16)(bmcr | BMCR_ANENABLE | BMCR_ANRESTART)); +} + // --- the class ops ---------------------------------------------------------- static const unsigned char * @@ -392,6 +796,7 @@ r8169_probe(struct pci_dev *d) } r->pdev = d; + r->ocp_base = OCP_STD_PHY_BASE; initlock(&r->lock, "r8169"); pci_enable(d, PCI_CMD_MEMORY | PCI_CMD_BUS_MASTER); @@ -404,6 +809,10 @@ r8169_probe(struct pci_dev *d) return -1; } + r->gen_g = r8169_gen_g((rd32(r, R_TCR) >> 20) & 0xFCF); + if (r->gen_g) + r8169_takeover_g(r); + // Soft reset, and wait for the chip to say it finished. wr8(r, R_CR, CR_RST); for (int i = 0; i < 1000; i++) { @@ -426,6 +835,20 @@ r8169_probe(struct pci_dev *d) wr8(r, R_9346CR, C9346_UNLOCK); // config registers are writable now + if (r->gen_g) { + // The PLL powered, the extra ERI bits the 8168H wants with it, and the + // receive gate opened again. + wr8(r, R_PMCH, rd8(r, R_PMCH) | 0xC0); + uint32 xid = (rd32(r, R_TCR) >> 20) & 0xFCF; + if (xid == 0x540 || xid == 0x541) + eri_write(r, 0x1A8, eri_read(r, 0x1A8) | 0xFC000000); + wr32(r, R_MISC, rd32(r, R_MISC) & ~MISC_RXDV_GATE); + if (xid == 0x541 || xid == 0x6C0) { + r8169_aspm_off(r); + r8169_start_8168h(r); + } + } + // CPCR is deliberately left alone. // // An earlier version set bit 3 here with a comment calling it "PCI multiple @@ -439,13 +862,16 @@ r8169_probe(struct pci_dev *d) // unlike the 8139 it has no other programming model to be in. wr16(r, R_RMS, BUFSZ); - wr8(r, R_MTPS, 0x3B); + // EarlySize on the 8168E-VL and later, TxPacketMax before, as in Linux. + wr8(r, R_MTPS, r->gen_g ? 0x27 : 0x3B); + wr16(r, R_INTRMIT, 0); // no interrupt coalescing wr64(r, R_RDSAR, r->rx_phys); wr64(r, R_TNPDS, r->tx_phys); - wr32(r, R_TCR, TCR_IFG_STD | TCR_MXDMA); - wr32(r, R_RCR, RCR_APM | RCR_AM | RCR_AB | RCR_UNLIMITED); + wr32(r, R_TCR, TCR_IFG_STD | TCR_MXDMA | (r->gen_g ? TCR_AUTO_FIFO : 0)); + wr32(r, R_RCR, RCR_APM | RCR_AM | RCR_AB | + (r->gen_g ? RCR_GEN_G : RCR_UNLIMITED)); // Multicast: everything. The stack filters, and a hash filter that is wrong // drops IPv6 neighbour discovery in a way that looks like a routing problem. @@ -455,14 +881,12 @@ r8169_probe(struct pci_dev *d) wr8(r, R_CR, CR_RE | CR_TE); wr8(r, R_9346CR, C9346_LOCK); - // Restart autonegotiation. The PHY normally does this out of reset, and - // asking again costs nothing and covers the case where firmware left it - // parked -- BMCR bit 12 is autoneg enable, bit 9 restarts it. { - int bmcr = mii_read(r, 0); - if (bmcr >= 0) - mii_write(r, 0, (uint16)(bmcr | 0x1200)); + uint32 xid = (rd32(r, R_TCR) >> 20) & 0xFCF; + if (xid == 0x541 || xid == 0x6C0) + r8169_phy_8168h(r); } + r8169_phy_up(r); wr16(r, R_ISR, 0xFFFF); // clear anything left over wr16(r, R_IMR, ISR_ROK | ISR_RER | ISR_TOK | ISR_TER | @@ -471,7 +895,7 @@ r8169_probe(struct pci_dev *d) // MSI first: every 8111 has it, and on the boards these are soldered to it // is the only interrupt that is sure to arrive. See pci_irq_attach(). if (pci_irq_attach(d, r8169_intr, r, "r8169", 0) < 0) - cprintf("r8169: no usable interrupt line; the link will be polled\n"); + kinfo("r8169: no usable interrupt line; the link will be polled\n"); r->ready = 1; @@ -487,7 +911,7 @@ r8169_probe(struct pci_dev *d) uint32 xid = (rd32(r, R_TCR) >> 20) & 0xFCF; int bmsr = mii_read(r, 1); - cprintf("r8169: xid %x, %x:%x:%x:%x:%x:%x, irq %d (%s), %s (bmsr %x)\n", + kinfo("r8169: xid %x, %x:%x:%x:%x:%x:%x, irq %d (%s), %s (bmsr %x)\n", xid, r->mac[0], r->mac[1], r->mac[2], r->mac[3], r->mac[4], r->mac[5], d->irq_line, pci_irq_kind(d), diff --git a/kernel/drivers/registry.c b/kernel/drivers/registry.c index 191899c..e9616ba 100644 --- a/kernel/drivers/registry.c +++ b/kernel/drivers/registry.c @@ -12,9 +12,10 @@ // a driver silently dropped by the linker looks exactly like one that was // never written. At this size, being able to read the list wins. // -// To add a driver: write kernel/drivers/.c exporting a struct -// pci_driver, declare it here, and add it to the array and to the Makefile's -// KERN_DRIVERS. +// To add a driver: write kernel/drivers///.c exporting a +// struct pci_driver, declare it here, add it to the array and to the Makefile's +// KERN_DRIVERS -- and, if nothing else in the kernel calls it, give it a +// DRV_ option in config/drivers.conf so it can be left out. #include "core/types.h" #include "core/defs.h" @@ -28,15 +29,34 @@ #include "dev/usb.h" #include "dev/device.h" +// Which of these exist in this kernel is config/drivers.conf: the Makefile +// leaves a disabled driver's object out and defines CONFIG_DRV_ for the +// ones it keeps. The ones with no option (xHCI) are always here. +#ifdef CONFIG_DRV_VGA_PCI extern const struct pci_driver vga_pci_driver; +#endif extern const struct pci_driver xhci_driver; +#ifdef CONFIG_DRV_VIRTIO_GPU extern const struct pci_driver virtio_gpu_driver; +#endif +#ifdef CONFIG_DRV_E1000 extern const struct pci_driver e1000_driver; +#endif +#ifdef CONFIG_DRV_AC97 extern const struct pci_driver ac97_driver; -extern const struct pci_driver r8169_driver; +#endif +#ifdef CONFIG_DRV_RTL8139 extern const struct pci_driver rtl8139_driver; +#endif +#ifdef CONFIG_DRV_R8169 +extern const struct pci_driver r8169_driver; +#endif +#ifdef CONFIG_DRV_AHCI extern const struct pci_driver ahci_driver; +#endif +#ifdef CONFIG_DRV_NVME extern const struct pci_driver nvme_driver; +#endif // Order matters here in one respect: virtio-gpu is offered the bus before the // VBE framebuffer driver, so that on a machine with both, the device with real @@ -44,21 +64,42 @@ extern const struct pci_driver nvme_driver; // claims the stdvga card underneath, which is what the boot splash and the // text console are drawn on. static const struct pci_driver *drivers[] = { +#ifdef CONFIG_DRV_VIRTIO_GPU &virtio_gpu_driver, +#endif +#ifdef CONFIG_DRV_VGA_PCI &vga_pci_driver, +#endif +#ifdef CONFIG_DRV_E1000 &e1000_driver, +#endif + // The RTL8168 is driven by Linux's own r8169 through LDLS + // (etc/schinit/ldls-r8169.job), which a kernel driver here would claim + // first -- so DRV_R8169 is off by default. The native driver stays in the + // tree as the fallback for a machine without that job. +#ifdef CONFIG_DRV_R8169 &r8169_driver, +#endif +#ifdef CONFIG_DRV_RTL8139 &rtl8139_driver, +#endif &xhci_driver, +#ifdef CONFIG_DRV_AHCI &ahci_driver, +#endif +#ifdef CONFIG_DRV_NVME &nvme_driver, +#endif +#ifdef CONFIG_DRV_AC97 &ac97_driver, +#endif + 0, // keeps the array non-empty when everything optional is off }; void pci_bind_all(void) { - for (unsigned i = 0; i < sizeof(drivers) / sizeof(drivers[0]); i++) + for (unsigned i = 0; drivers[i]; i++) pci_bind(drivers[i]); } diff --git a/kernel/drivers/storage/nvme/nvme.c b/kernel/drivers/storage/nvme/nvme.c index 22ca481..e376452 100644 --- a/kernel/drivers/storage/nvme/nvme.c +++ b/kernel/drivers/storage/nvme/nvme.c @@ -330,13 +330,13 @@ add_namespace(struct nvme *c, uint32 nsid) uint32 lbaf = *(uint32 *)(id + 128 + 4 * (id[26] & 0xF)); uint32 lbads = (lbaf >> 16) & 0xFF; if (lbads < 9 || lbads > 12) { - cprintf("nvme%d: namespace %d has %d-byte sectors; not used\n", + kinfo("nvme%d: namespace %d has %d-byte sectors; not used\n", c->index, nsid, 1 << lbads); return; } if (lbaf & 0xFFFF) { // Metadata interleaved with the data would land in our bounce buffer. - cprintf("nvme%d: namespace %d carries metadata; not used\n", + kinfo("nvme%d: namespace %d carries metadata; not used\n", c->index, nsid); return; } @@ -357,7 +357,7 @@ add_namespace(struct nvme *c, uint32 nsid) *p++ = '0' + nsid % 10; *p = 0; - cprintf("nvme: %s: %d MB, %d-byte sectors\n", ns->name, + kinfo("nvme: %s: %d MB, %d-byte sectors\n", ns->name, (int)((nsze << lbads) >> 20), ns->secsize); blkdev_register(ns->name, &nvme_ops, ns, ns->nsectors, ns->secsize); } diff --git a/kernel/drivers/usb/core/usb.c b/kernel/drivers/usb/core/usb.c index b4baec8..517cb29 100644 --- a/kernel/drivers/usb/core/usb.c +++ b/kernel/drivers/usb/core/usb.c @@ -184,21 +184,139 @@ static struct spinlock present_lock; static int present_init; static struct usb_dev *present[NPRESENT]; static void *buses[NBUSES]; +static uint8 bus_pci[NBUSES][3]; // the controller's PCI bus, slot, function + +// Give a controller its bus number if it has none. Caller holds present_lock. +// Returns the number (1-based), or 0 if there is no room. +static int +bus_number(void *hc) +{ + for(int b = 0; b < NBUSES; b++){ + if(buses[b] == hc) return b + 1; + if(buses[b] == 0){ buses[b] = hc; return b + 1; } + } + return 0; +} static void -present_add(struct usb_dev *dev) +present_init_once(void) { if(!present_init){ initlock(&present_lock, "usbpresent"); present_init = 1; } +} + +// A controller is up: number its bus now and announce its root hub, so usbN +// is in /sys before anything is plugged into it. The loader of the driver that +// calls this runs once per controller, in PCI order -- which is what ties bus +// N to the Nth controller in sysfs.c. +void +usb_bus_add(void *hc, int pci_bus, int pci_slot, int pci_func) +{ + int n; + + present_init_once(); + acquire(&present_lock); + n = bus_number(hc); + if(n){ + bus_pci[n - 1][0] = (uint8)pci_bus; + bus_pci[n - 1][1] = (uint8)pci_slot; + bus_pci[n - 1][2] = (uint8)pci_func; + } + release(&present_lock); + if(n) + sysfs_usb_root_uevent(n, "add"); +} + +int +usb_bus_pci(int bus, int *pb, int *ps, int *pf) +{ + int r = -1; + + if(!present_init || bus < 1 || bus > NBUSES) + return -1; + acquire(&present_lock); + if(buses[bus - 1]){ + *pb = bus_pci[bus - 1][0]; + *ps = bus_pci[bus - 1][1]; + *pf = bus_pci[bus - 1][2]; + r = 0; + } + release(&present_lock); + return r; +} + +// The nth interface (alternate setting 0) in `dev`'s configuration descriptor. +// Returns 0, or -1 if there are fewer. The driver is whatever is bound to an +// interface of that number, on the device itself or on one of its interface +// slots. +static int +if_desc(struct usb_dev *dev, int nth, struct usb_if_info *o) +{ + int off = 0, count = 0; + + while(off + 2 <= dev->conflen){ + uint8 len = dev->conf[off]; + uint8 type = dev->conf[off + 1]; + + if(len < 2 || off + len > dev->conflen) + break; + if(type == USB_DT_INTERFACE && len >= (int)sizeof(struct usb_interface_desc)){ + struct usb_interface_desc *id = (struct usb_interface_desc *)&dev->conf[off]; + if(id->bAlternateSetting == 0 && count++ == nth){ + memset(o, 0, sizeof *o); + o->num = id->bInterfaceNumber; + o->cls = id->bInterfaceClass; + o->subcls = id->bInterfaceSubClass; + o->proto = id->bInterfaceProtocol; + o->nendp = id->bNumEndpoints; + if(dev->driver && dev->iface.bInterfaceNumber == o->num) + safestrcpy(o->driver, dev->driver, sizeof o->driver); + for(int k = 0; !o->driver[0] && k < NIFDEV; k++) + if(ifdevs[k].in_use && ifdevs[k].base == dev && ifdevs[k].driver && + ifdevs[k].iface.bInterfaceNumber == o->num) + safestrcpy(o->driver, ifdevs[k].driver, sizeof o->driver); + return 0; + } + } + off += len; + } + return -1; +} + +int +usb_if_nth(int bus, int devnum, int n, struct usb_if_info *o) +{ + int r = -1; + + if(!present_init) + return -1; + acquire(&present_lock); + for(int i = 0; i < NPRESENT; i++){ + struct usb_dev *d = present[i]; + if(d == 0 || d->slot != devnum) + continue; + int b = 1; + for(int k = 0; k < NBUSES; k++) + if(buses[k] == d->hc){ b = k + 1; break; } + if(b != bus) + continue; + r = if_desc(d, n, o); + break; + } + release(&present_lock); + return r; +} + +static void +present_add(struct usb_dev *dev) +{ + present_init_once(); acquire(&present_lock); for(int i = 0; i < NPRESENT; i++) if(present[i] == 0){ present[i] = dev; break; } - for(int b = 0; b < NBUSES; b++){ - if(buses[b] == dev->hc) break; - if(buses[b] == 0){ buses[b] = dev->hc; break; } - } + bus_number(dev->hc); release(&present_lock); } @@ -223,6 +341,53 @@ put_num(char *out, int *n, int cap, int v) out[*n] = 0; } +// Copy `d`'s identity out. Caller holds present_lock, or is the one thread +// that owns `d` (attach and detach). +static void +fill_info(struct usb_dev *d, struct usb_info *o) +{ + memset(o, 0, sizeof *o); + o->bus = 1; + for(int b = 0; b < NBUSES; b++) + if(buses[b] == d->hc){ o->bus = b + 1; break; } + o->devnum = d->slot; + o->speed = d->speed; + o->vid = d->desc.idVendor; + o->pid = d->desc.idProduct; + o->bcd = d->desc.bcdDevice; + o->cls = d->desc.bDeviceClass; + o->subcls = d->desc.bDeviceSubClass; + o->proto = d->desc.bDeviceProtocol; + o->nconf = d->desc.bNumConfigurations; + o->bcdusb = d->desc.bcdUSB; + o->maxp0 = d->desc.bMaxPacketSize0; + if(d->conflen >= 9){ + o->nif = d->conf[4]; + o->confval = d->conf[5]; + o->attrs = d->conf[7]; + // bMaxPower counts 2 mA below SuperSpeed and 8 mA from it. + o->maxpower = (uint16)(d->conf[8] * (d->speed >= 4 ? 8 : 2)); + } + + // "-[....]": the route string holds one + // downstream port per tier below the root, a nibble each. + int n = 0, cap = (int)sizeof o->path; + put_num(o->path, &n, cap, o->bus); + o->path[n++] = '-'; + put_num(o->path, &n, cap, d->port); + for(int t = 0; t < d->tier && n < cap - 3; t++){ + o->path[n++] = '.'; + put_num(o->path, &n, cap, (int)((d->route >> (4 * t)) & 0xF)); + } + + const char *drv = d->driver; + for(int k = 0; !drv && k < NIFDEV; k++) + if(ifdevs[k].in_use && ifdevs[k].base == d) + drv = ifdevs[k].driver; + if(drv) + safestrcpy(o->driver, drv, sizeof o->driver); +} + int usb_info_nth(int idx, struct usb_info *o) { @@ -234,37 +399,7 @@ usb_info_nth(int idx, struct usb_info *o) struct usb_dev *d = present[i]; if(d == 0 || seen++ != idx) continue; - memset(o, 0, sizeof *o); - o->bus = 1; - for(int b = 0; b < NBUSES; b++) - if(buses[b] == d->hc){ o->bus = b + 1; break; } - o->devnum = d->slot; - o->speed = d->speed; - o->vid = d->desc.idVendor; - o->pid = d->desc.idProduct; - o->bcd = d->desc.bcdDevice; - o->cls = d->desc.bDeviceClass; - o->subcls = d->desc.bDeviceSubClass; - o->proto = d->desc.bDeviceProtocol; - o->nconf = d->desc.bNumConfigurations; - - // "-[....]": the route string holds one - // downstream port per tier below the root, a nibble each. - int n = 0, cap = (int)sizeof o->path; - put_num(o->path, &n, cap, o->bus); - o->path[n++] = '-'; - put_num(o->path, &n, cap, d->port); - for(int t = 0; t < d->tier && n < cap - 3; t++){ - o->path[n++] = '.'; - put_num(o->path, &n, cap, (int)((d->route >> (4 * t)) & 0xF)); - } - - const char *drv = d->driver; - for(int k = 0; !drv && k < NIFDEV; k++) - if(ifdevs[k].in_use && ifdevs[k].base == d) - drv = ifdevs[k].driver; - if(drv) - safestrcpy(o->driver, drv, sizeof o->driver); + fill_info(d, o); release(&present_lock); return 0; } @@ -272,6 +407,37 @@ usb_info_nth(int idx, struct usb_info *o) return -1; } +int +usb_nbuses(void) +{ + int n = 0; + if(!present_init) + return 0; + acquire(&present_lock); + for(int b = 0; b < NBUSES; b++) + if(buses[b]) + n++; + release(&present_lock); + return n; +} + +static void announce_ifaces(struct usb_dev *dev, const char *action); + +// Announce `dev` to udev. Attach says "add" once the device is in the table, +// so a rule that opens its /sys directory finds it; detach says "remove" +// first, while the directory still exists. +static void +announce(struct usb_dev *dev, const char *action) +{ + struct usb_info u; + if(!present_init) + return; + acquire(&present_lock); + fill_info(dev, &u); + release(&present_lock); + sysfs_usb_uevent(&u, action); +} + void usb_attach(struct usb_dev *dev) { @@ -282,6 +448,7 @@ usb_attach(struct usb_dev *dev) if(buf == 0) return; present_add(dev); + announce(dev, "add"); // Nine bytes first, because wTotalLength is in them and there is no other // way to know how much to ask for. @@ -341,7 +508,7 @@ usb_attach(struct usb_dev *dev) break; } - cprintf("usb: interface %d: class %d.%d protocol %d, %d endpoint%s%s%s%s\n", + kinfo("usb: interface %d: class %d.%d protocol %d, %d endpoint%s%s%s%s\n", d->iface.bInterfaceNumber, d->iface.bInterfaceClass, d->iface.bInterfaceSubClass, d->iface.bInterfaceProtocol, d->iface.bNumEndpoints, @@ -353,11 +520,30 @@ usb_attach(struct usb_dev *dev) usb_bind_all(d); if(d->driver == 0){ - cprintf("usb: no driver for this interface\n"); + kinfo("usb: no driver for this interface\n"); if(n > 0) d->in_use = 0; // nothing will ever use it; give it back } } + + // Every interface, once all of them have been offered to the drivers, so the + // event says which one was taken. + announce_ifaces(dev, "add"); +} + +static void +announce_ifaces(struct usb_dev *dev, const char *action) +{ + struct usb_info u; + struct usb_if_info ii; + + if(!present_init) + return; + acquire(&present_lock); + fill_info(dev, &u); + release(&present_lock); + for(int n = 0; if_desc(dev, n, &ii) == 0; n++) + sysfs_usb_if_uevent(&u, &ii, action); } // Tell every driver bound to any interface of `dev` that it is gone, and give @@ -365,6 +551,8 @@ usb_attach(struct usb_dev *dev) void usb_detach(struct usb_dev *dev) { + announce_ifaces(dev, "remove"); + announce(dev, "remove"); present_del(dev); for(int i = 0; i < NIFDEV; i++){ struct usb_dev *d = &ifdevs[i]; diff --git a/kernel/drivers/usb/hid/usb_hid.c b/kernel/drivers/usb/hid/usb_hid.c index 94cea98..96e9cfc 100644 --- a/kernel/drivers/usb/hid/usb_hid.c +++ b/kernel/drivers/usb/hid/usb_hid.c @@ -43,6 +43,7 @@ struct hfield { struct hid { int in_use; + struct usb_dev *dev; int proto; // USB_HID_PROTO_KEYBOARD, _MOUSE or _TABLET uchar prev[8]; // the previous report, for edge detection @@ -164,10 +165,13 @@ usb_kbd_getc(void) static int hid_special(uchar usage, uchar mods) { - // F1..F6 with Alt, AltGr or Ctrl+Alt: switch terminals, as on Linux. + // F1..F12 with left Alt or Ctrl+Alt: switch terminals, as on Linux and as + // the PS/2 path does. AltGr alone is a layout's third level, not this. if (usage >= 0x3A && usage < 0x3A + NVT && (mods & HID_MOD_ALT) && - !(mods & HID_MOD_SHIFT)) + !(mods & HID_MOD_SHIFT) && ((mods & 0x04) || (mods & HID_MOD_CTRL))) return KBD_VT_SWITCH + (usage - 0x3A); + if (usage >= 0x3A && usage <= 0x45) + return KBD_F1 + (usage - 0x3A); switch (usage) { case 0x49: return KBD_INS; case 0x4A: return KBD_HOME; @@ -245,6 +249,13 @@ keyboard_report(struct hid *h, const uchar *rep, int len) keyboard_events(h->prev, rep); + // A lock key let go: the next press toggles again. + for(i = 2; i < 8; i++){ + uchar was = h->prev[i]; + if((was == 0x39 || was == 0x53 || was == 0x47) && !was_down(rep, was)) + kbd_lock_key(was == 0x39 ? CAPSLOCK : was == 0x53 ? NUMLOCK : SCROLLLOCK, 0); + } + // Grabbed: the events above still went to /dev/input/event0, but nothing // reaches the console. Note prev[] is updated before returning, or the next // report would look like every held key had just been pressed again. @@ -261,11 +272,19 @@ keyboard_report(struct hid *h, const uchar *rep, int len) if(was_down(h->prev, usage)) continue; // still held from last time, not a new press + if(usage == 0x39 || usage == 0x53 || usage == 0x47){ + kbd_lock_key(usage == 0x39 ? CAPSLOCK : usage == 0x53 ? NUMLOCK : SCROLLLOCK, 1); + continue; + } if(usage >= 0x39){ if((c = hid_special(usage, mods)) == 0) continue; } else { - c = (mods & HID_MOD_SHIFT) ? hid_shifted[usage] : hid_unshifted[usage]; + // Caps lock shifts letters only, as on every keyboard. + int shifted = (mods & HID_MOD_SHIFT) != 0; + if((kbd_lock_state() & CAPSLOCK) && usage >= 0x04 && usage <= 0x1D) + shifted = !shifted; + c = shifted ? hid_shifted[usage] : hid_unshifted[usage]; if(c == 0) continue; } @@ -302,6 +321,39 @@ keyboard_report(struct hid *h, const uchar *rep, int len) consoleintr(usb_kbd_getc); } +// The lights every USB keyboard should show, in HID's order (Num, Caps, +// Scroll), and what was last sent. Set from interrupt context; sent from +// usb_hid_tick, which runs on a thread and may wait for a control transfer. +static volatile int leds_want = -1; +static int leds_sent = -1; + +void +hid_leds_want(int bits) +{ + leds_want = bits; +} + +static void +hid_send_leds(int bits) +{ + for(int i = 0; i < NHID; i++){ + struct hid *h = &hids[i]; + if(!h->in_use || !h->dev || + !(h->rkbd || h->proto == USB_HID_PROTO_KEYBOARD)) + continue; + // SET_REPORT, output report: one byte of lights, under the keyboard's + // report ID when its reports carry one. + struct usb_setup s; + uchar b = (uchar)bits; + s.bmRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE; + s.bRequest = 0x09; + s.wValue = (2 << 8) | (h->rkbd ? h->kbd_id : 0); + s.wIndex = h->dev->iface.bInterfaceNumber; + s.wLength = 1; + h->dev->ops->control(h->dev, &s, &b, 1); + } +} + void usb_hid_tick(void) { @@ -309,6 +361,11 @@ usb_hid_tick(void) if(!kqinit) return; + int want = leds_want; + if(want >= 0 && want != leds_sent){ + leds_sent = want; + hid_send_leds(want); + } acquire(&kqlock); if(krep.c && (int)(ticks - krep.at) >= 0){ c = krep.c; @@ -525,19 +582,19 @@ bits(const uchar *p, int len, int bit, int size) // A report-protocol keyboard report, rewritten as a boot one: modifiers, a // reserved byte, six usages. Six is a limit only of the rewrite; the seventh // key held at once is dropped, which is what a boot keyboard does too. -static void -report_keyboard(struct hid *h, const uchar *rep, int len) +// Nothing else happens here, which is what lets GMMOS call it after a panic. +static int +to_boot(struct hid *h, const uchar *rep, int len, uchar *boot) { - uchar boot[8]; int i, n = 0; if(h->kbd_id){ if(len < 1 || rep[0] != h->kbd_id) - return; + return 0; rep++; len--; } - memset(boot, 0, sizeof(boot)); + memset(boot, 0, 8); for(i = 0; i < 8; i++) if(h->mod_bit[i] >= 0 && bits(rep, len, h->mod_bit[i], 1)) boot[0] |= 1 << i; @@ -555,7 +612,36 @@ report_keyboard(struct hid *h, const uchar *rep, int len) if(n < 6) boot[2 + n++] = u; } - keyboard_report(h, boot, sizeof(boot)); + return 1; +} + +static void +report_keyboard(struct hid *h, const uchar *rep, int len) +{ + uchar boot[8]; + + if(to_boot(h, rep, len, boot)) + keyboard_report(h, boot, sizeof(boot)); +} + +int +hid_is_keyboard(void *hv) +{ + struct hid *h = hv; + return h && (h->rkbd || h->proto == USB_HID_PROTO_KEYBOARD); +} + +int +hid_boot_report(void *hv, const uchar *rep, int len, uchar *boot) +{ + struct hid *h = hv; + + if(h->rkbd) + return to_boot(h, rep, len, boot); + if(h->proto != USB_HID_PROTO_KEYBOARD || len < 8) + return 0; + memmove(boot, rep, 8); + return 1; } static int @@ -703,7 +789,7 @@ hid_attach(const uchar *desc, int n, const char *what) h->proto = !h->x.ok ? USB_HID_PROTO_KEYBOARD : h->x.rel ? USB_HID_PROTO_MOUSE : USB_HID_PROTO_TABLET; h->in_use = 1; - cprintf("%s: %s%s\n", what, + kinfo("%s: %s%s\n", what, h->proto == USB_HID_PROTO_KEYBOARD ? "keyboard" : h->proto == USB_HID_PROTO_MOUSE ? "pointer" : "absolute pointer", h->rkbd && h->x.ok ? " and keyboard" : ""); @@ -824,7 +910,7 @@ hid_probe(struct usb_dev *dev) } else { // A consumer-control interface (volume keys), a game pad, a UPS: HID, // and not something this driver has a use for. - cprintf("usb-hid: interface %d is neither a keyboard nor a pointer\n", + kinfo("usb-hid: interface %d is neither a keyboard nor a pointer\n", dev->iface.bInterfaceNumber); return -1; } @@ -838,7 +924,9 @@ hid_probe(struct usb_dev *dev) return -1; h->in_use = 1; + h->dev = dev; dev->drvdata = h; + leds_sent = -1; // a new keyboard gets the current lights dev->complete = hid_complete; // Arm the first transfer. Until a TRB is waiting on the endpoint the device @@ -849,7 +937,7 @@ hid_probe(struct usb_dev *dev) return -1; } - cprintf("usb-hid: %s%s on slot %d, endpoint 0x%x every %d ms\n", + kinfo("usb-hid: %s%s on slot %d, endpoint 0x%x every %d ms\n", h->proto == USB_HID_PROTO_KEYBOARD ? "keyboard" : h->proto == USB_HID_PROTO_MOUSE ? "mouse" : "absolute pointer", h->proto == USB_HID_PROTO_KEYBOARD ? @@ -877,7 +965,7 @@ hid_disconnect(struct usb_dev *dev) krep.c = 0; release(&kqlock); } - cprintf("usb-hid: %s on slot %d unplugged\n", + kinfo("usb-hid: %s on slot %d unplugged\n", h->proto == USB_HID_PROTO_KEYBOARD ? "keyboard" : "pointer", dev->slot); h->in_use = 0; diff --git a/kernel/drivers/usb/hub/usb_hub.c b/kernel/drivers/usb/hub/usb_hub.c index e8f104b..8abe83d 100644 --- a/kernel/drivers/usb/hub/usb_hub.c +++ b/kernel/drivers/usb/hub/usb_hub.c @@ -194,7 +194,7 @@ hub_probe(struct usb_dev *dev) } mtt = (hd->wHubCharacteristics & HUB_CHAR_TT_MASK) != 0; - cprintf("usb-hub: %d ports on slot %d\n", nports, dev->slot); + kinfo("usb-hub: %d ports on slot %d\n", nports, dev->slot); // Tell the controller *before* powering anything on. Addressing a device // behind a slot the controller does not know is a hub fails, and it fails @@ -291,7 +291,7 @@ usb_hub_poll(void) h->dev->ops->detach(h->dev, port); if(st & PORTSTAT_CONNECTION){ microdelay(100000); // debounce, as on a root port - cprintf("usb-hub: slot %d port %d: plugged in\n", h->dev->slot, port); + kinfo("usb-hub: slot %d port %d: plugged in\n", h->dev->slot, port); hub_port(h->dev, port, h->scratch); } // The hub may have been unplugged by what was just done; stop if so. diff --git a/kernel/drivers/usb/storage/usb_storage.c b/kernel/drivers/usb/storage/usb_storage.c index df55ddf..006b160 100644 --- a/kernel/drivers/usb/storage/usb_storage.c +++ b/kernel/drivers/usb/storage/usb_storage.c @@ -349,12 +349,12 @@ usb_storage_dump(void) return; } - cprintf("usb-storage: %d sectors of %d bytes\n", u->nsectors, + kinfo("usb-storage: %d sectors of %d bytes\n", u->nsectors, u->sector_size); b = bread(USBDISK_DEV, 1); sb = (struct superblock*)b->data; - cprintf("usb-storage: block 1 magic %x size %d nblocks %d ninodes %d\n", + kinfo("usb-storage: block 1 magic %x size %d nblocks %d ninodes %d\n", sb->magic, sb->size, sb->nblocks, sb->ninodes); brelse(b); } @@ -480,7 +480,7 @@ storage_probe(struct usb_dev *dev) } u->present = 1; - cprintf("usb-storage: %s: %s %s, %d sectors (%d MB)\n", u->name, + kinfo("usb-storage: %s: %s %s, %d sectors (%d MB)\n", u->name, vendor, product, u->nsectors, (int)((uint64)u->nsectors * SECTOR_SIZE / (1024 * 1024))); u->blkdev = blkdev_register(u->name, &ums_blk_ops, u, u->nsectors, @@ -517,7 +517,7 @@ storage_disconnect(struct usb_dev *dev) u->gone = 1; releasesleep(&u->lock); - cprintf("usb-storage: %s unplugged\n", u->name); + kinfo("usb-storage: %s unplugged\n", u->name); if(u->blkdev >= 0) blkdev_unregister(u->blkdev); // USBDISK_DEV may have been this stick; nothing it cached is true now. diff --git a/kernel/fs/devfs.c b/kernel/fs/devfs.c index 43c27c9..ab114e4 100644 --- a/kernel/fs/devfs.c +++ b/kernel/fs/devfs.c @@ -38,6 +38,8 @@ #include "fs/file.h" #include "uapi/errno.h" #include "fs/blkdev.h" +#include "dev/pty.h" +#include "core/uaccess.h" // Inode numbering. Two directories and a flat table of nodes, so the number is // either a directory id or an index into devnodes[] offset past them. @@ -49,6 +51,131 @@ // device number, and inode DEVFS_BLK0 plus that number. Not in devnodes[], // because they come and go: a stick plugged in appears, pulled out vanishes. #define DEVFS_BLK0 4096ULL +// Pseudo-terminals: the /dev/pts directory, its slaves by pair number, and the +// masters, which have inodes so that an open file can hold one but appear in no +// directory -- the only way to one is opening /dev/ptmx. +#define DEVFS_PTS 3ULL +#define DEVFS_PTS0 8192ULL +#define DEVFS_PTM0 12288ULL + +// Groups, by the numbers Alpine's /etc/group gives them. +#define G_ROOT 0 +#define G_TTY 5 +#define G_DISK 6 +#define G_AUDIO 18 +#define G_INPUT 23 +#define G_VIDEO 27 + +// What udev adds to /dev: directories (/dev/input/by-path, /dev/disk/...) and +// symlinks (/dev/fd, /dev/stdin, /dev/char/13:64, /dev/disk/by-id/...). The +// device nodes themselves stay the kernel's; this is a small table of names +// that live only in memory, as they would in a tmpfs, listed after the real +// entries of whichever directory they are in. Nothing here can hold data: a +// regular file in /dev is refused. +#define DEVFS_EXTRA0 32768ULL +#define NEXTRA 192 +#define EXTRA_NAME 48 +#define EXTRA_TARGET 112 + +struct extra { + uchar used; // allocated (an inode exists or is about to) + uchar linked; // has a name in a directory + short type; // T_DIR or T_SYMLINK + uint64 parent; // inum of the directory it is in + ushort tlen; + char name[EXTRA_NAME]; + char target[EXTRA_TARGET]; +}; + +static struct { + struct spinlock lock; + struct extra e[NEXTRA]; +} extras; + +static int extras_ready; + +static void +extras_init(void) +{ + if (!extras_ready) { + initlock(&extras.lock, "devfsx"); + extras_ready = 1; + } +} + +// What chmod and chown have done to the kernel's nodes. A node is built from +// the table above every time its inode is loaded, so a change has to be kept +// somewhere that outlives the inode: here, by inode number, applied on the way +// in. login claims its terminal this way, and udev sets modes on input nodes. +#define NOVR 128 +static struct { + struct spinlock lock; + int ready; + struct { uint64 inum; uint mode, uid, gid; } e[NOVR]; +} ovr; + +static void +ovr_init(void) +{ + if (!ovr.ready) { + initlock(&ovr.lock, "devfsovr"); + ovr.ready = 1; + } +} + +static void +ovr_apply(struct inode *ip) +{ + ovr_init(); + acquire(&ovr.lock); + for (int i = 0; i < NOVR; i++) + if (ovr.e[i].inum == ip->inum) { + ip->mode = ovr.e[i].mode; + ip->uid = ovr.e[i].uid; + ip->gid = ovr.e[i].gid; + break; + } + release(&ovr.lock); +} + +static void +ovr_set(uint64 inum, uint mode, uint uid, uint gid) +{ + int slot = -1; + + ovr_init(); + acquire(&ovr.lock); + for (int i = 0; i < NOVR; i++) { + if (ovr.e[i].inum == inum) { slot = i; break; } + if (slot < 0 && ovr.e[i].inum == 0) + slot = i; + } + if (slot >= 0) { + ovr.e[slot].inum = inum; + ovr.e[slot].mode = mode; + ovr.e[slot].uid = uid; + ovr.e[slot].gid = gid; + } + release(&ovr.lock); +} + +// A new pseudo-terminal's slave belongs to whoever opened ptmx, in group tty, +// readable and writable by the owner and writable by the group -- what grantpt +// does on Linux. +void +devfs_pty_claim(int n, uint uid) +{ + ovr_set(DEVFS_PTS0 + (uint64)n, 0620, uid, G_TTY); +} + +static int +extra_of(uint64 inum) +{ + if (inum < DEVFS_EXTRA0) + return -1; + uint64 i = inum - DEVFS_EXTRA0; + return i < NEXTRA ? (int)i : -1; +} // Which directory a node lives in. Only two exist and a third would want a real // tree rather than a flag, which is the point at which this file should grow @@ -61,8 +188,11 @@ struct devnode { short major; short minor; short dir; + ushort mode; // permission bits a fresh boot starts with + ushort gid; // owner is always root; the group by name }; + // The devices this kernel implements, in the order they appear in a listing. // // Minor numbers under major 1 follow Linux's mem-major layout where it costs @@ -71,57 +201,65 @@ struct devnode { // // The majors are devsw[] indices and nothing else; see kernel/dev/cdev.c. static const struct devnode devnodes[] = { - { "console", 1, 1, D_ROOT }, - { "null", 1, 2, D_ROOT }, - { "keyboard",1, 3, D_ROOT }, - { "zero", 1, 5, D_ROOT }, - { "full", 1, 7, D_ROOT }, - { "random", 1, 8, D_ROOT }, - { "urandom", 1, 9, D_ROOT }, + { "console", 1, 1, D_ROOT , 0600, G_ROOT }, + { "null", 1, 2, D_ROOT , 0666, G_ROOT }, + { "keyboard",1, 3, D_ROOT , 0660, G_INPUT }, + { "zero", 1, 5, D_ROOT , 0666, G_ROOT }, + { "full", 1, 7, D_ROOT , 0666, G_ROOT }, + { "random", 1, 8, D_ROOT , 0666, G_ROOT }, + { "urandom", 1, 9, D_ROOT , 0666, G_ROOT }, /* /dev/tty is the caller's *controlling* terminal, which is a different thing from the console and used to be the same thing only because there was one terminal. Minor 10 resolves through proc->ctty; see vt_for_minor() in kernel/core/console.c. */ - { "tty", 1, 10, D_ROOT }, + { "tty", 1, 10, D_ROOT , 0666, G_ROOT }, /* The terminal on screen, as on Linux. Xorg opens it to find a free VT and switch to it, and without one ran on no VT at all: the console went on drawing into the framebuffer over the X screen. Any minor that is not a numbered terminal or the controlling one resolves to vt_current(). */ - { "tty0", 1, 0, D_ROOT }, - { "tty1", 1, 11, D_ROOT }, - { "tty2", 1, 12, D_ROOT }, - { "tty3", 1, 13, D_ROOT }, - { "tty4", 1, 14, D_ROOT }, - { "tty5", 1, 15, D_ROOT }, - { "tty6", 1, 16, D_ROOT }, - { "stdin", 1, 1, D_ROOT }, - { "stdout", 1, 1, D_ROOT }, - { "stderr", 1, 1, D_ROOT }, - { "fb0", 2, 0, D_ROOT }, - { "pci", 9, 0, D_ROOT }, - { "dbg", 10, 0, D_ROOT }, + { "tty0", 1, 0, D_ROOT , 0660, G_TTY }, + { "tty1", 1, 11, D_ROOT , 0660, G_TTY }, + { "tty2", 1, 12, D_ROOT , 0660, G_TTY }, + { "tty3", 1, 13, D_ROOT , 0660, G_TTY }, + { "tty4", 1, 14, D_ROOT , 0660, G_TTY }, + { "tty5", 1, 15, D_ROOT , 0660, G_TTY }, + { "tty6", 1, 16, D_ROOT , 0660, G_TTY }, + { "tty7", 1, 17, D_ROOT , 0660, G_TTY }, + { "tty8", 1, 18, D_ROOT , 0660, G_TTY }, + { "tty9", 1, 19, D_ROOT , 0660, G_TTY }, + { "tty10", 1, 20, D_ROOT , 0660, G_TTY }, + { "tty11", 1, 21, D_ROOT , 0660, G_TTY }, + { "tty12", 1, 22, D_ROOT , 0660, G_TTY }, + { "stdin", 1, 1, D_ROOT , 0666, G_ROOT }, + { "stdout", 1, 1, D_ROOT , 0666, G_ROOT }, + { "stderr", 1, 1, D_ROOT , 0666, G_ROOT }, + { "fb0", 2, 0, D_ROOT , 0660, G_VIDEO }, + { "pci", 9, 0, D_ROOT , 0600, G_ROOT }, + { "dbg", 10, 0, D_ROOT , 0600, G_ROOT }, // The same device, answering only the views that read. Minor 1 rather than // a flag, because the minor is the one piece of per-open state a character // device already sees -- so which one you opened *is* the permission, and // nothing has to be remembered between the write and the read. See // include/core/dbg.h and cmd/schdbgd. - { "dbgro", 10, 1, D_ROOT }, - { "netdev", 11, 0, D_ROOT }, + { "dbgro", 10, 1, D_ROOT , 0644, G_ROOT }, + { "netdev", 11, 0, D_ROOT , 0600, G_ROOT }, // Audio, spelled the way UNIX spells it. See include/dev/audio.h. - { "dsp", 12, 0, D_ROOT }, - { "mouse", 3, 1, D_ROOT }, + { "dsp", 12, 0, D_ROOT , 0660, G_AUDIO }, + { "mouse", 3, 1, D_ROOT , 0660, G_INPUT }, + // Opening it makes a new pseudo-terminal pair; see kernel/dev/pty.c. + { "ptmx", PTMX_MAJOR, 0, D_ROOT , 0666, G_ROOT }, // Raw disks. Created whether or not a drive is present: opening one with no // disk behind it fails with ENXIO, which is a better answer than a missing // path -- and with no node at all, `echo x > /dev/hdb` silently creates an // ordinary file of that name and appears to work. - { "hda", 8, 0, D_ROOT }, - { "hdb", 8, 1, D_ROOT }, + { "hda", 8, 0, D_ROOT , 0660, G_DISK }, + { "hdb", 8, 1, D_ROOT , 0660, G_DISK }, // Raw input events, Linux evdev layout. The minor is the device index, so // event0 is the keyboard and event1 the mouse. - { "event0", 7, 0, D_INPUT }, - { "event1", 7, 1, D_INPUT }, + { "event0", 7, 0, D_INPUT , 0660, G_INPUT }, + { "event1", 7, 1, D_INPUT , 0660, G_INPUT }, }; #define NDEVNODES ((int)(sizeof(devnodes) / sizeof(devnodes[0]))) @@ -143,38 +281,153 @@ devfs_iload(struct inode *ip) ip->major = ip->minor = 0; ip->size = 0; - if (ip->inum == DEVFS_ROOT || ip->inum == DEVFS_INPUT) { + int x = extra_of(ip->inum); + ip->mode = 0; + if (x >= 0) { + extras_init(); + acquire(&extras.lock); + if (extras.e[x].used) { + ip->type = extras.e[x].type; + ip->nlink = extras.e[x].type == T_DIR ? 2 : 1; + ip->size = extras.e[x].tlen; + ip->mode = extras.e[x].type == T_DIR ? 0755 : 0777; + } else { + ip->type = 0; + ip->nlink = 0; + } + release(&extras.lock); + } else if (ip->inum == DEVFS_ROOT || ip->inum == DEVFS_INPUT || + ip->inum == DEVFS_PTS) { ip->type = T_DIR; ip->nlink = 2; + } else if (ip->inum >= DEVFS_PTS0 && ip->inum < DEVFS_PTS0 + NPTY && + pty_used((int)(ip->inum - DEVFS_PTS0))) { + ip->type = T_DEV; + ip->nlink = 1; + ip->major = PTS_MAJOR; + ip->minor = (short)(ip->inum - DEVFS_PTS0); + ip->mode = 0620; + } else if (ip->inum >= DEVFS_PTM0 && ip->inum < DEVFS_PTM0 + NPTY) { + ip->type = T_DEV; + ip->nlink = 1; + ip->major = PTM_MAJOR; + ip->minor = (short)(ip->inum - DEVFS_PTM0); } else if (i >= 0) { ip->type = T_DEV; ip->nlink = 1; ip->major = devnodes[i].major; ip->minor = devnodes[i].minor; + ip->mode = devnodes[i].mode; + ip->gid = devnodes[i].gid; } else if (ip->inum >= DEVFS_BLK0 && blkdev_present((uint)(ip->inum - DEVFS_BLK0))) { ip->type = T_DEV; ip->nlink = 1; ip->major = 8; ip->minor = (short)(ip->inum - DEVFS_BLK0); + ip->mode = 0660; + ip->gid = G_DISK; } else { ip->type = 0; /* no such node */ ip->nlink = 0; } + if (x < 0 && ip->type != 0) + ovr_apply(ip); ip->valid = 1; } -static void devfs_istore(struct inode *ip) { (void)ip; } -static void devfs_ifree(struct inode *ip) { (void)ip; } +// iupdate() lands here after a chmod or chown; see ovr above. Directories and +// udev's links keep nothing. +static void +devfs_istore(struct inode *ip) +{ + if (extra_of(ip->inum) < 0 && ip->type == T_DEV) + ovr_set(ip->inum, ip->mode, ip->uid, ip->gid); +} +static void +devfs_ifree(struct inode *ip) +{ + int x = extra_of(ip->inum); + if (x < 0) + return; + extras_init(); + acquire(&extras.lock); + extras.e[x].used = 0; + release(&extras.lock); +} static void devfs_truncate(struct inode *ip, uint64 n) { (void)ip; (void)n; } // Never reached for a device node: fileread and filewrite see T_DEV and go // through devsw. Present because fs_ops has the slots, and answering -EIO is // better than a null pointer if something ever does arrive here. -static int devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n) -{ (void)ip; (void)u; (void)d; (void)o; (void)n; return -1; } -static int devfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) -{ (void)ip; (void)u; (void)s; (void)o; (void)n; return -1; } +static int +devfs_read(struct inode *ip, int u, uint64 d, uint64 o, uint n) +{ + int x = extra_of(ip->inum); + // mkdir lays down the "." and ".." of a v1-style directory by reading and + // rewriting its first two entries (build_dir_tree). A directory here has + // none stored, so the read gives zeros and the write is swallowed. + if (x >= 0 && ip->type == T_DIR) { + char z[64]; + memset(z, 0, sizeof z); + for (uint done = 0; done < n; ) { + uint c = n - done < sizeof z ? n - done : (uint)sizeof z; + if (u) { + if (copy_to_user(d + done, z, c) < 0) + return -EFAULT; + } else { + memmove((char *)d + done, z, c); + } + done += c; + } + (void)o; + return (int)n; + } + if (x < 0 || ip->type != T_SYMLINK) + return -1; + extras_init(); + char buf[EXTRA_TARGET]; + acquire(&extras.lock); + uint len = extras.e[x].tlen; + memmove(buf, extras.e[x].target, len); + release(&extras.lock); + if (o >= len) + return 0; + if (o + n > len) + n = (uint)(len - o); + if (u) { + if (copy_to_user(d, buf + o, n) < 0) + return -EFAULT; + } else { + memmove((char *)d, buf + o, n); + } + return (int)n; +} + +// Only a symlink's target can be written, once, from offset 0. +static int +devfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) +{ + int x = extra_of(ip->inum); + if (x >= 0 && ip->type == T_DIR) + return (int)n; // see devfs_read + if (x < 0 || ip->type != T_SYMLINK || o != 0 || n >= EXTRA_TARGET) + return -1; + extras_init(); + char buf[EXTRA_TARGET]; + if (u) { + if (copy_from_user(buf, s, n) < 0) + return -EFAULT; + } else { + memmove(buf, (char *)s, n); + } + acquire(&extras.lock); + memmove(extras.e[x].target, buf, n); + extras.e[x].tlen = (ushort)n; + release(&extras.lock); + ip->size = n; + return (int)n; +} static int streq(const char *a, const char *b) @@ -183,12 +436,41 @@ streq(const char *a, const char *b) return *a == *b; } +static struct inode * +extra_find(uint64 parent, const char *name) +{ + extras_init(); + acquire(&extras.lock); + for (int i = 0; i < NEXTRA; i++) + if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == parent && + streq(name, extras.e[i].name)) { + release(&extras.lock); + return iget(DEVFS_DEV, DEVFS_EXTRA0 + (uint64)i); + } + release(&extras.lock); + return 0; +} + static struct inode * devfs_dirlookup(struct inode *dp, char *name, uint *poff) { if (poff) *poff = 0; + int dx = extra_of(dp->inum); + if (dx >= 0) { + extras_init(); + if (streq(name, ".")) + return iget(DEVFS_DEV, dp->inum); + if (streq(name, "..")) { + acquire(&extras.lock); + uint64 up = extras.e[dx].parent; + release(&extras.lock); + return iget(DEVFS_DEV, up ? up : DEVFS_ROOT); + } + return extra_find(dp->inum, name); + } + if (streq(name, ".") || (streq(name, "..") && dp->inum == DEVFS_ROOT)) return iget(DEVFS_DEV, dp->inum); if (streq(name, "..")) @@ -196,6 +478,32 @@ devfs_dirlookup(struct inode *dp, char *name, uint *poff) if (dp->inum == DEVFS_ROOT && streq(name, "input")) return iget(DEVFS_DEV, DEVFS_INPUT); + if (dp->inum == DEVFS_ROOT && streq(name, "pts")) + return iget(DEVFS_DEV, DEVFS_PTS); + + // /dev/pts/N: a pair that exists. + if (dp->inum == DEVFS_PTS) { + int n = 0; + const char *s = name; + if (!*s) + return 0; + for (; *s; s++) { + if (*s < '0' || *s > '9') + return 0; + n = n * 10 + (*s - '0'); + if (n >= NPTY) + return 0; + } + return pty_used(n) ? iget(DEVFS_DEV, DEVFS_PTS0 + (uint64)n) : 0; + } + + // /dev/tty is the caller's controlling terminal. When that is a pty, it is + // the slave itself -- what a program inside xterm or tmux opens to talk to + // its terminal must be that terminal, not the console. + if (dp->inum == DEVFS_ROOT && streq(name, "tty") && myproc() && + myproc()->ctty >= PTY_CTTY && myproc()->ctty < PTY_CTTY + NPTY && + pty_used(myproc()->ctty - PTY_CTTY)) + return iget(DEVFS_DEV, DEVFS_PTS0 + (uint64)(myproc()->ctty - PTY_CTTY)); short want = dp->inum == DEVFS_INPUT ? D_INPUT : D_ROOT; for (int i = 0; i < NDEVNODES; i++) @@ -207,19 +515,85 @@ devfs_dirlookup(struct inode *dp, char *name, uint *poff) if (streq(name, blkdev_name((uint)b))) return iget(DEVFS_DEV, DEVFS_BLK0 + (uint64)b); + return extra_find(dp->inum, name); +} + +// What can be created here: a directory or a symlink, by ialloc and then +// dirlink -- the order every filesystem is driven in. The device nodes +// themselves are the kernel's: mknod answers EROFS by never getting an inode. +static struct inode * +devfs_ialloc(uint dev, short type) +{ + (void)dev; + if (type != T_DIR && type != T_SYMLINK) + return 0; + extras_init(); + acquire(&extras.lock); + for (int i = 0; i < NEXTRA; i++) + if (!extras.e[i].used) { + memset(&extras.e[i], 0, sizeof(extras.e[i])); + extras.e[i].used = 1; + extras.e[i].type = type; + release(&extras.lock); + return iget(DEVFS_DEV, DEVFS_EXTRA0 + (uint64)i); + } + release(&extras.lock); return 0; } -// A read-only filesystem, and these say so rather than pretending. mknod on -// /dev returning EROFS is the correct answer: the node either exists because -// the kernel has the device, or it does not exist because it has not. -static int devfs_dirlink(struct inode *dp, char *name, uint64 inum) -{ (void)dp; (void)name; (void)inum; return -EROFS; } -static int devfs_dirunlink(struct inode *dp, char *name, uint off) -{ (void)dp; (void)name; (void)off; return -EROFS; } -static int devfs_dirempty(struct inode *dp) { (void)dp; return 0; } -static struct inode *devfs_ialloc(uint dev, short type) -{ (void)dev; (void)type; return 0; } +static int +devfs_dirlink(struct inode *dp, char *name, uint64 inum) +{ + int x = extra_of(inum); + if (streq(name, ".") || streq(name, "..")) + return 0; /* a new directory's own dots */ + if (x < 0 || strlen(name) >= EXTRA_NAME) + return -EINVAL; + extras_init(); + acquire(&extras.lock); + if (!extras.e[x].used || extras.e[x].linked) { + release(&extras.lock); + return -EINVAL; + } + extras.e[x].parent = dp->inum; + extras.e[x].linked = 1; + memmove(extras.e[x].name, name, strlen(name) + 1); + release(&extras.lock); + return 0; +} + +static int +devfs_dirempty(struct inode *dp) +{ + if (extra_of(dp->inum) < 0) + return 0; + extras_init(); + acquire(&extras.lock); + for (int i = 0; i < NEXTRA; i++) + if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == dp->inum) { + release(&extras.lock); + return 0; + } + release(&extras.lock); + return 1; +} + +static int +devfs_dirunlink(struct inode *dp, char *name, uint off) +{ + (void)off; + extras_init(); + acquire(&extras.lock); + for (int i = 0; i < NEXTRA; i++) + if (extras.e[i].used && extras.e[i].linked && extras.e[i].parent == dp->inum && + streq(name, extras.e[i].name)) { + extras.e[i].linked = 0; /* the inode goes when nobody holds it */ + release(&extras.lock); + return 0; + } + release(&extras.lock); + return -EROFS; /* a device node is the kernel's */ +} struct linux_dirent64 { uint64 d_ino; @@ -258,7 +632,9 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff) int used = 0; uint64 n = *poff; - if (dp->inum != DEVFS_ROOT && dp->inum != DEVFS_INPUT) + int isx = extra_of(dp->inum) >= 0; + if (dp->inum != DEVFS_ROOT && dp->inum != DEVFS_INPUT && + dp->inum != DEVFS_PTS && !isx) return -1; if (n == 0) { @@ -271,15 +647,55 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff) n++; } - // /dev/input is a directory entry of the root, and it is emitted between the - // dots and the nodes so that its ordinal is stable. + // A directory udev made: the dots, then what is in it. + if (isx) { + uint64 base = 2; + extras_init(); + acquire(&extras.lock); + for (int i = 0; i < NEXTRA; i++) { + if (!extras.e[i].used || !extras.e[i].linked || extras.e[i].parent != dp->inum) + continue; + if (n > base) { base++; continue; } + if (emit(out, cap, &used, DEVFS_EXTRA0 + (uint64)i, + extras.e[i].type == T_DIR ? 4 : 10 /* DT_LNK */, extras.e[i].name) < 0) + break; + n++; + base++; + } + release(&extras.lock); + goto out; + } + + // /dev/pts: the dots, then the pairs that exist. Ordinal 2 + N is pair N, + // whether or not the ones before it exist, so a pair opened between two + // calls does not shift the rest. + if (dp->inum == DEVFS_PTS) { + for (; n >= 2 && n < 2 + NPTY; n++) { + int k = (int)(n - 2); + char nm[4]; + if (!pty_used(k)) + continue; + nm[0] = k >= 10 ? '0' + k / 10 : '0' + k; + nm[1] = k >= 10 ? '0' + k % 10 : 0; + nm[2] = 0; + if (emit(out, cap, &used, DEVFS_PTS0 + (uint64)k, 2, nm) < 0) goto out; + } + goto out; + } + + // /dev/input and /dev/pts are directory entries of the root, emitted + // between the dots and the nodes so that their ordinals are stable. if (dp->inum == DEVFS_ROOT && n == 2) { if (emit(out, cap, &used, DEVFS_INPUT, 4, "input") < 0) goto out; n++; } + if (dp->inum == DEVFS_ROOT && n == 3) { + if (emit(out, cap, &used, DEVFS_PTS, 4, "pts") < 0) goto out; + n++; + } short want = dp->inum == DEVFS_INPUT ? D_INPUT : D_ROOT; - uint64 base = dp->inum == DEVFS_ROOT ? 3 : 2; + uint64 base = dp->inum == DEVFS_ROOT ? 4 : 2; for (int i = 0; i < NDEVNODES; i++) { if (devnodes[i].dir != want) @@ -302,6 +718,21 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff) base++; } + // What udev added to this directory, after everything the kernel put there. + extras_init(); + acquire(&extras.lock); + for (int i = 0; i < NEXTRA; i++) { + if (!extras.e[i].used || !extras.e[i].linked || extras.e[i].parent != dp->inum) + continue; + if (n > base) { base++; continue; } + if (emit(out, cap, &used, DEVFS_EXTRA0 + (uint64)i, + extras.e[i].type == T_DIR ? 4 : 10, extras.e[i].name) < 0) + break; + n++; + base++; + } + release(&extras.lock); + out: *poff = n; return used; @@ -339,3 +770,12 @@ devfs_mount(const char *target) iput(dp); return r < 0 ? -EBUSY : 0; } + +// The inode of pty master N, for ptmx_open() to hand to the file it opens. +struct inode * +devfs_pty_master(int n) +{ + if (n < 0 || n >= NPTY) + return 0; + return iget(DEVFS_DEV, DEVFS_PTM0 + (uint64)n); +} diff --git a/kernel/fs/exec.c b/kernel/fs/exec.c index 98edb15..0c9de3d 100644 --- a/kernel/fs/exec.c +++ b/kernel/fs/exec.c @@ -5,6 +5,7 @@ #include "arch/mmu.h" #include "core/proc.h" #include "core/defs.h" +#include "uapi/stat.h" #include "arch/x86.h" #include "core/elf.h" #include "uapi/fcntl.h" @@ -369,7 +370,8 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) uint64 sz, sp; struct elfhdr elf; struct inode *ip; - pml4e_t *pgdir, *oldpgdir; + pml4e_t *pgdir; + struct mm *oldmm, *newmm; // The arguments are measured before anything is loaded, so that too many // of them is E2BIG with nothing to undo -- and so the stack can be sized to @@ -402,6 +404,10 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) begin_op(); int in_op = 1; // the error path closes the transaction only once int ret = -1; + // What the file's mode asks for, read while it is still locked: a program + // with the set-user-id bit runs as its owner, set-group-id as its group. + int want_suid = 0, want_sgid = 0; + uint file_uid = 0, file_gid = 0; int err; if((ip = nameifollow(0, path, &err)) == 0){ @@ -414,6 +420,20 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) ilock(ip); pgdir = 0; + // To run a file is to have execute permission on it, and it has to be a + // plain file: a directory has the bit and is not a program. + if(ip->type == T_DIR || ip->type == T_DEV || inode_permission(ip, 1, 0) < 0){ + ret = -EACCES; + goto bad; + } + { + uint m = inode_mode_bits(ip); + want_suid = (m & 04000) != 0; + want_sgid = (m & 02000) != 0 && (m & 010) != 0; + file_uid = ip->uid; + file_gid = ip->gid; + } + if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf)){ ret = -ENOEXEC; // too short to be anything we can run goto bad; @@ -535,13 +555,15 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) if(*s == '/') last = s+1; safestrcpy(myproc()->name, last, sizeof(myproc()->name)); + if (strace_wanted(myproc()->name)) + myproc()->strace = 1; safestrcpy(myproc()->exe, path, sizeof(myproc()->exe)); for(int i = 0; i < NOFILE; i++){ - if(myproc()->ofile[i] && (myproc()->ofile_flags[i] & FD_CLOEXEC)){ - fileclose(myproc()->ofile[i]); - myproc()->ofile[i] = 0; - myproc()->ofile_flags[i] = 0; + if(myproc()->files->ofile[i] && (myproc()->files->flags[i] & FD_CLOEXEC)){ + fileclose(myproc()->files->ofile[i]); + myproc()->files->ofile[i] = 0; + myproc()->files->flags[i] = 0; } } @@ -555,18 +577,28 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) myproc()->map_size = 0; } - // Detach shared segments before anything forgets which mappings they were. - // - // This has to happen while myproc()->pgdir and myproc()->vma[] still describe the - // *old* image: the memset below erases the bookkeeping, and freevm() at the - // end would then free a shared memory segment's pages as though they were - // ordinary heap. Doing it here rather than there is the difference between - // detaching a segment and corrupting whoever else has it mapped. - proc_release_shared(myproc()); - - oldpgdir = myproc()->pgdir; + // A new image is a new address space: a fresh mm, and the old one dropped + // at the end. mm_put() detaches shared segments before freeing anything, + // and does nothing at all while a vfork parent is still using it. + if ((newmm = mm_alloc()) == 0) + goto bad; + proc_exec_threads(); + newmm->pgdir = pgdir; + newmm->sz = sz; + oldmm = myproc()->mm; + myproc()->mm = newmm; myproc()->pgdir = pgdir; myproc()->sz = sz; + pgdir = 0; // newmm owns it now; not bad:'s to free + + // The new image's identity. The saved ids become the effective ones, which + // is what lets a program drop its privilege and take it back (su, sudo). + if (want_suid) + myproc()->euid = file_uid; + if (want_sgid) + myproc()->egid = file_gid; + myproc()->suid = myproc()->euid; + myproc()->sgid = myproc()->egid; // New address space: reset all per-process mmap/signal/TLS state. // sig_handler/sig_restorer point into the *old* image — POSIX resets @@ -577,26 +609,25 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) // Ignored signals stay ignored, and the mask is inherited (execve(2)): // neither points into the old image. `nohup` and every program that ignores // SIGPIPE before running a helper depend on it. - memset(myproc()->vma, 0, sizeof(myproc()->vma)); for (int i = 0; i < 32; i++) - if (myproc()->sig_handler[i] != 1) - myproc()->sig_handler[i] = 0; - memset(myproc()->sig_restorer, 0, sizeof(myproc()->sig_restorer)); + if (myproc()->sighand->handler[i] != 1) + myproc()->sighand->handler[i] = 0; + memset(myproc()->sighand->restorer, 0, sizeof(myproc()->sighand->restorer)); myproc()->fs_base = 0; myproc()->pending_signals = 0; myproc()->in_signal = 0; myproc()->sigsusp_active = 0; - myproc()->mmap_next = 0; + myproc()->mm->mmap_next = 0; // The interpreter is ordinary memory to everything after this: fork copies // it, exit frees it. mmap carries on above it. if (interp[0]) { - myproc()->vma[0].addr = INTERP_BASE; - myproc()->vma[0].length = PGROUNDUP(interp_end); - myproc()->vma[0].used = 1; - myproc()->vma[0].no_free = 0; - myproc()->vma[0].shmid = -1; - myproc()->mmap_next = INTERP_BASE + PGROUNDUP(interp_end); + myproc()->mm->vma[0].addr = INTERP_BASE; + myproc()->mm->vma[0].length = PGROUNDUP(interp_end); + myproc()->mm->vma[0].used = 1; + myproc()->mm->vma[0].no_free = 0; + myproc()->mm->vma[0].shmid = -1; + myproc()->mm->mmap_next = INTERP_BASE + PGROUNDUP(interp_end); } myproc()->tf->rax = 0; @@ -635,7 +666,8 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev) // old one's registers. memmove(myproc()->fpu, fpu_default, sizeof(myproc()->fpu)); fxrstor(myproc()->fpu); - freevm(oldpgdir); + mm_put(oldmm); + proc_exec_done(); // a vfork parent may have its memory back kfree((char*)scratch); kfree(bounce); fsstat.exec_cycles += rdtsc() - _t0; diff --git a/kernel/fs/file.c b/kernel/fs/file.c index 382da87..a8d9210 100644 --- a/kernel/fs/file.c +++ b/kernel/fs/file.c @@ -2,6 +2,7 @@ #include "core/defs.h" #include "uapi/stat.h" #include "core/param.h" +#include "core/proc.h" #include "fs/fs.h" #include "core/spinlock.h" #include "core/sleeplock.h" @@ -9,7 +10,9 @@ #include "fs/fsstat.h" #include "uapi/posix.h" #include "uapi/errno.h" +#include "uapi/inotify.h" #include "core/kmem.h" +#include "dev/pty.h" extern struct devsw devsw[]; @@ -111,6 +114,9 @@ fileclose(struct file *f) FSSTAT_DROP(fsstat.file_used); // 1 -> 0 release(&ftable.lock); + // Before the path goes: the directory's watchers want the name. + if(ff.type == FD_INODE && ff.writable && ff.ip && ff.ip->type == T_FILE) + inotify_file(&ff, IN_CLOSE_WRITE); if(ff.path) kmfree(ff.path); @@ -273,7 +279,9 @@ fileread(struct file *f, char *addr, int n) } if((f->ip->rwmode & 2) == 0){ - cprintf("SchOS: permission denied (read)\n"); + cprintf("SchOS: %s (pid %d): permission denied (read) on inode %d:%ld, rwmode %d\n", + myproc()->name, myproc()->pid, (int)f->ip->dev, (long)f->ip->inum, + (int)f->ip->rwmode); iunlock(f->ip); return -1; } @@ -339,7 +347,9 @@ filewrite(struct file *f, char *addr, int n) } if((f->ip->rwmode & 1) == 0){ - cprintf("SchOS: permission denied (write)\n"); + cprintf("SchOS: %s (pid %d): permission denied (write) on inode %d:%ld, rwmode %d\n", + myproc()->name, myproc()->pid, (int)f->ip->dev, (long)f->ip->inum, + (int)f->ip->rwmode); iunlock(f->ip); return -1; } @@ -374,8 +384,10 @@ filewrite(struct file *f, char *addr, int n) if(r != n1) break; } - if(i > 0) + if(i > 0){ + inotify_file(f, IN_MODIFY); return i; + } return r < 0 ? r : -1; } panic("filewrite"); @@ -422,6 +434,15 @@ file_poll(struct file *f, int events) return rev; } + if(f->ip && f->ip->type == T_DEV && + (f->ip->major == PTM_MAJOR || f->ip->major == PTS_MAJOR)) + return pty_poll(f->ip, events, f->readable, f->writable); + + // /dev/dsp is writable when the driver has room. ALSA's OSS plugin waits + // in poll() for exactly this between periods. + if(f->ip && f->ip->type == T_DEV && f->ip->major == AUDIO_MAJOR) + return (events & POLLOUT) && f->writable && dsp_writable() ? POLLOUT : 0; + if(f->ip && f->ip->type == T_DEV && f->ip->major == CONSOLE){ int rev = 0; if((events & POLLIN) && f->readable && console_input_ready(f->ip ? f->ip->minor : 1)) diff --git a/kernel/fs/fs.c b/kernel/fs/fs.c index 38a0fe9..f7ea4c0 100644 --- a/kernel/fs/fs.c +++ b/kernel/fs/fs.c @@ -361,6 +361,82 @@ fs_readonly(uint dev) return SCHFS_IS_SNAP_DEV(dev) || (FAT_IS_DEV(dev) && fat_readonly(dev)); } +// =========================================================================== +// Permissions +// =========================================================================== + +// Is `gid` the caller's group, effective or supplementary? `real` asks about +// the real group instead of the effective one -- access(2) does. +int +in_group(uint gid, int real) +{ + struct proc *p = myproc(); + + if (p == 0) + return 1; + if (gid == (real ? p->gid : p->egid)) + return 1; + for (int i = 0; i < p->ngroups; i++) + if (p->groups[i] == gid) + return 1; + return 0; +} + +// The permission bits of an inode, 07777, as stat reports them. A filesystem +// that keeps real ones (SchFS) supplies them; the rest are made up from the +// type, which is what stat has always done. A mode that was recorded as "no +// bits" is 01000000, which masks to zero here. +uint +inode_mode_bits(struct inode *ip) +{ + if (ip->mode) + return ip->mode & 07777; + switch (ip->type) { + case T_DIR: return FAT_IS_DEV(ip->dev) ? 0777 : 0755; + case T_DEV: return 0666; + case T_SYMLINK: return 0777; + case T_SOCK: return 0755; + } + // A volume that cannot say who owns anything is everyone's: FAT on a USB + // stick was usable by whoever was logged in, and still is. + if (FAT_IS_DEV(ip->dev)) + return 0777; + return (ip->rwmode & 2) ? 0755 : 0555; +} + +// May the caller do `mask` (4 read, 2 write, 1 execute or search) to this +// inode? 0, or -EACCES. The caller holds the inode lock or knows it will not +// change under it. +// +// Root may read and write anything, and may execute a file only if somebody +// can -- the same rule Linux applies, so that a data file does not become a +// program because root asked. +int +inode_permission(struct inode *ip, int mask, int real) +{ + struct proc *p = myproc(); + uint m = inode_mode_bits(ip); + uint who = p ? (real ? p->uid : p->euid) : 0; + + if (p == 0 || who == 0) { + if ((mask & 1) && ip->type != T_DIR && !(m & 0111)) + return -EACCES; + return 0; + } + if (who == ip->uid) + m >>= 6; + else if (in_group(ip->gid, real)) + m >>= 3; + return ((m & 7) & (uint)mask) == (uint)mask ? 0 : -EACCES; +} + +// Superuser-only operations: mount, reboot, settimeofday and the rest. +int +capable(void) +{ + return myproc() == 0 || myproc()->euid == 0; +} + static const struct fs_ops * ops_for(uint dev) { @@ -624,8 +700,11 @@ ilock(struct inode *ip) acquiresleep(&ip->lock); - if (ip->valid == 0) + if (ip->valid == 0) { + ip->mode = 0; + ip->uid = ip->gid = 0; ip->ops->iload(ip); + } } void @@ -1968,6 +2047,15 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name, *err = ENOTDIR; goto fail; } + // Searching a directory takes its execute bit. The directory that holds + // the final name is searched too, even when the caller only wants its + // inode: nothing under a directory you cannot enter is yours to name. + if (inode_permission(ip, 1, 0) < 0) { + iunlockput(ip); + if (err) + *err = EACCES; + goto fail; + } if (nameiparent && *spath == '\0') { iunlock(ip); goto out; diff --git a/kernel/fs/pipe.c b/kernel/fs/pipe.c index 62c5189..5b37b02 100644 --- a/kernel/fs/pipe.c +++ b/kernel/fs/pipe.c @@ -183,6 +183,14 @@ piperead(struct pipe *p, char *addr, int n) release(&p->lock); return -1; } + if(sig_attention(myproc())){ // stopped, or a handler wants to run + release(&p->lock); + int e = sig_blocking_check(); + if(e) + return e; + acquire(&p->lock); + continue; + } sleep(&p->nread, &p->lock); //DOC: piperead-sleep } i = 0; @@ -223,3 +231,13 @@ pipe_poll(struct pipe *p, int readable, int writable) release(&p->lock); return rev; } + +// Bytes waiting to be read, for FIONREAD. +int +pipe_avail(struct pipe *p) +{ + acquire(&p->lock); + int n = (int)(p->nwrite - p->nread); + release(&p->lock); + return n; +} diff --git a/kernel/fs/procfs.c b/kernel/fs/procfs.c index 069d9a4..bc718bf 100644 --- a/kernel/fs/procfs.c +++ b/kernel/fs/procfs.c @@ -34,6 +34,7 @@ #include "uapi/stat.h" #include "core/uaccess.h" #include "uapi/errno.h" +#include "dev/pty.h" // --------------------------------------------------------------------------- // Inode numbering @@ -68,7 +69,10 @@ #define PF_EXE 5 #define PF_SCHEDSTAT 6 #define PF_FD 7 // the directory /proc//fd -#define PF_MAX 7 +#define PF_TASK 8 // the directory /proc//task +#define PF_COMM 9 +#define PF_MAPS 10 +#define PF_MAX 10 #define PF_FDENT 0x80 // | fd: one link in it #define INO_PID(ino) ((int)((ino) >> 8)) @@ -109,6 +113,9 @@ static const struct global_file globals[] = { // Disks and partitions, in Linux's format. See blkdev_info(). { "partitions", 21 }, { "kmsg", 17 }, + // A program's name: from its next exec on, it and everything it starts log + // every syscall. Empty to stop. See strace_call() in kernel/sys/syscall.c. + { "strace", 31 }, }; #define NGLOBALS ((int)(sizeof(globals) / sizeof(globals[0]))) @@ -132,6 +139,16 @@ static const struct global_file pidfiles[] = { // One symlink per open descriptor, named by its number and pointing at the // path it was opened by, or at "socket:[ino]" and the like. { "fd", PF_FD }, + // The process's threads, one directory each, named by thread id; each is + // /proc/ -- the same inode. htop reads every process through it and + // shows nothing at all without it. + { "task", PF_TASK }, + // The name alone, a line of it. btop reads this and nothing else for the + // Program column. + { "comm", PF_COMM }, + // Which file each span of the address space came from. See + // kernel/proc/mapname.c; only the names and ranges are to be trusted. + { "maps", PF_MAPS }, }; #define NPIDFILES ((int)(sizeof(pidfiles) / sizeof(pidfiles[0]))) @@ -188,7 +205,7 @@ gen_pid_stat(struct sb *s, const struct procinfo *pi) sbputs(s, " ("); sbputs(s, pi->name); sbputs(s, ") "); - sbputc(s, state_char(pi->state)); + sbputc(s, pi->stopped ? 'T' : state_char(pi->state)); sbputc(s, ' '); sbputi(s, pi->ppid); // 4 ppid sbputc(s, ' '); @@ -201,8 +218,16 @@ gen_pid_stat(struct sb *s, const struct procinfo *pi) // header and nothing else: ps with no arguments shows only the processes on // *this* terminal, and nothing could ever match. `ps -e` always worked. sbputc(s, ' '); - sbputi(s, pi->ctty ? ((1 << 8) | (10 + pi->ctty)) : 0); - sbputs(s, " -1 0"); // 8 tpgid, 9 flags + // A pty is Linux's (136, N), the same number stat() gives /dev/pts/N. + if (pi->ctty >= PTY_CTTY) + sbputi(s, (136 << 8) | (pi->ctty - PTY_CTTY)); + else + sbputi(s, pi->ctty ? ((1 << 8) | (10 + pi->ctty)) : 0); + // 8 tpgid, 9 flags. PF_KTHREAD (0x00200000) on a kernel thread is how htop + // and ps tell one from a process and hide it; without it acpi and dbgd + // listed as programs of four bytes each. + sbputs(s, " -1 "); + sbputi(s, pi->kthread ? 0x00200000 : 0); // 10..13: minflt cminflt majflt cmajflt. There is no fault accounting, and // a fabricated number is worse than none -- top would draw a plausible graph @@ -218,7 +243,9 @@ gen_pid_stat(struct sb *s, const struct procinfo *pi) sbputc(s, ' '); sbputi(s, (int64)pi->cutime); sbputc(s, ' '); sbputi(s, (int64)pi->cstime); - sbputs(s, " 20 0 1 0 0 "); // priority nice num_threads itrealvalue starttime + sbputs(s, " 20 0 "); // priority nice + sbputi(s, pi->nthreads); // 20 num_threads + sbputs(s, " 0 0 "); // itrealvalue starttime sbputi(s, (int64)pi->sz); // 23 vsize, in bytes sbputc(s, ' '); sbputi(s, (int64)(pi->sz / PGSIZE)); // 24 rss, in pages @@ -229,8 +256,8 @@ static void gen_pid_status(struct sb *s, const struct procinfo *pi) { sbputs(s, "Name:\t"); sbputs(s, pi->name); sbputc(s, '\n'); - sbputs(s, "State:\t"); sbputc(s, state_char(pi->state)); - sbputs(s, "\nTgid:\t"); sbputi(s, pi->pid); + sbputs(s, "State:\t"); sbputc(s, pi->stopped ? 'T' : state_char(pi->state)); + sbputs(s, "\nTgid:\t"); sbputi(s, pi->tgid); sbputs(s, "\nPid:\t"); sbputi(s, pi->pid); sbputs(s, "\nPPid:\t"); sbputi(s, pi->ppid); sbputs(s, "\nUid:\t"); sbputi(s, pi->uid); @@ -238,7 +265,7 @@ gen_pid_status(struct sb *s, const struct procinfo *pi) sbputc(s, '\t'); sbputi(s, pi->uid); sbputc(s, '\t'); sbputi(s, pi->uid); sbputs(s, "\nVmSize:\t"); sbputi(s, (int64)(pi->sz / 1024)); - sbputs(s, " kB\nThreads:\t1\n"); + sbputs(s, " kB\nThreads:\t"); sbputi(s, pi->nthreads); sbputc(s, '\n'); // Where Linux keeps them, under the names Linux uses. The two are worth // telling apart: a process with a high voluntary count is waiting on @@ -297,6 +324,8 @@ gen_file(uint64 ino, char *buf, int cap) case PF_CMDLINE: gen_pid_cmdline(&s, &pi); break; case PF_STATM: gen_pid_statm(&s, &pi); break; case PF_EXE: sbputs(&s, pi.exe[0] ? pi.exe : pi.name); break; + case PF_COMM: sbputs(&s, pi.name); sbputc(&s, '\n'); break; + case PF_MAPS: return proc_maps(INO_PID(ino), buf, cap); case PF_SCHEDSTAT: sbputi(&s, (int64)pi.vruntime); sbputc(&s, ' '); sbputi(&s, (int64)pi.nice); sbputc(&s, ' '); @@ -320,18 +349,20 @@ procfs_iload(struct inode *ip) ip->valid = 1; ip->nlink = 1; ip->major = ip->minor = 0; - // Read-only, with two exceptions: /proc/sysrq is the deliberate write target - // (`echo c > /proc/sysrq` to crash on purpose), and /proc/schinit is where - // PID 1 publishes its job table. Marking everything 2 made filewrite() + // Read-only, with three exceptions: /proc/sysrq is the deliberate write + // target (`echo c > /proc/sysrq` to crash on purpose), /proc/schinit is where + // PID 1 publishes its job table, and /proc/strace takes a name to trace. Marking everything 2 made filewrite() // refuse with "permission denied", which the USB suite caught immediately -- // three of its checks drive the disk through sysrq. ip->rwmode = (INO_IS_GLOBAL(ino) && - ((ino & 0xFF) == 10 || (ino & 0xFF) == 16)) ? 3 : 2; + ((ino & 0xFF) == 10 || (ino & 0xFF) == 16 || + (ino & 0xFF) == 31)) ? 3 : 2; ip->showmode = 1; ip->atime = ip->mtime = ip->ctime = 0; if (ino == PROCFS_ROOT || - (!INO_IS_GLOBAL(ino) && (INO_FILE(ino) == PF_DIR || INO_FILE(ino) == PF_FD))) { + (!INO_IS_GLOBAL(ino) && (INO_FILE(ino) == PF_DIR || INO_FILE(ino) == PF_FD || + INO_FILE(ino) == PF_TASK))) { ip->type = T_DIR; ip->size = 0; ip->nlink = 2; @@ -460,6 +491,8 @@ procfs_write(struct inode *ip, int user_src, uint64 src, uint64 off, uint n) // should be doing, and PID 1 already knows when the table changed. if (INO_IS_GLOBAL(ip->inum) && (ip->inum & 0xFF) == 16) return proc_write_schinit(user_src, src, n); + if (INO_IS_GLOBAL(ip->inum) && (ip->inum & 0xFF) == 31) + return proc_write_strace(user_src, src, n); (void)off; return -EROFS; } @@ -495,8 +528,18 @@ procfs_dirlookup(struct inode *dp, char *name, uint *poff) if (namecmp(name, ".") == 0) return iget(dp->dev, dp->inum); if (namecmp(name, "..") == 0) // /proc//.. is /proc - return iget(dp->dev, INO_FILE(dp->inum) == PF_FD ? (dp->inum & ~0xFFULL) - : PROCFS_ROOT); + return iget(dp->dev, INO_FILE(dp->inum) == PF_FD || + INO_FILE(dp->inum) == PF_TASK ? (dp->inum & ~0xFFULL) + : PROCFS_ROOT); + + // Inside /proc//task/: a thread of this process, which is /proc/. + if (dp->inum != PROCFS_ROOT && INO_FILE(dp->inum) == PF_TASK) { + int tid = name_to_pid(name); + if (tid <= 0 || proc_snapshot_pid(tid, &pi) < 0 || + pi.tgid != (int)INO_PID(dp->inum)) + return 0; + return iget(dp->dev, ((uint64)tid << 8) | PF_DIR); + } // Inside /proc//fd/: a number that is an open descriptor. if (dp->inum != PROCFS_ROOT && INO_FILE(dp->inum) == PF_FD) { @@ -617,7 +660,11 @@ procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off) EMIT(PROCFS_SELF, DT_LNK_, "self"); for (i = 0; i < NGLOBALS; i++) EMIT(PROCFS_GLOBAL | globals[i].id, DT_REG_, globals[i].name); + // Processes only. A thread is reachable as /proc/, as on Linux, + // but listed under its process's task/ rather than here. for (i = 0; proc_snapshot(i, &pi) == 0; i++) { + if (pi.tgid != pi.pid) + continue; utoa((uint)pi.pid, name); EMIT(((uint64)pi.pid << 8) | PF_DIR, DT_DIR_, name); } @@ -631,12 +678,22 @@ procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off) utoa((uint)i, name); EMIT((dp->inum & ~0xFFULL) | PF_FDENT | i, DT_LNK_, name); } + } else if (INO_FILE(dp->inum) == PF_TASK) { + EMIT(dp->inum, DT_DIR_, "."); + EMIT(dp->inum & ~0xFFULL, DT_DIR_, ".."); + for (i = 0; proc_snapshot(i, &pi) == 0; i++) { + if (pi.tgid != (int)INO_PID(dp->inum)) + continue; + utoa((uint)pi.pid, name); + EMIT(((uint64)pi.pid << 8) | PF_DIR, DT_DIR_, name); + } } else { EMIT(dp->inum, DT_DIR_, "."); EMIT(PROCFS_ROOT, DT_DIR_, ".."); for (i = 0; i < NPIDFILES; i++) EMIT((dp->inum & ~0xFFULL) | pidfiles[i].id, - pidfiles[i].id == PF_FD ? DT_DIR_ : pidfiles[i].id == PF_EXE ? DT_LNK_ : DT_REG_, + pidfiles[i].id == PF_FD || pidfiles[i].id == PF_TASK ? DT_DIR_ : + pidfiles[i].id == PF_EXE ? DT_LNK_ : DT_REG_, pidfiles[i].name); } diff --git a/kernel/fs/schfs/vfs.c b/kernel/fs/schfs/vfs.c index 2e5b3d6..9feb22a 100644 --- a/kernel/fs/schfs/vfs.c +++ b/kernel/fs/schfs/vfs.c @@ -100,6 +100,9 @@ schfs_vfs_iload(struct inode *ip) ip->nlink = in.nlink > 0x7fffffffu ? 0x7fffffff : (int)in.nlink; ip->size = in.size; ip->rwmode = RW; + ip->mode = in.mode & 07777; // 0 on disk: never recorded + ip->uid = in.uid; + ip->gid = in.gid; ip->supermode = 0; ip->showmode = 1; ip->atime = (uint)in.atime; @@ -134,6 +137,10 @@ schfs_vfs_istore(struct inode *ip) in.mode = ip->type == T_DIR ? 0755 : 0644; } + if (ip->mode) + in.mode = ip->mode & 07777; + in.uid = ip->uid; + in.gid = ip->gid; in.type = type_from_vfs(ip->type); in.rdev_major = (uint16_t)ip->major; in.rdev_minor = (uint16_t)ip->minor; diff --git a/kernel/fs/sysfs.c b/kernel/fs/sysfs.c index 0910261..d72a070 100644 --- a/kernel/fs/sysfs.c +++ b/kernel/fs/sysfs.c @@ -2,17 +2,28 @@ // // /sys, laid out the way Linux lays it out, for the parts that exist here. // -// /sys/bus/pci/devices/0000:00:1f.2/ vendor device class revision irq +// /sys/devices/pci0000:00/0000:00:1f.2/ vendor device class revision irq // subsystem_vendor subsystem_device // resource config uevent boot_vga -// enable graphics/fb0/ -// /sys/bus/usb/devices/1-3.2/ idVendor idProduct bcdDevice busnum +// enable graphics/fb0/ subsystem driver +// /sys/devices/pci0000:00/0000:00:14.0/usb1/1-3.2/ +// idVendor idProduct bcdDevice busnum // devnum speed bDevice* uevent +// subsystem driver +// /sys/bus/pci/devices/ symlinks to the above, as on Linux +// /sys/bus/usb/devices/ +// +// The real directories live under /sys/devices because that is what udev +// resolves a device to, and what an event's DEVPATH has to name. USB devices +// sit under the xHCI controller that carries their bus, flat below its usbN +// root hub: a hub's children are siblings of the hub here, not nested in it. // /sys/firmware/acpi/tables/DSDT the tables, byte for byte // /sys/firmware/dmi/tables/DMI the SMBIOS table and entry point // /sys/firmware/memmap/N/ start end type // /sys/firmware/efi/ present when UEFI booted the kernel // /sys/class/dmi/id/product_name ... the machine, as text +// /sys/class/power_supply/BAT0/ capacity status energy_* ... (ACPI) +// /sys/class/power_supply/AC/ type online // // Why Linux's layout rather than one of its own: everything that reads // hardware information already reads it from these paths. toybox's lspci and @@ -42,6 +53,9 @@ #include "dev/usb.h" #include "core/hwinfo.h" #include "gfx/gfx.h" +#include "dev/acpi.h" +#include "dev/kdev.h" +#include "net/socket.h" // --------------------------------------------------------------------------- // Inode numbers: kind, index, file -- the whole identity, since iget gives @@ -55,6 +69,22 @@ #define K_MEMMAP 5ULL // idx an entry of e820_nth #define K_DMIID 6ULL // file a DMI_* field + 1 #define K_DMITAB 7ULL // file 1 the entry point, 2 the table +#define K_PSU 8ULL // idx an acpi_psu_get() index; file a psufiles[] index +#define K_KDEV 9ULL // idx a kdev index; file 0 the directory, KF_* its files +#define K_KSUBV 10ULL // idx a kdev subsystem: /sys/devices/virtual/ +#define K_KCLASS 11ULL // idx a kdev subsystem: /sys/class/ +#define K_KCLINK 12ULL // idx a kdev: the symlink in /sys/class/ +#define K_KDEVD 13ULL // idx 0 char, 1 block: /sys/dev/char, /sys/dev/block +#define K_KDEVL 14ULL // idx a kdev: the "MAJ:MIN" symlink in /sys/dev/* +#define K_PCILINK 15ULL // idx a pci_get() index: the symlink in /sys/bus/pci/devices +#define K_USBLINK 16ULL // idx bus << 8 | devnum (0: the root hub): /sys/bus/usb/devices +#define K_USBIF 18ULL // idx devidx << 8 | interface number: 1-5:1.0 +#define K_USBIFLN 19ULL // the same, as the symlink in /sys/bus/usb/devices +#define K_USBROOT 17ULL // idx a bus number: the root hub, /sys/devices/pci.../usbN + +// Files in a kdev directory (K_KDEV). +enum { KF_DIR, KF_DEV, KF_UEVENT, KF_SUBSYSTEM, KF_NAME, KF_PROPERTIES, + KF_CAPS, KF_EV, KF_KEY, KF_REL, KF_ABS, KF_N }; #define INO(k, idx, f) (((uint64)(k) << 40) | ((uint64)(idx) << 8) | (uint64)(f)) #define I_KIND(i) ((int)((i) >> 40)) @@ -63,7 +93,9 @@ enum { D_ROOT, D_BUS, D_PCI, D_PCIDEV, D_USB, D_USBDEV, D_FW, D_ACPI, D_ACPITAB, - D_DMI, D_DMITAB, D_MEMMAP, D_EFI, D_CLASS, D_CLASSDMI, D_DMIID, D_NDIRS + D_DMI, D_DMITAB, D_MEMMAP, D_EFI, D_CLASS, D_CLASSDMI, D_DMIID, D_PSU, + D_DEVICES, D_VIRTUAL, D_DEVT, D_PCIROOT, + D_NDIRS }; static const struct { const char *name; int parent; } dirs[D_NDIRS] = { @@ -83,6 +115,11 @@ static const struct { const char *name; int parent; } dirs[D_NDIRS] = { [D_CLASS] = { "class", D_ROOT }, [D_CLASSDMI] = { "dmi", D_CLASS }, [D_DMIID] = { "id", D_CLASSDMI }, + [D_PSU] = { "power_supply", D_CLASS }, + [D_DEVICES] = { "devices", D_ROOT }, + [D_VIRTUAL] = { "virtual", D_DEVICES }, + [D_DEVT] = { "dev", D_ROOT }, + [D_PCIROOT] = { "pci0000:00", D_DEVICES }, }; #define SYSFS_ROOT INO(K_DIR, D_ROOT, 0) @@ -102,6 +139,8 @@ static const char *const pcifiles[] = { // boot_vga. Empty, because the path existing is the whole answer. #define PCI_GRAPHICS 13 #define PCI_FB0 14 +#define PCI_SUBSYS 15 // symlink: ../../../bus/pci +#define PCI_DRIVER 16 // symlink, only while a driver is bound // The display device: the one whose memory the framebuffer is in, which is // what boot_vga means on Linux -- the card firmware brought up and drew on. @@ -129,12 +168,58 @@ boot_vga_idx(void) static const char *const usbfiles[] = { 0, "idVendor", "idProduct", "bcdDevice", "busnum", "devnum", "speed", "bDeviceClass", "bDeviceSubClass", "bDeviceProtocol", "bNumConfigurations", - "uevent", + "uevent", "subsystem", "driver", + // What lsusb and libusb read from sysfs rather than from the device. + "version", "bMaxPacketSize0", "bNumInterfaces", "bConfigurationValue", + "bmAttributes", "bMaxPower", "configuration", "maxchild", "devpath", + "rx_lanes", "tx_lanes", }; #define NUSBFILES ((int)(sizeof usbfiles / sizeof usbfiles[0])) +#define USB_UEVENT 11 +#define USB_SUBSYS 12 // symlink +#define USB_DRIVER 13 // symlink, only while a driver is bound + +// A root hub, usbN: the little that udev and lsusb read. +static const char *const hubfiles[] = { + 0, "uevent", "subsystem", "idVendor", "idProduct", "busnum", "devnum", + "bDeviceClass", "speed", "version", "bMaxPacketSize0", "bNumInterfaces", + "bConfigurationValue", "bmAttributes", "bMaxPower", "configuration", + "maxchild", "devpath", "rx_lanes", "tx_lanes", "bDeviceSubClass", + "bDeviceProtocol", "bcdDevice", +}; +#define NHUBFILES ((int)(sizeof hubfiles / sizeof hubfiles[0])) +#define HUB_UEVENT 1 +#define HUB_SUBSYS 2 + +// A USB interface: 1-5:1.0, a directory of its own below its device. +static const char *const ifiles[] = { + 0, "uevent", "subsystem", "driver", "bInterfaceClass", "bInterfaceSubClass", + "bInterfaceProtocol", "bInterfaceNumber", "bNumEndpoints", "bAlternateSetting", +}; +#define NIFILES ((int)(sizeof ifiles / sizeof ifiles[0])) +#define IF_UEVENT 1 +#define IF_SUBSYS 2 +#define IF_DRIVER 3 static const char *const memfiles[] = { 0, "start", "end", "type" }; +// Linux's power_supply attributes. Which of them a battery has depends on +// whether its firmware counts in mWh (energy_*, power_now) or mAh +// (charge_*, current_now); see psu_has(). +enum { + PS_TYPE = 1, PS_PRESENT, PS_STATUS, PS_CAPACITY, PS_LEVEL, PS_TECH, + PS_CYCLES, PS_VNOW, PS_VMIN, PS_ENOW, PS_EFULL, PS_EDESIGN, PS_PNOW, + PS_CNOW, PS_CFULL, PS_CDESIGN, PS_INOW, PS_MODEL, PS_MAKER, PS_SERIAL, + PS_UEVENT, PS_ONLINE, NPSUFILES +}; +static const char *const psufiles[NPSUFILES] = { + 0, "type", "present", "status", "capacity", "capacity_level", "technology", + "cycle_count", "voltage_now", "voltage_min_design", "energy_now", + "energy_full", "energy_full_design", "power_now", "charge_now", + "charge_full", "charge_full_design", "current_now", "model_name", + "manufacturer", "serial_number", "uevent", "online", +}; + // --------------------------------------------------------------------------- // Text // --------------------------------------------------------------------------- @@ -234,6 +319,289 @@ usb_by_idx(int idx, struct usb_info *u) return -1; } +// Interface number `num` of the device `idx` (bus << 8 | devnum). +static int +usb_if_by_num(int idx, int num, struct usb_if_info *ii) +{ + for (int n = 0; usb_if_nth(idx >> 8, idx & 0xFF, n, ii) == 0; n++) + if (ii->num == num) + return 0; + return -1; +} + +// "1-5:1.0" +static void +usb_if_name(struct out *o, const struct usb_info *u, int num) +{ + puts_(o, u->path); + puts_(o, ":1."); + putu(o, (uint64)num); +} + +// The PCI device a USB bus is: the controller recorded its own address when it +// came up (usb_bus_add). -1 if there is no such controller. +static int +usb_hc_pci(int bus) +{ + int pb, ps, pf; + + if (usb_bus_pci(bus, &pb, &ps, &pf) < 0) + return -1; + for (int i = 0; i < pci_count(); i++) { + struct pci_dev *d = pci_get(i); + if (d->bus == pb && d->slot == ps && d->func == pf) + return i; + } + return -1; +} + +// The PCI-to-PCI bridge a device sits behind: the one whose secondary.. +// subordinate bus range holds the device's bus, the innermost if several do. +// -1 for a device on the root bus. The bridges' own windows are read from +// config space each time; there are a handful of devices and this runs when a +// directory is listed. +static int +pci_parent(int idx) +{ + struct pci_dev *d = pci_get(idx); + int best = -1, bestsec = -1; + + for (int i = 0; i < pci_count(); i++) { + struct pci_dev *b = pci_get(i); + if (i == idx || (b->header_type & 0x7F) != 1) + continue; + uint32 w = pci_read32(b->bus, b->slot, b->func, 0x18); + int sec = (w >> 8) & 0xFF, sub = (w >> 16) & 0xFF; + if (sec == 0 || d->bus < sec || d->bus > sub || sec <= b->bus) + continue; + if (sec > bestsec) { + best = i; + bestsec = sec; + } + } + return best; +} + +// BAT0, BAT1 for batteries and AC, AC1 for adapters, numbered in the order +// ACPI lists them -- the names Linux gives most laptops. +static void +psu_name(int idx, char *name) +{ + struct acpi_psu b; + int nth = 0; + acpi_psu_get(idx, &b); + for (int i = 0; i < idx; i++) { + struct acpi_psu c; + if (acpi_psu_get(i, &c) == 0 && c.battery == b.battery) + nth++; + } + struct out o = { name, 0, 15 }; + if (b.battery) { puts_(&o, "BAT"); putu(&o, (uint64)nth); } + else { puts_(&o, "AC"); if (nth) putu(&o, (uint64)nth); } + name[o.n] = 0; +} + +static uint32 +psu_capacity(const struct acpi_psu *b) +{ + if (b->remaining == ACPI_UNKNOWN || b->full == ACPI_UNKNOWN || b->full == 0) + return ACPI_UNKNOWN; + uint64 c = (uint64)b->remaining * 100 / b->full; + return c > 100 ? 100 : (uint32)c; +} + +// Whether this supply has the attribute at all. A value the firmware +// reports as unknown is a missing file, not a zero. +static int +psu_has(const struct acpi_psu *b, int f) +{ + int mw = b->unit == 0, live = b->present; + if (!b->battery) + return f == PS_TYPE || f == PS_ONLINE || f == PS_UEVENT; + switch (f) { + case PS_TYPE: case PS_PRESENT: case PS_UEVENT: return 1; + case PS_STATUS: case PS_TECH: return live; + case PS_CAPACITY: case PS_LEVEL: return live && psu_capacity(b) != ACPI_UNKNOWN; + case PS_CYCLES: return live && b->cycles != ACPI_UNKNOWN && b->cycles != 0; + case PS_VNOW: return live && b->voltage != ACPI_UNKNOWN; + case PS_VMIN: return live && b->design_voltage != ACPI_UNKNOWN; + case PS_ENOW: return live && mw && b->remaining != ACPI_UNKNOWN; + case PS_EFULL: return live && mw && b->full != ACPI_UNKNOWN; + case PS_EDESIGN: return live && mw && b->design != ACPI_UNKNOWN; + case PS_PNOW: return live && mw && b->rate != ACPI_UNKNOWN; + case PS_CNOW: return live && !mw && b->remaining != ACPI_UNKNOWN; + case PS_CFULL: return live && !mw && b->full != ACPI_UNKNOWN; + case PS_CDESIGN: return live && !mw && b->design != ACPI_UNKNOWN; + case PS_INOW: return live && !mw && b->rate != ACPI_UNKNOWN; + case PS_MODEL: return live && b->model[0]; + case PS_MAKER: return live && b->oem[0]; + case PS_SERIAL: return live && b->serial[0]; + } + return 0; +} + +static const char * +psu_status(const struct acpi_psu *b) +{ + if (b->state & 2) return "Charging"; + if (b->state & 1) return "Discharging"; + uint32 c = psu_capacity(b); + return (c != ACPI_UNKNOWN && c >= 98) ? "Full" : "Not charging"; +} + +static const char * +psu_level(uint32 c) +{ + if (c < 5) return "Critical"; + if (c < 15) return "Low"; + if (c >= 98) return "Full"; + if (c > 80) return "High"; + return "Normal"; +} + +static void +gen_psu_value(struct out *o, const struct acpi_psu *b, int f) +{ + switch (f) { + case PS_TYPE: puts_(o, b->battery ? "Battery" : "Mains"); break; + case PS_PRESENT: putc_(o, b->present ? '1' : '0'); break; + case PS_ONLINE: putc_(o, b->online ? '1' : '0'); break; + case PS_STATUS: puts_(o, psu_status(b)); break; + case PS_CAPACITY: putu(o, psu_capacity(b)); break; + case PS_LEVEL: puts_(o, psu_level(psu_capacity(b))); break; + case PS_TECH: puts_(o, b->kind[0] == 'L' || b->kind[0] == 'l' ? "Li-ion" + : b->technology == 0 ? "Unknown" : "Li-ion"); break; + case PS_CYCLES: putu(o, b->cycles); break; + // Linux reports micro-units; ACPI's are milli. + case PS_VNOW: putu(o, (uint64)b->voltage * 1000); break; + case PS_VMIN: putu(o, (uint64)b->design_voltage * 1000); break; + case PS_ENOW: case PS_CNOW: putu(o, (uint64)b->remaining * 1000); break; + case PS_EFULL: case PS_CFULL: putu(o, (uint64)b->full * 1000); break; + case PS_EDESIGN: case PS_CDESIGN: putu(o, (uint64)b->design * 1000); break; + case PS_PNOW: case PS_INOW: putu(o, (uint64)b->rate * 1000); break; + case PS_MODEL: puts_(o, b->model); break; + case PS_MAKER: puts_(o, b->oem); break; + case PS_SERIAL: puts_(o, b->serial); break; + } +} + +static void +gen_psu(struct out *o, int idx, int f) +{ + struct acpi_psu b; + if (acpi_psu_get(idx, &b) < 0) + return; + if (f != PS_UEVENT) { + gen_psu_value(o, &b, f); + putc_(o, '\n'); + return; + } + // uevent: every attribute this supply has, as POWER_SUPPLY_=value. + char name[16]; + psu_name(idx, name); + puts_(o, "POWER_SUPPLY_NAME="); puts_(o, name); putc_(o, '\n'); + for (int k = 1; k < NPSUFILES; k++) { + if (k == PS_UEVENT || !psu_has(&b, k)) + continue; + puts_(o, "POWER_SUPPLY_"); + for (const char *s = psufiles[k]; *s; s++) + putc_(o, *s >= 'a' && *s <= 'z' ? *s - 32 : *s); + putc_(o, '='); + gen_psu_value(o, &b, k); + putc_(o, '\n'); + } +} + +// ---- kernel-implemented devices (include/dev/kdev.h) ---- + +static int +kd_subsys_of(const struct kdev *k) +{ + for (int s = 0; s < kdev_nsubsys(); s++) { + const char *n = kdev_subsys(s), *m = k->subsys; + while (*n && *n == *m) { n++; m++; } + if (*n == *m) + return s; + } + return -1; +} + +static void +kd_devt(const struct kdev *k, char *name) +{ + struct out o = { name, 0, 30 }; + int maj, min; + kdev_numbers(k, &maj, &min); + putu(&o, (uint64)maj); + putc_(&o, ':'); + putu(&o, (uint64)min); + name[o.n] = 0; +} + +// Does file f exist in kdev k's directory? +static int +kd_has(const struct kdev *k, int f) +{ + switch (f) { + case KF_DEV: return k->devname[0] != 0; + case KF_UEVENT: + case KF_SUBSYSTEM: return 1; + case KF_NAME: case KF_PROPERTIES: case KF_CAPS: + return k->cap != KD_NONE; + } + return 0; +} + +static int +kd_children(uint64 dino, child_fn cb, void *ctx) +{ + int kind = I_KIND(dino), idx = I_IDX(dino); + char name[32]; + struct kdev k; + + if (kind == K_KSUBV) { + for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++) + if (kdev_get(i, &k) == 0 && k.parent < 0 && kd_subsys_of(&k) == idx && + cb(ctx, INO(K_KDEV, i, 0), 1, k.name)) + return 1; + } else if (kind == K_KCLASS) { + for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++) + if (kdev_get(i, &k) == 0 && kd_subsys_of(&k) == idx && + cb(ctx, INO(K_KCLINK, i, 0), 2, k.name)) + return 1; + } else if (kind == K_KDEVD) { + for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++) + if (kdev_get(i, &k) == 0 && k.devname[0] && k.block == idx) { + kd_devt(&k, name); + if (cb(ctx, INO(K_KDEVL, i, 0), 2, name)) + return 1; + } + } else if (kind == K_KDEV && I_FILE(dino) == KF_DIR) { + if (kdev_get(idx, &k) < 0) + return 0; + for (int f = KF_DEV; f <= KF_CAPS; f++) + if (kd_has(&k, f)) { + static const char *const names[] = { 0, "dev", "uevent", "subsystem", + "name", "properties", "capabilities" }; + if (cb(ctx, INO(K_KDEV, idx, f), + f == KF_CAPS ? 1 : f == KF_SUBSYSTEM ? 2 : 0, names[f])) + return 1; + } + for (int n = 0, i; (i = kdev_nth(n)) >= 0; n++) { + struct kdev c; + if (kdev_get(i, &c) == 0 && c.parent == idx && + cb(ctx, INO(K_KDEV, i, 0), 1, c.name)) + return 1; + } + } else if (kind == K_KDEV && I_FILE(dino) == KF_CAPS) { + static const char *const names[] = { "ev", "key", "rel", "abs" }; + for (int f = KF_EV; f <= KF_ABS; f++) + if (cb(ctx, INO(K_KDEV, idx, f), 0, names[f - KF_EV])) + return 1; + } + return 0; +} + static int children(uint64 dino, child_fn cb, void *ctx) { @@ -250,20 +618,67 @@ children(uint64 dino, child_fn cb, void *ctx) return 1; } switch (idx) { + case D_VIRTUAL: + for (int s = 0; s < kdev_nsubsys(); s++) + if (cb(ctx, INO(K_KSUBV, s, 0), 1, kdev_subsys(s))) + return 1; + break; + case D_DEVT: + if (cb(ctx, INO(K_KDEVD, 0, 0), 1, "char") || + cb(ctx, INO(K_KDEVD, 1, 0), 1, "block")) + return 1; + break; + case D_CLASS: + for (int s = 0; s < kdev_nsubsys(); s++) + if (cb(ctx, INO(K_KCLASS, s, 0), 1, kdev_subsys(s))) + return 1; + break; + case D_PCIROOT: case D_PCIDEV: + // The devices themselves live in the first; the second holds the + // links udev and lspci walk to reach them. for (int i = 0; i < pci_count(); i++) { struct out o = { name, 0, sizeof name - 1 }; pci_name(&o, pci_get(i)); name[o.n] = 0; - if (cb(ctx, INO(K_PCI, i, 0), 1, name)) + if (idx == D_PCIDEV ? cb(ctx, INO(K_PCILINK, i, 0), 2, name) + : pci_parent(i) < 0 && + cb(ctx, INO(K_PCI, i, 0), 1, name)) return 1; } + // A bus whose controller we cannot place hangs off the root instead. + if (idx == D_PCIROOT) + for (int b = 1; b <= usb_nbuses(); b++) + if (usb_hc_pci(b) < 0) { + struct out o = { name, 0, sizeof name - 1 }; + puts_(&o, "usb"); putu(&o, (uint64)b); + name[o.n] = 0; + if (cb(ctx, INO(K_USBROOT, b, 0), 1, name)) + return 1; + } break; case D_USBDEV: { struct usb_info u; - for (int i = 0; usb_info_nth(i, &u) == 0; i++) - if (cb(ctx, INO(K_USB, (u.bus << 8) | u.devnum, 0), 1, u.path)) + for (int b = 1; b <= usb_nbuses(); b++) { + struct out o = { name, 0, sizeof name - 1 }; + puts_(&o, "usb"); putu(&o, (uint64)b); + name[o.n] = 0; + if (cb(ctx, INO(K_USBLINK, b << 8, 0), 2, name)) return 1; + } + for (int i = 0; usb_info_nth(i, &u) == 0; i++) { + struct usb_if_info ii; + if (cb(ctx, INO(K_USBLINK, (u.bus << 8) | u.devnum, 0), 2, u.path)) + return 1; + for (int n = 0; usb_if_nth(u.bus, u.devnum, n, &ii) == 0; n++) { + struct out o = { name, 0, sizeof name - 1 }; + usb_if_name(&o, &u, ii.num); + name[o.n] = 0; + if (cb(ctx, INO(K_USBIFLN, (((u.bus << 8) | u.devnum) << 8) | ii.num, 0), + 2, name)) + return 1; + } + } break; } case D_ACPITAB: @@ -296,6 +711,13 @@ children(uint64 dino, child_fn cb, void *ctx) } break; } + case D_PSU: + for (int i = 0; i < acpi_psu_count(); i++) { + psu_name(i, name); + if (cb(ctx, INO(K_PSU, i, 0), 1, name)) + return 1; + } + break; case D_DMIID: for (int f = 0; f < DMI_NFIELDS; f++) if (dmi_field(f, name, sizeof name) >= 0 && @@ -313,13 +735,79 @@ children(uint64 dino, child_fn cb, void *ctx) if (idx == boot_vga_idx() && gfx_fb_phys() && cb(ctx, INO(K_PCI, idx, PCI_GRAPHICS), 1, "graphics")) return 1; + if (cb(ctx, INO(K_PCI, idx, PCI_SUBSYS), 2, "subsystem")) + return 1; + for (int i = 0; i < pci_count(); i++) + if (i != idx && pci_parent(i) == idx) { + struct out o = { name, 0, sizeof name - 1 }; + pci_name(&o, pci_get(i)); + name[o.n] = 0; + if (cb(ctx, INO(K_PCI, i, 0), 1, name)) + return 1; + } + if (idx < pci_count() && pci_get(idx)->driver && + cb(ctx, INO(K_PCI, idx, PCI_DRIVER), 2, "driver")) + return 1; + for (int b = 1; b <= usb_nbuses(); b++) + if (usb_hc_pci(b) == idx) { + char un[12] = "usb"; + struct out o = { un + 3, 0, 8 }; + putu(&o, (uint64)b); + un[3 + o.n] = 0; + if (cb(ctx, INO(K_USBROOT, b, 0), 1, un)) + return 1; + } + } else if (kind == K_USBROOT && I_FILE(dino) == 0) { + struct usb_info u; + for (int f = 1; f < NHUBFILES; f++) + if (cb(ctx, INO(K_USBROOT, idx, f), f == HUB_SUBSYS ? 2 : 0, hubfiles[f])) + return 1; + for (int i = 0; usb_info_nth(i, &u) == 0; i++) + if (u.bus == idx && + cb(ctx, INO(K_USB, (u.bus << 8) | u.devnum, 0), 1, u.path)) + return 1; } else if (kind == K_PCI && I_FILE(dino) == PCI_GRAPHICS) { if (cb(ctx, INO(K_PCI, idx, PCI_FB0), 1, "fb0")) return 1; } else if (kind == K_USB && I_FILE(dino) == 0) { - for (int f = 1; f < NUSBFILES; f++) - if (cb(ctx, INO(K_USB, idx, f), 0, usbfiles[f])) + struct usb_info u; + int have = usb_by_idx(idx, &u) == 0; + for (int f = 1; f < NUSBFILES; f++) { + if (f == USB_DRIVER && !(have && u.driver[0])) + continue; + if (cb(ctx, INO(K_USB, idx, f), f == USB_SUBSYS || f == USB_DRIVER ? 2 : 0, + usbfiles[f])) return 1; + } + for (int n = 0; have; n++) { + struct usb_if_info ii; + if (usb_if_nth(u.bus, u.devnum, n, &ii) < 0) + break; + struct out o = { name, 0, sizeof name - 1 }; + usb_if_name(&o, &u, ii.num); + name[o.n] = 0; + if (cb(ctx, INO(K_USBIF, (idx << 8) | ii.num, 0), 1, name)) + return 1; + } + } else if (kind == K_USBIF && I_FILE(dino) == 0) { + struct usb_if_info ii; + int have = usb_if_by_num(idx >> 8, idx & 0xFF, &ii) == 0; + for (int f = 1; have && f < NIFILES; f++) { + if (f == IF_DRIVER && !ii.driver[0]) + continue; + if (cb(ctx, INO(K_USBIF, idx, f), f == IF_SUBSYS || f == IF_DRIVER ? 2 : 0, + ifiles[f])) + return 1; + } + } else if (kind == K_PSU && I_FILE(dino) == 0) { + struct acpi_psu b; + if (acpi_psu_get(idx, &b) == 0) + for (int f = 1; f < NPSUFILES; f++) + if (psu_has(&b, f) && cb(ctx, INO(K_PSU, idx, f), 0, psufiles[f])) + return 1; + } else if (kind == K_KSUBV || kind == K_KCLASS || kind == K_KDEVD || + kind == K_KDEV) { + return kd_children(dino, cb, ctx); } else if (kind == K_MEMMAP && I_FILE(dino) == 0) { for (int f = 1; f < 4; f++) if (cb(ctx, INO(K_MEMMAP, idx, f), 0, memfiles[f])) @@ -336,9 +824,33 @@ parent_of(uint64 dino) case K_PCI: if (I_FILE(dino) == PCI_GRAPHICS) return INO(K_PCI, I_IDX(dino), 0); if (I_FILE(dino) == PCI_FB0) return INO(K_PCI, I_IDX(dino), PCI_GRAPHICS); - return INO(K_DIR, D_PCIDEV, 0); - case K_USB: return INO(K_DIR, D_USBDEV, 0); + if (I_IDX(dino) < pci_count() && pci_parent(I_IDX(dino)) >= 0) + return INO(K_PCI, pci_parent(I_IDX(dino)), 0); + return INO(K_DIR, D_PCIROOT, 0); + case K_PCILINK: return INO(K_DIR, D_PCIDEV, 0); + case K_USBLINK: return INO(K_DIR, D_USBDEV, 0); + case K_USBROOT: { + int hc = usb_hc_pci(I_IDX(dino)); + return hc >= 0 ? INO(K_PCI, hc, 0) : INO(K_DIR, D_PCIROOT, 0); + } + case K_USB: return INO(K_USBROOT, I_IDX(dino) >> 8, 0); + case K_USBIF: return INO(K_USB, I_IDX(dino) >> 8, 0); + case K_USBIFLN: return INO(K_DIR, D_USBDEV, 0); case K_MEMMAP: return INO(K_DIR, D_MEMMAP, 0); + case K_PSU: return INO(K_DIR, D_PSU, 0); + case K_KSUBV: return INO(K_DIR, D_VIRTUAL, 0); + case K_KCLASS: return INO(K_DIR, D_CLASS, 0); + case K_KDEVD: return INO(K_DIR, D_DEVT, 0); + case K_KDEV: { + struct kdev k; + if (I_FILE(dino) == KF_CAPS) + return INO(K_KDEV, I_IDX(dino), 0); + if (kdev_get(I_IDX(dino), &k) < 0) + return SYSFS_ROOT; + if (k.parent >= 0) + return INO(K_KDEV, k.parent, 0); + return INO(K_KSUBV, kd_subsys_of(&k), 0); + } } return SYSFS_ROOT; } @@ -465,6 +977,24 @@ gen_usb(struct out *o, const struct usb_info *u, int f) case 8: putx(o, u->subcls, 2, 0); break; case 9: putx(o, u->proto, 2, 0); break; case 10: putu(o, u->nconf); break; + case 14: // version: " 2.00", as Linux pads it + putc_(o, (u->bcdusb >> 8) > 9 ? '1' : ' '); + putx(o, (u->bcdusb >> 8) > 9 ? (u->bcdusb >> 8) - 10 : (u->bcdusb >> 8), 1, 0); + putc_(o, '.'); putx(o, u->bcdusb & 0xFF, 2, 0); break; + case 15: putu(o, u->maxp0); break; + case 16: putc_(o, ' '); putu(o, u->nif); break; + case 17: putu(o, u->confval); break; + case 18: putx(o, u->attrs, 2, 0); break; + case 19: putu(o, u->maxpower); puts_(o, "mA"); break; + case 20: break; // configuration: no string descriptor read + case 21: putu(o, 0); break; // maxchild: a hub's port count is not recorded + case 22: { // devpath: the port chain, "5" or "5.2" + const char *q = u->path; + while (*q && *q != '-') q++; + puts_(o, *q ? q + 1 : q); + break; + } + case 23: case 24: putu(o, 1); break; case 11: puts_(o, "DEVTYPE=usb_device\n"); if (u->driver[0]) { puts_(o, "DRIVER="); puts_(o, u->driver); putc_(o, '\n'); } @@ -480,11 +1010,239 @@ gen_usb(struct out *o, const struct usb_info *u, int f) if (u->devnum < 100) putc_(o, '0'); if (u->devnum < 10) putc_(o, '0'); putu(o, (uint64)u->devnum); + puts_(o, "\nMODALIAS=usb:v"); putx(o, u->vid, 4, 1); + puts_(o, "p"); putx(o, u->pid, 4, 1); + puts_(o, "d"); putx(o, u->bcd, 4, 1); + puts_(o, "dc"); putx(o, u->cls, 2, 1); + puts_(o, "dsc"); putx(o, u->subcls, 2, 1); + puts_(o, "dp"); putx(o, u->proto, 2, 1); + puts_(o, "ic*isc*ip*in*"); break; } putc_(o, '\n'); } +// An interface's own uevent: what udev needs to load a driver by alias and to +// tell a keyboard from a hub. +static void +gen_usbif(struct out *o, const struct usb_info *u, const struct usb_if_info *ii, int f) +{ + switch (f) { + case IF_UEVENT: + puts_(o, "DEVTYPE=usb_interface\n"); + if (ii->driver[0]) { puts_(o, "DRIVER="); puts_(o, ii->driver); putc_(o, '\n'); } + puts_(o, "PRODUCT="); putx(o, u->vid, 0, 0); putc_(o, '/'); + putx(o, u->pid, 0, 0); putc_(o, '/'); putx(o, u->bcd, 0, 0); + puts_(o, "\nTYPE="); putu(o, u->cls); putc_(o, '/'); + putu(o, u->subcls); putc_(o, '/'); putu(o, u->proto); + puts_(o, "\nINTERFACE="); putu(o, ii->cls); putc_(o, '/'); + putu(o, ii->subcls); putc_(o, '/'); putu(o, ii->proto); + puts_(o, "\nMODALIAS=usb:v"); putx(o, u->vid, 4, 1); + puts_(o, "p"); putx(o, u->pid, 4, 1); + puts_(o, "d"); putx(o, u->bcd, 4, 1); + puts_(o, "dc"); putx(o, u->cls, 2, 1); + puts_(o, "dsc"); putx(o, u->subcls, 2, 1); + puts_(o, "dp"); putx(o, u->proto, 2, 1); + puts_(o, "ic"); putx(o, ii->cls, 2, 1); + puts_(o, "isc"); putx(o, ii->subcls, 2, 1); + puts_(o, "ip"); putx(o, ii->proto, 2, 1); + puts_(o, "in"); putx(o, ii->num, 2, 1); + break; + case 4: putx(o, ii->cls, 2, 0); break; + case 5: putx(o, ii->subcls, 2, 0); break; + case 6: putx(o, ii->proto, 2, 0); break; + case 7: putx(o, ii->num, 2, 0); break; + case 8: putx(o, ii->nendp, 2, 0); break; + case 9: putu(o, 0); break; + } + putc_(o, '\n'); +} + +// --- where a device is, and how to say so ----------------------------------- + +// "devices/pci0000:00/0000:00:14.0/usb1": the directory of a root hub; with +// `path`, the device below it. No leading slash. Returns how many directories +// lie between it and /sys, which is how many "../" a link inside it needs. +// "devices/pci0000:00//.../0000:00:1f.2": a PCI device's directory, +// below its bridges. Returns the depth below /sys, for the ../ of a link. +static int +pci_dir(struct out *o, int idx) +{ + int par = pci_parent(idx), depth; + + if (par < 0) { + puts_(o, "devices/pci0000:00/"); + depth = 2; + } else { + depth = pci_dir(o, par); + putc_(o, '/'); + } + pci_name(o, pci_get(idx)); + return depth + 1; +} + +static int +usb_dir(struct out *o, int bus, const char *path) +{ + int hc = usb_hc_pci(bus), depth = 3; + + if (hc >= 0) { + depth = pci_dir(o, hc) + 1; + putc_(o, '/'); + } else { + puts_(o, "devices/pci0000:00/"); + } + puts_(o, "usb"); + putu(o, (uint64)bus); + if (path) { + putc_(o, '/'); + puts_(o, path); + depth++; + } + return depth; +} + +static void +updirs(struct out *o, int n) +{ + while (n-- > 0) + puts_(o, "../"); +} + +// The target of a symlink for the PCI or USB entry `ino`, or -1. +static int +link_text(uint64 ino, struct out *o) +{ + int idx = I_IDX(ino), f = I_FILE(ino); + struct usb_info u; + + switch (I_KIND(ino)) { + case K_PCILINK: + if (idx >= pci_count()) + return -1; + puts_(o, "../../../"); + pci_dir(o, idx); + return o->n; + case K_PCI: + if (idx >= pci_count()) + return -1; + if (f == PCI_SUBSYS || (f == PCI_DRIVER && pci_get(idx)->driver)) { + struct out d = { o->p + o->n, 0, 0 }; // measure only + updirs(o, pci_dir(&d, idx)); + if (f == PCI_SUBSYS) { + puts_(o, "bus/pci"); + } else { + puts_(o, "bus/pci/drivers/"); + puts_(o, pci_get(idx)->driver); + } + return o->n; + } + return -1; + case K_USBLINK: + if ((idx & 0xFF) == 0) { + if (idx >> 8 < 1 || idx >> 8 > usb_nbuses()) + return -1; + puts_(o, "../../../"); + usb_dir(o, idx >> 8, 0); + return o->n; + } + if (usb_by_idx(idx, &u) < 0) + return -1; + puts_(o, "../../../"); + usb_dir(o, u.bus, u.path); + return o->n; + case K_USB: + if (usb_by_idx(idx, &u) < 0) + return -1; + if (f == USB_SUBSYS) { + struct out d = { o->p + o->n, 0, 0 }; // measure only + updirs(o, usb_dir(&d, u.bus, u.path)); + puts_(o, "bus/usb"); + return o->n; + } + if (f == USB_DRIVER && u.driver[0]) { + struct out d = { o->p + o->n, 0, 0 }; + updirs(o, usb_dir(&d, u.bus, u.path)); + puts_(o, "bus/usb/drivers/"); + puts_(o, u.driver); + return o->n; + } + return -1; + case K_USBIFLN: { + struct usb_if_info ii; + if (usb_by_idx(idx >> 8, &u) < 0 || usb_if_by_num(idx >> 8, idx & 0xFF, &ii) < 0) + return -1; + puts_(o, "../../../"); + usb_dir(o, u.bus, u.path); + putc_(o, '/'); + usb_if_name(o, &u, ii.num); + return o->n; + } + case K_USBIF: { + struct usb_if_info ii; + if (usb_by_idx(idx >> 8, &u) < 0 || usb_if_by_num(idx >> 8, idx & 0xFF, &ii) < 0) + return -1; + if (f == IF_SUBSYS || (f == IF_DRIVER && ii.driver[0])) { + struct out d = { o->p + o->n, 0, 0 }; + updirs(o, usb_dir(&d, u.bus, u.path) + 1); + if (f == IF_SUBSYS) { + puts_(o, "bus/usb"); + } else { + puts_(o, "bus/usb/drivers/"); + puts_(o, ii.driver); + } + return o->n; + } + return -1; + } + case K_USBROOT: + if (f == HUB_SUBSYS && idx >= 1 && idx <= usb_nbuses()) { + struct out d = { o->p + o->n, 0, 0 }; + updirs(o, usb_dir(&d, idx, 0)); + puts_(o, "bus/usb"); + return o->n; + } + return -1; + } + return -1; +} + +static void +gen_hub(struct out *o, int bus, int f) +{ + switch (f) { + case 1: + puts_(o, "DEVTYPE=usb_device\nPRODUCT=1d6b/3/"); + putx(o, 0x0601, 4, 0); + puts_(o, "\nTYPE=9/0/1\nBUSNUM="); + if (bus < 100) putc_(o, '0'); + if (bus < 10) putc_(o, '0'); + putu(o, (uint64)bus); + puts_(o, "\nDEVNUM=001"); + break; + case 3: puts_(o, "1d6b"); break; + case 4: puts_(o, "0003"); break; + case 5: putu(o, (uint64)bus); break; + case 6: putu(o, 1); break; + case 7: puts_(o, "09"); break; + case 8: puts_(o, "5000"); break; + case 9: puts_(o, " 3.00"); break; + case 10: puts_(o, "9"); break; + case 11: puts_(o, " 1"); break; + case 12: puts_(o, "1"); break; + case 13: puts_(o, "e0"); break; + case 14: puts_(o, "0mA"); break; + case 15: break; + case 16: puts_(o, "0"); break; + case 17: puts_(o, "0"); break; + case 18: case 19: puts_(o, "1"); break; + case 20: puts_(o, "00"); break; + case 21: puts_(o, "03"); break; + case 22: puts_(o, "0601"); break; + } + putc_(o, '\n'); +} + // A text file, whole. Returns its length, or -1 if there is no such file. static int text(uint64 ino, char *buf, int cap) @@ -493,18 +1251,50 @@ text(uint64 ino, char *buf, int cap) int idx = I_IDX(ino), f = I_FILE(ino); switch (I_KIND(ino)) { + case K_PCILINK: + case K_USBLINK: + return link_text(ino, &o); case K_PCI: + if (f == PCI_SUBSYS || f == PCI_DRIVER) + return link_text(ino, &o); if (idx >= pci_count() || f < 1 || f >= NPCIFILES || f == PCI_CONFIG) return -1; gen_pci(&o, pci_get(idx), f); return o.n; + case K_USBIFLN: + return link_text(ino, &o); + case K_USBIF: { + struct usb_info u; + struct usb_if_info ii; + if (usb_by_idx(idx >> 8, &u) < 0 || usb_if_by_num(idx >> 8, idx & 0xFF, &ii) < 0 || + f < 1 || f >= NIFILES) + return -1; + if (f == IF_SUBSYS || f == IF_DRIVER) + return link_text(ino, &o); + gen_usbif(&o, &u, &ii, f); + return o.n; + } + case K_USBROOT: + if (idx < 1 || idx > usb_nbuses() || f < 1 || f >= NHUBFILES) + return -1; + if (f == HUB_SUBSYS) + return link_text(ino, &o); + gen_hub(&o, idx, f); + return o.n; case K_USB: { struct usb_info u; if (usb_by_idx(idx, &u) < 0 || f < 1 || f >= NUSBFILES) return -1; + if (f == USB_SUBSYS || f == USB_DRIVER) + return link_text(ino, &o); gen_usb(&o, &u, f); return o.n; } + case K_PSU: + if (idx >= acpi_psu_count() || f < 1 || f >= NPSUFILES) + return -1; + gen_psu(&o, idx, f); + return o.n; case K_MEMMAP: { struct e820_rec e; if (e820_nth(idx, &e) < 0 || f < 1 || f > 3) @@ -515,6 +1305,67 @@ text(uint64 ino, char *buf, int cap) putc_(&o, '\n'); return o.n; } + case K_KDEV: { + struct kdev k; + char path[160]; + if (kdev_get(idx, &k) < 0 || f < KF_DEV || f >= KF_N || f == KF_CAPS || + (f != KF_UEVENT && f != KF_SUBSYSTEM && !kd_has(&k, f) && + !(f >= KF_EV && k.cap != KD_NONE))) + return -1; + switch (f) { + case KF_DEV: + kd_devt(&k, buf); + o.n = (int)strlen(buf); + putc_(&o, '\n'); + return o.n; + case KF_UEVENT: + return kdev_uevent_text(idx, buf, cap); + case KF_SUBSYSTEM: { + // ../ once for each directory between here and /sys, then class/. + if (kdev_path(idx, path, sizeof path) < 0) + return -1; + for (const char *p = path; ; p++) { + if (*p == '/' || *p == 0) + puts_(&o, "../"); + if (*p == 0) + break; + } + puts_(&o, "class/"); + puts_(&o, k.subsys); + return o.n; + } + case KF_NAME: + puts_(&o, k.cap == KD_KEYBOARD ? "SchOS keyboard\n" : "SchOS mouse\n"); + return o.n; + case KF_PROPERTIES: + puts_(&o, "0\n"); + return o.n; + case KF_EV: + puts_(&o, k.cap == KD_KEYBOARD ? "120013\n" : "17\n"); + return o.n; + case KF_KEY: + puts_(&o, k.cap == KD_KEYBOARD + ? "ffffffffffffffff ffffffffffffffff ffffffffffffffff fffffffffffffffe\n" + : "70000 0 0 0 0\n"); + return o.n; + case KF_REL: + puts_(&o, k.cap == KD_MOUSE ? "903\n" : "0\n"); + return o.n; + case KF_ABS: + puts_(&o, "0\n"); + return o.n; + } + return -1; + } + case K_KCLINK: + case K_KDEVL: { + char path[160]; + if (kdev_path(idx, path, sizeof path) < 0) + return -1; + puts_(&o, "../../"); + puts_(&o, path); + return o.n; + } case K_DMIID: { int n = dmi_field(f - 1, buf, cap - 1); if (n < 0) @@ -526,6 +1377,91 @@ text(uint64 ino, char *buf, int cap) return -1; } +// --------------------------------------------------------------------------- +// Announcing devices +// +// A uevent is "action@/devpath", then NUL-separated KEY=value pairs: the +// four the kernel always sends, then whatever the device's own `uevent` file +// says. The same text serves a hot-plug event and the answer to +// `echo add > uevent`, so udev sees one thing however it learned of a device. +// --------------------------------------------------------------------------- + +static void +announce(const char *action, const char *devpath, const char *subsys, + const char *vars, int vlen) +{ + char msg[700]; + struct out o = { msg, 0, (int)sizeof msg - 1 }; + + puts_(&o, action); puts_(&o, "@/"); puts_(&o, devpath); putc_(&o, 0); + puts_(&o, "ACTION="); puts_(&o, action); putc_(&o, 0); + puts_(&o, "DEVPATH=/"); puts_(&o, devpath); putc_(&o, 0); + puts_(&o, "SUBSYSTEM="); puts_(&o, subsys); putc_(&o, 0); + puts_(&o, "SEQNUM="); putu(&o, kdev_seqnum()); putc_(&o, 0); + for (int i = 0; i < vlen; i++) + putc_(&o, vars[i] == '\n' ? 0 : vars[i]); + uevent_send(UEVENT_GROUP_KERNEL, msg, o.n, 0); +} + +void +sysfs_usb_uevent(const struct usb_info *u, const char *action) +{ + char path[96], vars[300]; + struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 }; + + usb_dir(&p, u->bus, u->path); + path[p.n] = 0; + gen_usb(&v, u, USB_UEVENT); + announce(action, path, "usb", vars, v.n); +} + +void +sysfs_usb_if_uevent(const struct usb_info *u, const struct usb_if_info *ii, + const char *action) +{ + char path[128], vars[400]; + struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 }; + + usb_dir(&p, u->bus, u->path); + putc_(&p, '/'); + usb_if_name(&p, u, ii->num); + path[p.n] = 0; + gen_usbif(&v, u, ii, IF_UEVENT); + announce(action, path, "usb", vars, v.n); +} + +static void +pci_uevent(int idx, const char *action) +{ + char path[128], vars[400]; + struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 }; + + if (idx >= pci_count()) + return; + pci_dir(&p, idx); + path[p.n] = 0; + gen_pci(&v, pci_get(idx), 10); + announce(action, path, "pci", vars, v.n); +} + +static void +hub_uevent(int bus, const char *action) +{ + char path[96], vars[200]; + struct out p = { path, 0, sizeof path - 1 }, v = { vars, 0, sizeof vars - 1 }; + + usb_dir(&p, bus, 0); + path[p.n] = 0; + gen_hub(&v, bus, HUB_UEVENT); + announce(action, path, "usb", vars, v.n); +} + +void +sysfs_usb_root_uevent(int bus, const char *action) +{ + hub_uevent(bus, action); +} + // --------------------------------------------------------------------------- // fs_ops // --------------------------------------------------------------------------- @@ -544,14 +1480,48 @@ exists_dir(uint64 ino) struct usb_info u; return usb_by_idx(I_IDX(ino), &u) == 0; } + case K_USBROOT: + return I_IDX(ino) >= 1 && I_IDX(ino) <= usb_nbuses(); + case K_USBIF: { + struct usb_if_info ii; + return usb_if_by_num(I_IDX(ino) >> 8, I_IDX(ino) & 0xFF, &ii) == 0; + } case K_MEMMAP: { struct e820_rec e; return e820_nth(I_IDX(ino), &e) == 0; } + case K_PSU: + return I_IDX(ino) < acpi_psu_count(); + case K_KSUBV: + case K_KCLASS: + return I_IDX(ino) < kdev_nsubsys(); + case K_KDEVD: + return I_IDX(ino) < 2; + case K_KDEV: { + struct kdev k; + if (kdev_get(I_IDX(ino), &k) < 0) + return 0; + return I_FILE(ino) == KF_DIR || (I_FILE(ino) == KF_CAPS && k.cap != KD_NONE); + } } return 0; } +// Symlinks: the class and dev/ entries, and a device's own `subsystem`. +static int +is_link(uint64 ino) +{ + int k = I_KIND(ino); + int f = I_FILE(ino); + return k == K_KCLINK || k == K_KDEVL || k == K_PCILINK || k == K_USBLINK || + k == K_USBIFLN || + (k == K_USBIF && (f == IF_SUBSYS || f == IF_DRIVER)) || + (k == K_KDEV && f == KF_SUBSYSTEM) || + (k == K_PCI && (f == PCI_SUBSYS || f == PCI_DRIVER)) || + (k == K_USB && (f == USB_SUBSYS || f == USB_DRIVER)) || + (k == K_USBROOT && f == HUB_SUBSYS); +} + static void sysfs_iload(struct inode *ip) { @@ -566,13 +1536,24 @@ sysfs_iload(struct inode *ip) ip->size = 0; ip->nlink = 1; - int subdir = I_KIND(ino) == K_PCI && - (I_FILE(ino) == PCI_GRAPHICS || I_FILE(ino) == PCI_FB0); + int subdir = (I_KIND(ino) == K_PCI && + (I_FILE(ino) == PCI_GRAPHICS || I_FILE(ino) == PCI_FB0)) || + (I_KIND(ino) == K_KDEV && I_FILE(ino) == KF_CAPS); if ((I_FILE(ino) == 0 || subdir) && exists_dir(ino)) { ip->type = T_DIR; ip->nlink = 2; return; } + if (is_link(ino)) { + char lb[200]; + int ln = text(ino, lb, sizeof lb); + if (ln >= 0) { + ip->type = T_SYMLINK; + ip->size = (uint64)ln; // readlink(2) trusts this length + ip->mode = 0777; + return; + } + } int n = binary(ino, 0, 0); if (n >= 0) { ip->type = T_FILE; @@ -587,7 +1568,12 @@ sysfs_iload(struct inode *ip) kmfree(buf); if (n >= 0) { ip->type = T_FILE; - if (I_KIND(ino) == K_PCI && I_FILE(ino) == PCI_ENABLE) + if ((I_KIND(ino) == K_PCI && (I_FILE(ino) == PCI_ENABLE || + I_FILE(ino) == 10 /* uevent */)) || + (I_KIND(ino) == K_USB && I_FILE(ino) == USB_UEVENT) || + (I_KIND(ino) == K_USBROOT && I_FILE(ino) == HUB_UEVENT) || + (I_KIND(ino) == K_USBIF && I_FILE(ino) == IF_UEVENT) || + (I_KIND(ino) == K_KDEV && I_FILE(ino) == KF_UEVENT)) ip->rwmode = 3; return; } @@ -648,9 +1634,65 @@ sysfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n) static int sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) { - (void)u; (void)s; (void)o; + (void)o; if (I_KIND(ip->inum) == K_PCI && I_FILE(ip->inum) == PCI_ENABLE) return (int)n; + // `echo add > uevent`: udevadm trigger asks the kernel to say again what it + // said when the device appeared. + { + int kind = I_KIND(ip->inum), f = I_FILE(ip->inum), idx = I_IDX(ip->inum); + int pcie = kind == K_PCI && f == 10; + int usbe = kind == K_USB && f == USB_UEVENT; + int hube = kind == K_USBROOT && f == HUB_UEVENT; + int ife = kind == K_USBIF && f == IF_UEVENT; + + if (pcie || usbe || hube || ife) { + char act[16]; + uint k = n < sizeof(act) - 1 ? n : (uint)sizeof(act) - 1; + if (u) { + if (copy_from_user(act, s, k) < 0) + return -EFAULT; + } else { + memmove(act, (void *)s, k); + } + uint e = 0; + while (e < k && act[e] != '\n' && act[e] != ' ') + e++; + act[e] = 0; + const char *a = e ? act : "change"; + struct usb_info ui; + if (pcie) + pci_uevent(idx, a); + else if (usbe && usb_by_idx(idx, &ui) == 0) + sysfs_usb_uevent(&ui, a); + else if (hube) + hub_uevent(idx, a); + else if (ife) { + struct usb_if_info ii; + if (usb_by_idx(idx >> 8, &ui) == 0 && usb_if_by_num(idx >> 8, idx & 0xFF, &ii) == 0) + sysfs_usb_if_uevent(&ui, &ii, a); + } + return (int)n; + } + if (kind == K_PCI || kind == K_USB) + return (int)n; + } + if (I_KIND(ip->inum) == K_KDEV && I_FILE(ip->inum) == KF_UEVENT) { + char act[16]; + uint k = n < sizeof(act) - 1 ? n : (uint)sizeof(act) - 1; + if (u) { + if (copy_from_user(act, s, k) < 0) + return -EFAULT; + } else { + memmove(act, (void *)s, k); + } + uint e = 0; + while (e < k && act[e] != '\n' && act[e] != ' ') + e++; + act[e] = 0; + kdev_uevent(I_IDX(ip->inum), e ? act : "change"); + return (int)n; + } return -EROFS; } @@ -722,7 +1764,7 @@ emit_cb(void *ctx, uint64 ino, int isdir, const char *name) de->d_ino = ino; de->d_off = l->used + rec; de->d_reclen = (ushort)rec; - de->d_type = isdir ? 4 : 8; + de->d_type = isdir == 2 ? 10 : isdir ? 4 : 8; memmove(de->d_name, name, nlen); memset(de->d_name + nlen, 0, rec - (int)sizeof(*de) - nlen); l->used += rec; diff --git a/kernel/gmmos/gmmos.c b/kernel/gmmos/gmmos.c new file mode 100644 index 0000000..1e66370 --- /dev/null +++ b/kernel/gmmos/gmmos.c @@ -0,0 +1,1049 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// GMMOS -- Guru Meditation Micro OS. See include/gmmos/gmmos.h for what it is. +// +// Four screens, one at a time: +// +// the report PSOGM as panic() drew it, with a live status line -- the +// power-off countdown -- and the keys +// QR pages the last 300 lines of this boot's log, as many QR symbols as +// it takes, one per screen, numbered so a phone can take them +// in order +// the log the same log on screen, scrolled with the arrows +// terminal a handful of commands for looking around a dead machine +// +// Rules this file keeps, because the kernel underneath it is dead: nothing +// that takes a lock, sleeps, allocates, or waits for an interrupt. Every +// buffer is static. Time comes from the TSC, keys from the micro drivers, and +// the only output is the framebuffer, redrawn whole after each key. + +#include "core/types.h" +#include "core/defs.h" +#include "core/param.h" +#include "core/klog.h" +#include "core/sbuf.h" +#include "core/proc.h" +#include "arch/x86.h" +#include "arch/memlayout.h" +#include "arch/mmu.h" +#include "dev/kbd.h" +#include "dev/pci.h" +#include "dev/acpi.h" +#include "gfx/gfx.h" +#include "gfx/theme.h" +#include "gfx/qr.h" +#include "gmmos/gmmos.h" + +extern uint ticks; + +// How long the report stays up on an ordinary boot before the machine powers +// itself off: a crashed laptop otherwise sits with its fans up until somebody +// holds the power button. Any key cancels it -- somebody is there. +#define OFF_MS (3 * 60 * 1000) +#define QR_LINES 300 + +static char *reason; +static addr_t *pcs; +static int npcs; + +static int sw, sh, cw, ch; +// Every micro driver, in the order they are tried. All that answer are +// polled: a laptop has its own keyboard and maybe a USB one on the side. +static const struct gmmos_kbd *const drivers[] = { + &gmmos_ps2, &gmmos_i2c, &gmmos_xhci, +}; +static const struct gmmos_kbd *kbds[NELEM(drivers)]; +static int nkbd; + +static int +poll_keys(void) +{ + for (int i = 0; i < nkbd; i++) { + int k = kbds[i]->poll(); + if (k) + return k; + } + return 0; +} + +static void +set_lights(int on) +{ + for (int i = 0; i < nkbd; i++) + if (kbds[i]->leds) + kbds[i]->leds(on); +} + +enum { MAIN, QRPAGES, LOG, TERM }; +static int mode = MAIN; + +// --- time ------------------------------------------------------------------ + +// Milliseconds since anything: only differences are used. From the TSC when +// it was measured, otherwise from counting the 1 ms waits of the main loop, +// which drifts by however long drawing took and is good enough for a +// countdown. +static uint64 counted_ms; + +static uint64 +now_ms(void) +{ + if (tsc_hz >= 1000) + return rdtsc() / (tsc_hz / 1000); + return counted_ms; +} + +// --- text ------------------------------------------------------------------ + +// A string NUL-terminated at its builder's end, or cut at its capacity. +static char * +sbz(struct sbuf *sb) +{ + sb->p[sb->len < sb->cap ? sb->len : sb->cap - 1] = 0; + return sb->p; +} + +// At most n characters of s, at pixel position x, y. +static void +textn(int x, int y, const char *s, int n, uint32 color) +{ + char buf[256]; + int i; + + if (n > (int)sizeof(buf) - 1) + n = sizeof(buf) - 1; + for (i = 0; i < n && s[i]; i++) + buf[i] = s[i]; + buf[i] = 0; + gfx_text_at(x, y, buf, color); +} + +// The full-screen layout every screen but the report shares: a title, a +// body of rows, and a line of keys at the bottom. +static int mx, my, cols, rows; + +static void +screen(const char *title, const char *keys) +{ + gfx_set_bg(PSOGM_BG); + gfx_clear(PSOGM_BG); + textn(mx, my, title, cols, PSOGM_FG); + gfx_fill_rect(mx, my + ch + ch / 2, 120, 2, SCH_ACCENT); + textn(mx, sh - my - ch, keys, cols, PSOGM_DIM); +} + +// Row r of the body, 0 at the top. +static int +body_y(int r) +{ + return my + ch * 3 + r * ch; +} + +static int +body_rows(void) +{ + return rows - 5; +} + +// --- the log --------------------------------------------------------------- + +// This boot's log, copied out of the ring once on the way in and cleaned up +// for a screen: no escape sequences, no carriage returns, and the timestamps' +// padding squeezed out -- "[ 12.34]" is "[12.34]" -- which matters when +// every byte is a byte of a QR code. +static char logbuf[KLOG_BYTES]; +static int lstart[8192]; +static int nlines; + +static void +log_snapshot(void) +{ + uint64 have = klog_len(), mine = klog_boot_bytes(), off = have - mine; + int partial = 0; + + if (mine > sizeof(logbuf) - 1) { + off = have - (sizeof(logbuf) - 1); + partial = 1; + } + int n = klog_read(off, logbuf, sizeof(logbuf) - 1); + if (n < 0) + n = 0; + + int j = 0; + for (int i = 0; i < n; i++) { + char c = logbuf[i]; + if (c == 0x1B) { // CSI: ESC [ params final + if (i + 1 < n && logbuf[i + 1] == '[') { + i += 2; + while (i < n && !(logbuf[i] >= 0x40 && logbuf[i] <= 0x7E)) + i++; + } + continue; + } + if (c == '\r' || c == 0) + continue; + if (c == ' ' && j > 0 && logbuf[j - 1] == '[') + continue; + logbuf[j++] = c; + } + logbuf[j] = 0; + + // A cut through the middle of a line leaves its tail, which is noise. + int i = 0; + if (partial) + while (i < j && logbuf[i++] != '\n') + ; + nlines = 0; + while (i < j && nlines < (int)NELEM(lstart)) { + lstart[nlines++] = i; + while (i < j && logbuf[i] != '\n') + i++; + logbuf[i++] = 0; + } +} + +static const char * +logline(int i) +{ + return logbuf + lstart[i]; +} + +// --- QR pages -------------------------------------------------------------- + +static struct qr pq; // about 62 KB: static, never on this stack +static char payload[QR_MAX_BYTES + 1]; +static int pfirst[129]; +static int npages, page; +static int density = 1; // 0 large modules, 1 normal, 2 dense + +// Pixels the symbol may take, on its square. +static int +qr_room(void) +{ + int avail = sh - my * 2 - ch * 6; + if (sw - mx * 2 < avail) + avail = sw - mx * 2; + return avail; +} + +// The largest version whose modules still come out at least this many pixels +// wide. Larger modules are easier on a phone camera and hold less; + and - +// trade one for the other. +static int +qr_version(void) +{ + static const int minpx[3] = { 8, 6, 4 }; + for (int v = QR_MAX_VERSION; v > 1; v--) + if (qr_room() / (17 + 4 * v + 8) >= minpx[density]) + return v; + return 1; +} + +// Page header: "SchOS log 3/12\n", and on the first page the panic itself. +static int +page_header(char *buf, int cap, int p) +{ + struct sbuf sb = { buf, 0, cap }; + sb_puts(&sb, "SchOS log "); + sb_putu(&sb, (uint64)(p + 1)); + sb_putc(&sb, '/'); + sb_putu(&sb, (uint64)npages); + sb_putc(&sb, '\n'); + if (p == 0) { + sb_puts(&sb, "panic: "); + sb_puts(&sb, reason); + sb_putc(&sb, '\n'); + } + return sb.len < cap ? sb.len : cap; +} + +static int +page_budget(int p) +{ + char hdr[160]; + int saved = npages; + npages = 999; // the widest the header can get + int h = page_header(hdr, sizeof(hdr), p); + npages = saved; + return qr_capacity(qr_version()) - h; +} + +static int +linelen(int i) +{ + return (int)strlen(logline(i)) + 1; +} + +static void +paginate(void) +{ + int i = nlines > QR_LINES ? nlines - QR_LINES : 0; + + npages = 0; + while (i < nlines && npages < (int)NELEM(pfirst) - 1) { + int budget = page_budget(npages), used = 0; + pfirst[npages++] = i; + while (i < nlines) { + int len = linelen(i); + if (used > 0 && used + len > budget) + break; + used += len; + i++; + } + } + pfirst[npages] = i; + if (page >= npages) + page = npages ? npages - 1 : 0; +} + +static int +build_page(int p) +{ + int cap = qr_capacity(qr_version()); + int n = page_header(payload, cap, p); + + for (int i = pfirst[p]; i < pfirst[p + 1] && n < cap; i++) { + const char *s = logline(i); + while (*s && n < cap) + payload[n++] = *s++; + if (n < cap) + payload[n++] = '\n'; + } + return n; +} + +static void +draw_qr(void) +{ + char title[96]; + struct sbuf sb = { title, 0, sizeof(title) }; + + if (npages == 0) { + screen("The log as QR codes", "Esc the report"); + textn(mx, body_y(0), "This boot's log is empty.", cols, PSOGM_FG); + gfx_flush_all(); + return; + } + + sb_puts(&sb, "The log as QR codes -- page "); + sb_putu(&sb, (uint64)(page + 1)); + sb_puts(&sb, " of "); + sb_putu(&sb, (uint64)npages); + screen(sbz(&sb), + "Space/Right next Left back + - bigger, smaller Esc the report"); + textn(mx, my + ch * 2 + ch / 2, + "The last 300 lines of this boot, oldest first. Scan the pages in order.", + cols, PSOGM_DIM); + + int n = build_page(page); + if (qr_encode(&pq, (const unsigned char *)payload, n) != 0) { + textn(mx, body_y(1), "This page did not encode.", cols, PSOGM_FG); + gfx_flush_all(); + return; + } + + const int quiet = 4; + int cells = pq.size + 2 * quiet; + int scale = qr_room() / cells; + if (scale < 1) + scale = 1; + int card = cells * scale; + int x0 = (sw - card) / 2; + int y0 = my + ch * 4; + + gfx_fill_rect(x0, y0, card, card, PSOGM_CARD); + for (int y = 0; y < pq.size; y++) + for (int x = 0; x < pq.size; x++) + if (pq.mod[y][x]) + gfx_fill_rect(x0 + (x + quiet) * scale, y0 + (y + quiet) * scale, + scale, scale, PSOGM_INK); + gfx_flush_all(); +} + +// --- the log on screen ----------------------------------------------------- + +static int ltop = -1; + +static void +draw_log(void) +{ + char title[96]; + struct sbuf sb = { title, 0, sizeof(title) }; + int r = body_rows(); + + if (ltop < 0 || ltop > nlines - r) + ltop = nlines - r; + if (ltop < 0) + ltop = 0; + + sb_puts(&sb, "The log, this boot -- lines "); + sb_putu(&sb, (uint64)(nlines ? ltop + 1 : 0)); + sb_putc(&sb, '-'); + sb_putu(&sb, (uint64)(ltop + r < nlines ? ltop + r : nlines)); + sb_puts(&sb, " of "); + sb_putu(&sb, (uint64)nlines); + screen(sbz(&sb), "Up Down PgUp PgDn Home End Esc the report"); + for (int i = 0; i < r && ltop + i < nlines; i++) + textn(mx, body_y(i), logline(ltop + i), cols, PSOGM_FG); + gfx_flush_all(); +} + +static void +log_key(int k) +{ + int r = body_rows(); + switch (k) { + case KBD_UP: ltop--; break; + case KBD_DN: ltop++; break; + case KBD_PGUP: ltop -= r - 1; break; + case KBD_PGDN: ltop += r - 1; break; + case KBD_HOME: ltop = 0; break; + case KBD_END: ltop = nlines; break; + } + if (ltop < 0) + ltop = 0; +} + +// --- terminal -------------------------------------------------------------- + +#define TLINES 256 +#define TCOLS 160 + +static char tout[TLINES][TCOLS]; +static int tn; // lines ever written +static char tin[TCOLS], tlast[TCOLS]; +static int tinlen; + +static void +tline(const char *s) +{ + int w = cols < TCOLS - 1 ? cols : TCOLS - 1; + do { + char *d = tout[tn++ % TLINES]; + int i; + for (i = 0; i < w && s[i]; i++) + d[i] = s[i]; + d[i] = 0; + s += i; + } while (*s); +} + +static void +draw_term(void) +{ + screen("GMMOS terminal -- help lists the commands", + "Enter run Up the last command Esc the report"); + + int r = body_rows() - 1; + int first = tn > r ? tn - r : 0; + if (first < tn - TLINES) + first = tn - TLINES; + int row = 0; + for (int i = first; i < tn; i++) + textn(mx, body_y(row++), tout[i % TLINES], cols, PSOGM_FG); + + char line[TCOLS + 16]; + struct sbuf sb = { line, 0, sizeof(line) }; + sb_puts(&sb, "gmmos> "); + sb_puts(&sb, tin); + sb_putc(&sb, '_'); + textn(mx, body_y(row), sbz(&sb), cols, SCH_ACCENT); + gfx_flush_all(); +} + +static int +streq(const char *a, const char *b) +{ + return strncmp(a, b, TCOLS) == 0; +} + +// A number in base, or in hex with 0x before it whatever base says. +static uint64 +parse_num(const char *s, int base, int *ok) +{ + uint64 v = 0; + int any = 0; + + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + base = 16; + s += 2; + } + for (; *s; s++) { + int d; + if (*s >= '0' && *s <= '9') d = *s - '0'; + else if (*s >= 'a' && *s <= 'f') d = *s - 'a' + 10; + else if (*s >= 'A' && *s <= 'F') d = *s - 'A' + 10; + else break; + if (d >= base) + break; + v = v * base + d; + any = 1; + } + *ok = any && *s == 0; + return v; +} + +// Whether va is mapped in the kernel's page tables, walked by hand so that +// reading it cannot fault. A fault in here would be a panic inside GMMOS. +static int +mapped(uint64 va) +{ + uint64 cr3; + + // Non-canonical addresses do not even page-fault; they are a #GP. + if ((uint64)((int64)(va << 16) >> 16) != va) + return 0; + asm volatile("mov %%cr3, %0" : "=r"(cr3)); + uint64 *t = (uint64 *)P2V(PTE_ADDR(cr3)); + for (int level = 3; level >= 0; level--) { + uint64 e = t[(va >> (12 + 9 * level)) & 511]; + if (!(e & PTE_P)) + return 0; + if (level == 0 || (level < 3 && (e & PTE_PS))) + return 1; + t = (uint64 *)P2V(PTE_ADDR(e)); + } + return 0; +} + +static void +cmd_help(void) +{ + tline("help this list"); + tline("why the panic and its call chain"); + tline("log [N] the last N lines of the log (20)"); + tline("grep TEXT lines of the log containing TEXT"); + tline("ps processes"); + tline("cpus processors"); + tline("pci PCI devices"); + tline("mem ADDR [LEN] bytes at a kernel address; both in hex (80, up to 400)"); + tline("uptime how long it ran, and how long since"); + tline("keyboards which keyboards GMMOS is reading"); + tline("clear empty this screen"); + tline("restart restart the machine"); + tline("poweroff power it off"); + tline("exit back to the report, as Esc does"); +} + +static void +cmd_why(void) +{ + char line[160]; + struct sbuf sb = { line, 0, sizeof(line) }; + sb_puts(&sb, "panic: "); + sb_puts(&sb, reason); + tline(sbz(&sb)); + for (int i = 0; i < npcs && pcs[i]; i++) { + sb.len = 0; + sb_puts(&sb, " #"); + sb_putu(&sb, (uint64)i); + sb_puts(&sb, " "); + sb_puthex(&sb, (uint64)pcs[i], 16); + if (ksym_ready() && ksym_lookup((uint64)pcs[i], 0)) { + char name[64]; + ksym_fmt(name, sizeof(name), (uint64)pcs[i]); + sb_puts(&sb, " "); + sb_puts(&sb, name); + } + tline(sbz(&sb)); + } +} + +static void +cmd_log(const char *arg) +{ + int ok, n = 20; + if (*arg) { + n = (int)parse_num(arg, 10, &ok); + if (!ok || n <= 0) { + tline("log: N is a number of lines"); + return; + } + } + for (int i = nlines > n ? nlines - n : 0; i < nlines; i++) + tline(logline(i)); +} + +static int +contains(const char *s, const char *pat) +{ + int m = (int)strlen(pat); + for (; *s; s++) + if (strncmp(s, pat, m) == 0) + return 1; + return 0; +} + +static void +cmd_grep(const char *arg) +{ + int found = 0; + if (!*arg) { + tline("grep: what to look for?"); + return; + } + for (int i = 0; i < nlines; i++) + if (contains(logline(i), arg)) { + tline(logline(i)); + found++; + } + if (!found) + tline("(nothing)"); +} + +static void +cmd_ps(void) +{ + static const char *const st[] = { + "unused", "embryo", "sleeping", "runnable", "running", "zombie", + }; + struct procinfo pi; + char line[96]; + struct sbuf sb = { line, 0, sizeof(line) }; + + tline(" PID PPID STATE NAME"); + for (int i = 0; proc_peek(i, &pi) == 0; i++) { + sb.len = 0; + sb_putu_pad(&sb, (uint64)pi.pid, 5); + sb_putu_pad(&sb, (uint64)pi.ppid, 6); + sb_puts(&sb, " "); + sb_pad(&sb, pi.state >= 0 && pi.state < (int)NELEM(st) ? st[pi.state] : "?", 10); + sb_puts(&sb, pi.name); + if (pi.killed) + sb_puts(&sb, " (killed)"); + tline(sbz(&sb)); + } +} + +static void +cmd_cpus(void) +{ + char line[96]; + struct sbuf sb = { line, 0, sizeof(line) }; + int me = cpunum(); + + for (int i = 0; i < ncpu; i++) { + sb.len = 0; + sb_puts(&sb, "cpu"); + sb_putu(&sb, (uint64)i); + sb_puts(&sb, " apic "); + sb_putu(&sb, (uint64)cpus[i].apicid); + sb_puts(&sb, i == me ? " panicked; runs GMMOS" + : cpus[i].started ? " stopped by the panic" + : " never started"); + tline(sbz(&sb)); + } +} + +static void +cmd_pci(void) +{ + char line[160], desc[64]; + struct sbuf sb = { line, 0, sizeof(line) }; + + for (int i = 0; i < pci_count(); i++) { + struct pci_dev *d = pci_get(i); + sb.len = 0; + sb_puthex(&sb, d->bus, 2); + sb_putc(&sb, ':'); + sb_puthex(&sb, d->slot, 2); + sb_putc(&sb, '.'); + sb_putu(&sb, d->func); + sb_puts(&sb, " "); + sb_puthex4(&sb, d->vendor); + sb_putc(&sb, ':'); + sb_puthex4(&sb, d->device); + sb_puts(&sb, " "); + pci_describe(d, desc, sizeof(desc)); + sb_pad(&sb, desc, 28); + sb_puts(&sb, d->driver ? d->driver : "-"); + tline(sbz(&sb)); + } + if (pci_count() == 0) + tline("(none found)"); +} + +static void +cmd_mem(const char *arg) +{ + char a[TCOLS], *b; + int ok, ok2 = 1; + + safestrcpy(a, arg, sizeof(a)); + for (b = a; *b && *b != ' '; b++) + ; + if (*b) + *b++ = 0; + uint64 va = parse_num(a, 16, &ok); + uint64 len = *b ? parse_num(b, 16, &ok2) : 0x80; + if (!ok || !ok2 || len == 0) { + tline("mem: mem ADDR [LEN], both in hex"); + return; + } + if (len > 0x400) + len = 0x400; + + char line[128]; + struct sbuf sb = { line, 0, sizeof(line) }; + for (uint64 row = va & ~15ull; row < va + len; row += 16) { + if (!mapped(row)) { + sb.len = 0; + sb_puthex(&sb, row, 16); + sb_puts(&sb, " not mapped"); + tline(sbz(&sb)); + row = (row | (PGSIZE - 1)) - 15; // on to the next page + continue; + } + volatile uchar *p = (volatile uchar *)row; + char asc[17]; + sb.len = 0; + sb_puthex(&sb, row, 16); + sb_puts(&sb, " "); + for (int i = 0; i < 16; i++) { + uchar c = p[i]; + sb_putc(&sb, ' '); + sb_puthex(&sb, c, 2); + asc[i] = c >= 0x20 && c < 0x7F ? (char)c : '.'; + } + asc[16] = 0; + sb_puts(&sb, " "); + sb_puts(&sb, asc); + tline(sbz(&sb)); + } +} + +static uint64 entered_ms; + +static void +cmd_uptime(void) +{ + char line[96]; + struct sbuf sb = { line, 0, sizeof(line) }; + sb_puts(&sb, "ran for "); + sb_putu(&sb, (uint64)ticks / HZ); + sb_puts(&sb, " s; dead for "); + sb_putu(&sb, (now_ms() - entered_ms) / 1000); + sb_puts(&sb, " s"); + tline(sbz(&sb)); +} + +static void +restart(void) +{ + // The chipset's reset port and the keyboard controller first: neither runs + // anything of the dead kernel. ACPI's reset register after them, then a + // triple fault, which resets every PC there is. + outb(0xCF9, 0x02); + microdelay(50); + outb(0xCF9, 0x06); + microdelay(100000); + outb(0x64, 0xFE); + microdelay(100000); + acpi_reboot(); + microdelay(100000); + struct { ushort limit; uint64 base; } __attribute__((packed)) none = { 0, 0 }; + asm volatile("lidt %0; int3" : : "m"(none)); + for (;;) + hlt(); +} + +static void enter(int m); + +static void +run(void) +{ + char cmd[TCOLS]; + const char *arg; + char line[TCOLS + 8]; + struct sbuf sb = { line, 0, sizeof(line) }; + + sb_puts(&sb, "gmmos> "); + sb_puts(&sb, tin); + tline(sbz(&sb)); + + safestrcpy(cmd, tin, sizeof(cmd)); + if (tinlen) + safestrcpy(tlast, tin, sizeof(tlast)); + tin[0] = 0; + tinlen = 0; + + char *p = cmd; + while (*p == ' ') + p++; + char *w = p; + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + while (*p == ' ') + p++; + arg = p; + + if (!*w) return; + else if (streq(w, "help")) cmd_help(); + else if (streq(w, "why")) cmd_why(); + else if (streq(w, "log")) cmd_log(arg); + else if (streq(w, "grep")) cmd_grep(arg); + else if (streq(w, "ps")) cmd_ps(); + else if (streq(w, "cpus")) cmd_cpus(); + else if (streq(w, "pci")) cmd_pci(); + else if (streq(w, "mem")) cmd_mem(arg); + else if (streq(w, "uptime")) cmd_uptime(); + else if (streq(w, "keyboards")) { + for (int i = 0; i < nkbd; i++) + tline(kbds[i]->name); + } + else if (streq(w, "clear")) tn = 0; + else if (streq(w, "restart") || streq(w, "reboot")) restart(); + else if (streq(w, "poweroff")) { + acpi_poweroff_now(); + tline("poweroff: the firmware did not power off"); + } + else if (streq(w, "exit")) enter(MAIN); + else { + sb.len = 0; + sb_puts(&sb, w); + sb_puts(&sb, ": no such command; help lists them"); + tline(sbz(&sb)); + } +} + +static void +term_key(int k) +{ + if (k == '\n') { + run(); + } else if (k == '\b' || k == 0x7F) { + if (tinlen) + tin[--tinlen] = 0; + } else if (k == 0x03) { // Ctrl-C: drop the line + tin[0] = 0; + tinlen = 0; + } else if (k == 0x0C) { // Ctrl-L + tn = 0; + } else if (k == KBD_UP) { + safestrcpy(tin, tlast, sizeof(tin)); + tinlen = (int)strlen(tin); + } else if (k >= 0x20 && k < 0x7F && tinlen < TCOLS - 1 && tinlen < cols - 10) { + tin[tinlen++] = (char)k; + tin[tinlen] = 0; + } +} + +// --- the report ------------------------------------------------------------ + +static int cancelled; // a key was pressed: no power-off +static int pending; // 'r' or 'p', waiting for a second press +static uint64 pending_until; + +static void +draw_status(void) +{ + int x, y, step; + char line[96]; + struct sbuf sb = { line, 0, sizeof(line) }; + + psogm_footer(&x, &y, &step); + gfx_set_bg(PSOGM_BG); + + if (pending == 'r') { + sb_puts(&sb, "Press R again to restart."); + } else if (pending == 'p') { + sb_puts(&sb, "Press P again to power off."); + } else if (!boot_verbose && !cancelled) { + uint64 left = (OFF_MS - (now_ms() - entered_ms)) / 1000; + if ((int64)left < 0) + left = 0; + sb_puts(&sb, "Powers off in "); + sb_putu(&sb, left / 60); + sb_putc(&sb, ':'); + sb_putc(&sb, (char)('0' + left % 60 / 10)); + sb_putc(&sb, (char)('0' + left % 10)); + sb_puts(&sb, nkbd ? ". Any key keeps it on." : "."); + } else { + sb_puts(&sb, "Staying on. Nothing here is lost until it restarts."); + } + gfx_fill_rect(x, y + step, sw * 2 / 3 - x, ch + 2, PSOGM_BG); + textn(x, y + step, sbz(&sb), (sw * 2 / 3 - x) / cw, PSOGM_DIM); + + gfx_fill_rect(x, y + step * 3, sw - x, ch + 2, PSOGM_BG); + if (nkbd) + textn(x, y + step * 3, + "Space log as QR L read the log T terminal R restart P power off", + (sw - x) / cw, PSOGM_FG); + else + textn(x, y + step * 3, + "No keyboard GMMOS can read on this machine yet.", + (sw - x) / cw, PSOGM_FG); + gfx_flush_all(); +} + +static void +main_key(int k) +{ + if (k == 0x1B) { + pending = 0; + } else if (k == 'r' || k == 'R' || k == 'p' || k == 'P') { + int want = k | 0x20; + if (pending == want) { + if (want == 'r') + restart(); + acpi_poweroff_now(); + pending = 0; + } else { + pending = want; + pending_until = now_ms() + 3000; + } + } else if (k == ' ') { + enter(QRPAGES); + return; + } else if (k == 'l' || k == 'L') { + enter(LOG); + return; + } else if (k == 't' || k == 'T') { + enter(TERM); + return; + } + draw_status(); +} + +// --- screens --------------------------------------------------------------- + +static void +redraw(void) +{ + switch (mode) { + case MAIN: psogm_repaint(); draw_status(); break; + case QRPAGES: draw_qr(); break; + case LOG: draw_log(); break; + case TERM: draw_term(); break; + } +} + +static void +enter(int m) +{ + mode = m; + pending = 0; + if (m == QRPAGES) + paginate(); + if (m == LOG) + ltop = -1; + redraw(); +} + +static void +qr_key(int k) +{ + if (k == ' ' || k == KBD_RT || k == KBD_PGDN || k == '\n') { + if (page + 1 < npages) + page++; + } else if (k == KBD_LF || k == KBD_PGUP || k == '\b') { + if (page > 0) + page--; + } else if (k == KBD_HOME) { + page = 0; + } else if (k == KBD_END) { + page = npages ? npages - 1 : 0; + } else if ((k == '+' || k == '=') && density > 0) { + density--; + page = 0; + paginate(); + } else if (k == '-' && density < 2) { + density++; + page = 0; + paginate(); + } else { + return; + } + draw_qr(); +} + +static void +key(int k) +{ + if (mode != MAIN && (k == 0x1B || (mode != TERM && (k == 'q' || k == 'Q')))) { + enter(MAIN); + return; + } + switch (mode) { + case MAIN: main_key(k); break; + case QRPAGES: qr_key(k); break; + case LOG: log_key(k); draw_log(); break; + case TERM: term_key(k); if (mode == TERM) draw_term(); break; + } +} + +void +gmmos_run(char *r, addr_t *p, int n) +{ + reason = r; + pcs = p; + npcs = n; + entered_ms = now_ms(); + + gfx_dims(&sw, &sh); + cw = gfx_char_w(); + ch = gfx_char_h(); + mx = cw * 3; + my = ch; + cols = (sw - mx * 2) / cw; + rows = (sh - my * 2) / ch; + + log_snapshot(); + for (int i = 0; i < (int)NELEM(drivers); i++) + if (drivers[i]->probe()) + kbds[nkbd++] = drivers[i]; + tline("GMMOS, the Guru Meditation Micro OS. The kernel is dead; this is what"); + tline("runs after it. help lists the commands."); + draw_status(); + + uint64 last_status = now_ms(), last_blink = now_ms(); + int lights = 0, keyed = 0; + + for (;;) { + int k = poll_keys(); + if (k) { + if (!keyed) { + set_lights(0); // somebody is here: the lights have done their job + lights = 0; + } + keyed = 1; + int was = cancelled; + cancelled = 1; + // The key that cancels the countdown does only that, on the report: + // whoever pressed it may not have read the keys yet. + if (!was && mode == MAIN && !boot_verbose) + draw_status(); + else + key(k); + continue; + } + + uint64 t = now_ms(); + if (pending && t >= pending_until) { + pending = 0; + if (mode == MAIN) + draw_status(); + } + if (mode == MAIN && t - last_status >= 1000) { + last_status = t; + draw_status(); + } + // The lights until somebody presses a key, as panic_blink did: on a + // machine whose screen is black, they are all there is to say it died. + if (!keyed && t - last_blink >= 333) { + last_blink = t; + lights = !lights; + set_lights(lights); + } + if (!boot_verbose && !cancelled && t - entered_ms >= OFF_MS) { + acpi_poweroff_now(); + cancelled = 1; // still on: stay up rather than retry for ever + draw_status(); + } + + microdelay(1000); + counted_ms++; + } +} diff --git a/kernel/gmmos/hidkeys.c b/kernel/gmmos/hidkeys.c new file mode 100644 index 0000000..e924f9e --- /dev/null +++ b/kernel/gmmos/hidkeys.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Boot-layout keyboard reports to keys, for GMMOS's USB and I2C keyboards. +// +// usb_hid.c's keyboard path with the console, /dev/input, the lock and key +// repeat taken out: what is left is edge detection and a table. US layout, as +// the live USB path is. + +#include "core/types.h" +#include "core/defs.h" +#include "dev/kbd.h" +#include "gmmos/gmmos.h" + +#define MOD_CTRL 0x11 +#define MOD_SHIFT 0x22 + +static const uchar plain[0x39] = { + 0, 0, 0, 0, + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z', + '1','2','3','4','5','6','7','8','9','0', + '\n', 0x1B, '\b', '\t', ' ', + '-','=','[',']','\\','#',';','\'','`',',','.','/', +}; + +static const uchar shifted[0x39] = { + 0, 0, 0, 0, + 'A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + '!','@','#','$','%','^','&','*','(',')', + '\n', 0x1B, '\b', '\t', ' ', + '_','+','{','}','|','~',':','"','~','<','>','?', +}; + +static int +special(uchar u) +{ + switch (u) { + case 0x4A: return KBD_HOME; + case 0x4B: return KBD_PGUP; + case 0x4C: return KBD_DEL; + case 0x4D: return KBD_END; + case 0x4E: return KBD_PGDN; + case 0x4F: return KBD_RT; + case 0x50: return KBD_LF; + case 0x51: return KBD_DN; + case 0x52: return KBD_UP; + case 0x56: return '-'; // keypad minus and plus: the QR size keys + case 0x57: return '+'; + case 0x58: return '\n'; // keypad Enter + } + return 0; +} + +static int +was_down(const uchar *rep, uchar u) +{ + for (int i = 2; i < 8; i++) + if (rep[i] == u) + return 1; + return 0; +} + +void +gmmos_hid_report(struct gmmos_hidkeys *k, const uchar *boot) +{ + uchar mods = boot[0]; + + for (int i = 2; i < 8; i++) { + uchar u = boot[i]; + int c; + + if (u < 4 || was_down(k->prev, u)) + continue; + if (u < 0x39) + c = (mods & MOD_SHIFT) ? shifted[u] : plain[u]; + else + c = special(u); + if (c == 0) + continue; + if ((mods & MOD_CTRL) && c < KBD_SPECIAL) { + if (c >= 'a' && c <= 'z') c = c - 'a' + 1; + else if (c >= 'A' && c <= 'Z') c = c - 'A' + 1; + else continue; + } + if (k->qt - k->qh < (int)NELEM(k->q)) + k->q[k->qt++ % NELEM(k->q)] = c; + } + memmove(k->prev, boot, 8); +} + +int +gmmos_hid_next(struct gmmos_hidkeys *k) +{ + if (k->qh == k->qt) + return 0; + return k->q[k->qh++ % NELEM(k->q)]; +} diff --git a/kernel/gmmos/i2c.c b/kernel/gmmos/i2c.c new file mode 100644 index 0000000..82f11e4 --- /dev/null +++ b/kernel/gmmos/i2c.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// GMMOS's I2C keyboards: kernel/acpi/i2c_hid.c with everything but reading a +// keyboard's input reports taken out. +// +// No ACPI -- the firmware's AML cannot run on a dead kernel, so the devices +// are the ones the live driver found at boot (i2c_hid_keyboards). No power +// commands, no reset, no report descriptor: the device is already on and was +// already described. No interrupt: the pin is read when the device has one, +// otherwise the input register is read every few milliseconds regardless. +// And the DesignWare transfer is read-only, since an input report is a plain +// read with no register write before it. + +#include "core/types.h" +#include "core/defs.h" +#include "dev/hid.h" +#include "gmmos/gmmos.h" + +#define IC_TAR 0x04 +#define IC_DATA_CMD 0x10 +#define IC_RAW_INTR_STAT 0x34 +#define IC_CLR_INTR 0x40 +#define IC_CLR_TX_ABRT 0x54 +#define IC_CLR_STOP_DET 0x60 +#define IC_ENABLE 0x6C +#define IC_TXFLR 0x74 +#define IC_RXFLR 0x78 +#define IC_ENABLE_STATUS 0x9C + +#define CMD_READ (1 << 8) +#define CMD_STOP (1 << 9) +#define INTR_TX_ABRT (1 << 6) +#define INTR_STOP_DET (1 << 9) + +#define AMD_PIN_STS (1u << 16) + +#define NKBD 4 + +static struct gmmos_i2c_kbd kb[NKBD]; +static int nkb, turn; +static uchar buf[4096]; +static struct gmmos_hidkeys keys; + +static uint32 +rd(struct gmmos_i2c_kbd *d, int reg) +{ + return d->regs[reg / 4]; +} + +static void +wr(struct gmmos_i2c_kbd *d, int reg, uint32 v) +{ + d->regs[reg / 4] = v; +} + +static int +enable(struct gmmos_i2c_kbd *d, int on) +{ + for (int i = 0; i < 1000; i++) { + wr(d, IC_ENABLE, on); + if ((rd(d, IC_ENABLE_STATUS) & 1) == (uint32)on) + return 0; + microdelay(25); + } + return -1; +} + +// n bytes from the device. Disabling the controller first also abandons +// whatever transfer the live driver had going when the kernel died. +static int +read_bytes(struct gmmos_i2c_kbd *d, uchar *r, int n) +{ + int sent = 0, got = 0, spin; + + if (enable(d, 0) < 0) + return -1; + wr(d, IC_TAR, d->addr); + rd(d, IC_CLR_INTR); + if (enable(d, 1) < 0) + return -1; + + for (spin = 0; got < n; spin++) { + if (rd(d, IC_RAW_INTR_STAT) & INTR_TX_ABRT) + goto abort; + while (got < n && rd(d, IC_RXFLR) > 0) + r[got++] = rd(d, IC_DATA_CMD); + while (sent < n && (int)rd(d, IC_TXFLR) < d->txdepth && + sent - got < d->rxdepth) { + wr(d, IC_DATA_CMD, CMD_READ | (sent == n - 1 ? CMD_STOP : 0)); + sent++; + } + if (spin > 20000) + goto abort; + microdelay(10); + } + for (spin = 0; !(rd(d, IC_RAW_INTR_STAT) & INTR_STOP_DET); spin++) { + if (spin > 2000) + goto abort; + microdelay(10); + } + rd(d, IC_CLR_STOP_DET); + enable(d, 0); + return 0; + +abort: + rd(d, IC_CLR_TX_ABRT); + rd(d, IC_CLR_INTR); + enable(d, 0); + return -1; +} + +static int +probe(void) +{ + nkb = i2c_hid_keyboards(kb, NKBD); + for (int i = 0; i < nkb; i++) + if (kb[i].maxin > sizeof(buf)) + kb[i].maxin = sizeof(buf); + return nkb > 0; +} + +static int +poll(void) +{ + int k = gmmos_hid_next(&keys); + if (k) + return k; + + // GMMOS polls every millisecond; a blind read is a whole report's worth of + // bus time, so a device with no pin is read every fourth time. + for (int i = 0; i < nkb; i++) { + struct gmmos_i2c_kbd *d = &kb[i]; + if (d->pin) { + int high = (*d->pin & AMD_PIN_STS) != 0; + if (high == d->active_low) + continue; + } else if (turn++ % 4) { + continue; + } + if (read_bytes(d, buf, d->maxin) < 0) + continue; + int len = buf[0] | (buf[1] << 8); + uchar boot[8]; + if (len > 2 && len <= (int)d->maxin && + hid_boot_report(d->hid, buf + 2, len - 2, boot)) + gmmos_hid_report(&keys, boot); + } + return gmmos_hid_next(&keys); +} + +const struct gmmos_kbd gmmos_i2c = { + .name = "I2C", + .probe = probe, + .poll = poll, +}; diff --git a/kernel/gmmos/ps2.c b/kernel/gmmos/ps2.c new file mode 100644 index 0000000..0af08f0 --- /dev/null +++ b/kernel/gmmos/ps2.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// GMMOS's PS/2 keyboard: kernel/dev/kbd.c with everything but the keys taken +// out. +// +// No interrupt: the status port is polled. That also makes it immune to the +// IRQ 1 polarity quirk of some AMD laptops -- the ASUS TUF among them -- which +// is about how the interrupt is routed, and nothing here is routed. No +// /dev/input, no grab, no terminal chords. Scancode set 1, as the controller +// translates it, through the same keymap the console uses. + +#include "core/types.h" +#include "arch/x86.h" +#include "core/defs.h" +#include "dev/kbd.h" +#include "dev/keymap.h" +#include "gmmos/gmmos.h" + +// kbd.c's tables, which are static there. Hardware facts, so they are short. +static const uchar shiftcode[256] = { + [0x1D] = CTL, [0x2A] = SHIFT, [0x36] = SHIFT, [0x38] = ALT, + [0x9D] = CTL, [0xB8] = RALT, +}; +static const uchar togglecode[256] = { + [0x3A] = CAPSLOCK, [0x45] = NUMLOCK, [0x46] = SCROLLLOCK, +}; + +static uint shift; +static int e0, e1skip; + +// Bytes that arrived while a command was waiting for its answer. Setting the +// lights is a command, and a key pressed during it would otherwise be eaten. +static uchar held[16]; +static int nheld, iheld; + +// A byte from the controller, or -1. A byte from the mouse is read too -- left +// in the buffer, it blocks every keyboard byte behind it -- and dropped. +static int +rd(void) +{ + uint st = inb(KBSTATP); + if (!(st & KBS_DIB)) + return -1; + uchar d = inb(KBDATAP); + if (st & 0x20) + return -1; + return d; +} + +static void +wr(uchar b) +{ + for (int i = 0; i < 10000; i++) { + if (!(inb(KBSTATP) & 0x02)) { + outb(KBDATAP, b); + return; + } + microdelay(10); + } +} + +// Wait up to 20 ms for the keyboard's answer to a command, keeping anything +// else that arrives first. +static void +answer(void) +{ + for (int i = 0; i < 2000; i++) { + int d = rd(); + if (d < 0) { + microdelay(10); + continue; + } + if (d == 0xFA || d == 0xFE) + return; + if (nheld < (int)sizeof(held)) + held[nheld++] = (uchar)d; + } +} + +static int +probe(void) +{ + // No controller decodes the port: every bit reads as one. + if (inb(KBSTATP) == 0xFF) + return 0; + for (int i = 0; i < 64 && (inb(KBSTATP) & KBS_DIB); i++) + (void)inb(KBDATAP); + shift = 0; + e0 = e1skip = 0; + + // A controller with no keyboard behind it -- a desktop with only USB ones + // -- must not be offered as one: GMMOS would list keys that do nothing. So + // ask the keyboard itself to echo, and wait 50 ms for it to. + wr(0xEE); + for (int i = 0; i < 5000; i++) { + int d = rd(); + if (d == 0xEE) + return 1; + if (d < 0) + microdelay(10); + } + return 0; +} + +static void +leds(int on) +{ + wr(0xED); + answer(); + wr(on ? 0x07 : 0x00); + answer(); +} + +static int +poll(void) +{ + int d; + + if (iheld < nheld) { + d = held[iheld++]; + } else { + nheld = iheld = 0; + d = rd(); + } + if (d < 0) + return 0; + + // Answers and self-test results, not keys. + if (d == 0xFA || d == 0xFE || d == 0xAA || d == 0xEE || d == 0x00 || d == 0xFF) + return 0; + // Pause is E1 1D 45 E1 9D C5, and means nothing here. Read as keys, it + // would be a Ctrl press and two Num Lock toggles. + if (e1skip) { + e1skip--; + return 0; + } + if (d == 0xE1) { + e1skip = 5; + return 0; + } + if (d == 0xE0) { + e0 = 1; + return 0; + } + + // As in kbdgetc: a release clears its modifier, a press with E0 before it + // is looked up with the high bit set. + if (d & 0x80) { + d = e0 ? d : (d & 0x7F); + shift &= ~shiftcode[d]; + e0 = 0; + return 0; + } + if (e0) { + d |= 0x80; + e0 = 0; + } + shift |= shiftcode[d]; + shift ^= togglecode[d]; + + return (int)keymap_lookup((uint)d, shift); +} + +const struct gmmos_kbd gmmos_ps2 = { + .name = "PS/2", + .probe = probe, + .poll = poll, + .leds = leds, +}; diff --git a/kernel/gmmos/xhci.c b/kernel/gmmos/xhci.c new file mode 100644 index 0000000..2900b90 --- /dev/null +++ b/kernel/gmmos/xhci.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// GMMOS's USB keyboards on xHCI: kernel/drivers/bus/xhci/xhci.c with +// everything but finding a keyboard and reading it taken out. +// +// It does not take over the live driver's state. The kernel may have died +// inside that driver, mid-command, with its rings half-written; so each +// controller is reset and enumerated from nothing, and of the live driver only +// two things are used: where the registers are mapped, and the scratchpad +// pages the controller was already lent (DCBAA entry 0 survives in memory). +// +// What is left, in order: +// +// - halt, reset, a command ring, an event ring, run +// - every connected root port: reset, Enable Slot, Address Device, the +// device descriptor, the configuration +// - a USB 2 hub, up to two deep: power its ports, reset each connected one, +// and enumerate what is behind it +// - a boot-protocol keyboard interface: Set Configuration, Set Protocol +// (boot), Set Idle, Configure Endpoint for its interrupt IN endpoint, and +// a Normal TRB queued on it +// +// and then the event ring is polled and every completed report re-queued. No +// interrupts, no hot-plug, no mice, no storage, no USB 3 hubs (a keyboard is +// never behind one's SuperSpeed half). Memory is a static pool: the DMA +// allocator belongs to the dead kernel. + +#include "core/types.h" +#include "core/defs.h" +#include "arch/memlayout.h" +#include "dev/usb.h" +#include "gmmos/gmmos.h" +#include "../drivers/bus/xhci/xhci.h" + +#define NHC 4 +#define NKBD 4 +#define NTRB 64 // per ring, link TRB included +#define MAXSLOT 32 + +// --- memory ---------------------------------------------------------------- + +static uchar pool[256 * 1024] __attribute__((aligned(4096))); +static uint64 used; + +static void * +palloc(uint64 size, uint64 align) +{ + used = (used + align - 1) & ~(align - 1); + if (used + size > sizeof(pool)) + return 0; + void *p = pool + used; + used += size; + memset(p, 0, size); + return p; +} + +static addr_t +phys(volatile void *p) +{ + return V2P((addr_t)p); +} + +// --- one controller -------------------------------------------------------- + +struct ring { + volatile struct trb *trb; + int enq, cycle; +}; + +struct hc { + volatile uchar *cap, *op, *rt; + volatile uint32 *db; + int ctxsize, maxslots, maxports; + uint64 *dcbaa; + struct ring cmd; + volatile struct trb *ev; + int evdeq, evcycle; + struct ring ep0[MAXSLOT + 1]; +}; + +struct kbd { + struct hc *hc; + int slot, dci; + struct ring ring; + uchar *buf; + int len; +}; + +static struct hc hcs[NHC]; +static struct kbd kbds[NKBD]; +static int nkbds; +static struct gmmos_hidkeys keys; + +static inline uint32 rd32(volatile uchar *p, int o) { return *(volatile uint32 *)(p + o); } +static inline void wr32(volatile uchar *p, int o, uint32 v) { *(volatile uint32 *)(p + o) = v; } +static inline void wr64(volatile uchar *p, int o, uint64 v) +{ + *(volatile uint32 *)(p + o) = (uint32)v; + *(volatile uint32 *)(p + o + 4) = (uint32)(v >> 32); +} + +static int +ring_init(struct ring *r) +{ + r->trb = palloc(NTRB * 16, 4096); + if (r->trb == 0) + return -1; + r->enq = 0; + r->cycle = 1; + r->trb[NTRB - 1].param = phys(r->trb); + r->trb[NTRB - 1].control = TRB_TYPE(TRB_LINK) | TRB_TC; + return 0; +} + +static void +push(struct ring *r, uint64 param, uint32 status, uint32 control) +{ + volatile struct trb *t = &r->trb[r->enq]; + t->param = param; + t->status = status; + __sync_synchronize(); + t->control = (control & ~TRB_C) | (r->cycle ? TRB_C : 0); + __sync_synchronize(); + if (++r->enq == NTRB - 1) { + r->trb[NTRB - 1].control = TRB_TYPE(TRB_LINK) | TRB_TC | (r->cycle ? TRB_C : 0); + r->enq = 0; + r->cycle ^= 1; + } +} + +static int +pop(struct hc *h, struct trb *out) +{ + volatile struct trb *e = &h->ev[h->evdeq]; + uint32 c = e->control; + if (((c & TRB_C) ? 1 : 0) != h->evcycle) + return 0; + __sync_synchronize(); + out->param = e->param; + out->status = e->status; + out->control = c; + if (++h->evdeq == NTRB) { + h->evdeq = 0; + h->evcycle ^= 1; + } + wr64(h->rt, XHCI_IR0 + IR_ERDP, (phys(h->ev) + h->evdeq * 16) | ERDP_EHB); + return 1; +} + +static void report(struct hc *h, struct trb *ev); + +// The next event of this type, within ms -- for a transfer event, the one +// for EP0 of this slot. Keyboard reports that arrive meanwhile, a key pressed +// while a second keyboard is enumerated, are handled rather than lost. +static int +wait_event(struct hc *h, int type, int slot, struct trb *out, int ms) +{ + for (int i = 0; i < ms * 100; i++) { + while (pop(h, out)) { + int t = TRB_GET_TYPE(out->control); + if (t == TRB_XFER_EVENT && (type != t || TRB_GET_SLOT(out->control) != slot || + TRB_GET_EPID(out->control) != DCI_EP0)) { + report(h, out); + continue; + } + if (t == type) + return 0; + } + microdelay(10); + } + return -1; +} + +static int +command(struct hc *h, uint64 param, uint32 control, struct trb *ev) +{ + struct trb e; + push(&h->cmd, param, 0, control); + h->db[0] = 0; + if (wait_event(h, TRB_CMD_COMPLETE, 0, &e, 500) < 0) + return -1; + if (ev) + *ev = e; + return TRB_CC(e.status); +} + +static uint32 * +ctx(struct hc *h, void *base, int i) +{ + return (uint32 *)((uchar *)base + i * h->ctxsize); +} + +// --- devices --------------------------------------------------------------- + +struct dev { + int slot, port, speed, route, tier; + int tt_slot, tt_port; + void *devctx; +}; + +static uchar dbuf[512] __attribute__((aligned(64))); + +static int +control(struct hc *h, int slot, int type, int req, int value, int index, + void *buf, int len) +{ + struct usb_setup s = { type, req, value, index, len }; + struct ring *r = &h->ep0[slot]; + int in = (type & 0x80) != 0; + struct trb ev; + + push(r, *(uint64 *)&s, 8, TRB_TYPE(TRB_SETUP) | TRB_IDT | + ((uint32)(len ? (in ? TRT_IN : TRT_OUT) : TRT_NONE) << 16)); + if (len) + push(r, phys(buf), len, TRB_TYPE(TRB_DATA) | (in ? DIR_IN : 0)); + push(r, 0, 0, TRB_TYPE(TRB_STATUS) | TRB_IOC | ((len == 0 || !in) ? DIR_IN : 0)); + h->db[slot] = DCI_EP0; + if (wait_event(h, TRB_XFER_EVENT, slot, &ev, 500) < 0) + return -1; + int cc = TRB_CC(ev.status); + return cc == CC_SUCCESS || cc == CC_SHORT_PACKET ? 0 : -1; +} + +static int +mps0(int speed) +{ + return speed == XSPEED_HIGH ? 64 : speed == XSPEED_SUPER ? 512 : 8; +} + +// Enable Slot and Address Device: the device answers on its own address with +// EP0 working at the default packet size. +static int +address(struct hc *h, struct dev *d) +{ + struct trb ev; + if (command(h, 0, TRB_TYPE(TRB_ENABLE_SLOT), &ev) != CC_SUCCESS) + return -1; + d->slot = TRB_GET_SLOT(ev.control); + if (d->slot <= 0 || d->slot > MAXSLOT || d->slot > h->maxslots) + return -1; + + d->devctx = palloc(h->ctxsize * 32, 64); + void *in = palloc(h->ctxsize * 33, 64); + if (!d->devctx || !in || ring_init(&h->ep0[d->slot]) < 0) + return -1; + h->dcbaa[d->slot] = phys(d->devctx); + + uint32 *c = ctx(h, in, 0), *sc = ctx(h, in, 1), *ep0 = ctx(h, in, 2); + c[1] = 0x3; + sc[0] = (d->route & 0xFFFFF) | ((uint32)d->speed << 20) | (1u << 27); + sc[1] = (uint32)d->port << 16; + sc[2] = d->tt_slot ? ((uint32)d->tt_slot | ((uint32)d->tt_port << 8)) : 0; + ep0[1] = (EPTYPE_CONTROL << 3) | (3 << 1) | ((uint32)mps0(d->speed) << 16); + *(uint64 *)&ep0[2] = phys(h->ep0[d->slot].trb) | 1; + ep0[4] = 8; + if (command(h, phys(in), TRB_TYPE(TRB_ADDRESS_DEV) | TRB_SLOT(d->slot), 0) != CC_SUCCESS) + return -1; + + // A full-speed device's real EP0 packet size is in byte 7 of its device + // descriptor, and the first eight bytes can be read at any size. + if (control(h, d->slot, 0x80, 6, 0x0100, 0, dbuf, 8) < 0) + return -1; + if (d->speed == XSPEED_FULL && dbuf[7] && dbuf[7] != 8) { + memset(in, 0, h->ctxsize * 3); + c[1] = 0x2; + ep0[1] = (EPTYPE_CONTROL << 3) | (3 << 1) | ((uint32)dbuf[7] << 16); + *(uint64 *)&ep0[2] = phys(h->ep0[d->slot].trb) | h->ep0[d->slot].cycle; + ep0[4] = 8; + command(h, phys(in), TRB_TYPE(TRB_EVAL_CTX) | TRB_SLOT(d->slot), 0); + } + return 0; +} + +// Configure Endpoint with only the slot context: how a slot is told it is a +// hub, and how many ports it has. Before anything behind it is addressed. +static int +make_hub(struct hc *h, struct dev *d, int nports, int mtt) +{ + void *in = palloc(h->ctxsize * 3, 64); + if (!in) + return -1; + uint32 *c = ctx(h, in, 0), *sc = ctx(h, in, 1); + c[1] = 1; + sc[0] = (d->route & 0xFFFFF) | ((uint32)d->speed << 20) | (1u << 27) | + (1u << 26) | (mtt ? (1u << 25) : 0); + sc[1] = ((uint32)d->port << 16) | ((uint32)nports << 24); + sc[2] = d->tt_slot ? ((uint32)d->tt_slot | ((uint32)d->tt_port << 8)) : 0; + return command(h, phys(in), TRB_TYPE(TRB_CONFIG_EP) | TRB_SLOT(d->slot), 0) + == CC_SUCCESS ? 0 : -1; +} + +static int +interval(int speed, int bInterval) +{ + int i; + if (speed == XSPEED_HIGH || speed == XSPEED_SUPER) + i = bInterval - 1; + else { + int f = bInterval ? bInterval : 1; + for (i = 0; (1 << (i + 1)) <= f; i++) + ; + i += 3; + } + return i < 0 ? 0 : i > 15 ? 15 : i; +} + +static void +arm(struct kbd *k) +{ + push(&k->ring, phys(k->buf), k->len, TRB_TYPE(TRB_NORMAL) | TRB_IOC | TRB_ISP); + k->hc->db[k->slot] = k->dci; +} + +static int +keyboard(struct hc *h, struct dev *d, int config, int iface, + const struct usb_endpoint_desc *ep) +{ + if (nkbds == NKBD) + return -1; + struct kbd *k = &kbds[nkbds]; + + if (control(h, d->slot, 0x00, 9, config, 0, 0, 0) < 0) // SET_CONFIGURATION + return -1; + control(h, d->slot, 0x21, 0x0B, 0, iface, 0, 0); // SET_PROTOCOL boot + control(h, d->slot, 0x21, 0x0A, 0, iface, 0, 0); // SET_IDLE: changes only + + int mps = ep->wMaxPacketSize & 0x7FF; + k->hc = h; + k->slot = d->slot; + k->dci = (ep->bEndpointAddress & 0xF) * 2 + 1; + k->len = mps < 64 ? mps : 64; + k->buf = palloc(64, 64); + void *in = palloc(h->ctxsize * 33, 64); + if (!k->buf || !in || ring_init(&k->ring) < 0) + return -1; + + uint32 *c = ctx(h, in, 0), *sc = ctx(h, in, 1), *ec = ctx(h, in, k->dci + 1); + uint32 *cur = ctx(h, d->devctx, 0); + c[1] = 1 | (1u << k->dci); + sc[0] = (cur[0] & ~(0x1Fu << 27)) | ((uint32)k->dci << 27); + sc[1] = cur[1]; + sc[2] = cur[2]; + ec[0] = (uint32)interval(d->speed, ep->bInterval) << 16; + ec[1] = (EPTYPE_INT_IN << 3) | (3 << 1) | ((uint32)mps << 16); + *(uint64 *)&ec[2] = phys(k->ring.trb) | 1; + ec[4] = (uint32)mps | ((uint32)mps << 16); + if (command(h, phys(in), TRB_TYPE(TRB_CONFIG_EP) | TRB_SLOT(d->slot), 0) != CC_SUCCESS) + return -1; + + nkbds++; + arm(k); + return 0; +} + +static void enumerate(struct hc *h, struct dev *d); + +// A USB 2 hub: power every port, then for each that has something on it, +// reset it and enumerate what comes up. +static void +hub(struct hc *h, struct dev *d, int config, int mtt) +{ + if (d->tier >= 2) + return; + if (control(h, d->slot, 0x00, 9, config, 0, 0, 0) < 0) + return; + if (control(h, d->slot, 0xA0, 6, 0x2900, 0, dbuf, 9) < 0) + return; + int nports = dbuf[2], pgood = dbuf[5] * 2; + if (nports > 15) + nports = 15; + if (make_hub(h, d, nports, mtt) < 0) + return; + + for (int p = 1; p <= nports; p++) + control(h, d->slot, 0x23, 3, 8, p, 0, 0); // PORT_POWER + microdelay((pgood < 100 ? 100 : pgood) * 1000); + + for (int p = 1; p <= nports; p++) { + uchar st[4]; + if (control(h, d->slot, 0xA3, 0, 0, p, st, 4) < 0 || !(st[0] & 1)) + continue; + control(h, d->slot, 0x23, 3, 4, p, 0, 0); // PORT_RESET + int ok = 0; + for (int i = 0; i < 50 && !ok; i++) { + microdelay(10000); + if (control(h, d->slot, 0xA3, 0, 0, p, st, 4) == 0 && (st[2] & 0x10)) + ok = 1; + } + control(h, d->slot, 0x23, 1, 20, p, 0, 0); // C_PORT_RESET + control(h, d->slot, 0x23, 1, 16, p, 0, 0); // C_PORT_CONNECTION + if (!ok || !(st[0] & 2)) + continue; + microdelay(10000); + + struct dev c; + memset(&c, 0, sizeof(c)); + c.port = d->port; + c.tier = d->tier + 1; + c.route = d->route | (p << (4 * d->tier)); + c.speed = (st[1] & 0x02) ? XSPEED_LOW : (st[1] & 0x04) ? XSPEED_HIGH : XSPEED_FULL; + // A slow device behind a high-speed hub goes through that hub's + // transaction translator; behind a full-speed hub, through whichever one + // the hub itself is behind. + if (c.speed != XSPEED_HIGH && d->speed == XSPEED_HIGH) { + c.tt_slot = d->slot; + c.tt_port = p; + } else { + c.tt_slot = d->tt_slot; + c.tt_port = d->tt_port; + } + enumerate(h, &c); + } +} + +// Address a device, and keep it only if it is a hub or a keyboard. +static void +enumerate(struct hc *h, struct dev *d) +{ + if (address(h, d) < 0) + return; + int devclass = dbuf[4], devproto = dbuf[6]; + + if (control(h, d->slot, 0x80, 6, 0x0200, 0, dbuf, 9) < 0) + return; + int total = dbuf[2] | (dbuf[3] << 8); + if (total > (int)sizeof(dbuf)) + total = sizeof(dbuf); + if (control(h, d->slot, 0x80, 6, 0x0200, 0, dbuf, total) < 0) + return; + int config = dbuf[5]; + + if (devclass == 9) { + if (d->speed != XSPEED_SUPER) + hub(h, d, config, devproto == 2); + return; + } + + // The first boot keyboard interface, and its interrupt IN endpoint. + int iface = -1; + for (int o = 0; o + 2 <= total && dbuf[o] >= 2; o += dbuf[o]) { + if (dbuf[o + 1] == 4 && o + 9 <= total) { + const struct usb_interface_desc *id = (const void *)&dbuf[o]; + iface = (id->bInterfaceClass == 3 && id->bInterfaceSubClass == 1 && + id->bInterfaceProtocol == 1) ? id->bInterfaceNumber : -1; + } else if (dbuf[o + 1] == 5 && o + 7 <= total && iface >= 0) { + const struct usb_endpoint_desc *ep = (const void *)&dbuf[o]; + if ((ep->bEndpointAddress & 0x80) && USB_EP_XFERTYPE(ep->bmAttributes) == 3) { + struct usb_endpoint_desc copy = *ep; + keyboard(h, d, config, iface, ©); + return; + } + } + } +} + +static int +start(struct hc *h, volatile uchar *cap) +{ + h->cap = cap; + h->op = cap + (rd32(cap, XHCI_CAPLENGTH) & 0xFF); + h->rt = cap + (rd32(cap, XHCI_RTSOFF) & ~0x1Fu); + h->db = (volatile uint32 *)(cap + (rd32(cap, XHCI_DBOFF) & ~0x3u)); + uint32 hcs1 = rd32(cap, XHCI_HCSPARAMS1), hcs2 = rd32(cap, XHCI_HCSPARAMS2); + h->maxslots = HCS1_MAXSLOTS(hcs1) < MAXSLOT ? HCS1_MAXSLOTS(hcs1) : MAXSLOT; + h->maxports = HCS1_MAXPORTS(hcs1); + h->ctxsize = (rd32(cap, XHCI_HCCPARAMS1) & HCC1_CSZ) ? 64 : 32; + + // The scratchpad array the live driver lent the controller, from the DCBAA + // it gave it. Read before the reset, which forgets where that was. + uint64 scratch = 0; + uint64 old = rd32(h->op, XHCI_DCBAAP) | ((uint64)rd32(h->op, XHCI_DCBAAP + 4) << 32); + if (old) + scratch = *(uint64 *)P2V(old & ~0x3Full); + int spb = (HCS2_SPB_HI(hcs2) << 5) | HCS2_SPB_LO(hcs2); + if (spb && !scratch) + return -1; + + wr32(h->op, XHCI_USBCMD, rd32(h->op, XHCI_USBCMD) & ~(CMD_RS | CMD_INTE)); + for (int i = 0; i < 100 && !(rd32(h->op, XHCI_USBSTS) & STS_HCH); i++) + microdelay(1000); + wr32(h->op, XHCI_USBCMD, CMD_HCRST); + int i; + for (i = 0; i < 1000; i++) { + microdelay(1000); + if (!(rd32(h->op, XHCI_USBCMD) & CMD_HCRST) && !(rd32(h->op, XHCI_USBSTS) & STS_CNR)) + break; + } + if (i == 1000) + return -1; + + h->dcbaa = palloc((MAXSLOT + 1) * 8, 4096); + uint64 *erst = palloc(64, 64); + h->ev = palloc(NTRB * 16, 4096); + if (!h->dcbaa || !erst || !h->ev || ring_init(&h->cmd) < 0) + return -1; + h->dcbaa[0] = scratch; + h->evdeq = 0; + h->evcycle = 1; + erst[0] = phys(h->ev); + erst[1] = NTRB; + + wr32(h->op, XHCI_CONFIG, h->maxslots); + wr64(h->op, XHCI_DCBAAP, phys(h->dcbaa)); + wr64(h->op, XHCI_CRCR, phys(h->cmd.trb) | CRCR_RCS); + wr32(h->rt, XHCI_IR0 + IR_IMAN, IMAN_IP); // pending cleared, IE off + wr32(h->rt, XHCI_IR0 + IR_ERSTSZ, 1); + wr64(h->rt, XHCI_IR0 + IR_ERDP, phys(h->ev)); + wr64(h->rt, XHCI_IR0 + IR_ERSTBA, phys(erst)); + wr32(h->op, XHCI_USBCMD, CMD_RS); + for (i = 0; i < 100 && (rd32(h->op, XHCI_USBSTS) & STS_HCH); i++) + microdelay(1000); + if (rd32(h->op, XHCI_USBSTS) & STS_HCH) + return -1; + + // USB 3 ports train on their own after a reset, USB 2 ports need one; a + // device that has not reconnected yet gets a moment. + microdelay(300000); + for (int p = 1; p <= h->maxports; p++) { + uint32 sc = rd32(h->op, XHCI_PORTSC(p)); + if (!(sc & PORT_CCS)) + continue; + if (!(sc & PORT_PED)) { + wr32(h->op, XHCI_PORTSC(p), (sc & ~PORT_RW1C) | PORT_PR); + for (i = 0; i < 200; i++) { + microdelay(1000); + sc = rd32(h->op, XHCI_PORTSC(p)); + if (sc & PORT_PRC) + break; + } + wr32(h->op, XHCI_PORTSC(p), (sc & ~PORT_RW1C) | PORT_PRC | PORT_CSC); + sc = rd32(h->op, XHCI_PORTSC(p)); + } + if (!(sc & PORT_PED)) + continue; + struct dev d; + memset(&d, 0, sizeof(d)); + d.port = p; + d.speed = PORT_SPEED(sc); + enumerate(h, &d); + } + return 0; +} + +static void +report(struct hc *h, struct trb *ev) +{ + int slot = TRB_GET_SLOT(ev->control), dci = TRB_GET_EPID(ev->control); + for (int i = 0; i < nkbds; i++) { + struct kbd *k = &kbds[i]; + if (k->hc != h || k->slot != slot || k->dci != dci) + continue; + int cc = TRB_CC(ev->status); + if ((cc == CC_SUCCESS || cc == CC_SHORT_PACKET) && + k->len - (int)(ev->status & 0xFFFFFF) >= 8) + gmmos_hid_report(&keys, k->buf); + arm(k); + return; + } +} + +static int +probe(void) +{ + volatile uchar *caps[NHC]; + int n = xhci_gmmos_controllers(caps, NHC); + + for (int i = 0; i < n; i++) + start(&hcs[i], caps[i]); + return nkbds > 0; +} + +static int +poll(void) +{ + struct trb ev; + int k = gmmos_hid_next(&keys); + if (k) + return k; + for (int i = 0; i < NHC; i++) { + if (!hcs[i].ev) + continue; + while (pop(&hcs[i], &ev)) + if (TRB_GET_TYPE(ev.control) == TRB_XFER_EVENT) + report(&hcs[i], &ev); + } + return gmmos_hid_next(&keys); +} + +const struct gmmos_kbd gmmos_xhci = { + .name = "USB (xHCI)", + .probe = probe, + .poll = poll, +}; diff --git a/kernel/net/inet.c b/kernel/net/inet.c index 0e43fd9..57ab806 100644 --- a/kernel/net/inet.c +++ b/kernel/net/inet.c @@ -169,7 +169,7 @@ net_interrupted(void) return 1; uint64 live = p->pending_signals & ~p->signal_mask; for (int sig = 1; sig < 32; sig++) - if ((live & (1ULL << sig)) && p->sig_handler[sig] > 1) // not DFL/IGN + if ((live & (1ULL << sig)) && p->sighand->handler[sig] > 1) // not DFL/IGN return 1; return 0; } diff --git a/kernel/net/lwip/netif_eth.c b/kernel/net/lwip/netif_eth.c index 356b12b..764a178 100644 --- a/kernel/net/lwip/netif_eth.c +++ b/kernel/net/lwip/netif_eth.c @@ -110,6 +110,11 @@ net_eth_attach(struct device *dev) if (nifs >= NETIF_MAX || dev == 0) return -1; + // Once only: a device that appears while netd is starting can be offered + // both by netd's boot-time walk and by net_attach_late. + for (int i = 0; i < nifs; i++) + if (ifs[i].used && ifs[i].dev == dev) + return -1; struct eth_if *e = &ifs[nifs]; e->dev = dev; @@ -259,3 +264,28 @@ net_eth_poll(void) } return moved; } + +// Follow each device's link into lwIP. +// +// The link was read once, at attach, and never again. Under QEMU it is up +// before the driver asks; on a real network autonegotiation takes two or +// three seconds, so attach saw it down and the interface stayed down for +// good -- no DHCP, no traffic, the cable plugged in and nothing to show for +// it. Bringing the link up also restarts DHCP (lwIP does that itself). +void +net_eth_link_check(void) +{ + for (int k = 0; k < nifs; k++) { + struct eth_if *e = &ifs[k]; + if (!e->used) + continue; + int up = netdev_link_up(e->dev) != 0; + if (up == (netif_is_link_up(&e->netif) != 0)) + continue; + if (up) + netif_set_link_up(&e->netif); + else + netif_set_link_down(&e->netif); + klog("net: %s link %s\n", e->dev->name, up ? "up" : "down"); + } +} diff --git a/kernel/net/net.c b/kernel/net/net.c index 095274a..b526350 100644 --- a/kernel/net/net.c +++ b/kernel/net/net.c @@ -194,8 +194,16 @@ net_attach_late(struct device *d) { int idx; - if (!net_ready) - return -1; // netinit() has not run; it will pick this up + // No stack yet. Either netinit() is still to run, or it ran on a machine + // with no network device and left the stack off -- which is every boot + // now that the laptop's RTL8168 is driven by a process (LDLS) that only + // appears later. Start it: net_thread's first act is to attach every + // network device there is, and that now includes this one. + if (!net_ready) { + if (!net_started) + netinit(); + return net_started ? 0 : -1; + } lwip_sys_lock(); idx = net_eth_attach(d); @@ -292,12 +300,19 @@ net_thread(void) // One bit per interface, so a second one binding later is announced too. + uint link_checked = 0; for (;;) { int moved; lwip_sys_lock(); moved = net_eth_poll(); sys_check_timeouts(); + // A quarter of a second is quick enough to notice a cable, and a + // register read per interface that often costs nothing. + if (ticks - link_checked >= 25) { + link_checked = ticks; + net_eth_link_check(); + } // Loopback has no driver and no interrupt: a packet sent to 127.0.0.1 // sits on the loopif's queue until something drains it. With NO_SYS=1 // that something is this thread. diff --git a/kernel/net/unix.c b/kernel/net/unix.c index fca12e9..449b3f1 100644 --- a/kernel/net/unix.c +++ b/kernel/net/unix.c @@ -15,15 +15,18 @@ // is the property sleep/wakeup actually requires. Thirty-two sockets carrying // GUI messages will not notice the contention. // -// **Names are held in a table here, not in the filesystem.** A real Unix -// socket appears in a directory listing as a special file, and binding one -// creates that node. Doing that properly means a new inode type threaded -// through both filesystems, iget, dirlookup and stat -- a VFS change, not a -// socket change. The table gives every behaviour that matters: a client -// finds a server by a name it agreed in advance, which is the whole job. -// What is lost is `ls -l /tmp/schcom` showing an `s`, and `unlink()` of a -// socket path failing with ENOENT. Both are worth fixing when the VFS next -// gets opened up; neither blocks anything. +// **Names live in the filesystem.** bind() creates a socket node +// (kernel/sys/sys_net.c) and connect() resolves a path to the inode and looks +// for the socket bound to that inode, so every spelling of a path arrives at +// the same place and the node can be stat()ed and unlink()ed. +// +// **Datagram and sequenced-packet sockets** share the ring with streams: each +// message is a 16-byte header -- length and multicast groups, sender port or +// pid, sender uid and gid -- and then its bytes, so a read takes exactly one +// message and credentials belong to the message, not to the socket. udev's +// workers share one end of a socketpair and the manager has to know which of +// them wrote what. The kernel's uevents travel the same way, to netlink +// sockets of protocol NETLINK_KOBJECT_UEVENT. #include "core/types.h" #include "core/defs.h" @@ -113,6 +116,139 @@ sockinit(void) "the socket table, with buffered bytes and descriptors in flight"); } +// SOCK_SEQPACKET and SOCK_DGRAM keep message boundaries. They ride in the same +// ring as a stream, each message behind a four-byte length, so a write is one +// message and a read takes exactly one -- the rest of a message that does not +// fit the buffer is dropped, as Linux drops it. A message is never split, so +// one has to fit the ring whole. +static int +is_packet(struct socket *s) +{ + if(s->domain == AF_NETLINK) + return s->nlproto == NETLINK_KOBJECT_UEVENT; + return s->domain == AF_UNIX && (s->type == SOCK_DGRAM || s->type == SOCK_SEQPACKET); +} + +int +sock_is_packet(struct socket *s) +{ + return is_packet(s); +} + +// Every packet in the ring is an eight-byte header and then the payload: the +// length with the multicast groups it arrived on in the top byte, and the port +// id of whoever sent it. Netlink receivers need both -- a datagram that says +// "no group" is unicast, and is believed only from the one sender they trust. +#define PKTHDR 16 + +// The multicast groups the datagram last read from this socket was sent to, +// and who sent it. +uint +sock_nl_groups(struct socket *s) +{ + return s->domain == AF_NETLINK ? s->nllast : 0; +} + +uint +sock_nl_from(struct socket *s) +{ + return s->domain == AF_NETLINK ? s->nlfrom : 0; +} + +uint +sock_nl_pid(struct socket *s) +{ + return s->nlpid; +} + +uint +sock_nl_bound_groups(struct socket *s) +{ + return s->nlgroups; +} + +static uint nlport = 4000; + +// bind(2) on a netlink socket. A port id of 0 asks the kernel to choose one, +// which is how a socket becomes something a datagram can be addressed to. +int +sock_netlink_bind(struct socket *s, uint pid, uint groups) +{ + acquire(&stab.lock); + s->nlgroups = groups; + if(s->nlproto == NETLINK_KOBJECT_UEVENT && s->nlpid == 0) + s->nlpid = pid ? pid : ++nlport; + release(&stab.lock); + return 0; +} + +// Caller holds stab.lock. Append one packet to dst's ring; 0, or -1 if it +// does not fit. +static int +pkt_put(struct socket *dst, const char *msg, int len, uint groups, uint from, + uint uid, uint gid) +{ + if(len < 0 || len + PKTHDR > SOCKBUF || + dst->nwrite - dst->nread + (uint)len + PKTHDR > SOCKBUF) + return -1; + uint h0 = (uint)len | ((groups & 0xFF) << 24); + for(int i = 0; i < 4; i++) + dst->data[dst->nwrite++ % SOCKBUF] = (char)(h0 >> (8 * i)); + for(int i = 0; i < 4; i++) + dst->data[dst->nwrite++ % SOCKBUF] = (char)(from >> (8 * i)); + for(int i = 0; i < 4; i++) + dst->data[dst->nwrite++ % SOCKBUF] = (char)(uid >> (8 * i)); + for(int i = 0; i < 4; i++) + dst->data[dst->nwrite++ % SOCKBUF] = (char)(gid >> (8 * i)); + for(int i = 0; i < len; i++) + dst->data[dst->nwrite++ % SOCKBUF] = msg[i]; + return 0; +} + +// Queue one uevent datagram: on the socket whose port id is `dpid` if there is +// one, and on every uevent socket bound to any of `groups`, except `from`. A +// full queue drops the message, as Linux does: udev recovers with a trigger, +// and the sender must never wait on a reader. +static void +uevent_dispatch(uint groups, uint dpid, const char *msg, int len, struct socket *from) +{ + struct socket *s; + uint spid = from ? from->nlpid : 0; + + if(len <= 0) + return; + acquire(&stab.lock); + for(s = stab.sock; s < stab.sock + NSOCK; s++){ + if(s->state == SS_FREE || s == from || s->domain != AF_NETLINK || + s->nlproto != NETLINK_KOBJECT_UEVENT) + continue; + uint g = groups & s->nlgroups; + if(dpid && s->nlpid == dpid) + g = 0; // unicast: no group, so the reader checks the sender + else if(!g) + continue; + if(pkt_put(s, msg, len, g, spid, 0, 0) == 0) + wakeup(&s->nread); + } + release(&stab.lock); + poll_kick(); +} + +void +uevent_send(uint groups, const char *msg, int len, struct socket *from) +{ + uevent_dispatch(groups, 0, msg, len, from); +} + +int +sock_uevent_send(struct socket *s, const char *msg, int len, uint dpid, uint dgroups) +{ + if(len + PKTHDR > SOCKBUF) + return -EMSGSIZE; + uevent_dispatch(dgroups, dpid, msg, len, s); + return len; +} + // Caller holds stab.lock. static struct socket * alloc_locked(int type) @@ -125,6 +261,11 @@ alloc_locked(int type) s->ref = 1; s->type = type; s->state = SS_UNBOUND; + if(myproc()){ + s->cred_pid = myproc()->pid; + s->cred_uid = myproc()->euid; + s->cred_gid = myproc()->egid; + } return s; } } @@ -266,6 +407,7 @@ sockopt_set(struct socket *s, int level, int name, int val) case SO_REUSEADDR: s->o_reuseaddr = !!val; break; case SO_KEEPALIVE: s->o_keepalive = !!val; break; case SO_BROADCAST: s->o_broadcast = !!val; break; + case 16: s->o_passcred = !!val; break; // SO_PASSCRED // Accepted and left at the size we have. Not the same lie as before: // Linux is explicitly free to clamp these, and sockopt_get below reports // the real figure rather than the requested one. @@ -308,6 +450,7 @@ sockopt_get(struct socket *s, int level, int name, int *out) case SO_REUSEADDR: *out = s->o_reuseaddr; break; case SO_KEEPALIVE: *out = s->o_keepalive; break; case SO_BROADCAST: *out = s->o_broadcast; break; + case 16: *out = s->o_passcred; break; // SO_PASSCRED // The real buffer, not a number somebody asked for. case SO_SNDBUF: case SO_RCVBUF: *out = SOCKBUF; break; @@ -332,7 +475,7 @@ sockname(struct socket *s) // --------------------------------------------------------------------------- // Descriptor passing // -// docs/plan/hybrid.md mechanism 1, and item 1 of foundations.md. The kernel +// docs/architecture/devices.md mechanism 1, and item 1 of the descriptor-passing work (kernel.md). The kernel // holds a reference on every descriptor in flight, which is what makes the // sender free to close its own copy the instant sendmsg returns -- and what // makes dropping a socket with descriptors still queued a thing that has to be @@ -408,7 +551,7 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout // so an empty buffer means the caller has already drained this dump rather // than that more is coming. Blocking here would hang a getifaddrs() that // asked one question too many. - if(s->domain == AF_NETLINK){ + if(s->domain == AF_NETLINK && !is_packet(s)){ acquire(&stab.lock); uint have = s->nwrite - s->nread; if(have == 0){ @@ -423,8 +566,10 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout return n; } + // A datagram socket has no end of stream: a reader whose peer has gone just + // waits, as on Linux, instead of reading "zero bytes" for ever. acquire(&stab.lock); - while(s->nread == s->nwrite && !s->peer_closed){ + while(s->nread == s->nwrite && !(s->peer_closed && s->type != SOCK_DGRAM)){ if(myproc()->killed || s->state == SS_FREE){ release(&stab.lock); return -EINTR; @@ -432,6 +577,51 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout sleep(&s->nread, &stab.lock); } + // One message, whole or truncated; descriptors ride with the header. + if(is_packet(s) && s->nread != s->nwrite){ + uint len = 0, from = 0, puid = 0, pgid = 0; + for(i = 0; i < 4; i++) + len |= (uint)(uchar)s->data[(s->nread + i) % SOCKBUF] << (8 * i); + for(i = 0; i < 4; i++) + from |= (uint)(uchar)s->data[(s->nread + 4 + i) % SOCKBUF] << (8 * i); + for(i = 0; i < 4; i++) + puid |= (uint)(uchar)s->data[(s->nread + 8 + i) % SOCKBUF] << (8 * i); + for(i = 0; i < 4; i++) + pgid |= (uint)(uchar)s->data[(s->nread + 12 + i) % SOCKBUF] << (8 * i); + if(s->nscm > 0 && s->scm[0].pos == s->nread){ + got = s->scm[0].n; + for(i = 0; i < got; i++) + taken[i] = s->scm[0].f[i]; + for(i = 1; i < s->nscm; i++) + s->scm[i - 1] = s->scm[i]; + s->nscm--; + } + s->nread += PKTHDR; + s->nllast = len >> 24; + s->nlfrom = from; + s->pk_uid = puid; + s->pk_gid = pgid; + s->pk_valid = 1; + len &= 0xFFFFFF; + uint take = len < (uint)n ? len : (uint)n; + for(i = 0; i < (int)take; i++) + addr[i] = s->data[(s->nread + i) % SOCKBUF]; + s->nread += len; + wakeup(&s->nwrite); + release(&stab.lock); + poll_kick(); + if(fout){ + for(int j = 0; j < got; j++) + fout[j] = taken[j]; + if(nfout) + *nfout = got; + } else { + for(int j = 0; j < got; j++) + fileclose(taken[j]); + } + return (int)take; + } + // A message carrying descriptors is a boundary. This read either starts // exactly on one -- and takes them -- or is cut short so that it stops // before it, leaving the next read to start there. @@ -502,10 +692,21 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf) if(s->domain == AF_INET) return inet_write(s->inet, addr, n); + if(myproc()){ + s->tx_pid = myproc()->pid; + s->tx_uid = myproc()->euid; + s->tx_gid = myproc()->egid; + } + // A netlink request is answered immediately, into this socket's own buffer // rather than a peer's -- there is no peer. Doing the work here is what lets // the read that follows never block, which matters because musl reads with // MSG_DONTWAIT and treats a short read as the end of the dump. + if(s->domain == AF_NETLINK && is_packet(s)){ + // udevd announces what it did on group 2; the sender does not hear itself. + uevent_dispatch(UEVENT_GROUP_UDEV, 0, addr, n, s); + return n; + } if(s->domain == AF_NETLINK){ acquire(&stab.lock); s->nread = s->nwrite = 0; @@ -522,6 +723,14 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf) // look for it. Same contract as a pipe with no reader: a signal, then an // error. A process that writes to a dead connection and is not told about // it spins forever believing it is making progress. + // + // A datagram socket is not a connection, so having no peer is not a broken + // pipe: syslog() sends to /dev/log whether or not anything listens, and a + // signal for that killed doas. + if(s->domain == AF_UNIX && s->type == SOCK_DGRAM && s->peer == 0){ + release(&stab.lock); + return -ENOTCONN; + } if(s->peer == 0 || s->wr_shut){ release(&stab.lock); sigsend(myproc()->pid, SIGPIPE); @@ -535,6 +744,49 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf) return -ENOBUFS; } + if(is_packet(s)){ + if(n + PKTHDR > SOCKBUF){ + release(&stab.lock); + return -EMSGSIZE; + } + for(;;){ + dst = s->peer; + if(dst == 0){ + release(&stab.lock); + sigsend(myproc()->pid, SIGPIPE); + return -EPIPE; + } + if(dst->nwrite - dst->nread + (uint)n + PKTHDR <= SOCKBUF) + break; + if(myproc()->killed){ + release(&stab.lock); + return -EINTR; + } + wakeup(&dst->nread); + sleep(&dst->nwrite, &stab.lock); + } + if(nf > 0){ + if(dst->nscm >= NSCM){ + release(&stab.lock); + return -ENOBUFS; + } + struct scm_fds *b = &dst->scm[dst->nscm++]; + b->pos = dst->nwrite; // the header, where a packet read starts + b->n = nf; + for(int j = 0; j < nf; j++) + b->f[j] = f[j]; + } + // The sender's credentials travel with the datagram, not with the socket: + // several processes can share one end (udev's workers do) and the reader + // has to know which of them each message came from. + pkt_put(dst, addr, n, 0, myproc() ? (uint)myproc()->pid : 0, + myproc() ? (uint)myproc()->euid : 0, myproc() ? (uint)myproc()->egid : 0); + wakeup(&dst->nread); + release(&stab.lock); + poll_kick(); + return n; + } + for(i = 0; i < n; i++){ // Re-read every iteration: the peer can go away while we sleep below. dst = s->peer; @@ -594,9 +846,11 @@ socknew(int domain, int type, int proto) if(domain != AF_UNIX && domain != AF_INET && domain != AF_NETLINK) return 0; - // AF_UNIX is stream-only here; AF_INET has datagrams too, and inet_new is + // AF_UNIX: streams, and message-preserving SEQPACKET and DGRAM (connected + // pairs and listeners; an unconnected DGRAM has no address to send to and + // will be refused when it tries). AF_INET has datagrams too, and inet_new is // what decides whether it can serve the type asked for. - if(domain == AF_UNIX && type != SOCK_STREAM) + if(domain == AF_UNIX && type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET) return 0; // AF_NETLINK carries whole messages and musl asks for SOCK_RAW; SOCK_DGRAM is // accepted too because Linux does. NETLINK_ROUTE is the only protocol served @@ -605,14 +859,16 @@ socknew(int domain, int type, int proto) if(domain == AF_NETLINK){ if(type != SOCK_RAW && type != SOCK_DGRAM) return 0; - if(proto != NETLINK_ROUTE) + if(proto != NETLINK_ROUTE && proto != NETLINK_KOBJECT_UEVENT) return 0; } acquire(&stab.lock); s = alloc_locked(type); - if(s) + if(s){ s->domain = domain; + s->nlproto = domain == AF_NETLINK ? proto : 0; + } release(&stab.lock); if(s == 0) return 0; @@ -631,16 +887,16 @@ socknew(int domain, int type, int proto) // A connected pair with no name and no listener, for socketpair(2). int -sockpair(struct socket **pa, struct socket **pb) +sockpair_type(struct socket **pa, struct socket **pb, int type) { struct socket *a, *b; acquire(&stab.lock); - if((a = alloc_locked(SOCK_STREAM)) == 0){ + if((a = alloc_locked(type)) == 0){ release(&stab.lock); return -ENFILE; } - if((b = alloc_locked(SOCK_STREAM)) == 0){ + if((b = alloc_locked(type)) == 0){ a->state = SS_FREE; a->ref = 0; release(&stab.lock); @@ -661,6 +917,12 @@ sockpair(struct socket **pa, struct socket **pb) return 0; } +int +sockpair(struct socket **pa, struct socket **pb) +{ + return sockpair_type(pa, pb, SOCK_STREAM); +} + // bind(2) for AF_UNIX. The caller has already created the filesystem node and // hands over its identity; see sys_bind() in kernel/sys/sys_net.c for why the // creation lives there and not here. @@ -850,8 +1112,17 @@ sockconnect(struct socket *s, uint dev, uint64 ino) return -ENFILE; } + // The accepted end is an AF_UNIX socket like the one that connected. It was + // left at domain 0, and nothing minded until a server passed a descriptor + // back through it: sendmsg refuses SCM_RIGHTS on a socket that is not + // AF_UNIX, so seatd could never hand over a device, and a Wayland + // compositor could never have sent its clients a buffer. + peer->domain = AF_UNIX; peer->peer = s; peer->state = SS_CONNECTED; + peer->cred_pid = srv->cred_pid; + peer->cred_uid = srv->cred_uid; + peer->cred_gid = srv->cred_gid; s->peer = peer; s->state = SS_CONNECTED; @@ -912,6 +1183,43 @@ sock_shutdown(struct socket *s, int how) // Readiness, for poll/select // --------------------------------------------------------------------------- +// Netlink, for recvmsg's MSG_PEEK and MSG_TRUNC: how much is waiting, a copy +// of it from `off` on without taking it, and taking it. Everything waiting +// counts as one datagram -- it is the answer to the requests sent so far, and +// a reader walks the nlmsghdrs inside it whatever their number. +uint +socknl_pending(struct socket *s) +{ + acquire(&stab.lock); + uint have = s->nwrite - s->nread; + release(&stab.lock); + return have; +} + +int +socknl_copy(struct socket *s, uint off, char *addr, int n) +{ + acquire(&stab.lock); + uint have = s->nwrite - s->nread; + if(off >= have) + n = 0; + else if((uint)n > have - off) + n = (int)(have - off); + memmove(addr, s->data + s->nread + off, (uint)n); + release(&stab.lock); + return n; +} + +void +socknl_take(struct socket *s, uint n) +{ + acquire(&stab.lock); + if(n > s->nwrite - s->nread) + n = s->nwrite - s->nread; + s->nread += n; + release(&stab.lock); +} + int sockreadable(struct socket *s) { @@ -919,15 +1227,21 @@ sockreadable(struct socket *s) return inet_readable(s->inet); if(s->state == SS_LISTENING) return s->nqueue > 0; - return s->nread != s->nwrite || s->peer_closed; + return s->nread != s->nwrite || (s->peer_closed && s->type != SOCK_DGRAM); } // The peer is gone and everything it sent has been read. +// +// A netlink socket's far end is the kernel, which never hangs up. It has no +// `peer` either, and once drained it used to read as hung up here -- so every +// poll() on GLib's network-monitor socket returned at once, forever. int sock_hup(struct socket *s) { if(s->domain == AF_INET) return inet_hup(s->inet); + if(s->domain == AF_NETLINK) + return 0; if(s->state == SS_LISTENING) return 0; @@ -1016,3 +1330,61 @@ socksendto(struct socket *s, char *buf, int n, uint addr, ushort port) return -EAFNOSUPPORT; // an AF_INET destination on an AF_UNIX socket return sockwrite(s, buf, n); } + +int +sock_passcred(struct socket *s) +{ + return s->domain != AF_INET && s->o_passcred; +} + +// SCM_CREDENTIALS for SO_PASSCRED: whoever last wrote at the far end, or the +// far end's SO_PEERCRED if nobody has. Per socket, not per message -- a +// receiver sees the writer of the latest bytes, which for the one-writer +// sockets that ask (WebKit's pid handshake, D-Bus) is the writer. +int +sock_sendercred(struct socket *s, int *pid, uint *uid, uint *gid) +{ + int r = -1; + + // Uevents come from the kernel -- or from udevd re-announcing them -- and a + // receiver that asked for credentials discards a datagram without them. + if(s->domain == AF_NETLINK && s->nlproto == NETLINK_KOBJECT_UEVENT){ + *pid = 0; *uid = 0; *gid = 0; + return 0; + } + + // A packet socket reports whoever sent the datagram just read. + if(s->domain == AF_UNIX && is_packet(s) && s->pk_valid){ + *pid = (int)s->nlfrom; *uid = s->pk_uid; *gid = s->pk_gid; + return 0; + } + + acquire(&stab.lock); + if(s->domain != AF_INET && s->peer){ + struct socket *p = s->peer; + *pid = p->tx_pid ? p->tx_pid : p->cred_pid; + *uid = p->tx_pid ? p->tx_uid : p->cred_uid; + *gid = p->tx_pid ? p->tx_gid : p->cred_gid; + r = 0; + } + release(&stab.lock); + return r; +} + +// SO_PEERCRED: the credentials recorded for the far end of a connected Unix +// socket. -1 if there is no far end. +int +sock_peercred(struct socket *s, int *pid, uint *uid, uint *gid) +{ + int r = -1; + + acquire(&stab.lock); + if(s->domain != AF_INET && s->peer){ + *pid = s->peer->cred_pid; + *uid = s->peer->cred_uid; + *gid = s->peer->cred_gid; + r = 0; + } + release(&stab.lock); + return r; +} diff --git a/kernel/proc/mapname.c b/kernel/proc/mapname.c new file mode 100644 index 0000000..a2a658b --- /dev/null +++ b/kernel/proc/mapname.c @@ -0,0 +1,228 @@ +// Which file each part of an address space came from. +// +// mmap of a file copies it into anonymous memory (see sys_mem.c), so the page +// tables and the vma records know nothing about files. This remembers the +// name beside them -- for /proc//maps, and so that a process killed by a +// fault is reported with the library and offset it died in rather than a bare +// address. There is no ptrace here and so no debugger: Epiphany crashing at +// "rip 0x8a094bea" in a library loaded with dlopen was a dead end without it. +// +// A dynamic linker maps each library as one span from offset 0 and then +// places its segments inside with MAP_FIXED; those inner maps of the same +// file are absorbed, so a library is one entry. Only diagnostics read this, +// and it is allowed to be approximate at the edges: a span unmapped in part +// keeps its name. + +#include "core/types.h" +#include "core/defs.h" +#include "core/param.h" +#include "arch/memlayout.h" +#include "arch/mmu.h" +#include "core/spinlock.h" +#include "core/sleeplock.h" +#include "core/proc.h" +#include "core/kmem.h" + +struct mapname { + struct mapname *next; + uint64 start, end, off; + char path[96]; +}; + +static struct spinlock mnlock; +static int mnlock_ready; + +static void +mn_lock(void) +{ + if (!mnlock_ready) { + initlock(&mnlock, "mapname"); + mnlock_ready = 1; + } + acquire(&mnlock); +} + +// Forget names wholly inside [start, end), and trim those hanging over an +// edge of it. A name the range falls inside stays whole. +static void +mn_cut(struct mm *m, uint64 start, uint64 end) +{ + struct mapname **pp = &m->names; + while (*pp) { + struct mapname *n = *pp; + if (n->start >= start && n->end <= end) { + *pp = n->next; + kmfree(n); + continue; + } + if (n->start < start && n->end > start && n->end <= end) + n->end = start; + else if (n->start >= start && n->start < end && n->end > end) { + n->off += end - n->start; + n->start = end; + } + pp = &n->next; + } +} + +void +mapname_add(struct mm *m, uint64 start, uint64 len, uint64 off, const char *path) +{ + struct mapname *n; + + if (!path || !*path) + return; + mn_lock(); + for (n = m->names; n; n = n->next) + if (n->start <= start && start + len <= n->end && strncmp(n->path, path, sizeof(n->path) - 1) == 0) { + release(&mnlock); + return; // a segment of a library already named + } + mn_cut(m, start, start + len); + release(&mnlock); + if ((n = kmalloc(sizeof(*n))) == 0) + return; + n->start = start; + n->end = start + len; + n->off = off; + safestrcpy(n->path, path, sizeof(n->path)); + mn_lock(); + n->next = m->names; + m->names = n; + release(&mnlock); +} + +void +mapname_unmap(struct mm *m, uint64 start, uint64 len) +{ + mn_lock(); + mn_cut(m, start, start + len); + release(&mnlock); +} + +void +mapname_free(struct mm *m) +{ + mn_lock(); + struct mapname *n = m->names; + m->names = 0; + release(&mnlock); + while (n) { + struct mapname *next = n->next; + kmfree(n); + n = next; + } +} + +// fork: the child's address space is the parent's, names and all. +void +mapname_copy(struct mm *dst, struct mm *src) +{ + struct mapname *n; + + mn_lock(); + for (n = src->names; n; n = n->next) { + struct mapname *c = kmalloc(sizeof(*c)); + if (!c) + break; + *c = *n; + c->next = dst->names; + dst->names = c; + } + release(&mnlock); +} + +// The name addr falls in and its offset in the file, copied out. 0 if found. +int +mapname_find(struct mm *m, uint64 addr, char *out, int cap, uint64 *off) +{ + int r = -1; + + mn_lock(); + for (struct mapname *n = m->names; n; n = n->next) + if (n->start <= addr && addr < n->end) { + safestrcpy(out, n->path, cap); + *off = n->off + (addr - n->start); + r = 0; + break; + } + release(&mnlock); + return r; +} + +static int +put(char *buf, int n, int cap, const char *s) +{ + while (*s && n < cap - 1) + buf[n++] = *s++; + return n; +} + +static int +puthex(char *buf, int n, int cap, uint64 v, int width) +{ + char t[17]; + int i = 16; + t[16] = 0; + do { t[--i] = "0123456789abcdef"[v & 15]; v >>= 4; } while (v && i > 0); + while (16 - i < width && i > 0) + t[--i] = '0'; + return put(buf, n, cap, t + i); +} + +// /proc//maps, in Linux's layout. Named spans and the anonymous records +// beside them; permissions are not tracked per page here, so every line +// says rw and private, and only the names are to be trusted. +int +mapname_gen(struct mm *m, char *buf, int cap) +{ + int n = 0; + + mn_lock(); + for (struct mapname *e = m->names; e; e = e->next) { + n = puthex(buf, n, cap, e->start, 8); + n = put(buf, n, cap, "-"); + n = puthex(buf, n, cap, e->end, 8); + n = put(buf, n, cap, " r-xp "); + n = puthex(buf, n, cap, e->off, 8); + n = put(buf, n, cap, " 00:00 0 "); + n = put(buf, n, cap, e->path); + n = put(buf, n, cap, "\n"); + } + release(&mnlock); + for (int i = 0; i < NVMA; i++) { + struct vma *v = &m->vma[i]; + if (!v->used) + continue; + n = puthex(buf, n, cap, v->addr, 8); + n = put(buf, n, cap, "-"); + n = puthex(buf, n, cap, v->addr + v->length, 8); + n = put(buf, n, cap, v->shmid >= 0 ? " rw-s 00000000 00:00 0 [memfd]\n" + : " rw-p 00000000 00:00 0\n"); + } + return n; +} + +// Said when a process dies of a fault: where its instruction pointer was, +// and where the return address on top of its stack points, by file. The +// second is the caller, which is what is left to go on when the first is in +// generated code. +void +mapname_report(struct proc *p) +{ + char path[96]; + uint64 off, ret = 0; + + if (!p->mm || !p->tf) + return; + if (mapname_find(p->mm, p->tf->rip, path, sizeof(path), &off) == 0) + cprintf("pid %d %s: rip %p is %s+0x%lx\n", p->pid, p->name, + (void *)p->tf->rip, path, off); + else + cprintf("pid %d %s: rip %p is in no file\n", p->pid, p->name, + (void *)p->tf->rip); + if (fetchaddr(p->tf->rsp, &ret) == 0 && + mapname_find(p->mm, ret, path, sizeof(path), &off) == 0) + cprintf("pid %d %s: [rsp] %p is %s+0x%lx\n", p->pid, p->name, + (void *)ret, path, off); +} diff --git a/kernel/proc/proc.c b/kernel/proc/proc.c index bde72f0..a873819 100644 --- a/kernel/proc/proc.c +++ b/kernel/proc/proc.c @@ -15,6 +15,8 @@ #include "uapi/posix.h" #include "uapi/errno.h" #include "fs/blkdev.h" +#include "core/uaccess.h" +#include "dev/acpi.h" // Verify myproc()->fs_base offset matches the hardcoded 0x688 in trapasm.S. _Static_assert(__builtin_offsetof(struct proc, fs_base) == 0x18, @@ -25,6 +27,10 @@ struct { struct proc proc[NPROC]; } ptable; +// CPU time of every process already reaped, so that /proc/stat's busy time +// does not drop each time one is. Under ptable.lock, like the processes. +static uint64 reaped_utime, reaped_stime; + // Kernel stacks, one per ptable slot, statically. // // **They used to be a single kalloc() page**, and a measurement said that was @@ -136,7 +142,7 @@ proc_dump(struct sbuf *out, const char *arg) sb_pad(out, state_name(p->state), 8); sb_putu_pad(out, p->sz / 1024, 6); sb_puts(out, " 0x"); - sb_puthex(out, p->mmap_next, 16); + sb_puthex(out, p->mm ? p->mm->mmap_next : 0, 16); sb_puts(out, " "); sb_puts(out, p->name); if (p->killed) @@ -170,18 +176,20 @@ vma_dump(struct sbuf *out, const char *arg) continue; if (only && p->pid != only) continue; + if (!p->mm || (p->tgid && p->tgid != p->pid)) + continue; // a thread: its leader lists the mappings for (int i = 0; i < NVMA; i++) { - if (!p->vma[i].used) + if (!p->mm->vma[i].used) continue; any = 1; sb_putu_pad(out, p->pid, 5); sb_putu_pad(out, i, 6); sb_puts(out, " 0x"); - sb_puthex(out, p->vma[i].addr, 16); - sb_putu_pad(out, p->vma[i].length, 12); - sb_puts(out, p->vma[i].no_free ? " yes " : " no "); - if (p->vma[i].shmid >= 0) - sb_putu_pad(out, p->vma[i].shmid, 5); + sb_puthex(out, p->mm->vma[i].addr, 16); + sb_putu_pad(out, p->mm->vma[i].length, 12); + sb_puts(out, p->mm->vma[i].no_free ? " yes " : " no "); + if (p->mm->vma[i].shmid >= 0) + sb_putu_pad(out, p->mm->vma[i].shmid, 5); else sb_puts(out, " -"); sb_puts(out, " "); @@ -235,15 +243,15 @@ maphit(void *ctx, uint64 va, uint64 pa, uint64 pte) { int i, found = 0; for (i = 0; i < NVMA; i++) { - if (!m->p->vma[i].used) + if (!m->p->mm->vma[i].used) continue; - if (va < m->p->vma[i].addr || - va >= m->p->vma[i].addr + m->p->vma[i].length) + if (va < m->p->mm->vma[i].addr || + va >= m->p->mm->vma[i].addr + m->p->mm->vma[i].length) continue; found = 1; sb_puts(m->out, " vma["); sb_putu(m->out, i); - sb_puts(m->out, m->p->vma[i].no_free ? "] no_free" : "]"); + sb_puts(m->out, m->p->mm->vma[i].no_free ? "] no_free" : "]"); break; } if (!found) { @@ -272,9 +280,197 @@ proc_whose(struct sbuf *out, struct whose *w) release(&ptable.lock); } +// --- Shared objects -------------------------------------------------------- +// +// One of each per slot is enough by construction: every struct proc holds at +// most one mm, one files and one sighand, so NPROC of each can never run out. +// ref 0 is free; -1 is being torn down and not yet free, so that a slot whose +// last user is still freeing it cannot be handed out from under it. +// +// objlock covers the counts and nothing else, and is never held while taking +// ptable.lock. mm_lock() sleeps on mmwait, a separate lock for the same reason. +static struct mm mmtab[NPROC]; +static struct files filestab[NPROC]; +static struct sighand sightab[NPROC]; +static struct spinlock objlock, mmwait; + +struct mm * +mm_alloc(void) +{ + acquire(&objlock); + for (struct mm *m = mmtab; m < &mmtab[NPROC]; m++) + if (m->ref == 0) { + memset(m, 0, sizeof(*m)); + for (int i = 0; i < NVMA; i++) + m->vma[i].shmid = -1; + m->ref = 1; + release(&objlock); + return m; + } + release(&objlock); + return 0; +} + +void +mm_get(struct mm *m) +{ + acquire(&objlock); + m->ref++; + release(&objlock); +} + +// Drop a reference. The last one releases shared segments and frees the page +// tables, which nobody can be running on any more: a thread's reference is +// only dropped once it is off the CPU for good. +void +mm_put(struct mm *m) +{ + if (m == 0) + return; + acquire(&objlock); + int last = --m->ref == 0; + if (last) + m->ref = -1; + release(&objlock); + if (!last) + return; + mm_release_shared(m); + mapname_free(m); + if (m->pgdir) + freevm(m->pgdir); + m->pgdir = 0; + acquire(&objlock); + m->ref = 0; + release(&objlock); +} + +int +mm_shared(struct mm *m) +{ + return m && m->ref > 1; +} + +// One mmap, munmap or brk at a time per address space. Sleeps: an mmap of a +// file reads it. +void +mm_lock(struct mm *m) +{ + acquire(&mmwait); + while (m->locked) + sleep(m, &mmwait); + m->locked = 1; + release(&mmwait); +} + +void +mm_unlock(struct mm *m) +{ + acquire(&mmwait); + m->locked = 0; + wakeup(m); + release(&mmwait); +} + +struct files * +files_alloc(void) +{ + acquire(&objlock); + for (struct files *f = filestab; f < &filestab[NPROC]; f++) + if (f->ref == 0) { + memset(f, 0, sizeof(*f)); + f->ref = 1; + release(&objlock); + return f; + } + release(&objlock); + return 0; +} + +void +files_get(struct files *f) +{ + acquire(&objlock); + f->ref++; + release(&objlock); +} + +// The last reference closes every descriptor. fileclose can sleep, so this is +// for process context only -- exit(), not the reaper. +void +files_put(struct files *f) +{ + if (f == 0) + return; + acquire(&objlock); + int last = --f->ref == 0; + if (last) + f->ref = -1; + release(&objlock); + if (!last) + return; + for (int fd = 0; fd < NOFILE; fd++) + if (f->ofile[fd]) { + struct file *x = f->ofile[fd]; + f->ofile[fd] = 0; + f->flags[fd] = 0; + fileclose(x); + } + acquire(&objlock); + f->ref = 0; + release(&objlock); +} + +// Slot allocation in a table two threads may be filling at once. +void +files_lock(void) +{ + acquire(&mmwait); +} + +void +files_unlock(void) +{ + release(&mmwait); +} + +struct sighand * +sighand_alloc(void) +{ + acquire(&objlock); + for (struct sighand *s = sightab; s < &sightab[NPROC]; s++) + if (s->ref == 0) { + memset(s, 0, sizeof(*s)); + s->ref = 1; + release(&objlock); + return s; + } + release(&objlock); + return 0; +} + +void +sighand_get(struct sighand *s) +{ + acquire(&objlock); + s->ref++; + release(&objlock); +} + +void +sighand_put(struct sighand *s) +{ + if (s == 0) + return; + acquire(&objlock); + s->ref--; + release(&objlock); +} + void pinit(void) { + initlock(&objlock, "procobj"); + initlock(&mmwait, "mmwait"); initlock(&ptable.lock, "ptable"); dbg_register("proc", proc_dump, "every process, with its state and size"); dbg_register("vma", vma_dump, "mappings a process holds but does not own"); @@ -308,11 +504,12 @@ found: // including stale handler addresses. deliver_signals() (syscall.c) // would then jump to garbage on this new process's very first syscall // return, faulting at whatever bogus rip happened to be sitting there. - memset(p->sig_handler, 0, sizeof(p->sig_handler)); - memset(p->sig_restorer, 0, sizeof(p->sig_restorer)); p->pending_signals = 0; p->alarm_at = p->alarm_every = 0; // a child's timers start disarmed p->utime = p->stime = p->cutime = p->cstime = 0; + p->strace = 0; + p->stopsig = p->stop_report = p->cont_report = 0; + p->umask = 022; // A recycled slot must not inherit a dead process's place in the queue -- // in either direction. sched_init_proc starts it at min_vruntime, which is @@ -335,6 +532,7 @@ found: p->sid = 0; p->uid = p->euid = p->suid = 0; p->gid = p->egid = p->sgid = 0; + p->ngroups = 0; release(&ptable.lock); @@ -361,10 +559,24 @@ found: p->syscall_map = 0; p->map_size = 0; - p->mmap_next = 0; p->kfn = 0; + p->tgid = p->pid; + p->clear_child_tid = 0; + p->futex_key = 0; + p->futex_woken = 0; + p->autoreap = 0; + p->vfork_parent = 0; + p->vfork_wait = 0; + p->thread_dying = 0; + p->group_status_set = 0; - memset(p->ofile_flags, 0, sizeof(p->ofile_flags)); + // Every process starts with its own of each; clone() swaps in the parent's + // to make a thread. The pools cannot run dry -- see mm_alloc(). + p->mm = mm_alloc(); + p->files = files_alloc(); + p->sighand = sighand_alloc(); + if (!p->mm || !p->files || !p->sighand) + panic("allocproc: object pools"); return p; } @@ -387,6 +599,8 @@ sinit(void) panic("sinit: allocuvm failed"); p->sz = 32 * PGSIZE; + p->mm->sz = p->sz; + p->mm->pgdir = p->pgdir; memset(p->tf, 0, sizeof(*p->tf)); p->tf->cs = (SEG_UCODE << 3) | DPL_USER; @@ -414,19 +628,54 @@ growproc(int64 n) { addr_t sz; - sz = myproc()->sz; + struct mm *m = myproc()->mm; + mm_lock(m); + sz = m->sz; if(n > 0){ - if((sz = allocuvm(myproc()->pgdir, sz, sz + n)) == 0) + if((sz = allocuvm(myproc()->pgdir, sz, sz + n)) == 0){ + mm_unlock(m); return -1; + } } else if(n < 0){ - if((sz = deallocuvm(myproc()->pgdir, sz, sz + n)) == 0) + if((sz = deallocuvm(myproc()->pgdir, sz, sz + n)) == 0){ + mm_unlock(m); return -1; + } } + m->sz = sz; myproc()->sz = sz; + mm_unlock(m); switchuvm(myproc()); return 0; } +// The leader of p's thread group: the process, as its parent sees it. +struct proc * +proc_leader(struct proc *p) +{ + if (p->tgid == p->pid) + return p; + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) + if (q->pid == p->tgid && q->state != UNUSED) + return q; + return p; +} + +// Give back a slot that fork() or clone() could not finish. +static void +fork_undo(struct proc *np) +{ + mm_put(np->mm); // frees the half-copied pgdir with it + files_put(np->files); + sighand_put(np->sighand); + np->mm = 0; + np->files = 0; + np->sighand = 0; + np->pgdir = 0; + np->kstack = 0; // the slot keeps its stack; see kstacks[] + np->state = UNUSED; +} + int fork(void) { @@ -440,35 +689,42 @@ fork(void) // them -- so the child's copy is taken straight from there. fxsave(np->fpu); - if((np->pgdir = copyuvm(myproc()->pgdir, myproc()->sz)) == 0){ - np->kstack = 0; // the slot keeps its stack; see kstacks[] - np->state = UNUSED; + // Another thread must not be reshaping the address space mid-copy. + struct mm *pm = myproc()->mm; + mm_lock(pm); + if((np->pgdir = copyuvm(myproc()->pgdir, pm->sz)) == 0){ + mm_unlock(pm); + fork_undo(np); return -1; } - np->sz = myproc()->sz; - np->parent = myproc(); + np->sz = pm->sz; + np->mm->sz = pm->sz; + np->mm->pgdir = np->pgdir; + // A child of any thread is a child of the process. + np->parent = proc_leader(myproc()); // copyuvm() only duplicates [0, sz) — mmap()'d regions live above that // and were never copied, leaving the child's vma[] bookkeeping (stale // from whichever process last occupied this ptable slot) pointing at // memory that isn't actually mapped in np->pgdir. Reset and rebuild it // from the parent's real, current mappings. - memset(np->vma, 0, sizeof(np->vma)); + memset(np->mm->vma, 0, sizeof(np->mm->vma)); for(i = 0; i < NVMA; i++){ - if(!myproc()->vma[i].used) + if(!myproc()->mm->vma[i].used) continue; - if(copy_vma_range(np->pgdir, myproc()->pgdir, myproc()->vma[i].addr, - myproc()->vma[i].length, myproc()->vma[i].no_free) < 0){ - freevm(np->pgdir); - np->kstack = 0; // the slot keeps its stack; see kstacks[] - np->state = UNUSED; + if(copy_vma_range(np->pgdir, myproc()->pgdir, myproc()->mm->vma[i].addr, + myproc()->mm->vma[i].length, myproc()->mm->vma[i].no_free) < 0){ + mm_unlock(pm); + fork_undo(np); return -1; } - np->vma[i] = myproc()->vma[i]; - if(np->vma[i].shmid >= 0) - shm_ref(np->vma[i].shmid); + np->mm->vma[i] = myproc()->mm->vma[i]; + if(np->mm->vma[i].shmid >= 0) + shm_ref(np->mm->vma[i].shmid); } - np->mmap_next = myproc()->mmap_next; + np->mm->mmap_next = myproc()->mm->mmap_next; + mapname_copy(np->mm, pm); + mm_unlock(pm); memmove(np->tf, myproc()->tf, sizeof(struct trapframe)); @@ -479,17 +735,20 @@ fork(void) // child starts with no pending signals of its own, though — those // aren't inherited. allocproc() already zeroed pending_signals/ // in_signal/saved_sigtf for this fresh slot. - memmove(np->sig_handler, myproc()->sig_handler, sizeof(np->sig_handler)); - memmove(np->sig_restorer, myproc()->sig_restorer, sizeof(np->sig_restorer)); + memmove(np->sighand->handler, myproc()->sighand->handler, sizeof(np->sighand->handler)); + memmove(np->sighand->flags, myproc()->sighand->flags, sizeof(np->sighand->flags)); + memmove(np->sighand->restorer, myproc()->sighand->restorer, sizeof(np->sighand->restorer)); np->signal_mask = myproc()->signal_mask; np->tf->rax = 0; + files_lock(); for(i = 0; i < NOFILE; i++) - if(myproc()->ofile[i]) { - np->ofile[i] = filedup(myproc()->ofile[i]); - np->ofile_flags[i] = myproc()->ofile_flags[i]; + if(myproc()->files->ofile[i]) { + np->files->ofile[i] = filedup(myproc()->files->ofile[i]); + np->files->flags[i] = myproc()->files->flags[i]; } + files_unlock(); // POSIX: the child inherits process group, session and all four ids. np->pgid = myproc()->pgid; np->sid = myproc()->sid; @@ -502,6 +761,8 @@ fork(void) np->ctty = myproc()->ctty; np->uid = myproc()->uid; np->euid = myproc()->euid; np->suid = myproc()->suid; np->gid = myproc()->gid; np->egid = myproc()->egid; np->sgid = myproc()->sgid; + np->ngroups = myproc()->ngroups; + memmove(np->groups, myproc()->groups, sizeof(np->groups)); np->cwd = idup(myproc()->cwd); np->root = myproc()->root ? idup(myproc()->root) : 0; @@ -509,6 +770,8 @@ fork(void) np->fs_base = myproc()->fs_base; safestrcpy(np->name, myproc()->name, sizeof(myproc()->name)); + np->strace = myproc()->strace; + np->umask = myproc()->umask; // The child inherits the parent's place in the queue, priority and all. // @@ -623,12 +886,499 @@ proc_reincarnate(int status) return 0; } +// --- Threads --------------------------------------------------------------- + +static void sched_enqueue(struct proc *p); + +static int reap_chan; + +// Every other live thread of self's group: killed, and woken so it notices. +// Non-leaders leave as threads; the leader, if it is one of them, leaves as +// the process. With ptable.lock. +static void +group_kill(struct proc *self) +{ + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) { + if (q == self || q->tgid != self->tgid) + continue; + if (q->state == UNUSED || q->state == ZOMBIE || q->state == EMBRYO) + continue; + q->killed = 1; + if (q->tgid != q->pid) + q->thread_dying = 1; + if (q->state == SLEEPING) { + q->state = RUNNABLE; + sched_enqueue(q); + } + } +} + +// A non-leader thread ending the program: the leader exits with its status. +static void +group_exit(struct proc *p, int status) +{ + acquire(&ptable.lock); + struct proc *l = proc_leader(p); + if (l != p && !l->group_status_set) { + l->group_status = status; + l->group_status_set = 1; + } + group_kill(p); + release(&ptable.lock); +} + +// Live threads in group tgid, the caller included. +int +proc_thread_count(int tgid) +{ + int n = 0; + acquire(&ptable.lock); + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) + if (q->tgid == tgid && q->state != UNUSED && q->state != ZOMBIE) + n++; + release(&ptable.lock); + return n; +} + +// A vfork parent waits until its child has its own address space or is gone. +static void +vfork_release(struct proc *p) +{ + struct proc *vp = p->vfork_parent; + if (vp == 0) + return; + acquire(&ptable.lock); + if (vp->vfork_wait == p->pid) { + vp->vfork_wait = 0; + wakeup1(&vp->vfork_wait); + } + p->vfork_parent = 0; + release(&ptable.lock); +} + +void +proc_exec_done(void) +{ + vfork_release(myproc()); +} + +// An exec leaves one thread: the others die, and the caller becomes the +// process if it was not already. Dispositions are unshared, ignoring stays +// ignored and handlers reset -- exec does the resetting on its own copy. +void +proc_exec_threads(void) +{ + struct proc *p = myproc(); + + acquire(&ptable.lock); + group_kill(p); + if (p->tgid != p->pid) { + struct proc *l = proc_leader(p); + p->parent = l->parent; + p->tgid = p->pid; + p->autoreap = 0; + } + release(&ptable.lock); + + if (p->sighand->ref > 1) { + struct sighand *s = sighand_alloc(); + if (s) { + memmove(s->handler, p->sighand->handler, sizeof(s->handler)); + memmove(s->flags, p->sighand->flags, sizeof(s->flags)); + sighand_put(p->sighand); + p->sighand = s; + } + } +} + +// CLONE_CHILD_CLEARTID: the word pthread_join waits on. +static void +clear_tid(struct proc *p) +{ + if (p->clear_child_tid == 0 || p->pgdir == 0) + return; + int *k = (int*)uva2ka(p->pgdir, (char*)p->clear_child_tid); + if (k && ((addr_t)k & 3) == 0) { + *k = 0; + futex_wake_key(k, 1); + } + p->clear_child_tid = 0; +} + +// The end of one thread. Its descriptors and directories go now; its slot and +// its hold on the address space go when reapd sees it is off the CPU. +void +thread_exit(void) +{ + struct proc *p = myproc(); + + clear_tid(p); + vfork_release(p); + files_put(p->files); + p->files = 0; + begin_op(); + if (p->cwd) + iput(p->cwd); + if (p->root) + iput(p->root); + end_op(); + p->cwd = 0; + p->root = 0; + + acquire(&ptable.lock); + p->autoreap = 1; + p->state = ZOMBIE; + wakeup1(&reap_chan); + sched(); + panic("zombie thread exit"); +} + +// Frees threads that have exited. A kernel thread because the last reference +// to an address space may be dropped here, and freeing one is not something +// to do inside the scheduler or under ptable.lock. +static void +reapd(void) +{ + acquire(&ptable.lock); + for (;;) { + struct proc *p, *found = 0; + for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) + if (p->state == ZOMBIE && p->autoreap) { + found = p; + break; + } + if (!found) { + sleep(&reap_chan, &ptable.lock); + continue; + } + p = found; + struct mm *m = p->mm; + struct sighand *s = p->sighand; + struct proc *l = proc_leader(p); + if (l != p) { + l->utime += p->utime; // a program's time is all of its threads' + l->stime += p->stime; + } else { + reaped_utime += p->utime; + reaped_stime += p->stime; + } + p->mm = 0; + p->sighand = 0; + p->pgdir = 0; + p->kstack = 0; + p->pid = 0; + p->tgid = 0; + p->parent = 0; + p->name[0] = 0; + p->killed = 0; + p->thread_dying = 0; + p->autoreap = 0; + p->xstate = 0; + p->killsig = 0; + p->pgid = 0; + p->sid = 0; + p->state = UNUSED; + // The leader may have been waiting on its threads to be reapable. + if (l != p && l->state == ZOMBIE && l->parent) + wakeup1(l->parent); + release(&ptable.lock); + mm_put(m); + sighand_put(s); + acquire(&ptable.lock); + } +} + +void +thread_reaper_start(void) +{ + if (kthread_create(reapd, "reapd") < 0) + cprintf("proc: no reaper; exited threads will not be freed\n"); +} + +#define CLONE_FILES_ 0x00000400 +#define CLONE_VFORK_ 0x00004000 +#define CLONE_PARENT_ 0x00008000 +#define CLONE_SETTLS_ 0x00080000 +#define CLONE_PARENT_SETTID_ 0x00100000 +#define CLONE_CHILD_CLEARTID_ 0x00200000 +#define CLONE_CHILD_SETTID_ 0x01000000 + +// clone(2), Linux's argument order: flags, stack, parent_tid, child_tid, tls. +// +// What musl's pthread_create asks for -- VM, FS, FILES, SIGHAND, THREAD, +// SYSVSEM, SETTLS, PARENT_SETTID, CHILD_CLEARTID -- and what its posix_spawn +// asks for -- VM, VFORK and a stack -- are the two shapes that matter. +int +clone_proc(uint64 flags, uint64 stack, uint64 ptid, uint64 ctid, uint64 tls) +{ + struct proc *cur = myproc(), *np; + int i, pid; + + if (!(flags & CLONE_VM)) { + if (stack || (flags & (CLONE_THREAD | CLONE_SIGHAND))) + return -EINVAL; + return fork(); // plain fork, which is all that is left + } + if ((flags & CLONE_THREAD) && !(flags & CLONE_SIGHAND)) + return -EINVAL; + + if ((np = allocproc()) == 0) + return -EAGAIN; + + mm_put(np->mm); + np->mm = cur->mm; + mm_get(cur->mm); + np->pgdir = cur->pgdir; + np->sz = cur->mm->sz; + + if (flags & CLONE_FILES_) { + files_put(np->files); + np->files = cur->files; + files_get(cur->files); + } else { + files_lock(); + for (i = 0; i < NOFILE; i++) + if (cur->files->ofile[i]) { + np->files->ofile[i] = filedup(cur->files->ofile[i]); + np->files->flags[i] = cur->files->flags[i]; + } + files_unlock(); + } + + if (flags & CLONE_SIGHAND) { + sighand_put(np->sighand); + np->sighand = cur->sighand; + sighand_get(cur->sighand); + } else { + memmove(np->sighand->handler, cur->sighand->handler, sizeof(np->sighand->handler)); + memmove(np->sighand->flags, cur->sighand->flags, sizeof(np->sighand->flags)); + memmove(np->sighand->restorer, cur->sighand->restorer, sizeof(np->sighand->restorer)); + } + + fxsave(np->fpu); + memmove(np->tf, cur->tf, sizeof(struct trapframe)); + np->tf->rax = 0; + if (stack) + np->tf->rsp = stack; // the top of the trapframe is the sysret RSP + np->fs_base = (flags & CLONE_SETTLS_) ? tls : cur->fs_base; + + np->syscall_map = cur->syscall_map; + np->map_size = cur->map_size; + np->signal_mask = cur->signal_mask; + np->pgid = cur->pgid; + np->sid = cur->sid; + np->ctty = cur->ctty; + np->uid = cur->uid; np->euid = cur->euid; np->suid = cur->suid; + np->gid = cur->gid; np->egid = cur->egid; np->sgid = cur->sgid; + np->ngroups = cur->ngroups; + memmove(np->groups, cur->groups, sizeof(np->groups)); + np->cwd = idup(cur->cwd); + np->root = cur->root ? idup(cur->root) : 0; + memmove(np->cwd_path, cur->cwd_path, sizeof(cur->cwd_path)); + safestrcpy(np->name, cur->name, sizeof(cur->name)); + safestrcpy(np->exe, cur->exe, sizeof(cur->exe)); + np->strace = cur->strace; + np->umask = cur->umask; + sched_set_nice(np, cur->nice); + np->vruntime = cur->vruntime; + + acquire(&ptable.lock); + struct proc *leader = proc_leader(cur); + if (flags & CLONE_THREAD) { + np->tgid = cur->tgid; + np->parent = leader->parent; + np->autoreap = 1; + } else { + np->tgid = np->pid; + np->parent = (flags & CLONE_PARENT_) ? leader->parent : leader; + } + release(&ptable.lock); + + pid = np->pid; + if (flags & CLONE_CHILD_CLEARTID_) + np->clear_child_tid = ctid; + if (flags & CLONE_PARENT_SETTID_) + put_user(pid, ptid); + if (flags & CLONE_CHILD_SETTID_) + put_user(pid, ctid); // the same address space, so the same word + + if (flags & CLONE_VFORK_) { + np->vfork_parent = cur; + cur->vfork_wait = pid; + } + + __sync_synchronize(); + acquire(&ptable.lock); + np->state = RUNNABLE; + sched_enqueue(np); + // vfork: the parent sleeps until the child has execed or exited, because + // until then they share one stack's worth of memory. + if (flags & CLONE_VFORK_) + while (cur->vfork_wait == pid && !cur->killed) + sleep(&cur->vfork_wait, &ptable.lock); + release(&ptable.lock); + return pid; +} + +// --- Futexes --------------------------------------------------------------- +// +// futex() used to say EAGAIN to every wait and succeed at every wake, which +// was enough while there was one thread per process. A waiter is keyed by the +// kernel address of the word -- the physical page, in effect -- so two +// mappings of one shared page meet on the same key, as they must. + +int +futex_wake_key(void *k, int n) +{ + int woke = 0; + acquire(&ptable.lock); + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC] && woke < n; q++) { + if (q->futex_key != k || q->futex_woken) + continue; + q->futex_woken = 1; + woke++; + if (q->state == SLEEPING) { + q->state = RUNNABLE; + sched_enqueue(q); + } + } + release(&ptable.lock); + return woke; +} + +static int * +futex_word(addr_t uaddr) +{ + if (uaddr & 3) + return 0; + return (int*)uva2ka(myproc()->pgdir, (char*)uaddr); +} + +// Wait while *uaddr == val, for at most `timeout` ticks (0: no limit). +static int +futex_wait(addr_t uaddr, int val, uint64 timeout) +{ + struct proc *p = myproc(); + int *k = futex_word(uaddr); + int r; + + if (k == 0) + return -EFAULT; + acquire(&ptable.lock); + // Checked under the lock every waker takes, so a wake between the check + // and the sleep cannot be missed. + if (*(volatile int*)k != val) { + release(&ptable.lock); + return -EAGAIN; + } + p->futex_key = k; + p->futex_woken = 0; + uint t0 = ticks; + for (;;) { + if (p->futex_woken) { r = 0; break; } + if (p->killed || sig_attention(p)) { r = -EINTR; break; } + if (timeout && (uint64)(uint)(ticks - t0) >= timeout) { r = -ETIMEDOUT; break; } + sleep(timeout ? (void*)&ticks : (void*)k, &ptable.lock); + } + p->futex_key = 0; + release(&ptable.lock); + return r; +} + +// Move up to n waiters from one word to another, still waiting. +static int +futex_requeue(int *from, int *to, int n) +{ + int moved = 0; + acquire(&ptable.lock); + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC] && moved < n; q++) + if (q->futex_key == from && !q->futex_woken) { + q->futex_key = to; + moved++; + } + release(&ptable.lock); + return moved; +} + +static uint64 +ts_ticks(const struct timespec *ts) +{ + if (ts->tv_sec < 0 || ts->tv_nsec < 0) + return 1; + uint64 t = (uint64)ts->tv_sec * HZ + + ((uint64)ts->tv_nsec * HZ + 999999999) / 1000000000; + return t ? t : 1; +} + +int +futex_op(addr_t uaddr, int op, int val, addr_t utime, addr_t uaddr2, int val3) +{ + int cmd = op & 0x7f; // without PRIVATE and CLOCK_REALTIME + struct timespec ts; + uint64 timeout = 0; + + switch (cmd) { + case 0: // FUTEX_WAIT: relative timeout + if (utime) { + if (copy_from_user(&ts, utime, sizeof(ts)) < 0) + return -EFAULT; + timeout = ts_ticks(&ts); + } + return futex_wait(uaddr, val, timeout); + case 9: { // FUTEX_WAIT_BITSET: absolute timeout + if (utime) { + if (copy_from_user(&ts, utime, sizeof(ts)) < 0) + return -EFAULT; + uint64 sec, usec; + wallclock(&sec, &usec); + if (!(op & 256)) + sec -= boot_epoch; // CLOCK_MONOTONIC unless asked otherwise + long ds = ts.tv_sec - (long)sec, dn = ts.tv_nsec - (long)(usec * 1000); + if (dn < 0) { dn += 1000000000; ds--; } + if (ds < 0) + return -ETIMEDOUT; + ts.tv_sec = ds; + ts.tv_nsec = dn; + timeout = ts_ticks(&ts); + } + return futex_wait(uaddr, val, timeout); + } + case 1: // FUTEX_WAKE + case 10: { // FUTEX_WAKE_BITSET + int *k = futex_word(uaddr); + return k ? futex_wake_key(k, val) : -EFAULT; + } + case 3: // FUTEX_REQUEUE + case 4: { // FUTEX_CMP_REQUEUE + int *k = futex_word(uaddr), *k2 = futex_word(uaddr2); + if (!k || !k2) + return -EFAULT; + if (cmd == 4 && *(volatile int*)k != val3) + return -EAGAIN; + int n = futex_wake_key(k, val); + return n + futex_requeue(k, k2, (int)utime); // val2 rides in utime + } + } + return -ENOSYS; +} + void exit(int status) { struct proc *p; int fd; + // Killed by a fault of its own -- the trapframe holds an exception, not a + // syscall -- so say which file it was executing. The register dump from + // trap() gives addresses; this gives the library they are in. + { + int sig = status & 0x7f; + struct trapframe *tf = myproc()->tf; + if ((sig == SIGSEGV || sig == SIGBUS || sig == SIGILL || sig == SIGFPE) && + tf && (tf->cs & 3) == DPL_USER && tf->trapno < 32) + mapname_report(myproc()); + } + if(myproc() == initproc) { // Returning from exit() is the *success* path here. exec() does not fail // to return -- it returns 0 having replaced the address space, and the new @@ -641,6 +1391,23 @@ exit(int status) panic("init resurrection failed: could not exec /bin/schinit"); } + // A thread that is not the leader ends the whole program -- that is what + // exit_group, a fatal signal and a fault mean -- unless it is already dying + // with it, and then leaves as a thread. + if (myproc()->tgid != myproc()->pid) { + if (!myproc()->thread_dying) + group_exit(myproc(), status); + thread_exit(); + } + // The leader: any other thread goes too, and if one of them asked for this + // with exit_group, its status is the program's. + acquire(&ptable.lock); + if (myproc()->group_status_set) + status = myproc()->group_status; + group_kill(myproc()); + release(&ptable.lock); + vfork_release(myproc()); + // Give back any device this process had claimed. A driver that crashed must // not keep its hardware locked away from the process that replaces it -- // that is the entire point of running drivers outside the kernel. @@ -651,13 +1418,10 @@ exit(int status) myproc()->xstate = status; - for(fd = 0; fd < NOFILE; fd++){ - if(myproc()->ofile[fd]){ - fileclose(myproc()->ofile[fd]); - myproc()->ofile[fd] = 0; - myproc()->ofile_flags[fd] = 0; - } - } + // Shared with other threads, the table lives on; the last one closes it. + (void)fd; + files_put(myproc()->files); + myproc()->files = 0; // Record locks belong to the process, not to any one descriptor. lock_exit(myproc()->pid); @@ -717,45 +1481,66 @@ wait(void) // the already-zombie child. mksh then fell back to sigsuspend(), which // this kernel can't wake for a real SIGCHLD (no signal delivery), so it // silently retried forever: a real, low-CPU hang with no error printed. +// Whether any thread of p's group but p still has a slot. With ptable.lock. +static int +group_has_others(struct proc *p) +{ + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) + if (q != p && q->tgid == p->pid && q->state != UNUSED) + return 1; + return 0; +} + int waitpid(int pid, int *status, int options, uint64 *ut, uint64 *st) { struct proc *p; int havekids; + // Children belong to the process, not to the thread that made them: any + // thread may wait for them, and their exit wakes the leader's channel. + struct proc *me = proc_leader(myproc()); acquire(&ptable.lock); for(;;){ havekids = 0; for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ - if(p->parent != myproc()) + if(p->parent != me) continue; // POSIX pid selection: >0 one child, 0 = caller's group, // -1 = any child, < -1 = the process group with id -pid. if(pid > 0 && p->pid != pid) continue; - if(pid == 0 && p->pgid != myproc()->pgid) + if(pid == 0 && p->pgid != me->pgid) continue; if(pid < -1 && p->pgid != -pid) continue; + if(p->tgid != p->pid) + continue; // a thread, which nobody waits for havekids = 1; + if(p->state == ZOMBIE && group_has_others(p)) + continue; // reaped once its last thread is if(p->state == ZOMBIE){ int rpid = p->pid; int rstatus = p->xstate; // copy out before the slot is recycled // RUSAGE_CHILDREN: the child's own time *and* whatever it had already // reaped, which is what makes the total transitive down a whole tree // of processes rather than one generation deep. - myproc()->cutime += p->utime + p->cutime; - myproc()->cstime += p->stime + p->cstime; + me->cutime += p->utime + p->cutime; + me->cstime += p->stime + p->cstime; + reaped_utime += p->utime; + reaped_stime += p->stime; // wait4()'s fourth argument wants the *child's* usage, not the // parent's running total, so it has to be read here -- three lines // further down the slot is UNUSED and these are zero. if (ut) *ut = p->utime + p->cutime; if (st) *st = p->stime + p->cstime; p->kstack = 0; // the slot keeps its stack; see kstacks[] - // Give back anything shared before freevm() decides it owns the - // pages. See proc_release_shared() in kernel/sys/sys_mem.c. - proc_release_shared(p); - freevm(p->pgdir); + // The address space goes with its last user. See mm_put(). + mm_put(p->mm); + p->mm = 0; + p->pgdir = 0; + sighand_put(p->sighand); + p->sighand = 0; p->pid = 0; p->parent = 0; p->name[0] = 0; @@ -764,11 +1549,32 @@ waitpid(int pid, int *status, int options, uint64 *ut, uint64 *st) p->killsig = 0; p->pgid = 0; p->sid = 0; + p->tgid = 0; + p->group_status_set = 0; p->state = UNUSED; release(&ptable.lock); if (status) *status = rstatus; return rpid; } + // WUNTRACED and WCONTINUED: a stop or a resume, each reported once. + if((options & 2) && p->stop_report){ + int rpid = p->pid, rstatus = (p->stop_report << 8) | 0x7f; + p->stop_report = 0; + if (ut) *ut = p->utime; + if (st) *st = p->stime; + release(&ptable.lock); + if (status) *status = rstatus; + return rpid; + } + if((options & 8) && p->cont_report){ + int rpid = p->pid; + p->cont_report = 0; + if (ut) *ut = p->utime; + if (st) *st = p->stime; + release(&ptable.lock); + if (status) *status = 0xffff; + return rpid; + } } // Two different failures, and they used to share one bare -1. @@ -792,7 +1598,7 @@ waitpid(int pid, int *status, int options, uint64 *ut, uint64 *st) return 0; } - sleep(myproc(), &ptable.lock); + sleep(me, &ptable.lock); } } @@ -1183,6 +1989,33 @@ sched_slice_expired(void) return (p->slice_used + ns) >= p->slice_ns; } +// Wait for an interrupt as deeply as the firmware allows. See acpi_idle in +// kernel/acpi/aml.c. Interrupts are off on the way down so that one arriving +// between the decision and the sleep is not lost: a pending interrupt still +// ends the sleep (MWAIT's ECX bit 0 says so explicitly; an I/O-entered C2 +// wakes on it by design), and is taken at the sti after. +static void +cpu_idle(void) +{ + static volatile int monitor_line[16]; + + switch (acpi_idle.kind) { + case ACPI_IDLE_IO: + cli(); + (void)inb((ushort)acpi_idle.addr); + sti(); + break; + case ACPI_IDLE_MWAIT: + cli(); + asm volatile("monitor" : : "a"(monitor_line), "c"(0), "d"(0)); + asm volatile("mwait" : : "a"((uint32)acpi_idle.addr), "c"(1)); + sti(); + break; + default: + hlt(); + } +} + void scheduler(void) { @@ -1199,7 +2032,7 @@ scheduler(void) // hlt() would stop every other CPU from scheduling until an interrupt // happened to arrive here. release(&ptable.lock); - hlt(); + cpu_idle(); continue; } @@ -1423,12 +2256,13 @@ kthread_create(void (*fn)(void), const char *name) return -1; if ((p->pgdir = setupkvm()) == 0) { - p->kstack = 0; // the slot keeps its stack; see kstacks[] - p->state = UNUSED; + fork_undo(p); return -1; } p->sz = PGSIZE; // never used; 0 upsets code that treats it as a size + p->mm->sz = p->sz; + p->mm->pgdir = p->pgdir; p->parent = 0; p->cwd = 0; p->kfn = fn; @@ -1513,7 +2347,7 @@ wakeup(void *chan) static int init_ignores(struct proc *p, int sig) { - return p == initproc && p->sig_handler[sig] == 0; + return p == initproc && p->sighand->handler[sig] == 0; } // Whether a signal's default action is to end the process. @@ -1541,7 +2375,7 @@ sig_kills_now(struct proc *p, int sig) return 1; if(p->signal_mask & (1ULL << sig)) return 0; - return p->sig_handler[sig] == 0 && sig_default_kills(sig); + return p->sighand->handler[sig] == 0 && sig_default_kills(sig); } // SIGALRM, on time. @@ -1591,6 +2425,7 @@ alarm_thread(void) sleep(&ticks, &tickslock); uint now = ticks; release(&tickslock); + vm_defer_tick(); // pages waiting out other CPUs' TLBs if (alarms_armed == 0) continue; @@ -1635,8 +2470,170 @@ alarm_thread_start(void) cprintf("alarm: no kernel thread; alarm() and setitimer() will not fire\n"); } +// --- Stopping and continuing -------------------------------------------- +// +// SIGSTOP, and SIGTSTP/SIGTTIN/SIGTTOU with no handler, stop a process; SIGCONT +// resumes it. Neither existed: Ctrl-Z did nothing, and mc -- which stops its +// subshell and waits to be told it has stopped -- hung before drawing. +// +// The stop itself happens on the way back to user mode, in deliver_signals(), +// where the process can sleep. SIGCONT acts when it is sent, because a +// stopped process will not reach that point by itself. + int -sigsend(int pid, int sig) +sig_default_stops(int sig) +{ + return sig == SIGSTOP || sig == SIGTSTP || sig == SIGTTIN || sig == SIGTTOU; +} + +#define STOP_SIGS ((1ULL << SIGSTOP) | (1ULL << SIGTSTP) | \ + (1ULL << SIGTTIN) | (1ULL << SIGTTOU)) + +// Tell the parent about a stop or a resume: SIGCHLD, and a wake for a wait() +// or a sigsuspend() that is waiting to hear it. Called with ptable.lock. +static void +notify_parent(struct proc *p) +{ + struct proc *pp = p->parent; + if(pp == 0) + return; + pp->pending_signals |= 1ULL << SIGCHLD; + if(pp->state == SLEEPING){ + pp->state = RUNNABLE; + sched_enqueue(pp); + } +} + +// The side effects of sending sig to p, beyond setting it pending. With +// ptable.lock held. +static void +sig_job_control(struct proc *p, int sig) +{ + if(sig == SIGCONT){ + // A resume cancels any stop still on its way, and the other way round. + p->pending_signals &= ~STOP_SIGS; + if(p->stopsig){ + p->stopsig = 0; + p->stop_report = 0; + p->cont_report = 1; + notify_parent(p); + } + } else if(sig_default_stops(sig)){ + p->pending_signals &= ~(1ULL << SIGCONT); + } +} + +// Stop the calling process with sig until SIGCONT or SIGKILL. +void +proc_stop(int sig) +{ + struct proc *p = myproc(); + + acquire(&ptable.lock); + p->stopsig = sig; + p->stop_report = sig; + p->cont_report = 0; + notify_parent(p); + // Other signals wake it too; only a resume or a kill ends the stop. + while(p->stopsig && !p->killed) + sleep(&p->stopsig, &ptable.lock); + p->stopsig = 0; + release(&ptable.lock); +} + +// A handler that ends a blocking call. Installed with SA_RESTART -- which +// musl's signal() always asks for -- the call goes on waiting instead, and +// the handler runs when it returns: Linux restarts the call around the +// handler, and a program that asked for that does not check for EINTR. +#define SA_RESTART 0x10000000 +static int +sig_interrupts(struct proc *p, int sig) +{ + return p->sighand->handler[sig] > 1 && !(p->sighand->flags[sig] & SA_RESTART); +} + +// Whether a pending signal should end a blocking wait: one that will stop the +// process, or one with a handler to run. A lockless look, for the inside of a +// sleep loop that holds some other lock; sig_blocking_check() does the work. +int +sig_attention(struct proc *p) +{ + uint64 d = p->pending_signals & ~p->signal_mask; + if(d == 0) + return 0; + for(int sig = 1; sig < 32; sig++) + if((d & (1ULL << sig)) && + (sig == SIGSTOP || sig_interrupts(p, sig) || + (sig_default_stops(sig) && p->sighand->handler[sig] == 0))) + return 1; + return 0; +} + +// For a blocking call that saw sig_attention(), with no locks held. A stop is +// taken here, and the call carries on waiting once continued -- which is what +// Linux's restart does, and what lets Ctrl-Z stop a `sleep` or a `cat`. A +// signal with a handler ends the call with -EINTR, as POSIX has it. +int +sig_blocking_check(void) +{ + struct proc *p = myproc(); + + for(;;){ + int stop = 0, caught = 0; + acquire(&ptable.lock); + uint64 d = p->pending_signals & ~p->signal_mask; + for(int sig = 1; sig < 32 && !stop; sig++){ + if(!(d & (1ULL << sig))) + continue; + if(sig == SIGSTOP || (sig_default_stops(sig) && p->sighand->handler[sig] == 0)){ + p->pending_signals &= ~(1ULL << sig); + stop = sig; + } else if(sig_interrupts(p, sig)){ + caught = 1; + } + } + release(&ptable.lock); + if(stop){ + proc_stop(stop); + if(p->killed) + return -EINTR; + continue; + } + return (caught || p->killed) ? -EINTR : 0; + } +} + +// The other threads of p's group, for a stop or a resume. With ptable.lock. +static void +sig_to_threads(struct proc *p, int sig) +{ + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) { + if (q == p || q->tgid != p->tgid || q->state == UNUSED || q->state == ZOMBIE) + continue; + q->pending_signals |= 1ULL << sig; + sig_job_control(q, sig); + if (q->state == SLEEPING) { + q->state = RUNNABLE; + sched_enqueue(q); + } + } +} + +// May `from` signal `to`? Root may; anyone else only a process whose real or +// saved user id is one of their own ids -- kill(2)'s rule. +static int +sig_permitted(struct proc *from, struct proc *to) +{ + if(from == 0 || from->euid == 0) + return 1; + return from->uid == to->uid || from->uid == to->suid || + from->euid == to->uid || from->euid == to->suid; +} + +// `check` is for a signal a process sends on purpose, by kill(2). The kernel's +// own -- SIGPIPE, the terminal's SIGINT -- is nobody's to refuse. +static int +sigsend_x(int pid, int sig, int check) { struct proc *p; @@ -1646,11 +2643,19 @@ sigsend(int pid, int sig) acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->pid == pid && p->state != UNUSED && p->state != ZOMBIE){ + if(check && !sig_permitted(myproc(), p)){ + release(&ptable.lock); + return -2; + } if(init_ignores(p, sig)){ release(&ptable.lock); return 0; // silently dropped, as POSIX requires } p->pending_signals |= 1ULL << sig; + sig_job_control(p, sig); + // A stop or a resume is the whole program's, so every thread gets it. + if(sig == SIGCONT || sig_default_stops(sig)) + sig_to_threads(p, sig); if(sig_kills_now(p, sig)) p->killed = 1; if(sig == SIGKILL) @@ -1669,16 +2674,29 @@ sigsend(int pid, int sig) return -1; } +int +sigsend(int pid, int sig) +{ + return sigsend_x(pid, sig, 0); +} + +// -1 if there is no such process, -2 if there is and the caller may not. +int +sigsend_user(int pid, int sig) +{ + return sigsend_x(pid, sig, 1); +} + // Deliver a signal to every member of a process group. This is what job // control actually needs: a shell sends SIGINT/SIGTSTP/SIGHUP to a *group*, // never to individual pids. sys_kill() with pid <= 0 routes here. // // Returns 0 if at least one process was signalled, -1 if the group is empty. -int -sigsend_pgrp(int pgid, int sig) +static int +sigsend_pgrp_x(int pgid, int sig, int check) { struct proc *p; - int found = 0; + int found = 0, denied = 0; if(sig < 0 || sig >= 32) return -1; @@ -1689,12 +2707,22 @@ sigsend_pgrp(int pgid, int sig) continue; if(p->pgid != pgid) continue; + if(check && !sig_permitted(myproc(), p)){ + denied = 1; + continue; + } found = 1; if(sig == 0) continue; // signal 0 is an existence probe only if(init_ignores(p, sig)) continue; // see init_ignores(): this is the Ctrl-C case + // A group signal is the process's, and goes to the leader -- once, not + // once per thread, or a SIGWINCH handler would run in each of them. A + // stop or a resume is the exception: every thread has to stop. + if(p->tgid != p->pid && sig != SIGCONT && !sig_default_stops(sig)) + continue; p->pending_signals |= 1ULL << sig; + sig_job_control(p, sig); if(sig_kills_now(p, sig)){ p->killed = 1; if(p->killsig == 0) @@ -1712,7 +2740,19 @@ sigsend_pgrp(int pgid, int sig) } release(&ptable.lock); - return found ? 0 : -1; + return found ? 0 : denied ? -2 : -1; +} + +int +sigsend_pgrp(int pgid, int sig) +{ + return sigsend_pgrp_x(pgid, sig, 0); +} + +int +sigsend_pgrp_user(int pgid, int sig) +{ + return sigsend_pgrp_x(pgid, sig, 1); } // Look up a process's process-group id. Returns -1 if no such live process. @@ -1754,7 +2794,7 @@ proc_setpgid(int pid, int pgid) continue; // A caller may only move itself or one of its own children, and never // across a session boundary. - if(p != myproc() && p->parent != myproc()) + if(p != myproc() && p->parent != proc_leader(myproc())) r = -ESRCH; else if(p->sid != myproc()->sid) r = -EPERM; @@ -1786,6 +2826,11 @@ proc_setsid(void) } myproc()->sid = myproc()->pid; myproc()->pgid = myproc()->pid; + // A new session starts with no controlling terminal; that is what lets the + // first terminal it opens become one. Keeping the old one meant a tmux pane + // still had tty1 as its /dev/tty, and zsh and htop drew straight onto the + // console underneath tmux instead of into their pane. + myproc()->ctty = 0; r = myproc()->sid; release(&ptable.lock); return r; @@ -1841,6 +2886,10 @@ procdump(void) for(i=0; i<10 && pc[i] != 0; i++) cprintf(" %p", pc[i]); } + // Where a thread that is not asleep last was in user space: the question + // when a program spins without making a system call. + if((p->state == RUNNABLE || p->state == RUNNING) && p->tf && p->pgdir) + cprintf(" user rip %p rsp %p", p->tf->rip, p->tf->rsp); cprintf("\n"); } } @@ -1857,6 +2906,38 @@ procdump(void) // Fill `out` with the `idx`-th live process (0-based, in table order). // Returns 0 on success, -1 when idx is past the end. +// proc_snapshot without the lock, and only the fields a crash screen lists. +// For GMMOS: the lock may be held by a processor the panic stopped, and a torn +// read of one line is a far smaller loss than a crash screen that hangs. +int +proc_peek(int idx, struct procinfo *out) +{ + struct proc *p; + int n = 0; + + for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { + if (p->state == UNUSED) + continue; + if (n++ != idx) + continue; + out->pid = p->pid; + out->ppid = p->parent ? p->parent->tgid : 0; + out->tgid = p->tgid; + out->nthreads = 0; + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) + if (q->tgid == p->tgid && q->state != UNUSED && q->state != ZOMBIE) + out->nthreads++; + if (out->nthreads == 0) + out->nthreads = 1; + out->state = p->state; + out->sz = p->sz; + out->killed = p->killed; + safestrcpy(out->name, p->name, sizeof(out->name)); + return 0; + } + return -1; +} + int proc_snapshot(int idx, struct procinfo *out) { @@ -1870,7 +2951,14 @@ proc_snapshot(int idx, struct procinfo *out) if (n++ != idx) continue; out->pid = p->pid; - out->ppid = p->parent ? p->parent->pid : 0; + out->ppid = p->parent ? p->parent->tgid : 0; + out->tgid = p->tgid; + out->nthreads = 0; + for (struct proc *q = ptable.proc; q < &ptable.proc[NPROC]; q++) + if (q->tgid == p->tgid && q->state != UNUSED && q->state != ZOMBIE) + out->nthreads++; + if (out->nthreads == 0) + out->nthreads = 1; out->pgid = p->pgid; out->sid = p->sid; out->uid = p->uid; @@ -1888,6 +2976,8 @@ proc_snapshot(int idx, struct procinfo *out) out->nivcsw = p->nivcsw; safestrcpy(out->name, p->name, sizeof(out->name)); safestrcpy(out->exe, p->exe, sizeof(out->exe)); + out->kthread = p->kfn != 0; + out->stopped = p->stopsig != 0; release(&ptable.lock); return 0; } @@ -1921,8 +3011,8 @@ proc_fd_mask(int pid, uint64 mask[2]) for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { if (p->state == UNUSED || p->pid != pid) continue; - for (int fd = 0; fd < NOFILE && fd < 128; fd++) - if (p->ofile[fd]) + for (int fd = 0; fd < NOFILE && fd < 128 && p->files; fd++) + if (p->files->ofile[fd]) mask[fd / 64] |= 1ULL << (fd % 64); r = 0; break; @@ -1943,8 +3033,8 @@ proc_fd_link(int pid, int fd, char *buf, int cap) for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { if (p->state == UNUSED || p->pid != pid) continue; - if (p->ofile[fd]) - r = file_describe(p->ofile[fd], buf, cap); + if (p->files && p->files->ofile[fd]) + r = file_describe(p->files->ofile[fd], buf, cap); break; } release(&ptable.lock); @@ -1987,3 +3077,45 @@ proc_kill_init(void) if (initproc) initproc->killed = 1; } + +// Ticks spent in user and kernel mode by every process, living or reaped. +// What /proc/stat's busy columns are made of; the rest of the time was idle. +void +proc_cputime(uint64 *user, uint64 *sys) +{ + struct proc *p; + uint64 u, s; + + acquire(&ptable.lock); + u = reaped_utime; + s = reaped_stime; + for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) + if (p->state != UNUSED) { + u += p->utime; + s += p->stime; + } + release(&ptable.lock); + *user = u; + *sys = s; +} + +// /proc//maps: see mapname_gen(). 0 bytes for a pid that is gone. +int +proc_maps(int pid, char *buf, int cap) +{ + struct mm *m = 0; + + acquire(&ptable.lock); + for (struct proc *p = ptable.proc; p < &ptable.proc[NPROC]; p++) + if (p->pid == pid && p->state != UNUSED && p->state != ZOMBIE && p->mm) { + m = p->mm; + mm_get(m); + break; + } + release(&ptable.lock); + if (!m) + return 0; + int n = mapname_gen(m, buf, cap); + mm_put(m); + return n; +} diff --git a/kernel/sys/sys_compat.c b/kernel/sys/sys_compat.c index 6e67b34..56a3509 100644 --- a/kernel/sys/sys_compat.c +++ b/kernel/sys/sys_compat.c @@ -32,10 +32,12 @@ sys_enable_linux_abi(void) addr_t sys_set_tid_address(void) { + // The word zeroed and woken when this thread exits -- what pthread_join + // waits on. It used to be *written*, with the tid, which is CHILD_SETTID's + // job and not this one's. addr_t tidp; argaddr(0, &tidp); - if (tidp) - put_user(myproc()->pid, tidp); + myproc()->clear_child_tid = tidp; return myproc()->pid; } @@ -46,12 +48,15 @@ addr_t sys_set_robust_list(void) { return 0; } addr_t sys_futex(void) { - int op; + addr_t uaddr, utime, uaddr2; + int op, val, val3; + argaddr(0, &uaddr); argint(1, &op); - op &= ~(FUTEX_WAIT_PRIVATE - FUTEX_WAIT); - if (op == FUTEX_WAKE || op == FUTEX_WAKE_PRIVATE) - return 0; - return -EAGAIN; // no blocking, musl retries + argint(2, &val); + argaddr(3, &utime); + argaddr(4, &uaddr2); + argint(5, &val3); + return futex_op(uaddr, op, val, utime, uaddr2, val3); } // arch_prctl: save FS_BASE for TLS. diff --git a/kernel/sys/sys_event.c b/kernel/sys/sys_event.c index dd0b0d3..f4b353f 100644 --- a/kernel/sys/sys_event.c +++ b/kernel/sys/sys_event.c @@ -1,5 +1,5 @@ -// eventfd, timerfd, signalfd and epoll: the descriptors an event loop is -// built from. +// eventfd, timerfd, signalfd, epoll and inotify: the descriptors an event +// loop is built from. // // Four kinds of file with no inode behind them, all FD_EVENT, told apart by // struct evobj's kind. Each one is a counter or a set that read() drains and @@ -30,8 +30,11 @@ #include "uapi/fcntl.h" #include "uapi/stat.h" #include "uapi/errno.h" +#include "uapi/inotify.h" -enum { EV_EVENTFD = 1, EV_TIMERFD, EV_SIGNALFD, EV_EPOLL }; +enum { EV_EVENTFD = 1, EV_TIMERFD, EV_SIGNALFD, EV_EPOLL, EV_INOTIFY }; + +struct inotify; // Linux's flag values, which are O_NONBLOCK and O_CLOEXEC for every one of // these: one spelling per meaning, so they are tested as such. @@ -79,6 +82,7 @@ struct epitem { struct file *f; // checked against the table, never dereferenced alone uint32 events; uint64 data; + uint seq; // EPOLLET: poll_generation() when last reported int was_ready; // for EPOLLET: report on becoming ready, not on being int disabled; // EPOLLONESHOT fired, until EPOLL_CTL_MOD }; @@ -98,6 +102,7 @@ struct evobj { } tfd; struct { uint64 mask; } sfd; // kernel bit layout, as signal_mask struct { int n; struct epitem *items; } ep; + struct inotify *in; }; }; @@ -143,7 +148,7 @@ ev_open(struct evobj *o, int flags, int readable, int writable) return -EMFILE; } if (flags & O_CLOEXEC) - myproc()->ofile_flags[fd] |= FD_CLOEXEC; + myproc()->files->flags[fd] |= FD_CLOEXEC; return fd; } @@ -163,7 +168,7 @@ ev_arg(int n, int kind, struct file **pf) { int fd; struct file *f; - if (argint(n, &fd) < 0 || fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd])) + if (argint(n, &fd) < 0 || fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd])) return 0; if (f->type != FD_EVENT || f->ev->kind != kind) return 0; @@ -181,14 +186,21 @@ ev_kind_name(struct evobj *o) case EV_TIMERFD: return "anon_inode:[timerfd]"; case EV_SIGNALFD: return "anon_inode:[signalfd]"; case EV_EPOLL: return "anon_inode:[eventpoll]"; + case EV_INOTIFY: return "anon_inode:inotify"; } return "anon_inode:[event]"; } +static void in_free(struct inotify *in); +static int in_pending(struct inotify *in); +static int in_read(struct file *f, struct inotify *in, char *addr, int n); + // Called by fileclose() for the last reference. void ev_close(struct evobj *o) { + if (o->kind == EV_INOTIFY) + in_free(o->in); if (o->kind == EV_EPOLL && o->ep.items) kfree((char *)o->ep.items); kmfree(o); @@ -243,6 +255,10 @@ ev_poll(struct file *f, int events) if (myproc()->pending_signals & o->sfd.mask) rev |= POLLIN; break; + case EV_INOTIFY: + if (in_pending(o->in)) + rev |= POLLIN; + break; } releasesleep(&o->lock); @@ -260,6 +276,9 @@ ev_read(struct file *f, char *addr, int n) { struct evobj *o = f->ev; + if (o->kind == EV_INOTIFY) + return in_read(f, o->in, addr, n); + for (;;) { acquiresleep(&o->lock); switch (o->kind) { @@ -497,7 +516,7 @@ signalfd_common(int fd, addr_t umask, uint64 size, int flags) // An existing signalfd: change its mask, keep the descriptor. if (fd != -1) { struct file *f; - if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd]) || + if (fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd]) || f->type != FD_EVENT || f->ev->kind != EV_SIGNALFD) return -EINVAL; acquiresleep(&f->ev->lock); @@ -606,9 +625,9 @@ sys_epoll_ctl(void) return -EBADF; if (argint(1, &op) < 0 || argint(2, &fd) < 0 || argaddr(3, &uev) < 0) return -EINVAL; - if (fd < 0 || fd >= NOFILE || !myproc()->ofile[fd]) + if (fd < 0 || fd >= NOFILE || !myproc()->files->ofile[fd]) return -EBADF; - struct file *tf = myproc()->ofile[fd]; + struct file *tf = myproc()->files->ofile[fd]; // A set cannot watch itself. (Nor contain a set that contains it; that // check is the deep one Linux does, and nothing here builds such loops.) if (tf == ef) @@ -631,7 +650,8 @@ sys_epoll_ctl(void) case EPOLL_CTL_ADD: if (i >= 0) { r = -EEXIST; break; } if (o->ep.n == EP_MAX) { r = -ENOSPC; break; } - o->ep.items[o->ep.n++] = (struct epitem){ fd, tf, ev.events, ev.data, 0, 0 }; + o->ep.items[o->ep.n++] = (struct epitem){ .fd = fd, .f = tf, .events = ev.events, + .data = ev.data }; break; case EPOLL_CTL_MOD: if (i < 0) { r = -ENOENT; break; } @@ -658,11 +678,14 @@ static int ep_ready(struct evobj *o, struct epoll_event *out, int max) { int got = 0; + // Taken before any descriptor is looked at, so a kick that lands during the + // scan is newer than what is recorded and is reported next time. + uint gen = poll_generation(); acquiresleep(&o->lock); for (int i = 0; i < o->ep.n && got < max; ) { struct epitem *it = &o->ep.items[i]; - struct file *now = (it->fd >= 0 && it->fd < NOFILE) ? myproc()->ofile[it->fd] : 0; + struct file *now = (it->fd >= 0 && it->fd < NOFILE) ? myproc()->files->ofile[it->fd] : 0; if (now != it->f) { ep_drop(o, i); // closed: it leaves the set continue; @@ -681,12 +704,21 @@ ep_ready(struct evobj *o, struct epoll_event *out, int max) it->was_ready = 0; continue; } - if ((it->events & EPOLLET) && it->was_ready) + // Edge-triggered: an edge is new data, not only a not-ready-to-ready + // change. A descriptor still ready since it was last reported is + // reported again if anything has signalled since -- possibly spuriously, + // which EPOLLET allows; never missed, which it does not. Xorg watches its + // clients this way. Waiting for a not-ready scan in between lost the + // edge whenever X read part of a client's requests and more arrived + // before it looked again: on one CPU that almost never happened, on + // several it hung the desktop -- X waiting on the client, the client on X. + if ((it->events & EPOLLET) && it->was_ready && it->seq == gen) continue; if (out) { out[got].events = (uint32)rev; out[got].data = it->data; it->was_ready = 1; + it->seq = gen; if (it->events & EPOLLONESHOT) it->disabled = 1; } @@ -751,3 +783,394 @@ sys_epoll_pwait(void) { return sys_epoll_wait(); } + +// --- inotify ------------------------------------------------------------------- +// +// Watches name an inode by device and number. The filesystem calls +// inotify_event() as it creates, removes, renames and changes things -- for +// the inode itself, and for the directory it is in, with the name -- and every +// watch on that inode gets the event queued on its descriptor. +// +// The hooks cost one load when nothing is watched anywhere, which is the +// usual case. GLib watches a directory for every GSettings and file monitor +// it keeps, and without inotify at all it found no file monitor and GNOME +// programs aborted at start. + +// The masks are in uapi/inotify.h. + +struct inwatch { + int wd; + uint dev; + uint64 inum; + uint32 mask; +}; + +// One page for the instance and its watches, one for the queue. +#define IN_QBYTES PGSIZE +#define IN_MAXW ((int)((PGSIZE - 64) / sizeof(struct inwatch))) + +struct inotify { + struct inotify *next; // in_list + int nw, next_wd; + int qlen, overflowed; + char *q; + struct inwatch w[]; +}; + +struct in_event { + int wd; + uint32 mask, cookie, len; +}; + +static struct spinlock in_lock; +static int in_lock_ready; +static struct inotify *in_list; +static int in_nwatch; // across every instance: the fast path +static uint32 in_cookie; + +static void +in_lock_once(void) +{ + if (!in_lock_ready) { + initlock(&in_lock, "inotify"); + in_lock_ready = 1; + } +} + +// Queue one event. in_lock held. Linux's padding: the name, its NUL, and +// zeros up to a multiple of the header's size. +static void +in_queue(struct inotify *in, int wd, uint32 mask, uint32 cookie, const char *name) +{ + int nlen = name ? strlen(name) + 1 : 0; + int pad = nlen ? (nlen + 15) & ~15 : 0; + int need = (int)sizeof(struct in_event) + pad; + struct in_event e; + + // Room is kept for one overflow event, which says "you missed some". + if (in->qlen + need > IN_QBYTES - (int)sizeof(struct in_event)) { + if (!in->overflowed) { + e.wd = -1; e.mask = IN_Q_OVERFLOW; e.cookie = 0; e.len = 0; + memmove(in->q + in->qlen, &e, sizeof(e)); + in->qlen += sizeof(e); + in->overflowed = 1; + } + return; + } + e.wd = wd; e.mask = mask; e.cookie = cookie; e.len = (uint32)pad; + // The same event as the last one queued is not queued twice, as on Linux: + // a file written in a hundred pieces is one IN_MODIFY until someone reads. + if (in->qlen >= need && !name && pad == 0) { + struct in_event last; + memmove(&last, in->q + in->qlen - sizeof(last), sizeof(last)); + if (last.wd == wd && last.mask == mask && last.cookie == cookie && last.len == 0) + return; + } + memmove(in->q + in->qlen, &e, sizeof(e)); + if (pad) { + memset(in->q + in->qlen + sizeof(e), 0, pad); + memmove(in->q + in->qlen + sizeof(e), name, nlen - 1); + } + in->qlen += need; +} + +static void +in_drop_watch(struct inotify *in, int i) +{ + in_queue(in, in->w[i].wd, IN_IGNORED, 0, 0); + in->w[i] = in->w[--in->nw]; + in_nwatch--; +} + +uint32 +inotify_cookie(void) +{ + in_lock_once(); + acquire(&in_lock); + uint32 c = ++in_cookie; + release(&in_lock); + return c; +} + +// Something happened to inode (dev, inum): to the inode itself when name is +// null, or to the entry `name` in it, a directory. Safe with inode locks +// held; takes a spinlock and nothing else. +void +inotify_event(uint dev, uint64 inum, uint32 mask, const char *name, uint32 cookie) +{ + struct inotify *in; + int queued = 0; + + if (in_nwatch == 0) + return; + in_lock_once(); + acquire(&in_lock); + for (in = in_list; in; in = in->next) { + for (int i = 0; i < in->nw; i++) { + struct inwatch *w = &in->w[i]; + if (w->dev != dev || w->inum != inum) + continue; + if (w->mask & mask & IN_ALL_EVENTS) { + in_queue(in, w->wd, mask, cookie, name); + queued = 1; + if (w->mask & IN_ONESHOT) { + in_drop_watch(in, i--); + continue; + } + } + // The watched thing is gone; so is the watch, and the reader is told. + if (!name && (mask & IN_DELETE_SELF)) { + in_drop_watch(in, i--); + queued = 1; + } + } + } + release(&in_lock); + if (queued) + poll_kick(); +} + +// An event on an open file, for the file and for its directory. The +// directory's needs the name, which only the path the file was opened by +// gives -- looked up again here, and only when something is being watched. +void +inotify_file(struct file *f, uint32 mask) +{ + char name[VFS_NAME_MAX + 1]; + struct inode *dp; + + if (in_nwatch == 0 || f->type != FD_INODE || !f->ip) + return; + inotify_event(f->ip->dev, f->ip->inum, mask, 0, 0); + if (!f->path) + return; + begin_op(); + if ((dp = nameiparent(f->path, name)) != 0) { + inotify_event(dp->dev, dp->inum, mask, name, 0); + iput(dp); + } + end_op(); +} + +static int +in_pending(struct inotify *in) +{ + in_lock_once(); + acquire(&in_lock); + int r = in->qlen > 0; + release(&in_lock); + return r; +} + +static void +in_free(struct inotify *in) +{ + struct inotify **pp; + + in_lock_once(); + acquire(&in_lock); + for (pp = &in_list; *pp; pp = &(*pp)->next) + if (*pp == in) { + *pp = in->next; + break; + } + in_nwatch -= in->nw; + release(&in_lock); + kfree(in->q); + kfree((char *)in); +} + +// As many whole events as fit; EINVAL if not even the first does. +static int +in_read(struct file *f, struct inotify *in, char *addr, int n) +{ + char *tmp; + int take; + + for (;;) { + in_lock_once(); + acquire(&in_lock); + if (in->qlen > 0) + break; + release(&in_lock); + if (f->nonblock) + return -EAGAIN; + if (ev_block() < 0) + return -EINTR; + } + take = 0; + while (take < in->qlen) { + struct in_event e; + memmove(&e, in->q + take, sizeof(e)); + int sz = (int)sizeof(e) + (int)e.len; + if (take + sz > n) + break; + take += sz; + } + if (take == 0) { + release(&in_lock); + return -EINVAL; + } + release(&in_lock); + // Copied out of the queue under the lock into a page of our own, and from + // there to the reader without it: copy_to_user may fault. + if ((tmp = kalloc()) == 0) + return -ENOMEM; + acquire(&in_lock); + memmove(tmp, in->q, take); + memmove(in->q, in->q + take, in->qlen - take); + in->qlen -= take; + if (in->qlen == 0) + in->overflowed = 0; + release(&in_lock); + int r = copy_to_user((addr_t)addr, tmp, take) < 0 ? -EFAULT : take; + kfree(tmp); + return r; +} + +static int +in_create(int flags) +{ + struct evobj *o; + struct inotify *in; + int fd; + + if (flags & ~(O_NONBLOCK | O_CLOEXEC)) + return -EINVAL; + if ((o = ev_new(EV_INOTIFY)) == 0) + return -ENOMEM; + if ((in = (struct inotify *)kalloc()) == 0) { + kmfree(o); + return -ENOMEM; + } + memset(in, 0, PGSIZE); + if ((in->q = kalloc()) == 0) { + kfree((char *)in); + kmfree(o); + return -ENOMEM; + } + in->next_wd = 1; + o->in = in; + // ev_open frees o on failure; past filealloc it does so through fileclose, + // and ev_close frees the instance too. + if ((fd = ev_open(o, flags, 1, 0)) < 0) { + if (fd == -ENFILE) { + kfree(in->q); + kfree((char *)in); + } + return fd; + } + in_lock_once(); + acquire(&in_lock); + in->next = in_list; + in_list = in; + release(&in_lock); + return fd; +} + +addr_t sys_inotify_init(void) { return in_create(0); } + +addr_t +sys_inotify_init1(void) +{ + int flags; + argint(0, &flags); + return in_create(flags); +} + +addr_t +sys_inotify_add_watch(void) +{ + struct evobj *o; + struct inotify *in; + char *path; + uint32 mask; + int err = 0, wd = -1; + uint dev; + uint64 inum; + + if ((o = ev_arg(0, EV_INOTIFY, 0)) == 0) + return -EBADF; + in = o->in; + if (argstr(1, &path) < 0) + return -EFAULT; + argint(2, (int *)&mask); + if (!(mask & IN_ALL_EVENTS)) + return -EINVAL; + if ((mask & IN_MASK_ADD) && (mask & IN_MASK_CREATE)) + return -EINVAL; + if ((err = path_identity(path, !(mask & IN_DONT_FOLLOW), + (mask & IN_ONLYDIR) != 0, &dev, &inum)) < 0) + return err; + + mask &= IN_ALL_EVENTS | IN_ONESHOT | IN_EXCL_UNLINK | IN_MASK_ADD; + in_lock_once(); + acquire(&in_lock); + for (int i = 0; i < in->nw; i++) { + if (in->w[i].dev == dev && in->w[i].inum == inum) { + if (mask & IN_MASK_CREATE) { + release(&in_lock); + return -EEXIST; + } + in->w[i].mask = (mask & IN_MASK_ADD) ? in->w[i].mask | (mask & ~IN_MASK_ADD) + : mask; + wd = in->w[i].wd; + release(&in_lock); + return wd; + } + } + if (in->nw >= IN_MAXW) { + release(&in_lock); + return -ENOSPC; + } + wd = in->next_wd++; + in->w[in->nw].wd = wd; + in->w[in->nw].dev = dev; + in->w[in->nw].inum = inum; + in->w[in->nw].mask = mask & ~IN_MASK_ADD; + in->nw++; + in_nwatch++; + release(&in_lock); + return wd; +} + +addr_t +sys_inotify_rm_watch(void) +{ + struct evobj *o; + struct inotify *in; + int wd; + + if ((o = ev_arg(0, EV_INOTIFY, 0)) == 0) + return -EBADF; + in = o->in; + argint(1, &wd); + in_lock_once(); + acquire(&in_lock); + for (int i = 0; i < in->nw; i++) { + if (in->w[i].wd == wd) { + in_drop_watch(in, i); + release(&in_lock); + poll_kick(); + return 0; + } + } + release(&in_lock); + return -EINVAL; +} + +// FIONREAD: bytes a read would return, for the kinds where that is a number +// worth asking. inotifywait sizes its read buffer by it, and stopped at +// "Not a tty" without. +int +ev_fionread(struct evobj *o) +{ + if (o->kind == EV_INOTIFY) { + in_lock_once(); + acquire(&in_lock); + int n = o->in->qlen; + release(&in_lock); + return n; + } + return -ENOTTY; +} diff --git a/kernel/sys/sys_fs.c b/kernel/sys/sys_fs.c index 1f6eaff..da2c1b1 100644 --- a/kernel/sys/sys_fs.c +++ b/kernel/sys/sys_fs.c @@ -15,9 +15,11 @@ #include "core/uaccess.h" #include "core/kmem.h" #include "uapi/errno.h" +#include "uapi/inotify.h" #include "fs/schfs.h" /* SCHFS_BLKSIZE, SCHFS_NAME_MAX — statfs reports them */ #include "gfx/gfx.h" /* gfx_cols/gfx_rows — TIOCGWINSZ reports them */ #include "dev/vt.h" /* the terminals the TIOC and VT_ ioctls act on */ +#include "dev/pty.h" #include "dev/pciuser.h" #include "dev/keymap.h" /* KB_*LAYOUT — the keyboard layout catalogue */ #include "net/netuser.h" @@ -80,9 +82,13 @@ static uint64 linux_tty_rdev(uint32 major, uint32 minor) { if (major == 1 && minor == 0) return linux_makedev(4, 0); - if (major == 1 && minor >= 11 && minor <= 16) return linux_makedev(4, minor - 10); + if (major == 1 && minor >= 11 && minor <= 22) return linux_makedev(4, minor - 10); if (major == 1 && minor == 10) return linux_makedev(5, 0); if (major == 1 && minor == 1) return linux_makedev(5, 1); + // Pseudo-terminals, as Linux numbers them: ptmx (5, 2), slaves 136. + if (major == PTMX_MAJOR) return linux_makedev(5, 2); + if (major == PTS_MAJOR) return linux_makedev(136, minor); + if (major == PTM_MAJOR) return linux_makedev(128, minor); return linux_makedev(major, minor); } @@ -112,7 +118,13 @@ stati_linux(struct inode *ip, struct linux_stat *st) case T_SOCK: type_bits = LINUX_S_IFSOCK; perm_bits = 0755; break; default: type_bits = LINUX_S_IFREG; perm_bits = (ip->rwmode & 2) ? 0755 : 0555; break; } + // A filesystem that keeps real permission bits -- SchFS does -- reports + // those instead. tmux, ssh and gpg refuse a directory others can read. + (void)perm_bits; + perm_bits = inode_mode_bits(ip); st->st_mode = type_bits | perm_bits; + st->st_uid = ip->uid; + st->st_gid = ip->gid; // Which device this node actually is. Left at zero until now, so every // entry in /dev printed as "0, 0" under ls -l -- harmless while nothing @@ -123,6 +135,16 @@ stati_linux(struct inode *ip, struct linux_stat *st) st->st_atime = ip->atime; st->st_mtime = ip->mtime; st->st_ctime = ip->ctime; } +// The number stat() reports for a device node, split back into major and +// minor. /sys must agree with stat() -- udev matches one against the other. +void +dev_stat_numbers(int major, int minor, int *maj, int *min) +{ + uint64 r = linux_tty_rdev((uint32)major, (uint32)minor); + *maj = (int)(((r >> 8) & 0xfff) | ((r >> 32) & ~0xfffULL)); + *min = (int)((r & 0xff) | ((r >> 12) & ~0xffULL)); +} + // ----------------------------------------------------------------------- // Internal helpers // ----------------------------------------------------------------------- @@ -134,7 +156,7 @@ argfd(int n, int *pfd, struct file **pf) struct file *f; if (argint(n, &fd) < 0) return -1; - if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd])) + if (fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd])) return -1; if (pfd) *pfd = fd; if (pf) *pf = f; @@ -146,13 +168,18 @@ argfd(int n, int *pfd, struct file **pf) int fdalloc(struct file *f) { + // Locked: threads share the table, and two picking the same empty slot + // would each believe they owned it. + files_lock(); for (int fd = 0; fd < NOFILE; fd++) { - if (!myproc()->ofile[fd]) { - myproc()->ofile[fd] = f; - myproc()->ofile_flags[fd] = 0; + if (!myproc()->files->ofile[fd]) { + myproc()->files->ofile[fd] = f; + myproc()->files->flags[fd] = 0; + files_unlock(); return fd; } } + files_unlock(); return -1; } @@ -169,7 +196,7 @@ file_setpath(struct file *f, int dirfd, const char *path) struct file *d; if (dirfd == AT_FDCWD) base = myproc()->cwd_path; - else if (dirfd >= 0 && dirfd < NOFILE && (d = myproc()->ofile[dirfd]) && d->path) + else if (dirfd >= 0 && dirfd < NOFILE && (d = myproc()->files->ofile[dirfd]) && d->path) base = d->path; else return; @@ -209,8 +236,8 @@ static struct inode * at_dir(int dirfd) { if (dirfd == AT_FDCWD) return myproc()->cwd; - if (dirfd < 0 || dirfd >= NOFILE || !myproc()->ofile[dirfd]) return 0; - struct file *f = myproc()->ofile[dirfd]; + if (dirfd < 0 || dirfd >= NOFILE || !myproc()->files->ofile[dirfd]) return 0; + struct file *f = myproc()->files->ofile[dirfd]; if (f->type != FD_INODE) return 0; return f->ip; } @@ -264,9 +291,39 @@ path_toolong(const char *path) return 0; } +// The mode argument of a Linux open/mkdir, or -1 from a native program, whose +// calls have no such argument and leave whatever was in the register. +static int +linux_arg_mode(int n) +{ + int mode; + if (!myproc()->syscall_map) + return -1; + argint(n, &mode); + return mode & 07777; +} + +// May the caller add or remove names in directory `dp`? It takes write and +// search on the directory, and in a sticky one (/tmp) only the owner of the +// file or of the directory may remove or rename a name -- `victim` is the +// inode being removed, or 0 when a name is being added. -EACCES, or -EPERM for +// the sticky rule, as Linux answers them. +static int +dir_may_modify(struct inode *dp, struct inode *victim) +{ + int r = inode_permission(dp, 3, 0); + if (r < 0) + return r; + if (victim && (inode_mode_bits(dp) & 01000) && myproc() && + myproc()->euid != 0 && myproc()->euid != dp->uid && + myproc()->euid != victim->uid) + return -EPERM; + return 0; +} + static struct inode* createat_x(int dirfd, char *path, short type, short major, short minor, - int *err) + int *err, int mode) { struct inode *ip, *dp; char name[VFS_NAME_MAX + 1]; @@ -308,6 +365,11 @@ createat_x(int dirfd, char *path, short type, short major, short minor, iunlockput(ip); return 0; } + if (dir_may_modify(dp, 0) < 0) { + iunlockput(dp); + if (err) *err = EACCES; + return 0; + } // v1 and SchFS cannot fail either of these for a reason the caller could // have avoided. FAT can: the volume fills up, the fixed root directory of // FAT16 holds 512 names and no more, and a name may hold characters FAT @@ -320,6 +382,19 @@ createat_x(int dirfd, char *path, short type, short major, short minor, ilock(ip); ip->major = major; ip->minor = minor; ip->nlink = 1; ip->showmode = 1; + // Owned by whoever made it. A directory with the setgid bit hands its + // group on, which is how a shared directory keeps one group. + ip->uid = myproc() ? myproc()->euid : 0; + ip->gid = (inode_mode_bits(dp) & 02000) ? dp->gid + : (myproc() ? myproc()->egid : 0); + // The permission bits asked for, less the umask -- or, from a caller that + // has none to give, what open() and mkdir() would ask for. A symlink's + // are 0777 on Linux whatever the umask, and nothing reads them. + if (type == T_SYMLINK) + ip->mode = 0777; + else + ip->mode = (uint)(mode >= 0 ? mode : type == T_DIR ? 0777 : 0666) + & 07777 & ~myproc()->umask; iupdate(ip); if (type == T_DIR) { dp->nlink++; @@ -336,14 +411,35 @@ createat_x(int dirfd, char *path, short type, short major, short minor, if (err) *err = r == -EINVAL ? EINVAL : ENOSPC; return 0; } + inotify_event(dp->dev, dp->inum, IN_CREATE | (type == T_DIR ? IN_ISDIR : 0), name, 0); iunlockput(dp); return ip; } +// The device and inode a path names, for inotify's watches. 0, or -errno. +int +path_identity(char *path, int follow, int onlydir, uint *dev, uint64 *inum) +{ + struct inode *ip; + int err = ENOENT, r = 0; + + begin_op(); + ip = follow ? at_nameif(AT_FDCWD, path, &err) : at_namei(AT_FDCWD, path); + if (!ip) { end_op(); return -err; } + ilock(ip); + if (onlydir && ip->type != T_DIR) + r = -ENOTDIR; + *dev = ip->dev; + *inum = ip->inum; + iunlockput(ip); + end_op(); + return r; +} + static struct inode* create(char *path, short type, short major, short minor) { - return createat_x(AT_FDCWD, path, type, major, minor, 0); + return createat_x(AT_FDCWD, path, type, major, minor, 0, -1); } // ----------------------------------------------------------------------- @@ -364,12 +460,12 @@ addr_t sys_dup2(void) int oldfd, newfd; struct file *f; if (argint(0, &oldfd) < 0 || argint(1, &newfd) < 0) return -1; - if (oldfd < 0 || oldfd >= NOFILE || !(f = myproc()->ofile[oldfd])) return -EBADF; + if (oldfd < 0 || oldfd >= NOFILE || !(f = myproc()->files->ofile[oldfd])) return -EBADF; if (newfd < 0 || newfd >= NOFILE) return -EBADF; if (oldfd == newfd) return newfd; - if (myproc()->ofile[newfd]) fileclose(myproc()->ofile[newfd]); - myproc()->ofile[newfd] = filedup(f); - myproc()->ofile_flags[newfd] = 0; // dup2 never carries FD_CLOEXEC over + if (myproc()->files->ofile[newfd]) fileclose(myproc()->files->ofile[newfd]); + myproc()->files->ofile[newfd] = filedup(f); + myproc()->files->flags[newfd] = 0; // dup2 never carries FD_CLOEXEC over return newfd; } @@ -383,9 +479,9 @@ addr_t sys_dup3(void) // Unlike dup2, equal fds are an error (dup3(2)). Without this check we // would fileclose(f) and then filedup() the file we just released. if (oldfd == newfd) return -EINVAL; - if (myproc()->ofile[newfd]) fileclose(myproc()->ofile[newfd]); - myproc()->ofile[newfd] = filedup(f); - myproc()->ofile_flags[newfd] = 0; + if (myproc()->files->ofile[newfd]) fileclose(myproc()->files->ofile[newfd]); + myproc()->files->ofile[newfd] = filedup(f); + myproc()->files->flags[newfd] = 0; return newfd; } @@ -394,8 +490,8 @@ addr_t sys_close(void) int fd; struct file *f; if (argfd(0, &fd, &f) < 0) return -EBADF; - myproc()->ofile[fd] = 0; - myproc()->ofile_flags[fd] = 0; + myproc()->files->ofile[fd] = 0; + myproc()->files->flags[fd] = 0; lock_close_fd(f); fileclose(f); return 0; @@ -415,23 +511,23 @@ sys_fcntl(void) { int fd, cmd, arg; if (argint(0, &fd) < 0 || argint(1, &cmd) < 0 || argint(2, &arg) < 0) return -1; - if (fd < 0 || fd >= NOFILE || !myproc()->ofile[fd]) return -1; - struct file *f = myproc()->ofile[fd]; + if (fd < 0 || fd >= NOFILE || !myproc()->files->ofile[fd]) return -1; + struct file *f = myproc()->files->ofile[fd]; switch (cmd) { case F_DUPFD: case F_DUPFD_CLOEXEC: { if (arg < 0) return -EINVAL; for (int nfd = arg; nfd < NOFILE; nfd++) { - if (!myproc()->ofile[nfd]) { - myproc()->ofile[nfd] = filedup(f); - myproc()->ofile_flags[nfd] = (cmd == F_DUPFD_CLOEXEC) ? FD_CLOEXEC : 0; + if (!myproc()->files->ofile[nfd]) { + myproc()->files->ofile[nfd] = filedup(f); + myproc()->files->flags[nfd] = (cmd == F_DUPFD_CLOEXEC) ? FD_CLOEXEC : 0; return nfd; } } return -EMFILE; } - case F_GETFD: return myproc()->ofile_flags[fd]; - case F_SETFD: myproc()->ofile_flags[fd] = arg; return 0; + case F_GETFD: return myproc()->files->flags[fd]; + case F_SETFD: myproc()->files->flags[fd] = arg; return 0; case F_GETFL: { int flags = 0; if (f->readable && f->writable) flags = O_RDWR; @@ -448,6 +544,13 @@ sys_fcntl(void) case F_SETFL: f->nonblock = (arg & O_NONBLOCK) != 0; return 0; + case F_ADD_SEALS: + case F_GET_SEALS: + if (f->type != FD_MEMFD) + return -EINVAL; + if (cmd == F_ADD_SEALS && !f->writable) + return -EPERM; + return memfd_seals(f->memfd_seg, cmd == F_ADD_SEALS ? arg : -1); case F_GETLK: case F_SETLK: case F_SETLKW: case F_OFD_GETLK: case F_OFD_SETLK: case F_OFD_SETLKW: { // A pointer, which argint() above would have cut to 32 bits. @@ -563,7 +666,7 @@ addr_t sys_pipe(void) int fd0 = -1, fd1; if (argptr(0, (void*)&fd, 2*sizeof(fd[0])) < 0 || pipealloc(&rf, &wf) < 0) return -1; if ((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0) { - if (fd0 >= 0) myproc()->ofile[fd0] = 0; + if (fd0 >= 0) myproc()->files->ofile[fd0] = 0; fileclose(rf); fileclose(wf); return -1; } @@ -571,20 +674,29 @@ addr_t sys_pipe(void) return 0; } +// The flags were ignored, and they are the point of pipe2: libevent makes its +// signal pipe with O_NONBLOCK and drains it until EAGAIN, so the drain blocked +// on the read after the last byte and tmux's client stopped dead. addr_t sys_pipe2(void) { addr_t fdarray; struct file *rf, *wf; - int fd0, fd1; + int fd0, fd1, flags; if (argaddr(0, &fdarray) < 0) return -1; + argint(1, &flags); int *fds = (int*)user_ptr(fdarray, 2 * sizeof(int)); if (!fds || pipealloc(&rf, &wf) < 0) return -1; fd0 = fd1 = -1; if ((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0) { - if (fd0 >= 0) myproc()->ofile[fd0] = 0; + if (fd0 >= 0) myproc()->files->ofile[fd0] = 0; fileclose(rf); fileclose(wf); return -1; } + rf->nonblock = wf->nonblock = (flags & O_NONBLOCK) != 0; + if (flags & O_CLOEXEC) { + myproc()->files->flags[fd0] |= FD_CLOEXEC; + myproc()->files->flags[fd1] |= FD_CLOEXEC; + } fds[0] = fd0; fds[1] = fd1; return 0; } @@ -622,6 +734,20 @@ open_existing(int dirfd, char *path, int omode) return at_nameif(dirfd, path, &err); } +// May the caller open an existing file this way? Newly created files are not +// asked: a file made with O_CREAT and mode 0444 can still be written by the +// call that made it. +static int +open_perm(struct inode *ip, int omode) +{ + int acc = omode & O_ACCMODE, mask = 0; + if (acc == O_RDONLY || acc == O_RDWR) + mask |= 4; + if (acc == O_WRONLY || acc == O_RDWR || (omode & O_TRUNC)) + mask |= 2; + return inode_permission(ip, mask, 0); +} + addr_t sys_open(void) { char *path; @@ -640,9 +766,11 @@ addr_t sys_open(void) if ((ip = open_existing(AT_FDCWD, path, omode)) != 0) { ilock(ip); if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; } + if ((cerr = open_perm(ip, omode)) < 0) { iunlockput(ip); end_op(); return cerr; } } else ip = createat_x(AT_FDCWD, path, - (omode & O_EXTENT) ? T_EXTENT : T_FILE, 0, 0, &cerr); + (omode & O_EXTENT) ? T_EXTENT : T_FILE, 0, 0, &cerr, + linux_arg_mode(2)); // A refusal because the filesystem is read-only is not "no such file". // A shell prints the same sentence for every create failure, so // returning ENOENT here made writing to a snapshot look like a missing @@ -654,12 +782,20 @@ addr_t sys_open(void) int err; if (!(ip = open_lookup(AT_FDCWD, path, omode, &err))) { end_op(); return -err; } ilock(ip); + if ((err = open_perm(ip, omode)) < 0) { iunlockput(ip); end_op(); return err; } // Directories may only be opened for reading, but callers (e.g. // opendir()) legitimately OR in flags like O_DIRECTORY/O_CLOEXEC/ // O_NONBLOCK alongside O_RDONLY — only the access-mode bits matter. if (ip->type == T_DIR && (omode & O_ACCMODE) != O_RDONLY) { iunlockput(ip); end_op(); return -EISDIR; } if ((omode & O_TRUNC) && ip->type == T_FILE) itrunc(ip); } + // /dev/ptmx: every open is a new pseudo-terminal, and the file holds its + // master rather than the ptmx node. See kernel/dev/pty.c. + if (ip->type == T_DEV && ip->major == PTMX_MAJOR) { + struct inode *m = ptmx_open(ip); + if (!m) { iunlockput(ip); end_op(); return -ENOSPC; } + ip = m; + } if (!(f = filealloc()) || (fd = fdalloc(f)) < 0) { if (f) fileclose(f); iunlockput(ip); end_op(); return -EMFILE; @@ -699,7 +835,9 @@ addr_t sys_openat(void) if ((ip = open_existing(dirfd, path, omode)) != 0) { ilock(ip); if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; } - } else if (!(ip = createat_x(dirfd, path, T_FILE, 0, 0, &cerr))) { + if ((cerr = open_perm(ip, omode)) < 0) { iunlockput(ip); end_op(); return cerr; } + } else if (!(ip = createat_x(dirfd, path, T_FILE, 0, 0, &cerr, + linux_arg_mode(3)))) { end_op(); return cerr ? -cerr : -ENOENT; } @@ -707,6 +845,7 @@ addr_t sys_openat(void) int err; if (!(ip = open_lookup(dirfd, path, omode, &err))) { end_op(); return -err; } ilock(ip); + if ((err = open_perm(ip, omode)) < 0) { iunlockput(ip); end_op(); return err; } // Directories may only be opened for reading, but callers (e.g. // opendir()) legitimately OR in flags like O_DIRECTORY/O_CLOEXEC/ // O_NONBLOCK alongside O_RDONLY — only the access-mode bits matter. @@ -717,6 +856,13 @@ addr_t sys_openat(void) // depending on which entry point libc happened to call. if ((omode & O_TRUNC) && (ip->type == T_FILE || ip->type == T_EXTENT)) itrunc(ip); + // /dev/ptmx: every open is a new pseudo-terminal, and the file holds its + // master rather than the ptmx node. See kernel/dev/pty.c. + if (ip->type == T_DEV && ip->major == PTMX_MAJOR) { + struct inode *m = ptmx_open(ip); + if (!m) { iunlockput(ip); end_op(); return -ENOSPC; } + ip = m; + } if (!(f = filealloc()) || (fd = fdalloc(f)) < 0) { if (f) fileclose(f); iunlockput(ip); end_op(); return -EMFILE; @@ -824,16 +970,38 @@ addr_t sys_lstat(void) return do_stat(AT_FDCWD, path, st, 0); } +// access(2) and friends: may the caller (by real ids, unless AT_EACCESS) do +// `amode` -- F_OK 0, X_OK 1, W_OK 2, R_OK 4 -- to this inode? Write on a +// read-only filesystem is EROFS rather than EACCES. +static int +access_check(struct inode *ip, int amode, int real) +{ + int r = 0; + amode &= 7; + if (amode == 0) + return 0; + ilock(ip); + if ((amode & 2) && fs_readonly(ip->dev) && ip->type != T_DEV) + r = -EROFS; + else + r = inode_permission(ip, amode, real); + iunlock(ip); + return r; +} + addr_t sys_access(void) { char *path; + int amode = 0; if (argstr(0, &path) < 0) return -1; + argint(1, &amode); int err; begin_op(); struct inode *ip = at_nameif(AT_FDCWD, path, &err); if (!ip) { end_op(); return -err; } + err = access_check(ip, amode, 1); iput(ip); end_op(); - return 0; + return err; } addr_t sys_chdir(void) @@ -849,6 +1017,7 @@ addr_t sys_chdir(void) // shell reported "Operation not permitted" -- which reads like a // permissions problem and sends you looking in entirely the wrong place. if (ip->type != T_DIR) { iunlockput(ip); end_op(); return -ENOTDIR; } + if ((err = inode_permission(ip, 1, 0)) < 0) { iunlockput(ip); end_op(); return err; } iunlock(ip); iput(myproc()->cwd); end_op(); myproc()->cwd = ip; if (path[0] == '/') { @@ -871,6 +1040,7 @@ addr_t sys_fchdir(void) if (argfd(0, 0, &f) < 0 || f->type != FD_INODE) return -1; ilock(f->ip); if (f->ip->type != T_DIR) { iunlock(f->ip); return -1; } + if (inode_permission(f->ip, 1, 0) < 0) { iunlock(f->ip); return -EACCES; } iunlock(f->ip); iput(myproc()->cwd); idup(f->ip); myproc()->cwd = f->ip; myproc()->cwd_path[0] = '/'; myproc()->cwd_path[1] = 0; @@ -905,7 +1075,8 @@ addr_t sys_mkdir(void) return -EEXIST; } int cerr = 0; - if (!(ip = createat_x(AT_FDCWD, path, T_DIR, 0, 0, &cerr))) { + if (!(ip = createat_x(AT_FDCWD, path, T_DIR, 0, 0, &cerr, + linux_arg_mode(1)))) { end_op(); return cerr ? -cerr : -ENOENT; } @@ -941,6 +1112,7 @@ do_mknod(int dirfd, char *path, uint32 mode, uint64 dev) int major = (int)(((dev >> 8) & 0xfff) | ((dev >> 32) & ~0xfffull)); int minor = (int)((dev & 0xff) | ((dev >> 12) & ~0xffull)); if (type == T_FILE) major = minor = 0; + if (type == T_DEV && !capable()) return -EPERM; begin_op(); // createat_x() opens an existing file when asked for one, as open(O_CREAT) // wants. mknod() must not. @@ -948,7 +1120,7 @@ do_mknod(int dirfd, char *path, uint32 mode, uint64 dev) if (ex) { iput(ex); end_op(); return -EEXIST; } int cerr = 0; struct inode *ip = createat_x(dirfd, path, type, (short)major, - (short)minor, &cerr); + (short)minor, &cerr, (int)(mode & 07777)); if (!ip) { end_op(); return cerr ? -cerr : -EEXIST; } iunlockput(ip); end_op(); return 0; @@ -1009,9 +1181,12 @@ addr_t sys_link(void) ip->nlink++; iupdate(ip); iunlock(ip); if (!(dp = nameiparent(new, name))) { err = ENOENT; goto bad_link; } ilock(dp); + if (dir_may_modify(dp, 0) < 0) { iunlockput(dp); err = EACCES; goto bad_link; } if (dp->dev != ip->dev) { iunlockput(dp); err = EXDEV; goto bad_link; } if (dp->ops->no_hardlinks) { iunlockput(dp); err = EPERM; goto bad_link; } if (dirlink(dp, name, ip->inum) < 0) { iunlockput(dp); err = EEXIST; goto bad_link; } + inotify_event(dp->dev, dp->inum, IN_CREATE, name, 0); + inotify_event(ip->dev, ip->inum, IN_ATTRIB, 0, 0); iunlockput(dp); iput(ip); end_op(); return 0; bad_link: @@ -1040,6 +1215,17 @@ create_sock(char *path) return create(path, T_SOCK, 0, 0); } +// A name removed from dp: IN_DELETE there, and for the inode itself either +// IN_ATTRIB (a link went) or IN_DELETE_SELF (the last one did). Before the +// caller drops nlink, so the last link is nlink == 1 here. +static void +inotify_unlinked(struct inode *dp, struct inode *ip, char *name) +{ + inotify_event(dp->dev, dp->inum, IN_DELETE | (ip->type == T_DIR ? IN_ISDIR : 0), name, 0); + inotify_event(ip->dev, ip->inum, ip->nlink <= 1 || ip->type == T_DIR + ? IN_DELETE_SELF : IN_ATTRIB, 0, 0); +} + // The body of unlink(2), by path. Factored out so that a failed bind can take // its own node back out without going through the syscall entry, and so there // is one implementation of what unlink means rather than two. @@ -1059,9 +1245,12 @@ unlink_path(char *path) if (!(ip = dirlookup(dp, name, &off))) { err = ENOENT; goto bad; } ilock(ip); if (ip->nlink < 1) panic("unlink: nlink < 1"); + if ((err = -dir_may_modify(dp, ip)) != 0) { iunlockput(ip); goto bad; } + err = EPERM; if (ip->type == T_DIR && dirempty(ip) != 1) { iunlockput(ip); err = ENOTEMPTY; goto bad; } if (dirunlink(dp, name, off) < 0) { iunlockput(ip); err = EIO; goto bad; } if (ip->type == T_DIR) { dp->nlink--; iupdate(dp); } + inotify_unlinked(dp, ip, name); iunlockput(dp); ip->nlink--; iupdate(ip); iunlockput(ip); return 0; bad: @@ -1118,6 +1307,7 @@ addr_t sys_ftruncate(void) begin_op(); ilock(f->ip); itrunc_to(f->ip, (uint64)length); iunlock(f->ip); end_op(); + inotify_file(f, IN_MODIFY); return 0; } @@ -1132,7 +1322,9 @@ addr_t sys_truncate(void) begin_op(); if (!(ip = at_nameif(AT_FDCWD, path, &err))) { end_op(); return -err; } ilock(ip); + if ((err = inode_permission(ip, 2, 0)) < 0) { iunlockput(ip); end_op(); return err; } itrunc_to(ip, (uint64)length); + inotify_event(ip->dev, ip->inum, IN_MODIFY, 0, 0); iunlockput(ip); end_op(); return 0; } @@ -1188,9 +1380,25 @@ do_rename(int olddirfd, char *oldpath, int newdirfd, char *newpath) if (dp_new) iput(dp_new); goto bad_rename; } + uint new_dev = dp_new->dev; // for inotify, after dp_new is put + uint64 new_inum = dp_new->inum; + + // Both directories are checked before anything changes: a refusal after + // the new name is in place would leave the file with two. + ilock(dp_old); + int perm = dir_may_modify(dp_old, ip); + iunlock(dp_old); + if (perm < 0) { + iput(dp_old); iput(dp_new); + err = EACCES; goto bad_rename; + } // Clear the destination, if anything is there. ilock(dp_new); + if (dir_may_modify(dp_new, 0) < 0) { + iunlockput(dp_new); iput(dp_old); + err = EACCES; goto bad_rename; + } if ((ip_dst = dirlookup(dp_new, newname, &dstoff)) != 0) { // Both names already reach the same file. POSIX: succeed and do // nothing -- which includes not unlinking either name, since removing @@ -1213,6 +1421,8 @@ do_rename(int olddirfd, char *oldpath, int newdirfd, char *newpath) iunlockput(ip_dst); iunlockput(dp_new); iput(dp_old); err = EIO; goto bad_rename; } + if (ip_dst->nlink <= 1) + inotify_event(ip_dst->dev, ip_dst->inum, IN_DELETE_SELF, 0, 0); ip_dst->nlink--; iupdate(ip_dst); iunlockput(ip_dst); } @@ -1230,6 +1440,12 @@ do_rename(int olddirfd, char *oldpath, int newdirfd, char *newpath) if (dirunlink(dp_old, oldname, off) < 0) { iunlockput(dp_old); err = EIO; goto bad_rename; } + { + uint32 cookie = inotify_cookie(); + inotify_event(dp_old->dev, dp_old->inum, IN_MOVED_FROM, oldname, cookie); + inotify_event(new_dev, new_inum, IN_MOVED_TO, newname, cookie); + inotify_event(ip->dev, ip->inum, IN_MOVE_SELF, 0, 0); + } iunlockput(dp_old); ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return 0; @@ -1275,7 +1491,7 @@ do_symlink(int newdirfd, char *target, char *linkpath) return -EEXIST; } int cerr = 0; - if (!(ip = createat_x(newdirfd, linkpath, T_SYMLINK, 0, 0, &cerr))) { + if (!(ip = createat_x(newdirfd, linkpath, T_SYMLINK, 0, 0, &cerr, -1))) { end_op(); return cerr ? -cerr : -ENOENT; } @@ -1463,7 +1679,7 @@ addr_t sys_ioctl(void) if (f->type != FD_INODE || !f->ip || !f->writable) return -EBADF; - if (sfd < 0 || sfd >= NOFILE || (sf = pr->ofile[sfd]) == 0) + if (sfd < 0 || sfd >= NOFILE || (sf = pr->files->ofile[sfd]) == 0) return -EBADF; if (sf->type != FD_INODE || !sf->ip || !sf->readable) return -EBADF; @@ -1487,6 +1703,34 @@ addr_t sys_ioctl(void) return 0; } + // The four that apply to any descriptor at all. Rust's standard library + // sets close-on-exec with FIOCLEX and non-blocking with FIONBIO rather + // than with fcntl, and on a pipe or a socket those fell through to the + // ENOTTY below: glycin's image loaders failed with "Not a tty" and GTK + // could load no icon. + if (request == 0x5451 || request == 0x5450) { // FIOCLEX, FIONCLEX + if (request == 0x5451) + myproc()->files->flags[fd] |= FD_CLOEXEC; + else + myproc()->files->flags[fd] &= ~FD_CLOEXEC; + return 0; + } + if (request == 0x5421) { // FIONBIO + int on; + if (copy_from_user(&on, argp, sizeof(on)) < 0) + return -EFAULT; + f->nonblock = on != 0; + return 0; + } + if (request == 0x5452) // FIOASYNC + return 0; + if (request == FIONREAD && (f->type == FD_EVENT || f->type == FD_PIPE)) { + int n = f->type == FD_PIPE ? pipe_avail(f->pipe) : ev_fionread(f->ev); + if (n < 0) + return n; + return put_user(n, argp) < 0 ? -EFAULT : 0; + } + // /dev/fb0 (major 2) — geometry queries. This has to come before the // catch-all below, which answers every non-tty ioctl with 0: a program // asking FBIOGET_VSCREENINFO would have been told "success" and then @@ -1503,6 +1747,9 @@ addr_t sys_ioctl(void) if (is_dev && f->ip->major == AUDIO_MAJOR) return dsp_ioctl((int)request, argp); + if (is_dev && (f->ip->major == PTM_MAJOR || f->ip->major == PTS_MAJOR)) + return pty_ioctl(f->ip, (uint)request, argp); + int is_tty = (is_dev && f->ip->major == 1); if (!is_tty) { // -ENOTTY, not 0. @@ -1595,6 +1842,20 @@ addr_t sys_ioctl(void) // terminal and nothing to choose. It records the choice now, which is what // /dev/tty resolves through and what a session leader on tty3 needs so // that its /dev/tty is tty3 and not whatever is on screen. + // TIOCLINUX. Only subcode 6, the modifier state, is answered -- with + // "none held", which is true often enough. mc asks it after every key on + // a Linux console, and the ioctl used to succeed without writing: the 6 + // it passed in came back as Ctrl+AltGr held, and Enter arrived as an + // unknown chord printed as a dot. + if (request == 0x541C) { + uchar sub; + if (copy_from_user(&sub, argp, 1) < 0) + return -EFAULT; + if (sub != 6) + return -EINVAL; + sub = 0; + return copy_to_user(argp, &sub, 1) < 0 ? -EFAULT : 0; + } if (request == TIOCSCTTY) { struct vt *v = vt_for_minor(f->ip->minor); myproc()->ctty = vt_index(v) + 1; @@ -1776,11 +2037,142 @@ addr_t sys_ioctl(void) // createat() — every *at() syscall below uses them for dirfd resolution.) // chmod / fchmod / chown / fchown / lchown — stubs (no permission model yet). -addr_t sys_chmod(void) { return 0; } -addr_t sys_fchmod(void) { return 0; } -addr_t sys_chown(void) { return 0; } -addr_t sys_fchown(void) { return 0; } -addr_t sys_lchown(void) { return 0; } +// The new bits go in the inode and through iupdate to the filesystem. One +// that keeps none (FAT) holds them only while the inode is cached, which is +// as much as it can; there is no failure to report. +static int +chmod_inode(struct inode *ip, int mode) +{ + if (fs_readonly(ip->dev)) + return -EROFS; + ilock(ip); + // The owner may change the mode, and so may root. Setting the group id bit + // on a file whose group you are not in is quietly dropped, as on Linux. + if (myproc()->euid != 0 && myproc()->euid != ip->uid) { + iunlock(ip); + return -EPERM; + } + ip->mode = (uint)mode & 07777; + if (myproc()->euid != 0 && !in_group(ip->gid, 0)) + ip->mode &= ~02000u; + if (ip->mode == 0) + ip->mode = 01000000; // "no bits", as distinct from "never set" + iupdate(ip); + inotify_event(ip->dev, ip->inum, IN_ATTRIB, 0, 0); + iunlock(ip); + return 0; +} + +addr_t sys_chmod(void) +{ + char *path; + int mode, err, r; + struct inode *ip; + if (argstr(0, &path) < 0) return -EFAULT; + argint(1, &mode); + begin_op(); + if (!(ip = at_nameif(AT_FDCWD, path, &err))) { end_op(); return -err; } + r = chmod_inode(ip, mode); + iput(ip); + end_op(); + return r; +} + +addr_t sys_fchmod(void) +{ + struct file *f; + int mode, r; + if (argfd(0, 0, &f) < 0) return -EBADF; + argint(1, &mode); + if (f->type != FD_INODE || !f->ip) return 0; + begin_op(); + r = chmod_inode(f->ip, mode); + end_op(); + return r; +} +// chown(2): -1 leaves an id alone. Root may do anything. Anyone else may only +// give a file they own to a group they are in, and any change by a non-root +// caller drops the set-user-id and set-group-id bits, so that a program cannot +// be handed over and stay privileged. +static int +chown_inode(struct inode *ip, int uid, int gid) +{ + uint nu = (uint)uid, ng = (uint)gid; + int root = myproc()->euid == 0; + + if (fs_readonly(ip->dev)) + return -EROFS; + ilock(ip); + if (!root) { + if (myproc()->euid != ip->uid || + (uid != -1 && nu != ip->uid) || + (gid != -1 && !in_group(ng, 0))) { + iunlock(ip); + return -EPERM; + } + } + if (uid != -1) + ip->uid = nu; + if (gid != -1) + ip->gid = ng; + if (!root || ip->type != T_DIR) { + uint m = inode_mode_bits(ip); + if (m & 06000 && (uid != -1 || gid != -1) && ip->type != T_DIR) { + ip->mode = m & ~06000u; + if (ip->mode == 0) + ip->mode = 01000000; + } + } + iupdate(ip); + inotify_event(ip->dev, ip->inum, IN_ATTRIB, 0, 0); + iunlock(ip); + return 0; +} + +static int +do_chown(int dirfd, char *path, int uid, int gid, int nofollow) +{ + struct inode *ip; + int err, r; + begin_op(); + ip = nofollow ? at_namei(dirfd, path) : at_nameif(dirfd, path, &err); + if (!ip) { end_op(); return nofollow ? -ENOENT : -err; } + r = chown_inode(ip, uid, gid); + iput(ip); + end_op(); + return r; +} + +addr_t sys_chown(void) +{ + char *path; + int uid, gid; + if (argstr(0, &path) < 0) return -EFAULT; + argint(1, &uid); argint(2, &gid); + return do_chown(AT_FDCWD, path, uid, gid, 0); +} + +addr_t sys_lchown(void) +{ + char *path; + int uid, gid; + if (argstr(0, &path) < 0) return -EFAULT; + argint(1, &uid); argint(2, &gid); + return do_chown(AT_FDCWD, path, uid, gid, 1); +} + +addr_t sys_fchown(void) +{ + struct file *f; + int uid, gid, r; + if (argfd(0, 0, &f) < 0) return -EBADF; + argint(1, &uid); argint(2, &gid); + if (f->type != FD_INODE || !f->ip) return 0; + begin_op(); + r = chown_inode(f->ip, uid, gid); + end_op(); + return r; +} // rmdir: remove an empty directory (same as unlink for dirs). addr_t sys_rmdir(void) @@ -1905,6 +2297,16 @@ addr_t sys_utimensat(void) begin_op(); ilock(ip); if (fs_readonly(ip->dev)) { iunlockput(ip); end_op(); return -EROFS; } + // Setting the times to "now" takes write access or ownership; setting them + // to anything else takes ownership. + if (myproc()->euid != 0 && myproc()->euid != ip->uid) { + int explicit_t = 0; + for (int i = 0; utimes && i < 2; i++) + if (ts[i].tv_nsec != UTIME_OMIT && ts[i].tv_nsec != UTIME_NOW) + explicit_t = 1; + int pr = explicit_t ? -EPERM : inode_permission(ip, 2, 0); + if (pr < 0) { iunlockput(ip); end_op(); return pr; } + } for (int i = 0; i < 2; i++) { long sec = utimes ? ts[i].tv_sec : (long)now; if (utimes && ts[i].tv_nsec == UTIME_OMIT) continue; @@ -1914,6 +2316,7 @@ addr_t sys_utimensat(void) } ip->ctime = (uint)now; iupdate(ip); + inotify_event(ip->dev, ip->inum, IN_ATTRIB, 0, 0); iunlockput(ip); end_op(); return 0; @@ -2024,6 +2427,7 @@ addr_t sys_chroot(void) struct inode *ip; if (argstr(0, &path) < 0) return -EFAULT; + if (!capable()) return -EPERM; begin_op(); if ((ip = namei(path)) == 0) { end_op(); return -ENOENT; } ilock(ip); @@ -2054,7 +2458,8 @@ addr_t sys_mkdirat(void) return -EEXIST; } int cerr = 0; - if (!(ip = createat_x(dirfd, path, T_DIR, 0, 0, &cerr))) { + if (!(ip = createat_x(dirfd, path, T_DIR, 0, 0, &cerr, + linux_arg_mode(2)))) { end_op(); return cerr ? -cerr : -ENOENT; } @@ -2100,6 +2505,7 @@ addr_t sys_unlinkat(void) if (ip->type == T_DIR && dirempty(ip) != 1) { err = ENOTEMPTY; goto badip; } if (dirunlink(dp, name, off) < 0) { err = EIO; goto badip; } if (ip->type == T_DIR) { dp->nlink--; iupdate(dp); } + inotify_unlinked(dp, ip, name); iunlockput(dp); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return 0; @@ -2143,6 +2549,8 @@ addr_t sys_linkat(void) dirlink(dp, name, ip->inum) < 0) { iunlockput(dp); goto bad_linkat; } + inotify_event(dp->dev, dp->inum, IN_CREATE, name, 0); + inotify_event(ip->dev, ip->inum, IN_ATTRIB, 0, 0); iunlockput(dp); iput(ip); end_op(); return 0; bad_linkat: @@ -2183,8 +2591,30 @@ addr_t sys_readlinkat(void) return got; } -addr_t sys_fchmodat(void) { return 0; } // stub -addr_t sys_fchownat(void) { return 0; } // stub +addr_t sys_fchmodat(void) +{ + int dirfd, mode, err, r; + char *path; + struct inode *ip; + argint(0, &dirfd); + if (argstr(1, &path) < 0) return -EFAULT; + argint(2, &mode); + begin_op(); + if (!(ip = at_nameif(dirfd, path, &err))) { end_op(); return -err; } + r = chmod_inode(ip, mode); + iput(ip); + end_op(); + return r; +} +addr_t sys_fchownat(void) +{ + int dirfd, uid, gid, flags; + char *path; + argint(0, &dirfd); + if (argstr(1, &path) < 0) return -EFAULT; + argint(2, &uid); argint(3, &gid); argint(4, &flags); + return do_chown(dirfd, path, uid, gid, flags & AT_SYMLINK_NOFOLLOW); +} addr_t sys_faccessat(void) { @@ -2192,14 +2622,47 @@ addr_t sys_faccessat(void) char *path; argint(0, &dirfd); if (argstr(1, &path) < 0) return -1; - argint(2, &(int){0}); // amode — ignored - // arg3 flags — ignored + int amode = 0; + argint(2, &amode); int err; begin_op(); struct inode *ip = at_nameif(dirfd, path, &err); if (!ip) { end_op(); return -err; } + err = access_check(ip, amode, 1); iput(ip); end_op(); - return 0; + return err; +} + +// faccessat2: faccessat with the flags argument the original never had. musl +// sends any faccessat with flags here, and without it GLib's every "does this +// file exist" (F_OK, AT_SYMLINK_NOFOLLOW) failed with EINVAL. The mode is not +// checked, as in faccessat above; AT_EACCESS changes nothing for the same +// reason. +addr_t sys_faccessat2(void) +{ + int dirfd, mode, flags, err = ENOENT; + char *path; + struct inode *ip; + + argint(0, &dirfd); + if (argstr(1, &path) < 0) return -EFAULT; + argint(2, &mode); + argint(3, &flags); + if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_EACCESS | AT_EMPTY_PATH)) + return -EINVAL; + if ((flags & AT_EMPTY_PATH) && path[0] == '\0') { + if (dirfd < 0 || dirfd >= NOFILE || !myproc()->files->ofile[dirfd]) + return -EBADF; + return 0; + } + begin_op(); + ip = (flags & AT_SYMLINK_NOFOLLOW) ? at_namei(dirfd, path) + : at_nameif(dirfd, path, &err); + if (!ip) { end_op(); return -err; } + err = access_check(ip, mode, !(flags & AT_EACCESS)); + iput(ip); + end_op(); + return err; } addr_t sys_fstatat(void) @@ -2215,8 +2678,8 @@ addr_t sys_fstatat(void) // AT_EMPTY_PATH: if path is empty, fstat the dirfd itself. if ((flags & AT_EMPTY_PATH) && path[0] == '\0') { - if (dirfd < 0 || dirfd >= NOFILE || !myproc()->ofile[dirfd]) return -1; - struct file *f = myproc()->ofile[dirfd]; + if (dirfd < 0 || dirfd >= NOFILE || !myproc()->files->ofile[dirfd]) return -1; + struct file *f = myproc()->files->ofile[dirfd]; if (f->type != FD_INODE) return -1; ilock(f->ip); if (myproc()->syscall_map) stati_linux(f->ip, (struct linux_stat*)st); @@ -2293,6 +2756,8 @@ addr_t sys_mount(void) if (argstr(0, &source) < 0 || argstr(1, &target) < 0 || argstr(2, &fstype) < 0) return -EFAULT; + if (!capable()) + return -EPERM; // proc: a synthetic filesystem, so there is no source device to check and // no volume to have found at boot. `mount -t proc proc /proc`. @@ -2434,6 +2899,8 @@ addr_t sys_umount2(void) { char *target; if (argstr(0, &target) < 0) return -EFAULT; + if (!capable()) + return -EPERM; begin_op(); struct inode *dp = namei(target); diff --git a/kernel/sys/sys_mem.c b/kernel/sys/sys_mem.c index bede4e9..76a3c31 100644 --- a/kernel/sys/sys_mem.c +++ b/kernel/sys/sys_mem.c @@ -3,6 +3,7 @@ #include "gfx/gfx.h" #include "core/param.h" #include "uapi/stat.h" +#include "uapi/fcntl.h" #include "arch/memlayout.h" #include "arch/mmu.h" #include "core/spinlock.h" @@ -45,8 +46,8 @@ static struct vma * vma_free_slot(struct proc *p) { for (int i = 0; i < NVMA; i++) - if (!p->vma[i].used) - return &p->vma[i]; + if (!p->mm->vma[i].used) + return &p->mm->vma[i]; return 0; } @@ -62,7 +63,7 @@ static int vma_note_anon(struct proc *p, uint64 va, uint64 len) { for (int i = 0; i < NVMA; i++) { - struct vma *v = &p->vma[i]; + struct vma *v = &p->mm->vma[i]; if (vma_anon(v) && v->addr + v->length == va) { v->length += len; return 0; @@ -81,7 +82,7 @@ static int range_free(struct proc *p, uint64 va, uint64 len) { for (int i = 0; i < NVMA; i++) { - struct vma *v = &p->vma[i]; + struct vma *v = &p->mm->vma[i]; if (v->used && va < v->addr + v->length && v->addr < va + len) return 0; } @@ -104,24 +105,99 @@ range_free(struct proc *p, uint64 va, uint64 len) static uint64 mmap_va(struct proc *p, uint64 len, int mergeable) { - if (p->mmap_next == 0) - p->mmap_next = 0x40000000; + if (p->mm->mmap_next == 0) + p->mm->mmap_next = 0x40000000; if (mergeable) { for (int i = 0; i < NVMA; i++) { - struct vma *v = &p->vma[i]; + struct vma *v = &p->mm->vma[i]; uint64 e = v->addr + v->length; - if (vma_anon(v) && e >= 0x40000000 && e + len <= p->mmap_next && + if (vma_anon(v) && e >= 0x40000000 && e + len <= p->mm->mmap_next && range_free(p, e, len)) return e; } } - uint64 va = p->mmap_next; + uint64 va = p->mm->mmap_next; if (bad_user_range(va, len)) return 0; - p->mmap_next += len; + p->mm->mmap_next += len; return va; } +// --- demand paging --------------------------------------------------------- +// +// Anonymous memory may be recorded without being backed: the page appears, +// zeroed, the first time it is touched -- by the program, through trap(), or +// by the kernel on its behalf, through uva2ka(). Large mappings are made this +// way, and so are the ones that say outright they will not all be used. +// WebKit reserves 4 GB of address space at start for its JIT and heaps and +// touches a sliver of it; backing all of it at mmap time failed with ENOMEM +// and Epiphany aborted. Go, Java and Firefox reserve the same way. +// +// fork and munmap already skip the holes in anonymous memory, so a page that +// was never touched is simply one they do not see, and a child that touches +// it gets zeroes -- which is what it would have read in the parent. + +#define LAZY_MIN (1024 * 1024) + +static int +mmap_lazy(int prot, int flags, uint64 length) +{ + return (flags & MAP_NORESERVE) || prot == PROT_NONE || length >= LAZY_MIN; +} + +static struct spinlock faultlock; +static int faultlock_ready; + +// Back the page at va if it lies in this process's anonymous memory and is +// not there yet. 0 if it is there now, -1 if this is a real fault. Takes a +// spinlock and nothing that sleeps, so uva2ka() may call it from anywhere; +// the lock is what keeps two threads of one process from both mapping it. +int +vm_fault(addr_t va) +{ + struct proc *p = myproc(); + pte_t *pte; + char *mem; + int found = 0; + + if (!p || !p->mm || va >= KERNBASE) + return -1; + va = PGROUNDDOWN(va); + for (int i = 0; i < NVMA; i++) { + struct vma *v = &p->mm->vma[i]; + if (vma_anon(v) && v->addr <= va && va < v->addr + v->length) { + found = 1; + break; + } + } + if (!found) + return -1; + if ((mem = kalloc()) == 0) + return -1; + memset(mem, 0, PGSIZE); + if (!faultlock_ready) { + initlock(&faultlock, "vmfault"); + faultlock_ready = 1; + } + acquire(&faultlock); + pte = walkpte(p->pgdir, (void *)va); + if (pte && (*pte & PTE_P)) { + // Another thread got here first, or the page was never missing and + // this is a protection fault: either way, not ours to fix. + int ours = (*pte & PTE_U) != 0; + release(&faultlock); + kfree(mem); + return ours ? 0 : -1; + } + if (mappages(p->pgdir, (void *)va, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0) { + release(&faultlock); + kfree(mem); + return -1; + } + release(&faultlock); + return 0; +} + // Record [va, va+len) as anonymous memory wherever no mapping already covers // it, for a MAP_FIXED that may land anywhere: inside an earlier mapping, across // the end of one, or in a gap. Below sz is the program image and its heap, @@ -138,7 +214,7 @@ vma_cover_anon(struct proc *p, uint64 va, uint64 len) uint64 next = end; int inside = -1; for (int i = 0; i < NVMA; i++) { - struct vma *v = &p->vma[i]; + struct vma *v = &p->mm->vma[i]; if (!v->used) continue; if (v->addr <= cur && cur < v->addr + v->length) @@ -147,7 +223,7 @@ vma_cover_anon(struct proc *p, uint64 va, uint64 len) next = v->addr; } if (inside >= 0) { - cur = p->vma[inside].addr + p->vma[inside].length; + cur = p->mm->vma[inside].addr + p->mm->vma[inside].length; continue; } if (vma_note_anon(p, cur, next - cur) < 0) @@ -180,8 +256,8 @@ sys_brk(void) return myproc()->sz; } -addr_t -sys_mmap(void) +static addr_t +mmap_locked(void) { uint64 addr, length, offset; int prot, flags, fd; @@ -193,7 +269,7 @@ sys_mmap(void) return -1; if (fd >= 0 && fd < NOFILE) - f = myproc()->ofile[fd]; + f = myproc()->files->ofile[fd]; length = PGROUNDUP(length); uint64 va = addr; @@ -204,8 +280,20 @@ sys_mmap(void) if (bad_user_range(va, length) || (va % PGSIZE)) return -EINVAL; vm_unmap_range(myproc()->pgdir, va, length); - if (prot == PROT_NONE) + int fixed_file = f && f->type == FD_INODE && f->ip->type != T_DEV && + !(flags & MAP_ANONYMOUS); + if (fixed_file) + mapname_add(myproc()->mm, va, length, offset, f->path); + else + mapname_unmap(myproc()->mm, va, length); + // Reserved, not backed. PROT_NONE is recorded too: mprotect() does not + // change pages here, so a range reserved PROT_NONE and opened up later + // has to be memory that can appear when it is touched. + if (!fixed_file && mmap_lazy(prot, flags, length)) { + if (vma_cover_anon(myproc(), va, length) < 0) + return -ENOMEM; return va; + } for (uint64 a = va; a < va + length; a += PGSIZE) { char *mem = kalloc(); if (!mem) return -ENOMEM; @@ -272,15 +360,21 @@ sys_mmap(void) // process that maps the same segment gets the same physical pages, and a // store by one is a load by the other because there is only one copy. // - // MAP_PRIVATE is refused rather than given a copy. Every caller of this is - // sharing on purpose, and a private mapping of an anonymous object is an - // expensive way to ask for MAP_ANONYMOUS. + // A private mapping is allowed read-only, and is then the same pages + // mapped without write access: until somebody writes, a private mapping + // sees the object's stores on Linux too. GLib's GMappedFile maps a memfd + // this way, and that is how gdk-pixbuf reads every image glycin decodes. + // Private and writable would need a copy that is not made; it is refused. if (f && f->type == FD_MEMFD) { int id = f->memfd_seg; - uint64 have = memfd_size(id); + uint64 have = PGROUNDUP(memfd_size(id)); // the tail page maps whole - if (!(flags & MAP_SHARED)) + if (!(flags & MAP_SHARED) && (prot & PROT_WRITE)) return -EINVAL; + int pte = (flags & MAP_SHARED) ? PTE_W | PTE_U : PTE_U; + if ((prot & PROT_WRITE) && + (memfd_seals(id, -1) & (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE))) + return -EPERM; if (offset % PGSIZE) return -EINVAL; // Mapping past the end is not an error on Linux -- the pages simply @@ -295,7 +389,7 @@ sys_mmap(void) for (uint64 o = 0; o < length; o += PGSIZE) { char *pg = shm_page(id, (int)((offset + o) / PGSIZE)); if (!pg || mappages(myproc()->pgdir, (void*)(va + o), PGSIZE, - V2P((uint64)pg), PTE_W | PTE_U) < 0) { + V2P((uint64)pg), pte) < 0) { vm_unmap_norelease(myproc()->pgdir, va, o); shm_detach(id); return -ENOMEM; @@ -350,6 +444,7 @@ sys_mmap(void) vm_unmap_range(myproc()->pgdir, va, length); return -ENOMEM; } + mapname_add(myproc()->mm, va, length, offset, f->path); return va; } @@ -400,6 +495,11 @@ sys_mmap(void) // to the text console. fbclose() puts it back. fb_mapped(); } else { + if (mmap_lazy(prot, flags, length)) { + if (vma_note_anon(myproc(), va, length) < 0) + return -ENOMEM; + return va; + } for (uint64 a = va; a < va + length; a += PGSIZE) { char *mem = kalloc(); if (!mem) { @@ -425,8 +525,8 @@ sys_mmap(void) return va; } -addr_t -sys_munmap(void) +static addr_t +munmap_locked(void) { uint64 addr, length; struct proc *p = myproc(); @@ -436,12 +536,13 @@ sys_munmap(void) if (bad_user_range(addr, length) || (addr % PGSIZE)) return -EINVAL; uint64 end = addr + length; + mapname_unmap(p->mm, addr, length); // A segment or device mapping goes only as a whole, by its own address. // Part of one is refused: unmapping it the anonymous way below would kfree // pages that are the framebuffer's or another process's. for (int i = 0; i < NVMA; i++) { - struct vma *v = &p->vma[i]; + struct vma *v = &p->mm->vma[i]; if (!v->used || vma_anon(v)) continue; if (v->addr == addr) { @@ -466,7 +567,7 @@ sys_munmap(void) // are not there, and the bump allocator never hands the addresses out // again. for (int i = 0; i < NVMA; i++) { - struct vma *v = &p->vma[i]; + struct vma *v = &p->mm->vma[i]; if (!vma_anon(v)) continue; uint64 vs = v->addr, ve = v->addr + v->length; @@ -517,6 +618,8 @@ struct shmseg { int used; int key; uint64 size; // rounded up to a page + uint64 bytes; // a memfd's length as ftruncate gave it + int seals; // a memfd's F_SEAL_* bits int npages; int nattach; // live attachments across all processes char *pages[SHM_MAXPAGES]; // kernel virtual addresses @@ -583,7 +686,7 @@ shm_detach(int id) // or race to invent a key. // // It is the same segment machinery underneath, which is exactly what -// docs/plan/foundations.md proposed: shmget()'s pages, refcounts, no_free +// docs/architecture/kernel.md proposed: shmget()'s pages, refcounts, no_free // VMAs and the fork/exit accounting are all already right. What memfd adds is // a segment that starts empty and is sized by ftruncate(), and a file that // owns one reference to it. @@ -605,6 +708,7 @@ shm_resize(int id, uint64 newsize) { struct shmseg *sg; int want, i; + uint64 len = newsize; if (id < 0 || id >= NSHM) return -EINVAL; @@ -644,6 +748,7 @@ shm_resize(int id, uint64 newsize) acquire(&shmlock); sg->npages = want; sg->size = newsize; + sg->bytes = len; release(&shmlock); return 0; } @@ -671,7 +776,11 @@ memfd_size(int id) return 0; shm_init_once(); acquire(&shmlock); - n = shm[id].used ? shm[id].size : 0; + // The length asked for, not the pages behind it. fstat reports this, and + // memmap2 -- which glycin uses to share every decoded image -- sizes its + // mapping by it: 24x24 icons came back as 4096 bytes instead of 2304, and + // the PNG decoder asserted. + n = shm[id].used ? shm[id].bytes : 0; release(&shmlock); return n; } @@ -679,9 +788,47 @@ memfd_size(int id) int memfd_truncate(int id, uint64 len) { + if (id < 0 || id >= NSHM) + return -EINVAL; + uint64 now = memfd_size(id); + if ((len < now && (shm[id].seals & F_SEAL_SHRINK)) || + (len > now && (shm[id].seals & F_SEAL_GROW))) + return -EPERM; return shm_resize(id, len); } +// File seals: promises about a memfd that whoever receives it can rely on. +// glycin seals each decoded image before handing it over, and gdk-pixbuf +// first asks for the seals just to learn the descriptor is a memfd at all. +// +// Linux refuses F_SEAL_WRITE while a writable mapping exists (EBUSY). That +// needs every mapping of the segment to be made read-only on the spot, which +// takes the page fault handling this kernel does not have, so the seal is +// recorded and later writes, truncates and writable mappings are refused -- +// the mapping that already exists keeps writing. +int +memfd_seals(int id, int add) +{ + int r; + if (id < 0 || id >= NSHM) + return -EINVAL; + acquire(&shmlock); + if (!shm[id].used) + r = -EINVAL; + else if (add < 0) + r = shm[id].seals; + else if (shm[id].seals & F_SEAL_SEAL) + r = -EPERM; + else if (add & ~(F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_FUTURE_WRITE)) + r = -EINVAL; + else { + shm[id].seals |= add; + r = 0; + } + release(&shmlock); + return r; +} + // read()/write() on a memfd, so it is a file rather than a token that only // mmap understands. Wayland clients use ftruncate and mmap, but a descriptor // that refuses read() is a surprise nobody needs. @@ -697,10 +844,12 @@ memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing) sg = &shm[id]; if (!sg->used) return -EINVAL; - if (off >= sg->size) + if (writing && (sg->seals & (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE))) + return -EPERM; + if (off >= sg->bytes) return 0; // EOF, or a write past the end - if (off + (uint64)n > sg->size) - n = (int)(sg->size - off); + if (off + (uint64)n > sg->bytes) + n = (int)(sg->bytes - off); while (done < n) { uint64 pos = off + (uint64)done; @@ -751,6 +900,8 @@ sys_memfd_create(void) shm[i].size = 0; shm[i].npages = 0; shm[i].nattach = 1; // the file holds this one + // Without MFD_ALLOW_SEALING the object is sealed against sealing. + shm[i].seals = (flags & MFD_ALLOW_SEALING) ? 0 : F_SEAL_SEAL; id = i; break; } @@ -761,6 +912,8 @@ sys_memfd_create(void) if ((f = filealloc()) == 0) { shm_detach(id); return -ENFILE; } if ((fd = fdalloc(f)) < 0) { fileclose(f); return -EMFILE; } + if (flags & MFD_CLOEXEC) + myproc()->files->flags[fd] = FD_CLOEXEC; f->type = FD_MEMFD; f->memfd_seg = id; f->readable = 1; @@ -835,8 +988,8 @@ sys_shmget(void) return -ENOSPC; } -addr_t -sys_shmat(void) +static addr_t +shmat_locked(void) { int id, flags; addr_t addr; @@ -859,15 +1012,15 @@ sys_shmat(void) struct vma *v = 0; for (int i = 0; i < NVMA; i++) - if (!myproc()->vma[i].used) { v = &myproc()->vma[i]; break; } + if (!myproc()->mm->vma[i].used) { v = &myproc()->mm->vma[i]; break; } if (!v) { shm_detach(id); return -ENOMEM; } // Ignore the hint and take a fresh range, the same way mmap does. - if (myproc()->mmap_next == 0) - myproc()->mmap_next = 0x40000000; - uint64 va = myproc()->mmap_next; + if (myproc()->mm->mmap_next == 0) + myproc()->mm->mmap_next = 0x40000000; + uint64 va = myproc()->mm->mmap_next; if (bad_user_range(va, len)) { shm_detach(id); return -ENOMEM; } - myproc()->mmap_next += len; + myproc()->mm->mmap_next += len; for (int pg = 0; pg < npages; pg++) { if (mappages(myproc()->pgdir, (void*)(va + (uint64)pg * PGSIZE), PGSIZE, @@ -895,35 +1048,36 @@ sys_shmat(void) // // Called from exit() and from exec() before the old address space goes away. void -proc_release_shared(struct proc *p) +mm_release_shared(struct mm *m) { for (int i = 0; i < NVMA; i++) { - if (!p->vma[i].used || !p->vma[i].no_free) + if (!m->vma[i].used || !m->vma[i].no_free) continue; - vm_unmap_norelease(p->pgdir, p->vma[i].addr, p->vma[i].length); - if (p->vma[i].shmid >= 0) - shm_detach(p->vma[i].shmid); - p->vma[i].used = 0; - p->vma[i].no_free = 0; - p->vma[i].shmid = -1; + if (m->pgdir) + vm_unmap_norelease(m->pgdir, m->vma[i].addr, m->vma[i].length); + if (m->vma[i].shmid >= 0) + shm_detach(m->vma[i].shmid); + m->vma[i].used = 0; + m->vma[i].no_free = 0; + m->vma[i].shmid = -1; } } -addr_t -sys_shmdt(void) +static addr_t +shmdt_locked(void) { addr_t addr; if (argaddr(0, &addr) < 0) return -EINVAL; for (int i = 0; i < NVMA; i++) { - if (myproc()->vma[i].used && myproc()->vma[i].addr == addr && - myproc()->vma[i].shmid >= 0) { - vm_unmap_norelease(myproc()->pgdir, addr, myproc()->vma[i].length); - shm_detach(myproc()->vma[i].shmid); - myproc()->vma[i].used = myproc()->vma[i].addr = myproc()->vma[i].length = 0; - myproc()->vma[i].no_free = 0; - myproc()->vma[i].shmid = -1; + if (myproc()->mm->vma[i].used && myproc()->mm->vma[i].addr == addr && + myproc()->mm->vma[i].shmid >= 0) { + vm_unmap_norelease(myproc()->pgdir, addr, myproc()->mm->vma[i].length); + shm_detach(myproc()->mm->vma[i].shmid); + myproc()->mm->vma[i].used = myproc()->mm->vma[i].addr = myproc()->mm->vma[i].length = 0; + myproc()->mm->vma[i].no_free = 0; + myproc()->mm->vma[i].shmid = -1; return 0; } } @@ -981,3 +1135,21 @@ sys_setpriority(void) return -EINVAL; return (addr_t)proc_setnice(who, nice); } + +// One mapping change at a time per address space: threads share the VMA table +// and the page tables, and two of them allocating the same missing page-table +// level at once would each install their own and lose the other's mappings. +#define MM_LOCKED(name) \ +addr_t \ +sys_##name(void) \ +{ \ + struct mm *m = myproc()->mm; \ + mm_lock(m); \ + addr_t r = name##_locked(); \ + mm_unlock(m); \ + return r; \ +} +MM_LOCKED(mmap) +MM_LOCKED(munmap) +MM_LOCKED(shmat) +MM_LOCKED(shmdt) diff --git a/kernel/sys/sys_net.c b/kernel/sys/sys_net.c index fcf2038..44368b1 100644 --- a/kernel/sys/sys_net.c +++ b/kernel/sys/sys_net.c @@ -24,6 +24,7 @@ #include "uapi/posix.h" #include "core/syscall.h" #include "core/uaccess.h" +#include "core/kmem.h" #include "uapi/errno.h" #include "net/socket.h" @@ -123,7 +124,7 @@ sock_to_fd(struct socket *s, int sflags) f->off = 0; f->nonblock = (sflags & SOCK_NONBLOCK) != 0; if (sflags & SOCK_CLOEXEC) - myproc()->ofile_flags[fd] |= FD_CLOEXEC; + myproc()->files->flags[fd] |= FD_CLOEXEC; return fd; } @@ -136,7 +137,7 @@ argsock(int n, struct socket **ps) if (argint(n, &fd) < 0) return -EBADF; - if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd])) + if (fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd])) return -EBADF; if (f->type != FD_SOCKET) return -ENOTSOCK; @@ -164,7 +165,7 @@ SYSCALL_DEFINE(socket) if (type & ~(0xFF | SOCK_CLOEXEC | SOCK_NONBLOCK)) return -EINVAL; type &= 0xFF; - if (domain == AF_UNIX && type != SOCK_STREAM) + if (domain == AF_UNIX && type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET) return -EPROTOTYPE; if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_RAW) @@ -185,7 +186,7 @@ SYSCALL_DEFINE(socket) // default rather than departing from it, and programs do write them out: // toybox's `nc` passes IPPROTO_TCP, and refusing it meant "Protocol not // supported" for TCP on a system whose whole network is TCP. - if (protocol != 0) { + if (protocol != 0 && domain != AF_NETLINK) { int redundant = (domain == AF_INET && type == SOCK_STREAM && protocol == IPPROTO_TCP) || (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP); @@ -270,6 +271,17 @@ SYSCALL_DEFINE(bind) return sockbind_in(s, ip, port_from_net(port)); } + // sockaddr_nl: family, pad, port id, groups. Only the groups matter here. + if (sock_domain(s) == AF_NETLINK) { + uint pid = 0, groups = 0; + if (ulen < 12) + return -EINVAL; + if (copy_from_user(&pid, uaddr + 4, 4) < 0 || + copy_from_user(&groups, uaddr + 8, 4) < 0) + return -EFAULT; + return sock_netlink_bind(s, pid, groups); + } + uint64 key; if (sun_abstract(uaddr, ulen, &key)) return sockbind_abstract(s, key); @@ -459,10 +471,11 @@ SYSCALL_DEFINE(socketpair) return -EAFNOSUPPORT; int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK); type &= 0xFF; - if (type != SOCK_STREAM) + // Datagram and sequenced-packet pairs keep message boundaries (kernel/net/unix.c). + if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET) return -EPROTOTYPE; - if ((r = sockpair(&a, &b)) < 0) + if ((r = sockpair_type(&a, &b, type)) < 0) return r; if ((fd0 = sock_to_fd(a, sflags)) < 0) { @@ -471,7 +484,7 @@ SYSCALL_DEFINE(socketpair) return fd0; } if ((fd1 = sock_to_fd(b, sflags)) < 0) { - myproc()->ofile[fd0] = 0; + myproc()->files->ofile[fd0] = 0; sockclose(a); sockclose(b); return fd1; @@ -479,8 +492,8 @@ SYSCALL_DEFINE(socketpair) int sv[2] = { fd0, fd1 }; if (copy_to_user(usv, sv, sizeof(sv)) < 0) { - myproc()->ofile[fd0] = 0; - myproc()->ofile[fd1] = 0; + myproc()->files->ofile[fd0] = 0; + myproc()->files->ofile[fd1] = 0; sockclose(a); sockclose(b); return -EFAULT; @@ -532,15 +545,18 @@ SYSCALL_DEFINE(sendto) return socksendto(s, p, len, ip, port); } +static int argsockfile(int n, struct socket **sp, struct file **fp); + SYSCALL_DEFINE(recvfrom) { struct socket *s; + struct file *file; addr_t ubuf, uaddr, ulenp; int len, flags, r; uint ip = 0; ushort port = 0; - if ((r = argsock(0, &s)) < 0) + if ((r = argsockfile(0, &s, &file)) < 0) return r; argaddr(1, &ubuf); argint(2, &len); @@ -554,6 +570,13 @@ SYSCALL_DEFINE(recvfrom) if (len && !p) return -EFAULT; + // As in recvmsg. This one blocked regardless, and musl's resolver -- which + // sends the A and AAAA queries together and reads its non-blocking socket + // until EAGAIN -- waited for ever after the first answer: NetSurf sat on + // "Loading" with the reply in hand. + if ((file->nonblock || (flags & MSG_DONTWAIT)) && !sockreadable(s)) + return -EAGAIN; + r = sockrecvfrom(s, p, len, &ip, &port); if (r < 0 || uaddr == 0) return r; @@ -581,6 +604,23 @@ SYSCALL_DEFINE(recvfrom) cap = alen; if (cap > 0 && copy_to_user(uaddr, &sa, (uint64)cap) < 0) return -EFAULT; + } else if (sock_domain(s) == AF_NETLINK) { + // Everything on a netlink socket here comes from the kernel: port id + // 0, no groups. GLib's network monitor checks exactly that before it + // believes a message, and with no address at all it hit an assertion + // and every GNOME program that watches the network aborted. + uint16 nl[6] = { AF_NETLINK, 0, 0, 0, 0, 0 }; + uint g = sock_nl_groups(s), from = sock_nl_from(s); + nl[2] = (uint16)from; nl[3] = (uint16)(from >> 16); + nl[4] = (uint16)g; nl[5] = (uint16)(g >> 16); + alen = (int)sizeof(nl); + int cap = alen; + if (ulenp && get_user(cap, ulenp) < 0) + return -EFAULT; + if (cap > alen) + cap = alen; + if (cap > 0 && copy_to_user(uaddr, nl, (uint64)cap) < 0) + return -EFAULT; } if (ulenp && copy_to_user(ulenp, &alen, sizeof(alen)) < 0) @@ -631,6 +671,12 @@ sock_name(int peer) // A sockaddr_nl of zeros: family, padding, port id 0 (the kernel), // no groups. sa.un.sun_family = AF_NETLINK; + { + // Port id and bound groups, at their offsets in sockaddr_nl. + uint pid = sock_nl_pid(s), grp = sock_nl_bound_groups(s); + memmove((char *)&sa + 4, &pid, 4); + memmove((char *)&sa + 8, &grp, 4); + } alen = 12; } else { if ((r = sockname_unix(s, peer, sa.un.sun_path)) < 0) @@ -752,6 +798,23 @@ SYSCALL_DEFINE(getsockopt) argaddr(3, &uval); argaddr(4, &ulenp); + // SO_PEERCRED: who is on the other end of a Unix socket. tmux asks its + // client this and, told nothing, refuses it as a stranger. + if (level == 1 && name == 17) { + struct { int pid; uint uid, gid; } cr; + int want; + if (sock_peercred(s, &cr.pid, &cr.uid, &cr.gid) < 0) + return -ENOTCONN; + if (!uval || !ulenp || get_user(want, ulenp) < 0) + return -EFAULT; + if (want < (int)sizeof(cr)) + return -EINVAL; + if (copy_to_user(uval, &cr, sizeof(cr)) < 0) + return -EFAULT; + want = sizeof(cr); + return put_user(want, ulenp) < 0 ? -EFAULT : 0; + } + if ((r = sockopt_get(s, level, name, &val)) < 0) return r; return opt_out(uval, ulenp, val); @@ -766,14 +829,14 @@ SYSCALL_DEFINE(getsockopt) // programs that need it are not ours: libwayland and toybox's ping both call // recvmsg with Linux's structures and would not notice a friendlier door. // -// docs/plan/foundations.md item 1, and docs/plan/hybrid.md mechanism 1. +// docs/architecture/kernel.md item 1, and docs/architecture/devices.md mechanism 1. // --------------------------------------------------------------------------- -// Linux caps an iovec at UIO_MAXIOV = 1024. Thirty-two is the cap here for one -// reason: each entry is validated and copied individually, and a caller that -// needs more than thirty-two separate buffers in one message is describing a -// problem this kernel has not met. EMSGSIZE is Linux's answer for too many. -#define SCH_IOV_MAX 32 +// Linux caps an iovec at UIO_MAXIOV = 1024, and so does this. It was 32, and +// tmux met it on its first message: imsg hands sendmsg every queued buffer at +// once, one iovec each, and a client announcing its environment has dozens. +// Nothing is sized by the count -- each entry is fetched and copied on its own. +#define SCH_IOV_MAX 1024 // The control buffer is copied onto the kernel stack, and that stack is 4 KB // (param.h, KSTACKSIZE). 256 bytes holds CMSG_SPACE(SCM_MAXFD * 4) six times @@ -791,7 +854,7 @@ argsockfile(int n, struct socket **ps, struct file **pf) if (argint(n, &fd) < 0) return -EBADF; - if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd])) + if (fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd])) return -EBADF; if (f->type != FD_SOCKET) return -ENOTSOCK; @@ -850,6 +913,13 @@ scm_gather(struct msghdr *m, struct file **fs, int *nfp) for (off = 0; off + sizeof(struct cmsghdr) <= m->msg_controllen; ) { c = (struct cmsghdr *)(buf + off); + // The control buffer may be longer than its messages: seatd passes + // the whole of a zeroed CMSG_SPACE(MAX_FDS) array with one descriptor + // in it. Linux's CMSG_NXTHDR treats a following header too short to + // be one as the end, not as an error, and so does this. (A first + // header that short is still wrong.) + if (off > 0 && c->cmsg_len < sizeof(struct cmsghdr)) + break; if (c->cmsg_len < CMSG_LEN(0) || c->cmsg_len > m->msg_controllen - off) { r = -EINVAL; goto fail; @@ -862,16 +932,37 @@ scm_gather(struct msghdr *m, struct file **fs, int *nfp) r = -EINVAL; goto fail; } - if (ufd < 0 || ufd >= NOFILE || !(f = myproc()->ofile[ufd])) { + if (ufd < 0 || ufd >= NOFILE || !(f = myproc()->files->ofile[ufd])) { r = -EBADF; goto fail; } fs[(*nfp)++] = filedup(f); } + } else if (c->cmsg_level == SOL_SOCKET && c->cmsg_type == 2 /* SCM_CREDENTIALS */) { + // Credentials, which GDBus sends with the first byte of every + // D-Bus connection. Checked as Linux checks them -- they must be + // the sender's own, or the sender root -- and then not delivered: + // nothing here sets SO_PASSCRED to receive them, and the D-Bus + // daemon asks SO_PEERCRED instead, which answers the same question + // from the kernel's side. Refusing them made every GNOME program + // fail its D-Bus handshake ("Error sending credentials"). + struct { int pid; uint uid, gid; } cr; + struct proc *me = myproc(); + if (c->cmsg_len < CMSG_LEN(sizeof(cr))) { + r = -EINVAL; + goto fail; + } + memmove(&cr, CMSG_DATA(c), sizeof(cr)); + if (me->euid != 0 && + (cr.pid != me->tgid || + (cr.uid != me->uid && cr.uid != me->euid) || + (cr.gid != me->gid && cr.gid != me->egid))) { + r = -EPERM; + goto fail; + } } else if (c->cmsg_level == SOL_SOCKET) { - // SCM_CREDENTIALS and friends. Refused rather than ignored: a - // caller that asked to pass credentials and was told yes would - // believe the receiver got them. + // Anything else. Refused rather than ignored: a caller that asked + // for something and was told yes would believe it happened. r = -EINVAL; goto fail; } @@ -898,6 +989,7 @@ SYSCALL_DEFINE(sendmsg) uint ip = 0; ushort port = 0; int addressed = 0; + int anydata = 0; if ((r = argsockfile(0, &s, &file)) < 0) return r; @@ -923,6 +1015,61 @@ SYSCALL_DEFINE(sendmsg) addressed = 1; } + // A datagram socket sends the whole message as one datagram, however many + // iovecs it is spread over -- udev's header and properties are two. + if (sock_is_packet(s)) { + char *buf = kalloc(); // a page: kmalloc has no 4 KB class + int total = 0; + if (!buf) { + r = -ENOMEM; + goto fail; + } + for (i = 0; i < m.msg_iovlen; i++) { + char *p; + int len; + if ((len = fetch_iov(&m, i, &p)) < 0) { + kfree(buf); + r = len; + goto fail; + } + if (total + len > 4096 - 8) { + kfree(buf); + r = -EMSGSIZE; + goto fail; + } + memmove(buf + total, p, (uint)len); + total += len; + } + if (sock_domain(s) == AF_NETLINK) { + // sockaddr_nl: where it goes, by port id and/or multicast group. + uint dpid = 0, dgrp = 0; + if (m.msg_name && m.msg_namelen >= 12) { + if (copy_from_user(&dpid, (addr_t)m.msg_name + 4, 4) < 0 || + copy_from_user(&dgrp, (addr_t)m.msg_name + 8, 4) < 0) { + kfree(buf); + r = -EFAULT; + goto fail; + } + } else { + dgrp = UEVENT_GROUP_UDEV; + } + r = sock_uevent_send(s, buf, total, dpid, dgrp); + } else if (nf > 0) { + r = sockwrite_fds(s, buf, total, fs, nf); + if (r < 0) { + kfree(buf); + goto fail; // not queued: the descriptors are still ours + } + nf = 0; + } else { + r = sockwrite(s, buf, total); + } + kfree(buf); + if (r < 0) + goto fail; + return total; + } + for (i = 0; i < m.msg_iovlen; i++) { char *p; int len, w; @@ -933,6 +1080,7 @@ SYSCALL_DEFINE(sendmsg) } if (len == 0) continue; + anydata = 1; if (addressed) w = socksendto(s, p, len, ip, port); @@ -963,6 +1111,12 @@ SYSCALL_DEFINE(sendmsg) r = -EINVAL; goto fail; } + // A datagram with nothing in it is still a datagram: udev's workers say + // "done" with exactly that, and the credentials it carries are the message. + if (!anydata && sock_is_packet(s)) { + if ((r = sockwrite(s, "", 0)) < 0) + return r; + } return (int)sent; fail: @@ -973,11 +1127,12 @@ fail: SYSCALL_DEFINE(recvmsg) { + int didread = 0; struct socket *s; struct file *file; struct msghdr m; struct file *fs[SCM_MAXFD]; - char cbuf[CMSG_SPACE(SCM_MAXFD * sizeof(int))]; + char cbuf[CMSG_SPACE(SCM_MAXFD * sizeof(int)) + CMSG_SPACE(12)]; int ufd[SCM_MAXFD]; addr_t umsg; uint64 i, clen = 0; @@ -999,6 +1154,38 @@ SYSCALL_DEFINE(recvmsg) if ((file->nonblock || (flags & MSG_DONTWAIT)) && !sockreadable(s)) return -EAGAIN; + // Netlink is datagrams, and its readers size them before reading: + // GLib's network monitor asks recvmsg(MSG_PEEK|MSG_TRUNC) with no buffer + // at all and expects the datagram's length back. Answered 0, it read 0, + // the answer stayed queued, poll said readable, and Epiphany's main + // thread spun on that forever. + // + // A plain read shorter than what is waiting keeps the rest for the next + // one rather than discarding it as Linux would: musl's getifaddrs reads + // in chunks, and this whole queue is one "datagram" only by convention. + int nlret = -1; + if (sock_domain(s) == AF_NETLINK && !sock_is_packet(s)) { + uint have = socknl_pending(s); + if (have == 0) + return -EAGAIN; // never blocks; see sockread + for (i = 0; i < m.msg_iovlen; i++) { + char *p; + int len, rd; + if ((len = fetch_iov(&m, i, &p)) < 0) + return len; + rd = socknl_copy(s, (uint)got, p, len); + got += rd; + if (rd < len) + break; + } + if (!(flags & MSG_PEEK)) + socknl_take(s, (uint)got); + if ((uint)got < have && (flags & MSG_PEEK)) + mflags |= MSG_TRUNC; + nlret = (flags & MSG_TRUNC) ? (int)have : (int)got; + m.msg_iovlen = 0; // the loop below has nothing left + } + for (i = 0; i < m.msg_iovlen; i++) { char *p; int len, rd; @@ -1014,6 +1201,7 @@ SYSCALL_DEFINE(recvmsg) // one ended and the next began. if (got > 0 && (nf > 0 || !sockreadable(s))) break; + didread = 1; if (sock_domain(s) == AF_UNIX) rd = sockread_fds(s, p, len, fs, &nf); @@ -1028,8 +1216,21 @@ SYSCALL_DEFINE(recvmsg) return rd; } got += rd; - if (rd < len) - break; + if (rd < len || sock_is_packet(s)) + break; // one message per call + } + + // A receive buffer of no bytes still takes a datagram off the queue (it is + // truncated to nothing). udev's workers report in with empty datagrams and + // read them this way; skipping the read left the message queued, the + // socket readable and the manager looping on it. + if (!didread && sock_is_packet(s)) { + char z; + int rd = sock_domain(s) == AF_UNIX + ? sockread_fds(s, &z, 0, fs, &nf) + : sockread(s, &z, 0); + if (rd < 0) + return rd; } if (nf > 0) { @@ -1061,7 +1262,7 @@ SYSCALL_DEFINE(recvmsg) if (d < 0) break; if (flags & MSG_CMSG_CLOEXEC) - myproc()->ofile_flags[d] |= FD_CLOEXEC; + myproc()->files->flags[d] |= FD_CLOEXEC; ufd[installed++] = d; } @@ -1088,6 +1289,35 @@ SYSCALL_DEFINE(recvmsg) } } + // SO_PASSCRED: who sent it, after any descriptors. On a connected Unix + // socket that is always the peer, so its recorded credentials are the + // answer. WebKit sets the option on its IPC sockets and aborts if it is + // refused. + if (sock_passcred(s) && (got > 0 || sock_is_packet(s))) { + struct { int pid; uint uid, gid; } cr; + uint64 off = clen ? CMSG_SPACE(clen - CMSG_LEN(0)) : 0; + if (sock_sendercred(s, &cr.pid, &cr.uid, &cr.gid) == 0) { + if (m.msg_control && off + CMSG_LEN(sizeof(cr)) <= m.msg_controllen) { + struct cmsghdr c; + char tmp[CMSG_SPACE(sizeof(cr))]; + memset(tmp, 0, sizeof(tmp)); + c.cmsg_len = CMSG_LEN(sizeof(cr)); + c.cmsg_level = SOL_SOCKET; + c.cmsg_type = 2; // SCM_CREDENTIALS + memmove(tmp, &c, sizeof(c)); + memmove(CMSG_DATA((struct cmsghdr *)tmp), &cr, sizeof(cr)); + uint64 n2 = CMSG_SPACE(sizeof(cr)); + if (off + n2 > m.msg_controllen) + n2 = CMSG_LEN(sizeof(cr)); + if (copy_to_user((addr_t)m.msg_control + off, tmp, n2) < 0) + return -EFAULT; + clen = off + n2; + } else { + mflags |= MSG_CTRUNC; + } + } + } + // The address, for the family that has one. Same rule as recvfrom: a Unix // socket reports a length of zero rather than a sockaddr full of nothing. if (m.msg_name) { @@ -1103,6 +1333,18 @@ SYSCALL_DEFINE(recvmsg) alen = m.msg_namelen; if (alen && copy_to_user((addr_t)m.msg_name, &sa, alen) < 0) return -EFAULT; + } else if (sock_domain(s) == AF_NETLINK) { + // The kernel, as in recvfrom above. + uint16 nl[6] = { AF_NETLINK, 0, 0, 0, 0, 0 }; + uint g = sock_nl_groups(s), from = sock_nl_from(s); + nl[2] = (uint16)from; nl[3] = (uint16)(from >> 16); + nl[4] = (uint16)g; nl[5] = (uint16)(g >> 16); + alen = (uint32)sizeof(nl); + if (alen > m.msg_namelen) + alen = m.msg_namelen; + if (alen && copy_to_user((addr_t)m.msg_name, nl, alen) < 0) + return -EFAULT; + alen = (uint32)sizeof(nl); } if (copy_to_user(umsg + __builtin_offsetof(struct msghdr, msg_namelen), &alen, sizeof(alen)) < 0) @@ -1115,5 +1357,5 @@ SYSCALL_DEFINE(recvmsg) if (copy_to_user(umsg + __builtin_offsetof(struct msghdr, msg_flags), &mflags, sizeof(mflags)) < 0) return -EFAULT; - return (int)got; + return nlret >= 0 ? nlret : (int)got; } diff --git a/kernel/sys/sys_poll.c b/kernel/sys/sys_poll.c index cdcfac3..8a688a2 100644 --- a/kernel/sys/sys_poll.c +++ b/kernel/sys/sys_poll.c @@ -47,6 +47,14 @@ poll_kick(void) wakeup(&ticks); } +// How many kicks there have been. epoll's edge-triggered mode compares it: +// see ep_ready(). +uint +poll_generation(void) +{ + return __atomic_load_n(&poll_seq, __ATOMIC_ACQUIRE); +} + // Sleep one timer tick, or until a poll_kick, or until killed/signaled. // Returns 1 if a tick went by, 0 if it was a kick -- which must not count // against a timeout, or a timed poll on a busy network would end early -- @@ -79,12 +87,12 @@ do_poll(struct pollfd *fds, int nfds, int timeout_ticks) fds[i].revents = 0; if (fds[i].fd < 0) continue; // negative fd: ignore, per poll(2) - if (fds[i].fd >= NOFILE || !myproc()->ofile[fds[i].fd]) { + if (fds[i].fd >= NOFILE || !myproc()->files->ofile[fds[i].fd]) { fds[i].revents = POLLNVAL; nready++; continue; } - int rev = file_poll(myproc()->ofile[fds[i].fd], fds[i].events); + int rev = file_poll(myproc()->files->ofile[fds[i].fd], fds[i].events); if (rev) { fds[i].revents = (short)rev; nready++; @@ -217,7 +225,7 @@ sys_select(void) int want_w = fdset_isset(wr, fd); if (!want_r && !want_w) continue; - struct file *f = myproc()->ofile[fd]; + struct file *f = myproc()->files->ofile[fd]; if (!f) return -EBADF; int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0)); @@ -292,7 +300,7 @@ sys_pselect6(void) int want_w = fdset_isset(wr, fd); if (!want_r && !want_w) continue; - struct file *f = myproc()->ofile[fd]; + struct file *f = myproc()->files->ofile[fd]; if (!f) return -EBADF; int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0)); diff --git a/kernel/sys/sys_proc.c b/kernel/sys/sys_proc.c index 554ed7e..fe8de8e 100644 --- a/kernel/sys/sys_proc.c +++ b/kernel/sys/sys_proc.c @@ -24,13 +24,27 @@ addr_t sys_wait(void) { return wait(); } // wait() reports back through WEXITSTATUS. addr_t sys_exit(void) +{ + int code = 0; + argint(0, &code); + // Linux's exit is one thread's: pthread_exit, and a thread returning + // from its start function. The program ends with exit_group -- or here, + // when this is the last thread, or the native ABI, which has no threads. + struct proc *p = myproc(); + if (p->syscall_map && p->tgid != p->pid && proc_thread_count(p->tgid) > 1) + thread_exit(); + exit(W_EXITCODE(code, 0)); + return 0; +} + +addr_t +sys_exit_group(void) { int code = 0; argint(0, &code); exit(W_EXITCODE(code, 0)); return 0; } -addr_t sys_exit_group(void) { return sys_exit(); } addr_t @@ -45,26 +59,36 @@ sys_kill(void) // pid == 0 -> every process in the caller's own group // pid == -1 -> every process we may signal (approximated as our group) // pid < -1 -> the process group with id -pid - if (pid == 0 || pid == -1) - return sigsend_pgrp(myproc()->pgid, sig) < 0 ? -ESRCH : 0; - if (pid < -1) - return sigsend_pgrp(-pid, sig) < 0 ? -ESRCH : 0; - + { + int r = 0; + if (pid == 0 || pid == -1) + r = sigsend_pgrp_user(myproc()->pgid, sig); + else if (pid < -1) + r = sigsend_pgrp_user(-pid, sig); + else + goto single; + return r == -2 ? -EPERM : r < 0 ? -ESRCH : 0; + } +single: // sig == 0: existence check only if (sig == 0) return proc_exists(pid) ? 0 : -ESRCH; // Raise the signal for real: pending bit + wakeup, handler delivery on // the target's next syscall boundary, default action if unhandled. - return sigsend(pid, sig) < 0 ? -ESRCH : 0; + { + int r = sigsend_user(pid, sig); + return r == -2 ? -EPERM : r < 0 ? -ESRCH : 0; + } } // Defined with getrusage() further down; wait4() needs it too. static void rusage_fill(struct rusage *ru, uint64 ut, uint64 st); -addr_t sys_getpid(void) { return myproc()->pid; } -addr_t sys_getppid(void) { return myproc()->parent ? myproc()->parent->pid : 1; } -addr_t sys_gettid(void) { return myproc()->pid; } // single-threaded: tid == pid +// A process is its thread group: getpid is the group's id, gettid the thread's. +addr_t sys_getpid(void) { return myproc()->tgid; } +addr_t sys_getppid(void) { return myproc()->parent ? myproc()->parent->tgid : 1; } +addr_t sys_gettid(void) { return myproc()->pid; } addr_t sys_waitpid(void) @@ -119,14 +143,49 @@ sys_wait4(void) return wpid; } +// Capabilities: all of them for root, none for anyone else -- which is what +// the permission checks here amount to. glycin asks before it sandboxes an +// image loader, and stopped at ENOSYS. +addr_t +sys_capget(void) +{ + addr_t hdrp, datap; + uint32 hdr[2]; + argaddr(0, &hdrp); + argaddr(1, &datap); + if (copy_from_user(hdr, hdrp, sizeof(hdr)) < 0) + return -EFAULT; + if (hdr[0] != 0x19980330 && hdr[0] != 0x20071026 && hdr[0] != 0x20080522) { + hdr[0] = 0x20080522; // the version this kernel speaks + put_user(hdr[0], hdrp); + return -EINVAL; + } + if (datap == 0) + return 0; + int words = hdr[0] == 0x19980330 ? 1 : 2; + uint32 all = myproc()->euid == 0 ? 0xffffffffu : 0; + for (int i = 0; i < words; i++) { + uint32 d[3] = { all, all, 0 }; // effective, permitted, inheritable + if (copy_to_user(datap + i * sizeof(d), d, sizeof(d)) < 0) + return -EFAULT; + } + return 0; +} + +// Dropping capabilities is accepted and changes nothing: there are none to +// drop that the uid does not already decide. +addr_t sys_capset(void) { return 0; } + addr_t sys_clone(void) { - int flags; - argint(0, &flags); - if (flags & (CLONE_VM | CLONE_THREAD | CLONE_SIGHAND)) - return -ENOSYS; - return fork(); + uint64 flags, stack, ptid, ctid, tls; + argaddr(0, &flags); + argaddr(1, &stack); + argaddr(2, &ptid); + argaddr(3, &ctid); + argaddr(4, &tls); + return clone_proc(flags, stack, ptid, ctid, tls); } addr_t @@ -158,6 +217,8 @@ sys_uptime(void) addr_t sys_reboot(void) { + if (!capable()) + return -EPERM; cprintf("System is rebooting...\n"); // A stick left mounted should come back readable, and marked clean. schfs_ksync_all(); @@ -173,6 +234,8 @@ addr_t sys_reboot(void) addr_t sys_shutdown(void) { + if (!capable()) + return -EPERM; cprintf("System is shutting down...\n"); // S5, through the firmware's own PM1 registers and its _PTS. The port // writes after it are QEMU's, Bochs' and VirtualBox's fixed addresses, @@ -272,18 +335,32 @@ sys_setgid(void) return 0; } -addr_t sys_umask(void) { return 022; } +addr_t sys_umask(void) +{ + int mask; + argint(0, &mask); + uint old = myproc()->umask; + myproc()->umask = (uint)mask & 0777; + return old; +} addr_t sys_setreuid(void) { int ruid, euid; + struct proc *p = myproc(); argint(0, &ruid); argint(1, &euid); - if (myproc()->euid != 0) - return -EPERM; - if (ruid >= 0) myproc()->uid = (uint)ruid; - if (euid >= 0) myproc()->euid = (uint)euid; - myproc()->suid = myproc()->euid; + if (p->euid != 0) { + if (ruid >= 0 && (uint)ruid != p->uid && (uint)ruid != p->euid) + return -EPERM; + if (euid >= 0 && (uint)euid != p->uid && (uint)euid != p->euid && + (uint)euid != p->suid) + return -EPERM; + } + if (ruid >= 0 || (euid >= 0 && (uint)euid != p->uid)) + p->suid = euid >= 0 ? (uint)euid : p->euid; + if (ruid >= 0) p->uid = (uint)ruid; + if (euid >= 0) p->euid = (uint)euid; return 0; } @@ -291,12 +368,19 @@ addr_t sys_setregid(void) { int rgid, egid; + struct proc *p = myproc(); argint(0, &rgid); argint(1, &egid); - if (myproc()->euid != 0) - return -EPERM; - if (rgid >= 0) myproc()->gid = (uint)rgid; - if (egid >= 0) myproc()->egid = (uint)egid; - myproc()->sgid = myproc()->egid; + if (p->euid != 0) { + if (rgid >= 0 && (uint)rgid != p->gid && (uint)rgid != p->egid) + return -EPERM; + if (egid >= 0 && (uint)egid != p->gid && (uint)egid != p->egid && + (uint)egid != p->sgid) + return -EPERM; + } + if (rgid >= 0 || (egid >= 0 && (uint)egid != p->gid)) + p->sgid = egid >= 0 ? (uint)egid : p->egid; + if (rgid >= 0) p->gid = (uint)rgid; + if (egid >= 0) p->egid = (uint)egid; return 0; } @@ -304,12 +388,19 @@ addr_t sys_setresuid(void) { int r, e, sv; + struct proc *p = myproc(); argint(0, &r); argint(1, &e); argint(2, &sv); - if (myproc()->euid != 0) - return -EPERM; - if (r >= 0) myproc()->uid = (uint)r; - if (e >= 0) myproc()->euid = (uint)e; - if (sv >= 0) myproc()->suid = (uint)sv; + // Unprivileged, each id may be set only to one the process already has. + if (p->euid != 0) { + int ids[3] = { r, e, sv }; + for (int i = 0; i < 3; i++) + if (ids[i] >= 0 && (uint)ids[i] != p->uid && + (uint)ids[i] != p->euid && (uint)ids[i] != p->suid) + return -EPERM; + } + if (r >= 0) p->uid = (uint)r; + if (e >= 0) p->euid = (uint)e; + if (sv >= 0) p->suid = (uint)sv; return 0; } @@ -317,12 +408,18 @@ addr_t sys_setresgid(void) { int r, e, sv; + struct proc *p = myproc(); argint(0, &r); argint(1, &e); argint(2, &sv); - if (myproc()->euid != 0) - return -EPERM; - if (r >= 0) myproc()->gid = (uint)r; - if (e >= 0) myproc()->egid = (uint)e; - if (sv >= 0) myproc()->sgid = (uint)sv; + if (p->euid != 0) { + int ids[3] = { r, e, sv }; + for (int i = 0; i < 3; i++) + if (ids[i] >= 0 && (uint)ids[i] != p->gid && + (uint)ids[i] != p->egid && (uint)ids[i] != p->sgid) + return -EPERM; + } + if (r >= 0) p->gid = (uint)r; + if (e >= 0) p->egid = (uint)e; + if (sv >= 0) p->sgid = (uint)sv; return 0; } @@ -355,10 +452,39 @@ sys_getresgid(void) addr_t sys_getgroups(void) { int size; + addr_t list; + struct proc *p = myproc(); argint(0, &size); - return 0; // no supplementary groups + argaddr(1, &list); + if (size < 0) + return -EINVAL; + if (size == 0) + return p->ngroups; // how many there are + if (size < p->ngroups) + return -EINVAL; + if (p->ngroups && copy_to_user(list, p->groups, p->ngroups * sizeof(uint)) < 0) + return -EFAULT; + return p->ngroups; +} + +addr_t sys_setgroups(void) +{ + int size; + addr_t list; + uint tmp[NGROUPS_MAX]; + struct proc *p = myproc(); + argint(0, &size); + argaddr(1, &list); + if (p->euid != 0) + return -EPERM; + if (size < 0 || size > NGROUPS_MAX) + return -EINVAL; + if (size && copy_from_user(tmp, list, size * sizeof(uint)) < 0) + return -EFAULT; + p->ngroups = size; + memmove(p->groups, tmp, size * sizeof(uint)); + return 0; } -addr_t sys_setgroups(void) { return 0; } // --- Process/scheduling extensions --- @@ -417,10 +543,14 @@ addr_t sys_tgkill(void) { int tgid, tid, sig; argint(0, &tgid); argint(1, &tid); argint(2, &sig); - // In our single-threaded model tgid == tid == pid. - if (tgid <= 0) return 0; - if (sig == 0) return proc_exists(tgid) ? 0 : -ESRCH; - return sigsend(tgid, sig) < 0 ? -ESRCH : 0; + // To one thread: musl's pthread_kill, and raise(), which is tgkill on + // the calling thread. + if (tid <= 0) return -EINVAL; + if (sig == 0) return proc_exists(tid) ? 0 : -ESRCH; + { + int r = sigsend_user(tid, sig); + return r == -2 ? -EPERM : r < 0 ? -ESRCH : 0; + } } addr_t sys_tkill(void) @@ -429,7 +559,10 @@ addr_t sys_tkill(void) argint(0, &tid); argint(1, &sig); if (tid <= 0) return 0; if (sig == 0) return proc_exists(tid) ? 0 : -ESRCH; - return sigsend(tid, sig) < 0 ? -ESRCH : 0; + { + int r = sigsend_user(tid, sig); + return r == -2 ? -EPERM : r < 0 ? -ESRCH : 0; + } } addr_t sys_pause(void) diff --git a/kernel/sys/sys_sig.c b/kernel/sys/sys_sig.c index fb94294..18356cb 100644 --- a/kernel/sys/sys_sig.c +++ b/kernel/sys/sys_sig.c @@ -79,13 +79,18 @@ sys_sigwait(void) return 0; } -// musl's struct sigaction (musl 1.2.5 signal.h) for this target: -// sa_handler (8 bytes) at offset 0, then a 128-byte sigset_t (musl uses -// 128/sizeof(long) = 16 longs regardless of NSIG), sa_flags (4 bytes) + -// padding, then sa_restorer (8 bytes) at offset 144. We only care about -// those two 8-byte fields. +// The native musl hands over its own struct sigaction (musl 1.2.5 signal.h): +// sa_handler at 0, a 128-byte sigset_t, sa_flags (4 bytes) at 136, then +// sa_restorer at 144. +// +// A Linux-ABI process does not. Its musl converts to the kernel's +// k_sigaction first -- handler at 0, flags at 8, restorer at 16, mask at 24, +// thirty-two bytes in all -- and this used the native offsets for both, so +// every Alpine program that asked for the old action had 8 bytes written 112 +// bytes past the end of the struct, into whatever its stack held there. #define SA_HANDLER_OFF 0 -#define SA_RESTORER_OFF 144 +#define SA_FLAGS_OFF(p) ((p)->syscall_map ? 8 : 136) +#define SA_RESTORER_OFF(p) ((p)->syscall_map ? 16 : 144) // rt_sigaction: store the handler + restorer so deliver_signals() (called // from syscall()'s tail, kernel/sys/syscall.c) can actually jump to user @@ -108,15 +113,30 @@ sys_rt_sigaction(void) argaddr(2, &oldactp); if (sig <= 0 || sig >= 32) return -1; + struct proc *p = myproc(); if (oldactp) { - put_user(myproc()->sig_handler[sig], oldactp + SA_HANDLER_OFF); - put_user(myproc()->sig_restorer[sig], oldactp + SA_RESTORER_OFF); + put_user(p->sighand->handler[sig], oldactp + SA_HANDLER_OFF); + put_user(p->sighand->restorer[sig], oldactp + SA_RESTORER_OFF(p)); + if (p->syscall_map) { + put_user(p->sighand->flags[sig], oldactp + SA_FLAGS_OFF(p)); + } else { + uint32 fl = (uint32)p->sighand->flags[sig]; + put_user(fl, oldactp + SA_FLAGS_OFF(p)); + } } if (actp) { - copy_from_user(&myproc()->sig_handler[sig], actp + SA_HANDLER_OFF, - sizeof(myproc()->sig_handler[sig])); - copy_from_user(&myproc()->sig_restorer[sig], actp + SA_RESTORER_OFF, - sizeof(myproc()->sig_restorer[sig])); + uint64 h, r, fl = 0; + if (copy_from_user(&h, actp + SA_HANDLER_OFF, sizeof(h)) < 0 || + copy_from_user(&r, actp + SA_RESTORER_OFF(p), sizeof(r)) < 0 || + copy_from_user(&fl, actp + SA_FLAGS_OFF(p), + p->syscall_map ? 8 : 4) < 0) + return -EFAULT; + // SIGKILL and SIGSTOP cannot be caught or ignored. + if (sig == SIGKILL || sig == SIGSTOP) + return -EINVAL; + p->sighand->handler[sig] = h; + p->sighand->restorer[sig] = r; + p->sighand->flags[sig] = fl; } return 0; } @@ -195,9 +215,19 @@ deliver_signals(struct trapframe *tf) // Except for a signal that was blocked when it arrived: sigsend() // left it pending without acting, and now that it is deliverable its // default action happens here. - if (myproc()->sig_handler[sig] <= 1) { + // SIGSTOP cannot be caught or ignored; the other three stop only by + // default. Stopped here and resumed here, by SIGCONT or SIGKILL. + if (sig == SIGSTOP || + (sig_default_stops(sig) && myproc()->sighand->handler[sig] == 0)) { myproc()->pending_signals &= ~(1ULL << sig); - if (myproc()->sig_handler[sig] == 0 && sig_default_kills(sig)) { + proc_stop(sig); + if (myproc()->killed) + exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL)); + continue; + } + if (myproc()->sighand->handler[sig] <= 1) { + myproc()->pending_signals &= ~(1ULL << sig); + if (myproc()->sighand->handler[sig] == 0 && sig_default_kills(sig)) { myproc()->killed = 1; if (myproc()->killsig == 0) myproc()->killsig = sig; @@ -253,8 +283,8 @@ deliver_signals(struct trapframe *tf) // shifted rsp, and its ret fell into the sigreturn trampoline, // silently undoing everything. exec() handles this same quirk by // setting tf->rcx = elf.entry. - tf->rip = myproc()->sig_handler[sig]; - tf->rcx = myproc()->sig_handler[sig]; + tf->rip = myproc()->sighand->handler[sig]; + tf->rcx = myproc()->sighand->handler[sig]; return; } } @@ -314,7 +344,7 @@ addr_t sys_rt_sigsuspend(void) uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask; int will_deliver = 0; for (int sig = 1; sig < 32; sig++) - if ((deliverable & (1ULL << sig)) && myproc()->sig_handler[sig]) + if ((deliverable & (1ULL << sig)) && myproc()->sighand->handler[sig]) { will_deliver = 1; break; } if (will_deliver && !myproc()->in_signal) { diff --git a/kernel/sys/sys_time.c b/kernel/sys/sys_time.c index 412a3fa..3824695 100644 --- a/kernel/sys/sys_time.c +++ b/kernel/sys/sys_time.c @@ -60,23 +60,65 @@ sys_gettimeofday(void) return 0; } +// Sleep for a timespec, in ticks, rounded up. +// +// Only tv_sec was ever read: `sleep 0.5` returned at once and every +// sub-second sleep -- cmatrix's frame delay, a game loop's -- was no sleep at +// all. And a signal could not end one: Ctrl-Z on `sleep 100` did nothing. +static addr_t +sleep_ticks(uint64 want, addr_t remaddr) +{ + struct proc *p = myproc(); + acquire(&tickslock); + uint t0 = ticks; + while ((uint64)(uint)(ticks - t0) < want) { + if (p->killed || sig_attention(p)) { + release(&tickslock); + int e = p->killed ? -EINTR : sig_blocking_check(); + if (e) { + if (remaddr) { + uint64 left = want - (uint)(ticks - t0); + struct timespec r = { (long)(left / HZ), + (long)((left % HZ) * (1000000000 / HZ)) }; + copy_to_user(remaddr, &r, sizeof(r)); + } + return e; + } + acquire(&tickslock); + continue; + } + sleep(&ticks, &tickslock); + } + release(&tickslock); + return 0; +} + +static int +timespec_ticks(const struct timespec *ts, uint64 *out) +{ + if (ts->tv_sec < 0 || ts->tv_nsec < 0 || ts->tv_nsec >= 1000000000) + return -EINVAL; + uint64 sec = (uint64)ts->tv_sec; + if (sec > 0xFFFFFFFFULL / HZ) + sec = 0xFFFFFFFFULL / HZ; // a few years: forever enough + *out = sec * HZ + ((uint64)ts->tv_nsec * HZ + 999999999) / 1000000000; + return 0; +} + addr_t sys_nanosleep(void) { - addr_t tsaddr, raw; - if (argaddr(0, &tsaddr) < 0 || fetchaddr(tsaddr, &raw) < 0) - return -1; - int64 sec = (int64)raw; - if (sec > 0) { - acquire(&tickslock); - uint t0 = ticks; - while (ticks - t0 < (uint)(sec * HZ)) { - if (myproc()->killed) { release(&tickslock); return -EINTR; } - sleep(&ticks, &tickslock); - } - release(&tickslock); - } - return 0; + addr_t tsaddr, remaddr; + struct timespec ts; + uint64 want; + argaddr(0, &tsaddr); + argaddr(1, &remaddr); + if (copy_from_user(&ts, tsaddr, sizeof(ts)) < 0) + return -EFAULT; + int e = timespec_ticks(&ts, &want); + if (e) + return e; + return sleep_ticks(want, remaddr); } addr_t @@ -173,20 +215,34 @@ sys_clock_getres(void) addr_t sys_clock_nanosleep(void) { - // Treat the same as nanosleep: arg2 is the timespec, arg1 is flags. - // Linux: clock_nanosleep(clockid, flags, rqtp, rmtp) - addr_t tsaddr, raw; - if (argaddr(2, &tsaddr) < 0 || fetchaddr(tsaddr, &raw) < 0) - return -1; - int64 sec = (int64)raw; - if (sec > 0) { - acquire(&tickslock); - uint t0 = ticks; - while (ticks - t0 < (uint)(sec * HZ)) { - if (myproc()->killed) { release(&tickslock); return -EINTR; } - sleep(&ticks, &tickslock); - } - release(&tickslock); + // clock_nanosleep(clockid, flags, rqtp, rmtp). TIMER_ABSTIME (1) makes + // rqtp a time on that clock rather than an interval. + int clkid, flags; + addr_t tsaddr, remaddr; + struct timespec ts; + uint64 want; + argint(0, &clkid); + argint(1, &flags); + argaddr(2, &tsaddr); + argaddr(3, &remaddr); + if (copy_from_user(&ts, tsaddr, sizeof(ts)) < 0) + return -EFAULT; + if (flags & 1) { + uint64 sec, usec; + wallclock(&sec, &usec); + if (clkid == 1) + sec -= boot_epoch; + long dsec = ts.tv_sec - (long)sec; + long dns = ts.tv_nsec - (long)(usec * 1000); + if (dns < 0) { dns += 1000000000; dsec--; } + if (dsec < 0) + return 0; // already past + ts.tv_sec = dsec; + ts.tv_nsec = dns; + remaddr = 0; // never written for an absolute sleep } - return 0; + int e = timespec_ticks(&ts, &want); + if (e) + return e; + return sleep_ticks(want, remaddr); } diff --git a/kernel/sys/syscall.c b/kernel/sys/syscall.c index 19c7c97..4140f51 100644 --- a/kernel/sys/syscall.c +++ b/kernel/sys/syscall.c @@ -50,6 +50,26 @@ fetchstr(addr_t addr, char **pp) return len; } +// /proc/strace. Which argument of a Linux syscall is a path, by number, so +// the trace can show the name rather than an address: that is most of what +// one wants from a trace of a program that will not start. +static int +path_arg(uint64 nr) +{ + switch (nr) { + case 2: case 4: case 6: case 21: case 59: case 76: case 80: case 82: + case 83: case 84: case 85: case 86: case 87: case 88: case 89: case 90: + case 92: case 137: case 161: + return 0; + case 257: case 258: case 260: case 262: case 263: case 264: case 267: + case 268: case 269: case 280: case 316: case 332: case 437: case 439: + return 1; + } + return -1; +} + +static void strace_call(struct trapframe *tf); + static addr_t fetcharg(int n) { @@ -117,6 +137,10 @@ static addr_t (*syscalls[NSYSCALLS])(void) = { void syscall(struct trapframe *tf) { + if (myproc()->strace) { + strace_call(tf); + return; + } kstack_check_canary("syscall entry"); myproc()->tf = tf; uint64 orig = myproc()->tf->rax; @@ -139,3 +163,63 @@ syscall(struct trapframe *tf) kstack_check_canary("syscall exit"); } + +// syscall() for a traced process: the same dispatch, with a line in the log +// for each call -- the Linux number, four arguments, a path where there is +// one, and what came back. The path is copied before the call runs, since +// execve replaces the memory it is in. +static void +strace_call(struct trapframe *tf) +{ + struct proc *p = myproc(); + uint64 orig = tf->rax, num = orig; + uint64 a0 = tf->rdi, a1 = tf->rsi, a2 = tf->rdx, a3 = tf->r10; + char path[64]; + int pa = p->syscall_map ? path_arg(orig) : -1; + + path[0] = 0; + if (pa >= 0) { + addr_t u = pa == 0 ? a0 : a1; + int i; + for (i = 0; i < (int)sizeof(path) - 1; i++) { + char c; + if (copy_from_user(&c, u + i, 1) < 0 || c == 0) + break; + path[i] = c; + } + path[i] = 0; + } + + // The result line is written when the call returns, so a call that never + // does would leave no trace at all. Everything that can wait gets a line + // on the way in as well. + switch (orig) { + case 0: case 2: case 7: case 23: case 35: case 43: case 45: case 47: + case 59: case 61: case 202: case 232: case 257: case 270: case 271: + case 281: case 288: + if (p->syscall_map) + kinfo("strace %d %s: %d(%lx, %lx, %lx, %lx)%s%s%s ...\n", + p->pid, p->name, (int)orig, a0, a1, a2, a3, + pa >= 0 ? " \"" : "", path, pa >= 0 ? "\"" : ""); + } + + kstack_check_canary("syscall entry"); + p->tf = tf; + if (p->syscall_map) + num = (num < (uint64)p->map_size) ? p->syscall_map[num] : 0; + if (num > 0 && num < NSYSCALLS && syscalls[num]) + tf->rax = syscalls[num](); + else + tf->rax = -ENOSYS; + + // A successful execve has replaced the program: its name is the new one. + kinfo("strace %d %s: %s%d(%lx, %lx, %lx, %lx)%s%s%s = %ld\n", + p->pid, p->name, p->syscall_map ? "" : "native ", (int)orig, + a0, a1, a2, a3, pa >= 0 ? " \"" : "", path, pa >= 0 ? "\"" : "", + (long)tf->rax); + + if (p->killed) + exit(W_EXITCODE(0, p->killsig ? p->killsig : SIGKILL)); + deliver_signals(tf); + kstack_check_canary("syscall exit"); +} diff --git a/kernel/sys/table.inc b/kernel/sys/table.inc index c2ac019..3e342aa 100644 --- a/kernel/sys/table.inc +++ b/kernel/sys/table.inc @@ -141,11 +141,11 @@ SYSCALL(192, 55, getsockopt) // reads whatever it last left there and invents a flag out of stack litter. SYSCALL(198, 288, accept4) // sendmsg/recvmsg exist for the one thing only they can do: carry a descriptor -// from one process to another (SCM_RIGHTS). docs/plan/foundations.md item 1. +// from one process to another (SCM_RIGHTS). docs/architecture/kernel.md item 1. SYSCALL(199, 46, sendmsg) SYSCALL(200, 47, recvmsg) // memfd_create: an anonymous shared memory object reached by descriptor. -// docs/plan/foundations.md item 2 -- the thing a Wayland buffer crosses on, +// docs/architecture/kernel.md item 2 -- the thing a Wayland buffer crosses on, // and the reason the descriptor passing above had to exist first. SYSCALL(201, 319, memfd_create) @@ -213,8 +213,11 @@ SYSCALL(127, 92, chown) SYSCALL(128, 93, fchown) SYSCALL(129, 94, lchown) SYSCALL(130, 84, rmdir) -SYSCALL(131, NO_LINUX, fsync) -SYSCALL(132, NO_LINUX, fdatasync) +// fsync and fdatasync had no Linux numbers from the first commit on, and +// every Linux program that saves a file carefully got ENOSYS: GLib's atomic +// file writes, gtk-update-icon-cache, dbus-uuidgen, apk's triggers. +SYSCALL(131, 74, fsync) +SYSCALL(132, 75, fdatasync) SYSCALL(133, 162, sync) SYSCALL(134, 17, pread) SYSCALL(135, 18, pwrite) @@ -298,6 +301,13 @@ SYSCALL(205, 284, eventfd) SYSCALL(206, 73, flock) SYSCALL(207, 51, getsockname) SYSCALL(208, 52, getpeername) +SYSCALL(209, 125, capget) +SYSCALL(210, 126, capset) +SYSCALL(211, 439, faccessat2) +SYSCALL(212, 253, inotify_init) +SYSCALL(213, 294, inotify_init1) +SYSCALL(214, 254, inotify_add_watch) +SYSCALL(215, 255, inotify_rm_watch) // --- mounts --- SYSCALL(185, 165, mount) diff --git a/ldls/ko.c b/ldls/ko.c index 46783bb..8874496 100644 --- a/ldls/ko.c +++ b/ldls/ko.c @@ -708,6 +708,8 @@ gs_form(const unsigned char *p, int want_pre) default: if (op >= 0x0f90 && op <= 0x0f9f) /* setcc */ { imm = 0; break; } + if (op >= 0x0f40 && op <= 0x0f4f) /* cmovcc -- r8169 reads numa_node so */ + { imm = 0; break; } return 0; } return pre + 4 + imm; diff --git a/ldls/ko.h b/ldls/ko.h index 73abd79..d39dd5e 100644 --- a/ldls/ko.h +++ b/ldls/ko.h @@ -37,7 +37,7 @@ // Undefined symbols the report will hold. Measured over the same corpus the // median is 25 and the worst seen is 245, so this is room to spare -- and // `undef_lost` says so rather than the list quietly ending. -#define KO_MAXUNDEF 512 +#define KO_MAXUNDEF 4096 // amdgpu alone imports well over a thousand // --- ELF64, the relocatable subset ------------------------------------------ // diff --git a/ldls/kpi/base.c b/ldls/kpi/base.c new file mode 100644 index 0000000..8baadbe --- /dev/null +++ b/ldls/kpi/base.c @@ -0,0 +1,985 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// The rest of the kernel a modern driver stands on: the driver core, device- +// managed resources, printf, time, bitmaps, firmware, and the data symbols. +// See ldls/kpi/kpi.h. +// +// r8169 is the reason for most of it. Unlike 8139too and e1000 it does not +// talk to its PHY itself: it registers an MDIO bus with libphy, and libphy +// finds the PHY on it and binds a PHY driver (realtek.ko) through Linux's +// driver core -- bus_type.match, then device_driver.probe. r8169 then checks +// that a dedicated PHY driver was bound and refuses the device if not. So the +// driver core here is not decoration; it is what makes the device usable. +// +// Several functions below exist because a stub's zero is a lie in their +// case, and each says which lie it was: +// __list_add_valid_or_report 0 = "corrupt list", and with CONFIG_DEBUG_LIST +// every list_add asks, so nothing is ever added; +// try_module_get 0 = "module going away", phylib refuses the PHY; +// dev_err_probe returns its error, or failure looks like success; +// eth_platform_get_mac_address 0 = "here is your MAC", all zeroes; +// firmware_request_nowarn 0 = "loaded", with a NULL firmware; +// __bitmap_* phylib computes link modes with them; +// __const_udelay a delay of nothing turns every poll-with- +// timeout into an immediate timeout. + +#include "core/types.h" +#include "uapi/user.h" +#include "uapi/fcntl.h" +#include "uapi/posix.h" +#include "kpi.h" + +typedef __builtin_va_list va_list; +#define va_start(a, l) __builtin_va_start(a, l) +#define va_end(a) __builtin_va_end(a) +#define va_arg(a, t) __builtin_va_arg(a, t) + +extern void *kpi_kmalloc(unsigned long n, unsigned int flags); +extern int kpi_pci_enable_device(void *pdev); +extern void *kpi_pci_iomap(void *pdev, int bar, unsigned long max); +extern int kpi_pci_read_config_word(void *pdev, int where, unsigned short *v); +extern void *kpi_alloc_etherdev_mqs(int priv, unsigned int tx, unsigned int rx); +extern void *kpi_dma_alloc_attrs(void *dev, unsigned long size, + unsigned long long *handle, unsigned int gfp, + unsigned long attrs); +extern void *kpi_alloc_pages(unsigned int gfp, unsigned int order); + +static void *zalloc(unsigned long n) { return kpi_kmalloc(n, 0); } + +static unsigned long rdp(const void *base, unsigned int off) +{ unsigned long v; __builtin_memcpy(&v, (const char *)base + off, 8); return v; } +static void wrp(void *base, unsigned int off, unsigned long v) +{ __builtin_memcpy((char *)base + off, &v, 8); } + +static int slen(const char *s) { int n = 0; while (s && s[n]) n++; return n; } + +// --- printf --------------------------------------------------------------------- +// +// Linux's format language: C's, plus %p followed by letters that say what the +// pointer is. Of those, only the MAC address (%pM) is worth expanding here; +// the rest print as a pointer and consume their letters, as Linux does, so +// that the text after them is not mistaken for part of the format. + +struct out { char *buf; unsigned long cap, n; }; + +static void put(struct out *o, char c) +{ + if (o->n + 1 < o->cap) + o->buf[o->n] = c; + o->n++; +} + +static void +putnum(struct out *o, unsigned long long v, int base, int neg, int width, + int zero, int left, int upper, int prec) +{ + char tmp[24]; + const char *dig = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + int n = 0, len, pad; + + if (v == 0 && prec != 0) + tmp[n++] = '0'; + while (v) { tmp[n++] = dig[v % (unsigned)base]; v /= (unsigned)base; } + while (n < prec && n < (int)sizeof(tmp)) tmp[n++] = '0'; + len = n + (neg ? 1 : 0); + pad = width > len ? width - len : 0; + if (!left && !zero) while (pad-- > 0) put(o, ' '); + if (neg) put(o, '-'); + if (!left && zero) while (pad-- > 0) put(o, '0'); + while (n > 0) put(o, tmp[--n]); + if (left) while (pad-- > 0) put(o, ' '); +} + +int +kpi_vsnprintf(char *buf, unsigned long cap, const char *f, va_list ap) +{ + struct out o = { buf, cap, 0 }; + + if (!f) f = ""; + while (*f) { + if (*f != '%') { put(&o, *f++); continue; } + f++; + int left = 0, zero = 0, width = 0, prec = -1, lng = 0; + for (;; f++) { + if (*f == '-') left = 1; + else if (*f == '0') zero = 1; + else if (*f == '+' || *f == ' ' || *f == '#') ; + else break; + } + if (*f == '*') { width = va_arg(ap, int); f++; } + else while (*f >= '0' && *f <= '9') width = width * 10 + (*f++ - '0'); + if (*f == '.') { + f++; prec = 0; + if (*f == '*') { prec = va_arg(ap, int); f++; } + else while (*f >= '0' && *f <= '9') prec = prec * 10 + (*f++ - '0'); + } + for (;; f++) { + if (*f == 'l') lng++; + else if (*f == 'z' || *f == 't' || *f == 'j') lng = 2; + else if (*f == 'h') ; + else break; + } + char c = *f ? *f++ : 0; + switch (c) { + case 'd': case 'i': { + long long v = lng ? va_arg(ap, long long) : va_arg(ap, int); + int neg = v < 0; + putnum(&o, neg ? (unsigned long long)-v : (unsigned long long)v, 10, + neg, width, zero, left, 0, prec); + break; + } + case 'u': case 'x': case 'X': case 'o': { + unsigned long long v = lng ? va_arg(ap, unsigned long long) + : va_arg(ap, unsigned int); + putnum(&o, v, c == 'u' ? 10 : c == 'o' ? 8 : 16, 0, width, zero, left, + c == 'X', prec); + break; + } + case 'c': + put(&o, (char)va_arg(ap, int)); + break; + case 's': { + const char *s = va_arg(ap, const char *); + int n, pad; + if (!s) s = "(null)"; + n = slen(s); + if (prec >= 0 && n > prec) n = prec; + pad = width > n ? width - n : 0; + if (!left) while (pad-- > 0) put(&o, ' '); + for (int i = 0; i < n; i++) put(&o, s[i]); + if (left) while (pad-- > 0) put(&o, ' '); + break; + } + case 'p': { + const unsigned char *p = va_arg(ap, const unsigned char *); + if ((*f == 'M' || *f == 'm') && p) { + for (int i = 0; i < 6; i++) { + if (i && *f == 'M') put(&o, ':'); + putnum(&o, p[i], 16, 0, 2, 1, 0, 0, -1); + } + } else if (*f == 'e') { + put(&o, 'E'); + long e = (long)p; + putnum(&o, (unsigned long long)(e < 0 ? -e : e), 10, e < 0, 0, 0, 0, 0, -1); + } else { + put(&o, '0'); put(&o, 'x'); + putnum(&o, (unsigned long)p, 16, 0, 0, 0, 0, 0, -1); + } + while ((*f >= 'a' && *f <= 'z') || (*f >= 'A' && *f <= 'Z') || + (*f >= '0' && *f <= '9')) + f++; + break; + } + case '%': + put(&o, '%'); + break; + default: + break; + } + } + if (cap) + buf[o.n < cap ? o.n : cap - 1] = 0; + return (int)o.n; +} + +int +kpi_snprintf(char *buf, unsigned long cap, const char *f, ...) +{ + va_list ap; + va_start(ap, f); + int n = kpi_vsnprintf(buf, cap, f, ap); + va_end(ap); + return n; +} + +int +kpi_sprintf(char *buf, const char *f, ...) +{ + va_list ap; + va_start(ap, f); + int n = kpi_vsnprintf(buf, 1UL << 20, f, ap); + va_end(ap); + return n; +} + +char * +kpi_kvasprintf(const char *f, va_list ap) +{ + char tmp[512]; + va_list ap2; + __builtin_va_copy(ap2, ap); + int n = kpi_vsnprintf(tmp, sizeof(tmp), f, ap2); + va_end(ap2); + char *s = zalloc((unsigned long)n + 1); + if (s) + kpi_vsnprintf(s, (unsigned long)n + 1, f, ap); + return s; +} + +char * +kpi_kasprintf(unsigned int gfp, const char *f, ...) +{ + (void)gfp; + va_list ap; + va_start(ap, f); + char *s = kpi_kvasprintf(f, ap); + va_end(ap); + return s; +} + +// A kernel message: the level byte off the front, the driver's text after a +// prefix that says whose it is. +static void +emit(const char *who, const char *f, va_list ap) +{ + char line[512]; + + if (f && (unsigned char)f[0] == 0x01 && f[1]) + f += 2; + kpi_vsnprintf(line, sizeof(line), f, ap); + kpi_log(" "); + if (who && *who) { kpi_log(who); kpi_log(": "); } + kpi_log(line); + int n = slen(line); + if (n == 0 || line[n - 1] != '\n') + kpi_log("\n"); +} + +static const char * +dev_name_of(void *dev) +{ + if (!dev || !kpi_l.dev_kobj_ok) + return ""; + const char *n = (const char *)rdp(dev, kpi_l.dev_kobj + kpi_l.kobj_name); + return n ? n : ""; +} + +int kpi_vprintk(const char *f, va_list ap) { emit(0, f, ap); return 0; } + +int +kpi_printk_fmt(const char *f, ...) +{ + va_list ap; va_start(ap, f); emit(0, f, ap); va_end(ap); return 0; +} + +void +kpi_dev_printk(void *dev, const char *f, ...) +{ + va_list ap; va_start(ap, f); emit(dev_name_of(dev), f, ap); va_end(ap); +} + +// netdev_* take the net_device, whose name is not kept anywhere this can +// read; "ldls0" is what the interface is called on this side anyway. +void +kpi_netdev_printk_fmt(void *nd, const char *f, ...) +{ + (void)nd; + va_list ap; va_start(ap, f); emit("ldls0", f, ap); va_end(ap); +} + +void +kpi_netdev_printk_lvl(const char *lvl, void *nd, const char *f, ...) +{ + (void)lvl; (void)nd; + va_list ap; va_start(ap, f); emit("ldls0", f, ap); va_end(ap); +} + +int +kpi_dev_err_probe(void *dev, int err, const char *f, ...) +{ + va_list ap; va_start(ap, f); emit(dev_name_of(dev), f, ap); va_end(ap); + return err; +} + +void +kpi_warn_printk(const char *f, ...) +{ + kpi_log(" WARNING "); + va_list ap; va_start(ap, f); emit(0, f, ap); va_end(ap); +} + +// --- strings ---------------------------------------------------------------------- + +int +kpi_strcmp(const char *a, const char *b) +{ + while (*a && *a == *b) { a++; b++; } + return (unsigned char)*a - (unsigned char)*b; +} + +char * +kpi_strcpy(char *d, const char *s) +{ + char *r = d; + while ((*d++ = *s++)) ; + return r; +} + +const char * +kpi_kstrdup_const(const char *s, unsigned int gfp) +{ + (void)gfp; + if (!s) return 0; + char *d = zalloc((unsigned long)slen(s) + 1); + if (d) kpi_strcpy(d, s); + return d; +} + +int +kpi_kstrtoull(const char *s, unsigned int base, unsigned long long *res) +{ + unsigned long long v = 0; + int any = 0; + if (base == 0) { + base = 10; + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; } + else if (s[0] == '0') base = 8; + } + for (; *s && *s != '\n'; s++) { + int d = (*s >= '0' && *s <= '9') ? *s - '0' : + (*s >= 'a' && *s <= 'f') ? *s - 'a' + 10 : + (*s >= 'A' && *s <= 'F') ? *s - 'A' + 10 : 99; + if (d >= (int)base) return -22; + v = v * base + (unsigned)d; + any = 1; + } + if (!any) return -22; + *res = v; + return 0; +} + +// --- time -------------------------------------------------------------------------- +// +// HZ is a compile-time constant in the module. It is 1000 on the kernels this +// has been built against, and jiffies has to count at the module's HZ or +// every delay it computes in jiffies is wrong by the ratio -- phylib's +// one-second link poll became ten seconds at the old 100. + +static unsigned long long +now_ns(void) +{ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) + return (unsigned long long)uptime() * 10000000ULL; + return (unsigned long long)ts.tv_sec * 1000000000ULL + (unsigned long long)ts.tv_nsec; +} + +unsigned long long kpi_ktime_get(void) { return now_ns(); } + +unsigned long +kpi_jiffies_ms(void) +{ + return (unsigned long)(now_ns() / 1000000ULL); +} + +static void +spin_us(unsigned long us) +{ + unsigned long long end = now_ns() + (unsigned long long)us * 1000ULL; + while (now_ns() < end) + __builtin_ia32_pause(); +} + +static void +sleep_us(unsigned long us) +{ + // Below a scheduler tick, a sleep would round up to the tick; the waits + // that short are MDIO and register polls, where that would be a hundredfold. + if (us < 10000) { + spin_us(us); + return; + } + struct timespec ts = { (long)(us / 1000000), (long)(us % 1000000) * 1000 }; + nanosleep(&ts); +} + +// udelay(n) arrives as __const_udelay(n * 0x10c7): microseconds scaled by +// 2^32 / 10^6. +void kpi_const_udelay(unsigned long xloops) { spin_us((unsigned long)(((unsigned long long)xloops * 1000000ULL) >> 32) + 1); } +void kpi_udelay(unsigned long us) { spin_us(us); } +void kpi_ndelay(unsigned long ns) { spin_us(ns / 1000 + 1); } +void kpi_msleep(unsigned int ms) { sleep_us((unsigned long)ms * 1000); } +void kpi_usleep_range(unsigned long min, unsigned long max, unsigned int st) +{ (void)max; (void)st; sleep_us(min); } + +// --- bitmaps ------------------------------------------------------------------------- +// +// phylib keeps link modes in 121-bit maps, past the size Linux inlines, so +// these are called for every "what can both ends do" question it asks. + +#define WORDS(n) (((n) + 63) / 64) + +static unsigned long +lastmask(unsigned int nbits) +{ + return (nbits % 64) ? (1UL << (nbits % 64)) - 1 : ~0UL; +} + +int +kpi_bitmap_and(unsigned long *d, const unsigned long *a, const unsigned long *b, + unsigned int nbits) +{ + unsigned long any = 0; + unsigned int w = WORDS(nbits); + for (unsigned int i = 0; i < w; i++) { + d[i] = a[i] & b[i]; + any |= (i == w - 1) ? (d[i] & lastmask(nbits)) : d[i]; + } + return any != 0; +} + +int +kpi_bitmap_andnot(unsigned long *d, const unsigned long *a, + const unsigned long *b, unsigned int nbits) +{ + unsigned long any = 0; + unsigned int w = WORDS(nbits); + for (unsigned int i = 0; i < w; i++) { + d[i] = a[i] & ~b[i]; + any |= (i == w - 1) ? (d[i] & lastmask(nbits)) : d[i]; + } + return any != 0; +} + +void +kpi_bitmap_or(unsigned long *d, const unsigned long *a, const unsigned long *b, + unsigned int nbits) +{ + for (unsigned int i = 0; i < WORDS(nbits); i++) + d[i] = a[i] | b[i]; +} + +int +kpi_bitmap_equal(const unsigned long *a, const unsigned long *b, + unsigned int nbits) +{ + unsigned int w = WORDS(nbits); + for (unsigned int i = 0; i < w; i++) { + unsigned long m = (i == w - 1) ? lastmask(nbits) : ~0UL; + if ((a[i] ^ b[i]) & m) return 0; + } + return 1; +} + +int +kpi_bitmap_intersects(const unsigned long *a, const unsigned long *b, + unsigned int nbits) +{ + unsigned int w = WORDS(nbits); + for (unsigned int i = 0; i < w; i++) { + unsigned long m = (i == w - 1) ? lastmask(nbits) : ~0UL; + if (a[i] & b[i] & m) return 1; + } + return 0; +} + +unsigned long +kpi_find_first_bit(const unsigned long *addr, unsigned long size) +{ + for (unsigned long i = 0; i < size; i++) + if (addr[i / 64] & (1UL << (i % 64))) + return i; + return size; +} + +// --- the driver core ----------------------------------------------------------------- +// +// Enough of it to bind a driver to a device on a bus, which is all libphy +// asks of it: drivers and devices are remembered, and whenever one arrives it +// is offered to the other side -- bus_type.match decides, and the bus's probe +// or else the driver's runs with device.driver already set, as Linux does. +// Nothing is reference-counted and nothing is ever torn down; see the note on +// memory in kpi.c. + +#define NCORE 64 +static void *core_drv[NCORE]; +static int core_ndrv; +static void *core_dev[NCORE]; +static int core_ndev; + +static int +core_bind(void *dev, void *drv) +{ + void *bus = (void *)rdp(dev, kpi_l.dev_bus); + int (*match)(void *, void *) = bus ? (int (*)(void *, void *))rdp(bus, kpi_l.bus_match) : 0; + int (*bprobe)(void *) = bus ? (int (*)(void *))rdp(bus, kpi_l.bus_probe) : 0; + int (*dprobe)(void *) = (int (*)(void *))rdp(drv, kpi_l.drv_probe); + + if (match && match(dev, drv) <= 0) + return 0; + wrp(dev, kpi_l.dev_driver, (unsigned long)drv); + int r = bprobe ? bprobe(dev) : dprobe ? dprobe(dev) : 0; + const char *dn = (const char *)rdp(drv, kpi_l.drv_name); + kpi_log(" kpi: bound "); kpi_log(dn ? dn : "a driver"); + kpi_log(" to "); kpi_log(dev_name_of(dev)); + if (r != 0) { + kpi_log(" -- probe said "); kpi_lognum(r); kpi_log(", unbound\n"); + wrp(dev, kpi_l.dev_driver, 0); + return 0; + } + kpi_log("\n"); + return 1; +} + +static void +core_offer_dev(void *dev) +{ + void *bus; + if (!kpi_l.core_ok || rdp(dev, kpi_l.dev_driver)) + return; + bus = (void *)rdp(dev, kpi_l.dev_bus); + if (!bus) + return; + for (int i = 0; i < core_ndrv; i++) + if ((void *)rdp(core_drv[i], kpi_l.drv_bus) == bus && core_bind(dev, core_drv[i])) + return; +} + +int +kpi_driver_register(void *drv) +{ + if (core_ndrv < NCORE) + core_drv[core_ndrv++] = drv; + if (!kpi_l.core_ok) + return 0; + void *bus = (void *)rdp(drv, kpi_l.drv_bus); + for (int i = 0; i < core_ndev; i++) + if (!rdp(core_dev[i], kpi_l.dev_driver) && + (void *)rdp(core_dev[i], kpi_l.dev_bus) == bus) + core_bind(core_dev[i], drv); + return 0; +} + +void +kpi_driver_unregister(void *drv) +{ + for (int i = 0; i < core_ndrv; i++) + if (core_drv[i] == drv) core_drv[i] = core_drv[--core_ndrv]; +} + +int +kpi_device_add(void *dev) +{ + int i; + for (i = 0; i < core_ndev; i++) + if (core_dev[i] == dev) break; + if (i == core_ndev && core_ndev < NCORE) + core_dev[core_ndev++] = dev; + core_offer_dev(dev); + return 0; +} + +void +kpi_device_del(void *dev) +{ + for (int i = 0; i < core_ndev; i++) + if (core_dev[i] == dev) core_dev[i] = core_dev[--core_ndev]; +} + +void kpi_device_release_driver(void *dev) +{ if (kpi_l.core_ok) wrp(dev, kpi_l.dev_driver, 0); } + +void *kpi_get_device(void *dev) { return dev; } + +int +kpi_dev_set_name(void *dev, const char *f, ...) +{ + if (!kpi_l.dev_kobj_ok) + return 0; + va_list ap; + va_start(ap, f); + char *s = kpi_kvasprintf(f, ap); + va_end(ap); + if (!s) + return -12; + wrp(dev, kpi_l.dev_kobj + kpi_l.kobj_name, (unsigned long)s); + return 0; +} + +const char * +kpi_dev_driver_string(void *dev) +{ + if (kpi_l.core_ok && dev) { + void *drv = (void *)rdp(dev, kpi_l.dev_driver); + if (drv) { + const char *n = (const char *)rdp(drv, kpi_l.drv_name); + if (n) return n; + } + } + return ""; +} + +// --- device-managed resources ---------------------------------------------------------- +// +// Freed when the device goes, which here is never. What has to be real is +// devres_find: mdio_devres registers a bus only after finding the record +// devm_mdiobus_alloc made, and says -EINVAL otherwise. + +#define NRES 128 +static struct { void *dev, *release, *data; } res[NRES]; +static int nres; + +void * +kpi_devres_alloc(void *release, unsigned long size, unsigned int gfp, int nid, + const char *name) +{ + (void)gfp; (void)nid; (void)name; + void *d = zalloc(size ? size : 8); + if (d && nres < NRES) { + res[nres].dev = 0; res[nres].release = release; res[nres].data = d; + nres++; + } + return d; +} + +void +kpi_devres_add(void *dev, void *data) +{ + for (int i = 0; i < nres; i++) + if (res[i].data == data) { res[i].dev = dev; return; } +} + +void * +kpi_devres_find(void *dev, void *release, int (*match)(void *, void *, void *), + void *mdata) +{ + for (int i = 0; i < nres; i++) + if (res[i].dev == dev && res[i].release == release && + (!match || match(dev, res[i].data, mdata))) + return res[i].data; + return 0; +} + +void +kpi_devres_free(void *data) +{ + for (int i = 0; i < nres; i++) + if (res[i].data == data) { res[i] = res[--nres]; return; } +} + +void *kpi_devm_kmalloc(void *dev, unsigned long size, unsigned int gfp) +{ (void)dev; (void)gfp; return zalloc(size); } + +void *kpi_devm_alloc_etherdev(void *dev, int priv, unsigned int tx, unsigned int rx) +{ (void)dev; return kpi_alloc_etherdev_mqs(priv, tx, rx); } + +void * +kpi_dmam_alloc_attrs(void *dev, unsigned long size, unsigned long long *h, + unsigned int gfp, unsigned long attrs) +{ return kpi_dma_alloc_attrs(dev, size, h, gfp, attrs); } + +// --- PCI, the managed and newer calls ---------------------------------------------------- + +int kpi_pcim_enable_device(void *pdev) { return kpi_pci_enable_device(pdev); } + +void * +kpi_pcim_iomap_region(void *pdev, int bar, const char *name) +{ + (void)name; + void *p = kpi_pci_iomap(pdev, bar, 0); + return p ? p : (void *)-12L; // ERR_PTR(-ENOMEM); NULL is not an error +} + +// One vector, and its number is whatever request_irq is then given back -- +// the handler is called from the poll loop either way. See kpi_request_irq. +int kpi_pci_alloc_irq_vectors(void *pdev, unsigned int min, unsigned int max, + unsigned int flags) +{ (void)pdev; (void)min; (void)max; (void)flags; return 1; } + +int kpi_pci_irq_vector(void *pdev, unsigned int nr) { (void)pdev; (void)nr; return 16; } + +int +kpi_pci_read_config_byte(void *pdev, int where, unsigned char *v) +{ + unsigned short w = 0; + int r = kpi_pci_read_config_word(pdev, where & ~1, &w); + if (v) *v = (unsigned char)(w >> ((where & 1) * 8)); + return r; +} + +// /dev/pci has no config write (see pci.c). The ones r8169 makes are tuning, +// and the ASPM change it wants is made by the kernel at boot instead. +int +kpi_pci_write_config_byte(void *pdev, int where, unsigned char v) +{ + (void)pdev; (void)v; + kpi_log(" kpi: config write to 0x"); kpi_loghex((unsigned)where, 2); + kpi_log(" not made\n"); + return 0; +} + +void *kpi_alloc_pages_node(unsigned int gfp, unsigned int order, int nid, void *mask) +{ (void)nid; (void)mask; return kpi_alloc_pages(gfp, order); } + +// --- work queues, the rest of the API ---------------------------------------------------- + +static char kpi_wq_dummy[64]; +void *kpi_system_wq = kpi_wq_dummy; // the *variable* is the symbol + +void *kpi_alloc_workqueue(const char *f, unsigned int flags, int max, ...) +{ (void)f; (void)flags; (void)max; return kpi_wq_dummy; } + +int +kpi_mod_delayed_work_on(int cpu, void *q, void *dwork, unsigned long delay) +{ + kpi_queue_delayed_work_on(cpu, q, dwork, delay); + return 0; +} + +int kpi_disable_work_sync(void *w) { kpi_cancel_work_sync(w); return 0; } + +// --- firmware ------------------------------------------------------------------------------ +// +// From /lib/firmware, which is where Alpine's linux-firmware-* packages put +// it. struct firmware's layout comes from BTF like everything else. + +int +kpi_request_firmware(void **out, const char *name, void *dev) +{ + char path[256]; + (void)dev; + if (out) *out = 0; + if (!kpi_l.fw_ok || !name) + return -2; + kpi_snprintf(path, sizeof(path), "/lib/firmware/%s", name); + int fd = open(path, O_RDONLY); + if (fd < 0) { + kpi_log(" kpi: no firmware "); kpi_log(path); kpi_log("\n"); + return -2; + } + unsigned long cap = 4UL << 20, n = 0; + unsigned char *buf = zalloc(cap); + if (!buf) { close(fd); return -12; } + for (;;) { + int r = read(fd, buf + n, (int)(cap - n > 65536 ? 65536 : cap - n)); + if (r <= 0) break; + n += (unsigned long)r; + if (n == cap) break; + } + close(fd); + void *fw = zalloc(kpi_l.fw_size_of ? kpi_l.fw_size_of : 64); + if (!fw) return -12; + wrp(fw, kpi_l.fw_size, n); + wrp(fw, kpi_l.fw_data, (unsigned long)buf); + if (out) *out = fw; + kpi_log(" kpi: firmware "); kpi_log(path); kpi_log(", "); + kpi_lognum((long long)n); kpi_log(" bytes\n"); + return 0; +} + +// --- small things that must not answer zero ------------------------------------------------ + +int kpi_true(void) { return 1; } +int kpi_enodev(void) { return -19; } +long kpi_ida_alloc(void *ida, unsigned int min, unsigned int max, unsigned int gfp) +{ + static long next; + (void)ida; (void)max; (void)gfp; + return (long)min + next++; +} + +unsigned int +kpi_crc32_le(unsigned int crc, const unsigned char *p, unsigned long len) +{ + while (len--) { + crc ^= *p++; + for (int k = 0; k < 8; k++) + crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1))); + } + return crc; +} + +void +kpi_get_random_bytes(void *buf, unsigned long n) +{ + static unsigned long long x = 0x9E3779B97F4A7C15ULL; + unsigned char *b = buf; + x ^= now_ns(); + for (unsigned long i = 0; i < n; i++) { + x ^= x << 13; x ^= x >> 7; x ^= x << 17; + b[i] = (unsigned char)x; + } +} + +// __skb_pad(skb, pad, free_on_error): zero the bytes after the data, so a +// runt frame goes out padded with zeroes rather than with whatever was there. +int +kpi_skb_pad(void *skb, int pad, int free_on_error) +{ + (void)free_on_error; + unsigned char *head = (unsigned char *)rdp(skb, kpi_l.skb_head); + unsigned int tail, end; + __builtin_memcpy(&tail, (char *)skb + kpi_l.skb_tail, 4); + __builtin_memcpy(&end, (char *)skb + kpi_l.skb_end, 4); + if (pad < 0 || tail + (unsigned)pad > end) + return -12; + for (int i = 0; i < pad; i++) head[tail + i] = 0; + return 0; +} + +// --- data ------------------------------------------------------------------------------------- +// +// Undefined data symbols. A stub would resolve them to machine code, which +// reads as nonsense and, written to, overwrites a trampoline. + +// net/ethtool/common.c's link_mode_params: speed, lanes, duplex per link mode +// bit, which phylib reads at init to build its capability table. A fact table, +// generated from Linux 6.18; struct link_mode_info is int, u8, u8 (8 bytes). +static const struct { int speed; unsigned char lanes, duplex; } kpi_link_mode_params[] = { +#include "link_modes.inc" +}; + +static unsigned char kpi_zero_data[16][4096] __attribute__((aligned(64))); +static unsigned long kpi_cpu_online_mask[16] = { 1 }; // CPU 0 +static int kpi_system_state = 3; // SYSTEM_RUNNING +// A list_head: empty means pointing at itself, not zero. +static void *kpi_leds_list[2] = { kpi_leds_list, kpi_leds_list }; + +// --- calls that must not touch a register --------------------------------------------- +// +// preempt_schedule is reached through a thunk that saves every register, so +// its callers keep live values in caller-saved ones across the call. A +// logging stub makes a syscall, and `syscall` writes RFLAGS into r11: mdio- +// bus kept the PHY register it had just read in r11, and every MDIO read on +// the RTL8168H came back 0x0246 -- the PHY ID read as 0x02460246 and no +// driver matched it. Nothing to schedule here anyway; a bare ret. +__asm__(".text\n.globl kpi_ret\nkpi_ret:\n\tret\n"); +extern void kpi_ret(void); + +// --- the table ------------------------------------------------------------------------------------ + +extern int kpi_queue_delayed_work_on(int cpu, void *q, void *dwork, unsigned long delay); +extern int kpi_cancel_work_sync(void *work); +extern void kpi_kfree_skb(void *skb); + +const struct kpi_sym kpi_base_table[] = { + // printing, formatted + { "_printk", (void *)kpi_printk_fmt }, + { "vprintk", (void *)kpi_vprintk }, + { "_dev_info", (void *)kpi_dev_printk }, + { "_dev_err", (void *)kpi_dev_printk }, + { "_dev_warn", (void *)kpi_dev_printk }, + { "_dev_notice", (void *)kpi_dev_printk }, + { "netdev_info", (void *)kpi_netdev_printk_fmt }, + { "netdev_err", (void *)kpi_netdev_printk_fmt }, + { "netdev_warn", (void *)kpi_netdev_printk_fmt }, + { "netdev_alert", (void *)kpi_netdev_printk_fmt }, + { "netdev_printk", (void *)kpi_netdev_printk_lvl }, + { "dev_err_probe", (void *)kpi_dev_err_probe }, + { "__warn_printk", (void *)kpi_warn_printk }, + { "snprintf", (void *)kpi_snprintf }, + { "sprintf", (void *)kpi_sprintf }, + { "kasprintf", (void *)kpi_kasprintf }, + + // strings + { "strcmp", (void *)kpi_strcmp }, + { "strcpy", (void *)kpi_strcpy }, + { "kstrdup_const", (void *)kpi_kstrdup_const }, + { "kfree_const", (void *)kpi_nop }, + { "kstrtoull", (void *)kpi_kstrtoull }, + + // time + { "ktime_get", (void *)kpi_ktime_get }, + { "__const_udelay", (void *)kpi_const_udelay }, + { "__udelay", (void *)kpi_udelay }, + { "__ndelay", (void *)kpi_ndelay }, + { "msleep", (void *)kpi_msleep }, + { "usleep_range_state", (void *)kpi_usleep_range }, + + // bitmaps + { "__bitmap_and", (void *)kpi_bitmap_and }, + { "__bitmap_andnot", (void *)kpi_bitmap_andnot }, + { "__bitmap_or", (void *)kpi_bitmap_or }, + { "__bitmap_equal", (void *)kpi_bitmap_equal }, + { "__bitmap_intersects", (void *)kpi_bitmap_intersects }, + { "_find_first_bit", (void *)kpi_find_first_bit }, + + // the driver core + { "driver_register", (void *)kpi_driver_register }, + { "driver_unregister", (void *)kpi_driver_unregister }, + { "device_add", (void *)kpi_device_add }, + { "device_register", (void *)kpi_device_add }, + { "device_del", (void *)kpi_device_del }, + { "device_unregister", (void *)kpi_device_del }, + { "device_initialize", (void *)kpi_nop }, + { "device_bind_driver", (void *)kpi_zero }, + { "device_release_driver", (void *)kpi_device_release_driver }, + { "get_device", (void *)kpi_get_device }, + { "put_device", (void *)kpi_nop }, + { "dev_set_name", (void *)kpi_dev_set_name }, + { "dev_driver_string", (void *)kpi_dev_driver_string }, + { "bus_register", (void *)kpi_zero }, + { "class_register", (void *)kpi_zero }, + { "try_module_get", (void *)kpi_true }, + { "module_put", (void *)kpi_nop }, + + // device-managed resources + { "__devres_alloc_node", (void *)kpi_devres_alloc }, + { "devres_add", (void *)kpi_devres_add }, + { "devres_find", (void *)kpi_devres_find }, + { "devres_free", (void *)kpi_devres_free }, + { "devm_kmalloc", (void *)kpi_devm_kmalloc }, + { "devm_alloc_etherdev_mqs", (void *)kpi_devm_alloc_etherdev }, + { "dmam_alloc_attrs", (void *)kpi_dmam_alloc_attrs }, + + // PCI + { "pcim_enable_device", (void *)kpi_pcim_enable_device }, + { "pcim_iomap_region", (void *)kpi_pcim_iomap_region }, + { "pci_alloc_irq_vectors", (void *)kpi_pci_alloc_irq_vectors }, + { "pci_irq_vector", (void *)kpi_pci_irq_vector }, + { "pci_read_config_byte", (void *)kpi_pci_read_config_byte }, + { "pci_write_config_byte", (void *)kpi_pci_write_config_byte }, + { "__alloc_pages_noprof", (void *)kpi_alloc_pages_node }, + { "__free_pages", (void *)kpi_nop }, + + // work + { "mod_delayed_work_on", (void *)kpi_mod_delayed_work_on }, + { "alloc_workqueue_noprof", (void *)kpi_alloc_workqueue }, + { "destroy_workqueue", (void *)kpi_nop }, + { "disable_work_sync", (void *)kpi_disable_work_sync }, + { "disable_work", (void *)kpi_disable_work_sync }, + { "enable_work", (void *)kpi_true }, + { "system_power_efficient_wq", (void *)&kpi_system_wq }, + { "system_percpu_wq", (void *)&kpi_system_wq }, + { "system_wq", (void *)&kpi_system_wq }, + + // firmware + { "firmware_request_nowarn", (void *)kpi_request_firmware }, + { "request_firmware", (void *)kpi_request_firmware }, + { "release_firmware", (void *)kpi_nop }, + + // must preserve every register + { "__SCT__preempt_schedule", (void *)kpi_ret }, + { "__SCT__preempt_schedule_notrace", (void *)kpi_ret }, + { "preempt_schedule_thunk", (void *)kpi_ret }, + { "preempt_schedule_notrace_thunk", (void *)kpi_ret }, + + // must not be zero + { "__list_add_valid_or_report", (void *)kpi_true }, + { "__list_del_entry_valid_or_report", (void *)kpi_true }, + { "mutex_is_locked", (void *)kpi_true }, + { "eth_platform_get_mac_address", (void *)kpi_enodev }, + { "ida_alloc_range", (void *)kpi_ida_alloc }, + { "crc32_le", (void *)kpi_crc32_le }, + { "get_random_bytes", (void *)kpi_get_random_bytes }, + { "__skb_pad", (void *)kpi_skb_pad }, + // r8169 frees every transmitted skb this way; a stub leaked one per frame + // until the DMA heap ran out. The budget argument changes only which cache + // Linux returns it to. + { "napi_consume_skb", (void *)kpi_kfree_skb }, + + // data + { "link_mode_params", (void *)kpi_link_mode_params }, + { "__cpu_online_mask", (void *)kpi_cpu_online_mask }, + { "system_state", (void *)&kpi_system_state }, + { "phylib_stubs", (void *)kpi_zero_data[0] }, + { "phys_base", (void *)kpi_zero_data[7] }, + { "kmalloc_caches", (void *)kpi_zero_data[1] }, + { "random_kmalloc_seed", (void *)kpi_zero_data[2] }, + { "mem_alloc_profiling_key", (void *)kpi_zero_data[3] }, + { "of_fwnode_ops", (void *)kpi_zero_data[4] }, + { "leds_list", (void *)kpi_leds_list }, + { "leds_list_lock", (void *)kpi_zero_data[6] }, +}; + +const int kpi_base_count = (int)(sizeof(kpi_base_table) / sizeof(kpi_base_table[0])); diff --git a/ldls/kpi/kpi.c b/ldls/kpi/kpi.c index a0fd940..58c1680 100644 --- a/ldls/kpi/kpi.c +++ b/ldls/kpi/kpi.c @@ -131,6 +131,9 @@ void * kpi_find(const char *name) { int i; + for (i = 0; i < kpi_base_count; i++) + if (eq(kpi_base_table[i].name, name)) + return kpi_base_table[i].fn; for (i = 0; i < kpi_count; i++) if (eq(kpi_table[i].name, name)) return kpi_table[i].fn; @@ -512,18 +515,16 @@ static const unsigned char kpi_byte_rev_table[256] = { // reads it, re-arms itself against it, and enables the transmitter from // inside it, so a stopped clock is a NIC that never transmits. // -// SchOS's uptime() counts 100 Hz ticks, which is a plausible HZ and is the -// only monotonic source a process has. What matters to a driver is that it -// advances and never goes backwards; the exact rate matters only to timeouts, -// and a timeout measured against a slower clock is a longer timeout. +// It counts at the module's HZ, which is compiled into the module and is 1000 +// on the kernels LDLS loads from -- phylib re-arms its link poll HZ jiffies +// ahead, and at the 100 Hz this used to count that poll ran every ten seconds. +// Milliseconds from CLOCK_MONOTONIC; see kpi_jiffies_ms() in base.c. unsigned long kpi_jiffies; unsigned long kpi_jiffies_now(void) { - int t = uptime(); - if (t > 0) - kpi_jiffies = (unsigned long)t; + kpi_jiffies = kpi_jiffies_ms(); return kpi_jiffies; } @@ -590,7 +591,7 @@ extern unsigned long kpi_strnlen(const char *s, unsigned long max); extern long kpi_sized_strscpy(char *d, const char *s, unsigned long n); extern void kpi_memcpy_fromio(void *d, const void *s, unsigned long n); extern void kpi_memcpy_toio(void *d, const void *s, unsigned long n); -extern void kpi_fortify_panic(void); +extern void kpi_fortify_panic(unsigned int, unsigned long, unsigned long); const struct kpi_sym kpi_table[] = { // PCI diff --git a/ldls/kpi/kpi.h b/ldls/kpi/kpi.h index 31df6cf..3871173 100644 --- a/ldls/kpi/kpi.h +++ b/ldls/kpi/kpi.h @@ -35,6 +35,15 @@ extern const int kpi_count; // driver ever calls it. void *kpi_find(const char *name); +// The second table, in base.c. Searched first, so that a formatted printk +// there replaces the plain one in kpi.c. +extern const struct kpi_sym kpi_base_table[]; +extern const int kpi_base_count; + +void kpi_nop(void); +int kpi_zero(void); +unsigned long kpi_jiffies_ms(void); + // --- what the host gives the KPI --------------------------------------------- // // The KPI cannot open these for itself: `/dev/pci` is one device per process @@ -74,6 +83,7 @@ extern struct kpi_ctx kpi; struct kpi_layout { unsigned int nd_netdev_ops; unsigned int nd_dev_addr, nd_perm_addr, nd_addr_len; + unsigned int nd_priv_len; // 0 when this kernel has none (before 6.17) unsigned int nd_mc, nd_uc; unsigned int nd_tx, nd_num_tx, nd_real_num_tx; unsigned int nq_size, nq_dev; @@ -92,6 +102,17 @@ struct kpi_layout { unsigned int skb_size; unsigned int skb_len, skb_data, skb_head, skb_tail, skb_end, skb_dev; + + // The driver core (ldls/kpi/base.c): device.bus and .driver, + // device_driver.bus, .probe and .name, bus_type.match and .probe. core_ok + // says all of them were found; without them nothing is ever bound. + unsigned int dev_bus, dev_driver, drv_bus, drv_probe, drv_name; + unsigned int bus_match, bus_probe; + int core_ok, dev_kobj_ok; + + // struct firmware: size and data, and how big it is. + unsigned int fw_size, fw_data, fw_size_of; + int fw_ok; }; extern struct kpi_layout kpi_l; @@ -131,6 +152,9 @@ unsigned long kpi_jiffies_now(void); // Output. The KPI is inside a process with no libc, and every implementation // wants to be able to say what it did. void kpi_log(const char *s); +// Says "module+0xoffset" for an address in a loaded module; set by the host. +extern void (*kpi_where)(unsigned long addr); + void kpi_lognum(long long v); void kpi_loghex(unsigned long long v, int digits); diff --git a/ldls/kpi/link_modes.inc b/ldls/kpi/link_modes.inc new file mode 100644 index 0000000..57151e5 --- /dev/null +++ b/ldls/kpi/link_modes.inc @@ -0,0 +1,122 @@ +// 121 entries, generated from Linux 6.18 net/ethtool/common.c + { 10, 1, 0x0 }, + { 10, 1, 0x1 }, + { 100, 1, 0x0 }, + { 100, 1, 0x1 }, + { 1000, 1, 0x0 }, + { 1000, 1, 0x1 }, + { -1, 0, 0xff }, + { -1, 0, 0xff }, + { -1, 0, 0xff }, + { -1, 0, 0xff }, + { -1, 0, 0xff }, + { -1, 0, 0xff }, + { 10000, 1, 0x1 }, + { -1, 0, 0xff }, + { -1, 0, 0xff }, + { 2500, 1, 0x1 }, + { -1, 0, 0xff }, + { 1000, 1, 0x1 }, + { 10000, 4, 0x1 }, + { 10000, 1, 0x1 }, + { 10000, 1, 0x1 }, + { 20000, 2, 0x1 }, + { 20000, 2, 0x1 }, + { 40000, 4, 0x1 }, + { 40000, 4, 0x1 }, + { 40000, 4, 0x1 }, + { 40000, 4, 0x1 }, + { 56000, 4, 0x1 }, + { 56000, 4, 0x1 }, + { 56000, 4, 0x1 }, + { 56000, 4, 0x1 }, + { 25000, 1, 0x1 }, + { 25000, 1, 0x1 }, + { 25000, 1, 0x1 }, + { 50000, 2, 0x1 }, + { 50000, 2, 0x1 }, + { 100000, 4, 0x1 }, + { 100000, 4, 0x1 }, + { 100000, 4, 0x1 }, + { 100000, 4, 0x1 }, + { 50000, 2, 0x1 }, + { 1000, 1, 0x1 }, + { 10000, 1, 0x1 }, + { 10000, 1, 0x1 }, + { 10000, 1, 0x1 }, + { 10000, 1, 0x1 }, + { 10000, 1, 0x1 }, + { 2500, 1, 0x1 }, + { 5000, 1, 0x1 }, + { -1, 0, 0xff }, + { -1, 0, 0xff }, + { -1, 0, 0xff }, + { 50000, 1, 0x1 }, + { 50000, 1, 0x1 }, + { 50000, 1, 0x1 }, + { 50000, 1, 0x1 }, + { 50000, 1, 0x1 }, + { 100000, 2, 0x1 }, + { 100000, 2, 0x1 }, + { 100000, 2, 0x1 }, + { 100000, 2, 0x1 }, + { 100000, 2, 0x1 }, + { 200000, 4, 0x1 }, + { 200000, 4, 0x1 }, + { 200000, 4, 0x1 }, + { 200000, 4, 0x1 }, + { 200000, 4, 0x1 }, + { 100, 1, 0x1 }, + { 1000, 1, 0x1 }, + { 400000, 8, 0x1 }, + { 400000, 8, 0x1 }, + { 400000, 8, 0x1 }, + { 400000, 8, 0x1 }, + { 400000, 8, 0x1 }, + { -1, 0, 0xff }, + { 100000, 1, 0x1 }, + { 100000, 1, 0x1 }, + { 100000, 1, 0x1 }, + { 100000, 1, 0x1 }, + { 100000, 1, 0x1 }, + { 200000, 2, 0x1 }, + { 200000, 2, 0x1 }, + { 200000, 2, 0x1 }, + { 200000, 2, 0x1 }, + { 200000, 2, 0x1 }, + { 400000, 4, 0x1 }, + { 400000, 4, 0x1 }, + { 400000, 4, 0x1 }, + { 400000, 4, 0x1 }, + { 400000, 4, 0x1 }, + { 100, 1, 0x0 }, + { 100, 1, 0x1 }, + { 10, 1, 0x1 }, + { 800000, 8, 0x1 }, + { 800000, 8, 0x1 }, + { 800000, 8, 0x1 }, + { 800000, 8, 0x1 }, + { 800000, 8, 0x1 }, + { 800000, 8, 0x1 }, + { 10, 1, 0x1 }, + { 10, 1, 0x0 }, + { 10, 1, 0x0 }, + { 10, 1, 0x1 }, + { 200000, 1, 0x1 }, + { 200000, 1, 0x1 }, + { 200000, 1, 0x1 }, + { 200000, 1, 0x1 }, + { 200000, 1, 0x1 }, + { 200000, 1, 0x1 }, + { 400000, 2, 0x1 }, + { 400000, 2, 0x1 }, + { 400000, 2, 0x1 }, + { 400000, 2, 0x1 }, + { 400000, 2, 0x1 }, + { 400000, 2, 0x1 }, + { 800000, 4, 0x1 }, + { 800000, 4, 0x1 }, + { 800000, 4, 0x1 }, + { 800000, 4, 0x1 }, + { 800000, 4, 0x1 }, + { 800000, 4, 0x1 }, diff --git a/ldls/kpi/net.c b/ldls/kpi/net.c index e702a0d..7418f61 100644 --- a/ldls/kpi/net.c +++ b/ldls/kpi/net.c @@ -79,6 +79,15 @@ kpi_alloc_etherdev_mqs(int priv, unsigned int txqs, unsigned int rxqs) kpi_log("kpi: no net_device.dev_addr offset -- the driver will fault\n"); } + // How big the private area is, which a driver's own bounds checks read: + // netdev_priv() is `dev->priv`, a flexible array counted by `priv_len`, so + // fortify sees the area as exactly that long. Zero, as left by mmap, made + // r8169's memset of its descriptor-ring bookkeeping -- tp->tx_skb, 4K at + // offset 0xa48 -- look like writing past the end of nothing, and it called + // __fortify_panic in rtl_open. + if (kpi_l.nd_priv_len) + *(unsigned int *)((unsigned char *)p + kpi_l.nd_priv_len) = (unsigned int)priv; + // The address lists have to be *empty*, not zeroed. // // A Linux list_head is empty when it points at itself; zeroed means a null @@ -220,8 +229,12 @@ kpi_register_netdev(void *dev) c.pad[0] = c.pad[1] = 0; c.name[0] = 'l'; c.name[1] = 'd'; c.name[2] = 'l'; c.name[3] = 's'; - if (ioctl(kpi.netfd, NETUSERIOC_CREATE, &c) < 0) { - kpi_log("kpi: /dev/netdev is already somebody else's\n"); + int r = ioctl(kpi.netfd, NETUSERIOC_CREATE, &c); + if (r < 0) { + // -16 EBUSY is another process; anything else is the kernel refusing. + kpi_log(r == -16 ? "kpi: /dev/netdev is already somebody else's\n" + : "kpi: the kernel would not take the interface, error "); + if (r != -16) { kpi_lognum(r); kpi_log("\n"); } close(kpi.netfd); kpi.netfd = -1; return -1; diff --git a/ldls/kpi/string.c b/ldls/kpi/string.c index 09a81b9..5e1c8ff 100644 --- a/ldls/kpi/string.c +++ b/ldls/kpi/string.c @@ -14,6 +14,7 @@ // have been easier to find. #include "core/types.h" +#include "uapi/user.h" #include "kpi.h" void * @@ -135,11 +136,31 @@ kpi_memcpy_toio(void *dst, const void *s, unsigned long n) // __fortify_panic is what a driver's bounds-checked string helpers call when // they detect an overflow. Reaching it means the driver found a bug in itself, // and the honest thing is to say so rather than return. +void (*kpi_where)(unsigned long addr); + void -kpi_fortify_panic(void) +kpi_fortify_panic(unsigned int reason, unsigned long avail, unsigned long size) { kpi_log("kpi: the driver called __fortify_panic -- it detected an " "overflow in itself\n"); + // Which check, how much it thought it had, how much it wanted, and where + // from -- the address is in the driver's own text, so subtract the load + // address logged for it and look the offset up with nm. This took a + // hardware round trip to find the last time it said nothing. + kpi_log(" reason "); kpi_lognum(reason); + kpi_log(", available "); kpi_lognum((long long)avail); + kpi_log(", wanted "); kpi_lognum((long long)size); + kpi_log(", from "); + if (kpi_where) + kpi_where((unsigned long)__builtin_return_address(0)); + else { + kpi_log("0x"); kpi_loghex((unsigned long)__builtin_return_address(0), 16); + } + kpi_log("\n"); + // Not spin: a driver that has found a bug in itself holds /dev/netdev and + // one core at 100% until reboot. The job is KeepAlive = no, so leaving is + // free, and the kernel takes the interface back. + exit_group(1); for (;;) ; } diff --git a/ldls/stub.c b/ldls/stub.c index 8d22310..35fb518 100644 --- a/ldls/stub.c +++ b/ldls/stub.c @@ -85,6 +85,12 @@ stub_is_bare(const char *name) void stub_build(struct stubs *s, unsigned char *code, int n) +{ + stub_build_from(s, code, n, 0); +} + +void +stub_build_from(struct stubs *s, unsigned char *code, int n, int first) { int i; @@ -107,7 +113,7 @@ stub_build(struct stubs *s, unsigned char *code, int n) // movl $i, %r11d -- 41 bb imm32. The upper half of %r11 is zeroed by the // 32-bit form, which is what we want for a small index. p[4] = 0x41; p[5] = 0xBB; - put32(p + 6, (unsigned int)i); + put32(p + 6, (unsigned int)(first + i)); // movabsq $ldls_thunk, %r10 -- 49 ba imm64 p[10] = 0x49; p[11] = 0xBA; diff --git a/ldls/stub.h b/ldls/stub.h index 0b662a4..af0f0ee 100644 --- a/ldls/stub.h +++ b/ldls/stub.h @@ -35,6 +35,9 @@ struct stubs { // // `code` has to be executable memory; this only writes the bytes. void stub_build(struct stubs *s, unsigned char *code, int n); +// The same, numbering the stubs from `first`: with several modules loaded, +// each module's stubs follow the previous module's in one shared name table. +void stub_build_from(struct stubs *s, unsigned char *code, int n, int first); // The address of stub `i`, to hand to the loader as a symbol value. unsigned long long stub_addr(const struct stubs *s, int i); diff --git a/ports/alpine/world b/ports/alpine/world index bb9a788..ac0ebd2 100644 --- a/ports/alpine/world +++ b/ports/alpine/world @@ -26,3 +26,58 @@ xinit windowmaker xterm font-dejavu +# Stress: terminal programs that draw, multiplex and poll. htop and btop read +# /proc hard; tmux wants ptys, sockets and signals; mc does everything at once. +htop +btop +tmux +mc +ncdu +fastfetch +# Games. In the terminal: nethack, the BSD set, and two toys. On X: +# lbreakouthd (SDL 2) and the classic X clients. Not lbreakout2: its stk.c +# hands vsnprintf a size of 511 + the prefix length, and Alpine's fortified +# headers trap on that before it loads its first image -- on Alpine too. +nethack +bsd-games +cmatrix +sl +lbreakouthd +xeyes +xclock +xcalc +# A browser: NetSurf, on GTK 3 -- glib's threads, cairo, pango, TLS. The step +# before WebKit (epiphany), which is `apk add` away. +netsurf +# curl's command line; libcurl is already here for NetSurf. +curl +# Sound for everything that speaks ALSA, SDL included: alsa-lib with the OSS +# plugin, pointed at /dev/dsp by schos-base's /etc/asound.conf. SchOS has OSS, +# not ALSA's kernel side, and SDL 3 has no OSS backend of its own. +alsa-lib +alsa-plugins +# udev: the daemon that names devices and tells Xorg (and libinput, and every +# Wayland compositor) what is plugged in. eudev is udev without systemd. +# `udevadm`, the rules, and libudev come with it. +eudev +eudev-libs +udev-init-scripts + +# Privilege for ordinary users: setuid doas (opendoas), configured in /etc/doas.d. +doas + +# Input and seats for Wayland: libinput turns the evdev nodes udev tagged into +# pointer, keyboard and touch events; libseat hands a compositor its devices +# without making it root, through seatd's socket (or directly when it is root). +# xf86-input-libinput is the same library for Xorg. +libinput +libinput-libs +libinput-udev +libseat +seatd +seatd-launch +# lsusb that reads /sys/bus/usb/devices, which busybox's does not. +usbutils + +# usb.ids, which lsusb reads to name devices. +hwdata-usb diff --git a/ports/alpine/world.lock b/ports/alpine/world.lock index fd28a61..29652d3 100644 --- a/ports/alpine/world.lock +++ b/ports/alpine/world.lock @@ -1,61 +1,138 @@ # Generated by tools/posix/alpine-lock.sh from world. Do not edit; # change world and run `make alpine-lock`. # file sha256 repository +alsa-lib-1.2.15.3-r0.apk 052de5639df635a03cfae1d7504ed4ed75c4e2d2cfff59efc116bb847c1f4ac4 main +alsa-plugins-1.2.12-r2.apk 0ac6513add2a5268f67231b241473b4b7b43f5aad9f4674c900525e5152e0752 community +at-spi2-core-2.60.5-r0.apk c9989ba9b2ff44f87a2106ca9560c2e554a56811d0fc2863b22741d576f1d84f main +at-spi2-core-libs-2.60.5-r0.apk bcd4846311c8b19ad7e08ffc3cf726ddbe3d36bbc524386bda64758f5c5981a6 main +avahi-libs-0.9_rc4-r0.apk 380d59c58cfce9cb5cf342cf73947749ca77a880f729970e2f508aaa6fc2f713 main brotli-libs-1.2.1-r0.apk 62a7e37e43bbfc1f8525a00ad2125fd5897135dc93b849733884cb78ffd8e26c main +bsd-games-3.3-r2.apk 0297419e0bb67817708c9150c74320a57c647020be9297237e6410df0de17a36 community +btop-1.4.6-r0.apk 82369611eaa6a1bd2e3738f2e5990429526060ad5319f729b4bc4acaddf296f3 community +bubblewrap-0.12.0-r0.apk 7755592c3f7c6de79a5c3c13554ada6302dbd98e520fe6dd8064f87ba77092e5 main +bubblewrap-zsh-completion-0.12.0-r0.apk 0a851aeb4cf8e9048d3863e3b43b3ace9295a57b583141646f475e04bf0ec3a9 main busybox-1.37.0-r31.apk 0e626fa97cf937fda7bba4ed4dde8e2f2dbb5083ac34e02180cec0c4eba9351b main busybox-binsh-1.37.0-r31.apk 93f803ae5db6b729c0f66f4afba6e907ccc17d7d5f8b8c3d25bdb4636de2c48a main +ca-certificates-bundle-20261020-r0.apk bb008f6d214b6af7576324659856fb63dc8dcb4373f8345ff706388696f1254d main +cairo-1.18.4-r1.apk 23364d77506b5f60eae26daa0a5489cdee8bc100fcd010cd73774dfa5a7f9305 main +cairo-gobject-1.18.4-r1.apk ec302c75a24edaa4a2f867cfb5eec85cf30efe184044bab6e2b9678840e2bc2a main +c-ares-1.34.9-r0.apk 3539a3e11c1ef1516117d8179a546ac882086e94e0d78ad51d687b3fa2b3b5f2 main +cmatrix-2.0-r2.apk d9f6bea3851492a6db2a9cd3f7e2df0f0756c6651a702b36ed2b887328ad1f00 community +cups-libs-2.4.19-r1.apk 5739c568d90f23006d74ff82619e1a18447a6f698ae2af61970debe6658a7bea main +curl-8.23.0-r0.apk 531ae298451a4c1c44f563fc3b2eedc2c1d7e0a26a33bdfc0efe6faaff6a5942 main +curl-zsh-completion-8.23.0-r0.apk 123ff531444fe6c3095c49553f8798bcf6f6ca8c7668b4140cc33daf24c351ec main +dbus-libs-1.16.2-r2.apk 105f6ba17454c587ca9345f9deb348012d1950588a22da42120c954d396c99b6 main +doas-6.8.2-r8.apk 1b0198d957fee06b484fc0e23783f03213cc97d8003c5f46b8d940d8ca9f57c8 main +e2fsprogs-libs-1.47.4-r0.apk 2e067395a6981b06cfd2cd3abf0e6c6574f7ef64d02e01f75fc5323d4a337fb9 main encodings-1.1.0-r0.apk f7c0eb762638d0249b4fbbe07a8e4ed6aab8e86b74c239c40e47a36219777d52 main +eudev-3.2.14-r6.apk 540c78af1eed3bc411cae4e9171e911afc0704210c0b97315e978e05c3b39dce main eudev-libs-3.2.14-r6.apk 45fdbcf37c5c0e82067e98a818d3c1fd16d0261c62128dcb6b935b86b4ec6c86 main +fastfetch-2.64.2-r0.apk 9c593d689f1c04625d3b433066ad92e430a384fa8e99585fee71310a20a965cb community +fastfetch-zsh-completion-2.64.2-r0.apk ded7a5c7911c4db60ab71a320104f9af3f629976429c897db67ea87c5ac53ec8 community font-alias-1.0.6-r0.apk 754e5c1d19c2c69d5a2c3c02c7340cad7df3c26e63789516372ea164b564b927 main fontconfig-2.17.1-r1.apk 7634cf2fb1066ec97c8f41ec90ec9f036540e056276b7bf9a10dc4ec9e44d238 main font-cursor-misc-1.0.4-r1.apk a1a0c0659a9a48e24b4cc4b35583f4e57da9d2a0ae2cc8adb9be1143b66911b4 main font-dejavu-2.37-r6.apk 6e7609e7a1aaed947afe4ddcc02bbdeec571dac5ff4ea1c04af9b8480b7b9dcf main font-misc-misc-1.1.3-r1.apk 2c48f2a8529ac63243a0bdd5e7ab2329e0a8b8f996ffe95faed8b79fb05b4a52 main freetype-2.14.3-r0.apk 09d76fde675a9ca3cfeb83d72fb09cb2e9ccf79b2937daa05a1af9a37a6b7815 main +fribidi-1.0.16-r3.apk be0cba50bc7bfdaadfba020fe8239ba3f25f35d309806a9e89e9e09860f985c0 main +gdk-pixbuf-2.44.7-r1.apk 71c422b35822af144fbc14baf7308fa6bd59c490b19842de93aef4ed46c88422 community +glib-2.88.1-r1.apk 2f32203b97789bc9a92e57e2d9b89eb2e6951b69534ba082a683c6e49031cb5d main +glycin-image-rs-2.1.5-r0.apk 9c9d639e1484e244316acfd8b9815ec90773f695b90f99451e914c66ddaf4726 community +glycin-svg-2.1.5-r0.apk 59cf8cb8816249019f9498ef3acf20073f5443d2e89d4d3baf7878c0a454137a community gmp-6.3.0-r4.apk 85323ef5bce8ff234736dc2d6592ea37aebdc6348a531e27430bdfe38364d256 main +gnutls-3.8.13-r0.apk 8d3f3612145db8cd7feb83607187a60f809f06cfa3c2e74d310d58a6b053d54c main +gobject-introspection-1.86.0-r1.apk ad831499410b235a1072dde40705f4ea5303be9ad47ce1e97dcb1b9658c6eee9 main +gpm-libs-1.20.7-r6.apk 4893672b19ff5b14e7a40e945105bca1c2ff2bcedcafd1f9298d4bbb46dbf58f main +graphite2-1.3.14-r6.apk 2caa36d901f79215753ce3b614818ca71596baf2bbb2c21b6eadcf073d81b552 main +gtk+3.0-3.24.52-r0.apk c7c0c0faffa3061bc8fca9540b250d024bdba1aa6ae1fb06f5185adefceaa070 community +harfbuzz-13.2.1-r0.apk 01f7862265ef2256bbadd0fc998df3d1243dee606df3bd8284471cf390f86f29 main +htop-3.5.3-r0.apk 6f6d444ca9a4133c2c13323ded04128783d59f924bcff0276d150a982904a2f3 main hwdata-pci-0.408-r0.apk fe61404e6d2bd1e04c021d53641c0ebce97911084d77994e43a288a1278686c1 main hwdata-usb-0.408-r0.apk 272d365cb05dd085dd8022154cf71e3f3b0aaddd41c1269eeaf939c9d38675c8 main +kmod-libs-34.2-r1.apk d6504eb204a47a35e1dd5eaa1018301b2dc0b1809f84d88fab2fdbadfc9c3c73 main +lbreakouthd-1.2.2-r0.apk 4d7f78d866a7c544132e5f159d18f978515ac948517f2b8a6c625756bb81a3e0 community +lbreakouthd-data-1.2.2-r0.apk 28ce98df27bfbb442fa397fbb9eb3eb5464e5b7b040ea034f207be52f4f4f07f community +lcms2-2.19-r0.apk b4cb038af3c2f02bedd8456ae876c97ac0a9f29789add77871bfd6ce084ff136 main +libatk-1.0-2.60.5-r0.apk ab5f603b17f0f0c5577d259b8263c5cbc6c7b3fd031f06ddc1e3e26417e1609a main +libatk-bridge-2.0-2.60.5-r0.apk f5e38bc54bae780680134f7b96ce8e01ef24e98ae0ac67ded7229af07aa0b0f1 main +libblkid-2.42.3-r1.apk 2512c2bff496ac2b8561a3c814d21d35ce10cda579b3d410f9f96da93a5aaac1 main libbsd-0.12.2-r0.apk 407597c04b41dbfe8a05b35d45a3453e32fae7df84aed3a06ff48dd4df1338b6 main libbz2-1.0.8-r6.apk 2737e2b23ed08d4911ee62cd0163b5f4bf0379cacf4dc35b23a00209584c98fd main libcap2-2.78-r0.apk 42886a7b4bcde26347c712925c8dc7db202b6029ef9b8c7ee81577ab7bdd8fad main +libcom_err-1.47.4-r0.apk 4723d0fb8e286dc6adb54dd3cdaedcd8db54260f75ee778a5acb4c16922c0f52 main +libcrypto3-3.5.9-r0.apk 6632d758d8f5e9ea3b650fe966f23bbf9a202f8b8dceecac93da135dec5e3689 main +libcurl-8.23.0-r0.apk 1f70ca404818805a6465b615bbfe84ef861488a963d7e630108ebd7fbaa51665 main +libdav1d-1.5.3-r0.apk 40a2b1b92cdcc6fd295934f799ee80ef67e2e384097624d565c6222d827f8480 main libdrm-2.4.134-r0.apk 584fd84ad5030040e613fa1816097cd1706ddd542100070852b0eab6e09e12ca main +libeconf-0.8.3-r0.apk 2a791c7957961f830cf31b04f35f814a63afcf633badc9052425ff1cf2f64380 main libelf-0.195-r0.apk 78c5a1993342ab1bdcdce30639fd0937e8000f6d0d85cea171f573d31decd036 main +libelogind-255.24-r0.apk 5f9d90571f5f097cb1067b73e0ae3b58778b974c67b574b257ecbad09f8c32ef community libepoxy-1.5.10-r1.apk 0be13b4b524fe64315e0663ab615a244d75d7da73130ac1c630cd6096841444e main libevdev-1.13.6-r0.apk b38a27a4ac55701fd2eb0032977af2664b6fc23b2f9c2cc6e893ad4452fd82d5 community +libevent-2.1.13-r0.apk 870d4b22a2e4068205b67bb8e4631342c9e84060d052fb36cf4721e18d4043a2 main libexpat-2.9.0-r0.apk 32b127f31e0a919110a18a7f0bb3780d8fb66ba4d8d61b2d301d633f0e319d0e main libffi-3.5.2-r1.apk 6da1a19313a40c56cac756fb11e2c4d5267831e1c22fc8323f24c943c94da6e0 main libfontenc-1.1.9-r0.apk ea7f6d097653572991da749ac0764c2568d8902a01eea4236e56b10b90a7b0e3 main libgcc-15.2.0-r5.apk 393dcd32629f06d7d85409c272d142d0c082772d10b87ef55ee82f47de3be637 main +libglycin-2.1.5-r0.apk 3541d1a5d4d2599d145904f4febf26e56942403b6d80834461e196636b35b280 community libice-1.1.2-r0.apk 08daa589d57149b52b00c666b4009212aa3af95021ebdb2a359555c9bcaa76e1 main +libidn2-2.3.8-r0.apk 7d038ac558dde464964b033ce5126dd2eaf84cc414dc15e77c5424a321d0ebee main +libinput-1.31.3-r0.apk fb4fb12c9cea28a3d975ea7f2c7b714cfcabedb4aef6bbf3ff687c08bfc380e4 community +libinput-libs-1.31.3-r0.apk f3b764d72c1913a8c9d781206fd365ecdfe088e0b09009831e7804627f5bd18d community +libinput-udev-1.31.3-r0.apk 716bc1d1ba68ca0c4d41de8923cf88d94946796ed85961a585ae778561ccf06b community +libinput-zsh-completion-1.31.3-r0.apk 2d85a61b395d3a88cbab6cdb433cc9202faaf636267ac29892057cfd3238a45b community +libintl-1.0-r0.apk cb03919cba5624b5e8759d04f3b31913b02585355988b914bc31de4746ff5ef6 main libjpeg-turbo-3.1.3-r0.apk 6299627dd0a9156a7463511e4915a6294dfb592d8b4bdf85e55265adf1b8ea04 main libmd-1.2.0-r0.apk f6d68d072fcefc8edd4ffe3afc341ca391f8a9a209130628fb065d98b18a2792 main +libmount-2.42.3-r1.apk e2ae003260f2c1ec13666c6e108d5e8e3d64974b0667166a02432bd3be263a7a main libncursesw-6.6_p20260516-r0.apk cf8caa8a88bc4ce9d9e395567fefaf9ef7fbd55c50ae6155b35c1b58f3755023 main libpciaccess-0.19-r0.apk 03fdccd1e2a0825e7f7fabbac67212e05d271d31d2eff44cc92f707e69908ad4 main libpng-1.6.59-r0.apk d16aeb0597f2bc43e0583ac9ff779388ec160c3e22568b640122e94ba55be6d7 main +libpsl-0.21.5-r3.apk ec665a344b6e887f00f0a47015cc732c3a281b69e84641dc8662ea234fde6cdb main +librsvg-2.62.3-r0.apk e2aeb4a2b6b1ab20627dbb0e9136ea90f7120ec61412ed52f4335ee231361fde community +libsamplerate-0.2.2-r3.apk d47765972e0f4b7c5585a5b27957ffe9bc63d4cc9729134fd876ee5d3f5f986d main +libseat-0.9.3-r0.apk 967ddb2db477b2a30c17a85135db2cb3fcb3f355fa10acfa1c5d6e9c74737fc6 community +libseccomp-2.6.1-r0.apk b22ccfe8cb072e4c8d3bc17222eeda132e270ce6258c27622acb193bf27b48e1 main libsharpyuv-1.6.0-r0.apk 34621ff53f43030d24d1224fe9cb53b1590ab614cbce6080005cde703e6c874a main libsm-1.2.6-r0.apk dd7eff58a46fd60e4afee9c7c1c3508920b0e1d6560129e59aee82329f68afc8 main +libssh2-1.11.1-r4.apk 13f707f411380e1e195e590ba80bce4c2406ffed6b28ef03f75043b45db156d5 main +libssl3-3.5.9-r0.apk 05e3393fb95aa5751ca2f9d242f659f6cff82c1cc7767cc2df4a086f7ad01877 main libstdc++-15.2.0-r5.apk 14c987b556f5385a5db18376e788c75f37d85321b8dc1920d926ea7daac1d6f6 main +libtasn1-4.21.0-r0.apk 407f255c5af989f4e22a5e9404c4558f6fda04f14a4209a2ca1e06c3f2ba476e main +libunistring-1.4.2-r0.apk 3aa6030e603e34d96192742f327c0509f409c6769ce2e7b1463685dede38d714 main +libusb-1.0.30-r0.apk dba6ff9dfcb06f9e9405414c10dc31dd45f73b853528127e6c91dfbe053ac212 main libuuid-2.42.3-r1.apk 8306e5bb577696c9069fe1dfd9e1dcc39d2d481c6a1b0e707fd03c3e21aa6aa2 main libwebp-1.6.0-r0.apk dc50c0280348330bb113079331c4a35d85e28351c7baa9268ee346aad8398288 main libx11-1.8.14-r0.apk 016efc189c6d566061ffe206ed0780e32686c1a8c832a3917d35d2c66d61a40f main libxau-1.0.12-r0.apk 3506cdba7ed916a6f814a010fbb127dbfdae4147107f64a15f5fa2651c5d8510 main libxaw-1.0.16-r1.apk 8ac469cddeac40ff2e91c84cb02ba526d7c62e30a79a25c2e3b6e5269236801d main libxcb-1.17.0-r2.apk 7935d5ad9c7137b7b3f1292d62f55f7e0cc5c919155218c3060ea46503b3f211 main +libxcomposite-0.4.7-r0.apk 90d6c22a5a730c7f884c992a8ca4c41102a71918aff9d804313356c721f2b50e main +libxcursor-1.2.3-r0.apk 5be10886e7361c8a9a6ae68b83ea664a6bd6a021043a9203fe6c8b878e99c0cd main libxcvt-0.1.3-r0.apk 809b454981887c1d1d835c909c46a80650ca72a73a92268a2cf8a666ad82b6b7 community +libxdamage-1.1.7-r0.apk d3fbef1ed735be3385531682c40a593d78467ff1713ad2c4a57daecb755b1bf7 main libxdmcp-1.1.5-r1.apk 2a93f44f3f85c1eda9ed53691c35d0742b5fcd8e0057cd7380934025936cbf6f main libxext-1.3.7-r0.apk 8afe1ee6ab8cb656e12f20dc5b7fb2d4103faee4a38298b94f26ab52e597cbc8 main +libxfixes-6.0.2-r0.apk cb00578d1b83abe8baf291f311f194d4deca0f9cf525f47ab10e3bd7c0e54e24 main libxfont2-2.0.8-r0.apk f8bf87b9ce835cb307eb8a3d270e226c4437757478c52ba3ad384cecc2ac6fef community libxft-2.3.9-r0.apk eba6fd85599b4fedf3acdd3ad3e2eec84ea97a5ba8f1ac570fa1ee37a7d81420 main +libxi-1.8.4-r0.apk e41ff864833603ab1065ac37680b47838436f78d4d9510604723341fcb933723 main libxinerama-1.1.6-r0.apk 9f780efb3cf675b5253b73b84f278b40757fa7869c6fe70a9790a73f4f7e810f main +libxkbcommon-1.13.1-r0.apk f52efd26a9069b1c3edc89cab399f7f81bdc47c9e88a54e017c470bd9734b003 main libxkbfile-1.2.0-r0.apk e575c6c314ad87e74887526aca3b1429f5c1d6290e66f2945d4053f387beea57 main libxml2-2.13.9-r2.apk 4b2f986159c659f014b942fe8a5d70c67af475153ca826c3dd53389eea46a300 main libxmu-1.3.1-r0.apk 5d0b06d1a4b38ac0e070e0dad5e61583e5e147e378ba50418c83e1be4b19986c main libxpm-3.5.20-r0.apk 1ed312a00abac5a5840c1f3721921ea8183e664566c19763ed00aa9f35bb2178 main +libxrandr-1.5.5-r0.apk c64856b77001cdf31a32841729b1950ad45e6981c16d09f9fdf08824ab1c0271 main libxrender-0.9.13-r0.apk 724aa6c6353e70869e0eb5aa5461e8fc0753b99db765de47a3ae238573dbdcf8 main libxshmfence-1.3.3-r0.apk 53790552de7eea17a47aa4c082b06b735c545a0947c41128f97c7be95a7d3d92 main libxt-1.3.1-r0.apk 08b167b05ac7dfb892421876cc2467214208b83b2960a9f49e84f9dbcf36e812 main +libxtst-1.2.6-r0.apk e6426212f638e23091a22e1a792684d0e105c5d7a020f3ba30c43b7d2f4e955b main libxxf86vm-1.1.7-r0.apk 887a3c5438e1db624d07ac23d16ec08d21fb16de438d75ad2dd5637c958b4108 main llvm22-libs-22.1.3-r1.apk dec15bdd29b6ec7fc22aee43bac153a22e6b132c599e5e5d9ccd0d159eae764f main +lsof-4.99.6-r0.apk 14e8ea4fd6346de5d2fb899d5287ccc839d8e6f4e6f47d1677c533a1eeef69e8 main +mc-4.8.33-r3.apk ccb74b5f22d00589c369df30f7bf786d5dee69c040ac2411a57eb3c1af4a57fb main mcookie-2.42.3-r1.apk 64f86340991e2f283ce95b1bae21157da6849118cd9f1a2931de5c73d0f9fc3d main mesa-26.1.6-r0.apk 8fb0e3d386f09df4f3e3643108b38197558465b2558e6d66d6a9c055adc8d1a9 main mesa-egl-26.1.6-r0.apk ccdd25c8f3a069b018a39a5641e4f13ab7ceb511d5991e9f8fb18bd551172ffa main @@ -65,16 +142,44 @@ mesa-gles-26.1.6-r0.apk 2544a75e74778ab9ddcecfc8d9dc35375cb3a4ac3661e94a1156e2c5 mkfontscale-1.2.4-r0.apk 2385a7aad3971fcad9d862cb90937e643e633b14bf4b91f8b8ce3040a1468d20 main mtdev-1.1.7-r0.apk 004f42bc67bc0eb573d64d802c5f0325f49013cf4aa7576d4b571399df46f3f7 community musl-1.2.6-r3.apk e142255d0fa397f14318996bc10378093dcc069dc3dd1b6818c8f28a789c21da main +ncdu-1.22-r2.apk 433ef46212f18b1d009080ace0a64a75d04610f60f2c38922f2a03fafffc75a0 main ncurses-terminfo-base-6.6_p20260516-r0.apk ce83be6d0bd10584a53c3a5868cc1595ce5f0f8e0b621f8f7f70b6144e521fc9 main +nethack-3.6.7-r2.apk 6ebc4642fe66037d3f0d34c07ae5bbfd5c138644e682cf3db8877f965980a3b2 community +netsurf-3.11-r1.apk 4ea4200bb12faec841295d9e432be27c90a2c2b454ca8aa85e58507751d4c767 community nettle-3.10.2-r0.apk 693c1a99b781cd17149916df30113ccd6bdddac130298c17b1286fd2ae438386 main +nghttp2-libs-1.70.0-r0.apk f0c4014eb2f10308dcdfd84cd49c121673c7b78cb8182b6e7a1388d8ae112a3b main +p11-kit-0.26.2-r0.apk 134d653aeeb4ada32746fbc3e2a7ae4201f584725de9263e4f32503658f3ddd8 main +p11-kit-zsh-completion-0.26.2-r0.apk e7393c59f19ebeda5080712b203d3e3049cdd15a8c35be2bf9d5cf7dad93082d main +pango-1.57.1-r0.apk c616b62df908026ef413b338b9043e36706b05b6d035049394e5d14022e64adc main +pcre2-10.49-r0.apk 2919897914820f42a17a8d7d37df310d2dfad1c82ebe5f539cc5c765f916752a main pixman-0.46.4-r0.apk 378550cbe348dda9b0733014375dba87a89f0077c5f3dbeba7dce8dbcdda707e main pkgconf-2.5.1-r0.apk 20cc47b76b82e9424a35ecc4fdd7bd904ef49ada1684214910fae31bc0cab737 main +sdl2-compat-2.32.68-r0.apk 0c4c1279446cba3c2065a3d3c2cacfa9eb4aeff507757c5a37193aaf028c5cf7 community +sdl2_image-2.8.12-r0.apk 9d9f337de7fe25c363700701b8dd76ff4026de9c3d977e321ada08058b57eeda community +sdl2_mixer-2.8.2-r0.apk 504b68ae9cf6705942853bb6977d3b341615c72066031e5aff54d0c6aca76964 community +sdl2_ttf-2.24.0-r0.apk 0efcd7f03a1c70fc87706b0bb1e95b8747a1b796f089af56f296bf764ac8d988 community +sdl3-3.4.10-r0.apk e9e4537d455f8b710a1f6fed7607c3cdb492fdc155684cf38e1b054bba809427 community +seatd-0.9.3-r0.apk 11216c03221ae8719a7cbff17cda2d6be1f8fef10752556382d14482cf5f15ea community +seatd-launch-0.9.3-r0.apk b403f9a93eec026430bb477aaa7b3dc37bf445b4695b5c415dd28c6333729b67 community +shared-mime-info-2.4-r7.apk b0ed78b502cacfa2dfb6ff87aaef36a034258026873ab5b73f5e603d6623a147 main +sl-5.02-r1.apk aa3ceb845953904ab61459f0f97416903c56a80b428f8bc320d879b9e58c1a57 community +slang-2.3.3-r3.apk 96daf8e02cbcc0d4fd7163d087ae2a71a1aea279a0ac8d2a00a1612ccdccf644 main spirv-tools-1.4.341.0-r0.apk 6034c22c4f61fb76e517c1d5b11ef5c26c7154d2baeacec8b0a9dcca94ff9a93 main +ssl_client-1.37.0-r31.apk e578a140670e4cf9816117b0c2063632bf3754ecbe8fa593a7fd2b4039372bf0 main tiff-4.7.2-r0.apk 757ce87ebe4923a6be9869d026e250958ca09696529c464781e8a91d2b81670b main +tmux-3.7c-r0.apk 16f3438f5a24124dd500f70bf6f2a232c9b3dfe5e7f7e615969ac5c00c2c8d0b main +tmux-zsh-completion-5.9-r7.apk e505b2a8c0d56beb65b6f47fd732f6ee6d8d8a72a1a1ea27a6922dad48d9ce89 main +udev-init-scripts-35-r1.apk b707bc50e5c8d2faec08f90e7603b76c2c42dfb0e90e713f82be0f98411d8161 main +usbutils-019-r0.apk 423b81ce888c1a988e5d4b6f114e9fb1dfe3125faf513a4982a8a1b4f7222228 main util-macros-1.20.2-r0.apk 7c129d085b646b13ccef3738e93038221aec840c1a6764279c5adc9950ba1bdc main wayland-libs-client-1.25.0-r0.apk a728f7103217cafdc83ff6ad0d679c197dbbe280bd5788c8336259f7a39cb303 main +wayland-libs-cursor-1.25.0-r0.apk 16ec369226507abaeb84b496ce2bb1a4ef29987ea91ba6bedc40bb0e0af274e7 main +wayland-libs-egl-1.25.0-r0.apk c944a59c91ce89972b4cf0ecbb46dfcd43f95c4b86ee63b5522b9a843a3d6af9 main windowmaker-0.96.0-r0.apk 15b500d9afccf94a385e042b01ed9b1d709b1627d97597cea59bf63d0bab4f6b community xauth-1.1.5-r0.apk 94e80e9687a1829a6fe95fb28d4b08a5531dab182cde8a58ff1b370b2cd293fb community +xcalc-1.1.3-r0.apk 2f0d9ab5674a5f82f4adfb6dd5f0a9c944421feea2d6aaf18998b0326fe4c236 community +xclock-1.1.1-r0.apk ae2a3cb515664cf34dd9cd0f81945cad75d0b6bbf44696830f29f1ab4713b475 community +xeyes-1.3.1-r0.apk 4b31dabe8d7f5000d82a24445972859bf960c1b07bca3d4f87e80b3c710902c6 community xf86-input-evdev-2.11.0-r0.apk 1f3fcd3e837b43ad5e32005e03095b547ddbfc25973f0511e9ff74bf342d2fe0 community xf86-video-fbdev-0.5.0-r6.apk 6a8a1b16f82d8810896f00c00b356c8b8d923e9aa0ce64737ae8274ca82f507a community xinit-1.4.4-r0.apk efb5dc1e7929824f5add7d5d51d0ecb34ecfd0a773dfca4f061adc8a353b234f community diff --git a/ports/fastfetch/Makefile b/ports/fastfetch/Makefile index 3df7b99..2662db0 100644 --- a/ports/fastfetch/Makefile +++ b/ports/fastfetch/Makefile @@ -91,7 +91,7 @@ $(BUILDDIR)/fastfetch: $(BUILDDIR)/toolchain.cmake build: $(BUILDDIR)/fastfetch install: $(BUILDDIR)/fastfetch | $(SYSROOT)/bin - install -m 0755 $(BUILDDIR)/fastfetch $(SYSROOT)/bin/fastfetch + install -C -m 0755 $(BUILDDIR)/fastfetch $(SYSROOT)/bin/fastfetch clean: rm -rf $(PORTDIR) diff --git a/projects/README.md b/projects/README.md index 27e6887..2163149 100644 --- a/projects/README.md +++ b/projects/README.md @@ -7,8 +7,7 @@ Everything above this directory — `kernel/`, `boot/`, `lib/`, `cmd/`, is one program that either works or does not. Everything in here is an ordinary program that talks to it through syscalls, exactly as `toybox` does. -The split exists because the alternative gets bad quickly. A compositor, -a window manager and a desktop are each larger than the kernel will be for a +The split exists because the alternative gets bad quickly. A desktop environment is larger than the kernel will be for a long time. Grown inside the OS tree they would blur into it: a compositor would start reaching into kernel headers because they are right there, the build would grow a dependency from the kernel to a GUI, and "does SchOS @@ -27,18 +26,12 @@ Concretely, code in here: | Directory | What it is | |------------|-----------------------------------------------------------------| -| `schcom/` | SchOS Compositor. Owns the display, composites clients, routes input. | | `schinst/` | The installer, the installation environment, and `.sip` tooling. | | `schfs-linux/` | SchFS as a filesystem for Linux. FUSE first, a kernel module later. | -`schwm/` and `schde/` used to be here and have been **deleted**. In Wayland the -compositor *is* the window manager — there is no protocol by which a second -process places another's windows — and a panel or a launcher is an ordinary -client that needs no home of its own until one is written. Both directories held -a README and no code, and both had said "to be deleted" for long enough to prove -that a directory marked for deletion is not deleted by being marked. What they -argued for survives as a module boundary inside Schcom; see -[`docs/plan/wayland.md`](../docs/plan/wayland.md). +`schcom/`, `schwm/` and `schde/` used to be here and were **deleted**: SchOS does not write its own compositor, window +manager or desktop. The desktop is Xorg with Window Maker ([`docs/architecture/desktop.md`](../docs/architecture/desktop.md)), +and the road to Wayland is Alpine's compositors on top of udev and a DRM device ([`docs/plan/roadmap.md`](../docs/plan/roadmap.md) §5). **`schfs-linux/` is written to stand on its own.** It does not mention SchOS anywhere, and that is deliberate rather than an oversight: it is intended to be @@ -55,17 +48,5 @@ by anything above it. ## Status -`schcom/` has a v0: 715 lines that run, over a wire format of its own. **That -wire format is superseded** — the compositor moves onto Wayland, keeping the -architecture and replacing the protocol. See -[`docs/plan/wayland.md`](../docs/plan/wayland.md). - -Everything Schcom was blocked on in the kernel now exists: unix domain sockets, -raw input events, shared memory, framebuffer `mmap`, and — as of the descriptor -passing work — `SCM_RIGHTS`, which is the one thing a Wayland client cannot be -written without. What is still missing is a **shareable memory object**, item 2 -of [`docs/plan/foundations.md`](../docs/plan/foundations.md): a Wayland buffer -is a file descriptor for memory, and a System V segment id is not one. - -`schinst/` and `schfs-linux/` are scoped and unstarted, and are **frozen** — -see [`docs/plan/1.0.md`](../docs/plan/1.0.md). +`schinst/` and `schfs-linux/` are scoped and unstarted, and are **frozen** -- see +[`docs/plan/1.0.md`](../docs/plan/1.0.md). diff --git a/projects/schcom/Makefile b/projects/schcom/Makefile deleted file mode 100644 index 90cbd2e..0000000 --- a/projects/schcom/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Schcom builds like any other program that runs *on* SchOS: against the -# sysroot's libc, never against kernel headers. The top-level Makefile passes -# CC, CFLAGS and DESTDIR in; nothing here reaches back up the tree. -# -# It is not part of the OS build. `make` at the top level never descends here -- -# `make projects` does. - -CC ?= cc -CFLAGS ?= -O2 -CFLAGS += -Wall -Wextra -std=c11 -DESTDIR ?= ../../sysroot - -BINS = schcom schcom-demo - -all: $(BINS) - -schcom: schcom.c proto.h - $(CC) $(CFLAGS) -o $@ schcom.c $(LDFLAGS) - -schcom-demo: schcom-demo.c proto.h - $(CC) $(CFLAGS) -o $@ schcom-demo.c $(LDFLAGS) - -install: $(BINS) - mkdir -p $(DESTDIR)/bin - cp $(BINS) $(DESTDIR)/bin/ - -clean: - rm -f $(BINS) - -.PHONY: all install clean diff --git a/projects/schcom/README.md b/projects/schcom/README.md deleted file mode 100644 index 86fc93c..0000000 --- a/projects/schcom/README.md +++ /dev/null @@ -1,110 +0,0 @@ -# Schcom — the SchOS Compositor - -Owns the display. Every pixel that reaches the screen goes through it. - -Clients do not draw to the framebuffer; they draw into their own buffers and -hand them over. Schcom decides what is on top of what, composites the result, -and presents it. It also owns input: it reads the raw event devices and sends -each event to whichever client should have it. - -That is the whole job. Window *placement* is not part of it — that is SchWM's, -and Schcom exposes a protocol for it rather than implementing policy itself. - -## Status - -**v0 runs.** A client connects, is given a shared buffer, draws into it and -commits; the result is composited and presented. Verified end to end by -`make schcom-test`, which boots SchOS, runs the compositor and a client, then -screendumps the framebuffer and asserts the pixels **from the host** — the -compositor's own report that it composited something proves nothing. It runs -two overlapping clients and checks that the newer one wins in the overlap: a -compositor that cannot order two surfaces cannot host a window manager. - -What exists: - -- framebuffer output through `/dev/fb0` and the fbdev ioctls, double-buffered -- a versioned protocol over a Unix socket, with a handshake that must come first -- shared-memory buffers, one per surface, owned by the compositor -- surface / attach / **commit** semantics: nothing is shown mid-draw -- input from `/dev/input/eventN`, routed to the topmost surface -- clients that die are cleaned up, including their shm segments -- **multiple clients**, composited bottom-to-top in creation order - -What does not exist yet: damage tracking beyond "repaint everything", multiple -outputs, subsurfaces, and any focus model beyond "topmost". Those are additions -rather than rewrites, which is the reason for committing to the surface/commit -model now instead of growing into it later. - -## Building - -Not part of the OS build. Needs `make ports` first, for the sysroot's libc: - -```sh -make projects # builds and stages into sysroot/bin -make # rebuilds the image so the binaries are on it -make schcom-test # boots it and checks the pixels -``` - -## What it needs from the kernel - -| Need | State | -|---|---| -| Shared memory (`shmget`/`shmat`/`shmdt`) | done | -| `mmap` of `/dev/fb0` | done | -| Framebuffer geometry via the fbdev ioctls | done | -| Unix domain sockets | done | -| Raw input events — key down/up with modifiers | done | -| `SCM_RIGHTS` | done — `sendmsg`/`recvmsg`, see [`docs/architecture/networking.md`](../../docs/architecture/networking.md#descriptor-passing) | -| A shareable memory object | **missing.** A Wayland buffer is a descriptor for memory; a System V segment id is not one. Item 2 of [`docs/plan/foundations.md`](../../docs/plan/foundations.md), and the one thing now standing between this directory and a Wayland compositor. | -| Mode setting and double buffering | partial — virtio-gpu binds, no modeset API yet | - -> **The v0 protocol below is superseded and the code is frozen.** It runs, and -> it is kept because it is the only thing in the tree that has ever composited -> anything — but nothing more will be added to it. SchWM and SchDE are referred -> to throughout and both have been deleted; read those mentions as "the policy -> module" and "a layer-shell client". See -> [`docs/plan/wayland.md`](../../docs/plan/wayland.md). - -## Design notes - -- **Buffers are shared memory, not pipes.** A pipe means copying every frame; at - 800×600×32 that is 1.9 MB per frame per client. -- **The compositor owns the segment, not the client.** It has to survive a - client crashing mid-frame, and it must not be something a client can resize - underneath the compositor. -- **One protocol, versioned from the start.** SchWM and SchDE are separate - programs, so this is a real compatibility surface on day one. Every message - carries its own length, so an unknown message is skipped rather than - desynchronising the stream — that is what lets a v0 compositor survive a v1 - client. -- **Policy belongs to SchWM.** If Schcom starts deciding where windows go, the - split has already failed. `CREATE_SURFACE` carries x/y today only because - there is no SchWM yet to ask. - -## On Wayland - -> **Decided: Schcom moves onto Wayland**, replacing the protocol below while -> keeping the architecture. See [`docs/plan/wayland.md`](../../docs/plan/wayland.md). - - -The protocol is deliberately Wayland-shaped — surfaces rather than windows, -buffers attached and made visible only on commit, and the compositor telling -clients when to draw. Wayland is **MIT-licensed**, so it can be linked against -without any conflict with GPL-2.0-or-later (see `docs/licensing.md`), and -supporting it later is intended. - -The one place v0 knowingly diverges: a buffer handle is a System V shared memory -id passed as an integer, where Wayland passes a file descriptor over -`SCM_RIGHTS`. `struct schcom_surface` carries a `handle_kind` field precisely so -that switching would be a new value rather than a protocol break. - -**That field could not have saved us.** Wayland's `wl_shm_create_pool` passes a -descriptor with no alternative form, so *our* protocol being the flexible one -was never the flexibility that was needed — and for a long time this README -claimed `SCM_RIGHTS` was done when there was no `sendmsg`/`recvmsg` in the -kernel at all. An entire GUI plan was drawn on top of that line. - -It is true now. Item 1 of -[`docs/plan/foundations.md`](../../docs/plan/foundations.md) is built and -tested; what a buffer still needs is item 2, a shareable memory object, because -a descriptor for a System V segment is not a thing that exists. diff --git a/projects/schcom/proto.h b/projects/schcom/proto.h deleted file mode 100644 index f7ebf15..0000000 --- a/projects/schcom/proto.h +++ /dev/null @@ -1,128 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * The Schcom wire protocol. - * - * Versioned from the first commit, because SchWM and SchDE are separate - * programs: the protocol is a real compatibility surface on day one, not after - * the fact. A client sends SCHCOM_OP_HELLO with the version it speaks and the - * compositor either acknowledges it or closes the connection. Nothing else is - * accepted before the handshake. - * - * The shape is deliberately Wayland's, because Wayland support is intended - * later and it is MIT-licensed, so it can be linked (see the licensing note in - * the README). Borrowed, specifically: - * - * - a surface is not a window. It is a rectangle of pixels with no policy - * attached; placement belongs to SchWM. - * - a buffer is attached to a surface and nothing appears until COMMIT. - * Atomicity is the whole point: a client that is halfway through drawing - * must never be shown. - * - the compositor tells the client when to draw again (FRAME), rather than - * the client drawing whenever it likes. That is what stops a fast client - * from burning the machine rendering frames nobody sees. - * - * Where it deliberately differs, for now: a buffer is a System V shared memory - * id passed as an integer, not a file descriptor passed over SCM_RIGHTS. - * SCM_RIGHTS is the Wayland-compatible route and the kernel supports it, but an - * integer needs no ancillary-data handling on either side and this is v0. The - * message carries a `handle_kind` field so the switch does not become a - * protocol break. - */ -#ifndef SCHCOM_PROTO_H -#define SCHCOM_PROTO_H - -#include - -#define SCHCOM_SOCKET_PATH "/tmp/schcom.sock" - -/* Bumped on any incompatible change. v0 is provisional in the SchAPI sense: - * nothing here is frozen until 1.0. */ -#define SCHCOM_VERSION 0u - -enum schcom_op { - /* client -> compositor */ - SCHCOM_OP_HELLO = 1, - SCHCOM_OP_CREATE_SURFACE = 2, - SCHCOM_OP_COMMIT = 3, - SCHCOM_OP_DESTROY = 4, - - /* compositor -> client */ - SCHCOM_OP_HELLO_ACK = 128, - SCHCOM_OP_SURFACE = 129, /* the buffer for a CREATE_SURFACE */ - SCHCOM_OP_FRAME = 130, /* draw again now */ - SCHCOM_OP_INPUT = 131, - SCHCOM_OP_ERROR = 132, -}; - -/* How the pixels are shared. Only SHMID exists today; the field exists so that - * adding SCM_RIGHTS later is a new value rather than a new protocol. */ -enum schcom_handle_kind { - SCHCOM_HANDLE_SHMID = 1, - SCHCOM_HANDLE_FD = 2, /* reserved: passed over SCM_RIGHTS */ -}; - -/* Every message begins with this. `length` counts the header itself, so a - * reader can skip a message it does not understand instead of losing sync -- - * which is what lets an old compositor talk to a newer client at all. */ -struct schcom_header { - uint32_t op; - uint32_t length; - uint32_t serial; /* echoed in the reply, 0 for events */ - uint32_t surface; /* surface id, 0 where not applicable */ -}; - -struct schcom_hello { - struct schcom_header hdr; - uint32_t version; - uint32_t pad; -}; - -struct schcom_create_surface { - struct schcom_header hdr; - uint32_t width; - uint32_t height; - int32_t x; /* v0: the client asks. SchWM will own this later. */ - int32_t y; -}; - -/* The compositor's answer: here is your surface id and where its pixels live. - * Always 32-bit XRGB8888, stride in bytes, because a client that has to - * negotiate a format before it can draw a rectangle is a client that cannot be - * written in an afternoon. Format negotiation is a v1 problem. */ -struct schcom_surface { - struct schcom_header hdr; - uint32_t handle_kind; - uint32_t handle; /* shm id when handle_kind == SHMID */ - uint32_t width; - uint32_t height; - uint32_t stride; - uint32_t size; -}; - -/* Nothing is shown until this arrives. The damage rectangle is advisory: the - * compositor may repaint more, never less. */ -struct schcom_commit { - struct schcom_header hdr; - int32_t x, y; - uint32_t width, height; -}; - -/* Raw-ish input, already routed to the surface that should have it. Codes are - * the Linux input event codes the kernel's /dev/input/eventN already speaks -- - * translating them here would mean inventing a second table to disagree with - * the first. */ -struct schcom_input { - struct schcom_header hdr; - uint32_t type; /* EV_KEY, EV_REL, ... */ - uint32_t code; - int32_t value; - int32_t x, y; /* pointer position at the time of the event */ -}; - -struct schcom_error { - struct schcom_header hdr; - uint32_t code; - uint32_t pad; -}; - -#endif /* SCHCOM_PROTO_H */ diff --git a/projects/schcom/schcom-demo.c b/projects/schcom/schcom-demo.c deleted file mode 100644 index dc4bdeb..0000000 --- a/projects/schcom/schcom-demo.c +++ /dev/null @@ -1,149 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * A Schcom client, and the test for the compositor. - * - * Draws a flat rectangle of a known colour at a known position and commits it. - * Both of those are chosen so the result can be checked from outside the - * machine: `make schcom-shot` screendumps the framebuffer and a script on the - * host asserts the pixel at a given coordinate. That is the same oracle - * pattern the SchFS round-trip uses -- something with no connection to the code - * under test has to agree that it worked. - * - * schcom-demo [--colour 0xRRGGBB] [--at X Y] [--size W H] [--wait SECONDS] - */ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include - -#include "proto.h" - -static int -read_all(int fd, void *buf, size_t n) -{ - unsigned char *p = buf; - while (n) { - ssize_t r = read(fd, p, n); - if (r == 0) return -1; - if (r < 0) { if (errno == EINTR) continue; return -1; } - p += r; n -= (size_t)r; - } - return 0; -} - -int -main(int argc, char **argv) -{ - uint32_t colour = 0x00C04020; - int32_t at_x = 100, at_y = 80; - uint32_t w = 240, h = 160; - int wait_s = 3; - - for (int i = 1; i < argc; i++) { - if (!strcmp(argv[i], "--colour") && i + 1 < argc) - colour = (uint32_t)strtoul(argv[++i], NULL, 0); - else if (!strcmp(argv[i], "--at") && i + 2 < argc) { - at_x = atoi(argv[++i]); at_y = atoi(argv[++i]); - } else if (!strcmp(argv[i], "--size") && i + 2 < argc) { - w = (uint32_t)atoi(argv[++i]); h = (uint32_t)atoi(argv[++i]); - } else if (!strcmp(argv[i], "--wait") && i + 1 < argc) - wait_s = atoi(argv[++i]); - } - - int fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) { perror("socket"); return 1; } - - struct sockaddr_un addr; - memset(&addr, 0, sizeof addr); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, SCHCOM_SOCKET_PATH, sizeof addr.sun_path - 1); - if (connect(fd, (struct sockaddr *)&addr, sizeof addr) < 0) { - fprintf(stderr, "schcom-demo: connect %s: %s\n", - SCHCOM_SOCKET_PATH, strerror(errno)); - return 1; - } - - /* Handshake first: the compositor accepts nothing else before it. */ - struct schcom_hello hello; - memset(&hello, 0, sizeof hello); - hello.hdr.op = SCHCOM_OP_HELLO; - hello.hdr.length = sizeof hello; - hello.hdr.serial = 1; - hello.version = SCHCOM_VERSION; - if (write(fd, &hello, sizeof hello) != (ssize_t)sizeof hello) { - perror("write hello"); return 1; - } - - struct schcom_hello ack; - if (read_all(fd, &ack, sizeof ack) < 0 || ack.hdr.op != SCHCOM_OP_HELLO_ACK) { - fprintf(stderr, "schcom-demo: no hello ack\n"); - return 1; - } - printf("schcom-demo: connected, protocol v%u\n", ack.version); - - struct schcom_create_surface req; - memset(&req, 0, sizeof req); - req.hdr.op = SCHCOM_OP_CREATE_SURFACE; - req.hdr.length = sizeof req; - req.hdr.serial = 2; - req.width = w; req.height = h; - req.x = at_x; req.y = at_y; - if (write(fd, &req, sizeof req) != (ssize_t)sizeof req) { - perror("write create"); return 1; - } - - struct schcom_surface surf; - if (read_all(fd, &surf, sizeof surf) < 0) { - fprintf(stderr, "schcom-demo: no reply to create\n"); - return 1; - } - if (surf.hdr.op != SCHCOM_OP_SURFACE) { - fprintf(stderr, "schcom-demo: create refused (op %u)\n", surf.hdr.op); - return 1; - } - if (surf.handle_kind != SCHCOM_HANDLE_SHMID) { - fprintf(stderr, "schcom-demo: unexpected handle kind %u\n", surf.handle_kind); - return 1; - } - - void *pix = shmat((int)surf.handle, NULL, 0); - if (pix == (void *)-1) { perror("shmat"); return 1; } - - printf("schcom-demo: surface %u, %ux%u stride %u\n", - surf.hdr.surface, surf.width, surf.height, surf.stride); - - /* Draw: a solid block with a one-pixel border, so a test can tell "the - * buffer arrived" from "the buffer arrived at the wrong offset". */ - for (uint32_t y = 0; y < surf.height; y++) { - uint32_t *row = (uint32_t *)((unsigned char *)pix + (size_t)y * surf.stride); - for (uint32_t x = 0; x < surf.width; x++) { - int edge = (x == 0 || y == 0 || x == surf.width - 1 || y == surf.height - 1); - row[x] = edge ? 0x00FFFFFF : colour; - } - } - - struct schcom_commit commit; - memset(&commit, 0, sizeof commit); - commit.hdr.op = SCHCOM_OP_COMMIT; - commit.hdr.length = sizeof commit; - commit.hdr.serial = 3; - commit.hdr.surface = surf.hdr.surface; - commit.width = surf.width; commit.height = surf.height; - if (write(fd, &commit, sizeof commit) != (ssize_t)sizeof commit) { - perror("write commit"); return 1; - } - printf("schcom-demo: committed 0x%06X at %d,%d\n", colour, at_x, at_y); - fflush(stdout); - - /* Stay alive: the surface belongs to this connection, and the compositor - * drops it when the client goes. Exiting immediately would erase the very - * thing the screenshot is meant to catch. */ - sleep(wait_s); - return 0; -} diff --git a/projects/schcom/schcom.c b/projects/schcom/schcom.c deleted file mode 100644 index 7a45438..0000000 --- a/projects/schcom/schcom.c +++ /dev/null @@ -1,715 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Schcom -- the SchOS compositor. - * - * Owns the display and the input devices. Clients draw into shared buffers and - * hand them over; Schcom decides what is on top of what, composites, and - * presents. It does not decide where windows go: that is SchWM's job, and the - * moment this file starts making placement decisions the split has failed. - * - * v0, deliberately small but not a toy: - * - * - real framebuffer output through /dev/fb0 and the fbdev ioctls - * - real clients over a Unix socket, with a versioned handshake - * - real shared-memory buffers, one per surface - * - a back buffer, so a half-composited frame never reaches the screen - * - input read from /dev/input/eventN and routed to the top surface - * - * What it does not do yet: damage tracking beyond "repaint everything", - * multiple outputs, subsurfaces, or any notion of focus beyond "topmost". - * Those are additions, not rewrites -- which is the point of committing to the - * surface/commit model now rather than growing into it. - */ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "proto.h" - -#define MAX_CLIENTS 16 -#define MAX_SURFACES 32 - -struct surface { - int used; - uint32_t id; - int client; /* index into clients[] */ - int shmid; - void *pixels; - uint32_t w, h, stride, size; - int32_t x, y; -}; - -struct client { - int fd; /* -1 when the slot is free */ - int hello_done; -}; - -static struct client clients[MAX_CLIENTS]; -static struct surface surfaces[MAX_SURFACES]; -static uint32_t next_surface_id = 1; - -/* The screen. */ -static uint8_t *fb; -static uint32_t fb_w, fb_h, fb_stride, fb_bpp; -static size_t fb_size; -static uint8_t *back; /* composited here, then copied out in one go */ - -/* Pointer position: set outright by EV_ABS from a tablet, nudged by EV_REL - * from a plain mouse. */ -static int32_t ptr_x, ptr_y; - -static void -die(const char *what) -{ - fprintf(stderr, "schcom: %s: %s\n", what, strerror(errno)); - exit(1); -} - -/* ---------------------------------------------------------------- output */ - -static int -open_framebuffer(void) -{ - int fd = open("/dev/fb0", O_RDWR); - if (fd < 0) - die("open /dev/fb0"); - - struct fb_var_screeninfo var; - struct fb_fix_screeninfo fix; - if (ioctl(fd, FBIOGET_VSCREENINFO, &var) < 0) - die("FBIOGET_VSCREENINFO"); - if (ioctl(fd, FBIOGET_FSCREENINFO, &fix) < 0) - die("FBIOGET_FSCREENINFO"); - - fb_w = var.xres; - fb_h = var.yres; - fb_bpp = var.bits_per_pixel; - /* The stride comes from the driver, never from xres*bpp: the two differ on - * real hardware and assuming otherwise shears the image. */ - fb_stride = fix.line_length; - fb_size = (size_t)fb_stride * fb_h; - - if (fb_bpp != 32) { - fprintf(stderr, "schcom: need 32bpp, got %u\n", fb_bpp); - exit(1); - } - - fb = mmap(NULL, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (fb == MAP_FAILED) - die("mmap /dev/fb0"); - - back = malloc(fb_size); - if (!back) - die("malloc back buffer"); - - printf("schcom: %ux%u, %u bpp, stride %u\n", fb_w, fb_h, fb_bpp, fb_stride); - fflush(stdout); - return fd; -} - -/* Damage: the rectangle that has to be redrawn and copied out. - * - * Without this the compositor repainted the whole screen for every event -- - * at 1280x800 that is a million pixels of desktop fill plus a 4 MB copy to the - * framebuffer, per mouse movement. Under emulation the pointer lagged behind - * the mouse by whole seconds, and it was not the emulation's fault: moving a - * 10x10 cursor does not require touching four megabytes. - * - * One rectangle rather than a list. Two distant small changes then repaint the - * span between them, which is wasteful in theory; in practice the changes are - * a pointer and the window under it, and a list costs more bookkeeping than it - * saves. - */ -static int dmg_x0, dmg_y0, dmg_x1, dmg_y1; /* x1/y1 exclusive */ -static int dirty; - -static void -damage(int x, int y, int w, int h) -{ - int x0 = x, y0 = y, x1 = x + w, y1 = y + h; - - if (x0 < 0) x0 = 0; - if (y0 < 0) y0 = 0; - if (x1 > (int)fb_w) x1 = (int)fb_w; - if (y1 > (int)fb_h) y1 = (int)fb_h; - if (x0 >= x1 || y0 >= y1) - return; - - if (!dirty) { - dmg_x0 = x0; dmg_y0 = y0; dmg_x1 = x1; dmg_y1 = y1; - dirty = 1; - return; - } - if (x0 < dmg_x0) dmg_x0 = x0; - if (y0 < dmg_y0) dmg_y0 = y0; - if (x1 > dmg_x1) dmg_x1 = x1; - if (y1 > dmg_y1) dmg_y1 = y1; -} - -static void -damage_all(void) -{ - damage(0, 0, (int)fb_w, (int)fb_h); -} - -#define PTR_SIZE 10 - -static void -composite(void) -{ - if (!dirty) - return; - - const int rx0 = dmg_x0, ry0 = dmg_y0, rx1 = dmg_x1, ry1 = dmg_y1; - - /* Desktop. A flat colour rather than black, so that "nothing was drawn" - * and "the compositor is not running" cannot look the same. */ - for (int y = ry0; y < ry1; y++) { - uint32_t *row = (uint32_t *)(back + (size_t)y * fb_stride); - for (int x = rx0; x < rx1; x++) - row[x] = 0x00203040; - } - - /* Surfaces, bottom to top. Creation order is stacking order in v0; SchWM - * will reorder later through a protocol request rather than by reaching in - * here. */ - for (int i = 0; i < MAX_SURFACES; i++) { - struct surface *s = &surfaces[i]; - if (!s->used || !s->pixels) - continue; - - /* Only the part of this surface inside the damage rectangle. */ - for (uint32_t sy = 0; sy < s->h; sy++) { - int dy = s->y + (int)sy; - if (dy < ry0 || dy >= ry1) - continue; - - uint32_t *dst = (uint32_t *)(back + (size_t)dy * fb_stride); - uint32_t *src = (uint32_t *)((uint8_t *)s->pixels + (size_t)sy * s->stride); - - for (uint32_t sx = 0; sx < s->w; sx++) { - int dx = s->x + (int)sx; - if (dx < rx0 || dx >= rx1) - continue; - dst[dx] = src[sx]; - } - } - } - - /* A crude pointer, drawn last so it is never under a window. */ - for (int py = 0; py < PTR_SIZE; py++) { - int dy = ptr_y + py; - if (dy < ry0 || dy >= ry1) - continue; - uint32_t *dst = (uint32_t *)(back + (size_t)dy * fb_stride); - for (int px = 0; px < PTR_SIZE - py; px++) { - int dx = ptr_x + px; - if (dx >= rx0 && dx < rx1) - dst[dx] = 0x00FFFFFF; - } - } - - /* Copy out only what changed, row by row. Compositing straight into the - * framebuffer would show every intermediate state -- the flicker would be - * the whole desktop being repainted under the user's eyes -- but copying - * the whole thing to move a cursor is what made the pointer lag. */ - size_t bytes = (size_t)(rx1 - rx0) * 4; - for (int y = ry0; y < ry1; y++) { - size_t off = (size_t)y * fb_stride + (size_t)rx0 * 4; - memcpy(fb + off, back + off, bytes); - } - - dirty = 0; -} - -/* ---------------------------------------------------------------- clients */ - -static int -send_all(int fd, const void *buf, size_t n) -{ - const uint8_t *p = buf; - while (n) { - ssize_t w = write(fd, p, n); - if (w < 0) { - if (errno == EINTR) - continue; - return -1; - } - p += w; - n -= (size_t)w; - } - return 0; -} - -static int -read_all(int fd, void *buf, size_t n) -{ - uint8_t *p = buf; - while (n) { - ssize_t r = read(fd, p, n); - if (r == 0) - return -1; /* client went away */ - if (r < 0) { - if (errno == EINTR) - continue; - return -1; - } - p += r; - n -= (size_t)r; - } - return 0; -} - -static void -send_error(int fd, uint32_t serial, uint32_t code) -{ - struct schcom_error e; - memset(&e, 0, sizeof e); - e.hdr.op = SCHCOM_OP_ERROR; - e.hdr.length = sizeof e; - e.hdr.serial = serial; - e.code = code; - send_all(fd, &e, sizeof e); -} - -static void -drop_client(int ci) -{ - if (clients[ci].fd < 0) - return; - - for (int i = 0; i < MAX_SURFACES; i++) { - struct surface *s = &surfaces[i]; - if (s->used && s->client == ci) { - damage(s->x, s->y, (int)s->w, (int)s->h); - if (s->pixels) - shmdt(s->pixels); - /* IPC_RMID once, here: the segment goes when the last attach does, - * so a client that died mid-frame does not leak it. */ - shmctl(s->shmid, IPC_RMID, NULL); - memset(s, 0, sizeof *s); - } - } - - close(clients[ci].fd); - clients[ci].fd = -1; - clients[ci].hello_done = 0; - printf("schcom: client %d gone\n", ci); - fflush(stdout); -} - -static void -handle_create_surface(int ci, const struct schcom_create_surface *req) -{ - int fd = clients[ci].fd; - - int slot = -1; - for (int i = 0; i < MAX_SURFACES; i++) - if (!surfaces[i].used) { slot = i; break; } - if (slot < 0) { - send_error(fd, req->hdr.serial, ENOSPC); - return; - } - - uint32_t w = req->width, h = req->height; - if (w == 0 || h == 0 || w > fb_w || h > fb_h) { - send_error(fd, req->hdr.serial, EINVAL); - return; - } - - uint32_t stride = w * 4; - uint32_t size = stride * h; - - /* The compositor owns the segment, not the client: it has to survive the - * client crashing mid-frame, and it must not be something a client can - * quietly resize underneath us. */ - int shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600); - if (shmid < 0) { - send_error(fd, req->hdr.serial, errno); - return; - } - void *pix = shmat(shmid, NULL, 0); - if (pix == (void *)-1) { - shmctl(shmid, IPC_RMID, NULL); - send_error(fd, req->hdr.serial, errno); - return; - } - memset(pix, 0, size); - - struct surface *s = &surfaces[slot]; - s->used = 1; - s->id = next_surface_id++; - s->client = ci; - s->shmid = shmid; - s->pixels = pix; - s->w = w; - s->h = h; - s->stride = stride; - s->size = size; - s->x = req->x; - s->y = req->y; - - struct schcom_surface rep; - memset(&rep, 0, sizeof rep); - rep.hdr.op = SCHCOM_OP_SURFACE; - rep.hdr.length = sizeof rep; - rep.hdr.serial = req->hdr.serial; - rep.hdr.surface = s->id; - rep.handle_kind = SCHCOM_HANDLE_SHMID; - rep.handle = (uint32_t)shmid; - rep.width = w; - rep.height = h; - rep.stride = stride; - rep.size = size; - - if (send_all(fd, &rep, sizeof rep) < 0) { - drop_client(ci); - return; - } - damage(s->x, s->y, (int)w, (int)h); - printf("schcom: surface %u %ux%u at %d,%d for client %d (shm %d)\n", - s->id, w, h, s->x, s->y, ci, shmid); - fflush(stdout); -} - -static struct surface * -find_surface(uint32_t id) -{ - for (int i = 0; i < MAX_SURFACES; i++) - if (surfaces[i].used && surfaces[i].id == id) - return &surfaces[i]; - return NULL; -} - -/* Returns 1 if the screen needs repainting. */ -static int -handle_message(int ci) -{ - int fd = clients[ci].fd; - struct schcom_header hdr; - - if (read_all(fd, &hdr, sizeof hdr) < 0) { - drop_client(ci); - return 0; - } - if (hdr.length < sizeof hdr || hdr.length > 4096) { - send_error(fd, hdr.serial, EPROTO); - drop_client(ci); - return 0; - } - - uint8_t body[4096]; - memcpy(body, &hdr, sizeof hdr); - size_t rest = hdr.length - sizeof hdr; - if (rest && read_all(fd, body + sizeof hdr, rest) < 0) { - drop_client(ci); - return 0; - } - - if (!clients[ci].hello_done && hdr.op != SCHCOM_OP_HELLO) { - /* Nothing before the handshake. A client that gets this wrong is a - * client built against a protocol we do not speak. */ - send_error(fd, hdr.serial, EPROTO); - drop_client(ci); - return 0; - } - - switch (hdr.op) { - case SCHCOM_OP_HELLO: { - const struct schcom_hello *h = (const void *)body; - if (hdr.length < sizeof *h || h->version != SCHCOM_VERSION) { - send_error(fd, hdr.serial, EPROTONOSUPPORT); - drop_client(ci); - return 0; - } - struct schcom_hello ack; - memset(&ack, 0, sizeof ack); - ack.hdr.op = SCHCOM_OP_HELLO_ACK; - ack.hdr.length = sizeof ack; - ack.hdr.serial = hdr.serial; - ack.version = SCHCOM_VERSION; - if (send_all(fd, &ack, sizeof ack) < 0) { - drop_client(ci); - return 0; - } - clients[ci].hello_done = 1; - printf("schcom: client %d speaks v%u\n", ci, SCHCOM_VERSION); - fflush(stdout); - return 0; - } - - case SCHCOM_OP_CREATE_SURFACE: - if (hdr.length < sizeof(struct schcom_create_surface)) { - send_error(fd, hdr.serial, EINVAL); - return 0; - } - handle_create_surface(ci, (const void *)body); - return 1; - - case SCHCOM_OP_COMMIT: { - struct surface *s = find_surface(hdr.surface); - if (!s || s->client != ci) { - send_error(fd, hdr.serial, ENOENT); - return 0; - } - /* The commit is the whole point: the pixels were already there, but - * they were not allowed to be shown until now. The damage rectangle in - * the message is advisory -- repaint the whole surface, which is never - * wrong, only sometimes more than asked. */ - damage(s->x, s->y, (int)s->w, (int)s->h); - struct schcom_header fr; - memset(&fr, 0, sizeof fr); - fr.op = SCHCOM_OP_FRAME; - fr.length = sizeof fr; - fr.surface = s->id; - if (send_all(fd, &fr, sizeof fr) < 0) { - drop_client(ci); - return 1; - } - return 1; - } - - case SCHCOM_OP_DESTROY: { - struct surface *s = find_surface(hdr.surface); - if (s && s->client == ci) { - damage(s->x, s->y, (int)s->w, (int)s->h); - if (s->pixels) - shmdt(s->pixels); - shmctl(s->shmid, IPC_RMID, NULL); - memset(s, 0, sizeof *s); - return 1; - } - return 0; - } - - default: - /* Unknown but well-formed: skipped, not fatal. That is what the length - * field is for, and it is how a v0 compositor survives a v1 client. */ - return 0; - } -} - -/* ----------------------------------------------------------------- input */ - -/* 1 if the screen needs repainting, 0 if the event changed nothing, -1 to - * quit, -2 when the queue is empty. The last is separate from 0 on purpose: - * every burst of movement ends in an EV_SYN that changes nothing, and a - * drain loop that stopped there would leave the rest of the burst queued. */ -static int -handle_input(int fd) -{ - struct input_event ev; - if (read(fd, &ev, sizeof ev) != (ssize_t)sizeof ev) - return -2; - - int repaint = 0; - - /* An absolute pointer says where it is; a relative one says how far it - * moved. Taking the absolute value as authoritative is the whole point of - * usb-tablet: two pointers that accumulate deltas independently drift - * apart, which inside a VM shows up as the guest cursor sitting somewhere - * the host's is not. */ - if (ev.type == EV_ABS) { - int old_x = ptr_x, old_y = ptr_y; - if (ev.code == ABS_X) { ptr_x = ev.value; repaint = 1; } - if (ev.code == ABS_Y) { ptr_y = ev.value; repaint = 1; } - if (ptr_x < 0) ptr_x = 0; - if (ptr_y < 0) ptr_y = 0; - if (ptr_x >= (int32_t)fb_w) ptr_x = (int32_t)fb_w - 1; - if (ptr_y >= (int32_t)fb_h) ptr_y = (int32_t)fb_h - 1; - if (repaint) { - damage(old_x, old_y, PTR_SIZE, PTR_SIZE); - damage(ptr_x, ptr_y, PTR_SIZE, PTR_SIZE); - } - } - - if (ev.type == EV_REL) { - int old_x = ptr_x, old_y = ptr_y; - if (ev.code == REL_X) { ptr_x += ev.value; repaint = 1; } - if (ev.code == REL_Y) { ptr_y += ev.value; repaint = 1; } - if (ptr_x < 0) ptr_x = 0; - if (ptr_y < 0) ptr_y = 0; - if (ptr_x >= (int32_t)fb_w) ptr_x = (int32_t)fb_w - 1; - if (ptr_y >= (int32_t)fb_h) ptr_y = (int32_t)fb_h - 1; - if (repaint) { - /* Where it was and where it is. Everything between is untouched. */ - damage(old_x, old_y, PTR_SIZE, PTR_SIZE); - damage(ptr_x, ptr_y, PTR_SIZE, PTR_SIZE); - } - } - - if (ev.type == EV_KEY && ev.value == 1 && ev.code == KEY_ESC) - return -1; - - /* Route to the topmost surface. "Topmost" is the whole focus model in v0; - * anything cleverer is policy and belongs to SchWM. */ - struct surface *top = NULL; - for (int i = 0; i < MAX_SURFACES; i++) - if (surfaces[i].used) - top = &surfaces[i]; - - if (top && (ev.type == EV_KEY || ev.type == EV_REL || - ev.type == EV_ABS)) { - struct schcom_input msg; - memset(&msg, 0, sizeof msg); - msg.hdr.op = SCHCOM_OP_INPUT; - msg.hdr.length = sizeof msg; - msg.hdr.surface = top->id; - msg.type = ev.type; - msg.code = ev.code; - msg.value = ev.value; - msg.x = ptr_x; - msg.y = ptr_y; - if (clients[top->client].fd >= 0) - send_all(clients[top->client].fd, &msg, sizeof msg); - } - - return repaint; -} - -/* ------------------------------------------------------------------ main */ - -int -main(int argc, char **argv) -{ - int frames_then_exit = 0; /* --frames N: for automated testing */ - - for (int i = 1; i < argc; i++) - if (!strcmp(argv[i], "--frames") && i + 1 < argc) - frames_then_exit = atoi(argv[++i]); - - for (int i = 0; i < MAX_CLIENTS; i++) - clients[i].fd = -1; - - open_framebuffer(); - ptr_x = (int32_t)fb_w / 2; - ptr_y = (int32_t)fb_h / 2; - - unlink(SCHCOM_SOCKET_PATH); - int lfd = socket(AF_UNIX, SOCK_STREAM, 0); - if (lfd < 0) - die("socket"); - - struct sockaddr_un addr; - memset(&addr, 0, sizeof addr); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, SCHCOM_SOCKET_PATH, sizeof addr.sun_path - 1); - if (bind(lfd, (struct sockaddr *)&addr, sizeof addr) < 0) - die("bind " SCHCOM_SOCKET_PATH); - if (listen(lfd, 8) < 0) - die("listen"); - - int kbd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); - int mouse = open("/dev/input/event1", O_RDONLY | O_NONBLOCK); - - printf("schcom: listening on %s\n", SCHCOM_SOCKET_PATH); - fflush(stdout); - - damage_all(); - composite(); - int frames = 1; - - for (;;) { - struct pollfd pfd[MAX_CLIENTS + 3]; - int n = 0; - - pfd[n].fd = lfd; pfd[n].events = POLLIN; n++; - int kbd_slot = -1, mouse_slot = -1; - if (kbd >= 0) { kbd_slot = n; pfd[n].fd = kbd; pfd[n].events = POLLIN; n++; } - if (mouse >= 0) { mouse_slot = n; pfd[n].fd = mouse; pfd[n].events = POLLIN; n++; } - - int first_client = n; - for (int i = 0; i < MAX_CLIENTS; i++) - if (clients[i].fd >= 0) { - pfd[n].fd = clients[i].fd; - pfd[n].events = POLLIN; - n++; - } - - if (poll(pfd, n, 100) < 0) { - if (errno == EINTR) - continue; - die("poll"); - } - - int repaint = 0; - - if (pfd[0].revents & POLLIN) { - int cfd = accept(lfd, NULL, NULL); - if (cfd >= 0) { - int slot = -1; - for (int i = 0; i < MAX_CLIENTS; i++) - if (clients[i].fd < 0) { slot = i; break; } - if (slot < 0) { - close(cfd); - } else { - clients[slot].fd = cfd; - clients[slot].hello_done = 0; - printf("schcom: client %d connected\n", slot); - fflush(stdout); - } - } - } - - /* Drain both devices before painting anything. A single mouse - * movement produces a burst of EV_REL followed by EV_SYN, and - * repainting per event meant the compositor rendered every - * intermediate position of a cursor nobody saw at those positions -- - * which is most of what "the mouse lags" was. */ - int quit = 0; - if (kbd_slot >= 0 && (pfd[kbd_slot].revents & POLLIN)) { - for (int n = 0; n < 256; n++) { - int r = handle_input(kbd); - if (r == -2) break; - if (r < 0) { quit = 1; break; } - if (r > 0) repaint = 1; - } - } - if (!quit && mouse_slot >= 0 && (pfd[mouse_slot].revents & POLLIN)) { - for (int n = 0; n < 256; n++) { - int r = handle_input(mouse); - if (r == -2) break; - if (r < 0) { quit = 1; break; } - if (r > 0) repaint = 1; - } - } - if (quit) break; - - /* Walk the client slots in the same order they were added to pfd. */ - int k = first_client; - for (int i = 0; i < MAX_CLIENTS; i++) { - if (clients[i].fd < 0) - continue; - if (pfd[k].revents & (POLLIN | POLLHUP)) - repaint |= handle_message(i); - k++; - } - - if (repaint) { - composite(); - frames++; - if (frames_then_exit && frames >= frames_then_exit) { - printf("schcom: %d frames, exiting as asked\n", frames); - fflush(stdout); - break; - } - } - } - - unlink(SCHCOM_SOCKET_PATH); - return 0; -} diff --git a/shell.nix b/shell.nix index 2a63eb7..a201277 100644 --- a/shell.nix +++ b/shell.nix @@ -43,6 +43,7 @@ pkgs.mkShell { qemu # make qemu, and every test harness OVMF # firmware for the UEFI path; the Makefile searches for it + bubblewrap # mkaroot runs Alpine's triggers in the root as / xorriso # make iso mtools # ESP staging curl gnutar # fetching ports diff --git a/tools/mkschfs.c b/tools/mkschfs.c index 18c7b6f..6a4ed1f 100644 --- a/tools/mkschfs.c +++ b/tools/mkschfs.c @@ -9,7 +9,7 @@ // construction — there is only one implementation, exercised on the host where // a mistake costs a second instead of a boot. // -// mkschfs out.img --sysroot DIR [--size BLOCKS] [--label NAME] +// mkschfs out.img --sysroot DIR [--size BLOCKS] [--label NAME] [--owners FILE] // // Two behaviours worth knowing about: // @@ -289,6 +289,63 @@ die(const char *what, int r) exit(1); } +/* Who owns what. The host's owners mean nothing inside the image -- every file + came from whoever ran the build -- so everything is root's unless a rule in + the --owners file says otherwise. One rule per line, `path uid gid [mode]`, + with the path relative to the sysroot ("/home/schos"). uid and gid apply to + the path and to everything below it; a mode, in octal, to that path alone. + Later rules win. */ +struct owner_rule { char *path; uint32_t uid, gid; int has_mode; uint32_t mode; }; +static struct owner_rule *owner_rules; +static int nowner_rules; +static const char *cur_rel; /* the entry being added, set by add_entry */ + +static void +load_owners(const char *file) +{ + FILE *f = fopen(file, "r"); + char line[1024]; + if (!f) { perror(file); exit(1); } + while (fgets(line, sizeof line, f)) { + char path[900]; + unsigned uid, gid, mode; + int n; + if (line[0] == '#' || line[0] == '\n') + continue; + n = sscanf(line, "%899s %u %u %o", path, &uid, &gid, &mode); + if (n < 3) { fprintf(stderr, "mkschfs: bad owners line: %s", line); exit(1); } + owner_rules = realloc(owner_rules, (size_t)(nowner_rules + 1) * sizeof *owner_rules); + owner_rules[nowner_rules].path = strdup(path); + owner_rules[nowner_rules].uid = uid; + owner_rules[nowner_rules].gid = gid; + owner_rules[nowner_rules].has_mode = n == 4; + owner_rules[nowner_rules].mode = mode; + nowner_rules++; + } + fclose(f); +} + +static void +apply_owners(struct schfs_inode *in) +{ + if (!cur_rel) + return; + for (int i = 0; i < nowner_rules; i++) { + const char *r = owner_rules[i].path; + size_t n; + if (*r == '/') + r++; /* cur_rel is relative: "home/schos" */ + n = strlen(r); + int exact = strcmp(cur_rel, r) == 0; + if (!exact && !(strncmp(cur_rel, r, n) == 0 && cur_rel[n] == '/')) + continue; + in->uid = owner_rules[i].uid; + in->gid = owner_rules[i].gid; + if (exact && owner_rules[i].has_mode) + in->mode = owner_rules[i].mode; + } +} + static void inode_init(struct schfs_inode *in, const struct stat *st, uint16_t type, uint64_t parent) @@ -301,6 +358,7 @@ inode_init(struct schfs_inode *in, const struct stat *st, uint16_t type, : (type == SCHFS_T_DIR ? 0755u : 0644u); in->uid = 0; in->gid = 0; + apply_owners(in); if (st) { // All three come from mtime, deliberately. // @@ -537,6 +595,7 @@ add_entry(uint64_t dirino, const char *hostdir, const char *name) // lstat, not stat: a symlink must be stored as a symlink rather than // silently turned into a second copy of whatever it points at. if (lstat(path, &st) != 0) { perror(path); exit(1); } + cur_rel = strlen(path) >= sysroot_prefix ? path + sysroot_prefix : path; if (strlen(name) > SCHFS_NAME_MAX) { fprintf(stderr, "mkschfs: name too long (max %d): %s\n", @@ -631,6 +690,8 @@ main(int argc, char **argv) nblocks = strtoull(argv[++i], NULL, 0); else if (strcmp(argv[i], "--label") == 0 && i + 1 < argc) label = argv[++i]; + else if (strcmp(argv[i], "--owners") == 0 && i + 1 < argc) + load_owners(argv[++i]); else if (strcmp(argv[i], "--snapshot") == 0 && i + 1 < argc) snapname = argv[++i]; else if (strcmp(argv[i], "--exclude") == 0 && i + 1 < argc) { diff --git a/tools/posix/mkaroot.sh b/tools/posix/mkaroot.sh index 4d2e71e..ec2b820 100644 --- a/tools/posix/mkaroot.sh +++ b/tools/posix/mkaroot.sh @@ -91,25 +91,40 @@ rm -rf "$P/usr/share/hwdata" esac done +# su and passwd are SchOS's own and run setuid root (cmd/su, cmd/passwd): the +# busybox ones are not setuid here, and a setuid toybox is one binary under +# every name. passwd goes where PATH finds it before busybox's link. +mkdir -p "$P/usr/bin" +cp "$SYSROOT/bin/su" "$P/bin/su" +cp "$SYSROOT/bin/passwd" "$P/usr/bin/passwd" +rm -f "$P/bin/passwd" +chmod 4755 "$P/bin/su" "$P/usr/bin/passwd" + +# udev's device permissions for the kernel's own nodes. +mkdir -p "$P/etc/udev/rules.d" +mv "$P/etc/udev-schos.rules" "$P/etc/udev/rules.d/90-schos.rules" + # SchOS's profile without the three lines Alpine's already sets, and sets # better: PATH with sbin, HOME from passwd, TERM from the console job. mkdir -p "$P/etc/profile.d" grep -v '^export \(PATH\|HOME\|TERM\)=' "$P/etc/profile" > "$P/etc/profile.d/schos.sh" rm -f "$P/etc/profile" -# How startx starts the X server for root: Alpine's xserverrc, plus -# -dumbSched. Xorg reads input on a thread of its own, SchOS has no threads -# yet, and the server crashed on its way out the first time a client -# disconnected, in pthread_join on a thread that was never made. -dumbSched is -# Xorg's own switch for input on the main loop. The file is root's own rather -# than /etc/X11/xinit/xserverrc, which belongs to the xinit package. +# How startx starts the X server for root: Alpine's xserverrc. (It used to add +# -dumbSched, Xorg's switch for reading input on the main loop, because when SchOS +# had no threads the server crashed on its way out the first time a client +# disconnected, in pthread_join on a thread that was never made. SchOS has threads +# now; X was run without the switch under QEMU -- clients connecting and +# disconnecting, an xterm starting and exiting -- and it stays up.) The file is +# root's own rather than /etc/X11/xinit/xserverrc, which belongs to the xinit +# package. mkdir -p "$P/root" cat > "$P/root/.xserverrc" <<'XEOF' #!/bin/sh # The VT X runs on is the one startx was started from. startx works that out # only when `uname -s` says Linux, and SchOS's says SchIFT. vt=$(tty | sed -n 's|^/dev/tty\([0-9][0-9]*\)$|vt\1|p') -exec /usr/bin/X -nolisten tcp -dumbSched $vt "$@" +exec /usr/bin/X -nolisten tcp $vt "$@" XEOF # What startx runs inside it: Window Maker, which world installs. Without this @@ -119,37 +134,26 @@ cat > "$P/root/.xinitrc" <<'XEOF' exec wmaker XEOF -# X's input devices, named rather than found. Alpine's Xorg asks udev for -# them, SchOS has no udev, and X came up with no keyboard and no mouse: the -# pointer did not move, Ctrl+Alt+F-keys did nothing and nothing could end the -# session. SchOS has exactly two input nodes whatever the machine -- every -# keyboard on event0, every mouse on event1 (kernel/dev/input.c) -- so a -# fixed list is the whole answer. The file is SchOS's, in xorg.conf.d, which -# no Alpine package writes into. +# X's input devices are found, not named: udevd (etc/schinit/udevd.job) turns +# the kernel's uevents into /run/udev data, and Xorg asks libudev for every +# device with ID_INPUT set. That was a hand-written list of event0 and event1 +# while SchOS had no udev. The file is SchOS's, in xorg.conf.d, which no Alpine +# package writes into; what is left here is the options the defaults do not +# give, and the framebuffer driver. mkdir -p "$P/etc/X11/xorg.conf.d" cat > "$P/etc/X11/xorg.conf.d/10-schos.conf" <<'XEOF' -# SchOS has no udev, so X is told its input devices instead of finding them. -# event0 is every keyboard and event1 every mouse (kernel/dev/input.c). Section "ServerFlags" - Option "AutoAddDevices" "false" # Ctrl+Alt+Backspace ends X. Option "DontZap" "false" EndSection -Section "InputDevice" - Identifier "keyboard" - Driver "evdev" - Option "Device" "/dev/input/event0" +Section "InputClass" + Identifier "SchOS keyboards" + MatchIsKeyboard "on" Option "XkbRules" "evdev" Option "XkbOptions" "terminate:ctrl_alt_bksp" EndSection -Section "InputDevice" - Identifier "mouse" - Driver "evdev" - Option "Device" "/dev/input/event1" -EndSection - Section "Device" Identifier "fb" Driver "fbdev" @@ -163,11 +167,87 @@ EndSection Section "ServerLayout" Identifier "schos" Screen "screen" - InputDevice "keyboard" "CoreKeyboard" - InputDevice "mouse" "CorePointer" EndSection XEOF +# Who may start X. The wrapper's default is "console users", decided by whether +# a standard descriptor is a Linux VT, which xinit's server child does not +# keep. SchOS has one seat, and /dev's group permissions (video, input, tty) +# are what actually guard the hardware, so anyone who can open them may run X. +cat > "$P/etc/X11/Xwrapper.config" <<'XEOF' +allowed_users=anybody +needs_root_rights=no +XEOF + +# bwrap, without the sandbox. glycin -- which loads every image gdk-pixbuf is +# asked for, so every GTK icon -- runs its loaders under bubblewrap and has no +# other way to start them. Real bwrap needs user and mount namespaces, which +# SchOS does not have. This takes the same arguments, keeps what they say +# about the environment and the directory, and runs the program as it is: +# no isolation at all, and it says so in its name. It replaces bubblewrap's own +# /usr/bin/bwrap, which glycin depends on and which cannot run here. +mkdir -p "$P/usr/bin" +cat > "$P/usr/bin/bwrap" <<'BEOF' +#!/bin/sh +# SchOS: bubblewrap's command line, with no sandbox behind it. SchOS has no +# namespaces; what the options say about the environment and the working +# directory is honoured, and everything about isolation is ignored. +clear= envs= +while [ $# -gt 0 ]; do + case "$1" in + --clearenv) clear=1; shift ;; + --setenv) envs="$envs $2=$3"; shift 3 ;; + --unsetenv) unset "$2"; shift 2 ;; + --chdir) cd "$2" || exit 1; shift 2 ;; + --bind|--bind-try|--ro-bind|--ro-bind-try|--dev-bind|--dev-bind-try|\ + --symlink|--bind-data|--ro-bind-data|--file|--chmod) shift 3 ;; + --dev|--proc|--tmpfs|--dir|--mqueue|--remount-ro|--seccomp|--add-seccomp-fd|\ + --uid|--gid|--hostname|--cap-add|--cap-drop|--lock-file|--sync-fd|\ + --info-fd|--json-status-fd|--block-fd|--userns-block-fd|--perms|--size|\ + --argv0|--exec-label|--file-label|--userns|--userns2|--pidns) shift 2 ;; + --unshare-*|--share-net|--die-with-parent|--new-session|--as-pid-1|\ + --disable-userns|--assert-userns-disabled) shift ;; + --) shift; break ;; + -*) echo "bwrap: $1: not understood (SchOS runs bwrap without a sandbox)" >&2 + exit 1 ;; + *) break ;; + esac +done +[ $# -gt 0 ] || { echo "bwrap: no command" >&2; exit 1; } +# shellcheck disable=SC2086 +if [ -n "$clear" ]; then exec env -i PATH="$PATH" $envs "$@"; fi +# shellcheck disable=SC2086 +[ -n "$envs" ] && exec env $envs "$@" +exec "$@" +BEOF +chmod 755 "$P/usr/bin/bwrap" + +# ALSA's default device, through the OSS plugin to /dev/dsp. The plug in front +# converts whatever a program plays to what every SchOS audio driver accepts: +# 16-bit stereo at 48 kHz (AC'97 without variable rate plays nothing else, and +# the plugin refuses a rate the device rounds by more than 1%). +mkdir -p "$P/etc" +cat > "$P/etc/asound.conf" <<'AEOF' +# SchOS: ALSA plays through OSS's /dev/dsp (kernel/dev/audio.c). +pcm.!default { + type plug + slave { + pcm "dsp" + format S16_LE + channels 2 + rate 48000 + } +} +pcm.dsp { + type oss + device /dev/dsp +} +ctl.!default { + type hw + card 0 +} +AEOF + # Mount points and runtime directories are alpine-baselayout's; an empty # directory here only duplicates it. for d in dev proc sys run tmp srv; do @@ -197,7 +277,7 @@ cp "$PKG/post-install" "$PKG/post-upgrade" --info url:https://github.com/Nexoniarz/SchOS \ --info origin:schos-base \ --info "depends:alpine-baselayout busybox musl apk-tools zsh" \ - --info "replaces:alpine-release alpine-baselayout zsh" \ + --info "replaces:alpine-release alpine-baselayout zsh bubblewrap busybox" \ --files "$P" \ --script "post-install:$PKG/post-install" \ --script "post-upgrade:$PKG/post-upgrade" \ @@ -234,6 +314,83 @@ ROOT_PI=$ROOT sh -c ' done grep -qw schos "$ROOT_PI/etc/hosts" || printf "127.0.1.1\tschos\n" >> "$ROOT_PI/etc/hosts"' +# The triggers --no-scripts skipped, and they are not optional: they build the +# caches half the desktop reads. Without shared-mime-info's, GLib cannot tell a +# PNG from anything else, so gdk-pixbuf -- which hands images to glycin by MIME +# type -- refused every icon and NetSurf died on its first toolbar button. +# Likewise gdk-pixbuf's loader list, GLib's compiled schemas and GIO modules, +# GTK's input methods, the font caches and busybox's applet links. +# +# They run in the root as /, which is what bubblewrap is for: no chroot, so no +# root needed. apk names each script by the hex form of the checksum the +# triggers database keys in base64, and passes the paths it watches as +# arguments, globs expanded. +if command -v bwrap >/dev/null 2>&1; then + TRIG=$(mktemp -d) + tar -xzf "$ROOT/lib/apk/db/scripts.tar.gz" -C "$TRIG" --wildcards '*.trigger' 2>/dev/null || true + mkdir -p "$ROOT/tmp" + for t in "$TRIG"/*.trigger; do + [ -e "$t" ] || continue + hex=${t##*.X1}; hex=${hex%.trigger} + q=Q1$(python3 -c 'import base64,sys; print(base64.b64encode(bytes.fromhex(sys.argv[1])).decode())' "$hex") + paths=$(grep "^$q " "$ROOT/lib/apk/db/triggers" | cut -d' ' -f2-) + cp "$t" "$ROOT/tmp/apk-trigger" + # --clearenv: the host's environment is not the root's. A Nix shell + # exports GDK_PIXBUF_MODULE_FILE, and the loader cache went there. + bwrap --bind "$ROOT" / --dev /dev --proc /proc --clearenv \ + --setenv PATH /usr/sbin:/usr/bin:/sbin:/bin --setenv HOME /root \ + /bin/sh -c 'a=; for g in '"$paths"'; do for p in $g; do [ -e "$p" ] && a="$a $p"; done; done + exec /bin/sh /tmp/apk-trigger $a' \ + || echo "mkaroot: trigger $(basename "$t") failed" >&2 + rm -f "$ROOT/tmp/apk-trigger" + done + rm -rf "$TRIG" +else + echo "mkaroot: no bwrap -- triggers not run; image and MIME caches are missing" >&2 +fi + +# The shared scratch directories, world-writable and sticky as baselayout +# means them. apk running as an ordinary user on the build host lands them as +# 0755, and with the kernel reporting real modes that is what tmux and every +# other program checking /tmp would see. +chmod 1777 "$ROOT/tmp" "$ROOT/var/tmp" + +# --- accounts ------------------------------------------------------------------- +# +# Alpine's baselayout has root and the system accounts, with root's password +# locked. A live image needs a way in, so: root, with no password -- login lets +# root in without asking, and `passwd` sets one. That is the only account; an +# ordinary user is one `adduser` away, and the permission checks, su and doas +# are all still there for when there is one. +sed -i 's/^root:[^:]*:/root::/' "$ROOT/etc/shadow" +chmod 640 "$ROOT/etc/shadow" + +# doas: members of wheel run anything, without being asked again. Nobody is in +# wheel yet. The file has to be root's and not writable by anyone else. +cat > "$ROOT/etc/doas.conf" <<'DEOF' +# doas: see doas.conf(5). Members of wheel run anything as anyone. +permit nopass :wheel +DEOF +chmod 644 "$ROOT/etc/doas.conf" + +# zsh offers to write one on first login; /etc/zsh is where the settings are. +: > "$ROOT/root/.zshrc" +chmod 700 "$ROOT/root" + +# The setuid programs. Run as an ordinary user, apk may have dropped the bit, +# and nothing here should depend on whether it did. +for f in bin/su usr/bin/passwd usr/bin/doas; do + [ -f "$ROOT/$f" ] && chmod 4755 "$ROOT/$f" +done + +# Who owns what inside the image: everything is root's -- the build host's +# user means nothing here. +cat > "$OUT/owners" <<'OEOF' +/root 0 0 0700 +/tmp 0 0 1777 +/var/tmp 0 0 1777 +OEOF + # The repository goes in too, so the installed system can reinstall and # upgrade schos-base, and Alpine's are listed for everything else. mkdir -p "$ROOT/var/lib/schos" diff --git a/tools/posix/schcom_test.sh b/tools/posix/schcom_test.sh deleted file mode 100644 index 13986f8..0000000 --- a/tools/posix/schcom_test.sh +++ /dev/null @@ -1,154 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Run Schcom and a client inside SchOS, then check the actual pixels from the -# host with QEMU's screendump. The host has no connection to the compositor, so -# it is a real oracle: if the block is the right colour in the right place, the -# buffer really was shared, composited and presented. -# -# One display device on purpose (-vga std, no virtio-gpu): with two, screendump -# captures whichever QEMU considers active, which need not be the one /dev/fb0 -# maps. -set -e -# Run from anywhere: resolve the repo root from this script's own location -# rather than trusting the caller's working directory. Everything below is -# relative to it, and running from tools/posix/ used to fail with "missing -# build/kernel/kernel -- run make first" on a tree that was perfectly built. -script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -repo=$(CDPATH= cd -- "$script_dir/../.." && pwd) -cd "$repo" - -BUILD=${BUILD:-build} -mkdir -p "$BUILD" - -LOG=$BUILD/schcom-test.log -PPM=${1:-$BUILD/schcom.ppm} -rm -f "$LOG" "$PPM" - -# The firmware varstore is written by OVMF, so this run gets its own copy -# rather than sharing the pristine one every other run starts from. -cp -f build/OVMF_VARS.fd build/schcom-OVMF_VARS.fd - -PORT=$(python3 - <<'EOF' -import socket -s=socket.socket(); s.bind(('127.0.0.1',0)); print(s.getsockname()[1]); s.close() -EOF -) - -( - sleep 26 - printf 'schcom --frames 24 &\n'; sleep 6 - printf 'schcom-demo --colour 0x00C04020 --at 100 80 --size 240 160 --wait 40 &\n'; sleep 10 - printf 'schcom-demo --colour 0x002060C0 --at 260 180 --size 240 160 --wait 30 &\n'; sleep 12 - printf '\n' - sleep 40 -) | timeout -k 5 120 qemu-system-x86_64 \ - -machine pc-i440fx-9.2 -cpu qemu64,+rdtscp \ - -drive if=pflash,format=raw,unit=0,readonly=on,file=build/OVMF_CODE.fd \ - -drive if=pflash,format=raw,unit=1,file=build/schcom-OVMF_VARS.fd \ - -drive file=fat:rw:build/esp,index=0,media=disk,format=raw \ - -drive file=build/schfs.img,format=raw,index=1,media=disk \ - -m 1G -smp 1 \ - -display none -vga std \ - -monitor tcp:127.0.0.1:$PORT,server,nowait \ - -chardev stdio,id=c0,logfile="$LOG" -serial chardev:c0 \ - >/dev/null 2>&1 & -QPID=$! - -# Wait until *both* clients have committed, then photograph. Breaking on the -# first one screenshots a desktop the second client has not reached yet, which -# looks exactly like a compositor that cannot handle two clients. -for i in $(seq 1 90); do - [ "$(grep -ac 'committed' "$LOG" 2>/dev/null)" -ge 2 ] && break - sleep 1 -done -sleep 2 - -python3 - "$PORT" "$PPM" <<'EOF' -import socket, sys, time -port, ppm = int(sys.argv[1]), sys.argv[2] -s = socket.create_connection(('127.0.0.1', port), timeout=10) -time.sleep(0.5) -s.sendall(b'screendump ' + ppm.encode() + b'\n') -time.sleep(2.0) -s.close() -EOF - -sleep 1 -kill -9 $QPID 2>/dev/null || true -wait 2>/dev/null || true - -echo "=== guest output ===" -sed 's/\x1b\[[0-9;=]*[A-Za-z]//g' "$LOG" | tr -d '\r' | grep -aE 'schcom|surface|committed|listening' | head -12 - -echo "=== pixel check (host oracle) ===" -python3 - "$PPM" <<'EOF' -import sys -path = sys.argv[1] -try: - data = open(path, 'rb').read() -except FileNotFoundError: - print("FAIL: no screendump produced"); sys.exit(1) - -# Binary PPM: P6, whitespace-separated width height maxval, then RGB triples. -def token(buf, i): - while buf[i:i+1].isspace(): i += 1 - j = i - while not buf[j:j+1].isspace(): j += 1 - return buf[i:j], j - -if data[:2] != b'P6': - print("FAIL: not a P6 PPM"); sys.exit(1) -i = 2 -w, i = token(data, i); h, i = token(data, i); mx, i = token(data, i) -w, h = int(w), int(h) -px = data[i+1:] -print(f" screendump {w}x{h}") - -def at(x, y): - o = (y*w + x)*3 - return px[o], px[o+1], px[o+2] - -def near(got, want, tol=8): - return all(abs(a-b) <= tol for a, b in zip(got, want)) - -fails = 0 -inside = at(150, 120) # middle of the 240x160 block at 100,80 -if near(inside, (0xC0, 0x40, 0x20)): - print(f" [ok] surface pixel at 150,120 is {inside} -- the client's colour") -else: - print(f" FAIL: 150,120 is {inside}, expected (192, 64, 32)"); fails += 1 - -desktop = at(20, 20) # compositor background 0x203040 -if near(desktop, (0x20, 0x30, 0x40)): - print(f" [ok] desktop pixel at 20,20 is {desktop} -- compositor background") -else: - print(f" FAIL: 20,20 is {desktop}, expected (32, 48, 64)"); fails += 1 - -border = at(100, 80) # top-left of the first surface -if near(border, (0xFF, 0xFF, 0xFF)): - print(f" [ok] border pixel at 100,80 is {border} -- surface landed exactly") -else: - print(f" FAIL: 100,80 is {border}, expected white border"); fails += 1 - -# Second client, created later, so it is above the first where they overlap. -second = at(460, 320) # inside surface 2, clear of the pointer -if near(second, (0x20, 0x60, 0xC0)): - print(f" [ok] second surface at 460,320 is {second} -- two clients composited") -else: - print(f" FAIL: 460,320 is {second}, expected (32, 96, 192)"); fails += 1 - -# 260,180..340,240 is where the two overlap. The later surface must win: if -# stacking were ignored the first client's colour would show through here, and -# a compositor that cannot order two windows cannot host a window manager. -overlap = at(300, 220) -if near(overlap, (0x20, 0x60, 0xC0)): - print(f" [ok] overlap at 300,220 is {overlap} -- newer surface on top") -elif near(overlap, (0xC0, 0x40, 0x20)): - print(f" FAIL: overlap shows the older surface -- stacking order ignored"); fails += 1 -else: - print(f" FAIL: overlap at 300,220 is {overlap}, expected the newer surface"); fails += 1 - -print("schcom end-to-end: PASSED" if not fails else f"schcom end-to-end: {fails} FAILED") -sys.exit(1 if fails else 0) -EOF diff --git a/tools/python/mknvfont.py b/tools/python/mknvfont.py new file mode 100644 index 0000000..3004caa --- /dev/null +++ b/tools/python/mknvfont.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Rasterise Niveus's interface face into a header the boot manager can draw. + +The loader cannot carry a TrueType rasteriser, and the answer to that has been +the same since include/gfx/font.h: do the work at build time and commit the +result. This takes Instrument Sans (SIL OFL, see docs/Website/niveus/fonts/) +at three sizes and writes 8-bit coverage bitmaps, one per printable ASCII +character, with the metrics needed to lay them out proportionally. + + python3 tools/python/mknvfont.py docs/Website/niveus/fonts/static include/gfx/nvfont.h + +Needs Pillow; nothing else, and nothing at all at build time of the OS. +""" +import sys, os +from PIL import ImageFont + +# Each size twice: x1 for ordinary screens and x2 for the ones where a 15 px +# face would be a speck. Rendering at the real size beats doubling pixels. +SIZES = [] +for _scale in (1, 2): + SIZES += [ + ("body%d" % _scale, "InstrumentSans-Medium.ttf", 15 * _scale), + ("small%d" % _scale, "InstrumentSans-Regular.ttf", 12 * _scale), + ("title%d" % _scale, "InstrumentSans-SemiBold.ttf", 24 * _scale), + ] +FIRST, LAST = 32, 126 + + +def render(path, px): + f = ImageFont.truetype(path, px) + asc, desc = f.getmetrics() + glyphs, blob = [], bytearray() + for code in range(FIRST, LAST + 1): + ch = chr(code) + adv = int(round(f.getlength(ch) * 64)) # 26.6 fixed point: whole-pixel advances make text look cramped + mask, (ox, oy) = f.getmask2(ch, mode="L", anchor="ls") + w, h = mask.size + data = bytes(mask) if (w and h) else b"" + glyphs.append((len(blob), w, h, ox, oy, adv)) + blob += data + return asc, desc, glyphs, blob + + +def main(): + src, out = sys.argv[1], sys.argv[2] + lines = [ + "#pragma once", + "", + "// GENERATED by tools/python/mknvfont.py -- do not edit. Run `make nvfont`.", + "// source: docs/Website/niveus/fonts/static (Instrument Sans, SIL OFL 1.1)", + "//", + "// 8-bit coverage bitmaps for printable ASCII, proportionally spaced, used by", + "// the boot manager (boot/common/ui.c). Not included by the kernel.", + "", + "struct nvglyph { unsigned short off; unsigned char w, h; signed char ox, oy; unsigned short adv; /* 1/64 px */ };", + "struct nvfont { unsigned char ascent, descent; const struct nvglyph *g; const unsigned char *bits; };", + "#define NVFONT_FIRST %d" % FIRST, + "#define NVFONT_LAST %d" % LAST, + "", + ] + for name, fn, px in SIZES: + asc, desc, glyphs, blob = render(os.path.join(src, fn), px) + lines.append("static const struct nvglyph nvg_%s[] = {" % name) + for (o, w, h, ox, oy, adv) in glyphs: + lines.append(" {%d,%d,%d,%d,%d,%d}," % (o, w, h, ox, oy, adv)) + lines.append("};") + lines.append("static const unsigned char nvb_%s[] = {" % name) + for i in range(0, len(blob), 24): + lines.append(" " + ",".join(str(b) for b in blob[i:i + 24]) + ",") + if not blob: + lines.append(" 0") + lines.append("};") + lines.append("static const struct nvfont nvfont_%s = { %d, %d, nvg_%s, nvb_%s };" % + (name, asc, desc, name, name)) + lines.append("") + open(out, "w").write("\n".join(lines) + "\n") + print("wrote", out) + + +main() diff --git a/tools/python/qrcheck.py b/tools/python/qrcheck.py index e034439..87ef221 100644 --- a/tools/python/qrcheck.py +++ b/tools/python/qrcheck.py @@ -117,10 +117,15 @@ def decode(m): return "" -# Byte capacity per version: total - ecc*blocks - 2. Mirrors the table in qr.c; -# the exact-fill cases below are built from it, and a disagreement would show up +# Byte-mode capacity per version at level L, from ISO/IEC 18004 table 7 -- +# written out rather than computed, so it checks qr.c instead of agreeing with +# it. The exact-fill cases below are built from it, and a disagreement shows up # as a version mismatch rather than passing quietly. -CAPACITY = {1: 17, 2: 32, 3: 53, 4: 78, 5: 106, 6: 134} +CAPACITY = dict(enumerate([ + 17, 32, 53, 78, 106, 134, 154, 192, 230, 271, 321, 367, 425, 458, 520, + 586, 644, 718, 792, 858, 929, 1003, 1091, 1171, 1273, 1367, 1465, 1528, + 1628, 1732, 1840, 1952, 2068, 2188, 2303, 2431, 2563, 2699, 2809, 2953, +], start=1)) alpha = lambda n: "".join(chr(65 + i % 26) for i in range(n)) @@ -133,8 +138,9 @@ CASES = [ bytes(range(1, 40)).decode("latin-1"), # non-printable payload ] # One payload that exactly fills each version, and one just under, so both the -# padded and unpadded paths are covered at every version -- including 6, which -# is the only one with more than one Reed-Solomon block. +# padded and unpadded paths are covered at every version: several Reed-Solomon +# blocks from 6, blocks of two lengths from 6 too, the version information +# from 7, the 16-bit character count from 10. for v, cap in CAPACITY.items(): CASES.append(alpha(cap)) CASES.append(alpha(cap - 1)) diff --git a/tools/python/stats.py b/tools/python/stats.py index 7b96b38..57e73d5 100644 --- a/tools/python/stats.py +++ b/tools/python/stats.py @@ -23,7 +23,7 @@ import sys ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -FIRST_PARTY = ["kernel", "include", "boot", "lib", "cmd", "tools"] +FIRST_PARTY = ["kernel", "include", "boot", "lib", "cmd", "tools", "ldls", "etc"] LANGS = { ".c": "C", ".h": "C", ".S": "Assembly", ".s": "Assembly", ".asm": "Assembly", ".py": "Python", ".sh": "Shell", ".ps1": "PowerShell", ".pl": "Perl", diff --git a/tools/qrtest.c b/tools/qrtest.c index ba2b64f..65ecee2 100644 --- a/tools/qrtest.c +++ b/tools/qrtest.c @@ -155,25 +155,26 @@ check(int ok, const char *what) } // The version table is the one part of qr.c that is transcription rather than -// reasoning, so check what can be checked: block sizes must divide evenly, and -// the symbol must have room for exactly the codewords claimed. +// reasoning, so check what can be checked: the data left over is positive and +// the blocks are no more than one codeword apart, and the symbol has room for +// exactly the codewords the geometry formula claims. static void check_tables(void) { printf("version table\n"); for (int v = 1; v <= QR_MAX_VERSION; v++) { - int total = vinfo[v].total; - int data = total - vinfo[v].ecc * vinfo[v].blocks; + int total = total_codewords(v); + int data = data_codewords(v); char msg[128]; - snprintf(msg, sizeof msg, "v%d: %d data codewords divide into %d blocks", - v, data, vinfo[v].blocks); - check(data > 0 && data % vinfo[v].blocks == 0, msg); + snprintf(msg, sizeof msg, "v%d: %d data codewords in %d blocks", + v, data, ecc_blocks[v]); + check(data > 0 && data / ecc_blocks[v] > 0, msg); // Count the modules a symbol of this version actually leaves for data, - // by building one and asking. If the table's codeword count disagrees + // by building one and asking. If the formula's codeword count disagrees // with the geometry, one of the two is wrong. - struct qr q; + static struct qr q; memset(&q, 0, sizeof q); q.size = 17 + 4 * v; draw_function_patterns(&q); @@ -219,7 +220,7 @@ static void check_encode(void) { printf("encode\n"); - struct qr q; + static struct qr q; check(qr_encode(&q, (const unsigned char *)"HELLO", 5) == 0, "short payload encodes"); check(q.size == 21, "and picks version 1"); @@ -227,7 +228,9 @@ check_encode(void) unsigned char big[QR_MAX_BYTES]; memset(big, 'A', sizeof big); check(qr_encode(&q, big, QR_MAX_BYTES) == 0, "maximum payload encodes"); - check(q.size == 41, "and picks version 6"); + check(q.size == QR_MAX_SIZE, "and picks version 40"); + check(qr_capacity(40) == 2953 && qr_capacity(10) == 271 && qr_capacity(9) == 230, + "capacities match the standard at v9, v10 and v40"); check(qr_encode(&q, big, QR_MAX_BYTES + 1) == -1, "one byte over is refused"); // Every function module must still be a function module after masking -- @@ -270,7 +273,7 @@ main(int argc, char **argv) // codeword two encoders disagree about is the difference between a bug you // can see and one you can only stare at. if (argc > 2 && strcmp(argv[1], "-f") == 0) { - struct qr q; + static struct qr q; int n = (int)strlen(argv[2]); if (n > QR_MAX_BYTES || qr_encode(&q, (const unsigned char *)argv[2], n) != 0) return 1; @@ -283,7 +286,7 @@ main(int argc, char **argv) } if (argc > 2 && strcmp(argv[1], "-m") == 0) { - struct qr q; + static struct qr q; int n = (int)strlen(argv[2]); if (n > QR_MAX_BYTES || qr_encode(&q, (const unsigned char *)argv[2], n) != 0) return 1; @@ -308,7 +311,7 @@ main(int argc, char **argv) check_rs(); check_encode(); - struct qr q; + static struct qr q; int len = (int)strlen(text); if (len > QR_MAX_BYTES) { fprintf(stderr, "qrtest: payload is %d bytes, max %d\n", len, QR_MAX_BYTES); diff --git a/write.sh b/write.sh index 1740a71..db21b0d 100644 --- a/write.sh +++ b/write.sh @@ -3,6 +3,7 @@ # # ./write.sh sdc or ./write.sh /dev/sdc # ./write.sh sdc other.iso to write a different image +# ./write.sh sdc --no-verify skip the read-back check (halves the time) # # Shows what is on the device, asks you to type the device's name back, then # asks for your password and writes. Everything on the device is destroyed; @@ -26,7 +27,7 @@ fi die() { printf '%swrite.sh:%s %s\n' "$R" "$N" "$*" >&2; exit 1; } usage() { - echo "usage: ./write.sh [image.iso] [--any-disk]" + echo "usage: ./write.sh [image.iso] [--any-disk] [--no-verify]" echo " ./write.sh sdc writes schos.iso to /dev/sdc" echo echo "Removable disks on this machine:" @@ -36,11 +37,13 @@ usage() { } ANY=0 +VERIFY=1 DEV= ISO= for a in "$@"; do case "$a" in --any-disk) ANY=1 ;; + --no-verify) VERIFY=0 ;; -h|--help) usage ;; *) if [ -z "$DEV" ]; then DEV=$a; elif [ -z "$ISO" ]; then ISO=$a; else usage; fi ;; esac @@ -127,12 +130,22 @@ sync # --- check ------------------------------------------------------------------ # Read it back. A stick that takes the write and keeps none of it is common -# enough among cheap ones to be worth thirty seconds. -echo "Checking what was written..." -if sudo cmp -n "$ISO_BYTES" "$ISO" "$DEV"; then - echo "${G}Done.${N} $DEV holds $(basename "$ISO") and can be removed." +# enough among cheap ones to be worth the time. +# +# iflag=direct and 4 MB reads: a plain `cmp ISO /dev/sdX` reads through the page +# cache in small requests, which a USB stick serves slowly -- and may be answered +# from the cache rather than from the stick, checking nothing. Direct reads +# check the stick itself, and at the speed it can actually deliver. +if [ "$VERIFY" = 1 ]; then + echo "Checking what was written..." + BLOCKS=$(( (ISO_BYTES + 4194303) / 4194304 )) + if sudo dd if="$DEV" bs=4M count="$BLOCKS" iflag=direct status=none | cmp -n "$ISO_BYTES" - "$ISO"; then + echo "${G}Done.${N} $DEV holds $(basename "$ISO") and can be removed." + else + die "the device does not read back what was written -- the stick may be failing" + fi else - die "the device does not read back what was written -- the stick may be failing" + echo "${G}Done.${N} $DEV holds $(basename "$ISO") (not verified) and can be removed." fi # The kernel re-reads the partition table so the new layout shows up here